Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b57b0bf63a |
@@ -0,0 +1,139 @@
|
|||||||
|
# Abaqus Input Parser I/O Definition
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
- feature_id: abaqus-input-parser
|
||||||
|
- status: ready-for-implementation-planning
|
||||||
|
- owner_agent: io-definition-agent
|
||||||
|
- date: 2026-06-12
|
||||||
|
|
||||||
|
## Abaqus Input Scope
|
||||||
|
|
||||||
|
FESA supports only the Abaqus input keyword subset listed here. This document
|
||||||
|
does not claim full Abaqus compatibility.
|
||||||
|
|
||||||
|
| keyword | support_status | level | required_parameters | mapped_internal_concept | unsupported-case behavior |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| `*HEADING` | supported | model | none | model title metadata | accepted; title storage may be deferred |
|
||||||
|
| `*NODE` | supported | model | none | node id and global coordinates | malformed rows are errors |
|
||||||
|
| `*ELEMENT` | supported | model | `TYPE` | element id, topology, connectivity | missing/unsupported `TYPE` is an error |
|
||||||
|
| `*NSET` | supported | model | `NSET` | named node set | missing name is an error |
|
||||||
|
| `*ELSET` | supported | model | `ELSET` | named element set | missing name is an error |
|
||||||
|
| `*MATERIAL` | supported | model | `NAME` | material identity | missing name is an error |
|
||||||
|
| `*ELASTIC` | supported | model | none | linear elastic constants | malformed rows are errors |
|
||||||
|
| section keyword | supported | model | `ELSET`, `MATERIAL` | property assignment | unsupported section type is an error |
|
||||||
|
| `*STEP` | supported | history | optional `NAME` | analysis step | malformed parameters are errors |
|
||||||
|
| `*STATIC` | supported | history | none | static procedure marker | unsupported procedures are errors |
|
||||||
|
| `*BOUNDARY` | supported | model/history | none | prescribed nodal dof values | malformed rows are errors |
|
||||||
|
| `*CLOAD` | supported | history | none | concentrated nodal load | malformed rows are errors |
|
||||||
|
| `*OUTPUT` | supported | history | none | output request root | unsupported options warn or error by context |
|
||||||
|
| `*NODE OUTPUT` | supported | history | none | nodal output request | unsupported quantities warn or error by context |
|
||||||
|
| `*ELEMENT OUTPUT` | supported | history | none | element output request | unsupported quantities warn or error by context |
|
||||||
|
|
||||||
|
Initial V0 element names are two-node line/bar types only: `T2D2`, `T3D2`,
|
||||||
|
`C3D2`, and `B31`.
|
||||||
|
|
||||||
|
## Syntax Policy
|
||||||
|
|
||||||
|
- Keywords and parameter names are case-insensitive.
|
||||||
|
- Keyword lines begin with `*`.
|
||||||
|
- Comment lines begin with `**` and are ignored.
|
||||||
|
- Blank lines are ignored.
|
||||||
|
- Fields are comma-separated. Leading and trailing whitespace around fields is
|
||||||
|
ignored.
|
||||||
|
- Empty required fields are parse errors.
|
||||||
|
- Unsupported keywords are errors unless this contract explicitly marks them as
|
||||||
|
ignored-with-warning.
|
||||||
|
- Diagnostics must include severity, a stable code, a human-readable message,
|
||||||
|
and enough line context to locate the input row.
|
||||||
|
- Include files are out of scope for this parser phase.
|
||||||
|
|
||||||
|
## Model Data Mapping
|
||||||
|
|
||||||
|
- Nodes map an Abaqus node label to a FESA node id and three global coordinate
|
||||||
|
components. Missing trailing coordinate components are interpreted as `0.0`
|
||||||
|
only when the keyword-specific test documents that behavior.
|
||||||
|
- Elements map an Abaqus element label, supported element type, and connectivity
|
||||||
|
labels to a FESA element. Section assignment supplies material/property
|
||||||
|
linkage in a later parser slice.
|
||||||
|
- Node sets and element sets preserve deterministic membership order.
|
||||||
|
- Materials map by Abaqus `NAME`. Linear elastic data maps to the semantic
|
||||||
|
material contract used by solver implementation.
|
||||||
|
- Section keywords bind an element set to a material and create the semantic
|
||||||
|
property assignment needed by the solver.
|
||||||
|
- The global coordinate system is assumed. Units are user-consistent and are not
|
||||||
|
converted by the parser.
|
||||||
|
|
||||||
|
## History Data Mapping
|
||||||
|
|
||||||
|
- `*STEP` begins an ordered analysis step. If no name is provided, the parser
|
||||||
|
assigns a deterministic step name or id.
|
||||||
|
- `*STATIC` marks the step as a linear static procedure for V0.
|
||||||
|
- `*BOUNDARY` maps node id, dof range, and value to prescribed boundary
|
||||||
|
conditions.
|
||||||
|
- `*CLOAD` maps node id, dof component, and magnitude to concentrated loads.
|
||||||
|
- Output request keywords define requested quantities when semantic storage
|
||||||
|
exists; unsupported quantities must not be silently accepted.
|
||||||
|
|
||||||
|
## Internal Model Contract
|
||||||
|
|
||||||
|
- `Domain` owns model definition data created from the input file.
|
||||||
|
- Parsed model objects should be treated as immutable after parsing where
|
||||||
|
practical.
|
||||||
|
- `AnalysisStep` owns step-local boundary conditions and loads.
|
||||||
|
- The parser must not store equation ids on nodes or elements.
|
||||||
|
- Parser diagnostics are part of the result; callers do not need to inspect
|
||||||
|
partial `Domain` state to detect failure.
|
||||||
|
|
||||||
|
## Output HDF5 Schema
|
||||||
|
|
||||||
|
The parser itself does not write HDF5. Solver output remains authoritative in
|
||||||
|
`results.h5` and follows the project HDF5 contract:
|
||||||
|
|
||||||
|
| quantity | dataset_path | location | component policy |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| displacement | `/steps/<step>/frames/<frame>/field_outputs/U` | nodal | `U1`, `U2`, `U3` as applicable |
|
||||||
|
| reaction | `/steps/<step>/frames/<frame>/field_outputs/RF` | nodal | `RF1`, `RF2`, `RF3` as applicable |
|
||||||
|
| internal force | `/steps/<step>/frames/<frame>/field_outputs/element_forces` | element | feature-specific |
|
||||||
|
| stress | `/steps/<step>/frames/<frame>/field_outputs/S` | integration point or element | feature-specific |
|
||||||
|
|
||||||
|
Required metadata includes schema version, model id, source input identity,
|
||||||
|
coordinate system, units policy, solver version, step/frame identity, and row
|
||||||
|
identity fields.
|
||||||
|
|
||||||
|
## FESA HDF5 To Reference CSV Comparison Schema
|
||||||
|
|
||||||
|
Reference comparison reads `results.h5` and matches deterministic rows against
|
||||||
|
Abaqus-generated CSV files under `reference/<model-id>/`. This parser phase does
|
||||||
|
not generate or modify those reference artifacts.
|
||||||
|
|
||||||
|
Common row identity:
|
||||||
|
|
||||||
|
- sort order: step, frame, entity id, location, component
|
||||||
|
- node id and element id are Abaqus labels preserved by the parser
|
||||||
|
- component names follow the HDF5 component policy
|
||||||
|
|
||||||
|
CSV files remain:
|
||||||
|
|
||||||
|
- `<model-id>_displacements.csv`
|
||||||
|
- `<model-id>_reactions.csv`
|
||||||
|
- `<model-id>_internalforces.csv`
|
||||||
|
- `<model-id>_stresses.csv`
|
||||||
|
|
||||||
|
## Validation Rules
|
||||||
|
|
||||||
|
- Duplicate node, element, material, property, set, or step labels are errors.
|
||||||
|
- Missing references are errors.
|
||||||
|
- Unsupported keywords are errors unless explicitly documented otherwise.
|
||||||
|
- Malformed numeric fields are errors.
|
||||||
|
- Parser unit tests must cover valid subset mapping and invalid subset
|
||||||
|
diagnostics before production parser changes.
|
||||||
|
- Workspace validation remains `python scripts/validate_workspace.py`.
|
||||||
|
|
||||||
|
## Downstream Handoff
|
||||||
|
|
||||||
|
Implementation planning should split work into mesh keyword parsing,
|
||||||
|
diagnostics, set/section mapping, material/history mapping, and integration
|
||||||
|
validation. Reference model work may later use this subset to prepare
|
||||||
|
`reference/<model-id>/model.inp`, but this phase must not create or modify
|
||||||
|
Abaqus reference CSV artifacts.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"project": "FESA Structural Solver",
|
||||||
|
"phase": "abaqus-input-parser",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step": 0,
|
||||||
|
"name": "io-contract",
|
||||||
|
"status": "completed",
|
||||||
|
"allowed_paths": [
|
||||||
|
"docs/io-definitions/abaqus-input-parser-io.md"
|
||||||
|
],
|
||||||
|
"summary": "Abaqus input parser I/O contract added"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"name": "mesh-keyword-parser",
|
||||||
|
"status": "completed",
|
||||||
|
"allowed_paths": [
|
||||||
|
"src/fesa/io/abaqus/",
|
||||||
|
"tests/unit/abaqus_input_parser_*_test.cpp"
|
||||||
|
],
|
||||||
|
"summary": "Mesh keyword parser maps NODE and ELEMENT data into Domain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"name": "syntax-diagnostics",
|
||||||
|
"status": "pending",
|
||||||
|
"allowed_paths": [
|
||||||
|
"src/fesa/io/abaqus/",
|
||||||
|
"tests/unit/abaqus_input_parser_*_test.cpp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"name": "sets-and-section-properties",
|
||||||
|
"status": "pending",
|
||||||
|
"allowed_paths": [
|
||||||
|
"src/fesa/io/abaqus/",
|
||||||
|
"src/fesa/model/",
|
||||||
|
"tests/unit/abaqus_input_parser_*_test.cpp",
|
||||||
|
"tests/unit/model_*_test.cpp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 4,
|
||||||
|
"name": "material-step-load-parser",
|
||||||
|
"status": "pending",
|
||||||
|
"allowed_paths": [
|
||||||
|
"src/fesa/io/abaqus/",
|
||||||
|
"src/fesa/model/",
|
||||||
|
"tests/unit/abaqus_input_parser_*_test.cpp",
|
||||||
|
"tests/unit/model_*_test.cpp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 5,
|
||||||
|
"name": "integration-validation-report",
|
||||||
|
"status": "pending",
|
||||||
|
"allowed_paths": [
|
||||||
|
"tests/integration/",
|
||||||
|
"docs/build-test-reports/abaqus-input-parser.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Step 0: io-contract
|
||||||
|
|
||||||
|
## Read First
|
||||||
|
|
||||||
|
Read these files before editing:
|
||||||
|
|
||||||
|
- `/AGENTS.md`
|
||||||
|
- `/docs/PRD.md`
|
||||||
|
- `/docs/ARCHITECTURE.md`
|
||||||
|
- `/docs/ADR.md`
|
||||||
|
- `/docs/SOLVER_AGENT_DESIGN.md`
|
||||||
|
- `/docs/io-definitions/README.md`
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
Create the feature-level I/O contract for the Abaqus input parser at
|
||||||
|
`/docs/io-definitions/abaqus-input-parser-io.md`.
|
||||||
|
|
||||||
|
The contract must explicitly state that FESA supports only the documented
|
||||||
|
Abaqus keyword subset, not full Abaqus compatibility. Keep this document at a
|
||||||
|
semantic level. Do not design C++ APIs and do not implement parser code in this
|
||||||
|
step.
|
||||||
|
|
||||||
|
Required scope:
|
||||||
|
|
||||||
|
- Model data keywords: `*HEADING`, `*NODE`, `*ELEMENT`, `*NSET`, `*ELSET`,
|
||||||
|
`*MATERIAL`, `*ELASTIC`, and section keywords needed for V0 bar/truss use.
|
||||||
|
- History data keywords: `*STEP`, `*STATIC`, `*BOUNDARY`, `*CLOAD`,
|
||||||
|
`*OUTPUT`, `*NODE OUTPUT`, and `*ELEMENT OUTPUT`.
|
||||||
|
- Syntax policy for comments beginning with `**`, case-insensitive keywords,
|
||||||
|
comma-separated parameters and data, blank lines, unsupported keywords, and
|
||||||
|
parse diagnostics.
|
||||||
|
- Internal semantic mapping to `Domain`, nodes, elements, sets, materials,
|
||||||
|
properties, `AnalysisStep`, boundary conditions, loads, and output requests.
|
||||||
|
- HDF5 and reference CSV comparison schema summary consistent with existing
|
||||||
|
PRD/architecture documents. Do not create or modify reference artifacts.
|
||||||
|
|
||||||
|
## Tests To Write First
|
||||||
|
|
||||||
|
No C++ tests are required in this documentation-only step.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m unittest discover -s scripts -p "test_*.py"
|
||||||
|
python scripts/validate_workspace.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification Procedure
|
||||||
|
|
||||||
|
1. Run the acceptance criteria commands.
|
||||||
|
2. Confirm the contract does not claim full Abaqus compatibility.
|
||||||
|
3. Confirm every supported keyword has purpose, data requirements, mapping, and
|
||||||
|
unsupported-case behavior.
|
||||||
|
4. Update `phases/abaqus-input-parser/index.json` step 0:
|
||||||
|
- success: `"status": "completed"`, `"summary": "Abaqus input parser I/O contract added"`
|
||||||
|
- error after three attempts: `"status": "error"`, `"error_message": "<specific error>"`
|
||||||
|
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||||
|
|
||||||
|
## Forbidden
|
||||||
|
|
||||||
|
- Do not implement parser code.
|
||||||
|
- Do not design C++ APIs.
|
||||||
|
- Do not run Abaqus, Nastran, or any reference solver.
|
||||||
|
- Do not generate or modify Abaqus reference CSV files.
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# Step 1: mesh-keyword-parser
|
||||||
|
|
||||||
|
## Read First
|
||||||
|
|
||||||
|
Read these files before editing:
|
||||||
|
|
||||||
|
- `/AGENTS.md`
|
||||||
|
- `/docs/PRD.md`
|
||||||
|
- `/docs/ARCHITECTURE.md`
|
||||||
|
- `/docs/ADR.md`
|
||||||
|
- `/docs/io-definitions/abaqus-input-parser-io.md`
|
||||||
|
- `/src/fesa/model/domain.hpp`
|
||||||
|
- `/src/fesa/model/node.hpp`
|
||||||
|
- `/src/fesa/model/element.hpp`
|
||||||
|
- `/tests/unit/model_domain_test.cpp`
|
||||||
|
|
||||||
|
Review step 0 output before implementing. Keep this step limited to parser API
|
||||||
|
and mesh keyword mapping.
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
Add the first C++ parser slice under `/src/fesa/io/abaqus/`.
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
|
||||||
|
- Provide a small `InputParser` that accepts an in-memory `.inp` string and
|
||||||
|
returns a parse result containing `fesa::model::Domain` and
|
||||||
|
`fesa::core::Status`.
|
||||||
|
- Parse `*HEADING` as accepted but not yet stored.
|
||||||
|
- Parse `*NODE` data lines into `Domain::add_node`.
|
||||||
|
- Parse `*ELEMENT, TYPE=<type>` data lines into `Domain::add_element`.
|
||||||
|
- Support only two-node line/bar element names needed by the current model:
|
||||||
|
`T2D2`, `T3D2`, `B31`, and `C3D2`.
|
||||||
|
- Map `T2D2`, `T3D2`, and `C3D2` to `ElementTopology::truss2`; map `B31` to
|
||||||
|
`ElementTopology::bar2`.
|
||||||
|
- Use `PropertyId{0}` for elements in this first mesh-only slice because
|
||||||
|
section parsing is introduced later.
|
||||||
|
- Treat keywords and parameter names case-insensitively.
|
||||||
|
- Skip blank lines and comment lines that begin with `**`.
|
||||||
|
|
||||||
|
Keep implementation C++17/MSVC-compatible. Do not introduce external
|
||||||
|
libraries, regex-heavy parser frameworks, JavaScript, TypeScript, or npm
|
||||||
|
fallbacks.
|
||||||
|
|
||||||
|
## Tests To Write First
|
||||||
|
|
||||||
|
Add `/tests/unit/abaqus_input_parser_mesh_test.cpp` before production code.
|
||||||
|
|
||||||
|
The test must first fail because `fesa/io/abaqus/input_parser.hpp` does not
|
||||||
|
exist, then pass after implementation. It should verify:
|
||||||
|
|
||||||
|
- a small input with `*HEADING`, `*NODE`, and `*ELEMENT, TYPE=T3D2` returns
|
||||||
|
`Status::is_ok() == true`;
|
||||||
|
- the returned `Domain` contains the expected node ids and 3D coordinates;
|
||||||
|
- the returned `Domain` contains the expected element id, topology,
|
||||||
|
connectivity, and `PropertyId{0}`;
|
||||||
|
- keyword and parameter casing are accepted case-insensitively.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R abaqus_input_parser_mesh_test
|
||||||
|
python -m unittest discover -s scripts -p "test_*.py"
|
||||||
|
python scripts/validate_workspace.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification Procedure
|
||||||
|
|
||||||
|
1. Run the targeted CTest after writing the test and before production code;
|
||||||
|
confirm the failure is due to the missing parser API.
|
||||||
|
2. Implement the minimum code needed for the test to pass.
|
||||||
|
3. Run all acceptance criteria commands.
|
||||||
|
4. Confirm C++ production changes have a related C++ test file.
|
||||||
|
5. Update `phases/abaqus-input-parser/index.json` step 1:
|
||||||
|
- success: `"status": "completed"`, `"summary": "Mesh keyword parser maps NODE and ELEMENT data into Domain"`
|
||||||
|
- error after three attempts: `"status": "error"`, `"error_message": "<specific error>"`
|
||||||
|
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||||
|
|
||||||
|
## Forbidden
|
||||||
|
|
||||||
|
- Do not add set, material, section, load, boundary condition, output request,
|
||||||
|
include-file, or HDF5 behavior in this step.
|
||||||
|
- Do not mutate reference artifacts.
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Step 2: syntax-diagnostics
|
||||||
|
|
||||||
|
## Read First
|
||||||
|
|
||||||
|
Read these files before editing:
|
||||||
|
|
||||||
|
- `/AGENTS.md`
|
||||||
|
- `/docs/ARCHITECTURE.md`
|
||||||
|
- `/docs/ADR.md`
|
||||||
|
- `/docs/io-definitions/abaqus-input-parser-io.md`
|
||||||
|
- `/src/fesa/io/abaqus/input_parser.hpp`
|
||||||
|
- `/src/fesa/io/abaqus/input_parser.cpp`
|
||||||
|
- `/tests/unit/abaqus_input_parser_mesh_test.cpp`
|
||||||
|
|
||||||
|
Review completed step summaries in `/phases/abaqus-input-parser/index.json`.
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
Add structured diagnostics for parser syntax and unsupported subset behavior.
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
|
||||||
|
- Unsupported keywords produce `Status::is_ok() == false` with an error
|
||||||
|
diagnostic code and message that identifies the keyword.
|
||||||
|
- Malformed `*NODE` and `*ELEMENT` data lines produce error diagnostics.
|
||||||
|
- Missing required `TYPE` on `*ELEMENT` produces an error diagnostic.
|
||||||
|
- Unsupported element types produce an error diagnostic and do not add that
|
||||||
|
element.
|
||||||
|
- Diagnostics must include enough context in the message to locate the line
|
||||||
|
number.
|
||||||
|
- Existing valid mesh parser behavior remains unchanged.
|
||||||
|
|
||||||
|
## Tests To Write First
|
||||||
|
|
||||||
|
Extend `/tests/unit/abaqus_input_parser_mesh_test.cpp` or add
|
||||||
|
`/tests/unit/abaqus_input_parser_diagnostics_test.cpp` before production code.
|
||||||
|
|
||||||
|
Test:
|
||||||
|
|
||||||
|
- unsupported keyword failure;
|
||||||
|
- malformed node row failure;
|
||||||
|
- missing element `TYPE` failure;
|
||||||
|
- unsupported element type failure.
|
||||||
|
|
||||||
|
Run the targeted CTest and confirm the new tests fail for missing behavior
|
||||||
|
before implementation.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R abaqus_input_parser
|
||||||
|
python -m unittest discover -s scripts -p "test_*.py"
|
||||||
|
python scripts/validate_workspace.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification Procedure
|
||||||
|
|
||||||
|
Update step 2 status with summary or a concrete error/blocked reason.
|
||||||
|
|
||||||
|
## Forbidden
|
||||||
|
|
||||||
|
- Do not add new semantic model fields except what diagnostics require.
|
||||||
|
- Do not add material, section, step, boundary, or load parsing.
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Step 3: sets-and-section-properties
|
||||||
|
|
||||||
|
## Read First
|
||||||
|
|
||||||
|
Read these files before editing:
|
||||||
|
|
||||||
|
- `/AGENTS.md`
|
||||||
|
- `/docs/ARCHITECTURE.md`
|
||||||
|
- `/docs/ADR.md`
|
||||||
|
- `/docs/io-definitions/abaqus-input-parser-io.md`
|
||||||
|
- `/src/fesa/model/domain.hpp`
|
||||||
|
- `/src/fesa/model/element.hpp`
|
||||||
|
- `/src/fesa/model/property.hpp`
|
||||||
|
- `/src/fesa/io/abaqus/input_parser.hpp`
|
||||||
|
- `/src/fesa/io/abaqus/input_parser.cpp`
|
||||||
|
|
||||||
|
Review completed parser tests and phase summaries first.
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
Extend the model and parser only as needed to represent set membership and
|
||||||
|
section-to-property assignment for V0 bar/truss models.
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
|
||||||
|
- Parse `*NSET, NSET=<name>` node membership rows.
|
||||||
|
- Parse `*ELSET, ELSET=<name>` element membership rows.
|
||||||
|
- Parse one V0 section keyword that assigns a material name to an element set.
|
||||||
|
Prefer the smallest section subset compatible with the current V0 element
|
||||||
|
path.
|
||||||
|
- Preserve deterministic member ordering as read, unless the I/O contract
|
||||||
|
explicitly states otherwise.
|
||||||
|
- Keep `Domain` as the owner of model definition data. Do not store equation ids
|
||||||
|
on nodes or elements.
|
||||||
|
|
||||||
|
## Tests To Write First
|
||||||
|
|
||||||
|
Add focused C++ tests before production code:
|
||||||
|
|
||||||
|
- a model test for any new set/section semantic model API;
|
||||||
|
- a parser test proving `*NSET`, `*ELSET`, and the selected section keyword map
|
||||||
|
into the semantic model.
|
||||||
|
|
||||||
|
Run targeted CTest and confirm the tests fail before implementation.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "(model_|abaqus_input_parser_)"
|
||||||
|
python -m unittest discover -s scripts -p "test_*.py"
|
||||||
|
python scripts/validate_workspace.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification Procedure
|
||||||
|
|
||||||
|
Update step 3 status with summary or a concrete error/blocked reason.
|
||||||
|
|
||||||
|
## Forbidden
|
||||||
|
|
||||||
|
- Do not parse materials beyond names needed for section linkage.
|
||||||
|
- Do not add loads, boundary conditions, or analysis steps.
|
||||||
|
- Do not generate or modify reference artifacts.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Step 4: material-step-load-parser
|
||||||
|
|
||||||
|
## Read First
|
||||||
|
|
||||||
|
Read these files before editing:
|
||||||
|
|
||||||
|
- `/AGENTS.md`
|
||||||
|
- `/docs/PRD.md`
|
||||||
|
- `/docs/ARCHITECTURE.md`
|
||||||
|
- `/docs/ADR.md`
|
||||||
|
- `/docs/io-definitions/abaqus-input-parser-io.md`
|
||||||
|
- `/src/fesa/model/material.hpp`
|
||||||
|
- `/src/fesa/model/analysis_step.hpp`
|
||||||
|
- `/src/fesa/model/boundary_condition.hpp`
|
||||||
|
- `/src/fesa/model/load.hpp`
|
||||||
|
- `/src/fesa/io/abaqus/input_parser.hpp`
|
||||||
|
- `/src/fesa/io/abaqus/input_parser.cpp`
|
||||||
|
|
||||||
|
Review completed parser and model tests first.
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
Extend the parser for the V0 material and history data subset.
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
|
||||||
|
- Parse `*MATERIAL, NAME=<name>` and `*ELASTIC` data into the internal material
|
||||||
|
representation. If the current `Material` model cannot store elastic data,
|
||||||
|
add the smallest semantic extension with tests.
|
||||||
|
- Parse `*STEP` and `*STATIC` into ordered `AnalysisStep` definitions.
|
||||||
|
- Parse `*BOUNDARY` rows into step boundary conditions using existing
|
||||||
|
`DofComponent` mapping.
|
||||||
|
- Parse `*CLOAD` rows into step concentrated loads using existing
|
||||||
|
`DofComponent` mapping.
|
||||||
|
- Parse `*OUTPUT`, `*NODE OUTPUT`, and `*ELEMENT OUTPUT` only to the extent
|
||||||
|
documented in the I/O contract. If semantic storage is not yet present, record
|
||||||
|
an explicit ignored-with-warning diagnostic rather than inventing output model
|
||||||
|
APIs.
|
||||||
|
|
||||||
|
## Tests To Write First
|
||||||
|
|
||||||
|
Add focused C++ tests before production code:
|
||||||
|
|
||||||
|
- model tests for any new material or output request semantic fields;
|
||||||
|
- parser tests for material/elastic, step/static, boundary, and cload rows.
|
||||||
|
|
||||||
|
Run targeted CTest and confirm the tests fail before implementation.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "(model_|abaqus_input_parser_)"
|
||||||
|
python -m unittest discover -s scripts -p "test_*.py"
|
||||||
|
python scripts/validate_workspace.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification Procedure
|
||||||
|
|
||||||
|
Update step 4 status with summary or a concrete error/blocked reason.
|
||||||
|
|
||||||
|
## Forbidden
|
||||||
|
|
||||||
|
- Do not implement analysis execution, assembly, HDF5 writing, or reference
|
||||||
|
comparison in this step.
|
||||||
|
- Do not mutate reference artifacts.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Step 5: integration-validation-report
|
||||||
|
|
||||||
|
## Read First
|
||||||
|
|
||||||
|
Read these files before editing:
|
||||||
|
|
||||||
|
- `/AGENTS.md`
|
||||||
|
- `/docs/PRD.md`
|
||||||
|
- `/docs/ARCHITECTURE.md`
|
||||||
|
- `/docs/ADR.md`
|
||||||
|
- `/docs/io-definitions/abaqus-input-parser-io.md`
|
||||||
|
- `/src/fesa/io/abaqus/input_parser.hpp`
|
||||||
|
- `/tests/unit/abaqus_input_parser_mesh_test.cpp`
|
||||||
|
- existing parser/model tests added by prior steps
|
||||||
|
|
||||||
|
Review all completed step summaries in `/phases/abaqus-input-parser/index.json`.
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
Add an integration-level parser test and a build/test report.
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
|
||||||
|
- Add a small integration test that parses a V0 Abaqus `.inp` string containing
|
||||||
|
the supported parser subset and validates the resulting `Domain` and
|
||||||
|
`AnalysisStep` data.
|
||||||
|
- Keep the test in memory; do not create or modify Abaqus reference CSV
|
||||||
|
artifacts.
|
||||||
|
- Write `/docs/build-test-reports/abaqus-input-parser.md` with command evidence,
|
||||||
|
exit codes, and known limitations.
|
||||||
|
|
||||||
|
## Tests To Write First
|
||||||
|
|
||||||
|
Add `/tests/integration/abaqus_input_parser_integration_test.cpp` before any
|
||||||
|
final integration adjustments. Confirm it fails for the missing integrated
|
||||||
|
behavior, then make it pass with the minimum implementation-owned changes.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R abaqus_input_parser
|
||||||
|
python -m unittest discover -s scripts -p "test_*.py"
|
||||||
|
python scripts/validate_workspace.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification Procedure
|
||||||
|
|
||||||
|
1. Run the acceptance criteria commands.
|
||||||
|
2. Confirm no reference artifacts were generated or modified.
|
||||||
|
3. Update step 5 status with summary or a concrete error/blocked reason.
|
||||||
|
|
||||||
|
## Forbidden
|
||||||
|
|
||||||
|
- Do not claim reference comparison, physics sanity, or release readiness.
|
||||||
|
- Do not generate, restore, or modify Abaqus reference CSV files.
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
{
|
|
||||||
"project": "FESA Structural Solver",
|
|
||||||
"phase": "euler-beam-3d",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"step": 0,
|
|
||||||
"name": "requirements-baseline",
|
|
||||||
"status": "pending",
|
|
||||||
"allowed_paths": [
|
|
||||||
"docs/requirements/euler-beam-3d.md"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": 1,
|
|
||||||
"name": "research-evidence",
|
|
||||||
"status": "pending",
|
|
||||||
"allowed_paths": [
|
|
||||||
"docs/research/euler-beam-3d-research.md"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": 2,
|
|
||||||
"name": "formulation-spec",
|
|
||||||
"status": "pending",
|
|
||||||
"allowed_paths": [
|
|
||||||
"docs/formulations/euler-beam-3d-formulation.md"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": 3,
|
|
||||||
"name": "numerical-review",
|
|
||||||
"status": "pending",
|
|
||||||
"allowed_paths": [
|
|
||||||
"docs/numerical-reviews/euler-beam-3d-review.md"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": 4,
|
|
||||||
"name": "io-reference-contract",
|
|
||||||
"status": "pending",
|
|
||||||
"allowed_paths": [
|
|
||||||
"docs/io-definitions/euler-beam-3d-io.md",
|
|
||||||
"docs/reference-models/euler-beam-3d-reference-models.md"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": 5,
|
|
||||||
"name": "implementation-plan",
|
|
||||||
"status": "pending",
|
|
||||||
"allowed_paths": [
|
|
||||||
"docs/implementation-plans/euler-beam-3d-implementation-plan.md"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": 6,
|
|
||||||
"name": "model-beam-topology",
|
|
||||||
"status": "pending",
|
|
||||||
"allowed_paths": [
|
|
||||||
"src/fesa/model/element.hpp",
|
|
||||||
"src/fesa/model/element.cpp",
|
|
||||||
"tests/unit/model_element_test.cpp"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": 7,
|
|
||||||
"name": "local-stiffness-kernel",
|
|
||||||
"status": "pending",
|
|
||||||
"allowed_paths": [
|
|
||||||
"src/fesa/elements/",
|
|
||||||
"tests/unit/euler_beam_3d_*_test.cpp"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": 8,
|
|
||||||
"name": "global-transform-recovery",
|
|
||||||
"status": "pending",
|
|
||||||
"allowed_paths": [
|
|
||||||
"src/fesa/elements/",
|
|
||||||
"tests/unit/euler_beam_3d_*_test.cpp"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": 9,
|
|
||||||
"name": "build-test-report",
|
|
||||||
"status": "pending",
|
|
||||||
"allowed_paths": [
|
|
||||||
"docs/build-test-reports/euler-beam-3d-build-test.md"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": 10,
|
|
||||||
"name": "release-readiness-note",
|
|
||||||
"status": "pending",
|
|
||||||
"allowed_paths": [
|
|
||||||
"docs/releases/euler-beam-3d-release.md"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
# Step 0: requirements-baseline
|
|
||||||
|
|
||||||
## Read These Files First
|
|
||||||
|
|
||||||
Read the following files before editing:
|
|
||||||
|
|
||||||
- `/AGENTS.md`
|
|
||||||
- `/docs/PRD.md`
|
|
||||||
- `/docs/ARCHITECTURE.md`
|
|
||||||
- `/docs/ADR.md`
|
|
||||||
- `/docs/requirements/README.md`
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Create `/docs/requirements/euler-beam-3d.md` for a kernel-first 3D Euler-Bernoulli beam feature.
|
|
||||||
|
|
||||||
Scope the first implementable increment narrowly:
|
|
||||||
|
|
||||||
- two-node, straight, prismatic, small-displacement 3D Euler-Bernoulli beam element
|
|
||||||
- six mechanical DOFs per node in this order: `ux, uy, uz, rx, ry, rz`
|
|
||||||
- linear elastic section constants: `E`, `G`, `A`, `J`, `Iy`, `Iz`
|
|
||||||
- local and global 12x12 stiffness matrix support
|
|
||||||
- local and global element end-force recovery from nodal displacement vectors
|
|
||||||
- no shear deformation, warping, end releases, offsets, distributed loads, mass matrix, geometric stiffness, nonlinear kinematics, dynamics, or thermal coupling in this increment
|
|
||||||
- no Abaqus reference CSV generation in this increment
|
|
||||||
- no full Abaqus compatibility claim
|
|
||||||
|
|
||||||
The document must contain:
|
|
||||||
|
|
||||||
- metadata: `feature_id: euler-beam-3d`, owner agent, status
|
|
||||||
- explicit assumptions and non-goals
|
|
||||||
- `must` requirements with stable IDs such as `EB3D-REQ-001`
|
|
||||||
- verification quantities: displacement DOFs, reactions/end forces, stiffness symmetry, rigid body modes, local/global transformation consistency
|
|
||||||
- acceptance criteria that distinguish kernel completion from full solver release readiness
|
|
||||||
- open issues for parser integration, reference artifact availability, and full end-to-end assembly
|
|
||||||
|
|
||||||
Do not create C++ files in this step.
|
|
||||||
|
|
||||||
## Tests To Write First
|
|
||||||
|
|
||||||
- No C++ test is required in this documentation-only step.
|
|
||||||
- Still run the harness validation commands in the acceptance criteria.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
python -m unittest discover -s scripts -p "test_*.py"
|
|
||||||
python scripts/validate_workspace.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
1. Confirm the requirements do not claim full Abaqus compatibility.
|
|
||||||
2. Confirm each `must` requirement has an acceptance criterion or a downstream verification hook.
|
|
||||||
3. Update `phases/euler-beam-3d/index.json` step 0:
|
|
||||||
- success: `"status": "completed"`, `"summary": "3D Euler beam kernel requirements baseline added"`
|
|
||||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
|
||||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Do not create or modify reference CSV files.
|
|
||||||
- Do not modify source or test files.
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# Step 1: research-evidence
|
|
||||||
|
|
||||||
## Read These Files First
|
|
||||||
|
|
||||||
Read the following files before editing:
|
|
||||||
|
|
||||||
- `/AGENTS.md`
|
|
||||||
- `/docs/PRD.md`
|
|
||||||
- `/docs/ARCHITECTURE.md`
|
|
||||||
- `/docs/ADR.md`
|
|
||||||
- `/docs/requirements/euler-beam-3d.md`
|
|
||||||
- `/docs/research/README.md`
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Create `/docs/research/euler-beam-3d-research.md`.
|
|
||||||
|
|
||||||
Summarize the evidence needed to implement the approved kernel-first 3D Euler-Bernoulli beam increment. The document must be implementation-oriented and must include:
|
|
||||||
|
|
||||||
- supported theory: straight prismatic Euler-Bernoulli beam, axial, torsion, and two uncoupled bending planes
|
|
||||||
- assumptions and applicability limits
|
|
||||||
- local DOF ordering and sign convention used by the planned matrix
|
|
||||||
- source reliability classification
|
|
||||||
- benchmark-style checks that do not require external reference solver execution:
|
|
||||||
- local stiffness symmetry
|
|
||||||
- axial-only response
|
|
||||||
- torsion-only response
|
|
||||||
- bending about local `y`
|
|
||||||
- bending about local `z`
|
|
||||||
- rigid body translation/rotation zero internal forces in local coordinates
|
|
||||||
- global transform identity for an axis-aligned beam
|
|
||||||
- risks: orientation vector parallel to element axis, near-zero length, nonpositive section constants, ill-conditioning for very slender elements
|
|
||||||
|
|
||||||
If internet access or a FEM wiki is used, cite sources briefly. Do not include long copyrighted excerpts.
|
|
||||||
|
|
||||||
Do not create C++ files in this step.
|
|
||||||
|
|
||||||
## Tests To Write First
|
|
||||||
|
|
||||||
- No C++ test is required in this documentation-only step.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
python -m unittest discover -s scripts -p "test_*.py"
|
|
||||||
python scripts/validate_workspace.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
1. Confirm the research document ties each source or theory point to a planned implementation check.
|
|
||||||
2. Confirm unresolved items are listed as risks or open issues instead of silently assumed.
|
|
||||||
3. Update `phases/euler-beam-3d/index.json` step 1:
|
|
||||||
- success: `"status": "completed"`, `"summary": "3D Euler beam research evidence added"`
|
|
||||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
|
||||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Do not modify source, test, reference, or I/O contract files.
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# Step 10: release-readiness-note
|
|
||||||
|
|
||||||
## Read These Files First
|
|
||||||
|
|
||||||
Read the following files before editing:
|
|
||||||
|
|
||||||
- `/AGENTS.md`
|
|
||||||
- `/docs/PRD.md`
|
|
||||||
- `/docs/ADR.md`
|
|
||||||
- `/docs/releases/README.md`
|
|
||||||
- `/docs/requirements/euler-beam-3d.md`
|
|
||||||
- `/docs/io-definitions/euler-beam-3d-io.md`
|
|
||||||
- `/docs/reference-models/euler-beam-3d-reference-models.md`
|
|
||||||
- `/docs/build-test-reports/euler-beam-3d-build-test.md`
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Create `/docs/releases/euler-beam-3d-release.md`.
|
|
||||||
|
|
||||||
This is a readiness note, not a release approval. It must state:
|
|
||||||
|
|
||||||
- status: `not-release-ready-kernel-increment-complete` unless all upstream gates and reference artifacts somehow exist
|
|
||||||
- completed scope: local/global stiffness and end-force kernel for two-node 3D Euler-Bernoulli beam
|
|
||||||
- missing for full feature release:
|
|
||||||
- parser implementation for the approved Abaqus subset
|
|
||||||
- section/property semantic model integration
|
|
||||||
- assembler/static solver integration
|
|
||||||
- HDF5 result emission for beam quantities
|
|
||||||
- stored Abaqus reference artifacts
|
|
||||||
- reference comparison report
|
|
||||||
- physics sanity report
|
|
||||||
- known limitations from requirements
|
|
||||||
- next recommended phase or phase dependencies
|
|
||||||
|
|
||||||
Do not change source or tests in this step.
|
|
||||||
|
|
||||||
## Tests To Write First
|
|
||||||
|
|
||||||
- No C++ test is required in this documentation-only step.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
python -m unittest discover -s scripts -p "test_*.py"
|
|
||||||
python scripts/validate_workspace.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
1. Confirm the note does not claim full release readiness.
|
|
||||||
2. Confirm all missing gates are explicit.
|
|
||||||
3. Update `phases/euler-beam-3d/index.json` step 10:
|
|
||||||
- success: `"status": "completed"`, `"summary": "3D Euler beam release readiness note added"`
|
|
||||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
|
||||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Do not create or modify reference artifacts.
|
|
||||||
- Do not modify source, tests, requirements, formulations, or I/O contracts.
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
# Step 2: formulation-spec
|
|
||||||
|
|
||||||
## Read These Files First
|
|
||||||
|
|
||||||
Read the following files before editing:
|
|
||||||
|
|
||||||
- `/AGENTS.md`
|
|
||||||
- `/docs/PRD.md`
|
|
||||||
- `/docs/ARCHITECTURE.md`
|
|
||||||
- `/docs/ADR.md`
|
|
||||||
- `/docs/requirements/euler-beam-3d.md`
|
|
||||||
- `/docs/research/euler-beam-3d-research.md`
|
|
||||||
- `/docs/formulations/README.md`
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Create `/docs/formulations/euler-beam-3d-formulation.md`.
|
|
||||||
|
|
||||||
The formulation must define the implementation contract for the C++ kernel. Include:
|
|
||||||
|
|
||||||
- local coordinate system: local `x` from node 1 to node 2; local `y` from the user orientation vector projected normal to local `x`; local `z = x cross y`
|
|
||||||
- local DOF order: `[u1, v1, w1, rx1, ry1, rz1, u2, v2, w2, rx2, ry2, rz2]`
|
|
||||||
- section constants: `E`, `G`, `A`, `J`, `Iy`, `Iz`
|
|
||||||
- local 12x12 stiffness matrix terms for:
|
|
||||||
- axial: `EA/L`
|
|
||||||
- torsion: `GJ/L`
|
|
||||||
- bending in local `x-y` using `EIz`
|
|
||||||
- bending in local `x-z` using `EIy`
|
|
||||||
- transformation matrix convention and global stiffness equation `K_global = T^T K_local T`
|
|
||||||
- end-force recovery equation `f_local = K_local u_local`
|
|
||||||
- validation tolerances for unit tests, using deterministic double comparisons
|
|
||||||
- singular and invalid input handling:
|
|
||||||
- zero or near-zero length throws `std::invalid_argument`
|
|
||||||
- nonpositive `E`, `G`, `A`, `J`, `Iy`, or `Iz` throws `std::invalid_argument`
|
|
||||||
- orientation vector parallel to beam axis throws `std::invalid_argument`
|
|
||||||
|
|
||||||
Use compact matrices and named scalar coefficients so the implementation step can transcribe directly.
|
|
||||||
|
|
||||||
Do not create C++ files in this step.
|
|
||||||
|
|
||||||
## Tests To Write First
|
|
||||||
|
|
||||||
- No C++ test is required in this documentation-only step.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
python -m unittest discover -s scripts -p "test_*.py"
|
|
||||||
python scripts/validate_workspace.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
1. Confirm the matrix is symmetric by construction.
|
|
||||||
2. Confirm local/global transformation convention is unambiguous.
|
|
||||||
3. Update `phases/euler-beam-3d/index.json` step 2:
|
|
||||||
- success: `"status": "completed"`, `"summary": "3D Euler beam formulation contract added"`
|
|
||||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
|
||||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Do not modify requirements, source, tests, or reference artifacts.
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
# Step 3: numerical-review
|
|
||||||
|
|
||||||
## Read These Files First
|
|
||||||
|
|
||||||
Read the following files before editing:
|
|
||||||
|
|
||||||
- `/AGENTS.md`
|
|
||||||
- `/docs/PRD.md`
|
|
||||||
- `/docs/ARCHITECTURE.md`
|
|
||||||
- `/docs/ADR.md`
|
|
||||||
- `/docs/requirements/euler-beam-3d.md`
|
|
||||||
- `/docs/research/euler-beam-3d-research.md`
|
|
||||||
- `/docs/formulations/euler-beam-3d-formulation.md`
|
|
||||||
- `/docs/numerical-reviews/README.md`
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Create `/docs/numerical-reviews/euler-beam-3d-review.md`.
|
|
||||||
|
|
||||||
Review the formulation for implementation readiness. The review must include:
|
|
||||||
|
|
||||||
- dimension and units check for each stiffness coefficient
|
|
||||||
- symmetry and rigid body mode expectations
|
|
||||||
- local axis construction risks
|
|
||||||
- positive semi-definite local stiffness expectation before constraints
|
|
||||||
- test obligations before production code
|
|
||||||
- known numerical limits for slender beams and very small section constants
|
|
||||||
- pass/fail verdict for kernel implementation planning
|
|
||||||
|
|
||||||
If the formulation is not ready, mark the document status as `needs-upstream-decision` and update this phase step as blocked with a concrete reason.
|
|
||||||
|
|
||||||
Do not create C++ files in this step.
|
|
||||||
|
|
||||||
## Tests To Write First
|
|
||||||
|
|
||||||
- No C++ test is required in this documentation-only step.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
python -m unittest discover -s scripts -p "test_*.py"
|
|
||||||
python scripts/validate_workspace.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
1. Confirm the review does not change requirements or formulation.
|
|
||||||
2. Confirm implementation-owned risks are turned into concrete tests.
|
|
||||||
3. Update `phases/euler-beam-3d/index.json` step 3:
|
|
||||||
- success: `"status": "completed"`, `"summary": "3D Euler beam numerical review added"`
|
|
||||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
|
||||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Do not modify requirements, formulation, source, tests, or reference artifacts.
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# Step 4: io-reference-contract
|
|
||||||
|
|
||||||
## Read These Files First
|
|
||||||
|
|
||||||
Read the following files before editing:
|
|
||||||
|
|
||||||
- `/AGENTS.md`
|
|
||||||
- `/docs/PRD.md`
|
|
||||||
- `/docs/ARCHITECTURE.md`
|
|
||||||
- `/docs/ADR.md`
|
|
||||||
- `/docs/requirements/euler-beam-3d.md`
|
|
||||||
- `/docs/formulations/euler-beam-3d-formulation.md`
|
|
||||||
- `/docs/numerical-reviews/euler-beam-3d-review.md`
|
|
||||||
- `/docs/io-definitions/README.md`
|
|
||||||
- `/docs/reference-models/README.md`
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Create two documents:
|
|
||||||
|
|
||||||
- `/docs/io-definitions/euler-beam-3d-io.md`
|
|
||||||
- `/docs/reference-models/euler-beam-3d-reference-models.md`
|
|
||||||
|
|
||||||
The I/O contract must describe the planned Abaqus subset without claiming it is implemented in this kernel increment:
|
|
||||||
|
|
||||||
- element keyword mapping candidate: two-node beam topology equivalent to Abaqus `B31`
|
|
||||||
- section keyword candidate: beam section constants sufficient for `A`, `J`, `Iy`, `Iz`, `E`, and `G`
|
|
||||||
- orientation data requirement for constructing local axes
|
|
||||||
- boundary/load DOFs: `1..6` map to `ux, uy, uz, rx, ry, rz`
|
|
||||||
- HDF5 output quantities expected after solver integration: nodal displacement, reaction, element internal force, stress placeholders if stress recovery is later approved
|
|
||||||
- unsupported input cases and diagnostics
|
|
||||||
|
|
||||||
The reference model contract must list required future models without generating artifacts:
|
|
||||||
|
|
||||||
- axial cantilever bar-as-beam
|
|
||||||
- torsion cantilever
|
|
||||||
- bending cantilever about local `y`
|
|
||||||
- bending cantilever about local `z`
|
|
||||||
- skew-oriented beam transform check
|
|
||||||
|
|
||||||
For each future model, specify expected files under `reference/<model-id>/` and which CSV quantities are required. State that Abaqus reference CSVs must not be generated or modified in this phase.
|
|
||||||
|
|
||||||
Do not create C++ files in this step.
|
|
||||||
|
|
||||||
## Tests To Write First
|
|
||||||
|
|
||||||
- No C++ test is required in this documentation-only step.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
python -m unittest discover -s scripts -p "test_*.py"
|
|
||||||
python scripts/validate_workspace.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
1. Confirm reference artifacts are specified, not created.
|
|
||||||
2. Confirm unsupported parser or solver paths are explicit open issues.
|
|
||||||
3. Update `phases/euler-beam-3d/index.json` step 4:
|
|
||||||
- success: `"status": "completed"`, `"summary": "3D Euler beam I/O and reference model contracts added"`
|
|
||||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
|
||||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Do not create or modify files under `/reference/`.
|
|
||||||
- Do not modify source or tests.
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# Step 5: implementation-plan
|
|
||||||
|
|
||||||
## Read These Files First
|
|
||||||
|
|
||||||
Read the following files before editing:
|
|
||||||
|
|
||||||
- `/AGENTS.md`
|
|
||||||
- `/docs/PRD.md`
|
|
||||||
- `/docs/ARCHITECTURE.md`
|
|
||||||
- `/docs/ADR.md`
|
|
||||||
- `/docs/SOLVER_AGENT_DESIGN.md`
|
|
||||||
- `/docs/implementation-plans/README.md`
|
|
||||||
- `/docs/requirements/euler-beam-3d.md`
|
|
||||||
- `/docs/research/euler-beam-3d-research.md`
|
|
||||||
- `/docs/formulations/euler-beam-3d-formulation.md`
|
|
||||||
- `/docs/numerical-reviews/euler-beam-3d-review.md`
|
|
||||||
- `/docs/io-definitions/euler-beam-3d-io.md`
|
|
||||||
- `/docs/reference-models/euler-beam-3d-reference-models.md`
|
|
||||||
- `/src/fesa/model/element.hpp`
|
|
||||||
- `/tests/unit/model_element_test.cpp`
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Create `/docs/implementation-plans/euler-beam-3d-implementation-plan.md`.
|
|
||||||
|
|
||||||
The implementation plan must be ready for C++ TDD work and must include:
|
|
||||||
|
|
||||||
- readiness check for requirements, research, formulation, numerical review, I/O, reference model contract
|
|
||||||
- explicit kernel-first implementation scope
|
|
||||||
- tasks matching the remaining phase steps:
|
|
||||||
- model beam topology
|
|
||||||
- local stiffness kernel
|
|
||||||
- global transform and end-force recovery
|
|
||||||
- build/test report
|
|
||||||
- release readiness note
|
|
||||||
- test IDs and RED/GREEN conditions for each production change
|
|
||||||
- candidate files:
|
|
||||||
- `src/fesa/model/element.hpp`
|
|
||||||
- `src/fesa/model/element.cpp`
|
|
||||||
- `tests/unit/model_element_test.cpp`
|
|
||||||
- `src/fesa/elements/euler_beam_3d.hpp`
|
|
||||||
- `src/fesa/elements/euler_beam_3d.cpp`
|
|
||||||
- `tests/unit/euler_beam_3d_local_stiffness_test.cpp`
|
|
||||||
- `tests/unit/euler_beam_3d_transform_recovery_test.cpp`
|
|
||||||
- CTest commands:
|
|
||||||
- `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model_element_test`
|
|
||||||
- `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_local_stiffness_test`
|
|
||||||
- `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_transform_recovery_test`
|
|
||||||
- acceptance traceability from requirements to tests
|
|
||||||
|
|
||||||
Do not create C++ files in this step.
|
|
||||||
|
|
||||||
## Tests To Write First
|
|
||||||
|
|
||||||
- No C++ test is required in this documentation-only step.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
python -m unittest discover -s scripts -p "test_*.py"
|
|
||||||
python scripts/validate_workspace.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
1. Confirm every production file named in the plan has a related test.
|
|
||||||
2. Confirm the plan does not ask Implementation Agent to change requirements, formulation, I/O contracts, reference artifacts, or tolerance policies.
|
|
||||||
3. Update `phases/euler-beam-3d/index.json` step 5:
|
|
||||||
- success: `"status": "completed"`, `"summary": "3D Euler beam implementation plan added"`
|
|
||||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
|
||||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Do not modify source, tests, reference artifacts, or CMake files.
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
# Step 6: model-beam-topology
|
|
||||||
|
|
||||||
## Read These Files First
|
|
||||||
|
|
||||||
Read the following files before editing:
|
|
||||||
|
|
||||||
- `/AGENTS.md`
|
|
||||||
- `/docs/ARCHITECTURE.md`
|
|
||||||
- `/docs/ADR.md`
|
|
||||||
- `/docs/implementation-plans/euler-beam-3d-implementation-plan.md`
|
|
||||||
- `/src/fesa/model/element.hpp`
|
|
||||||
- `/src/fesa/model/element.cpp`
|
|
||||||
- `/tests/unit/model_element_test.cpp`
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Use TDD to add a semantic model topology for a two-node 3D beam.
|
|
||||||
|
|
||||||
Required production behavior:
|
|
||||||
|
|
||||||
- Add `beam2` to `fesa::model::ElementTopology`.
|
|
||||||
- Preserve existing `truss2`, `bar2`, and `unknown` behavior.
|
|
||||||
- Do not store equation IDs on `Element`.
|
|
||||||
- Do not add section constants to `Element` in this step.
|
|
||||||
|
|
||||||
## Tests To Write First
|
|
||||||
|
|
||||||
Modify `/tests/unit/model_element_test.cpp` first.
|
|
||||||
|
|
||||||
Add a failing assertion that constructs an element with:
|
|
||||||
|
|
||||||
- id `ElementId{10}`
|
|
||||||
- topology `ElementTopology::beam2`
|
|
||||||
- node ids `{NodeId{1}, NodeId{2}}`
|
|
||||||
- property id `PropertyId{7}`
|
|
||||||
|
|
||||||
The test must verify:
|
|
||||||
|
|
||||||
- `element.topology() == ElementTopology::beam2`
|
|
||||||
- `element.node_ids().size() == 2`
|
|
||||||
- existing `bar2` behavior still works or the test still covers it
|
|
||||||
|
|
||||||
RED command:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model_element_test
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected RED: compile failure because `ElementTopology::beam2` is not defined.
|
|
||||||
|
|
||||||
Then implement the minimal enum addition.
|
|
||||||
|
|
||||||
GREEN command:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model_element_test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model_element_test
|
|
||||||
python -m unittest discover -s scripts -p "test_*.py"
|
|
||||||
python scripts/validate_workspace.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
1. Confirm the test failed before editing production code.
|
|
||||||
2. Confirm no unrelated model refactor was made.
|
|
||||||
3. Update `phases/euler-beam-3d/index.json` step 6:
|
|
||||||
- success: `"status": "completed"`, `"summary": "beam2 model topology added with unit test"`
|
|
||||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
|
||||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Do not create parser code.
|
|
||||||
- Do not add beam stiffness code in this step.
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
# Step 7: local-stiffness-kernel
|
|
||||||
|
|
||||||
## Read These Files First
|
|
||||||
|
|
||||||
Read the following files before editing:
|
|
||||||
|
|
||||||
- `/AGENTS.md`
|
|
||||||
- `/docs/ARCHITECTURE.md`
|
|
||||||
- `/docs/ADR.md`
|
|
||||||
- `/docs/formulations/euler-beam-3d-formulation.md`
|
|
||||||
- `/docs/numerical-reviews/euler-beam-3d-review.md`
|
|
||||||
- `/docs/implementation-plans/euler-beam-3d-implementation-plan.md`
|
|
||||||
|
|
||||||
Also read any files created by previous steps in this phase.
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Use TDD to create the local 12x12 stiffness kernel for the 3D Euler-Bernoulli beam.
|
|
||||||
|
|
||||||
Create:
|
|
||||||
|
|
||||||
- `/src/fesa/elements/euler_beam_3d.hpp`
|
|
||||||
- `/src/fesa/elements/euler_beam_3d.cpp`
|
|
||||||
- `/tests/unit/euler_beam_3d_local_stiffness_test.cpp`
|
|
||||||
|
|
||||||
Required API:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
namespace fesa::elements {
|
|
||||||
|
|
||||||
using Vector12 = std::array<double, 12>;
|
|
||||||
using Matrix12 = std::array<double, 144>;
|
|
||||||
|
|
||||||
struct EulerBeam3DSection {
|
|
||||||
double young_modulus;
|
|
||||||
double shear_modulus;
|
|
||||||
double area;
|
|
||||||
double torsion_constant;
|
|
||||||
double second_moment_y;
|
|
||||||
double second_moment_z;
|
|
||||||
};
|
|
||||||
|
|
||||||
Matrix12 euler_beam_3d_local_stiffness(double length, const EulerBeam3DSection& section);
|
|
||||||
Vector12 euler_beam_3d_local_end_forces(double length,
|
|
||||||
const EulerBeam3DSection& section,
|
|
||||||
const Vector12& local_displacements);
|
|
||||||
|
|
||||||
} // namespace fesa::elements
|
|
||||||
```
|
|
||||||
|
|
||||||
Implementation rules:
|
|
||||||
|
|
||||||
- Matrix storage is row-major: index `(row, col)` is `row * 12 + col`.
|
|
||||||
- Validate `length > 0` and all section constants are positive; throw `std::invalid_argument` otherwise.
|
|
||||||
- Use only C++17 standard library.
|
|
||||||
- Do not introduce an external linear algebra dependency.
|
|
||||||
- Do not edit CMake files; source and test globs should pick up new files.
|
|
||||||
|
|
||||||
## Tests To Write First
|
|
||||||
|
|
||||||
Create `/tests/unit/euler_beam_3d_local_stiffness_test.cpp` before production code.
|
|
||||||
|
|
||||||
Test behavior:
|
|
||||||
|
|
||||||
- For `L = 2.0`, `E = 210.0`, `G = 80.0`, `A = 3.0`, `J = 4.0`, `Iy = 5.0`, `Iz = 6.0`, verify representative matrix entries:
|
|
||||||
- axial: `K(0,0) = EA/L`, `K(0,6) = -EA/L`, `K(6,6) = EA/L`
|
|
||||||
- torsion: `K(3,3) = GJ/L`, `K(3,9) = -GJ/L`, `K(9,9) = GJ/L`
|
|
||||||
- local `x-y` bending uses `EIz`: `K(1,1) = 12*E*Iz/L^3`, `K(1,5) = 6*E*Iz/L^2`, `K(5,5) = 4*E*Iz/L`
|
|
||||||
- local `x-z` bending uses `EIy`: `K(2,2) = 12*E*Iy/L^3`, `K(2,4) = -6*E*Iy/L^2`, `K(4,4) = 4*E*Iy/L`
|
|
||||||
- Verify all entries are symmetric within `1.0e-10`.
|
|
||||||
- Verify `euler_beam_3d_local_end_forces` returns `K * u` for a displacement vector with at least three nonzero components.
|
|
||||||
- Verify invalid length and nonpositive section constants throw `std::invalid_argument`.
|
|
||||||
|
|
||||||
RED command:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_local_stiffness_test
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected RED: test executable missing or compile failure because the header/API does not exist.
|
|
||||||
|
|
||||||
Then implement the minimal API.
|
|
||||||
|
|
||||||
GREEN command:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_local_stiffness_test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_local_stiffness_test
|
|
||||||
python -m unittest discover -s scripts -p "test_*.py"
|
|
||||||
python scripts/validate_workspace.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
1. Confirm RED failed before production code was added.
|
|
||||||
2. Confirm no parser, assembly, or results writer code was changed.
|
|
||||||
3. Update `phases/euler-beam-3d/index.json` step 7:
|
|
||||||
- success: `"status": "completed"`, `"summary": "local 3D Euler beam stiffness and local end-force kernel added"`
|
|
||||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
|
||||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Do not add shear deformation or Timoshenko terms.
|
|
||||||
- Do not modify reference artifacts.
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
# Step 8: global-transform-recovery
|
|
||||||
|
|
||||||
## Read These Files First
|
|
||||||
|
|
||||||
Read the following files before editing:
|
|
||||||
|
|
||||||
- `/AGENTS.md`
|
|
||||||
- `/docs/ARCHITECTURE.md`
|
|
||||||
- `/docs/ADR.md`
|
|
||||||
- `/docs/formulations/euler-beam-3d-formulation.md`
|
|
||||||
- `/docs/numerical-reviews/euler-beam-3d-review.md`
|
|
||||||
- `/docs/implementation-plans/euler-beam-3d-implementation-plan.md`
|
|
||||||
- `/src/fesa/elements/euler_beam_3d.hpp`
|
|
||||||
- `/src/fesa/elements/euler_beam_3d.cpp`
|
|
||||||
- `/tests/unit/euler_beam_3d_local_stiffness_test.cpp`
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Use TDD to add local/global transformation and global end-force recovery to the 3D Euler-Bernoulli beam kernel.
|
|
||||||
|
|
||||||
Extend the API in `/src/fesa/elements/euler_beam_3d.hpp`:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
using Vector3 = std::array<double, 3>;
|
|
||||||
|
|
||||||
struct EulerBeam3DGeometry {
|
|
||||||
Vector3 node1;
|
|
||||||
Vector3 node2;
|
|
||||||
Vector3 orientation;
|
|
||||||
};
|
|
||||||
|
|
||||||
Matrix12 euler_beam_3d_global_stiffness(const EulerBeam3DGeometry& geometry,
|
|
||||||
const EulerBeam3DSection& section);
|
|
||||||
Vector12 euler_beam_3d_global_end_forces(const EulerBeam3DGeometry& geometry,
|
|
||||||
const EulerBeam3DSection& section,
|
|
||||||
const Vector12& global_displacements);
|
|
||||||
```
|
|
||||||
|
|
||||||
Implementation rules:
|
|
||||||
|
|
||||||
- local `x` is normalized `node2 - node1`
|
|
||||||
- local `y` is the normalized projection of `orientation` onto the plane normal to local `x`
|
|
||||||
- local `z = x cross y`
|
|
||||||
- use the same 3x3 rotation block for translational and rotational DOFs
|
|
||||||
- compute `K_global = T^T K_local T`
|
|
||||||
- compute global end forces by transforming global displacements to local, recovering local forces, then transforming forces back to global
|
|
||||||
- throw `std::invalid_argument` for zero-length element, zero orientation vector, or orientation parallel to the beam axis
|
|
||||||
|
|
||||||
## Tests To Write First
|
|
||||||
|
|
||||||
Create `/tests/unit/euler_beam_3d_transform_recovery_test.cpp` before production code.
|
|
||||||
|
|
||||||
Test behavior:
|
|
||||||
|
|
||||||
- Axis-aligned beam from `(0,0,0)` to `(2,0,0)` with orientation `(0,1,0)` gives global stiffness equal to local stiffness.
|
|
||||||
- A rotated beam preserves stiffness symmetry.
|
|
||||||
- A rigid global translation vector produces near-zero global end forces.
|
|
||||||
- A simple global axial extension on the axis-aligned beam produces equal and opposite axial end forces.
|
|
||||||
- Parallel orientation vector throws `std::invalid_argument`.
|
|
||||||
|
|
||||||
RED command:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_transform_recovery_test
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected RED: test executable missing or compile failure because the transform API does not exist.
|
|
||||||
|
|
||||||
Then implement the minimal API.
|
|
||||||
|
|
||||||
GREEN command:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_transform_recovery_test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "euler_beam_3d_(local_stiffness|transform_recovery)_test"
|
|
||||||
python -m unittest discover -s scripts -p "test_*.py"
|
|
||||||
python scripts/validate_workspace.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
1. Confirm RED failed before production code was added.
|
|
||||||
2. Confirm local stiffness tests still pass.
|
|
||||||
3. Update `phases/euler-beam-3d/index.json` step 8:
|
|
||||||
- success: `"status": "completed"`, `"summary": "global transform and global end-force recovery added for 3D Euler beam"`
|
|
||||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
|
||||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Do not add assembly or solver integration in this step.
|
|
||||||
- Do not modify reference artifacts.
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# Step 9: build-test-report
|
|
||||||
|
|
||||||
## Read These Files First
|
|
||||||
|
|
||||||
Read the following files before editing:
|
|
||||||
|
|
||||||
- `/AGENTS.md`
|
|
||||||
- `/docs/build-test-reports/README.md`
|
|
||||||
- `/docs/implementation-plans/euler-beam-3d-implementation-plan.md`
|
|
||||||
- `/src/fesa/elements/euler_beam_3d.hpp`
|
|
||||||
- `/src/fesa/elements/euler_beam_3d.cpp`
|
|
||||||
- `/tests/unit/euler_beam_3d_local_stiffness_test.cpp`
|
|
||||||
- `/tests/unit/euler_beam_3d_transform_recovery_test.cpp`
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
Create `/docs/build-test-reports/euler-beam-3d-build-test.md`.
|
|
||||||
|
|
||||||
The report must record:
|
|
||||||
|
|
||||||
- feature id
|
|
||||||
- changed files observed for this phase
|
|
||||||
- command log summary with exit codes and short evidence tails
|
|
||||||
- validation results for:
|
|
||||||
- harness self-test
|
|
||||||
- CMake configure/build
|
|
||||||
- CTest
|
|
||||||
- feature-specific tests
|
|
||||||
- failure classification if anything failed
|
|
||||||
- explicit statement that this report does not approve reference verification or release readiness
|
|
||||||
|
|
||||||
Do not change source or tests in this step.
|
|
||||||
|
|
||||||
## Tests To Write First
|
|
||||||
|
|
||||||
- No C++ test is required in this documentation-only step.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "model_element_test|euler_beam_3d_(local_stiffness|transform_recovery)_test"
|
|
||||||
python -m unittest discover -s scripts -p "test_*.py"
|
|
||||||
python scripts/validate_workspace.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
1. Record actual command exit codes and concise output evidence.
|
|
||||||
2. If validation fails for an environment reason, mark the report `needs-environment-fix` and update this phase step as blocked.
|
|
||||||
3. Update `phases/euler-beam-3d/index.json` step 9:
|
|
||||||
- success: `"status": "completed"`, `"summary": "3D Euler beam build/test report added"`
|
|
||||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
|
||||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
|
||||||
|
|
||||||
## Forbidden
|
|
||||||
|
|
||||||
- Do not modify source, tests, requirements, formulations, I/O contracts, or reference artifacts.
|
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
"status": "completed"
|
"status": "completed"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dir": "euler-beam-3d",
|
"dir": "abaqus-input-parser",
|
||||||
"status": "pending"
|
"status": "pending"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+2
-2
@@ -373,8 +373,8 @@ class StepExecutor:
|
|||||||
|
|
||||||
prompt = preamble + step_file.read_text(encoding="utf-8")
|
prompt = preamble + step_file.read_text(encoding="utf-8")
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["codex", "exec", "--dangerously-bypass-approvals-and-sandbox", "--json", "-"],
|
["codex", "exec", "--dangerously-bypass-approvals-and-sandbox", "--json", prompt],
|
||||||
cwd=self._root, capture_output=True, text=True, input=prompt, timeout=1800,
|
cwd=self._root, capture_output=True, text=True, timeout=1800,
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
|
|||||||
@@ -281,30 +281,6 @@ class ExecuteRunnerSafetyTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(cm.exception.code, 1)
|
self.assertEqual(cm.exception.code, 1)
|
||||||
|
|
||||||
def test_invoke_codex_passes_prompt_through_stdin(self):
|
|
||||||
execute = load_execute()
|
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
|
||||||
root = Path(tmp)
|
|
||||||
write_phase(root)
|
|
||||||
executor = make_executor(execute, root)
|
|
||||||
step = {"step": 1, "name": "Docs"}
|
|
||||||
long_preamble = "x" * 40000
|
|
||||||
|
|
||||||
def fake_run(cmd, **kwargs):
|
|
||||||
return subprocess.CompletedProcess(cmd, 0, '{"event":"done"}\n', "")
|
|
||||||
|
|
||||||
with patch.object(execute.subprocess, "run", side_effect=fake_run) as run_mock:
|
|
||||||
executor._invoke_codex(step, long_preamble)
|
|
||||||
|
|
||||||
cmd = run_mock.call_args.args[0]
|
|
||||||
kwargs = run_mock.call_args.kwargs
|
|
||||||
self.assertEqual(
|
|
||||||
cmd,
|
|
||||||
["codex", "exec", "--dangerously-bypass-approvals-and-sandbox", "--json", "-"],
|
|
||||||
)
|
|
||||||
self.assertEqual(kwargs["input"], long_preamble + "# Step 1\n")
|
|
||||||
self.assertEqual(kwargs["cwd"], str(root))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -30,14 +30,15 @@ class ValidateWorkspaceTests(unittest.TestCase):
|
|||||||
(root / "CMakeLists.txt").write_text("cmake_minimum_required(VERSION 3.20)\n", encoding="utf-8")
|
(root / "CMakeLists.txt").write_text("cmake_minimum_required(VERSION 3.20)\n", encoding="utf-8")
|
||||||
build_dir = root / "build" / "msvc-debug"
|
build_dir = root / "build" / "msvc-debug"
|
||||||
with patch.dict(os.environ, {}, clear=True):
|
with patch.dict(os.environ, {}, clear=True):
|
||||||
self.assertEqual(
|
with patch.object(validate_workspace.shutil, "which", return_value="C:\\tools\\cmake.exe"):
|
||||||
validate_workspace.discover_commands(root),
|
self.assertEqual(
|
||||||
[
|
validate_workspace.discover_commands(root),
|
||||||
f'cmake -S "{root}" -B "{build_dir}" -G "Visual Studio 17 2022" -A x64',
|
[
|
||||||
f'cmake --build "{build_dir}" --config Debug',
|
f'cmake -S "{root}" -B "{build_dir}" -G "Visual Studio 17 2022" -A x64',
|
||||||
f'ctest --test-dir "{build_dir}" --output-on-failure -C Debug',
|
f'cmake --build "{build_dir}" --config Debug',
|
||||||
],
|
f'ctest --test-dir "{build_dir}" --output-on-failure -C Debug',
|
||||||
)
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def test_msvc_debug_configure_preset_is_preferred_when_present(self):
|
def test_msvc_debug_configure_preset_is_preferred_when_present(self):
|
||||||
validate_workspace = load_validate_workspace()
|
validate_workspace = load_validate_workspace()
|
||||||
@@ -60,14 +61,39 @@ class ValidateWorkspaceTests(unittest.TestCase):
|
|||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
with patch.dict(os.environ, {}, clear=True):
|
with patch.dict(os.environ, {}, clear=True):
|
||||||
self.assertEqual(
|
with patch.object(validate_workspace.shutil, "which", return_value="C:\\tools\\cmake.exe"):
|
||||||
validate_workspace.discover_commands(root),
|
self.assertEqual(
|
||||||
[
|
validate_workspace.discover_commands(root),
|
||||||
"cmake --preset msvc-debug",
|
[
|
||||||
f'cmake --build "{root / "out" / "msvc-debug"}" --config Debug',
|
"cmake --preset msvc-debug",
|
||||||
f'ctest --test-dir "{root / "out" / "msvc-debug"}" --output-on-failure -C Debug',
|
f'cmake --build "{root / "out" / "msvc-debug"}" --config Debug',
|
||||||
],
|
f'ctest --test-dir "{root / "out" / "msvc-debug"}" --output-on-failure -C Debug',
|
||||||
)
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cmake_commands_use_common_install_path_when_tools_are_not_on_path(self):
|
||||||
|
validate_workspace = load_validate_workspace()
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp) / "project"
|
||||||
|
root.mkdir()
|
||||||
|
(root / "CMakeLists.txt").write_text("cmake_minimum_required(VERSION 3.20)\n", encoding="utf-8")
|
||||||
|
common_bin = Path(tmp) / "CMake" / "bin"
|
||||||
|
common_bin.mkdir(parents=True)
|
||||||
|
(common_bin / "cmake.exe").write_text("", encoding="utf-8")
|
||||||
|
(common_bin / "ctest.exe").write_text("", encoding="utf-8")
|
||||||
|
build_dir = root / "build" / "msvc-debug"
|
||||||
|
|
||||||
|
with patch.dict(os.environ, {}, clear=True):
|
||||||
|
with patch.object(validate_workspace, "COMMON_CMAKE_BIN", common_bin):
|
||||||
|
with patch.object(validate_workspace.shutil, "which", return_value=None):
|
||||||
|
self.assertEqual(
|
||||||
|
validate_workspace.discover_commands(root),
|
||||||
|
[
|
||||||
|
f'"{common_bin / "cmake.exe"}" -S "{root}" -B "{build_dir}" -G "Visual Studio 17 2022" -A x64',
|
||||||
|
f'"{common_bin / "cmake.exe"}" --build "{build_dir}" --config Debug',
|
||||||
|
f'"{common_bin / "ctest.exe"}" --test-dir "{build_dir}" --output-on-failure -C Debug',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def test_no_cmake_project_has_no_validation_commands(self):
|
def test_no_cmake_project_has_no_validation_commands(self):
|
||||||
validate_workspace = load_validate_workspace()
|
validate_workspace = load_validate_workspace()
|
||||||
|
|||||||
@@ -32,6 +32,17 @@ def _cmake_config() -> tuple[str, str, str, Path]:
|
|||||||
return generator, platform, config, build_dir
|
return generator, platform, config, build_dir
|
||||||
|
|
||||||
|
|
||||||
|
def _tool_command(tool_name: str) -> str:
|
||||||
|
if shutil.which(tool_name) is not None:
|
||||||
|
return tool_name
|
||||||
|
|
||||||
|
exe = COMMON_CMAKE_BIN / f"{tool_name}.exe"
|
||||||
|
if exe.exists():
|
||||||
|
return f'"{exe}"'
|
||||||
|
|
||||||
|
return tool_name
|
||||||
|
|
||||||
|
|
||||||
def _read_presets(root: Path) -> dict:
|
def _read_presets(root: Path) -> dict:
|
||||||
presets_file = root / "CMakePresets.json"
|
presets_file = root / "CMakePresets.json"
|
||||||
if not presets_file.exists():
|
if not presets_file.exists():
|
||||||
@@ -53,13 +64,15 @@ def _preset_binary_dir(root: Path, preset: dict) -> Path:
|
|||||||
def load_preset_commands(root: Path) -> list[str]:
|
def load_preset_commands(root: Path) -> list[str]:
|
||||||
payload = _read_presets(root)
|
payload = _read_presets(root)
|
||||||
config = os.environ.get("HARNESS_CMAKE_CONFIG", DEFAULT_CONFIG)
|
config = os.environ.get("HARNESS_CMAKE_CONFIG", DEFAULT_CONFIG)
|
||||||
|
cmake = _tool_command("cmake")
|
||||||
|
ctest = _tool_command("ctest")
|
||||||
for preset in payload.get("configurePresets", []):
|
for preset in payload.get("configurePresets", []):
|
||||||
if isinstance(preset, dict) and preset.get("name") == PRESET_NAME:
|
if isinstance(preset, dict) and preset.get("name") == PRESET_NAME:
|
||||||
build_dir = _preset_binary_dir(root, preset)
|
build_dir = _preset_binary_dir(root, preset)
|
||||||
return [
|
return [
|
||||||
f"cmake --preset {PRESET_NAME}",
|
f"{cmake} --preset {PRESET_NAME}",
|
||||||
f'cmake --build "{build_dir}" --config {config}',
|
f'{cmake} --build "{build_dir}" --config {config}',
|
||||||
f'ctest --test-dir "{build_dir}" --output-on-failure -C {config}',
|
f'{ctest} --test-dir "{build_dir}" --output-on-failure -C {config}',
|
||||||
]
|
]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -71,10 +84,12 @@ def load_cmake_commands(root: Path) -> list[str]:
|
|||||||
generator, platform, config, build_dir = _cmake_config()
|
generator, platform, config, build_dir = _cmake_config()
|
||||||
if not build_dir.is_absolute():
|
if not build_dir.is_absolute():
|
||||||
build_dir = root / build_dir
|
build_dir = root / build_dir
|
||||||
|
cmake = _tool_command("cmake")
|
||||||
|
ctest = _tool_command("ctest")
|
||||||
return [
|
return [
|
||||||
f'cmake -S "{root}" -B "{build_dir}" -G "{generator}" -A {platform}',
|
f'{cmake} -S "{root}" -B "{build_dir}" -G "{generator}" -A {platform}',
|
||||||
f'cmake --build "{build_dir}" --config {config}',
|
f'{cmake} --build "{build_dir}" --config {config}',
|
||||||
f'ctest --test-dir "{build_dir}" --output-on-failure -C {config}',
|
f'{ctest} --test-dir "{build_dir}" --output-on-failure -C {config}',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
#include <fesa/io/abaqus/input_parser.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cctype>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace fesa::io::abaqus {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
enum class Section {
|
||||||
|
none,
|
||||||
|
heading,
|
||||||
|
node,
|
||||||
|
element
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ElementSection {
|
||||||
|
model::ElementTopology topology = model::ElementTopology::unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string trim(std::string_view text)
|
||||||
|
{
|
||||||
|
auto first = text.begin();
|
||||||
|
auto last = text.end();
|
||||||
|
while (first != last && std::isspace(static_cast<unsigned char>(*first)) != 0) {
|
||||||
|
++first;
|
||||||
|
}
|
||||||
|
while (first != last && std::isspace(static_cast<unsigned char>(*(last - 1))) != 0) {
|
||||||
|
--last;
|
||||||
|
}
|
||||||
|
return std::string(first, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string lower(std::string text)
|
||||||
|
{
|
||||||
|
std::transform(text.begin(), text.end(), text.begin(), [](unsigned char ch) {
|
||||||
|
return static_cast<char>(std::tolower(ch));
|
||||||
|
});
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> split_commas(std::string_view line)
|
||||||
|
{
|
||||||
|
std::vector<std::string> fields;
|
||||||
|
std::size_t start = 0;
|
||||||
|
while (start <= line.size()) {
|
||||||
|
const auto comma = line.find(',', start);
|
||||||
|
const auto end = comma == std::string_view::npos ? line.size() : comma;
|
||||||
|
fields.push_back(trim(line.substr(start, end - start)));
|
||||||
|
if (comma == std::string_view::npos) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start = comma + 1;
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_int(const std::string& text, int& value)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
std::size_t parsed = 0;
|
||||||
|
value = std::stoi(text, &parsed);
|
||||||
|
return parsed == text.size();
|
||||||
|
} catch (const std::invalid_argument&) {
|
||||||
|
return false;
|
||||||
|
} catch (const std::out_of_range&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_double(const std::string& text, double& value)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
std::size_t parsed = 0;
|
||||||
|
value = std::stod(text, &parsed);
|
||||||
|
return parsed == text.size();
|
||||||
|
} catch (const std::invalid_argument&) {
|
||||||
|
return false;
|
||||||
|
} catch (const std::out_of_range&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model::ElementTopology topology_for_type(const std::string& raw_type)
|
||||||
|
{
|
||||||
|
const auto type = lower(raw_type);
|
||||||
|
if (type == "t2d2" || type == "t3d2" || type == "c3d2") {
|
||||||
|
return model::ElementTopology::truss2;
|
||||||
|
}
|
||||||
|
if (type == "b31") {
|
||||||
|
return model::ElementTopology::bar2;
|
||||||
|
}
|
||||||
|
return model::ElementTopology::unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string parameter_value(const std::vector<std::string>& fields, const std::string& key)
|
||||||
|
{
|
||||||
|
const auto expected_key = lower(key);
|
||||||
|
for (std::size_t i = 1; i < fields.size(); ++i) {
|
||||||
|
const auto equals = fields[i].find('=');
|
||||||
|
if (equals == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto field_key = lower(trim(std::string_view{fields[i]}.substr(0, equals)));
|
||||||
|
if (field_key == expected_key) {
|
||||||
|
return trim(std::string_view{fields[i]}.substr(equals + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_node_line(model::Domain& domain, const std::vector<std::string>& fields)
|
||||||
|
{
|
||||||
|
if (fields.size() != 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int id = 0;
|
||||||
|
std::array<double, 3> coordinates{};
|
||||||
|
if (!parse_int(fields[0], id) ||
|
||||||
|
!parse_double(fields[1], coordinates[0]) ||
|
||||||
|
!parse_double(fields[2], coordinates[1]) ||
|
||||||
|
!parse_double(fields[3], coordinates[2])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
domain.add_node(model::Node{core::NodeId{id}, coordinates});
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_element_line(model::Domain& domain,
|
||||||
|
const ElementSection& element_section,
|
||||||
|
const std::vector<std::string>& fields)
|
||||||
|
{
|
||||||
|
if (element_section.topology == model::ElementTopology::unknown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fields.size() != 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int id = 0;
|
||||||
|
int first_node = 0;
|
||||||
|
int second_node = 0;
|
||||||
|
if (!parse_int(fields[0], id) || !parse_int(fields[1], first_node) || !parse_int(fields[2], second_node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
domain.add_element(model::Element{
|
||||||
|
core::ElementId{id},
|
||||||
|
element_section.topology,
|
||||||
|
{core::NodeId{first_node}, core::NodeId{second_node}},
|
||||||
|
core::PropertyId{0}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ParseResult InputParser::parse(std::string_view input) const
|
||||||
|
{
|
||||||
|
ParseResult result{model::Domain{}, core::Status::ok()};
|
||||||
|
Section section = Section::none;
|
||||||
|
ElementSection element_section{};
|
||||||
|
std::size_t start = 0;
|
||||||
|
|
||||||
|
while (start <= input.size()) {
|
||||||
|
const auto newline = input.find('\n', start);
|
||||||
|
const auto end = newline == std::string_view::npos ? input.size() : newline;
|
||||||
|
auto line = input.substr(start, end - start);
|
||||||
|
if (!line.empty() && line.back() == '\r') {
|
||||||
|
line.remove_suffix(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto cleaned = trim(line);
|
||||||
|
if (cleaned.empty() || cleaned.rfind("**", 0) == 0) {
|
||||||
|
if (newline == std::string_view::npos) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start = newline + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleaned.front() == '*') {
|
||||||
|
const auto keyword_fields = split_commas(std::string_view{cleaned}.substr(1));
|
||||||
|
const auto keyword = keyword_fields.empty() ? std::string{} : lower(keyword_fields[0]);
|
||||||
|
if (keyword == "heading") {
|
||||||
|
section = Section::heading;
|
||||||
|
} else if (keyword == "node") {
|
||||||
|
section = Section::node;
|
||||||
|
} else if (keyword == "element") {
|
||||||
|
section = Section::element;
|
||||||
|
const auto type = parameter_value(keyword_fields, "type");
|
||||||
|
element_section.topology = topology_for_type(type);
|
||||||
|
} else {
|
||||||
|
section = Section::none;
|
||||||
|
}
|
||||||
|
} else if (section == Section::node) {
|
||||||
|
parse_node_line(result.domain, split_commas(cleaned));
|
||||||
|
} else if (section == Section::element) {
|
||||||
|
parse_element_line(result.domain, element_section, split_commas(cleaned));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newline == std::string_view::npos) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start = newline + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace fesa::io::abaqus
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <fesa/core/status.hpp>
|
||||||
|
#include <fesa/model/domain.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace fesa::io::abaqus {
|
||||||
|
|
||||||
|
struct ParseResult {
|
||||||
|
model::Domain domain;
|
||||||
|
core::Status status;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputParser {
|
||||||
|
public:
|
||||||
|
ParseResult parse(std::string_view input) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa::io::abaqus
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#include <fesa/io/abaqus/input_parser.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
int fail()
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
fesa::io::abaqus::InputParser parser;
|
||||||
|
|
||||||
|
const std::string input = R"inp(
|
||||||
|
** mesh parser smoke case
|
||||||
|
*HeAdInG
|
||||||
|
small truss model
|
||||||
|
*NoDe
|
||||||
|
1, 0.0, 0.0, 0.0
|
||||||
|
2, 1.5, 0.0, 0.0
|
||||||
|
*ElEmEnT, TyPe=t3d2
|
||||||
|
10, 1, 2
|
||||||
|
)inp";
|
||||||
|
|
||||||
|
const auto result = parser.parse(input);
|
||||||
|
if (!result.status.is_ok()) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& domain = result.domain;
|
||||||
|
if (domain.nodes().size() != 2 || domain.elements().size() != 1) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* first_node = domain.find_node(fesa::core::NodeId{1});
|
||||||
|
if (first_node == nullptr || first_node->coordinates()[0] != 0.0 ||
|
||||||
|
first_node->coordinates()[1] != 0.0 || first_node->coordinates()[2] != 0.0) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* second_node = domain.find_node(fesa::core::NodeId{2});
|
||||||
|
if (second_node == nullptr || second_node->coordinates()[0] != 1.5 ||
|
||||||
|
second_node->coordinates()[1] != 0.0 || second_node->coordinates()[2] != 0.0) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* element = domain.find_element(fesa::core::ElementId{10});
|
||||||
|
if (element == nullptr ||
|
||||||
|
element->topology() != fesa::model::ElementTopology::truss2 ||
|
||||||
|
element->node_ids().size() != 2 ||
|
||||||
|
element->node_ids()[0].value != 1 ||
|
||||||
|
element->node_ids()[1].value != 2 ||
|
||||||
|
element->property_id().value != 0) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string beam_input = R"inp(
|
||||||
|
*NODE
|
||||||
|
1, 0.0, 0.0, 0.0
|
||||||
|
2, 0.0, 2.0, 0.0
|
||||||
|
*ELEMENT, TYPE=B31
|
||||||
|
20, 1, 2
|
||||||
|
)inp";
|
||||||
|
|
||||||
|
const auto beam_result = parser.parse(beam_input);
|
||||||
|
const auto* beam = beam_result.domain.find_element(fesa::core::ElementId{20});
|
||||||
|
if (!beam_result.status.is_ok() ||
|
||||||
|
beam == nullptr ||
|
||||||
|
beam->topology() != fesa::model::ElementTopology::bar2 ||
|
||||||
|
beam->node_ids().size() != 2 ||
|
||||||
|
beam->property_id().value != 0) {
|
||||||
|
return fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user