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.
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
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