feat(abaqus-subset-completion): step 0 — abaqus-input-contract

This commit is contained in:
KOKO\Mimi
2026-08-01 02:24:24 +09:00
parent 18361e4eb2
commit 2b83b1128f
36 changed files with 938 additions and 1 deletions
+298
View File
@@ -0,0 +1,298 @@
# FESA Phase 1 Abaqus Input Subset
## 1. Status and scope
This document is the normative input contract for the FESA Phase 1 Abaqus
adapter. FESA accepts only the keywords, parameters, scopes, and data forms
defined here. It does not implement general Abaqus input syntax, and it never
silently ignores an unknown keyword or option.
Two mutually exclusive organizations are supported:
- a flat/orphan mesh whose mesh and set records are in global scope; or
- one or more `*PART` definitions followed by exactly one `*ASSEMBLY` that
contains exactly one untransformed `*INSTANCE` of the active Part.
Only the active Part is normalized into `Domain`. Names and external labels are
input identity; generated nonnegative FESA IDs and dense indices are separate.
FESA performs no unit conversion.
`tests/fixtures/abaqus/contract.tsv` is the executable valid/invalid fixture
matrix for this document. Its expected diagnostic code and line are part of
the contract. A diagnostic for a bad data row points to that row; a keyword,
parameter, or scope error points to the keyword row.
## 2. Common lexical rules
- Input is UTF-8. A UTF-8 BOM is accepted only at the start of the file.
- Blank lines and lines whose first non-whitespace characters are `**` are
ignored. A comment or blank line does not end the current keyword record.
- Keyword names, parameter names, flag parameters, and the enumerated values
named below are ASCII case-insensitive. Entity names are trimmed and then
matched exactly.
- Fields are comma-separated and surrounding ASCII whitespace is ignored.
- External node and element labels are positive signed 64-bit integers. DOF
numbers are integers 1 through 6. Real fields must be finite.
- A flag parameter has no `=` value. A valued parameter must have one nonempty
value. Duplicate parameters are `abaqus.syntax.duplicate_parameter`.
- Parameters not listed for a keyword are
`abaqus.syntax.unsupported_parameter`. Unknown keywords, including
`*INCLUDE`, are `abaqus.unsupported_keyword`.
- A non-comment data line without an open data-bearing keyword is
`abaqus.syntax.data_without_keyword`.
## 3. Scope and ordering
The parser maintains `global`, `part`, `assembly`, `instance`, and `step`
scope. `*STEP` is a global child scope: model-data records already opened in
global scope remain model data, while `*STATIC`, `*CLOAD`, `*RESTART`, and
`*OUTPUT` belong to the open Step.
The accepted ordering is:
```text
optional *HEADING and *PREPRINT
flat mesh, or one or more *PART blocks and one *ASSEMBLY block
global materials
optional global model-data *BOUNDARY
one *STEP
one *STATIC
optional *BOUNDARY and *CLOAD
optional no-op *RESTART and *OUTPUT
*END STEP
```
References are resolved after the complete deck is parsed. A material may
therefore follow the Part that uses it, and a nested set may refer to a set
declared later in the same scope.
## 4. Mesh and hierarchy keywords
### 4.1 `*NODE`
- Scope: flat global or Part; not Assembly, Instance, or Step.
- Parameters: none.
- Data: one or more `label, x, y, z` rows, with exactly four nonempty fields.
- Semantics: labels are unique in their mesh scope. Reuse in an inactive Part
is allowed because it is a different Part scope.
- Diagnostics: `abaqus.syntax.invalid_node_scope`,
`abaqus.semantic.invalid_node_data`, `abaqus.semantic.duplicate_node_label`.
### 4.2 `*ELEMENT`
- Scope: flat global or Part.
- Parameters: required `TYPE=B31`; optional `ELSET=<name>`; no others.
- Data: one or more `element_label, node_1_label, node_2_label` rows.
- Semantics: element labels are unique in their mesh scope. Both nodes must
exist in that scope. `ELSET=` adds every row to the named element set and
merges with an explicit set of the same name using sorted-unique membership.
- Diagnostics: `abaqus.syntax.invalid_element_scope`,
`abaqus.semantic.unsupported_element`,
`abaqus.semantic.invalid_element_data`,
`abaqus.semantic.duplicate_element_label`,
`abaqus.semantic.missing_node`.
### 4.3 Part delimiters
`*PART` is global-only, requires exactly `NAME=<name>`, and accepts no data.
Part names are unique. `*END PART` accepts no parameters or data and closes the
open Part. Nesting or a mismatched delimiter is invalid.
Diagnostics are `abaqus.syntax.invalid_part_scope`,
`abaqus.syntax.unexpected_end_part`, `abaqus.syntax.unclosed_part`, and
`abaqus.semantic.duplicate_part`.
### 4.4 Assembly delimiters
`*ASSEMBLY` is global-only, requires exactly `NAME=<name>`, and accepts no
data. Phase 1 accepts exactly one Assembly. `*END ASSEMBLY` has no parameters
or data and closes the open Assembly.
Diagnostics are `abaqus.syntax.invalid_assembly_scope`,
`abaqus.syntax.multiple_assemblies`,
`abaqus.syntax.unexpected_end_assembly`, and
`abaqus.syntax.unclosed_assembly`.
### 4.5 Instance delimiters
`*INSTANCE` is Assembly-only and requires exactly `NAME=<name>, PART=<name>`.
Phase 1 accepts exactly one Instance. No translation or rotation data and no
keyword record are allowed inside it. `*END INSTANCE` has no parameters or
data.
Diagnostics are `abaqus.syntax.invalid_instance_scope`,
`abaqus.syntax.instance_local_keyword`,
`abaqus.syntax.unexpected_end_instance`,
`abaqus.syntax.unclosed_instance`, `abaqus.semantic.instance_count`,
`abaqus.semantic.instance_transform`, and `abaqus.semantic.missing_part`.
A transform diagnostic points to the first transform data row.
Flat `*NODE`/`*ELEMENT` records combined with any Part/Assembly organization
are `abaqus.semantic.mixed_mesh_organization`.
## 5. Sets
`*NSET` and `*ELSET` are allowed in flat global, Part, or Assembly scope.
- Required parameter: respectively `NSET=<name>` or `ELSET=<name>`.
- Optional flag: `GENERATE`.
- Assembly scope additionally requires `INSTANCE=<active-instance-name>`.
`INSTANCE=` is forbidden in flat global and Part scope.
- Explicit data consists of comma-separated positive labels and/or names of
sets of the same kind and scope. Empty trailing fields are ignored.
- `GENERATE` data consists of exactly one `start, end, increment` row. All
values are positive integers, `start <= end`, and `(end-start)` is divisible
by `increment`.
- Repeated declarations of the same set merge. Nested references are resolved
independent of declaration order, cycles are rejected, and final membership
is deterministic sorted-unique.
- An Assembly set lifts active-Part local labels through its named Instance.
It cannot reference an inactive or unknown Instance.
Diagnostics are `abaqus.syntax.invalid_set_scope`,
`abaqus.semantic.invalid_generate`, `abaqus.semantic.set_cycle`,
`abaqus.semantic.missing_set_member`, and
`abaqus.semantic.wrong_instance`.
## 6. Material and Beam section
### 6.1 `*MATERIAL` and `*ELASTIC`
`*MATERIAL` is global-only, requires exactly `NAME=<name>`, and has no data.
Material names are unique. Its `*ELASTIC` child is global model data, has no
parameters, and has exactly one `young_modulus, poisson_ratio` row. Young's
modulus is finite and positive; Poisson's ratio is finite and satisfies
`-1 < nu < 0.5`. Temperature and field dependencies are not supported.
Diagnostics are `abaqus.syntax.invalid_material_scope`,
`abaqus.semantic.duplicate_material`,
`abaqus.semantic.elastic_without_material`,
`abaqus.semantic.missing_elastic`, and
`abaqus.semantic.invalid_elastic_data`.
### 6.2 `*BEAM GENERAL SECTION`
- Scope: flat global or Part.
- Parameters: required `SECTION=GENERAL`, `ELSET=<name>`, and
`MATERIAL=<name>`; no others.
- First data row: exactly `A, I_y, I_yz, I_z, J`. `A`, `I_y`, `I_z`, and `J`
are finite and positive; `I_yz` must be finite and exactly zero for Phase 1.
- Second data row: exactly three finite components of the local section-axis
reference direction. Model validation rejects a zero direction or one
parallel to an assigned element axis.
- The material and set may be declared later, but must resolve. Every active
B31 element has exactly one section assignment.
Diagnostics are `abaqus.syntax.invalid_section_scope`,
`abaqus.semantic.unsupported_section`,
`abaqus.semantic.invalid_section_data`,
`abaqus.semantic.missing_material`,
`abaqus.semantic.missing_element_set`,
`abaqus.semantic.missing_section`, and
`abaqus.semantic.duplicate_section_assignment`.
### 6.3 `*TRANSVERSE SHEAR STIFFNESS`
This optional record is in the same flat-global or Part scope as, and must
immediately follow, the affected `*BEAM GENERAL SECTION`. It has no parameters
and exactly one `K23, K13, SCF` data row. `K23` and `K13` are finite and
positive. Phase 1 accepts only numeric `SCF=0`; omitted/default `0.25`, nonzero
values, and the Abaqus `SCF` label are unsupported.
For isotropic `G=E/[2(1+nu)]`, FESA stores
`A_sy=K23/G` and `A_sz=K13/G` with source `input`. If this keyword is absent,
the semantic mapper stores `A_sy=A_sz=5A/6`, `SCF=0`, with source
`phase1_default`.
The data order follows the Abaqus 2024
[*TRANSVERSE SHEAR STIFFNESS* reference](https://docs.software.vt.edu/abaqusv2024/English/SIMACAEKEYRefMap/simakey-r-transverseshearstiffness.htm);
the restriction to numeric zero SCF and the effective-area mapping are FESA
Phase 1 decisions.
Diagnostics are `abaqus.syntax.invalid_transverse_shear_scope`,
`abaqus.semantic.orphan_transverse_shear`,
`abaqus.semantic.invalid_transverse_shear_data`, and
`abaqus.semantic.nonzero_scf`.
## 7. Linear static step, BC, and load
### 7.1 `*BOUNDARY`
`*BOUNDARY` is allowed as global model data before the Step or inside the sole
Step. It has no parameters. Each row is
`node-or-nset, first_dof[, last_dof[, value]]`. `last_dof` defaults to
`first_dof`; value defaults to zero. The inclusive DOF range is 1 through 6.
Repeated identical prescriptions are deduplicated; differing values for one
node/DOF are rejected.
Diagnostics are `abaqus.syntax.invalid_boundary_scope`,
`abaqus.semantic.invalid_boundary_data`,
`abaqus.semantic.invalid_dof`, `abaqus.semantic.invalid_dof_range`,
`abaqus.semantic.missing_node_target`, and
`abaqus.semantic.conflicting_boundary`.
### 7.2 `*CLOAD`
`*CLOAD` is Step-only and has no parameters. Each row is exactly
`node-or-nset, dof, magnitude`; DOF is 1 through 6 and magnitude is finite.
Loads on the same node/DOF are summed in input order after target resolution.
Diagnostics are `abaqus.syntax.invalid_cload_scope`,
`abaqus.semantic.invalid_cload_data`, `abaqus.semantic.invalid_dof`, and
`abaqus.semantic.missing_node_target`.
### 7.3 `*STEP`, `*STATIC`, and `*END STEP`
`*STEP` is global-only. Optional parameters are `NAME=<name>` and
`NLGEOM=NO`; omitted name becomes `Step-1`. Exactly one Step is required.
`NLGEOM=YES` and every other option are unsupported.
Exactly one `*STATIC` occurs inside the Step. It has no parameters and accepts
either no data row or one row of one through four finite positive values
`initial_increment[, time_period[, minimum_increment[, maximum_increment]]]`.
The values are accepted as load-step metadata; Phase 1 performs one linear
solve.
`*END STEP` has no parameters or data and closes the Step.
Diagnostics are `abaqus.syntax.invalid_step_scope`,
`abaqus.syntax.unexpected_end_step`, `abaqus.syntax.unclosed_step`,
`abaqus.semantic.step_count`, `abaqus.semantic.unsupported_step_option`,
`abaqus.semantic.missing_static`, and
`abaqus.semantic.invalid_static_data`.
## 8. Recognized no-op directives
These records are deliberately recognized and do not create Domain entities:
- `*HEADING`: global-only, no parameters, zero or more text data rows.
- `*PREPRINT`: global-only, optional `ECHO`, `MODEL`, `HISTORY`, and `CONTACT`
parameters, each with value `YES` or `NO`; no data.
- `*RESTART`: Step-only, optional `WRITE` flag and optional nonnegative integer
`FREQUENCY`; no data.
- `*OUTPUT`: Step-only, exactly one `FIELD` or `HISTORY` flag and optional
`VARIABLE=PRESELECT`; no data.
Diagnostics are the common unsupported-parameter diagnostic plus
`abaqus.syntax.invalid_heading_scope`,
`abaqus.syntax.invalid_preprint_scope`,
`abaqus.syntax.invalid_restart_scope`, and
`abaqus.syntax.invalid_output_scope`. There is no general no-op or
ignore-unknown path.
## 9. Fixture matrix contract
The tab-separated manifest columns are:
```text
case_id, outcome, fixture, expected_stage, expected_code, expected_line,
node_count, element_count, node_set_count, element_set_count,
prescribed_dof_count, nodal_load_count, shear_source, shear_area_y,
shear_area_z, checked_node_set, checked_element_set
```
For a valid case, the public `parse_deck()` and `map_deck_to_domain()` path must
produce a Domain matching every populated expected field. Set checks use
`name:local-label,...`. For an invalid case, that same public path must fail
and contain the exact stage, code, and source line in the manifest. Partial
decks and test-only semantic construction are not accepted.
+1 -1
View File
@@ -46,4 +46,4 @@
"status": "pending" "status": "pending"
} }
] ]
} }
+9
View File
@@ -118,6 +118,7 @@ add_test(
) )
add_executable(fesa_abaqus_parser_tests add_executable(fesa_abaqus_parser_tests
unit/io/abaqus/input_contract_test.cpp
unit/io/abaqus/parser_test.cpp unit/io/abaqus/parser_test.cpp
) )
@@ -147,6 +148,14 @@ add_test(
--gtest_filter=ScopedDeck.* --gtest_filter=ScopedDeck.*
) )
add_test(
NAME AbaqusInputContract
COMMAND "$<TARGET_FILE:fesa_abaqus_parser_tests>"
--gtest_filter=AbaqusInputContract/*
)
# Steps 1-4 make this normative matrix pass, then remove WILL_FAIL.
set_tests_properties(AbaqusInputContract PROPERTIES WILL_FAIL TRUE)
add_executable(fesa_deck_to_domain_tests add_executable(fesa_deck_to_domain_tests
integration/io/minimal_deck_to_domain_test.cpp integration/io/minimal_deck_to_domain_test.cpp
) )
+54
View File
@@ -0,0 +1,54 @@
# case_id outcome fixture expected_stage expected_code expected_line node_count element_count node_set_count element_set_count prescribed_dof_count nodal_load_count shear_source shear_area_y shear_area_z checked_node_set checked_element_set
node_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
element_b31_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
nset_explicit_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
elset_explicit_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
material_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
elastic_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
beam_general_section_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
boundary_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
cload_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
step_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
static_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
end_step_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
part_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
end_part_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
assembly_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
end_assembly_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
instance_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
end_instance_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
assembly_nset_instance_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
generate_nset_valid valid valid/generated_nested_sets.inp - - 0 3 2 2 2 18 1 phase1_default 0.8333333333333334 0.8333333333333334 AllNodes:1,2,3 AllElements:1,2
generate_elset_valid valid valid/generated_nested_sets.inp - - 0 3 2 2 2 18 1 phase1_default 0.8333333333333334 0.8333333333333334 AllNodes:1,2,3 AllElements:1,2
nested_sets_valid valid valid/generated_nested_sets.inp - - 0 3 2 2 2 18 1 phase1_default 0.8333333333333334 0.8333333333333334 AllNodes:1,2,3 AllElements:1,2
transverse_shear_valid valid valid/explicit_transverse_shear.inp - - 0 2 1 1 1 6 1 input 0.8 0.5 Fixed:1 Beam:1
heading_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
preprint_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
restart_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
output_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
node_duplicate_invalid invalid invalid/node_duplicate.inp semantic abaqus.semantic.duplicate_node_label 3 - - - - - - - - - - -
element_type_invalid invalid invalid/element_wrong_type.inp semantic abaqus.semantic.unsupported_element 4 - - - - - - - - - - -
part_unclosed_invalid invalid invalid/part_unclosed.inp syntax abaqus.syntax.unclosed_part 1 - - - - - - - - - - -
assembly_multiple_invalid invalid invalid/assembly_multiple.inp syntax abaqus.syntax.multiple_assemblies 3 - - - - - - - - - - -
instance_transform_invalid invalid invalid/instance_transform.inp semantic abaqus.semantic.instance_transform 5 - - - - - - - - - - -
instance_local_mesh_invalid invalid invalid/instance_local_mesh.inp syntax abaqus.syntax.instance_local_keyword 5 - - - - - - - - - - -
mixed_mesh_invalid invalid invalid/mixed_mesh.inp semantic abaqus.semantic.mixed_mesh_organization 1 - - - - - - - - - - -
nset_parameter_invalid invalid invalid/nset_missing_name.inp semantic abaqus.semantic.missing_parameter 1 - - - - - - - - - - -
elset_generate_invalid invalid invalid/elset_invalid_generate.inp semantic abaqus.semantic.invalid_generate 2 - - - - - - - - - - -
nested_set_cycle_invalid invalid invalid/nested_set_cycle.inp semantic abaqus.semantic.set_cycle 4 - - - - - - - - - - -
set_instance_invalid invalid invalid/assembly_set_wrong_instance.inp semantic abaqus.semantic.wrong_instance 6 - - - - - - - - - - -
material_missing_elastic_invalid invalid invalid/material_missing_elastic.inp semantic abaqus.semantic.missing_elastic 1 - - - - - - - - - - -
elastic_data_invalid invalid invalid/elastic_invalid_data.inp semantic abaqus.semantic.invalid_elastic_data 3 - - - - - - - - - - -
section_type_invalid invalid invalid/section_wrong_type.inp semantic abaqus.semantic.unsupported_section 4 - - - - - - - - - - -
section_duplicate_invalid invalid invalid/section_duplicate_assignment.inp semantic abaqus.semantic.duplicate_section_assignment 7 - - - - - - - - - - -
transverse_scf_invalid invalid invalid/transverse_nonzero_scf.inp semantic abaqus.semantic.nonzero_scf 8 - - - - - - - - - - -
boundary_conflict_invalid invalid invalid/boundary_conflict.inp semantic abaqus.semantic.conflicting_boundary 10 - - - - - - - - - - -
cload_dof_invalid invalid invalid/cload_invalid_dof.inp semantic abaqus.semantic.invalid_dof 6 - - - - - - - - - - -
step_multiple_invalid invalid invalid/step_multiple.inp semantic abaqus.semantic.step_count 4 - - - - - - - - - - -
static_data_invalid invalid invalid/static_invalid_data.inp semantic abaqus.semantic.invalid_static_data 3 - - - - - - - - - - -
end_step_missing_invalid invalid invalid/end_step_missing.inp syntax abaqus.syntax.unclosed_step 1 - - - - - - - - - - -
heading_parameter_invalid invalid invalid/heading_invalid_parameter.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
preprint_parameter_invalid invalid invalid/preprint_invalid_parameter.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
restart_scope_invalid invalid invalid/restart_outside_step.inp syntax abaqus.syntax.invalid_restart_scope 1 - - - - - - - - - - -
output_parameter_invalid invalid invalid/output_invalid_parameter.inp syntax abaqus.syntax.unsupported_parameter 3 - - - - - - - - - - -
unknown_keyword_invalid invalid invalid/unknown_include.inp syntax abaqus.unsupported_keyword 1 - - - - - - - - - - -
1 # case_id outcome fixture expected_stage expected_code expected_line node_count element_count node_set_count element_set_count prescribed_dof_count nodal_load_count shear_source shear_area_y shear_area_z checked_node_set checked_element_set
2 node_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
3 element_b31_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
4 nset_explicit_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
5 elset_explicit_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
6 material_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
7 elastic_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
8 beam_general_section_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
9 boundary_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
10 cload_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
11 step_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
12 static_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
13 end_step_valid valid valid/flat_complete.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
14 part_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
15 end_part_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
16 assembly_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
17 end_assembly_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
18 instance_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
19 end_instance_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
20 assembly_nset_instance_valid valid valid/hierarchical_complete.inp - - 0 2 1 2 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
21 generate_nset_valid valid valid/generated_nested_sets.inp - - 0 3 2 2 2 18 1 phase1_default 0.8333333333333334 0.8333333333333334 AllNodes:1,2,3 AllElements:1,2
22 generate_elset_valid valid valid/generated_nested_sets.inp - - 0 3 2 2 2 18 1 phase1_default 0.8333333333333334 0.8333333333333334 AllNodes:1,2,3 AllElements:1,2
23 nested_sets_valid valid valid/generated_nested_sets.inp - - 0 3 2 2 2 18 1 phase1_default 0.8333333333333334 0.8333333333333334 AllNodes:1,2,3 AllElements:1,2
24 transverse_shear_valid valid valid/explicit_transverse_shear.inp - - 0 2 1 1 1 6 1 input 0.8 0.5 Fixed:1 Beam:1
25 heading_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
26 preprint_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
27 restart_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
28 output_noop_valid valid valid/noop_directives.inp - - 0 2 1 1 1 6 1 phase1_default 0.8333333333333334 0.8333333333333334 Fixed:1 Beam:1
29 node_duplicate_invalid invalid invalid/node_duplicate.inp semantic abaqus.semantic.duplicate_node_label 3 - - - - - - - - - - -
30 element_type_invalid invalid invalid/element_wrong_type.inp semantic abaqus.semantic.unsupported_element 4 - - - - - - - - - - -
31 part_unclosed_invalid invalid invalid/part_unclosed.inp syntax abaqus.syntax.unclosed_part 1 - - - - - - - - - - -
32 assembly_multiple_invalid invalid invalid/assembly_multiple.inp syntax abaqus.syntax.multiple_assemblies 3 - - - - - - - - - - -
33 instance_transform_invalid invalid invalid/instance_transform.inp semantic abaqus.semantic.instance_transform 5 - - - - - - - - - - -
34 instance_local_mesh_invalid invalid invalid/instance_local_mesh.inp syntax abaqus.syntax.instance_local_keyword 5 - - - - - - - - - - -
35 mixed_mesh_invalid invalid invalid/mixed_mesh.inp semantic abaqus.semantic.mixed_mesh_organization 1 - - - - - - - - - - -
36 nset_parameter_invalid invalid invalid/nset_missing_name.inp semantic abaqus.semantic.missing_parameter 1 - - - - - - - - - - -
37 elset_generate_invalid invalid invalid/elset_invalid_generate.inp semantic abaqus.semantic.invalid_generate 2 - - - - - - - - - - -
38 nested_set_cycle_invalid invalid invalid/nested_set_cycle.inp semantic abaqus.semantic.set_cycle 4 - - - - - - - - - - -
39 set_instance_invalid invalid invalid/assembly_set_wrong_instance.inp semantic abaqus.semantic.wrong_instance 6 - - - - - - - - - - -
40 material_missing_elastic_invalid invalid invalid/material_missing_elastic.inp semantic abaqus.semantic.missing_elastic 1 - - - - - - - - - - -
41 elastic_data_invalid invalid invalid/elastic_invalid_data.inp semantic abaqus.semantic.invalid_elastic_data 3 - - - - - - - - - - -
42 section_type_invalid invalid invalid/section_wrong_type.inp semantic abaqus.semantic.unsupported_section 4 - - - - - - - - - - -
43 section_duplicate_invalid invalid invalid/section_duplicate_assignment.inp semantic abaqus.semantic.duplicate_section_assignment 7 - - - - - - - - - - -
44 transverse_scf_invalid invalid invalid/transverse_nonzero_scf.inp semantic abaqus.semantic.nonzero_scf 8 - - - - - - - - - - -
45 boundary_conflict_invalid invalid invalid/boundary_conflict.inp semantic abaqus.semantic.conflicting_boundary 10 - - - - - - - - - - -
46 cload_dof_invalid invalid invalid/cload_invalid_dof.inp semantic abaqus.semantic.invalid_dof 6 - - - - - - - - - - -
47 step_multiple_invalid invalid invalid/step_multiple.inp semantic abaqus.semantic.step_count 4 - - - - - - - - - - -
48 static_data_invalid invalid invalid/static_invalid_data.inp semantic abaqus.semantic.invalid_static_data 3 - - - - - - - - - - -
49 end_step_missing_invalid invalid invalid/end_step_missing.inp syntax abaqus.syntax.unclosed_step 1 - - - - - - - - - - -
50 heading_parameter_invalid invalid invalid/heading_invalid_parameter.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
51 preprint_parameter_invalid invalid invalid/preprint_invalid_parameter.inp syntax abaqus.syntax.unsupported_parameter 1 - - - - - - - - - - -
52 restart_scope_invalid invalid invalid/restart_outside_step.inp syntax abaqus.syntax.invalid_restart_scope 1 - - - - - - - - - - -
53 output_parameter_invalid invalid invalid/output_invalid_parameter.inp syntax abaqus.syntax.unsupported_parameter 3 - - - - - - - - - - -
54 unknown_keyword_invalid invalid invalid/unknown_include.inp syntax abaqus.unsupported_keyword 1 - - - - - - - - - - -
+4
View File
@@ -0,0 +1,4 @@
*ASSEMBLY, NAME=First
*END ASSEMBLY
*ASSEMBLY, NAME=Second
*END ASSEMBLY
@@ -0,0 +1,8 @@
*PART, NAME=BeamPart
*END PART
*ASSEMBLY, NAME=Root
*INSTANCE, NAME=Beam-1, PART=BeamPart
*END INSTANCE
*NSET, NSET=Fixed, INSTANCE=Other
1
*END ASSEMBLY
+11
View File
@@ -0,0 +1,11 @@
*NODE
1, 0.0, 0.0, 0.0
*NSET, NSET=Fixed
1
*STEP
*STATIC
*BOUNDARY
Fixed, 1, 1, 0.0
*BOUNDARY
Fixed, 1, 1, 1.0
*END STEP
+7
View File
@@ -0,0 +1,7 @@
*NODE
1, 0.0, 0.0, 0.0
*STEP
*STATIC
*CLOAD
1, 7, 1.0
*END STEP
@@ -0,0 +1,6 @@
*MATERIAL, NAME=Steel
*ELASTIC
-1.0, 0.25
*STEP
*STATIC
*END STEP
+8
View File
@@ -0,0 +1,8 @@
*NODE
1, 0.0, 0.0, 0.0
2, 1.0, 0.0, 0.0
*ELEMENT, TYPE=B32, ELSET=Beam
1, 1, 2
*STEP
*STATIC
*END STEP
@@ -0,0 +1,5 @@
*ELSET, ELSET=Beam, GENERATE
3, 1, 1
*STEP
*STATIC
*END STEP
+2
View File
@@ -0,0 +1,2 @@
*STEP
*STATIC
@@ -0,0 +1 @@
*HEADING, NAME=Unsupported
+8
View File
@@ -0,0 +1,8 @@
*PART, NAME=BeamPart
*END PART
*ASSEMBLY, NAME=Root
*INSTANCE, NAME=Beam-1, PART=BeamPart
*NODE
1, 0.0, 0.0, 0.0
*END INSTANCE
*END ASSEMBLY
+7
View File
@@ -0,0 +1,7 @@
*PART, NAME=BeamPart
*END PART
*ASSEMBLY, NAME=Root
*INSTANCE, NAME=Beam-1, PART=BeamPart
1.0, 2.0, 3.0
*END INSTANCE
*END ASSEMBLY
@@ -0,0 +1,4 @@
*MATERIAL, NAME=Steel
*STEP
*STATIC
*END STEP
+8
View File
@@ -0,0 +1,8 @@
*NODE
1, 0.0, 0.0, 0.0
*PART, NAME=BeamPart
*END PART
*ASSEMBLY, NAME=Root
*INSTANCE, NAME=Beam-1, PART=BeamPart
*END INSTANCE
*END ASSEMBLY
+7
View File
@@ -0,0 +1,7 @@
*NSET, NSET=First
Second
*NSET, NSET=Second
First
*STEP
*STATIC
*END STEP
+6
View File
@@ -0,0 +1,6 @@
*NODE
1, 0.0, 0.0, 0.0
1, 1.0, 0.0, 0.0
*STEP
*STATIC
*END STEP
+5
View File
@@ -0,0 +1,5 @@
*NSET
1
*STEP
*STATIC
*END STEP
@@ -0,0 +1,4 @@
*STEP
*STATIC
*OUTPUT, FIELD, VARIABLE=ALL
*END STEP
+3
View File
@@ -0,0 +1,3 @@
*PART, NAME=BeamPart
*NODE
1, 0.0, 0.0, 0.0
@@ -0,0 +1 @@
*PREPRINT, COLOR=YES
@@ -0,0 +1 @@
*RESTART, WRITE, FREQUENCY=0
@@ -0,0 +1,12 @@
*MATERIAL, NAME=Steel
*ELASTIC
200.0, 0.25
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
1.0, 1.0, 0.0, 1.0, 1.0
0.0, 1.0, 0.0
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
1.0, 2.0, 0.0, 2.0, 1.0
0.0, 1.0, 0.0
*STEP
*STATIC
*END STEP
+9
View File
@@ -0,0 +1,9 @@
*MATERIAL, NAME=Steel
*ELASTIC
200.0, 0.25
*BEAM GENERAL SECTION, SECTION=ARBITRARY, ELSET=Beam, MATERIAL=Steel
1.0, 1.0, 0.0, 1.0, 1.0
0.0, 1.0, 0.0
*STEP
*STATIC
*END STEP
+4
View File
@@ -0,0 +1,4 @@
*STEP
*STATIC
1.0, -1.0
*END STEP
+6
View File
@@ -0,0 +1,6 @@
*STEP, NAME=First
*STATIC
*END STEP
*STEP, NAME=Second
*STATIC
*END STEP
@@ -0,0 +1,11 @@
*MATERIAL, NAME=Steel
*ELASTIC
200.0, 0.25
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
1.0, 1.0, 0.0, 1.0, 1.0
0.0, 1.0, 0.0
*TRANSVERSE SHEAR STIFFNESS
64.0, 40.0, 0.25
*STEP
*STATIC
*END STEP
+1
View File
@@ -0,0 +1 @@
*INCLUDE, INPUT=other.inp
@@ -0,0 +1,24 @@
*NODE
1, 0.0, 0.0, 0.0
2, 1.0, 0.0, 0.0
*ELEMENT, TYPE=B31, ELSET=Beam
1, 1, 2
*NSET, NSET=Fixed
1
*ELSET, ELSET=Beam
1
*MATERIAL, NAME=Steel
*ELASTIC
200.0, 0.25
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
1.0, 1.0, 0.0, 1.0, 1.0
0.0, 1.0, 0.0
*TRANSVERSE SHEAR STIFFNESS
64.0, 40.0, 0.0
*STEP
*STATIC
*BOUNDARY
Fixed, 1, 6
*CLOAD
2, 2, -1.0
*END STEP
+22
View File
@@ -0,0 +1,22 @@
*NODE
1, 0.0, 0.0, 0.0
2, 1.0, 0.0, 0.0
*ELEMENT, TYPE=B31, ELSET=Beam
1, 1, 2
*NSET, NSET=Fixed
1
*ELSET, ELSET=Beam
1
*MATERIAL, NAME=Steel
*ELASTIC
200.0, 0.25
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
1.0, 1.0, 0.0, 1.0, 1.0
0.0, 1.0, 0.0
*BOUNDARY
Fixed, 1, 6
*STEP, NAME=Load
*STATIC
*CLOAD
2, 2, -1.0
*END STEP
+28
View File
@@ -0,0 +1,28 @@
*NODE
1, 0.0, 0.0, 0.0
2, 1.0, 0.0, 0.0
3, 2.0, 0.0, 0.0
*ELEMENT, TYPE=B31
1, 1, 2
2, 2, 3
*NSET, NSET=FirstTwo, GENERATE
1, 2, 1
*NSET, NSET=AllNodes
FirstTwo, 3
*ELSET, ELSET=GeneratedElements, GENERATE
1, 2, 1
*ELSET, ELSET=AllElements
GeneratedElements
*MATERIAL, NAME=Steel
*ELASTIC
200.0, 0.25
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=AllElements, MATERIAL=Steel
1.0, 1.0, 0.0, 1.0, 1.0
0.0, 1.0, 0.0
*STEP
*STATIC
*BOUNDARY
AllNodes, 1, 6
*CLOAD
3, 2, -1.0
*END STEP
+31
View File
@@ -0,0 +1,31 @@
*PART, NAME=BeamPart
*NODE
1, 0.0, 0.0, 0.0
2, 1.0, 0.0, 0.0
*ELEMENT, TYPE=B31, ELSET=Beam
1, 1, 2
*ELSET, ELSET=Beam
1
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
1.0, 1.0, 0.0, 1.0, 1.0
0.0, 1.0, 0.0
*END PART
*ASSEMBLY, NAME=RootAssembly
*INSTANCE, NAME=Beam-1, PART=BeamPart
*END INSTANCE
*NSET, NSET=Fixed, INSTANCE=Beam-1
1
*NSET, NSET=Tip, INSTANCE=Beam-1
2
*END ASSEMBLY
*MATERIAL, NAME=Steel
*ELASTIC
200.0, 0.25
*STEP, NAME=Load, NLGEOM=NO
*STATIC
1.0, 1.0, 0.01, 1.0
*BOUNDARY
Fixed, 1, 6
*CLOAD
Tip, 2, -1.0
*END STEP
+28
View File
@@ -0,0 +1,28 @@
*HEADING
FESA recognized no-op heading
*PREPRINT, ECHO=NO, MODEL=NO, HISTORY=NO, CONTACT=NO
*NODE
1, 0.0, 0.0, 0.0
2, 1.0, 0.0, 0.0
*ELEMENT, TYPE=B31, ELSET=Beam
1, 1, 2
*NSET, NSET=Fixed
1
*ELSET, ELSET=Beam
1
*MATERIAL, NAME=Steel
*ELASTIC
200.0, 0.25
*BEAM GENERAL SECTION, SECTION=GENERAL, ELSET=Beam, MATERIAL=Steel
1.0, 1.0, 0.0, 1.0, 1.0
0.0, 1.0, 0.0
*STEP
*STATIC
*BOUNDARY
Fixed, 1, 6
*CLOAD
2, 2, -1.0
*RESTART, WRITE, FREQUENCY=0
*OUTPUT, FIELD, VARIABLE=PRESELECT
*OUTPUT, HISTORY, VARIABLE=PRESELECT
*END STEP
@@ -0,0 +1,294 @@
#include <fesa/io/abaqus/parser.hpp>
#include <fesa/io/abaqus/semantic_mapper.hpp>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <optional>
#include <ranges>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
namespace {
struct ContractCase final {
std::string id;
bool valid;
std::filesystem::path fixture;
std::optional<fesa::DiagnosticStage> expected_stage;
std::string expected_code;
std::size_t expected_line;
std::optional<std::size_t> node_count;
std::optional<std::size_t> element_count;
std::optional<std::size_t> node_set_count;
std::optional<std::size_t> element_set_count;
std::optional<std::size_t> prescribed_dof_count;
std::optional<std::size_t> nodal_load_count;
std::string shear_source;
std::optional<double> shear_area_y;
std::optional<double> shear_area_z;
std::string checked_node_set;
std::string checked_element_set;
};
struct ContractOutcome final {
std::optional<fesa::Domain> domain;
std::vector<fesa::Diagnostic> diagnostics;
};
std::vector<std::string> split(
const std::string_view text,
const char delimiter) {
std::vector<std::string> fields;
std::size_t first = 0;
while (true) {
const std::size_t next = text.find(delimiter, first);
fields.emplace_back(
text.substr(
first,
next == std::string_view::npos
? std::string_view::npos
: next - first));
if (next == std::string_view::npos) {
break;
}
first = next + 1U;
}
return fields;
}
std::optional<std::size_t> optional_size(const std::string& field) {
if (field == "-") {
return std::nullopt;
}
return static_cast<std::size_t>(std::stoull(field));
}
std::optional<double> optional_real(const std::string& field) {
if (field == "-") {
return std::nullopt;
}
return std::stod(field);
}
std::optional<fesa::DiagnosticStage> optional_stage(
const std::string& field) {
if (field == "-") {
return std::nullopt;
}
if (field == "io") {
return fesa::DiagnosticStage::io;
}
if (field == "syntax") {
return fesa::DiagnosticStage::syntax;
}
if (field == "semantic") {
return fesa::DiagnosticStage::semantic;
}
if (field == "model") {
return fesa::DiagnosticStage::model;
}
throw std::runtime_error{"Unknown diagnostic stage in contract manifest."};
}
std::vector<ContractCase> load_contract_cases() {
const std::filesystem::path path =
std::filesystem::path{FESA_TEST_SOURCE_DIR} / "fixtures" / "abaqus" /
"contract.tsv";
std::ifstream input{path, std::ios::binary};
if (!input) {
throw std::runtime_error{"Unable to open Abaqus contract manifest."};
}
std::vector<ContractCase> cases;
std::string line;
while (std::getline(input, line)) {
if (line.empty() || line.front() == '#') {
continue;
}
const std::vector<std::string> fields = split(line, '\t');
if (fields.size() != 17U) {
throw std::runtime_error{
"Abaqus contract manifest row must contain 17 fields."};
}
const bool valid = fields[1] == "valid";
if (!valid && fields[1] != "invalid") {
throw std::runtime_error{
"Abaqus contract outcome must be valid or invalid."};
}
cases.push_back({
fields[0],
valid,
fields[2],
optional_stage(fields[3]),
fields[4] == "-" ? std::string{} : fields[4],
static_cast<std::size_t>(std::stoull(fields[5])),
optional_size(fields[6]),
optional_size(fields[7]),
optional_size(fields[8]),
optional_size(fields[9]),
optional_size(fields[10]),
optional_size(fields[11]),
fields[12],
optional_real(fields[13]),
optional_real(fields[14]),
fields[15],
fields[16],
});
}
if (cases.empty()) {
throw std::runtime_error{"Abaqus contract manifest is empty."};
}
return cases;
}
ContractOutcome parse_and_map(const std::filesystem::path& path) {
auto parsed = fesa::parse_deck(path);
if (!parsed.deck.has_value()) {
return {std::nullopt, std::move(parsed.diagnostics)};
}
auto mapped = fesa::map_deck_to_domain(*parsed.deck);
return {std::move(mapped.domain), std::move(mapped.diagnostics)};
}
std::string diagnostics_text(
const std::vector<fesa::Diagnostic>& diagnostics) {
std::ostringstream output;
for (const auto& diagnostic : diagnostics) {
output << diagnostic.code;
if (diagnostic.source.has_value()) {
output << '@' << diagnostic.source->line;
}
output << '\n';
}
return output.str();
}
std::pair<std::string, std::vector<std::int64_t>> expected_set(
const std::string& field) {
const std::size_t colon = field.find(':');
if (colon == std::string::npos) {
throw std::runtime_error{"Set check must use name:label,... format."};
}
std::vector<std::int64_t> labels;
for (const std::string& label :
split(std::string_view{field}.substr(colon + 1U), ',')) {
labels.push_back(std::stoll(label));
}
return {field.substr(0, colon), std::move(labels)};
}
void expect_node_set(
const fesa::Domain& domain,
const std::string& field) {
if (field == "-") {
return;
}
const auto [name, expected_labels] = expected_set(field);
const auto found = std::ranges::find(
domain.node_sets(), name, &fesa::NodeSet::name);
ASSERT_NE(found, domain.node_sets().end());
std::vector<std::int64_t> actual_labels;
for (const fesa::NodeId member : found->members) {
actual_labels.push_back(domain.node(member).origin.local_label);
}
std::ranges::sort(actual_labels);
EXPECT_EQ(actual_labels, expected_labels);
}
void expect_element_set(
const fesa::Domain& domain,
const std::string& field) {
if (field == "-") {
return;
}
const auto [name, expected_labels] = expected_set(field);
const auto found = std::ranges::find(
domain.element_sets(), name, &fesa::ElementSet::name);
ASSERT_NE(found, domain.element_sets().end());
std::vector<std::int64_t> actual_labels;
for (const fesa::ElementId member : found->members) {
const auto element = std::ranges::find(
domain.beam_elements(), member, &fesa::BeamElement::id);
ASSERT_NE(element, domain.beam_elements().end());
actual_labels.push_back(element->origin.local_label);
}
std::ranges::sort(actual_labels);
EXPECT_EQ(actual_labels, expected_labels);
}
class AbaqusInputContractTest
: public testing::TestWithParam<ContractCase> {};
TEST_P(AbaqusInputContractTest, FixtureMatchesNormativeContract) {
const ContractCase& contract = GetParam();
const std::filesystem::path path =
std::filesystem::path{FESA_TEST_SOURCE_DIR} / "fixtures" / "abaqus" /
contract.fixture;
SCOPED_TRACE(contract.id);
ASSERT_TRUE(std::filesystem::is_regular_file(path));
const ContractOutcome outcome = parse_and_map(path);
if (!contract.valid) {
EXPECT_FALSE(outcome.domain.has_value());
ASSERT_TRUE(contract.expected_stage.has_value());
const auto diagnostic = std::ranges::find_if(
outcome.diagnostics,
[&contract](const fesa::Diagnostic& candidate) {
return candidate.stage == *contract.expected_stage &&
candidate.code == contract.expected_code &&
candidate.source.has_value() &&
candidate.source->line == contract.expected_line;
});
EXPECT_NE(diagnostic, outcome.diagnostics.end())
<< "Expected " << contract.expected_code << '@'
<< contract.expected_line << " but received:\n"
<< diagnostics_text(outcome.diagnostics);
return;
}
ASSERT_TRUE(outcome.domain.has_value())
<< diagnostics_text(outcome.diagnostics);
ASSERT_TRUE(outcome.diagnostics.empty())
<< diagnostics_text(outcome.diagnostics);
const fesa::Domain& domain = *outcome.domain;
EXPECT_EQ(domain.nodes().size(), *contract.node_count);
EXPECT_EQ(domain.beam_elements().size(), *contract.element_count);
EXPECT_EQ(domain.node_sets().size(), *contract.node_set_count);
EXPECT_EQ(domain.element_sets().size(), *contract.element_set_count);
EXPECT_EQ(
domain.step().prescribed_dofs.size(),
*contract.prescribed_dof_count);
EXPECT_EQ(domain.step().nodal_loads.size(), *contract.nodal_load_count);
ASSERT_EQ(domain.sections().size(), 1U);
const fesa::BeamSection& section = domain.sections().front();
const auto expected_source = contract.shear_source == "input"
? fesa::ShearPropertySource::input
: fesa::ShearPropertySource::phase1_default;
EXPECT_EQ(section.shear_source, expected_source);
EXPECT_NEAR(section.shear_area_y, *contract.shear_area_y, 1.0e-12);
EXPECT_NEAR(section.shear_area_z, *contract.shear_area_z, 1.0e-12);
expect_node_set(domain, contract.checked_node_set);
expect_element_set(domain, contract.checked_element_set);
}
INSTANTIATE_TEST_SUITE_P(
AbaqusInputContract,
AbaqusInputContractTest,
testing::ValuesIn(load_contract_cases()),
[](const testing::TestParamInfo<ContractCase>& info) {
return info.param.id;
});
} // namespace