3 Commits

Author SHA1 Message Date
김경종 f4196efb10 refactor: store runtime objects in domain 2026-06-09 10:08:34 +09:00
김경종 8f24213ab7 feat: add analysis model objects 2026-06-09 09:04:21 +09:00
김경종 fdeac602f4 feat: add domain model foundation 2026-06-08 16:40:04 +09:00
84 changed files with 4969 additions and 10 deletions
+53
View File
@@ -0,0 +1,53 @@
cmake_minimum_required(VERSION 3.20)
project(FESA LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_library(fesa_core STATIC
src/boundary/SinglePointConstraint.cpp
src/core/Domain.cpp
src/element/Mitc4Element.cpp
src/load/NodalLoad.cpp
src/material/LinearElasticMaterial.cpp
src/property/ShellProperty.cpp
)
target_include_directories(fesa_core PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)
enable_testing()
add_executable(fesa_domain_tests
tests/core/boundary_condition_test.cpp
tests/core/domain_bootstrap_test.cpp
tests/core/domain_model_object_test.cpp
tests/core/domain_storage_test.cpp
tests/core/element_definition_test.cpp
tests/core/load_definition_test.cpp
tests/core/material_definition_test.cpp
tests/core/model_types_test.cpp
tests/core/node_test.cpp
tests/core/property_definition_test.cpp
tests/core/step_definition_test.cpp
)
target_link_libraries(fesa_domain_tests PRIVATE fesa_core)
add_test(NAME domain.bootstrap COMMAND fesa_domain_tests)
set_tests_properties(domain.bootstrap PROPERTIES LABELS "domain;core")
add_executable(fesa_model_object_tests
tests/boundary/boundary_base_test.cpp
tests/element/element_base_test.cpp
tests/element/mitc4_element_model_test.cpp
tests/load/load_base_test.cpp
tests/material/material_base_test.cpp
tests/model_object_main.cpp
tests/property/shell_property_test.cpp
)
target_link_libraries(fesa_model_object_tests PRIVATE fesa_core)
add_test(NAME model-object.base COMMAND fesa_model_object_tests)
set_tests_properties(model-object.base PROPERTIES LABELS "model-object;core")
+2 -2
View File
@@ -64,9 +64,9 @@
**Tradeoff**: Implementation can be blocked until required reference artifacts are supplied.
## ADR-010: Domain, AnalysisModel, And AnalysisState Are Separate
**Decision**: `Domain` owns parsed model definition, `AnalysisModel` owns the active step execution view, and `AnalysisState` owns mutable solution and iteration state.
**Decision**: `Domain` owns the validated runtime model object graph created from parsed input, `AnalysisModel` owns the active step execution view, and `AnalysisState` owns mutable solution and iteration state.
**Reason**: This prevents equation ids, displacement vectors, residuals, and future nonlinear/time states from leaking into input model objects. It also keeps the static solver compatible with future nonlinear, dynamic, and thermal workflows.
**Reason**: This prevents parser DTOs, equation ids, displacement vectors, residuals, and future nonlinear/time states from leaking into the persistent domain model. It also keeps the static solver compatible with future nonlinear, dynamic, and thermal workflows.
**Tradeoff**: The initial implementation has more object boundaries than a single monolithic model container.
+6 -3
View File
@@ -80,7 +80,7 @@ tests/
The current repository may not yet contain all directories. They are intended ownership boundaries for implementation planning.
## Core Runtime Objects
- `Domain`: owns model definitions from input: nodes, elements, materials, properties, sets, boundary conditions, loads, and step definitions.
- `Domain`: owns the validated runtime model object graph created from input: nodes, elements, materials, properties, sets, boundary conditions, loads, and step definitions.
- `AnalysisModel`: step-local execution view over active elements, loads, boundary conditions, properties, materials, and equation system view.
- `AnalysisState`: owns changing physical quantities and iteration/time state such as displacement, velocity, acceleration, temperature, forces, residual, current time, increment, and element/integration-point state.
- `Node`: stores node id, coordinates, and six DOF values in order `U1, U2, U3, UR1, UR2, UR3`.
@@ -100,7 +100,7 @@ The current repository may not yet contain all directories. They are intended ow
## State Ownership
### Domain
`Domain` represents parsed model definition and should not store equation ids, solver vectors, current displacement, or iteration state.
`Domain` represents the validated model definition as runtime objects and should not store parser DTOs, equation ids, solver vectors, current displacement, or iteration state.
Included:
- nodes and elements
@@ -109,6 +109,8 @@ Included:
- loads and boundary conditions
- analysis step definitions
Parser-style definition records may exist as temporary input or factory records, but they are not persistent `Domain` storage. `Domain` owns runtime objects such as `Element`, `Material`, `ShellProperty`, `Load`, and `BoundaryCondition`.
### AnalysisModel
`AnalysisModel` is built per active step. It references `Domain` objects by id or stable reference and defines what participates in the current solve.
@@ -152,8 +154,9 @@ Results use:
```text
Abaqus input file
-> InputParser
-> temporary parsed records
-> Factory/Registry object creation
-> Domain
-> Domain runtime object graph
-> StepDefinition loop
-> AnalysisModel
-> DofManager
+28 -1
View File
@@ -8,6 +8,8 @@
- Multi-Agent coordination files live under `docs/`: `docs/PLAN.md`, `docs/PROGRESS.md`, and `docs/WORKNOTE.md`.
- Project plan/design documents live under `docs/project-plan/`.
- Common per-run AI Agent rules live in `docs/AGENT_RULES.md`.
- Domain model foundation phase exists under `phases/domain-model-foundation/` and the first C++/MSVC CMake/CTest core implementation slice has been added.
- Analysis model object phase exists under `phases/analysis-model-objects/` and adds identity-only model objects for Domain ownership.
## Completed
- Defined the nine-step solver development workflow.
@@ -23,9 +25,22 @@
- Documented the canonical git remote `https://teagit.mimi1011.synology.me/baram2584/FESADev.git`, default remote `origin`, shared baseline branch `dev`, and `codex/<short-task-name>` work branch convention in `AGENTS.md`, `docs/AGENT_RULES.md`, and `docs/PLAN.md`.
- Confirmed local HDF5 `2.1.1` installation at `C:\Program Files\HDF_Group\HDF5\2.1.1` and recorded the `HDF5_ROOT`/`HDF5_DIR` discovery policy in architecture, agent, skill, and handoff documentation.
- Recorded the standing user instruction in `AGENTS.md` that Agent-authored work should be validated, committed, and pushed to `origin` after completion.
- Created `docs/implementation-plans/domain-model-foundation-implementation-plan.md`.
- Added root CMake/CTest bootstrap for `fesa_core` and `fesa_domain_tests`.
- Implemented `Domain` model-definition storage for nodes, MITC4 element definitions, linear elastic material definitions, shell properties, node sets, element sets, boundary conditions, nodal loads, and linear static step definitions.
- Added Domain foundation C++ tests under `tests/core/`.
- Fixed `scripts/validate_workspace.py` so CMake/CTest validation can use the default CMake install path when CMake is not on the shell PATH.
- Created `docs/implementation-plans/analysis-model-objects-implementation-plan.md`.
- Added identity-only model object base classes and concrete skeletons for `Element`/`Mitc4Element`, `Material`/`LinearElasticMaterial`, `ShellProperty`, `Load`/`NodalLoad`, and `BoundaryCondition`/`SinglePointConstraint`.
- Extended `Domain` with RAII `std::unique_ptr` ownership APIs for polymorphic element, material, load, and boundary model objects while preserving existing `*Definition` storage APIs.
- Added `fesa_model_object_tests` CTest target and model-object tests under `tests/element/`, `tests/material/`, `tests/property/`, `tests/load/`, and `tests/boundary/`.
- Created `docs/implementation-plans/domain-runtime-storage-implementation-plan.md` and `phases/domain-runtime-storage/`.
- Migrated `Domain` canonical storage away from parser-style definition DTOs to runtime model objects for elements, materials, shell properties, loads, and boundary conditions.
- Moved `ElementType` into `ModelTypes.hpp` so runtime element interfaces no longer include `ElementDefinition.hpp`.
- Updated Domain storage tests so element sets and linear static step indices validate against runtime element, load, and boundary containers.
## In Progress
- Ready for the next stage: new solver feature requirements analysis for `mitc4-linear-static-shell`.
- Ready for the next upstream MITC4 stage: new solver feature requirements analysis for `mitc4-linear-static-shell`.
## Next Tasks
1. Create `docs/requirements/mitc4-linear-static-shell.md`.
@@ -36,6 +51,18 @@
6. Create `docs/implementation-plans/mitc4-linear-static-shell-implementation-plan.md`.
## Last Validation
- 2026-06-09: After analysis model object implementation, `python -m unittest discover -s scripts -p "test_*.py"` passed. 89 tests ran successfully.
- 2026-06-09: After analysis model object implementation, `python scripts/validate_workspace.py` configured CMake with Visual Studio 17 2022 x64, built Debug targets, ran CTest, and passed.
- 2026-06-09: After analysis model object implementation, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` passed. 2 CTest executables ran successfully.
- 2026-06-09: After analysis model object implementation, `git diff --check` passed.
- 2026-06-09: After Domain runtime storage migration, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` passed. 2 CTest executables ran successfully.
- 2026-06-09: After Domain runtime storage migration, `python -m unittest discover -s scripts -p "test_*.py"` passed. 89 tests ran successfully.
- 2026-06-09: After Domain runtime storage migration, `python scripts/validate_workspace.py` configured CMake with Visual Studio 17 2022 x64, built Debug targets, ran CTest, and passed.
- 2026-06-09: After Domain runtime storage migration, `git diff --check` passed with only Git line-ending normalization warnings.
- 2026-06-08: After Domain model foundation implementation, `python -m unittest discover -s scripts -p "test_*.py"` passed. 89 tests ran successfully.
- 2026-06-08: After Domain model foundation implementation, `python scripts/validate_workspace.py` configured CMake with Visual Studio 17 2022 x64, built Debug targets, ran CTest, and passed.
- 2026-06-08: After Domain model foundation implementation, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` passed. 1 domain/core test executable ran successfully.
- 2026-06-08: After Domain model foundation implementation, `git diff --check` passed.
- 2026-06-08: After recording the standing commit/push instruction in `AGENTS.md`, `python -m unittest discover -s scripts -p "test_*.py"` passed. 85 tests ran successfully.
- 2026-06-08: After recording the standing commit/push instruction in `AGENTS.md`, `python scripts/validate_workspace.py` passed through the expected no-op path because no root `CMakeLists.txt` exists yet.
- 2026-06-08: After recording the standing commit/push instruction in `AGENTS.md`, `git diff --check` passed with only Git line-ending normalization warnings.
+3
View File
@@ -13,6 +13,9 @@ During planning, standard checks for `HDF5_ROOT`, `HDF5_DIR`, `h5dump`, and obvi
### 2026-06-08 - HDF5 Installed Under HDF_Group
The local HDF5 install was later confirmed at `C:\Program Files\HDF_Group\HDF5\2.1.1`, and `h5dump.exe -V` reports `2.1.1`. Future CMake work should prefer `HDF5_ROOT=C:\Program Files\HDF_Group\HDF5\2.1.1` or `HDF5_DIR=C:\Program Files\HDF_Group\HDF5\2.1.1\cmake`. Keep the old 2026-06-05 note as historical context for stale sessions, but do not treat HDF5 as currently missing on this machine.
### 2026-06-08 - CMake Installed But Not On Shell PATH
`cmake.exe` exists at `C:\Program Files\CMake\bin\cmake.exe`, but the Codex shell did not resolve bare `cmake`. `scripts/validate_workspace.py` was updated to resolve bare `cmake` and `ctest` validation commands to the known CMake install path when needed. Use `python scripts/validate_workspace.py` as the canonical validation path, or call `C:\Program Files\CMake\bin\cmake.exe` / `ctest.exe` explicitly for manual commands.
### 2026-06-05 - Validation Currently Has No CMake Project
`python scripts/validate_workspace.py` currently exits successfully through the no-op path because there is no root `CMakeLists.txt`. This is expected until the C++ solver project is bootstrapped. Once CMake is introduced, validation must configure, build, and run CTest.
@@ -0,0 +1,73 @@
# Analysis Model Objects Build/Test Report
## Metadata
- feature_id: analysis-model-objects
- source_implementation_report: N/A
- source_implementation_plan: docs/implementation-plans/analysis-model-objects-implementation-plan.md
- status: pass-for-next-implementation-stage
- owner_agent: build-test-executor-agent
- date: 2026-06-09
## Execution Environment
- os: Windows
- generator: Visual Studio 17 2022
- platform: x64
- config: Debug
- build_dir: build/msvc-debug
- active_override_env_vars: none
- command_discovery_path: default CMake/MSVC x64 Debug through `python scripts/validate_workspace.py`
## Command Log Summary
| order | command | exit_code | duration | stdout_stderr_tail |
| --- | --- | --- | --- | --- |
| 1 | `python -m unittest discover -s scripts -p "test_*.py"` | 0 | <1s | 89 tests passed |
| 2 | `python scripts/validate_workspace.py` | 0 | ~4s | CMake configure, Debug build, and CTest passed |
| 3 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` | 0 | <1s | 2 CTest executables passed |
| 4 | `git diff --check` | 0 | <1s | no whitespace errors; line-ending normalization warnings may appear |
## Validation Results
| validation_stage | result | evidence |
| --- | --- | --- |
| harness self-test | pass | Python unittest discovery passed 89 tests |
| configure | pass | CMake generated `build/msvc-debug` with Visual Studio 17 2022 x64 |
| build | pass | `fesa_core.lib`, `fesa_domain_tests.exe`, and `fesa_model_object_tests.exe` built |
| CTest | pass | `domain.bootstrap` and `model-object.base` passed |
| feature-specific tests | pass | model-object and domain CTest filters passed |
## Failure Classification
- classification: N/A
- primary_failure: N/A
- first_failed_command: N/A
- evidence_tail: validation succeeded
## Failed Test Inventory
| test_name | label | command | failure_summary |
| --- | --- | --- | --- |
| N/A | N/A | N/A | N/A |
## Handoff Recommendation
| target_agent | reason | required_input |
| --- | --- | --- |
| Parser/Factory Implementation Agent | New model objects can be populated from future parser/factory mappings | model object headers and compatibility note |
| AnalysisModel Implementation Agent | Domain now exposes identity-only model objects through const base APIs | Domain object ownership APIs |
| Build/Test Executor Agent | Re-run after downstream C++ changes | validation commands in this report |
| Reference Verification Agent | Not ready; this phase produces no solver HDF5 result | N/A |
## No-Change Assertion
- source_files_modified: true
- test_files_modified: true
- cmake_files_modified: true
- reference_artifacts_modified: false
- tolerance_policies_modified: false
- notes: This phase added model object classes and Domain ownership APIs only. It did not run reference solvers, generate reference artifacts, change tolerances, or claim numerical MITC4 correctness.
## Open Issues
- Parser/factory mapping from core `*Definition` DTOs to model objects remains deferred.
- MITC4 stiffness, material constitutive behavior, shell section stiffness, assembly, solver, and HDF5 output remain future phases.
@@ -0,0 +1,74 @@
# Domain Model Foundation Build/Test Report
## Metadata
- feature_id: domain-model-foundation
- source_implementation_report: N/A
- source_implementation_plan: docs/implementation-plans/domain-model-foundation-implementation-plan.md
- status: pass-for-next-implementation-stage
- owner_agent: build-test-executor-agent
- date: 2026-06-08
## Execution Environment
- os: Windows
- generator: Visual Studio 17 2022
- platform: x64
- config: Debug
- build_dir: build/msvc-debug
- active_override_env_vars: none
- command_discovery_path: default CMake/MSVC x64 Debug
## Command Log Summary
| order | command | exit_code | duration | stdout_stderr_tail |
| --- | --- | --- | --- | --- |
| 1 | `git status --short --branch` | 0 | <1s | branch `codex/domain-model-foundation`; only Agent-authored changes observed |
| 2 | `git remote -v` | 0 | <1s | `origin` points to `https://teagit.mimi1011.synology.me/baram2584/FESADev.git` |
| 3 | `python -m unittest discover -s scripts -p "test_*.py"` | 0 | <1s | 89 tests passed |
| 4 | `python scripts/validate_workspace.py` | 0 | ~8s | configure, build, and CTest passed; validation succeeded |
| 5 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` | 0 | <1s | 1 domain/core CTest passed |
| 6 | `git diff --check` | 0 | <1s | no whitespace errors |
## Validation Results
| validation_stage | result | evidence |
| --- | --- | --- |
| harness self-test | pass | `python -m unittest discover -s scripts -p "test_*.py"` ran 89 tests successfully |
| configure | pass | CMake generated `build/msvc-debug` with Visual Studio 17 2022 x64 |
| build | pass | `fesa_core.lib` and `fesa_domain_tests.exe` built in Debug |
| CTest | pass | `domain.bootstrap` passed with labels `domain;core` |
| feature-specific tests | pass | Domain model storage tests are compiled into `fesa_domain_tests` |
## Failure Classification
- classification: N/A
- primary_failure: N/A
- first_failed_command: N/A
- evidence_tail: validation succeeded
## Failed Test Inventory
| test_name | label | command | failure_summary |
| --- | --- | --- | --- |
| N/A | N/A | N/A | N/A |
## Handoff Recommendation
| target_agent | reason | required_input |
| --- | --- | --- |
| Implementation Agent | Continue with parser/factory or AnalysisModel work after upstream contracts are ready | Domain headers, tests, and implementation plan |
| Build/Test Executor Agent | Re-run after downstream C++ changes | validation commands in this report |
| Reference Verification Agent | Not ready; this phase produces no solver HDF5 result | N/A |
## No-Change Assertion
- source_files_modified: true
- test_files_modified: true
- cmake_files_modified: true
- reference_artifacts_modified: false
- tolerance_policies_modified: false
- notes: This phase added C++ core model-definition storage only. It did not run reference solvers, generate reference artifacts, or change tolerance policy.
## Open Issues
- MITC4 requirements, research, formulation, I/O contract, reference models, and full implementation plan remain separate upstream stages.
- Parser-to-Domain factory API is intentionally deferred.
@@ -0,0 +1,179 @@
# Analysis Model Objects Implementation Plan
## Metadata
- feature_id: analysis-model-objects
- source_requirement: AGENTS.md; docs/ARCHITECTURE.md; docs/ADR.md
- source_research: N/A for this architecture/model-object slice
- source_formulation: N/A for this architecture/model-object slice
- source_numerical_review: N/A for this architecture/model-object slice
- source_io_definition: docs/ARCHITECTURE.md Domain and factory/registry ownership rules
- source_reference_models: N/A for this architecture/model-object slice
- status: ready-for-implementation
- owner_agent: implementation-planning-agent
- date: 2026-06-09
## Readiness Check
| input | required_status | observed_status | decision |
| --- | --- | --- | --- |
| architecture | model object boundaries documented | documented in docs/ARCHITECTURE.md and ADR-004/010/011 | proceed |
| domain foundation | base Domain storage available | implemented in `domain-model-foundation` | proceed |
| formulation | not required for identity-only model object skeletons | N/A | proceed |
| numerical_review | not required for identity-only model object skeletons | N/A | proceed |
| io_definition | parser DTO compatibility sufficient | existing `*Definition` objects remain input DTOs | proceed |
| reference_models | not required because this slice produces no solver result | N/A | proceed |
## Implementation Scope
- included_behavior: C++17 model object classes for nodes, elements, materials, shell properties, loads, and boundary conditions.
- included_behavior: abstract base classes for element, material, load, and boundary condition model objects.
- included_behavior: MITC4 element model skeleton with id, connectivity, property id, 4-node count, and 24-DOF count.
- included_behavior: Domain RAII ownership for polymorphic model objects through `std::unique_ptr`.
- included_behavior: compatibility with existing `*Definition` value objects and tests.
- excluded_behavior: MITC4 stiffness, Jacobian, MITC shear interpolation, constitutive matrices, global force assembly, boundary-condition elimination, equation numbering, solver vectors, reaction recovery, HDF5 output, parser/factory implementation, and reference comparison.
- non_goals: numerical correctness claims, release readiness, reference-solver execution, and reference artifact generation.
## Model Object Contract
`Domain` remains the owner of parsed model definition. This phase adds runtime model objects that can later be used by `AnalysisModel`, factories, and element/material implementations without storing analysis state in `Domain`.
Model object responsibilities:
- `Node`: id and reference coordinates only; fixed six-DOF count from project convention.
- `Element`: identity, element type, connectivity, property reference, and node count only.
- `Mitc4Element`: MITC4 model skeleton with four node ids and 24 element DOFs only.
- `Material`: material identity only.
- `LinearElasticMaterial`: id, Young's modulus, and Poisson ratio only.
- `ShellProperty`: property id, material id, and thickness only.
- `Load`: load kind only.
- `NodalLoad`: node id, DOF, and scalar value only.
- `BoundaryCondition`: boundary kind only.
- `SinglePointConstraint`: node id, DOF, and prescribed scalar value only.
Excluded from all model objects:
- equation ids
- sparse matrix data
- displacement, residual, reaction, velocity, acceleration, or temperature vectors
- current time, increment, or iteration counters
- element integration point state
- MKL, TBB, HDF5, or reference-comparison handles
Existing `*Definition` value objects remain parser/factory DTOs for now. This phase must not delete them. Later parser/factory work may either map DTOs into model objects or retire DTOs with an explicit migration plan.
Compatibility decision:
- Existing `ElementDefinition`, `LinearElasticMaterialDefinition`, `ShellPropertyDefinition`, `BoundaryCondition`, `NodalLoadDefinition`, and `LinearStaticStepDefinition` classes remain under `include/fesa/core/` as value-style input DTOs.
- New runtime model objects live under ownership namespaces: `fesa::element`, `fesa::material`, `fesa::property`, `fesa::load`, and `fesa::boundary`.
- `Domain` supports both existing DTO storage APIs and new polymorphic model-object ownership APIs during the transition.
- Parser/factory work must make an explicit choice to map DTOs to model objects or replace DTOs in a separate phase. This phase does not decide parser behavior.
## Work Breakdown
| task_id | order | purpose | upstream_trace | depends_on | expected_test_first |
| --- | --- | --- | --- | --- | --- |
| AMO-001 | 1 | refine `Node` model class contract | ADR-010; docs/ARCHITECTURE.md Node | none | AMO-TEST-001 |
| AMO-002 | 2 | add abstract `Element` base interface | ADR-004; docs/ARCHITECTURE.md Element | AMO-001 | AMO-TEST-002 |
| AMO-003 | 3 | add MITC4 element model skeleton | AGENTS.md MITC4 scope | AMO-002 | AMO-TEST-003 |
| AMO-004 | 4 | add material base and linear elastic material object | docs/ARCHITECTURE.md Material | AMO-002 | AMO-TEST-004 |
| AMO-005 | 5 | add shell property model object | docs/ARCHITECTURE.md Property | AMO-004 | AMO-TEST-005 |
| AMO-006 | 6 | add load base and nodal load object | docs/ARCHITECTURE.md loads | AMO-002 | AMO-TEST-006 |
| AMO-007 | 7 | add boundary base and single-point constraint object | ADR-012 | AMO-002 | AMO-TEST-007 |
| AMO-008 | 8 | extend Domain with polymorphic object ownership | ADR-010; ADR-011 | AMO-003 to AMO-007 | AMO-TEST-008 |
| AMO-009 | 9 | preserve `*Definition` compatibility | domain-model-foundation plan | AMO-008 | AMO-TEST-009 |
| AMO-010 | 10 | record build/test evidence and handoff | docs/build-test-reports/README.md | AMO-009 | AMO-TEST-010 |
## TDD Test Plan
| test_id | order | test_type | red_condition | green_condition | linked_task | command |
| --- | --- | --- | --- | --- | --- | --- |
| AMO-TEST-001 | 1 | unit | `Node::dofCount()` missing | Node model tests pass | AMO-001 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
| AMO-TEST-002 | 2 | unit | `fesa/element/Element.hpp` missing | Element base polymorphism tests pass | AMO-002 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
| AMO-TEST-003 | 3 | unit | `Mitc4Element` missing | MITC4 model skeleton tests pass | AMO-003 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
| AMO-TEST-004 | 4 | unit | material model headers missing | material base and linear elastic tests pass | AMO-004 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
| AMO-TEST-005 | 5 | unit | shell property model missing | shell property tests pass | AMO-005 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
| AMO-TEST-006 | 6 | unit | load model headers missing | load base and nodal load tests pass | AMO-006 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
| AMO-TEST-007 | 7 | unit | boundary model headers missing | boundary base and SPC tests pass | AMO-007 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
| AMO-TEST-008 | 8 | unit | Domain polymorphic APIs missing | Domain owns and retrieves model objects by base interface | AMO-008 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` |
| AMO-TEST-009 | 9 | regression | existing definition tests fail | existing Domain definition tests remain passing | AMO-009 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
| AMO-TEST-010 | 10 | validation | report evidence missing | validation report records passing commands or classified failure | AMO-010 | `python scripts/validate_workspace.py` |
## CMake/CTest Plan
- target_candidates: `fesa_core`, `fesa_domain_tests`, `fesa_model_object_tests`
- labels: `domain`, `core`, `model-object`, `element`, `material`, `property`, `load`, `boundary`
- msvc_config: Debug
- expected_feature_command: `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"`
- workspace_validation: `python scripts/validate_workspace.py`
## Candidate Files and Ownership
| file_candidate | purpose | owner_boundary | notes |
| --- | --- | --- | --- |
| `include/fesa/core/Node.hpp` | existing node model object | core model definition | no solver state |
| `include/fesa/element/Element.hpp` | abstract element model base | element model identity | no stiffness API |
| `include/fesa/element/Mitc4Element.hpp` | MITC4 element model skeleton | element model identity | no formulation implementation |
| `src/element/Mitc4Element.cpp` | MITC4 model accessors | element model identity | no numerical behavior |
| `include/fesa/material/Material.hpp` | abstract material model base | material identity | no constitutive matrix |
| `include/fesa/material/LinearElasticMaterial.hpp` | linear elastic material model | material input values | no stress update |
| `src/material/LinearElasticMaterial.cpp` | linear elastic material accessors | material input values | no numerical behavior |
| `include/fesa/property/ShellProperty.hpp` | shell property model | property input values | no section stiffness |
| `src/property/ShellProperty.cpp` | shell property validation/accessors | property input values | thickness validation only |
| `include/fesa/load/Load.hpp` | abstract load model base | load identity | no assembly |
| `include/fesa/load/NodalLoad.hpp` | nodal load model | load input values | no global vector behavior |
| `src/load/NodalLoad.cpp` | nodal load accessors | load input values | no assembly |
| `include/fesa/boundary/BoundaryCondition.hpp` | abstract boundary model base | boundary identity | no matrix application |
| `include/fesa/boundary/SinglePointConstraint.hpp` | SPC boundary model | boundary input values | no reaction recovery |
| `src/boundary/SinglePointConstraint.cpp` | SPC accessors | boundary input values | no matrix application |
| `include/fesa/core/Domain.hpp` | Domain ownership extension | core ownership | preserve existing APIs |
| `src/core/Domain.cpp` | Domain ownership implementation | core ownership | no solver state |
| `tests/**` | TDD coverage | tests | write before production changes |
## Acceptance Traceability Matrix
| requirement_id | task_id | test_id | reference_model_id | acceptance_criterion | status |
| --- | --- | --- | --- | --- | --- |
| AMO-REQ-001 Node is model data only | AMO-001 | AMO-TEST-001 | N/A | Node tests pass and no solver state added | draft |
| AMO-REQ-002 Element base is identity-only | AMO-002 | AMO-TEST-002 | N/A | Element base tests pass | draft |
| AMO-REQ-003 MITC4 model skeleton has 4 nodes and 24 DOFs | AMO-003 | AMO-TEST-003 | N/A | MITC4 model tests pass | draft |
| AMO-REQ-004 Material model stores input values only | AMO-004 | AMO-TEST-004 | N/A | material tests pass | draft |
| AMO-REQ-005 Shell property validates thickness only | AMO-005 | AMO-TEST-005 | N/A | shell property tests pass | draft |
| AMO-REQ-006 Load and boundary models do not assemble/apply | AMO-006; AMO-007 | AMO-TEST-006; AMO-TEST-007 | N/A | load/boundary tests pass and APIs remain value accessors | draft |
| AMO-REQ-007 Domain owns polymorphic model objects via RAII | AMO-008 | AMO-TEST-008 | N/A | Domain ownership tests pass | draft |
| AMO-REQ-008 Existing definition APIs remain compatible | AMO-009 | AMO-TEST-009 | N/A | existing domain tests pass | draft |
## Validation Commands
```powershell
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
```
## Risks and Downstream Handoff
### Implementation Agent
- Keep model objects identity-only until formulation and solver stages define numerical APIs.
- Preserve existing `*Definition` tests.
- Prefer RAII ownership through `std::unique_ptr` and const retrieval.
### Build/Test Executor Agent
- Use Visual Studio 17 2022, x64, Debug, and `build/msvc-debug`.
- Use `python scripts/validate_workspace.py` as canonical validation.
### Correction Agent
- Implementation-owned failures are expected to be compile or unit-test failures in model object headers, model object sources, CMake registration, or Domain ownership methods.
- Upstream-contract failures include requests to add numerical MITC4 behavior or solver state in this phase.
### Reference Verification Agent
- No reference verification is required for this phase.
- This phase produces no HDF5 result and consumes no reference artifacts.
## Open Issues
- Parser/factory mapping from `*Definition` DTOs to model objects remains deferred.
- MITC4 formulation, material constitutive behavior, shell section stiffness, assembly, solver, and HDF5 output remain separate future phases.
@@ -0,0 +1,190 @@
# Domain Model Foundation Implementation Plan
## Metadata
- feature_id: domain-model-foundation
- source_requirement: AGENTS.md; docs/ARCHITECTURE.md; docs/ADR.md
- source_research: N/A for this architecture foundation slice
- source_formulation: N/A for this architecture foundation slice
- source_numerical_review: N/A for this architecture foundation slice
- source_io_definition: docs/ARCHITECTURE.md input-model ownership rules
- source_reference_models: N/A for this architecture foundation slice
- status: ready-for-implementation
- owner_agent: implementation-planning-agent
- date: 2026-06-08
## Readiness Check
| input | required_status | observed_status | decision |
| --- | --- | --- | --- |
| architecture | Domain, AnalysisModel, AnalysisState, DofManager boundaries documented | documented in docs/ARCHITECTURE.md and docs/ADR.md | proceed |
| requirements | Domain ownership rules documented | documented in AGENTS.md and docs/ARCHITECTURE.md | proceed |
| formulation | not required for model-container storage | N/A | proceed |
| numerical_review | not required for model-container storage | N/A | proceed |
| io_definition | parser contract not required; semantic storage boundary documented | sufficient for Domain foundation | proceed |
| reference_models | not required because this slice produces no solver result | N/A | proceed |
## Implementation Scope
- included_behavior: C++17 core model-definition storage for nodes, element definitions, material definitions, shell property definitions, sets, boundary conditions, nodal loads, and linear static step definitions.
- included_behavior: CMake/CTest bootstrap for MSVC x64 Debug validation.
- included_behavior: deterministic id validation and const retrieval APIs for stored model definitions.
- excluded_behavior: MITC4 stiffness, shape functions, Jacobian checks, assembly, solver, reaction recovery, HDF5 output, Abaqus parser, MKL, TBB, and reference comparison.
- non_goals: numerical correctness claims, release readiness, Abaqus/Nastran execution, and stored reference artifact generation.
## Domain Contract
`Domain` owns parsed model definitions only. It preserves the model that an input parser or factory layer creates and should be treated as immutable by analysis code after parsing/building is complete.
Included model definitions:
- nodes
- element definitions
- material definitions
- shell property definitions
- node sets
- element sets
- boundary conditions
- nodal loads
- analysis step definitions
Excluded state:
- equation ids
- sparse matrix structure or values
- reduced or full displacement vectors
- residual vectors
- reaction vectors
- current time
- increment counters
- nonlinear iteration counters
- element integration point state
- MKL, TBB, or HDF5 handles
Boundary responsibilities:
- `DofManager` owns active DOF discovery, constrained/free DOF mapping, equation numbering, sparse pattern connectivity, and full-vector reconstruction.
- `AnalysisModel` owns the step-local execution view over active domain objects. It references `Domain` objects by id or stable reference and does not copy the whole domain.
- `AnalysisState` owns mutable solution, residual, load vector, time/increment, and future element state.
Common data rules:
- All ids use signed 64-bit storage.
- Node DOF order is `U1, U2, U3, UR1, UR2, UR3`.
- Coordinates and scalar physical values use `double`.
- Units are user-consistent and are not enforced or converted by `Domain`.
- Duplicate ids fail with `std::invalid_argument`.
- Insertions that reference missing required objects fail with `std::invalid_argument`.
- Direct lookup of a missing object fails with `std::out_of_range`.
- Optional `find*` lookup APIs return `nullptr` for missing objects.
- Retrieval APIs return const references or const pointers.
## Work Breakdown
| task_id | order | purpose | upstream_trace | depends_on | expected_test_first |
| --- | --- | --- | --- | --- | --- |
| TASK-001 | 1 | Bootstrap CMake, core target, and CTest for Domain tests | ADR-002 | none | TEST-001 |
| TASK-002 | 2 | Define signed 64-bit id aliases and six-DOF ordering constants | ADR-014; AGENTS.md MITC4 scope | TASK-001 | TEST-002 |
| TASK-003 | 3 | Add `Node` and node storage/retrieval in `Domain` | ADR-010; docs/ARCHITECTURE.md Domain section | TASK-002 | TEST-003 |
| TASK-004 | 4 | Add MITC4 element definition storage without element computation | ADR-004; docs/ARCHITECTURE.md Core Runtime Objects | TASK-003 | TEST-004 |
| TASK-005 | 5 | Add material, shell property, node set, and element set storage | docs/ARCHITECTURE.md Domain included data | TASK-004 | TEST-005 |
| TASK-006 | 6 | Add boundary condition, nodal load, and linear static step definition storage | ADR-012; docs/ARCHITECTURE.md Domain included data | TASK-005 | TEST-006 |
| TASK-007 | 7 | Lock down Domain invariants and failed-insert stability | ADR-010; docs/AGENT_RULES.md boundaries | TASK-006 | TEST-007 |
| TASK-008 | 8 | Record build/test evidence and handoff status | docs/build-test-reports/README.md | TASK-007 | TEST-008 |
## TDD Test Plan
| test_id | order | test_type | red_condition | green_condition | linked_task | command |
| --- | --- | --- | --- | --- | --- | --- |
| TEST-001 | 1 | CTest bootstrap | `domain.bootstrap` is not registered or cannot run | `domain.bootstrap` passes under MSVC Debug | TASK-001 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain.bootstrap` |
| TEST-002 | 2 | unit | `fesa/core/ModelTypes.hpp` is missing | id aliases are 64-bit and DOF ordinals are 0..5 | TASK-002 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
| TEST-003 | 3 | unit | `Node` and `Domain` node APIs are missing | add/find/direct lookup/duplicate node tests pass | TASK-003 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
| TEST-004 | 4 | unit | element definition APIs are missing | element add/find/direct lookup/connectivity/missing-node tests pass | TASK-004 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
| TEST-005 | 5 | unit | material, property, and set APIs are missing | material/property/set validation tests pass | TASK-005 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
| TEST-006 | 6 | unit | boundary, load, and step APIs are missing | BC/load/step storage tests pass | TASK-006 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
| TEST-007 | 7 | unit | failed insertion mutates Domain or mutable access leaks | invariant and failed-insert stability tests pass | TASK-007 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
| TEST-008 | 8 | validation | build/test evidence is missing | build/test report records passing validation or classified failure | TASK-008 | `python scripts/validate_workspace.py` |
## CMake/CTest Plan
- target_candidates: `fesa_core`, `fesa_domain_tests`
- add_test_needs: `domain.bootstrap` initially, then Domain behavior tests under the same `domain` CTest filter.
- labels: `domain`, `core`
- msvc_config: Debug
- expected_feature_command: `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain`
- workspace_validation: `python scripts/validate_workspace.py`
## Candidate Files and Ownership
| file_candidate | purpose | owner_boundary | notes |
| --- | --- | --- | --- |
| `CMakeLists.txt` | root CMake project, core library, CTest registration | build/test bootstrap | MSVC x64 Debug baseline |
| `include/fesa/core/ModelTypes.hpp` | id aliases and DOF constants | core model definitions | no solver state |
| `include/fesa/core/Node.hpp` | node id and coordinate definition | core model definitions | no displacement storage |
| `include/fesa/core/ElementDefinition.hpp` | element id, type, connectivity, property reference | core model definitions | no stiffness behavior |
| `include/fesa/core/MaterialDefinition.hpp` | linear elastic material input definition | core model definitions | no constitutive matrix |
| `include/fesa/core/PropertyDefinition.hpp` | shell property input definition | core model definitions | no section stiffness |
| `include/fesa/core/BoundaryCondition.hpp` | prescribed DOF value definition | core model definitions | no matrix application |
| `include/fesa/core/LoadDefinition.hpp` | nodal load definition | core model definitions | no global vector assembly |
| `include/fesa/core/StepDefinition.hpp` | linear static step definition | core model definitions | no analysis driver |
| `include/fesa/core/Domain.hpp` | Domain public storage/retrieval API | core model definitions | const retrieval only |
| `src/core/Domain.cpp` | Domain validation and storage implementation | core model definitions | no external dependencies |
| `tests/core/domain_bootstrap_test.cpp` | CTest bootstrap executable | test | created before production code |
| `tests/core/model_types_test.cpp` | id and DOF tests | test | optional separate executable |
| `tests/core/domain_storage_test.cpp` | Domain behavior tests | test | required before production changes |
## Data Flow Contract
1. A future Abaqus parser and factory/registry layer creates semantic model definitions.
2. `Domain` stores those definitions and validates duplicate ids and missing required references.
3. A future `AnalysisModel` builds a step-local execution view from `Domain` ids or stable references.
4. A future `DofManager` derives active DOFs and equation ids outside `Domain`.
5. A future `AnalysisState` stores solution and iteration state outside `Domain`.
## Acceptance Traceability Matrix
| requirement_id | task_id | test_id | reference_model_id | acceptance_criterion | status |
| --- | --- | --- | --- | --- | --- |
| DOM-REQ-001 Domain owns parsed model definitions | TASK-003 to TASK-006 | TEST-003 to TEST-006 | N/A | storage and const retrieval tests pass | draft |
| DOM-REQ-002 Domain excludes solver state | TASK-007 | TEST-007 | N/A | invariant tests and member review pass | draft |
| DOM-REQ-003 ids use signed 64-bit storage | TASK-002 | TEST-002 | N/A | `sizeof(Id) == 8` test passes | draft |
| DOM-REQ-004 node DOF order is fixed | TASK-002 | TEST-002 | N/A | DOF ordinal test passes | draft |
| DOM-REQ-005 duplicate ids are rejected | TASK-003 to TASK-006 | TEST-003 to TEST-006 | N/A | duplicate tests throw `std::invalid_argument` | draft |
| DOM-REQ-006 missing required references are rejected | TASK-004 to TASK-006 | TEST-004 to TEST-006 | N/A | missing-reference tests throw `std::invalid_argument` | draft |
| DOM-REQ-007 direct missing lookup is deterministic | TASK-003 to TASK-007 | TEST-003 to TEST-007 | N/A | direct missing lookup throws `std::out_of_range` | draft |
| DOM-REQ-008 validation uses MSVC CMake/CTest | TASK-001; TASK-008 | TEST-001; TEST-008 | N/A | configure/build/CTest evidence recorded | draft |
## Validation Commands
```powershell
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
```
## Risks and Downstream Handoff
### Implementation Agent
- Keep each storage layer separate and test-first.
- Do not add numerical behavior while implementing model definitions.
- Preserve `Domain` as a model container, not a solver-state object.
### Build/Test Executor Agent
- Use Visual Studio 17 2022, x64, Debug, and `build/msvc-debug`.
- After `CMakeLists.txt` exists, `python scripts/validate_workspace.py` must configure, build, and run CTest instead of taking the no-CMake path.
### Correction Agent
- Implementation-owned failures are expected to be compile or unit-test failures in `fesa_core` or `fesa_domain_tests`.
- Upstream-contract failures include requests to store equation ids, displacements, reaction vectors, or integration point state inside `Domain`.
### Reference Verification Agent
- No reference verification is required for this phase.
- This phase produces no HDF5 result and consumes no reference artifacts.
## Open Issues
- The full MITC4 requirements, formulation, I/O definition, and reference model artifacts are still separate upstream stages.
- The parser-to-Domain factory API is intentionally deferred until the Abaqus input subset phase.
@@ -0,0 +1,98 @@
# Domain Runtime Storage Implementation Plan
## Objective
Make `Domain` store runtime model objects as its canonical model representation instead of persisting parser-style definition DTOs.
This follows the approved direction:
```text
Abaqus .inp parser
-> temporary parsed or semantic records
-> factory / DomainBuilder
-> Domain owns runtime objects
```
The parser and factory are not implemented in this phase. Definition DTOs may remain as temporary parser/factory-local records, but `Domain` must not use them as persistent storage.
## Phase Overview
1. `runtime-storage-contract`
- Record the migration contract and clarify which model objects are canonical in `Domain`.
- Keep parser/factory work out of scope.
2. `element-material-property-runtime-storage`
- Store elements as `fesa::element::Element`.
- Store materials as `fesa::material::Material`.
- Store shell properties as `fesa::property::ShellProperty`.
- Move `ElementType` to `ModelTypes.hpp` so runtime element interfaces do not depend on `ElementDefinition`.
3. `load-boundary-runtime-storage`
- Store loads as `fesa::load::Load`.
- Store boundary conditions as `fesa::boundary::BoundaryCondition`.
- Validate runtime `NodalLoad` and `SinglePointConstraint` node references during insertion.
4. `step-and-set-runtime-references`
- Validate element sets against runtime elements.
- Validate step load and boundary indices against runtime containers.
- Keep `LinearStaticStepDefinition` as a step configuration record until a dedicated runtime step object is designed.
5. `legacy-definition-extraction`
- Remove `core::*Definition` DTOs from `Domain` includes, fields, and public API.
- Keep focused DTO tests only for parser/factory-local record compatibility.
6. `validation-report-handoff`
- Run harness and CMake/CTest validation.
- Update project handoff documents with concrete evidence.
## Canonical Domain Storage
`Domain` stores:
- `fesa::core::Node` values;
- `std::unique_ptr<fesa::element::Element>`;
- `std::unique_ptr<fesa::material::Material>`;
- `fesa::property::ShellProperty` values;
- `std::unique_ptr<fesa::load::Load>`;
- `std::unique_ptr<fesa::boundary::BoundaryCondition>`;
- node sets and element sets by id;
- `LinearStaticStepDefinition` as a temporary step configuration record.
`Domain` must not store:
- `ElementDefinition`;
- `LinearElasticMaterialDefinition`;
- `ShellPropertyDefinition`;
- `NodalLoadDefinition`;
- `fesa::core::BoundaryCondition`.
## Non-Goals
- No Abaqus parser implementation.
- No factory/registry implementation.
- No MITC4 stiffness, mass, residual, stress, or tangent implementation.
- No equation numbering or solver state in `Domain`.
- No HDF5, MKL, TBB, reference artifact, tolerance, or formulation changes.
## Tests
Primary C++ tests:
- `/tests/core/domain_storage_test.cpp`
- `/tests/core/domain_model_object_test.cpp`
- `/tests/core/model_types_test.cpp`
- `/tests/element/element_base_test.cpp`
- `/tests/element/mitc4_element_model_test.cpp`
- `/tests/material/material_base_test.cpp`
- `/tests/property/shell_property_test.cpp`
- `/tests/load/load_base_test.cpp`
- `/tests/boundary/boundary_base_test.cpp`
Required validation:
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
git diff --check
```
@@ -0,0 +1,16 @@
#pragma once
namespace fesa::boundary {
enum class BoundaryConditionKind {
SinglePointConstraint
};
class BoundaryCondition {
public:
virtual ~BoundaryCondition() = default;
virtual BoundaryConditionKind kind() const noexcept = 0;
};
} // namespace fesa::boundary
@@ -0,0 +1,26 @@
#pragma once
#include "fesa/boundary/BoundaryCondition.hpp"
#include "fesa/core/ModelTypes.hpp"
namespace fesa::boundary {
using fesa::core::Dof;
using fesa::core::NodeId;
class SinglePointConstraint final : public BoundaryCondition {
public:
SinglePointConstraint(NodeId node_id, Dof dof, double value);
BoundaryConditionKind kind() const noexcept override;
NodeId nodeId() const noexcept;
Dof dof() const noexcept;
double value() const noexcept;
private:
NodeId node_id_;
Dof dof_;
double value_;
};
} // namespace fesa::boundary
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
namespace fesa::core {
class BoundaryCondition {
public:
BoundaryCondition(NodeId node_id, Dof dof, double value);
NodeId nodeId() const noexcept;
Dof dof() const noexcept;
double value() const noexcept;
private:
NodeId node_id_;
Dof dof_;
double value_;
};
} // namespace fesa::core
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include "fesa/boundary/BoundaryCondition.hpp"
#include "fesa/core/ModelTypes.hpp"
#include "fesa/core/Node.hpp"
#include "fesa/core/StepDefinition.hpp"
#include "fesa/element/Element.hpp"
#include "fesa/load/Load.hpp"
#include "fesa/material/Material.hpp"
#include "fesa/property/ShellProperty.hpp"
#include <cstddef>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace fesa::core {
class Domain {
public:
void addNode(Node node);
void addElement(std::unique_ptr<fesa::element::Element> element);
void addMaterial(std::unique_ptr<fesa::material::Material> material);
void addShellProperty(fesa::property::ShellProperty property);
void addNodeSet(std::string name, std::vector<NodeId> node_ids);
void addElementSet(std::string name, std::vector<ElementId> element_ids);
std::size_t addBoundaryCondition(std::unique_ptr<fesa::boundary::BoundaryCondition> boundary);
std::size_t addLoad(std::unique_ptr<fesa::load::Load> load);
void addStep(LinearStaticStepDefinition step);
const Node* findNode(NodeId id) const noexcept;
const Node& node(NodeId id) const;
std::size_t nodeCount() const noexcept;
const fesa::element::Element* findElement(ElementId id) const noexcept;
const fesa::element::Element& element(ElementId id) const;
std::size_t elementCount() const noexcept;
const fesa::material::Material* findMaterial(MaterialId id) const noexcept;
const fesa::material::Material& material(MaterialId id) const;
std::size_t materialCount() const noexcept;
const fesa::property::ShellProperty* findShellProperty(PropertyId id) const noexcept;
const fesa::property::ShellProperty& shellProperty(PropertyId id) const;
std::size_t shellPropertyCount() const noexcept;
const std::vector<NodeId>* findNodeSet(const std::string& name) const noexcept;
const std::vector<NodeId>& nodeSet(const std::string& name) const;
std::size_t nodeSetCount() const noexcept;
const std::vector<ElementId>* findElementSet(const std::string& name) const noexcept;
const std::vector<ElementId>& elementSet(const std::string& name) const;
std::size_t elementSetCount() const noexcept;
const fesa::boundary::BoundaryCondition* findBoundaryCondition(std::size_t index) const noexcept;
const fesa::boundary::BoundaryCondition& boundaryCondition(std::size_t index) const;
std::size_t boundaryConditionCount() const noexcept;
const fesa::load::Load* findLoad(std::size_t index) const noexcept;
const fesa::load::Load& load(std::size_t index) const;
std::size_t loadCount() const noexcept;
const LinearStaticStepDefinition* findStep(StepId id) const noexcept;
const LinearStaticStepDefinition& step(StepId id) const;
std::size_t stepCount() const noexcept;
private:
std::unordered_map<NodeId, Node> nodes_;
std::unordered_map<ElementId, std::unique_ptr<fesa::element::Element>> elements_;
std::unordered_map<MaterialId, std::unique_ptr<fesa::material::Material>> materials_;
std::unordered_map<PropertyId, fesa::property::ShellProperty> shell_properties_;
std::unordered_map<std::string, std::vector<NodeId>> node_sets_;
std::unordered_map<std::string, std::vector<ElementId>> element_sets_;
std::vector<std::unique_ptr<fesa::boundary::BoundaryCondition>> boundary_conditions_;
std::vector<std::unique_ptr<fesa::load::Load>> loads_;
std::unordered_map<StepId, LinearStaticStepDefinition> steps_;
};
} // namespace fesa::core
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
#include <array>
namespace fesa::core {
class ElementDefinition {
public:
ElementDefinition(
ElementId id,
ElementType type,
std::array<NodeId, 4> connectivity,
PropertyId property_id);
ElementId id() const noexcept;
ElementType type() const noexcept;
const std::array<NodeId, 4>& connectivity() const noexcept;
PropertyId propertyId() const noexcept;
private:
ElementId id_;
ElementType type_;
std::array<NodeId, 4> connectivity_;
PropertyId property_id_;
};
} // namespace fesa::core
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
namespace fesa::core {
class NodalLoadDefinition {
public:
NodalLoadDefinition(NodeId node_id, Dof dof, double value);
NodeId nodeId() const noexcept;
Dof dof() const noexcept;
double value() const noexcept;
private:
NodeId node_id_;
Dof dof_;
double value_;
};
} // namespace fesa::core
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
namespace fesa::core {
class LinearElasticMaterialDefinition {
public:
LinearElasticMaterialDefinition(MaterialId id, double young_modulus, double poisson_ratio);
MaterialId id() const noexcept;
double youngModulus() const noexcept;
double poissonRatio() const noexcept;
private:
MaterialId id_;
double young_modulus_;
double poisson_ratio_;
};
} // namespace fesa::core
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <cstddef>
#include <cstdint>
namespace fesa::core {
using Id = std::int64_t;
using NodeId = Id;
using ElementId = Id;
using MaterialId = Id;
using PropertyId = Id;
using StepId = Id;
enum class Dof : std::uint8_t {
U1 = 0,
U2 = 1,
U3 = 2,
UR1 = 3,
UR2 = 4,
UR3 = 5
};
enum class ElementType {
Mitc4
};
constexpr std::size_t kDofPerNode = 6;
} // namespace fesa::core
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
#include <array>
namespace fesa::core {
class Node {
public:
Node(NodeId id, double x, double y, double z);
static constexpr std::size_t dofCount() noexcept { return kDofPerNode; }
NodeId id() const noexcept;
double x() const noexcept;
double y() const noexcept;
double z() const noexcept;
const std::array<double, 3>& coordinates() const noexcept;
private:
NodeId id_;
std::array<double, 3> coordinates_;
};
} // namespace fesa::core
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
namespace fesa::core {
class ShellPropertyDefinition {
public:
ShellPropertyDefinition(PropertyId id, MaterialId material_id, double thickness);
PropertyId id() const noexcept;
MaterialId materialId() const noexcept;
double thickness() const noexcept;
private:
PropertyId id_;
MaterialId material_id_;
double thickness_;
};
} // namespace fesa::core
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
#include <cstddef>
#include <string>
#include <vector>
namespace fesa::core {
class LinearStaticStepDefinition {
public:
LinearStaticStepDefinition(
StepId id,
std::string name,
std::vector<std::size_t> boundary_condition_indices,
std::vector<std::size_t> load_indices);
StepId id() const noexcept;
const std::string& name() const noexcept;
const std::vector<std::size_t>& boundaryConditionIndices() const noexcept;
const std::vector<std::size_t>& loadIndices() const noexcept;
private:
StepId id_;
std::string name_;
std::vector<std::size_t> boundary_condition_indices_;
std::vector<std::size_t> load_indices_;
};
} // namespace fesa::core
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
#include <array>
#include <cstddef>
namespace fesa::element {
using fesa::core::ElementId;
using fesa::core::ElementType;
using fesa::core::NodeId;
using fesa::core::PropertyId;
class Element {
public:
virtual ~Element() = default;
virtual ElementId id() const noexcept = 0;
virtual ElementType type() const noexcept = 0;
virtual std::size_t nodeCount() const noexcept = 0;
virtual const std::array<NodeId, 4>& connectivity() const noexcept = 0;
virtual PropertyId propertyId() const noexcept = 0;
};
} // namespace fesa::element
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "fesa/element/Element.hpp"
namespace fesa::element {
class Mitc4Element final : public Element {
public:
Mitc4Element(ElementId id, std::array<NodeId, 4> connectivity, PropertyId property_id);
ElementId id() const noexcept override;
ElementType type() const noexcept override;
std::size_t nodeCount() const noexcept override;
std::size_t dofCount() const noexcept;
const std::array<NodeId, 4>& connectivity() const noexcept override;
PropertyId propertyId() const noexcept override;
private:
ElementId id_;
std::array<NodeId, 4> connectivity_;
PropertyId property_id_;
};
} // namespace fesa::element
+16
View File
@@ -0,0 +1,16 @@
#pragma once
namespace fesa::load {
enum class LoadKind {
Nodal
};
class Load {
public:
virtual ~Load() = default;
virtual LoadKind kind() const noexcept = 0;
};
} // namespace fesa::load
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
#include "fesa/load/Load.hpp"
namespace fesa::load {
using fesa::core::Dof;
using fesa::core::NodeId;
class NodalLoad final : public Load {
public:
NodalLoad(NodeId node_id, Dof dof, double value);
LoadKind kind() const noexcept override;
NodeId nodeId() const noexcept;
Dof dof() const noexcept;
double value() const noexcept;
private:
NodeId node_id_;
Dof dof_;
double value_;
};
} // namespace fesa::load
@@ -0,0 +1,21 @@
#pragma once
#include "fesa/material/Material.hpp"
namespace fesa::material {
class LinearElasticMaterial final : public Material {
public:
LinearElasticMaterial(MaterialId id, double young_modulus, double poisson_ratio);
MaterialId id() const noexcept override;
double youngModulus() const noexcept;
double poissonRatio() const noexcept;
private:
MaterialId id_;
double young_modulus_;
double poisson_ratio_;
};
} // namespace fesa::material
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
namespace fesa::material {
using fesa::core::MaterialId;
class Material {
public:
virtual ~Material() = default;
virtual MaterialId id() const noexcept = 0;
};
} // namespace fesa::material
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
namespace fesa::property {
using fesa::core::MaterialId;
using fesa::core::PropertyId;
class ShellProperty {
public:
ShellProperty(PropertyId id, MaterialId material_id, double thickness);
PropertyId id() const noexcept;
MaterialId materialId() const noexcept;
double thickness() const noexcept;
private:
PropertyId id_;
MaterialId material_id_;
double thickness_;
};
} // namespace fesa::property
+72
View File
@@ -0,0 +1,72 @@
{
"project": "FESA Structural Solver",
"phase": "analysis-model-objects",
"steps": [
{
"step": 0,
"name": "model-object-contract",
"status": "completed",
"summary": "Created analysis model object implementation plan with base-class contracts, exclusions, and TDD traceability."
},
{
"step": 1,
"name": "node-model-class",
"status": "completed",
"summary": "Added Node DOF count contract and tests while keeping Node free of solver state."
},
{
"step": 2,
"name": "element-base-interface",
"status": "completed",
"summary": "Added identity-only Element base interface and model-object CTest target."
},
{
"step": 3,
"name": "mitc4-element-model",
"status": "completed",
"summary": "Added MITC4 element model skeleton with four-node connectivity and 24-DOF count tests."
},
{
"step": 4,
"name": "material-base-interface",
"status": "completed",
"summary": "Added Material base and LinearElasticMaterial model object with ownership tests."
},
{
"step": 5,
"name": "property-section-model",
"status": "completed",
"summary": "Added ShellProperty model object with positive-thickness validation tests."
},
{
"step": 6,
"name": "load-base-interface",
"status": "completed",
"summary": "Added Load base and NodalLoad model object with polymorphic access tests."
},
{
"step": 7,
"name": "boundary-base-interface",
"status": "completed",
"summary": "Added BoundaryCondition base and SinglePointConstraint model object with polymorphic access tests."
},
{
"step": 8,
"name": "domain-polymorphic-ownership",
"status": "completed",
"summary": "Extended Domain with unique_ptr ownership APIs for element, material, load, and boundary model objects while preserving existing definition APIs."
},
{
"step": 9,
"name": "legacy-definition-compatibility",
"status": "completed",
"summary": "Documented compatibility between core definition DTOs and new runtime model object ownership APIs."
},
{
"step": 10,
"name": "validation-report-handoff",
"status": "completed",
"summary": "Recorded build/test evidence, updated project progress, and marked the phase completed."
}
]
}
+54
View File
@@ -0,0 +1,54 @@
# Step 0: model-object-contract
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/PLAN.md`
- `/docs/PROGRESS.md`
- `/docs/WORKNOTE.md`
- `/docs/AGENT_RULES.md`
- `/docs/PRD.md`
- `/docs/ADR.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
## Task
Create `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`.
The document must define the model object layer that will be stored by `Domain`:
- `Node` model object.
- abstract `Element` base and MITC4 model object skeleton.
- abstract `Material` base and linear elastic material model object.
- shell property/section model object.
- abstract `Load` base and nodal load model object.
- abstract `BoundaryCondition` base and single-point constraint model object.
- `Domain` RAII ownership of polymorphic model objects through `std::unique_ptr`.
State explicitly that this phase does not implement MITC4 stiffness, material constitutive matrices, global force assembly, boundary-condition matrix application, equation numbering, solver vectors, HDF5 output, reference comparison, or reference-solver execution.
Document how existing `*Definition` value objects remain compatible as input DTOs until parser/factory work replaces or maps them.
## Tests To Write First
This is a documentation planning step. Do not write C++ production code in this step.
## Acceptance Criteria
Run:
```powershell
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
```
Update `/phases/analysis-model-objects/index.json` step 0 with `completed`, `error`, or `blocked`.
## Do Not
- Do not change C++ code in this step.
- Do not change MITC4 formulation, I/O contracts, reference artifacts, or tolerance policy.
+53
View File
@@ -0,0 +1,53 @@
# Step 1: node-model-class
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
- `/include/fesa/core/Node.hpp`
- `/tests/core/node_test.cpp`
## Task
Refine the existing `Node` model class contract without adding solver state.
Allowed files:
- Modify `/include/fesa/core/Node.hpp`
- Modify `/src/core/Domain.cpp` only if constructor/accessor definitions move or change
- Modify `/tests/core/node_test.cpp`
Required behavior:
- `Node` exposes `id()`, `coordinates()`, `x()`, `y()`, `z()`.
- `Node` exposes `static constexpr std::size_t dofCount()` or equivalent compile-time DOF count of 6.
- `Node` does not store equation ids, displacements, residuals, reactions, or constraints.
## Tests To Write First
Extend `/tests/core/node_test.cpp` before production changes:
- Verify `Node::dofCount() == 6`.
- Verify coordinate order is preserved.
- Verify the returned coordinate data is const from a const `Node`.
Run the targeted build and confirm RED before implementation.
## Acceptance Criteria
Run:
```powershell
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
```
Update `/phases/analysis-model-objects/index.json` step 1 with status and summary.
## Do Not
- Do not add equation ids or solver state to `Node`.
+54
View File
@@ -0,0 +1,54 @@
# Step 10: validation-report-handoff
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/build-test-reports/README.md`
- `/docs/PROGRESS.md`
- `/docs/WORKNOTE.md`
- `/phases/analysis-model-objects/index.json`
## Task
Run final verification and write handoff evidence.
Allowed files:
- Create `/docs/build-test-reports/analysis-model-objects-build-test.md`
- Modify `/docs/PROGRESS.md`
- Modify `/docs/WORKNOTE.md` only if new traps or failed approaches occurred
- Modify `/phases/analysis-model-objects/index.json`
- Modify `/phases/index.json`
The build/test report must include command log summary, exit codes, configure/build/CTest status, failure classification or `N/A`, and no-change assertions for reference artifacts and tolerance policy.
## Tests To Write First
This is a validation/reporting step. Do not write new C++ production behavior.
## Acceptance Criteria
Run:
```powershell
git status --short --branch
git remote -v
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
git diff --check
```
Update:
- `/phases/analysis-model-objects/index.json` step 10 with `completed`, `error`, or `blocked`.
- `/phases/index.json` phase status to `completed` only if all steps are completed.
- `/docs/PROGRESS.md` with validation evidence.
## Do Not
- Do not claim MITC4 numerical implementation, reference comparison success, or release readiness.
- Do not run Abaqus, Nastran, or any reference solver.
+58
View File
@@ -0,0 +1,58 @@
# Step 2: element-base-interface
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
- `/include/fesa/core/ElementDefinition.hpp`
- `/CMakeLists.txt`
## Task
Add the abstract element model interface.
Allowed files:
- Create `/include/fesa/element/Element.hpp`
- Create `/tests/element/element_base_test.cpp`
- Modify `/tests/core/domain_bootstrap_test.cpp` only if the current test executable remains the single test runner
- Modify `/CMakeLists.txt`
Required interface:
- virtual destructor.
- `ElementId id() const noexcept`.
- `ElementType type() const noexcept`.
- `std::size_t nodeCount() const noexcept`.
- `std::span` is not allowed because this project targets C++17.
- Use const reference return for connectivity data.
- no stiffness, residual, stress, state, or equation API in this phase.
## Tests To Write First
Write `/tests/element/element_base_test.cpp` before production changes. Use a small test subclass to prove:
- base pointer dispatch works.
- id/type/connectivity/property id are readable through base API.
- deleting through `std::unique_ptr<Element>` is valid.
Run targeted build and confirm RED because `Element.hpp` is missing.
## Acceptance Criteria
Run:
```powershell
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
```
Update step 2 status.
## Do Not
- Do not implement MITC4 stiffness or numerical element behavior.
+57
View File
@@ -0,0 +1,57 @@
# Step 3: mitc4-element-model
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
- `/include/fesa/element/Element.hpp`
- `/include/fesa/core/ElementDefinition.hpp`
## Task
Add a MITC4 element model object skeleton.
Allowed files:
- Create `/include/fesa/element/Mitc4Element.hpp`
- Create `/src/element/Mitc4Element.cpp`
- Create `/tests/element/mitc4_element_model_test.cpp`
- Modify `/CMakeLists.txt`
Required behavior:
- `Mitc4Element` derives from `Element`.
- Stores element id, four node ids, and property id.
- Reports `ElementType::Mitc4`.
- Reports `nodeCount() == 4`.
- Reports `dofCount() == 24`.
- Preserves connectivity order.
## Tests To Write First
Write `/tests/element/mitc4_element_model_test.cpp` before production changes:
- Construct `Mitc4Element`.
- Access it through `const Element&`.
- Verify id, type, node count, DOF count, connectivity, and property id.
Run targeted build and confirm RED because `Mitc4Element.hpp` is missing.
## Acceptance Criteria
Run:
```powershell
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
```
Update step 3 status.
## Do Not
- Do not implement element stiffness, Jacobians, MITC shear interpolation, resultants, stress, or integration-point state.
+55
View File
@@ -0,0 +1,55 @@
# Step 4: material-base-interface
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
- `/include/fesa/core/MaterialDefinition.hpp`
## Task
Add material model base and linear elastic material model object.
Allowed files:
- Create `/include/fesa/material/Material.hpp`
- Create `/include/fesa/material/LinearElasticMaterial.hpp`
- Create `/src/material/LinearElasticMaterial.cpp`
- Create `/tests/material/material_base_test.cpp`
- Modify `/CMakeLists.txt`
Required behavior:
- `Material` has virtual destructor and `MaterialId id() const noexcept`.
- `LinearElasticMaterial` derives from `Material`.
- Stores Young's modulus and Poisson ratio.
- No constitutive matrix or stress update in this phase.
## Tests To Write First
Write `/tests/material/material_base_test.cpp` before production changes:
- Access `LinearElasticMaterial` through `const Material&`.
- Verify id, E, and nu.
- Verify deletion through `std::unique_ptr<Material>`.
Run targeted build and confirm RED.
## Acceptance Criteria
Run:
```powershell
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
```
Update step 4 status.
## Do Not
- Do not implement constitutive matrices, stress updates, or tangent stiffness.
+53
View File
@@ -0,0 +1,53 @@
# Step 5: property-section-model
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
- `/include/fesa/core/PropertyDefinition.hpp`
## Task
Add shell property/section model object.
Allowed files:
- Create `/include/fesa/property/ShellProperty.hpp`
- Create `/src/property/ShellProperty.cpp`
- Create `/tests/property/shell_property_test.cpp`
- Modify `/CMakeLists.txt`
Required behavior:
- Stores property id, material id, and thickness.
- Exposes const accessors.
- Rejects non-positive thickness with `std::invalid_argument`.
- Does not compute shell section stiffness.
## Tests To Write First
Write `/tests/property/shell_property_test.cpp` before production changes:
- Valid property stores id/material/thickness.
- Zero or negative thickness throws.
Run targeted build and confirm RED.
## Acceptance Criteria
Run:
```powershell
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
```
Update step 5 status.
## Do Not
- Do not implement membrane, bending, or shear constitutive stiffness.
+55
View File
@@ -0,0 +1,55 @@
# Step 6: load-base-interface
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
- `/include/fesa/core/LoadDefinition.hpp`
## Task
Add load model base and nodal load object.
Allowed files:
- Create `/include/fesa/load/Load.hpp`
- Create `/include/fesa/load/NodalLoad.hpp`
- Create `/src/load/NodalLoad.cpp`
- Create `/tests/load/load_base_test.cpp`
- Modify `/CMakeLists.txt`
Required behavior:
- `Load` has virtual destructor and a load kind enum.
- `NodalLoad` derives from `Load`.
- Stores node id, DOF, and value.
- No global vector assembly in this phase.
## Tests To Write First
Write `/tests/load/load_base_test.cpp` before production changes:
- Access `NodalLoad` through `const Load&`.
- Verify kind, node id, DOF, value.
- Verify deletion through `std::unique_ptr<Load>`.
Run targeted build and confirm RED.
## Acceptance Criteria
Run:
```powershell
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
```
Update step 6 status.
## Do Not
- Do not assemble forces or apply time/load factors.
+55
View File
@@ -0,0 +1,55 @@
# Step 7: boundary-base-interface
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
- `/include/fesa/core/BoundaryCondition.hpp`
## Task
Add boundary condition model base and single-point constraint object.
Allowed files:
- Create `/include/fesa/boundary/BoundaryCondition.hpp`
- Create `/include/fesa/boundary/SinglePointConstraint.hpp`
- Create `/src/boundary/SinglePointConstraint.cpp`
- Create `/tests/boundary/boundary_base_test.cpp`
- Modify `/CMakeLists.txt`
Required behavior:
- `BoundaryCondition` has virtual destructor and a boundary kind enum.
- `SinglePointConstraint` derives from `BoundaryCondition`.
- Stores node id, DOF, prescribed value.
- No matrix elimination or reaction recovery in this phase.
## Tests To Write First
Write `/tests/boundary/boundary_base_test.cpp` before production changes:
- Access `SinglePointConstraint` through `const BoundaryCondition&`.
- Verify kind, node id, DOF, value.
- Verify deletion through `std::unique_ptr<BoundaryCondition>`.
Run targeted build and confirm RED.
## Acceptance Criteria
Run:
```powershell
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
```
Update step 7 status.
## Do Not
- Do not apply constraints to matrices or compute reactions.
+62
View File
@@ -0,0 +1,62 @@
# Step 8: domain-polymorphic-ownership
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- all model object headers created by previous steps
## Task
Extend `Domain` with RAII ownership APIs for polymorphic model objects while preserving existing definition-storage tests.
Allowed files:
- Modify `/include/fesa/core/Domain.hpp`
- Modify `/src/core/Domain.cpp`
- Create `/tests/core/domain_model_object_test.cpp`
- Modify `/tests/core/domain_bootstrap_test.cpp`
- Modify `/CMakeLists.txt`
Required behavior:
- `Domain::addElementObject(std::unique_ptr<Element>)`
- `Domain::findElementObject(ElementId)` and `Domain::elementObject(ElementId)`
- equivalent add/find/direct APIs for `Material`, `Load`, and model `BoundaryCondition`.
- duplicate ids or duplicate load/boundary keys throw `std::invalid_argument`.
- missing direct lookup throws `std::out_of_range`.
- retrieval returns const base references or const base pointers.
- existing `Definition` APIs remain available.
## Tests To Write First
Write `/tests/core/domain_model_object_test.cpp` before production changes:
- Domain owns `Mitc4Element`, `LinearElasticMaterial`, `NodalLoad`, and `SinglePointConstraint` through `std::unique_ptr`.
- Find/direct lookup through base APIs works.
- Duplicate ids throw for element/material.
- Missing direct lookup throws.
Run targeted build and confirm RED.
## Acceptance Criteria
Run:
```powershell
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
```
Update step 8 status.
## Do Not
- Do not remove existing definition APIs in this step.
- Do not add solver state to Domain.
+49
View File
@@ -0,0 +1,49 @@
# Step 9: legacy-definition-compatibility
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/tests/core/domain_storage_test.cpp`
- `/tests/core/domain_model_object_test.cpp`
## Task
Preserve and document compatibility between existing `*Definition` value objects and new model-object base classes.
Allowed files:
- Modify `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
- Modify tests only if compatibility gaps are found
- Modify production code only for narrow compatibility fixes
Required behavior:
- Existing Domain definition-storage tests still pass.
- New model-object ownership tests still pass.
- The implementation plan clearly states that `*Definition` objects remain parser/factory DTOs until the parser phase decides whether to map or replace them.
## Tests To Write First
If production code must change, write or extend a failing C++ test before the change. If this is documentation-only, do not add production code.
## Acceptance Criteria
Run:
```powershell
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
```
Update step 9 status.
## Do Not
- Do not delete existing `*Definition` headers or tests.
- Do not rewrite parser or factory behavior.
+60
View File
@@ -0,0 +1,60 @@
{
"project": "FESA Structural Solver",
"phase": "domain-model-foundation",
"steps": [
{
"step": 0,
"name": "domain-contract",
"status": "completed",
"summary": "Created the Domain model foundation implementation plan with ownership boundaries, TDD tasks, and validation commands."
},
{
"step": 1,
"name": "cmake-test-bootstrap",
"status": "completed",
"summary": "Added root CMake/CTest bootstrap for fesa_domain_tests and fixed validation CMake path resolution on Windows."
},
{
"step": 2,
"name": "core-id-types",
"status": "completed",
"summary": "Added ModelTypes.hpp with 64-bit ids and fixed six-DOF ordering covered by C++ tests."
},
{
"step": 3,
"name": "node-and-domain-storage",
"status": "completed",
"summary": "Implemented Node and Domain node storage with duplicate and missing lookup tests."
},
{
"step": 4,
"name": "element-definition-storage",
"status": "completed",
"summary": "Added MITC4 element definition storage with connectivity preservation and missing-node validation tests."
},
{
"step": 5,
"name": "model-attribute-storage",
"status": "completed",
"summary": "Added material, shell property, node set, and element set storage with duplicate and missing-reference tests."
},
{
"step": 6,
"name": "boundary-load-step-storage",
"status": "completed",
"summary": "Added boundary condition, nodal load, and linear static step storage with missing-reference tests."
},
{
"step": 7,
"name": "domain-invariants",
"status": "completed",
"summary": "Locked down const retrieval and failed-insert count stability with invariant tests."
},
{
"step": 8,
"name": "validation-report-handoff",
"status": "completed",
"summary": "Recorded build/test evidence, updated handoff docs, and marked the phase completed."
}
]
}
+77
View File
@@ -0,0 +1,77 @@
# Step 0: domain-contract
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/PLAN.md`
- `/docs/PROGRESS.md`
- `/docs/WORKNOTE.md`
- `/docs/AGENT_RULES.md`
- `/docs/PRD.md`
- `/docs/ADR.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/README.md`
## Task
Create `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`.
The document must define the first C++ implementation slice for the `Domain` model container. It must explicitly state:
- `Domain` owns parsed model definitions only.
- Included model definitions: nodes, elements, materials, shell properties, node sets, element sets, boundary conditions, nodal loads, and analysis step definitions.
- Excluded state: equation ids, sparse matrix state, displacement vectors, residuals, reactions, current time, iteration counters, and element integration point state.
- `DofManager` owns equation numbering.
- `AnalysisModel` owns step-local execution views.
- `AnalysisState` owns mutable solution and iteration state.
- All ids use signed 64-bit storage.
- Node DOF order is `U1, U2, U3, UR1, UR2, UR3`.
- Units are user-consistent and not enforced by `Domain`.
- No Abaqus, Nastran, reference solver, HDF5 result, MKL, or TBB behavior is implemented in this phase.
Use the implementation-plan README template. Set:
- `feature_id: domain-model-foundation`
- `status: ready-for-implementation`
- `owner_agent: implementation-planning-agent`
- `date: 2026-06-08`
Include a work breakdown and TDD test plan for the following downstream steps:
1. CMake/CTest bootstrap.
2. Core id and DOF types.
3. Node and node storage.
4. Element definition storage.
5. Material, property, and set storage.
6. Boundary condition, nodal load, and step storage.
7. Domain invariant tests.
8. Validation report and handoff.
## Tests To Write First
This is a documentation planning step. Do not write C++ production code in this step.
## Acceptance Criteria
Run:
```powershell
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
```
The current repository may still take the no-CMake informational path until Step 1 creates CMake files.
Update `/phases/domain-model-foundation/index.json` step 0:
- On success: `"status": "completed"` and a one-line `"summary"`.
- On repeated failure: `"status": "error"` and a concrete `"error_message"`.
- On user decision needed: `"status": "blocked"` and a concrete `"blocked_reason"`.
## Do Not
- Do not create C++ headers, sources, tests, or CMake files in this step.
- Do not change requirements, formulation, I/O contracts, reference artifacts, or tolerance policy.
- Do not run Abaqus, Nastran, or any reference solver.
+70
View File
@@ -0,0 +1,70 @@
# Step 1: cmake-test-bootstrap
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ADR.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
- `/phases/domain-model-foundation/index.json`
- `/phases/domain-model-foundation/step0.md`
## Task
Create the minimum C++17/MSVC/CMake/CTest project structure needed to run Domain tests.
Allowed files:
- Create `/CMakeLists.txt`
- Create `/include/fesa/core/.gitkeep` only if needed to preserve the directory before headers exist
- Create `/src/core/.gitkeep` only if needed to preserve the directory before sources exist
- Create `/tests/core/domain_bootstrap_test.cpp`
CMake requirements:
- `cmake_minimum_required(VERSION 3.20)`
- Project name: `FESA`
- Language: CXX
- Set C++ standard to 17 and require it.
- Create interface or library target `fesa_core`.
- Add include directory `/include`.
- Enable testing.
- Create test executable `fesa_domain_tests`.
- Register CTest name `domain.bootstrap`.
- Apply CTest labels `domain;core`.
## Tests To Write First
Write `/tests/core/domain_bootstrap_test.cpp` before adding any production C++ code. It should be a minimal self-contained executable with `main()` returning 0 only when the C++ test harness is running.
Example intended behavior:
```cpp
int main() {
return 0;
}
```
This step does not implement Domain behavior yet. The purpose is to prove the MSVC/CTest path exists.
## Acceptance Criteria
Run:
```powershell
cmake -S . -B build/msvc-debug -G "Visual Studio 17 2022" -A x64
cmake --build build/msvc-debug --config Debug
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain.bootstrap
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
```
Update `/phases/domain-model-foundation/index.json` step 1 with `completed`, `error`, or `blocked`.
## Do Not
- Do not add Domain, Node, Element, solver, parser, MKL, TBB, or HDF5 implementation in this step.
- Do not add JavaScript, TypeScript, npm, or non-MSVC fallback tooling.
+76
View File
@@ -0,0 +1,76 @@
# Step 2: core-id-types
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ADR.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
- `/CMakeLists.txt`
- `/tests/core/domain_bootstrap_test.cpp`
## Task
Introduce core id aliases and DOF ordering constants.
Allowed files:
- Create `/include/fesa/core/ModelTypes.hpp`
- Modify `/tests/core/domain_bootstrap_test.cpp` or create `/tests/core/model_types_test.cpp`
- Modify `/CMakeLists.txt` only to register the new test file if a separate test executable is created
Required API:
```cpp
namespace fesa::core {
using Id = std::int64_t;
using NodeId = Id;
using ElementId = Id;
using MaterialId = Id;
using PropertyId = Id;
using StepId = Id;
enum class Dof : std::uint8_t {
U1 = 0,
U2 = 1,
U3 = 2,
UR1 = 3,
UR2 = 4,
UR3 = 5
};
constexpr std::size_t kDofPerNode = 6;
}
```
## Tests To Write First
Write a failing C++ test that includes `fesa/core/ModelTypes.hpp` before creating the header.
The test must verify:
- `sizeof(fesa::core::Id) == 8`
- `kDofPerNode == 6`
- `Dof::U1` through `Dof::UR3` have ordinal values 0 through 5
Run the targeted CTest and verify it fails because the header is missing. Then implement the header.
## Acceptance Criteria
Run:
```powershell
cmake --build build/msvc-debug --config Debug
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
python scripts/validate_workspace.py
```
Update `/phases/domain-model-foundation/index.json` step 2 with `completed`, `error`, or `blocked`.
## Do Not
- Do not add model storage in this step.
- Do not add equation numbering or solver state.
+63
View File
@@ -0,0 +1,63 @@
# Step 3: node-and-domain-storage
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ADR.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
- `/include/fesa/core/ModelTypes.hpp`
- `/CMakeLists.txt`
## Task
Implement the first useful `Node` and `Domain` slice.
Allowed files:
- Create `/include/fesa/core/Node.hpp`
- Create `/include/fesa/core/Domain.hpp`
- Create `/src/core/Domain.cpp`
- Create or modify `/tests/core/domain_storage_test.cpp`
- Modify `/CMakeLists.txt` to compile the source and register the test
Required behavior:
- `Node` stores `NodeId` and three coordinates as `double`.
- `Domain::addNode(Node)` inserts a node.
- Duplicate node ids throw `std::invalid_argument`.
- `Domain::findNode(NodeId)` returns a pointer or `nullptr`.
- `Domain::node(NodeId)` returns a const reference or throws `std::out_of_range`.
- `Domain::nodeCount()` returns the stored node count.
- `Domain` must not expose mutable node references.
## Tests To Write First
Write failing tests before creating production headers/sources:
- Add and retrieve one node by id.
- Missing node returns `nullptr` from `findNode`.
- Missing node throws from `node`.
- Duplicate node id throws.
Run the targeted CTest and verify failure due to missing API. Then implement the minimal code.
## Acceptance Criteria
Run:
```powershell
cmake --build build/msvc-debug --config Debug
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
python scripts/validate_workspace.py
```
Update `/phases/domain-model-foundation/index.json` step 3 with `completed`, `error`, or `blocked`.
## Do Not
- Do not add elements, materials, properties, sets, loads, or steps yet.
- Do not store equation ids, displacement vectors, residuals, reactions, or solver state.
+65
View File
@@ -0,0 +1,65 @@
# Step 4: element-definition-storage
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ADR.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/include/fesa/core/Node.hpp`
- `/include/fesa/core/ModelTypes.hpp`
- `/tests/core/domain_storage_test.cpp`
## Task
Add element definition storage without implementing element stiffness or MITC4 formulation.
Allowed files:
- Create `/include/fesa/core/ElementDefinition.hpp`
- Modify `/include/fesa/core/Domain.hpp`
- Modify `/src/core/Domain.cpp`
- Modify `/tests/core/domain_storage_test.cpp`
Required behavior:
- `ElementDefinition` stores `ElementId`, type enum/string for `MITC4`, four `NodeId` connectivity entries, and a `PropertyId`.
- `Domain::addElement(ElementDefinition)` inserts an element.
- Duplicate element ids throw `std::invalid_argument`.
- Element connectivity must contain exactly four node ids for the MITC4 definition.
- Adding an element with a missing node id throws `std::invalid_argument`.
- `Domain::findElement(ElementId)` returns pointer or `nullptr`.
- `Domain::element(ElementId)` returns const reference or throws `std::out_of_range`.
- `Domain::elementCount()` returns the stored element count.
## Tests To Write First
Write failing tests before production changes:
- Add an element with four existing nodes and retrieve connectivity in order.
- Duplicate element id throws.
- Element referencing a missing node throws.
- Missing element lookup follows the `findElement` and `element` contracts.
Run targeted CTest and verify RED before implementation.
## Acceptance Criteria
Run:
```powershell
cmake --build build/msvc-debug --config Debug
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
python scripts/validate_workspace.py
```
Update `/phases/domain-model-foundation/index.json` step 4 with `completed`, `error`, or `blocked`.
## Do Not
- Do not implement element stiffness, Jacobians, shape functions, sparse assembly, or solver behavior.
- Do not add parser behavior.
+66
View File
@@ -0,0 +1,66 @@
# Step 5: model-attribute-storage
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ADR.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/include/fesa/core/ElementDefinition.hpp`
- `/tests/core/domain_storage_test.cpp`
## Task
Add minimal material, shell property, node set, and element set storage.
Allowed files:
- Create `/include/fesa/core/MaterialDefinition.hpp`
- Create `/include/fesa/core/PropertyDefinition.hpp`
- Modify `/include/fesa/core/Domain.hpp`
- Modify `/src/core/Domain.cpp`
- Modify `/tests/core/domain_storage_test.cpp`
Required behavior:
- `LinearElasticMaterialDefinition` stores material id, Young's modulus, and Poisson's ratio.
- `ShellPropertyDefinition` stores property id, material id, and thickness.
- `Domain` stores node sets and element sets by string name.
- Duplicate material ids, property ids, or set names throw `std::invalid_argument`.
- Adding a shell property with missing material id throws `std::invalid_argument`.
- Node sets can only reference existing nodes.
- Element sets can only reference existing elements.
- All retrieval APIs return const data.
## Tests To Write First
Write failing tests before production changes:
- Add material and shell property, then retrieve them.
- Duplicate material/property id throws.
- Shell property referencing missing material throws.
- Node set stores existing node ids in input order.
- Element set stores existing element ids in input order.
- Sets referencing missing ids throw.
Run targeted CTest and verify RED before implementation.
## Acceptance Criteria
Run:
```powershell
cmake --build build/msvc-debug --config Debug
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
python scripts/validate_workspace.py
```
Update `/phases/domain-model-foundation/index.json` step 5 with `completed`, `error`, or `blocked`.
## Do Not
- Do not implement constitutive matrices, shell section stiffness, units conversion, or parser behavior.
+66
View File
@@ -0,0 +1,66 @@
# Step 6: boundary-load-step-storage
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ADR.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/include/fesa/core/ModelTypes.hpp`
- `/tests/core/domain_storage_test.cpp`
## Task
Add boundary condition, nodal load, and analysis step definition storage.
Allowed files:
- Create `/include/fesa/core/BoundaryCondition.hpp`
- Create `/include/fesa/core/LoadDefinition.hpp`
- Create `/include/fesa/core/StepDefinition.hpp`
- Modify `/include/fesa/core/Domain.hpp`
- Modify `/src/core/Domain.cpp`
- Modify `/tests/core/domain_storage_test.cpp`
Required behavior:
- A boundary condition stores node id, constrained `Dof`, and prescribed value.
- A nodal load stores node id, `Dof`, and value.
- A linear static step definition stores step id, name, boundary condition indices or ids, and load indices or ids.
- Adding BC/load for a missing node throws `std::invalid_argument`.
- Duplicate step ids throw `std::invalid_argument`.
- Stored BCs, loads, and steps are returned as const data.
## Tests To Write First
Write failing tests before production changes:
- Add and retrieve a boundary condition on an existing node.
- Add and retrieve a nodal load on an existing node.
- Missing-node BC/load throws.
- Add a linear static step referencing stored BC/load entries.
- Duplicate step id throws.
Run targeted CTest and verify RED before implementation.
## Acceptance Criteria
Run:
```powershell
cmake --build build/msvc-debug --config Debug
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
python scripts/validate_workspace.py
```
Update `/phases/domain-model-foundation/index.json` step 6 with `completed`, `error`, or `blocked`.
## Do Not
- Do not apply boundary conditions to matrices.
- Do not compute reactions.
- Do not add solver state, displacements, equation ids, or sparse matrix behavior.
+61
View File
@@ -0,0 +1,61 @@
# Step 7: domain-invariants
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/ADR.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/src/core/Domain.cpp`
- `/tests/core/domain_storage_test.cpp`
## Task
Add tests and small API cleanup to lock down `Domain` invariants.
Allowed files:
- Modify `/include/fesa/core/Domain.hpp`
- Modify `/src/core/Domain.cpp`
- Modify `/tests/core/domain_storage_test.cpp`
- Modify `/docs/implementation-plans/domain-model-foundation-implementation-plan.md` only if a test traceability table needs correction
Required invariants:
- `Domain` does not expose mutable references to stored objects.
- `Domain` has no equation id, displacement, residual, reaction, current time, iteration, sparse matrix, MKL, TBB, or HDF5 members.
- Retrieval does not create missing objects implicitly.
- Insert operations preserve input ids and ordering where the API promises ordering.
- Error types remain deterministic: duplicate ids use `std::invalid_argument`; missing required references use `std::invalid_argument`; direct missing lookup uses `std::out_of_range`.
## Tests To Write First
Write failing compile/runtime tests before API cleanup:
- A const `Domain` can retrieve model definitions.
- Missing direct lookup throws without changing counts.
- Failed insert due to missing reference does not change the relevant count.
- Existing node, element, material, property, set, BC, load, and step counts remain stable after failed inserts.
Run targeted CTest and verify RED before implementation.
## Acceptance Criteria
Run:
```powershell
cmake --build build/msvc-debug --config Debug
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
python scripts/validate_workspace.py
```
Update `/phases/domain-model-foundation/index.json` step 7 with `completed`, `error`, or `blocked`.
## Do Not
- Do not add new solver features beyond invariant enforcement.
- Do not refactor unrelated files.
+63
View File
@@ -0,0 +1,63 @@
# Step 8: validation-report-handoff
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/build-test-reports/README.md`
- `/docs/PROGRESS.md`
- `/docs/WORKNOTE.md`
- `/phases/domain-model-foundation/index.json`
- All Domain files created by previous steps
## Task
Run final verification and write the handoff evidence.
Allowed files:
- Create `/docs/build-test-reports/domain-model-foundation-build-test.md`
- Modify `/docs/PROGRESS.md`
- Modify `/docs/WORKNOTE.md` only if a failed approach, environment issue, or trap was encountered
- Modify `/phases/domain-model-foundation/index.json`
- Modify `/phases/index.json`
The build/test report must include:
- command log summary with exit codes
- command discovery path
- configure/build/CTest status
- failure classification or `N/A`
- failed test inventory or `N/A`
- no-change assertion for reference artifacts and tolerance policies
## Tests To Write First
This is a validation/reporting step. Do not write new C++ production behavior.
## Acceptance Criteria
Run:
```powershell
git status --short --branch
git remote -v
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
git diff --check
```
Update:
- `/phases/domain-model-foundation/index.json` step 8 with `completed`, `error`, or `blocked`.
- `/phases/index.json` phase status to `completed` only if all steps are completed.
- `/docs/PROGRESS.md` with the executed validation evidence.
## Do Not
- Do not claim MITC4 implementation, numerical correctness, reference comparison success, or release readiness.
- Do not run Abaqus, Nastran, or any reference solver.
- Do not edit reference artifacts.
+42
View File
@@ -0,0 +1,42 @@
{
"project": "FESA Structural Solver",
"phase": "domain-runtime-storage",
"steps": [
{
"step": 0,
"name": "runtime-storage-contract",
"status": "completed",
"summary": "Created the Domain runtime storage implementation plan and recorded the parser-record-to-factory-to-runtime-Domain contract."
},
{
"step": 1,
"name": "element-material-property-runtime-storage",
"status": "completed",
"summary": "Migrated Domain element, material, and shell property storage to runtime objects and moved ElementType to ModelTypes."
},
{
"step": 2,
"name": "load-boundary-runtime-storage",
"status": "completed",
"summary": "Migrated Domain load and boundary storage to runtime polymorphic objects with node-reference and duplicate-key validation."
},
{
"step": 3,
"name": "step-and-set-runtime-references",
"status": "completed",
"summary": "Updated element set and linear static step validation to reference runtime element, load, and boundary containers."
},
{
"step": 4,
"name": "legacy-definition-extraction",
"status": "completed",
"summary": "Removed core definition DTOs from Domain public API and persistent storage while keeping focused definition tests for parser/factory records."
},
{
"step": 5,
"name": "validation-report-handoff",
"status": "completed",
"summary": "Validated with script self-tests, targeted CTest, workspace validation, and git diff whitespace checks."
}
]
}
+50
View File
@@ -0,0 +1,50 @@
# Step 0: runtime-storage-contract
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/PLAN.md`
- `/docs/PROGRESS.md`
- `/docs/WORKNOTE.md`
- `/docs/AGENT_RULES.md`
- `/docs/ARCHITECTURE.md`
- `/docs/ADR.md`
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/src/core/Domain.cpp`
## Task
Create `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`.
The plan must state that `Domain` owns runtime model objects as its canonical storage:
- nodes remain value objects under `fesa::core::Node`;
- elements are stored as `fesa::element::Element` objects;
- materials are stored as `fesa::material::Material` objects;
- shell properties are stored as `fesa::property::ShellProperty` runtime objects;
- loads are stored as `fesa::load::Load` objects;
- boundary conditions are stored as `fesa::boundary::BoundaryCondition` objects;
- steps may remain `LinearStaticStepDefinition` until an analysis-step object is introduced, but step indices must reference runtime load and boundary containers;
- `core::*Definition` classes may remain as parser/factory-local records, but `Domain` must not persist them.
The plan must also list the migration order and identify the C++ tests that will be rewritten or added.
## Tests To Write First
- Documentation-only step. No C++ test is required in this step.
## Acceptance Criteria
```powershell
python -m unittest discover -s scripts -p "test_*.py"
```
## Verification Notes
1. Confirm the plan does not introduce parser implementation work.
2. Confirm the plan does not change MITC4 formulation, reference artifacts, or tolerance policy.
3. Update `phases/domain-runtime-storage/index.json` for this step result.
+64
View File
@@ -0,0 +1,64 @@
# Step 1: element-material-property-runtime-storage
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/include/fesa/core/ModelTypes.hpp`
- `/include/fesa/core/ElementDefinition.hpp`
- `/include/fesa/element/Element.hpp`
- `/include/fesa/element/Mitc4Element.hpp`
- `/include/fesa/material/Material.hpp`
- `/include/fesa/material/LinearElasticMaterial.hpp`
- `/include/fesa/property/ShellProperty.hpp`
- `/src/core/Domain.cpp`
- `/tests/core/domain_storage_test.cpp`
- `/tests/core/domain_model_object_test.cpp`
## Task
Make runtime element, material, and shell property objects the canonical Domain storage.
Required API shape:
- `void Domain::addElement(std::unique_ptr<fesa::element::Element> element)`
- `const fesa::element::Element* Domain::findElement(ElementId id) const noexcept`
- `const fesa::element::Element& Domain::element(ElementId id) const`
- `std::size_t Domain::elementCount() const noexcept`
- `void Domain::addMaterial(std::unique_ptr<fesa::material::Material> material)`
- `const fesa::material::Material* Domain::findMaterial(MaterialId id) const noexcept`
- `const fesa::material::Material& Domain::material(MaterialId id) const`
- `std::size_t Domain::materialCount() const noexcept`
- `void Domain::addShellProperty(fesa::property::ShellProperty property)`
- `const fesa::property::ShellProperty* Domain::findShellProperty(PropertyId id) const noexcept`
- `const fesa::property::ShellProperty& Domain::shellProperty(PropertyId id) const`
- `std::size_t Domain::shellPropertyCount() const noexcept`
Rules:
- Move `ElementType` to `/include/fesa/core/ModelTypes.hpp` so runtime `Element` no longer includes `/include/fesa/core/ElementDefinition.hpp`.
- `Domain::addShellProperty` must reject duplicate property ids and missing material ids.
- `Domain::addElement` must reject null pointers, duplicate element ids, missing node ids, and missing shell property ids.
- Keep C++17/MSVC compatibility and RAII ownership.
## Tests To Write First
- Rewrite or extend `/tests/core/domain_storage_test.cpp` so element, material, and shell property storage uses runtime objects instead of `ElementDefinition`, `LinearElasticMaterialDefinition`, and `ShellPropertyDefinition`.
- Add or adjust a test that proves `Element` no longer depends on `ElementDefinition` for `ElementType`.
## Acceptance Criteria
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
python scripts/validate_workspace.py
```
## Verification Notes
1. Run the targeted CTest before and after implementation to capture RED then GREEN.
2. Do not delete definition classes in this step; only remove them from Domain storage.
3. Update `phases/domain-runtime-storage/index.json` for this step result.
+59
View File
@@ -0,0 +1,59 @@
# Step 2: load-boundary-runtime-storage
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/include/fesa/load/Load.hpp`
- `/include/fesa/load/NodalLoad.hpp`
- `/include/fesa/boundary/BoundaryCondition.hpp`
- `/include/fesa/boundary/SinglePointConstraint.hpp`
- `/src/core/Domain.cpp`
- `/tests/core/domain_storage_test.cpp`
- `/tests/core/domain_model_object_test.cpp`
## Task
Make runtime load and boundary objects the canonical Domain storage.
Required API shape:
- `std::size_t Domain::addLoad(std::unique_ptr<fesa::load::Load> load)`
- `const fesa::load::Load* Domain::findLoad(std::size_t index) const noexcept`
- `const fesa::load::Load& Domain::load(std::size_t index) const`
- `std::size_t Domain::loadCount() const noexcept`
- `std::size_t Domain::addBoundaryCondition(std::unique_ptr<fesa::boundary::BoundaryCondition> boundary)`
- `const fesa::boundary::BoundaryCondition* Domain::findBoundaryCondition(std::size_t index) const noexcept`
- `const fesa::boundary::BoundaryCondition& Domain::boundaryCondition(std::size_t index) const`
- `std::size_t Domain::boundaryConditionCount() const noexcept`
Rules:
- Reject null load and boundary pointers.
- `NodalLoad` must reference an existing node.
- `SinglePointConstraint` must reference an existing node.
- Duplicate `(node, dof)` nodal loads must throw.
- Duplicate `(node, dof)` single-point constraints must throw.
- Runtime load and boundary storage must not use `core::NodalLoadDefinition` or `core::BoundaryCondition`.
## Tests To Write First
- Rewrite or extend `/tests/core/domain_storage_test.cpp` so load and boundary storage uses runtime objects.
- Add missing-node tests for `NodalLoad` and `SinglePointConstraint` runtime insertion.
## Acceptance Criteria
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
python scripts/validate_workspace.py
```
## Verification Notes
1. Run targeted CTest before production edits and confirm failure.
2. Keep solver state out of `Domain`.
3. Update `phases/domain-runtime-storage/index.json` for this step result.
+42
View File
@@ -0,0 +1,42 @@
# Step 3: step-and-set-runtime-references
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/include/fesa/core/StepDefinition.hpp`
- `/src/core/Domain.cpp`
- `/tests/core/domain_storage_test.cpp`
## Task
Make Domain set and step validation reference runtime storage.
Rules:
- `Domain::addElementSet` must validate ids against runtime elements stored by `Domain::findElement`.
- `Domain::addStep(LinearStaticStepDefinition)` may remain for now, but its boundary and load indices must validate against runtime boundary and load containers.
- Failed insertions must not mutate Domain counts.
- No equation ids, solver vectors, residuals, reactions, current time, or iteration state may be added to Domain.
## Tests To Write First
- Rewrite `/tests/core/domain_storage_test.cpp` set and step tests so they build elements, loads, and boundary conditions through runtime object APIs.
- Preserve count-stability tests for failed insertions.
## Acceptance Criteria
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
python scripts/validate_workspace.py
```
## Verification Notes
1. Run targeted CTest before production edits and confirm failure.
2. Keep `LinearStaticStepDefinition` as the only remaining step record until a separate step-model phase exists.
3. Update `phases/domain-runtime-storage/index.json` for this step result.
+47
View File
@@ -0,0 +1,47 @@
# Step 4: legacy-definition-extraction
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/src/core/Domain.cpp`
- `/tests/core/domain_storage_test.cpp`
- `/CMakeLists.txt`
## Task
Remove parser-definition DTO persistence from `Domain`.
Rules:
- `Domain` must no longer include or store:
- `fesa/core/BoundaryCondition.hpp`
- `fesa/core/ElementDefinition.hpp`
- `fesa/core/LoadDefinition.hpp`
- `fesa/core/MaterialDefinition.hpp`
- `fesa/core/PropertyDefinition.hpp`
- `Domain` may still include `StepDefinition.hpp` until a runtime step object is designed.
- Definition classes may remain in the repository and their focused tests may remain, but they must not be part of Domain storage or Domain public API.
- Do not implement parser or factory behavior in this step.
## Tests To Write First
- Add or update a compile-time assertion in `/tests/core/domain_storage_test.cpp` or a dedicated domain API test proving Domain retrieval returns runtime types for elements, materials, properties, loads, and boundary conditions.
## Acceptance Criteria
```powershell
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
git diff --check
```
## Verification Notes
1. Confirm no `Domain` method returns a definition DTO except `LinearStaticStepDefinition`.
2. Confirm `include/fesa/element/Element.hpp` no longer includes `ElementDefinition.hpp`.
3. Update `phases/domain-runtime-storage/index.json` for this step result.
+42
View File
@@ -0,0 +1,42 @@
# Step 5: validation-report-handoff
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/PLAN.md`
- `/docs/PROGRESS.md`
- `/docs/WORKNOTE.md`
- `/docs/AGENT_RULES.md`
- `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`
- `/phases/domain-runtime-storage/index.json`
## Task
Record validation evidence and handoff notes after the runtime Domain storage migration.
Required updates:
- Update `/docs/PROGRESS.md` with completed runtime Domain storage work and validation commands.
- Update `/docs/PLAN.md` only if this migration changes sequencing or acceptance criteria.
- Update `/docs/WORKNOTE.md` only if there were failures, repeated mistakes, or environment traps.
- Mark `/phases/domain-runtime-storage/index.json` steps and `/phases/index.json` phase status according to actual results.
## Tests To Write First
- Documentation/reporting step. No new C++ test is required.
## Acceptance Criteria
```powershell
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
git diff --check
```
## Verification Notes
1. Do not claim numerical solver correctness.
2. Do not mark the phase completed unless all previous steps are complete and validation passed.
3. Keep validation evidence concrete: command, result, and scope.
+16
View File
@@ -0,0 +1,16 @@
{
"phases": [
{
"dir": "domain-model-foundation",
"status": "completed"
},
{
"dir": "analysis-model-objects",
"status": "completed"
},
{
"dir": "domain-runtime-storage",
"status": "completed"
}
]
}
+56 -1
View File
@@ -87,7 +87,62 @@ class ValidateWorkspaceTests(unittest.TestCase):
with patch.object(validate_workspace.shutil, "which", return_value=None):
env = validate_workspace.validation_environment({"PATH": "C:\\Windows\\System32"})
self.assertTrue(env["PATH"].startswith(str(common_bin)))
path_key = "Path" if os.name == "nt" else "PATH"
self.assertTrue(env[path_key].startswith(str(common_bin)))
def test_common_cmake_install_path_updates_existing_windows_path_key(self):
validate_workspace = load_validate_workspace()
with tempfile.TemporaryDirectory() as tmp:
common_bin = Path(tmp) / "CMake" / "bin"
common_bin.mkdir(parents=True)
(common_bin / "cmake.exe").write_text("", encoding="utf-8")
with patch.object(validate_workspace, "COMMON_CMAKE_BIN", common_bin):
with patch.object(validate_workspace.shutil, "which", return_value=None):
env = validate_workspace.validation_environment({"Path": "C:\\Windows\\System32"})
self.assertIn("Path", env)
self.assertNotIn("PATH", env)
self.assertTrue(env["Path"].startswith(str(common_bin)))
def test_common_cmake_install_path_normalizes_uppercase_path_on_windows(self):
if os.name != "nt":
self.skipTest("Windows-specific subprocess environment behavior")
validate_workspace = load_validate_workspace()
with tempfile.TemporaryDirectory() as tmp:
common_bin = Path(tmp) / "CMake" / "bin"
common_bin.mkdir(parents=True)
(common_bin / "cmake.exe").write_text("", encoding="utf-8")
with patch.object(validate_workspace, "COMMON_CMAKE_BIN", common_bin):
with patch.object(validate_workspace.shutil, "which", return_value=None):
env = validate_workspace.validation_environment({"PATH": "C:\\Windows\\System32"})
self.assertIn("Path", env)
self.assertNotIn("PATH", env)
self.assertTrue(env["Path"].startswith(str(common_bin)))
def test_common_cmake_executable_is_used_when_command_tool_is_not_on_path(self):
validate_workspace = load_validate_workspace()
with tempfile.TemporaryDirectory() as tmp:
common_bin = Path(tmp) / "CMake" / "bin"
common_bin.mkdir(parents=True)
(common_bin / "cmake.exe").write_text("", encoding="utf-8")
with patch.object(validate_workspace, "COMMON_CMAKE_BIN", common_bin):
with patch.object(validate_workspace.shutil, "which", return_value=None):
command = validate_workspace.resolve_validation_command("cmake --version")
self.assertEqual(command, f'"{common_bin / "cmake.exe"}" --version')
def test_common_ctest_executable_is_used_when_command_tool_is_not_on_path(self):
validate_workspace = load_validate_workspace()
with tempfile.TemporaryDirectory() as tmp:
common_bin = Path(tmp) / "CMake" / "bin"
common_bin.mkdir(parents=True)
(common_bin / "ctest.exe").write_text("", encoding="utf-8")
with patch.object(validate_workspace, "COMMON_CMAKE_BIN", common_bin):
with patch.object(validate_workspace.shutil, "which", return_value=None):
command = validate_workspace.resolve_validation_command("ctest --version")
self.assertEqual(command, f'"{common_bin / "ctest.exe"}" --version')
if __name__ == "__main__":
+31 -3
View File
@@ -88,7 +88,27 @@ def discover_commands(root: Path) -> list[str]:
return load_cmake_commands(root)
def resolve_validation_command(command: str) -> str:
parts = command.split(maxsplit=1)
if not parts:
return command
tool = parts[0].lower()
if tool not in {"cmake", "ctest"}:
return command
if shutil.which(tool) is not None:
return command
exe = COMMON_CMAKE_BIN / f"{tool}.exe"
if not exe.exists():
return command
suffix = f" {parts[1]}" if len(parts) > 1 else ""
return f'"{exe}"{suffix}'
def run_command(command: str, root: Path) -> subprocess.CompletedProcess:
command = resolve_validation_command(command)
return subprocess.run(
command,
cwd=root,
@@ -103,17 +123,25 @@ def run_command(command: str, root: Path) -> subprocess.CompletedProcess:
def validation_environment(base_env: os._Environ | dict[str, str]) -> dict[str, str]:
env = dict(base_env)
if shutil.which("cmake") is not None:
path_key = "Path" if os.name == "nt" else "PATH"
path_values = []
for key in list(env):
if key.lower() == "path":
path_values.append(env.pop(key))
current_path = os.pathsep.join(part for part in path_values if part)
env[path_key] = current_path
if shutil.which("cmake", path=current_path) is not None:
return env
cmake_exe = COMMON_CMAKE_BIN / "cmake.exe"
if not cmake_exe.exists():
return env
current_path = env.get("PATH", "")
paths = [part for part in current_path.split(os.pathsep) if part]
common_bin_text = str(COMMON_CMAKE_BIN)
if not any(part.lower() == common_bin_text.lower() for part in paths):
env["PATH"] = common_bin_text + (os.pathsep + current_path if current_path else "")
env[path_key] = common_bin_text + (os.pathsep + current_path if current_path else "")
return env
+24
View File
@@ -0,0 +1,24 @@
#include "fesa/boundary/SinglePointConstraint.hpp"
namespace fesa::boundary {
SinglePointConstraint::SinglePointConstraint(NodeId node_id, Dof dof, double value)
: node_id_(node_id), dof_(dof), value_(value) {}
BoundaryConditionKind SinglePointConstraint::kind() const noexcept {
return BoundaryConditionKind::SinglePointConstraint;
}
NodeId SinglePointConstraint::nodeId() const noexcept {
return node_id_;
}
Dof SinglePointConstraint::dof() const noexcept {
return dof_;
}
double SinglePointConstraint::value() const noexcept {
return value_;
}
} // namespace fesa::boundary
+441
View File
@@ -0,0 +1,441 @@
#include "fesa/core/Domain.hpp"
#include "fesa/boundary/SinglePointConstraint.hpp"
#include "fesa/core/BoundaryCondition.hpp"
#include "fesa/core/ElementDefinition.hpp"
#include "fesa/core/LoadDefinition.hpp"
#include "fesa/core/MaterialDefinition.hpp"
#include "fesa/core/PropertyDefinition.hpp"
#include "fesa/load/NodalLoad.hpp"
#include <stdexcept>
#include <utility>
namespace fesa::core {
BoundaryCondition::BoundaryCondition(NodeId node_id, Dof dof, double value)
: node_id_(node_id), dof_(dof), value_(value) {}
NodeId BoundaryCondition::nodeId() const noexcept {
return node_id_;
}
Dof BoundaryCondition::dof() const noexcept {
return dof_;
}
double BoundaryCondition::value() const noexcept {
return value_;
}
ElementDefinition::ElementDefinition(
ElementId id,
ElementType type,
std::array<NodeId, 4> connectivity,
PropertyId property_id)
: id_(id), type_(type), connectivity_(connectivity), property_id_(property_id) {}
ElementId ElementDefinition::id() const noexcept {
return id_;
}
ElementType ElementDefinition::type() const noexcept {
return type_;
}
const std::array<NodeId, 4>& ElementDefinition::connectivity() const noexcept {
return connectivity_;
}
PropertyId ElementDefinition::propertyId() const noexcept {
return property_id_;
}
LinearElasticMaterialDefinition::LinearElasticMaterialDefinition(
MaterialId id,
double young_modulus,
double poisson_ratio)
: id_(id), young_modulus_(young_modulus), poisson_ratio_(poisson_ratio) {}
MaterialId LinearElasticMaterialDefinition::id() const noexcept {
return id_;
}
double LinearElasticMaterialDefinition::youngModulus() const noexcept {
return young_modulus_;
}
double LinearElasticMaterialDefinition::poissonRatio() const noexcept {
return poisson_ratio_;
}
LinearStaticStepDefinition::LinearStaticStepDefinition(
StepId id,
std::string name,
std::vector<std::size_t> boundary_condition_indices,
std::vector<std::size_t> load_indices)
: id_(id),
name_(std::move(name)),
boundary_condition_indices_(std::move(boundary_condition_indices)),
load_indices_(std::move(load_indices)) {}
StepId LinearStaticStepDefinition::id() const noexcept {
return id_;
}
const std::string& LinearStaticStepDefinition::name() const noexcept {
return name_;
}
const std::vector<std::size_t>& LinearStaticStepDefinition::boundaryConditionIndices() const noexcept {
return boundary_condition_indices_;
}
const std::vector<std::size_t>& LinearStaticStepDefinition::loadIndices() const noexcept {
return load_indices_;
}
NodalLoadDefinition::NodalLoadDefinition(NodeId node_id, Dof dof, double value)
: node_id_(node_id), dof_(dof), value_(value) {}
NodeId NodalLoadDefinition::nodeId() const noexcept {
return node_id_;
}
Dof NodalLoadDefinition::dof() const noexcept {
return dof_;
}
double NodalLoadDefinition::value() const noexcept {
return value_;
}
Node::Node(NodeId id, double x, double y, double z)
: id_(id), coordinates_{x, y, z} {}
NodeId Node::id() const noexcept {
return id_;
}
double Node::x() const noexcept {
return coordinates_[0];
}
double Node::y() const noexcept {
return coordinates_[1];
}
double Node::z() const noexcept {
return coordinates_[2];
}
const std::array<double, 3>& Node::coordinates() const noexcept {
return coordinates_;
}
ShellPropertyDefinition::ShellPropertyDefinition(
PropertyId id,
MaterialId material_id,
double thickness)
: id_(id), material_id_(material_id), thickness_(thickness) {}
PropertyId ShellPropertyDefinition::id() const noexcept {
return id_;
}
MaterialId ShellPropertyDefinition::materialId() const noexcept {
return material_id_;
}
double ShellPropertyDefinition::thickness() const noexcept {
return thickness_;
}
void Domain::addNode(Node node) {
const NodeId id = node.id();
const auto inserted = nodes_.emplace(id, std::move(node));
if (!inserted.second) {
throw std::invalid_argument("duplicate node id");
}
}
void Domain::addElement(std::unique_ptr<fesa::element::Element> element) {
if (!element) {
throw std::invalid_argument("element is null");
}
const ElementId id = element->id();
if (elements_.find(id) != elements_.end()) {
throw std::invalid_argument("duplicate element id");
}
for (const NodeId node_id : element->connectivity()) {
if (findNode(node_id) == nullptr) {
throw std::invalid_argument("element references missing node id");
}
}
if (findShellProperty(element->propertyId()) == nullptr) {
throw std::invalid_argument("element references missing shell property id");
}
elements_.emplace(id, std::move(element));
}
void Domain::addMaterial(std::unique_ptr<fesa::material::Material> material) {
if (!material) {
throw std::invalid_argument("material is null");
}
const MaterialId id = material->id();
const auto inserted = materials_.emplace(id, std::move(material));
if (!inserted.second) {
throw std::invalid_argument("duplicate material id");
}
}
void Domain::addShellProperty(fesa::property::ShellProperty property) {
const PropertyId id = property.id();
if (shell_properties_.find(id) != shell_properties_.end()) {
throw std::invalid_argument("duplicate shell property id");
}
if (findMaterial(property.materialId()) == nullptr) {
throw std::invalid_argument("shell property references missing material id");
}
shell_properties_.emplace(id, std::move(property));
}
void Domain::addNodeSet(std::string name, std::vector<NodeId> node_ids) {
if (node_sets_.find(name) != node_sets_.end()) {
throw std::invalid_argument("duplicate node set name");
}
for (const NodeId node_id : node_ids) {
if (findNode(node_id) == nullptr) {
throw std::invalid_argument("node set references missing node id");
}
}
node_sets_.emplace(std::move(name), std::move(node_ids));
}
void Domain::addElementSet(std::string name, std::vector<ElementId> element_ids) {
if (element_sets_.find(name) != element_sets_.end()) {
throw std::invalid_argument("duplicate element set name");
}
for (const ElementId element_id : element_ids) {
if (findElement(element_id) == nullptr) {
throw std::invalid_argument("element set references missing element id");
}
}
element_sets_.emplace(std::move(name), std::move(element_ids));
}
std::size_t Domain::addBoundaryCondition(std::unique_ptr<fesa::boundary::BoundaryCondition> boundary) {
if (!boundary) {
throw std::invalid_argument("boundary condition is null");
}
if (const auto* spc = dynamic_cast<const fesa::boundary::SinglePointConstraint*>(boundary.get())) {
if (findNode(spc->nodeId()) == nullptr) {
throw std::invalid_argument("boundary condition references missing node id");
}
for (const auto& existing : boundary_conditions_) {
const auto* existing_spc =
dynamic_cast<const fesa::boundary::SinglePointConstraint*>(existing.get());
if (existing_spc != nullptr &&
existing_spc->nodeId() == spc->nodeId() &&
existing_spc->dof() == spc->dof()) {
throw std::invalid_argument("duplicate boundary condition key");
}
}
}
const std::size_t index = boundary_conditions_.size();
boundary_conditions_.push_back(std::move(boundary));
return index;
}
std::size_t Domain::addLoad(std::unique_ptr<fesa::load::Load> load) {
if (!load) {
throw std::invalid_argument("load is null");
}
if (const auto* nodal = dynamic_cast<const fesa::load::NodalLoad*>(load.get())) {
if (findNode(nodal->nodeId()) == nullptr) {
throw std::invalid_argument("nodal load references missing node id");
}
for (const auto& existing : loads_) {
const auto* existing_nodal = dynamic_cast<const fesa::load::NodalLoad*>(existing.get());
if (existing_nodal != nullptr &&
existing_nodal->nodeId() == nodal->nodeId() &&
existing_nodal->dof() == nodal->dof()) {
throw std::invalid_argument("duplicate nodal load key");
}
}
}
const std::size_t index = loads_.size();
loads_.push_back(std::move(load));
return index;
}
void Domain::addStep(LinearStaticStepDefinition step) {
const StepId id = step.id();
if (steps_.find(id) != steps_.end()) {
throw std::invalid_argument("duplicate step id");
}
for (const std::size_t index : step.boundaryConditionIndices()) {
if (index >= boundary_conditions_.size()) {
throw std::invalid_argument("step references missing boundary condition");
}
}
for (const std::size_t index : step.loadIndices()) {
if (index >= loads_.size()) {
throw std::invalid_argument("step references missing load");
}
}
steps_.emplace(id, std::move(step));
}
const Node* Domain::findNode(NodeId id) const noexcept {
const auto it = nodes_.find(id);
return it == nodes_.end() ? nullptr : &it->second;
}
const Node& Domain::node(NodeId id) const {
const Node* found = findNode(id);
if (found == nullptr) {
throw std::out_of_range("node id not found");
}
return *found;
}
std::size_t Domain::nodeCount() const noexcept {
return nodes_.size();
}
const fesa::element::Element* Domain::findElement(ElementId id) const noexcept {
const auto it = elements_.find(id);
return it == elements_.end() ? nullptr : it->second.get();
}
const fesa::element::Element& Domain::element(ElementId id) const {
const fesa::element::Element* found = findElement(id);
if (found == nullptr) {
throw std::out_of_range("element id not found");
}
return *found;
}
std::size_t Domain::elementCount() const noexcept {
return elements_.size();
}
const fesa::material::Material* Domain::findMaterial(MaterialId id) const noexcept {
const auto it = materials_.find(id);
return it == materials_.end() ? nullptr : it->second.get();
}
const fesa::material::Material& Domain::material(MaterialId id) const {
const fesa::material::Material* found = findMaterial(id);
if (found == nullptr) {
throw std::out_of_range("material id not found");
}
return *found;
}
std::size_t Domain::materialCount() const noexcept {
return materials_.size();
}
const fesa::property::ShellProperty* Domain::findShellProperty(PropertyId id) const noexcept {
const auto it = shell_properties_.find(id);
return it == shell_properties_.end() ? nullptr : &it->second;
}
const fesa::property::ShellProperty& Domain::shellProperty(PropertyId id) const {
const fesa::property::ShellProperty* found = findShellProperty(id);
if (found == nullptr) {
throw std::out_of_range("shell property id not found");
}
return *found;
}
std::size_t Domain::shellPropertyCount() const noexcept {
return shell_properties_.size();
}
const std::vector<NodeId>* Domain::findNodeSet(const std::string& name) const noexcept {
const auto it = node_sets_.find(name);
return it == node_sets_.end() ? nullptr : &it->second;
}
const std::vector<NodeId>& Domain::nodeSet(const std::string& name) const {
const std::vector<NodeId>* found = findNodeSet(name);
if (found == nullptr) {
throw std::out_of_range("node set not found");
}
return *found;
}
std::size_t Domain::nodeSetCount() const noexcept {
return node_sets_.size();
}
const std::vector<ElementId>* Domain::findElementSet(const std::string& name) const noexcept {
const auto it = element_sets_.find(name);
return it == element_sets_.end() ? nullptr : &it->second;
}
const std::vector<ElementId>& Domain::elementSet(const std::string& name) const {
const std::vector<ElementId>* found = findElementSet(name);
if (found == nullptr) {
throw std::out_of_range("element set not found");
}
return *found;
}
std::size_t Domain::elementSetCount() const noexcept {
return element_sets_.size();
}
const fesa::boundary::BoundaryCondition* Domain::findBoundaryCondition(std::size_t index) const noexcept {
return index < boundary_conditions_.size() ? boundary_conditions_[index].get() : nullptr;
}
const fesa::boundary::BoundaryCondition& Domain::boundaryCondition(std::size_t index) const {
const fesa::boundary::BoundaryCondition* found = findBoundaryCondition(index);
if (found == nullptr) {
throw std::out_of_range("boundary condition index not found");
}
return *found;
}
std::size_t Domain::boundaryConditionCount() const noexcept {
return boundary_conditions_.size();
}
const fesa::load::Load* Domain::findLoad(std::size_t index) const noexcept {
return index < loads_.size() ? loads_[index].get() : nullptr;
}
const fesa::load::Load& Domain::load(std::size_t index) const {
const fesa::load::Load* found = findLoad(index);
if (found == nullptr) {
throw std::out_of_range("load index not found");
}
return *found;
}
std::size_t Domain::loadCount() const noexcept {
return loads_.size();
}
const LinearStaticStepDefinition* Domain::findStep(StepId id) const noexcept {
const auto it = steps_.find(id);
return it == steps_.end() ? nullptr : &it->second;
}
const LinearStaticStepDefinition& Domain::step(StepId id) const {
const LinearStaticStepDefinition* found = findStep(id);
if (found == nullptr) {
throw std::out_of_range("step id not found");
}
return *found;
}
std::size_t Domain::stepCount() const noexcept {
return steps_.size();
}
} // namespace fesa::core
+37
View File
@@ -0,0 +1,37 @@
#include "fesa/element/Mitc4Element.hpp"
#include "fesa/core/Node.hpp"
namespace fesa::element {
Mitc4Element::Mitc4Element(
ElementId id,
std::array<NodeId, 4> connectivity,
PropertyId property_id)
: id_(id), connectivity_(connectivity), property_id_(property_id) {}
ElementId Mitc4Element::id() const noexcept {
return id_;
}
ElementType Mitc4Element::type() const noexcept {
return ElementType::Mitc4;
}
std::size_t Mitc4Element::nodeCount() const noexcept {
return connectivity_.size();
}
std::size_t Mitc4Element::dofCount() const noexcept {
return connectivity_.size() * fesa::core::Node::dofCount();
}
const std::array<NodeId, 4>& Mitc4Element::connectivity() const noexcept {
return connectivity_;
}
PropertyId Mitc4Element::propertyId() const noexcept {
return property_id_;
}
} // namespace fesa::element
+24
View File
@@ -0,0 +1,24 @@
#include "fesa/load/NodalLoad.hpp"
namespace fesa::load {
NodalLoad::NodalLoad(NodeId node_id, Dof dof, double value)
: node_id_(node_id), dof_(dof), value_(value) {}
LoadKind NodalLoad::kind() const noexcept {
return LoadKind::Nodal;
}
NodeId NodalLoad::nodeId() const noexcept {
return node_id_;
}
Dof NodalLoad::dof() const noexcept {
return dof_;
}
double NodalLoad::value() const noexcept {
return value_;
}
} // namespace fesa::load
+23
View File
@@ -0,0 +1,23 @@
#include "fesa/material/LinearElasticMaterial.hpp"
namespace fesa::material {
LinearElasticMaterial::LinearElasticMaterial(
MaterialId id,
double young_modulus,
double poisson_ratio)
: id_(id), young_modulus_(young_modulus), poisson_ratio_(poisson_ratio) {}
MaterialId LinearElasticMaterial::id() const noexcept {
return id_;
}
double LinearElasticMaterial::youngModulus() const noexcept {
return young_modulus_;
}
double LinearElasticMaterial::poissonRatio() const noexcept {
return poisson_ratio_;
}
} // namespace fesa::material
+26
View File
@@ -0,0 +1,26 @@
#include "fesa/property/ShellProperty.hpp"
#include <stdexcept>
namespace fesa::property {
ShellProperty::ShellProperty(PropertyId id, MaterialId material_id, double thickness)
: id_(id), material_id_(material_id), thickness_(thickness) {
if (thickness <= 0.0) {
throw std::invalid_argument("shell property thickness must be positive");
}
}
PropertyId ShellProperty::id() const noexcept {
return id_;
}
MaterialId ShellProperty::materialId() const noexcept {
return material_id_;
}
double ShellProperty::thickness() const noexcept {
return thickness_;
}
} // namespace fesa::property
+31
View File
@@ -0,0 +1,31 @@
#include "fesa/boundary/BoundaryCondition.hpp"
#include "fesa/boundary/SinglePointConstraint.hpp"
#include <memory>
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_boundary_base_tests() {
std::unique_ptr<fesa::boundary::BoundaryCondition> owned =
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::UR3, 0.25);
const fesa::boundary::BoundaryCondition& boundary = *owned;
const auto& spc = static_cast<const fesa::boundary::SinglePointConstraint&>(boundary);
if (const int result = require(boundary.kind() == fesa::boundary::BoundaryConditionKind::SinglePointConstraint);
result != 0) {
return result;
}
if (const int result = require(spc.nodeId() == 1); result != 0) {
return result;
}
if (const int result = require(spc.dof() == fesa::core::Dof::UR3); result != 0) {
return result;
}
return require(spc.value() == 0.25);
}
+25
View File
@@ -0,0 +1,25 @@
#include "fesa/core/BoundaryCondition.hpp"
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_boundary_condition_tests() {
const fesa::core::BoundaryCondition condition{1, fesa::core::Dof::UR3, 0.25};
if (const int result = require(condition.nodeId() == 1); result != 0) {
return result;
}
if (const int result = require(condition.dof() == fesa::core::Dof::UR3); result != 0) {
return result;
}
if (const int result = require(condition.value() == 0.25); result != 0) {
return result;
}
return 0;
}
+44
View File
@@ -0,0 +1,44 @@
int run_boundary_condition_tests();
int run_domain_model_object_tests();
int run_domain_storage_tests();
int run_element_definition_tests();
int run_load_definition_tests();
int run_material_definition_tests();
int run_model_types_tests();
int run_node_tests();
int run_property_definition_tests();
int run_step_definition_tests();
int main() {
if (const int result = run_model_types_tests(); result != 0) {
return result;
}
if (const int result = run_node_tests(); result != 0) {
return result;
}
if (const int result = run_element_definition_tests(); result != 0) {
return result;
}
if (const int result = run_material_definition_tests(); result != 0) {
return result;
}
if (const int result = run_property_definition_tests(); result != 0) {
return result;
}
if (const int result = run_boundary_condition_tests(); result != 0) {
return result;
}
if (const int result = run_domain_model_object_tests(); result != 0) {
return result;
}
if (const int result = run_load_definition_tests(); result != 0) {
return result;
}
if (const int result = run_step_definition_tests(); result != 0) {
return result;
}
if (const int result = run_domain_storage_tests(); result != 0) {
return result;
}
return 0;
}
+170
View File
@@ -0,0 +1,170 @@
#include "fesa/boundary/SinglePointConstraint.hpp"
#include "fesa/core/Domain.hpp"
#include "fesa/element/Mitc4Element.hpp"
#include "fesa/load/NodalLoad.hpp"
#include "fesa/material/LinearElasticMaterial.hpp"
#include "fesa/property/ShellProperty.hpp"
#include <memory>
#include <stdexcept>
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
template <typename Exception, typename Function>
int require_throws(Function&& function) {
try {
function();
} catch (const Exception&) {
return 0;
} catch (...) {
return 1;
}
return 1;
}
fesa::core::Domain populated_domain() {
fesa::core::Domain domain;
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
domain.addNode(fesa::core::Node{2, 1.0, 0.0, 0.0});
domain.addNode(fesa::core::Node{3, 1.0, 1.0, 0.0});
domain.addNode(fesa::core::Node{4, 0.0, 1.0, 0.0});
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3));
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.01});
return domain;
}
int domain_owns_element_and_material_objects() {
fesa::core::Domain domain = populated_domain();
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
500));
const fesa::element::Element* element = domain.findElement(100);
if (const int result = require(element != nullptr); result != 0) {
return result;
}
if (const int result = require(element->type() == fesa::core::ElementType::Mitc4); result != 0) {
return result;
}
if (const int result = require(domain.element(100).propertyId() == 500); result != 0) {
return result;
}
const fesa::material::Material* material = domain.findMaterial(700);
if (const int result = require(material != nullptr); result != 0) {
return result;
}
return require(domain.material(700).id() == 700);
}
int duplicate_element_and_material_object_ids_throw() {
fesa::core::Domain domain = populated_domain();
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
500));
if (const int result = require_throws<std::invalid_argument>([&domain]() {
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
500));
});
result != 0) {
return result;
}
return require_throws<std::invalid_argument>([&domain]() {
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 100.0, 0.25));
});
}
int missing_model_object_direct_lookup_throws() {
const fesa::core::Domain domain;
if (const int result = require(domain.findElement(404) == nullptr); result != 0) {
return result;
}
if (const int result = require(domain.findMaterial(404) == nullptr); result != 0) {
return result;
}
if (const int result = require_throws<std::out_of_range>([&domain]() {
(void)domain.element(404);
});
result != 0) {
return result;
}
return require_throws<std::out_of_range>([&domain]() {
(void)domain.material(404);
});
}
int domain_owns_load_and_boundary_objects_by_index() {
fesa::core::Domain domain = populated_domain();
const std::size_t load_index = domain.addLoad(
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
const std::size_t boundary_index = domain.addBoundaryCondition(
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
if (const int result = require(load_index == 0); result != 0) {
return result;
}
if (const int result = require(boundary_index == 0); result != 0) {
return result;
}
if (const int result = require(domain.findLoad(load_index) != nullptr); result != 0) {
return result;
}
if (const int result = require(domain.findBoundaryCondition(boundary_index) != nullptr); result != 0) {
return result;
}
if (const int result = require(domain.load(load_index).kind() == fesa::load::LoadKind::Nodal); result != 0) {
return result;
}
return require(
domain.boundaryCondition(boundary_index).kind() ==
fesa::boundary::BoundaryConditionKind::SinglePointConstraint);
}
int duplicate_load_and_boundary_keys_throw() {
fesa::core::Domain domain = populated_domain();
domain.addLoad(std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
domain.addBoundaryCondition(std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
if (const int result = require_throws<std::invalid_argument>([&domain]() {
domain.addLoad(std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -200.0));
});
result != 0) {
return result;
}
return require_throws<std::invalid_argument>([&domain]() {
domain.addBoundaryCondition(std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 1.0));
});
}
} // namespace
int run_domain_model_object_tests() {
if (const int result = domain_owns_element_and_material_objects(); result != 0) {
return result;
}
if (const int result = duplicate_element_and_material_object_ids_throw(); result != 0) {
return result;
}
if (const int result = missing_model_object_direct_lookup_throws(); result != 0) {
return result;
}
if (const int result = domain_owns_load_and_boundary_objects_by_index(); result != 0) {
return result;
}
if (const int result = duplicate_load_and_boundary_keys_throw(); result != 0) {
return result;
}
return 0;
}
+679
View File
@@ -0,0 +1,679 @@
#include "fesa/boundary/SinglePointConstraint.hpp"
#include "fesa/core/Domain.hpp"
#include "fesa/core/Node.hpp"
#include "fesa/core/StepDefinition.hpp"
#include "fesa/element/Mitc4Element.hpp"
#include "fesa/load/NodalLoad.hpp"
#include "fesa/material/LinearElasticMaterial.hpp"
#include "fesa/property/ShellProperty.hpp"
#include <array>
#include <memory>
#include <stdexcept>
#include <type_traits>
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
template <typename Exception, typename Function>
int require_throws(Function&& function) {
try {
function();
} catch (const Exception&) {
return 0;
} catch (...) {
return 1;
}
return 1;
}
void add_four_nodes(fesa::core::Domain& domain) {
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
domain.addNode(fesa::core::Node{2, 1.0, 0.0, 0.0});
domain.addNode(fesa::core::Node{3, 1.0, 1.0, 0.0});
domain.addNode(fesa::core::Node{4, 0.0, 1.0, 0.0});
}
void add_material_and_property(fesa::core::Domain& domain) {
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3));
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.01});
}
void add_element(fesa::core::Domain& domain) {
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
500));
}
int add_and_retrieve_node_by_id() {
fesa::core::Domain domain;
domain.addNode(fesa::core::Node{10, 1.0, 2.0, 3.0});
if (const int result = require(domain.nodeCount() == 1); result != 0) {
return result;
}
const fesa::core::Node* found = domain.findNode(10);
if (const int result = require(found != nullptr); result != 0) {
return result;
}
if (const int result = require(found->id() == 10); result != 0) {
return result;
}
if (const int result = require(found->x() == 1.0); result != 0) {
return result;
}
if (const int result = require(found->y() == 2.0); result != 0) {
return result;
}
if (const int result = require(found->z() == 3.0); result != 0) {
return result;
}
const fesa::core::Node& direct = domain.node(10);
return require(direct.id() == 10);
}
int missing_node_lookup_contracts() {
const fesa::core::Domain domain;
if (const int result = require(domain.findNode(99) == nullptr); result != 0) {
return result;
}
return require_throws<std::out_of_range>([&domain]() {
(void)domain.node(99);
});
}
int duplicate_node_id_throws() {
fesa::core::Domain domain;
domain.addNode(fesa::core::Node{10, 0.0, 0.0, 0.0});
return require_throws<std::invalid_argument>([&domain]() {
domain.addNode(fesa::core::Node{10, 1.0, 0.0, 0.0});
});
}
int add_and_retrieve_element_by_id() {
fesa::core::Domain domain;
add_four_nodes(domain);
add_material_and_property(domain);
add_element(domain);
if (const int result = require(domain.elementCount() == 1); result != 0) {
return result;
}
const fesa::element::Element* found = domain.findElement(100);
if (const int result = require(found != nullptr); result != 0) {
return result;
}
if (const int result = require(found->id() == 100); result != 0) {
return result;
}
if (const int result = require(found->type() == fesa::core::ElementType::Mitc4); result != 0) {
return result;
}
if (const int result = require(found->propertyId() == 500); result != 0) {
return result;
}
if (const int result = require(found->connectivity()[0] == 1); result != 0) {
return result;
}
if (const int result = require(found->connectivity()[1] == 2); result != 0) {
return result;
}
if (const int result = require(found->connectivity()[2] == 3); result != 0) {
return result;
}
if (const int result = require(found->connectivity()[3] == 4); result != 0) {
return result;
}
const fesa::element::Element& direct = domain.element(100);
return require(direct.id() == 100);
}
int duplicate_element_id_throws() {
fesa::core::Domain domain;
add_four_nodes(domain);
add_material_and_property(domain);
add_element(domain);
return require_throws<std::invalid_argument>([&domain]() {
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
500));
});
}
int element_referencing_missing_node_throws() {
fesa::core::Domain domain;
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
domain.addNode(fesa::core::Node{2, 1.0, 0.0, 0.0});
domain.addNode(fesa::core::Node{3, 1.0, 1.0, 0.0});
add_material_and_property(domain);
return require_throws<std::invalid_argument>([&domain]() {
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
500));
});
}
int element_referencing_missing_property_throws() {
fesa::core::Domain domain;
add_four_nodes(domain);
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3));
return require_throws<std::invalid_argument>([&domain]() {
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
500));
});
}
int missing_element_lookup_contracts() {
const fesa::core::Domain domain;
if (const int result = require(domain.findElement(404) == nullptr); result != 0) {
return result;
}
return require_throws<std::out_of_range>([&domain]() {
(void)domain.element(404);
});
}
int add_and_retrieve_material_and_property() {
fesa::core::Domain domain;
add_material_and_property(domain);
if (const int result = require(domain.materialCount() == 1); result != 0) {
return result;
}
if (const int result = require(domain.shellPropertyCount() == 1); result != 0) {
return result;
}
const fesa::material::Material* material = domain.findMaterial(700);
if (const int result = require(material != nullptr); result != 0) {
return result;
}
if (const int result = require(material->id() == 700); result != 0) {
return result;
}
const fesa::property::ShellProperty* property = domain.findShellProperty(500);
if (const int result = require(property != nullptr); result != 0) {
return result;
}
if (const int result = require(property->materialId() == 700); result != 0) {
return result;
}
if (const int result = require(property->thickness() == 0.01); result != 0) {
return result;
}
if (const int result = require(domain.material(700).id() == 700); result != 0) {
return result;
}
return require(domain.shellProperty(500).id() == 500);
}
int duplicate_material_and_property_ids_throw() {
fesa::core::Domain domain;
add_material_and_property(domain);
if (const int result = require_throws<std::invalid_argument>([&domain]() {
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 100.0, 0.25));
});
result != 0) {
return result;
}
return require_throws<std::invalid_argument>([&domain]() {
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.02});
});
}
int shell_property_referencing_missing_material_throws() {
fesa::core::Domain domain;
return require_throws<std::invalid_argument>([&domain]() {
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.01});
});
}
int add_and_retrieve_sets() {
fesa::core::Domain domain;
add_four_nodes(domain);
add_material_and_property(domain);
add_element(domain);
domain.addNodeSet("left-edge", {1, 4});
domain.addElementSet("shells", {100});
const auto* node_set = domain.findNodeSet("left-edge");
if (const int result = require(node_set != nullptr); result != 0) {
return result;
}
if (const int result = require(node_set->size() == 2); result != 0) {
return result;
}
if (const int result = require((*node_set)[0] == 1); result != 0) {
return result;
}
if (const int result = require((*node_set)[1] == 4); result != 0) {
return result;
}
const auto* element_set = domain.findElementSet("shells");
if (const int result = require(element_set != nullptr); result != 0) {
return result;
}
if (const int result = require(element_set->size() == 1); result != 0) {
return result;
}
if (const int result = require((*element_set)[0] == 100); result != 0) {
return result;
}
if (const int result = require(domain.nodeSetCount() == 1); result != 0) {
return result;
}
return require(domain.elementSetCount() == 1);
}
int duplicate_set_names_throw() {
fesa::core::Domain domain;
add_four_nodes(domain);
add_material_and_property(domain);
add_element(domain);
domain.addNodeSet("left-edge", {1, 4});
domain.addElementSet("shells", {100});
if (const int result = require_throws<std::invalid_argument>([&domain]() {
domain.addNodeSet("left-edge", {1});
});
result != 0) {
return result;
}
return require_throws<std::invalid_argument>([&domain]() {
domain.addElementSet("shells", {100});
});
}
int sets_referencing_missing_ids_throw() {
fesa::core::Domain domain;
add_four_nodes(domain);
add_material_and_property(domain);
add_element(domain);
if (const int result = require_throws<std::invalid_argument>([&domain]() {
domain.addNodeSet("bad-nodes", {1, 99});
});
result != 0) {
return result;
}
return require_throws<std::invalid_argument>([&domain]() {
domain.addElementSet("bad-elements", {100, 404});
});
}
int add_and_retrieve_boundary_condition() {
fesa::core::Domain domain;
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
const std::size_t index = domain.addBoundaryCondition(
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
if (const int result = require(index == 0); result != 0) {
return result;
}
if (const int result = require(domain.boundaryConditionCount() == 1); result != 0) {
return result;
}
const fesa::boundary::BoundaryCondition& condition = domain.boundaryCondition(index);
if (const int result = require(condition.kind() == fesa::boundary::BoundaryConditionKind::SinglePointConstraint);
result != 0) {
return result;
}
const auto& spc = static_cast<const fesa::boundary::SinglePointConstraint&>(condition);
if (const int result = require(spc.nodeId() == 1); result != 0) {
return result;
}
if (const int result = require(spc.dof() == fesa::core::Dof::U1); result != 0) {
return result;
}
return require(spc.value() == 0.0);
}
int add_and_retrieve_nodal_load() {
fesa::core::Domain domain;
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
const std::size_t index = domain.addLoad(
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
if (const int result = require(index == 0); result != 0) {
return result;
}
if (const int result = require(domain.loadCount() == 1); result != 0) {
return result;
}
const fesa::load::Load& load = domain.load(index);
if (const int result = require(load.kind() == fesa::load::LoadKind::Nodal); result != 0) {
return result;
}
const auto& nodal_load = static_cast<const fesa::load::NodalLoad&>(load);
if (const int result = require(nodal_load.nodeId() == 1); result != 0) {
return result;
}
if (const int result = require(nodal_load.dof() == fesa::core::Dof::U3); result != 0) {
return result;
}
return require(nodal_load.value() == -100.0);
}
int missing_node_boundary_condition_and_load_throw() {
fesa::core::Domain domain;
if (const int result = require_throws<std::invalid_argument>([&domain]() {
(void)domain.addBoundaryCondition(
std::make_unique<fesa::boundary::SinglePointConstraint>(99, fesa::core::Dof::U1, 0.0));
});
result != 0) {
return result;
}
return require_throws<std::invalid_argument>([&domain]() {
(void)domain.addLoad(
std::make_unique<fesa::load::NodalLoad>(99, fesa::core::Dof::U3, -100.0));
});
}
int duplicate_load_and_boundary_keys_throw() {
fesa::core::Domain domain;
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
domain.addBoundaryCondition(
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
domain.addLoad(std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
if (const int result = require_throws<std::invalid_argument>([&domain]() {
(void)domain.addBoundaryCondition(
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 1.0));
});
result != 0) {
return result;
}
return require_throws<std::invalid_argument>([&domain]() {
(void)domain.addLoad(
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -200.0));
});
}
int add_and_retrieve_linear_static_step() {
fesa::core::Domain domain;
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
const std::size_t bc = domain.addBoundaryCondition(
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
const std::size_t load = domain.addLoad(
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}});
if (const int result = require(domain.stepCount() == 1); result != 0) {
return result;
}
const fesa::core::LinearStaticStepDefinition* found = domain.findStep(1);
if (const int result = require(found != nullptr); result != 0) {
return result;
}
if (const int result = require(found->id() == 1); result != 0) {
return result;
}
if (const int result = require(found->name() == "load-step"); result != 0) {
return result;
}
if (const int result = require(found->boundaryConditionIndices().size() == 1); result != 0) {
return result;
}
if (const int result = require(found->boundaryConditionIndices()[0] == bc); result != 0) {
return result;
}
if (const int result = require(found->loadIndices().size() == 1); result != 0) {
return result;
}
return require(found->loadIndices()[0] == load);
}
int duplicate_and_invalid_step_references_throw() {
fesa::core::Domain domain;
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
const std::size_t bc = domain.addBoundaryCondition(
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
const std::size_t load = domain.addLoad(
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}});
if (const int result = require_throws<std::invalid_argument>([&domain, bc, load]() {
domain.addStep(fesa::core::LinearStaticStepDefinition{1, "duplicate", {bc}, {load}});
});
result != 0) {
return result;
}
if (const int result = require_throws<std::invalid_argument>([&domain, load]() {
domain.addStep(fesa::core::LinearStaticStepDefinition{2, "bad-bc", {99}, {load}});
});
result != 0) {
return result;
}
return require_throws<std::invalid_argument>([&domain, bc]() {
domain.addStep(fesa::core::LinearStaticStepDefinition{2, "bad-load", {bc}, {99}});
});
}
int const_domain_retrieval_returns_const_runtime_model_data() {
fesa::core::Domain domain;
add_four_nodes(domain);
add_material_and_property(domain);
add_element(domain);
domain.addNodeSet("left-edge", {1, 4});
domain.addElementSet("shells", {100});
const std::size_t bc = domain.addBoundaryCondition(
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
const std::size_t load = domain.addLoad(
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}});
const fesa::core::Domain& const_domain = domain;
if (const int result = require((std::is_same<decltype(const_domain.node(1)), const fesa::core::Node&>::value));
result != 0) {
return result;
}
if (const int result = require((std::is_same<decltype(const_domain.element(100)), const fesa::element::Element&>::value));
result != 0) {
return result;
}
if (const int result = require((std::is_same<decltype(const_domain.material(700)), const fesa::material::Material&>::value));
result != 0) {
return result;
}
if (const int result = require((std::is_same<decltype(const_domain.shellProperty(500)), const fesa::property::ShellProperty&>::value));
result != 0) {
return result;
}
if (const int result = require((std::is_same<decltype(const_domain.nodeSet("left-edge")), const std::vector<fesa::core::NodeId>&>::value));
result != 0) {
return result;
}
if (const int result = require((std::is_same<decltype(const_domain.elementSet("shells")), const std::vector<fesa::core::ElementId>&>::value));
result != 0) {
return result;
}
if (const int result = require((std::is_same<decltype(const_domain.boundaryCondition(0)), const fesa::boundary::BoundaryCondition&>::value));
result != 0) {
return result;
}
if (const int result = require((std::is_same<decltype(const_domain.load(0)), const fesa::load::Load&>::value));
result != 0) {
return result;
}
return require((std::is_same<decltype(const_domain.step(1)), const fesa::core::LinearStaticStepDefinition&>::value));
}
int failed_inserts_do_not_mutate_counts() {
fesa::core::Domain domain;
add_four_nodes(domain);
add_material_and_property(domain);
add_element(domain);
domain.addNodeSet("left-edge", {1, 4});
domain.addElementSet("shells", {100});
const std::size_t bc = domain.addBoundaryCondition(
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
const std::size_t load = domain.addLoad(
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}});
if (const int result = require_throws<std::invalid_argument>([&domain]() {
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
101,
std::array<fesa::core::NodeId, 4>{1, 2, 3, 99},
500));
});
result != 0) {
return result;
}
if (const int result = require(domain.elementCount() == 1); result != 0) {
return result;
}
if (const int result = require_throws<std::invalid_argument>([&domain]() {
domain.addShellProperty(fesa::property::ShellProperty{501, 404, 0.01});
});
result != 0) {
return result;
}
if (const int result = require(domain.shellPropertyCount() == 1); result != 0) {
return result;
}
if (const int result = require_throws<std::invalid_argument>([&domain]() {
domain.addNodeSet("bad-nodes", {1, 99});
});
result != 0) {
return result;
}
if (const int result = require(domain.nodeSetCount() == 1); result != 0) {
return result;
}
if (const int result = require_throws<std::invalid_argument>([&domain]() {
(void)domain.addBoundaryCondition(
std::make_unique<fesa::boundary::SinglePointConstraint>(99, fesa::core::Dof::U1, 0.0));
});
result != 0) {
return result;
}
if (const int result = require(domain.boundaryConditionCount() == 1); result != 0) {
return result;
}
if (const int result = require_throws<std::invalid_argument>([&domain, bc]() {
domain.addStep(fesa::core::LinearStaticStepDefinition{2, "bad-load", {bc}, {99}});
});
result != 0) {
return result;
}
return require(domain.stepCount() == 1);
}
} // namespace
int run_domain_storage_tests() {
if (const int result = add_and_retrieve_node_by_id(); result != 0) {
return result;
}
if (const int result = missing_node_lookup_contracts(); result != 0) {
return result;
}
if (const int result = duplicate_node_id_throws(); result != 0) {
return result;
}
if (const int result = add_and_retrieve_element_by_id(); result != 0) {
return result;
}
if (const int result = duplicate_element_id_throws(); result != 0) {
return result;
}
if (const int result = element_referencing_missing_node_throws(); result != 0) {
return result;
}
if (const int result = element_referencing_missing_property_throws(); result != 0) {
return result;
}
if (const int result = missing_element_lookup_contracts(); result != 0) {
return result;
}
if (const int result = add_and_retrieve_material_and_property(); result != 0) {
return result;
}
if (const int result = duplicate_material_and_property_ids_throw(); result != 0) {
return result;
}
if (const int result = shell_property_referencing_missing_material_throws(); result != 0) {
return result;
}
if (const int result = add_and_retrieve_sets(); result != 0) {
return result;
}
if (const int result = duplicate_set_names_throw(); result != 0) {
return result;
}
if (const int result = sets_referencing_missing_ids_throw(); result != 0) {
return result;
}
if (const int result = add_and_retrieve_boundary_condition(); result != 0) {
return result;
}
if (const int result = add_and_retrieve_nodal_load(); result != 0) {
return result;
}
if (const int result = missing_node_boundary_condition_and_load_throw(); result != 0) {
return result;
}
if (const int result = duplicate_load_and_boundary_keys_throw(); result != 0) {
return result;
}
if (const int result = add_and_retrieve_linear_static_step(); result != 0) {
return result;
}
if (const int result = duplicate_and_invalid_step_references_throw(); result != 0) {
return result;
}
if (const int result = const_domain_retrieval_returns_const_runtime_model_data(); result != 0) {
return result;
}
if (const int result = failed_inserts_do_not_mutate_counts(); result != 0) {
return result;
}
return 0;
}
+41
View File
@@ -0,0 +1,41 @@
#include "fesa/core/ElementDefinition.hpp"
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_element_definition_tests() {
const fesa::core::ElementDefinition element{
100,
fesa::core::ElementType::Mitc4,
{1, 2, 3, 4},
500};
if (const int result = require(element.id() == 100); result != 0) {
return result;
}
if (const int result = require(element.type() == fesa::core::ElementType::Mitc4); result != 0) {
return result;
}
if (const int result = require(element.connectivity()[0] == 1); result != 0) {
return result;
}
if (const int result = require(element.connectivity()[1] == 2); result != 0) {
return result;
}
if (const int result = require(element.connectivity()[2] == 3); result != 0) {
return result;
}
if (const int result = require(element.connectivity()[3] == 4); result != 0) {
return result;
}
if (const int result = require(element.propertyId() == 500); result != 0) {
return result;
}
return 0;
}
+25
View File
@@ -0,0 +1,25 @@
#include "fesa/core/LoadDefinition.hpp"
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_load_definition_tests() {
const fesa::core::NodalLoadDefinition load{1, fesa::core::Dof::U3, -100.0};
if (const int result = require(load.nodeId() == 1); result != 0) {
return result;
}
if (const int result = require(load.dof() == fesa::core::Dof::U3); result != 0) {
return result;
}
if (const int result = require(load.value() == -100.0); result != 0) {
return result;
}
return 0;
}
+25
View File
@@ -0,0 +1,25 @@
#include "fesa/core/MaterialDefinition.hpp"
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_material_definition_tests() {
const fesa::core::LinearElasticMaterialDefinition material{700, 210.0, 0.3};
if (const int result = require(material.id() == 700); result != 0) {
return result;
}
if (const int result = require(material.youngModulus() == 210.0); result != 0) {
return result;
}
if (const int result = require(material.poissonRatio() == 0.3); result != 0) {
return result;
}
return 0;
}
+49
View File
@@ -0,0 +1,49 @@
#include "fesa/core/ModelTypes.hpp"
#include <cstdint>
#include <type_traits>
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_model_types_tests() {
using namespace fesa::core;
if (const int result = require(sizeof(Id) == 8); result != 0) {
return result;
}
if (const int result = require(std::is_same<Id, std::int64_t>::value); result != 0) {
return result;
}
if (const int result = require(kDofPerNode == 6); result != 0) {
return result;
}
if (const int result = require(ElementType::Mitc4 == ElementType::Mitc4); result != 0) {
return result;
}
if (const int result = require(static_cast<int>(Dof::U1) == 0); result != 0) {
return result;
}
if (const int result = require(static_cast<int>(Dof::U2) == 1); result != 0) {
return result;
}
if (const int result = require(static_cast<int>(Dof::U3) == 2); result != 0) {
return result;
}
if (const int result = require(static_cast<int>(Dof::UR1) == 3); result != 0) {
return result;
}
if (const int result = require(static_cast<int>(Dof::UR2) == 4); result != 0) {
return result;
}
if (const int result = require(static_cast<int>(Dof::UR3) == 5); result != 0) {
return result;
}
return 0;
}
+47
View File
@@ -0,0 +1,47 @@
#include "fesa/core/Node.hpp"
#include <array>
#include <type_traits>
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_node_tests() {
const fesa::core::Node node{42, 1.0, 2.0, 3.0};
if (const int result = require(fesa::core::Node::dofCount() == 6); result != 0) {
return result;
}
if (const int result = require(node.id() == 42); result != 0) {
return result;
}
if (const int result = require(node.x() == 1.0); result != 0) {
return result;
}
if (const int result = require(node.y() == 2.0); result != 0) {
return result;
}
if (const int result = require(node.z() == 3.0); result != 0) {
return result;
}
if (const int result = require(node.coordinates()[0] == 1.0); result != 0) {
return result;
}
if (const int result = require(node.coordinates()[1] == 2.0); result != 0) {
return result;
}
if (const int result = require(node.coordinates()[2] == 3.0); result != 0) {
return result;
}
if (const int result = require((std::is_same<decltype(node.coordinates()), const std::array<double, 3>&>::value));
result != 0) {
return result;
}
return 0;
}
+25
View File
@@ -0,0 +1,25 @@
#include "fesa/core/PropertyDefinition.hpp"
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_property_definition_tests() {
const fesa::core::ShellPropertyDefinition property{500, 700, 0.01};
if (const int result = require(property.id() == 500); result != 0) {
return result;
}
if (const int result = require(property.materialId() == 700); result != 0) {
return result;
}
if (const int result = require(property.thickness() == 0.01); result != 0) {
return result;
}
return 0;
}
+37
View File
@@ -0,0 +1,37 @@
#include "fesa/core/StepDefinition.hpp"
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_step_definition_tests() {
const fesa::core::LinearStaticStepDefinition step{1, "load-step", {0, 2}, {1}};
if (const int result = require(step.id() == 1); result != 0) {
return result;
}
if (const int result = require(step.name() == "load-step"); result != 0) {
return result;
}
if (const int result = require(step.boundaryConditionIndices().size() == 2); result != 0) {
return result;
}
if (const int result = require(step.boundaryConditionIndices()[0] == 0); result != 0) {
return result;
}
if (const int result = require(step.boundaryConditionIndices()[1] == 2); result != 0) {
return result;
}
if (const int result = require(step.loadIndices().size() == 1); result != 0) {
return result;
}
if (const int result = require(step.loadIndices()[0] == 1); result != 0) {
return result;
}
return 0;
}
+51
View File
@@ -0,0 +1,51 @@
#include "fesa/element/Element.hpp"
#include <array>
#include <memory>
namespace {
using fesa::element::ElementId;
using fesa::element::ElementType;
using fesa::element::NodeId;
using fesa::element::PropertyId;
int require(bool condition) {
return condition ? 0 : 1;
}
class TestElement final : public fesa::element::Element {
public:
ElementId id() const noexcept override { return 100; }
ElementType type() const noexcept override { return ElementType::Mitc4; }
std::size_t nodeCount() const noexcept override { return connectivity_.size(); }
const std::array<NodeId, 4>& connectivity() const noexcept override { return connectivity_; }
PropertyId propertyId() const noexcept override { return 500; }
private:
std::array<NodeId, 4> connectivity_{1, 2, 3, 4};
};
} // namespace
int run_element_base_tests() {
std::unique_ptr<fesa::element::Element> owned = std::make_unique<TestElement>();
const fesa::element::Element& element = *owned;
if (const int result = require(element.id() == 100); result != 0) {
return result;
}
if (const int result = require(element.type() == fesa::core::ElementType::Mitc4); result != 0) {
return result;
}
if (const int result = require(element.nodeCount() == 4); result != 0) {
return result;
}
if (const int result = require(element.connectivity()[0] == 1); result != 0) {
return result;
}
if (const int result = require(element.connectivity()[3] == 4); result != 0) {
return result;
}
return require(element.propertyId() == 500);
}
@@ -0,0 +1,40 @@
#include "fesa/element/Mitc4Element.hpp"
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_mitc4_element_model_tests() {
const fesa::element::Mitc4Element mitc4{100, {1, 2, 3, 4}, 500};
const fesa::element::Element& element = mitc4;
if (const int result = require(element.id() == 100); result != 0) {
return result;
}
if (const int result = require(element.type() == fesa::core::ElementType::Mitc4); result != 0) {
return result;
}
if (const int result = require(element.nodeCount() == 4); result != 0) {
return result;
}
if (const int result = require(mitc4.dofCount() == 24); result != 0) {
return result;
}
if (const int result = require(element.connectivity()[0] == 1); result != 0) {
return result;
}
if (const int result = require(element.connectivity()[1] == 2); result != 0) {
return result;
}
if (const int result = require(element.connectivity()[2] == 3); result != 0) {
return result;
}
if (const int result = require(element.connectivity()[3] == 4); result != 0) {
return result;
}
return require(element.propertyId() == 500);
}
+30
View File
@@ -0,0 +1,30 @@
#include "fesa/load/Load.hpp"
#include "fesa/load/NodalLoad.hpp"
#include <memory>
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_load_base_tests() {
std::unique_ptr<fesa::load::Load> owned =
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0);
const fesa::load::Load& load = *owned;
const auto& nodal = static_cast<const fesa::load::NodalLoad&>(load);
if (const int result = require(load.kind() == fesa::load::LoadKind::Nodal); result != 0) {
return result;
}
if (const int result = require(nodal.nodeId() == 1); result != 0) {
return result;
}
if (const int result = require(nodal.dof() == fesa::core::Dof::U3); result != 0) {
return result;
}
return require(nodal.value() == -100.0);
}
+27
View File
@@ -0,0 +1,27 @@
#include "fesa/material/LinearElasticMaterial.hpp"
#include "fesa/material/Material.hpp"
#include <memory>
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
} // namespace
int run_material_base_tests() {
std::unique_ptr<fesa::material::Material> owned =
std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3);
const fesa::material::Material& material = *owned;
const auto& elastic = static_cast<const fesa::material::LinearElasticMaterial&>(material);
if (const int result = require(material.id() == 700); result != 0) {
return result;
}
if (const int result = require(elastic.youngModulus() == 210.0); result != 0) {
return result;
}
return require(elastic.poissonRatio() == 0.3);
}
+28
View File
@@ -0,0 +1,28 @@
int run_boundary_base_tests();
int run_element_base_tests();
int run_load_base_tests();
int run_material_base_tests();
int run_mitc4_element_model_tests();
int run_shell_property_tests();
int main() {
if (const int result = run_boundary_base_tests(); result != 0) {
return result;
}
if (const int result = run_element_base_tests(); result != 0) {
return result;
}
if (const int result = run_mitc4_element_model_tests(); result != 0) {
return result;
}
if (const int result = run_material_base_tests(); result != 0) {
return result;
}
if (const int result = run_shell_property_tests(); result != 0) {
return result;
}
if (const int result = run_load_base_tests(); result != 0) {
return result;
}
return 0;
}
+46
View File
@@ -0,0 +1,46 @@
#include "fesa/property/ShellProperty.hpp"
#include <stdexcept>
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
template <typename Exception, typename Function>
int require_throws(Function&& function) {
try {
function();
} catch (const Exception&) {
return 0;
} catch (...) {
return 1;
}
return 1;
}
} // namespace
int run_shell_property_tests() {
const fesa::property::ShellProperty property{500, 700, 0.01};
if (const int result = require(property.id() == 500); result != 0) {
return result;
}
if (const int result = require(property.materialId() == 700); result != 0) {
return result;
}
if (const int result = require(property.thickness() == 0.01); result != 0) {
return result;
}
if (const int result = require_throws<std::invalid_argument>([]() {
(void)fesa::property::ShellProperty{501, 700, 0.0};
});
result != 0) {
return result;
}
return require_throws<std::invalid_argument>([]() {
(void)fesa::property::ShellProperty{502, 700, -0.01};
});
}