feat(abaqus-subset-completion): step 0 — abaqus-input-contract
This commit is contained in:
@@ -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
@@ -46,4 +46,4 @@
|
||||
"status": "pending"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ add_test(
|
||||
)
|
||||
|
||||
add_executable(fesa_abaqus_parser_tests
|
||||
unit/io/abaqus/input_contract_test.cpp
|
||||
unit/io/abaqus/parser_test.cpp
|
||||
)
|
||||
|
||||
@@ -147,6 +148,14 @@ add_test(
|
||||
--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
|
||||
integration/io/minimal_deck_to_domain_test.cpp
|
||||
)
|
||||
|
||||
Vendored
+54
@@ -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 - - - - - - - - - - -
|
||||
|
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
*STEP
|
||||
*STATIC
|
||||
@@ -0,0 +1 @@
|
||||
*HEADING, NAME=Unsupported
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
*NSET, NSET=First
|
||||
Second
|
||||
*NSET, NSET=Second
|
||||
First
|
||||
*STEP
|
||||
*STATIC
|
||||
*END STEP
|
||||
@@ -0,0 +1,6 @@
|
||||
*NODE
|
||||
1, 0.0, 0.0, 0.0
|
||||
1, 1.0, 0.0, 0.0
|
||||
*STEP
|
||||
*STATIC
|
||||
*END STEP
|
||||
@@ -0,0 +1,5 @@
|
||||
*NSET
|
||||
1
|
||||
*STEP
|
||||
*STATIC
|
||||
*END STEP
|
||||
@@ -0,0 +1,4 @@
|
||||
*STEP
|
||||
*STATIC
|
||||
*OUTPUT, FIELD, VARIABLE=ALL
|
||||
*END STEP
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
*STEP
|
||||
*STATIC
|
||||
1.0, -1.0
|
||||
*END STEP
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user