diff --git a/docs/ADR.md b/docs/ADR.md index 3ed085e..f86e1e7 100644 --- a/docs/ADR.md +++ b/docs/ADR.md @@ -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. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 344090d..de0e180 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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 diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 69bfb0a..744cff9 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -34,6 +34,10 @@ - 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 upstream MITC4 stage: new solver feature requirements analysis for `mitc4-linear-static-shell`. @@ -51,6 +55,10 @@ - 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. diff --git a/docs/implementation-plans/domain-runtime-storage-implementation-plan.md b/docs/implementation-plans/domain-runtime-storage-implementation-plan.md new file mode 100644 index 0000000..a0870d8 --- /dev/null +++ b/docs/implementation-plans/domain-runtime-storage-implementation-plan.md @@ -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`; +- `std::unique_ptr`; +- `fesa::property::ShellProperty` values; +- `std::unique_ptr`; +- `std::unique_ptr`; +- 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 +``` diff --git a/include/fesa/core/Domain.hpp b/include/fesa/core/Domain.hpp index f9ebaf6..fb7ad1f 100644 --- a/include/fesa/core/Domain.hpp +++ b/include/fesa/core/Domain.hpp @@ -1,17 +1,13 @@ #pragma once #include "fesa/boundary/BoundaryCondition.hpp" -#include "fesa/core/BoundaryCondition.hpp" -#include "fesa/core/ElementDefinition.hpp" -#include "fesa/core/LoadDefinition.hpp" -#include "fesa/core/MaterialDefinition.hpp" #include "fesa/core/ModelTypes.hpp" #include "fesa/core/Node.hpp" -#include "fesa/core/PropertyDefinition.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 #include @@ -24,33 +20,29 @@ namespace fesa::core { class Domain { public: void addNode(Node node); - void addElement(ElementDefinition element); - void addMaterial(LinearElasticMaterialDefinition material); - void addShellProperty(ShellPropertyDefinition property); + void addElement(std::unique_ptr element); + void addMaterial(std::unique_ptr material); + void addShellProperty(fesa::property::ShellProperty property); void addNodeSet(std::string name, std::vector node_ids); void addElementSet(std::string name, std::vector element_ids); - std::size_t addBoundaryCondition(BoundaryCondition condition); - std::size_t addNodalLoad(NodalLoadDefinition load); + std::size_t addBoundaryCondition(std::unique_ptr boundary); + std::size_t addLoad(std::unique_ptr load); void addStep(LinearStaticStepDefinition step); - void addElementObject(std::unique_ptr element); - void addMaterialObject(std::unique_ptr material); - std::size_t addLoadObject(std::unique_ptr load); - std::size_t addBoundaryObject(std::unique_ptr boundary); const Node* findNode(NodeId id) const noexcept; const Node& node(NodeId id) const; std::size_t nodeCount() const noexcept; - const ElementDefinition* findElement(ElementId id) const noexcept; - const ElementDefinition& element(ElementId id) const; + const fesa::element::Element* findElement(ElementId id) const noexcept; + const fesa::element::Element& element(ElementId id) const; std::size_t elementCount() const noexcept; - const LinearElasticMaterialDefinition* findMaterial(MaterialId id) const noexcept; - const LinearElasticMaterialDefinition& material(MaterialId id) const; + const fesa::material::Material* findMaterial(MaterialId id) const noexcept; + const fesa::material::Material& material(MaterialId id) const; std::size_t materialCount() const noexcept; - const ShellPropertyDefinition* findShellProperty(PropertyId id) const noexcept; - const ShellPropertyDefinition& shellProperty(PropertyId id) const; + 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* findNodeSet(const std::string& name) const noexcept; @@ -61,46 +53,28 @@ public: const std::vector& elementSet(const std::string& name) const; std::size_t elementSetCount() const noexcept; - const BoundaryCondition& boundaryCondition(std::size_t index) const; + 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 NodalLoadDefinition& nodalLoad(std::size_t index) const; - std::size_t nodalLoadCount() 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; - const fesa::element::Element* findElementObject(ElementId id) const noexcept; - const fesa::element::Element& elementObject(ElementId id) const; - std::size_t elementObjectCount() const noexcept; - - const fesa::material::Material* findMaterialObject(MaterialId id) const noexcept; - const fesa::material::Material& materialObject(MaterialId id) const; - std::size_t materialObjectCount() const noexcept; - - const fesa::load::Load* findLoadObject(std::size_t index) const noexcept; - const fesa::load::Load& loadObject(std::size_t index) const; - std::size_t loadObjectCount() const noexcept; - - const fesa::boundary::BoundaryCondition* findBoundaryObject(std::size_t index) const noexcept; - const fesa::boundary::BoundaryCondition& boundaryObject(std::size_t index) const; - std::size_t boundaryObjectCount() const noexcept; - private: std::unordered_map nodes_; - std::unordered_map elements_; - std::unordered_map materials_; - std::unordered_map shell_properties_; + std::unordered_map> elements_; + std::unordered_map> materials_; + std::unordered_map shell_properties_; std::unordered_map> node_sets_; std::unordered_map> element_sets_; - std::vector boundary_conditions_; - std::vector nodal_loads_; + std::vector> boundary_conditions_; + std::vector> loads_; std::unordered_map steps_; - std::unordered_map> element_objects_; - std::unordered_map> material_objects_; - std::vector> load_objects_; - std::vector> boundary_objects_; }; } // namespace fesa::core diff --git a/include/fesa/core/ElementDefinition.hpp b/include/fesa/core/ElementDefinition.hpp index c2cacfb..3ec2498 100644 --- a/include/fesa/core/ElementDefinition.hpp +++ b/include/fesa/core/ElementDefinition.hpp @@ -6,10 +6,6 @@ namespace fesa::core { -enum class ElementType { - Mitc4 -}; - class ElementDefinition { public: ElementDefinition( diff --git a/include/fesa/core/ModelTypes.hpp b/include/fesa/core/ModelTypes.hpp index 18d4ffb..cb5b5de 100644 --- a/include/fesa/core/ModelTypes.hpp +++ b/include/fesa/core/ModelTypes.hpp @@ -21,6 +21,10 @@ enum class Dof : std::uint8_t { UR3 = 5 }; +enum class ElementType { + Mitc4 +}; + constexpr std::size_t kDofPerNode = 6; } // namespace fesa::core diff --git a/include/fesa/element/Element.hpp b/include/fesa/element/Element.hpp index 1cc3cd8..0a41f0a 100644 --- a/include/fesa/element/Element.hpp +++ b/include/fesa/element/Element.hpp @@ -1,6 +1,6 @@ #pragma once -#include "fesa/core/ElementDefinition.hpp" +#include "fesa/core/ModelTypes.hpp" #include #include diff --git a/phases/domain-runtime-storage/index.json b/phases/domain-runtime-storage/index.json new file mode 100644 index 0000000..39e7ba4 --- /dev/null +++ b/phases/domain-runtime-storage/index.json @@ -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." + } + ] +} diff --git a/phases/domain-runtime-storage/step0.md b/phases/domain-runtime-storage/step0.md new file mode 100644 index 0000000..72dc911 --- /dev/null +++ b/phases/domain-runtime-storage/step0.md @@ -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. diff --git a/phases/domain-runtime-storage/step1.md b/phases/domain-runtime-storage/step1.md new file mode 100644 index 0000000..dace616 --- /dev/null +++ b/phases/domain-runtime-storage/step1.md @@ -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 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 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. diff --git a/phases/domain-runtime-storage/step2.md b/phases/domain-runtime-storage/step2.md new file mode 100644 index 0000000..054149c --- /dev/null +++ b/phases/domain-runtime-storage/step2.md @@ -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 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 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. diff --git a/phases/domain-runtime-storage/step3.md b/phases/domain-runtime-storage/step3.md new file mode 100644 index 0000000..43ac1b5 --- /dev/null +++ b/phases/domain-runtime-storage/step3.md @@ -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. diff --git a/phases/domain-runtime-storage/step4.md b/phases/domain-runtime-storage/step4.md new file mode 100644 index 0000000..e779ac6 --- /dev/null +++ b/phases/domain-runtime-storage/step4.md @@ -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. diff --git a/phases/domain-runtime-storage/step5.md b/phases/domain-runtime-storage/step5.md new file mode 100644 index 0000000..73ad6f1 --- /dev/null +++ b/phases/domain-runtime-storage/step5.md @@ -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. diff --git a/phases/index.json b/phases/index.json index 9b3404a..3165eff 100644 --- a/phases/index.json +++ b/phases/index.json @@ -7,6 +7,10 @@ { "dir": "analysis-model-objects", "status": "completed" + }, + { + "dir": "domain-runtime-storage", + "status": "completed" } ] } diff --git a/src/core/Domain.cpp b/src/core/Domain.cpp index 7a9a986..f5ab39d 100644 --- a/src/core/Domain.cpp +++ b/src/core/Domain.cpp @@ -1,6 +1,11 @@ #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 @@ -154,28 +159,37 @@ void Domain::addNode(Node node) { } } -void Domain::addElement(ElementDefinition element) { - const ElementId id = element.id(); +void Domain::addElement(std::unique_ptr 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()) { + 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(LinearElasticMaterialDefinition material) { - const MaterialId id = material.id(); +void Domain::addMaterial(std::unique_ptr 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(ShellPropertyDefinition property) { +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"); @@ -210,21 +224,48 @@ void Domain::addElementSet(std::string name, std::vector element_ids) element_sets_.emplace(std::move(name), std::move(element_ids)); } -std::size_t Domain::addBoundaryCondition(BoundaryCondition condition) { - if (findNode(condition.nodeId()) == nullptr) { - throw std::invalid_argument("boundary condition references missing node id"); +std::size_t Domain::addBoundaryCondition(std::unique_ptr boundary) { + if (!boundary) { + throw std::invalid_argument("boundary condition is null"); + } + if (const auto* spc = dynamic_cast(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(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(condition); + boundary_conditions_.push_back(std::move(boundary)); return index; } -std::size_t Domain::addNodalLoad(NodalLoadDefinition load) { - if (findNode(load.nodeId()) == nullptr) { - throw std::invalid_argument("nodal load references missing node id"); +std::size_t Domain::addLoad(std::unique_ptr load) { + if (!load) { + throw std::invalid_argument("load is null"); } - const std::size_t index = nodal_loads_.size(); - nodal_loads_.push_back(load); + if (const auto* nodal = dynamic_cast(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(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; } @@ -239,79 +280,13 @@ void Domain::addStep(LinearStaticStepDefinition step) { } } for (const std::size_t index : step.loadIndices()) { - if (index >= nodal_loads_.size()) { - throw std::invalid_argument("step references missing nodal load"); + if (index >= loads_.size()) { + throw std::invalid_argument("step references missing load"); } } steps_.emplace(id, std::move(step)); } -void Domain::addElementObject(std::unique_ptr element) { - if (!element) { - throw std::invalid_argument("element object is null"); - } - const ElementId id = element->id(); - if (element_objects_.find(id) != element_objects_.end()) { - throw std::invalid_argument("duplicate element object id"); - } - for (const NodeId node_id : element->connectivity()) { - if (findNode(node_id) == nullptr) { - throw std::invalid_argument("element object references missing node id"); - } - } - element_objects_.emplace(id, std::move(element)); -} - -void Domain::addMaterialObject(std::unique_ptr material) { - if (!material) { - throw std::invalid_argument("material object is null"); - } - const MaterialId id = material->id(); - const auto inserted = material_objects_.emplace(id, std::move(material)); - if (!inserted.second) { - throw std::invalid_argument("duplicate material object id"); - } -} - -std::size_t Domain::addLoadObject(std::unique_ptr load) { - if (!load) { - throw std::invalid_argument("load object is null"); - } - if (const auto* nodal = dynamic_cast(load.get())) { - for (const auto& existing : load_objects_) { - const auto* existing_nodal = dynamic_cast(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 = load_objects_.size(); - load_objects_.push_back(std::move(load)); - return index; -} - -std::size_t Domain::addBoundaryObject(std::unique_ptr boundary) { - if (!boundary) { - throw std::invalid_argument("boundary object is null"); - } - if (const auto* spc = dynamic_cast(boundary.get())) { - for (const auto& existing : boundary_objects_) { - const auto* existing_spc = - dynamic_cast(existing.get()); - if (existing_spc != nullptr && - existing_spc->nodeId() == spc->nodeId() && - existing_spc->dof() == spc->dof()) { - throw std::invalid_argument("duplicate boundary object key"); - } - } - } - const std::size_t index = boundary_objects_.size(); - boundary_objects_.push_back(std::move(boundary)); - return index; -} - const Node* Domain::findNode(NodeId id) const noexcept { const auto it = nodes_.find(id); return it == nodes_.end() ? nullptr : &it->second; @@ -329,13 +304,13 @@ std::size_t Domain::nodeCount() const noexcept { return nodes_.size(); } -const ElementDefinition* Domain::findElement(ElementId id) const noexcept { +const fesa::element::Element* Domain::findElement(ElementId id) const noexcept { const auto it = elements_.find(id); - return it == elements_.end() ? nullptr : &it->second; + return it == elements_.end() ? nullptr : it->second.get(); } -const ElementDefinition& Domain::element(ElementId id) const { - const ElementDefinition* found = findElement(id); +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"); } @@ -346,13 +321,13 @@ std::size_t Domain::elementCount() const noexcept { return elements_.size(); } -const LinearElasticMaterialDefinition* Domain::findMaterial(MaterialId id) const noexcept { +const fesa::material::Material* Domain::findMaterial(MaterialId id) const noexcept { const auto it = materials_.find(id); - return it == materials_.end() ? nullptr : &it->second; + return it == materials_.end() ? nullptr : it->second.get(); } -const LinearElasticMaterialDefinition& Domain::material(MaterialId id) const { - const LinearElasticMaterialDefinition* found = findMaterial(id); +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"); } @@ -363,13 +338,13 @@ std::size_t Domain::materialCount() const noexcept { return materials_.size(); } -const ShellPropertyDefinition* Domain::findShellProperty(PropertyId id) const noexcept { +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 ShellPropertyDefinition& Domain::shellProperty(PropertyId id) const { - const ShellPropertyDefinition* found = findShellProperty(id); +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"); } @@ -414,20 +389,36 @@ std::size_t Domain::elementSetCount() const noexcept { return element_sets_.size(); } -const BoundaryCondition& Domain::boundaryCondition(std::size_t index) const { - return boundary_conditions_.at(index); +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 NodalLoadDefinition& Domain::nodalLoad(std::size_t index) const { - return nodal_loads_.at(index); +const fesa::load::Load* Domain::findLoad(std::size_t index) const noexcept { + return index < loads_.size() ? loads_[index].get() : nullptr; } -std::size_t Domain::nodalLoadCount() const noexcept { - return nodal_loads_.size(); +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 { @@ -447,70 +438,4 @@ std::size_t Domain::stepCount() const noexcept { return steps_.size(); } -const fesa::element::Element* Domain::findElementObject(ElementId id) const noexcept { - const auto it = element_objects_.find(id); - return it == element_objects_.end() ? nullptr : it->second.get(); -} - -const fesa::element::Element& Domain::elementObject(ElementId id) const { - const fesa::element::Element* found = findElementObject(id); - if (found == nullptr) { - throw std::out_of_range("element object id not found"); - } - return *found; -} - -std::size_t Domain::elementObjectCount() const noexcept { - return element_objects_.size(); -} - -const fesa::material::Material* Domain::findMaterialObject(MaterialId id) const noexcept { - const auto it = material_objects_.find(id); - return it == material_objects_.end() ? nullptr : it->second.get(); -} - -const fesa::material::Material& Domain::materialObject(MaterialId id) const { - const fesa::material::Material* found = findMaterialObject(id); - if (found == nullptr) { - throw std::out_of_range("material object id not found"); - } - return *found; -} - -std::size_t Domain::materialObjectCount() const noexcept { - return material_objects_.size(); -} - -const fesa::load::Load* Domain::findLoadObject(std::size_t index) const noexcept { - return index < load_objects_.size() ? load_objects_[index].get() : nullptr; -} - -const fesa::load::Load& Domain::loadObject(std::size_t index) const { - const fesa::load::Load* found = findLoadObject(index); - if (found == nullptr) { - throw std::out_of_range("load object index not found"); - } - return *found; -} - -std::size_t Domain::loadObjectCount() const noexcept { - return load_objects_.size(); -} - -const fesa::boundary::BoundaryCondition* Domain::findBoundaryObject(std::size_t index) const noexcept { - return index < boundary_objects_.size() ? boundary_objects_[index].get() : nullptr; -} - -const fesa::boundary::BoundaryCondition& Domain::boundaryObject(std::size_t index) const { - const fesa::boundary::BoundaryCondition* found = findBoundaryObject(index); - if (found == nullptr) { - throw std::out_of_range("boundary object index not found"); - } - return *found; -} - -std::size_t Domain::boundaryObjectCount() const noexcept { - return boundary_objects_.size(); -} - } // namespace fesa::core diff --git a/tests/core/domain_model_object_test.cpp b/tests/core/domain_model_object_test.cpp index 4c0d92a..6482545 100644 --- a/tests/core/domain_model_object_test.cpp +++ b/tests/core/domain_model_object_test.cpp @@ -3,6 +3,7 @@ #include "fesa/element/Mitc4Element.hpp" #include "fesa/load/NodalLoad.hpp" #include "fesa/material/LinearElasticMaterial.hpp" +#include "fesa/property/ShellProperty.hpp" #include #include @@ -31,46 +32,46 @@ fesa::core::Domain populated_domain() { 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(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.addElementObject(std::make_unique( + domain.addElement(std::make_unique( 100, std::array{1, 2, 3, 4}, 500)); - domain.addMaterialObject(std::make_unique(700, 210.0, 0.3)); - const fesa::element::Element* element = domain.findElementObject(100); + 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.elementObject(100).propertyId() == 500); result != 0) { + if (const int result = require(domain.element(100).propertyId() == 500); result != 0) { return result; } - const fesa::material::Material* material = domain.findMaterialObject(700); + const fesa::material::Material* material = domain.findMaterial(700); if (const int result = require(material != nullptr); result != 0) { return result; } - return require(domain.materialObject(700).id() == 700); + return require(domain.material(700).id() == 700); } int duplicate_element_and_material_object_ids_throw() { fesa::core::Domain domain = populated_domain(); - domain.addElementObject(std::make_unique( + domain.addElement(std::make_unique( 100, std::array{1, 2, 3, 4}, 500)); - domain.addMaterialObject(std::make_unique(700, 210.0, 0.3)); if (const int result = require_throws([&domain]() { - domain.addElementObject(std::make_unique( + domain.addElement(std::make_unique( 100, std::array{1, 2, 3, 4}, 500)); @@ -79,36 +80,36 @@ int duplicate_element_and_material_object_ids_throw() { return result; } return require_throws([&domain]() { - domain.addMaterialObject(std::make_unique(700, 100.0, 0.25)); + domain.addMaterial(std::make_unique(700, 100.0, 0.25)); }); } int missing_model_object_direct_lookup_throws() { const fesa::core::Domain domain; - if (const int result = require(domain.findElementObject(404) == nullptr); result != 0) { + if (const int result = require(domain.findElement(404) == nullptr); result != 0) { return result; } - if (const int result = require(domain.findMaterialObject(404) == nullptr); result != 0) { + if (const int result = require(domain.findMaterial(404) == nullptr); result != 0) { return result; } if (const int result = require_throws([&domain]() { - (void)domain.elementObject(404); + (void)domain.element(404); }); result != 0) { return result; } return require_throws([&domain]() { - (void)domain.materialObject(404); + (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.addLoadObject( + const std::size_t load_index = domain.addLoad( std::make_unique(1, fesa::core::Dof::U3, -100.0)); - const std::size_t boundary_index = domain.addBoundaryObject( + const std::size_t boundary_index = domain.addBoundaryCondition( std::make_unique(1, fesa::core::Dof::U1, 0.0)); if (const int result = require(load_index == 0); result != 0) { @@ -117,33 +118,33 @@ int domain_owns_load_and_boundary_objects_by_index() { if (const int result = require(boundary_index == 0); result != 0) { return result; } - if (const int result = require(domain.findLoadObject(load_index) != nullptr); result != 0) { + if (const int result = require(domain.findLoad(load_index) != nullptr); result != 0) { return result; } - if (const int result = require(domain.findBoundaryObject(boundary_index) != nullptr); result != 0) { + if (const int result = require(domain.findBoundaryCondition(boundary_index) != nullptr); result != 0) { return result; } - if (const int result = require(domain.loadObject(load_index).kind() == fesa::load::LoadKind::Nodal); result != 0) { + if (const int result = require(domain.load(load_index).kind() == fesa::load::LoadKind::Nodal); result != 0) { return result; } return require( - domain.boundaryObject(boundary_index).kind() == + domain.boundaryCondition(boundary_index).kind() == fesa::boundary::BoundaryConditionKind::SinglePointConstraint); } int duplicate_load_and_boundary_keys_throw() { fesa::core::Domain domain = populated_domain(); - domain.addLoadObject(std::make_unique(1, fesa::core::Dof::U3, -100.0)); - domain.addBoundaryObject(std::make_unique(1, fesa::core::Dof::U1, 0.0)); + domain.addLoad(std::make_unique(1, fesa::core::Dof::U3, -100.0)); + domain.addBoundaryCondition(std::make_unique(1, fesa::core::Dof::U1, 0.0)); if (const int result = require_throws([&domain]() { - domain.addLoadObject(std::make_unique(1, fesa::core::Dof::U3, -200.0)); + domain.addLoad(std::make_unique(1, fesa::core::Dof::U3, -200.0)); }); result != 0) { return result; } return require_throws([&domain]() { - domain.addBoundaryObject(std::make_unique(1, fesa::core::Dof::U1, 1.0)); + domain.addBoundaryCondition(std::make_unique(1, fesa::core::Dof::U1, 1.0)); }); } diff --git a/tests/core/domain_storage_test.cpp b/tests/core/domain_storage_test.cpp index 18a0d57..e11956a 100644 --- a/tests/core/domain_storage_test.cpp +++ b/tests/core/domain_storage_test.cpp @@ -1,12 +1,14 @@ -#include "fesa/core/BoundaryCondition.hpp" +#include "fesa/boundary/SinglePointConstraint.hpp" #include "fesa/core/Domain.hpp" -#include "fesa/core/ElementDefinition.hpp" -#include "fesa/core/LoadDefinition.hpp" -#include "fesa/core/MaterialDefinition.hpp" #include "fesa/core/Node.hpp" -#include "fesa/core/PropertyDefinition.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 +#include #include #include @@ -28,6 +30,25 @@ int require_throws(Function&& function) { 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(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( + 100, + std::array{1, 2, 3, 4}, + 500)); +} + int add_and_retrieve_node_by_id() { fesa::core::Domain domain; @@ -55,11 +76,7 @@ int add_and_retrieve_node_by_id() { } const fesa::core::Node& direct = domain.node(10); - if (const int result = require(direct.id() == 10); result != 0) { - return result; - } - - return 0; + return require(direct.id() == 10); } int missing_node_lookup_contracts() { @@ -83,28 +100,18 @@ int duplicate_node_id_throws() { }); } -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}); -} - int add_and_retrieve_element_by_id() { fesa::core::Domain domain; add_four_nodes(domain); + add_material_and_property(domain); - domain.addElement(fesa::core::ElementDefinition{ - 100, - fesa::core::ElementType::Mitc4, - {1, 2, 3, 4}, - 500}); + add_element(domain); if (const int result = require(domain.elementCount() == 1); result != 0) { return result; } - const fesa::core::ElementDefinition* found = domain.findElement(100); + const fesa::element::Element* found = domain.findElement(100); if (const int result = require(found != nullptr); result != 0) { return result; } @@ -130,25 +137,21 @@ int add_and_retrieve_element_by_id() { return result; } - const fesa::core::ElementDefinition& direct = domain.element(100); + 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); - domain.addElement(fesa::core::ElementDefinition{ - 100, - fesa::core::ElementType::Mitc4, - {1, 2, 3, 4}, - 500}); + add_material_and_property(domain); + add_element(domain); return require_throws([&domain]() { - domain.addElement(fesa::core::ElementDefinition{ + domain.addElement(std::make_unique( 100, - fesa::core::ElementType::Mitc4, - {1, 2, 3, 4}, - 500}); + std::array{1, 2, 3, 4}, + 500)); }); } @@ -157,13 +160,26 @@ int element_referencing_missing_node_throws() { 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([&domain]() { - domain.addElement(fesa::core::ElementDefinition{ + domain.addElement(std::make_unique( 100, - fesa::core::ElementType::Mitc4, - {1, 2, 3, 4}, - 500}); + std::array{1, 2, 3, 4}, + 500)); + }); +} + +int element_referencing_missing_property_throws() { + fesa::core::Domain domain; + add_four_nodes(domain); + domain.addMaterial(std::make_unique(700, 210.0, 0.3)); + + return require_throws([&domain]() { + domain.addElement(std::make_unique( + 100, + std::array{1, 2, 3, 4}, + 500)); }); } @@ -179,11 +195,6 @@ int missing_element_lookup_contracts() { }); } -void add_material_and_property(fesa::core::Domain& domain) { - domain.addMaterial(fesa::core::LinearElasticMaterialDefinition{700, 210.0, 0.3}); - domain.addShellProperty(fesa::core::ShellPropertyDefinition{500, 700, 0.01}); -} - int add_and_retrieve_material_and_property() { fesa::core::Domain domain; @@ -196,18 +207,15 @@ int add_and_retrieve_material_and_property() { return result; } - const fesa::core::LinearElasticMaterialDefinition* material = domain.findMaterial(700); + 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->youngModulus() == 210.0); result != 0) { - return result; - } - if (const int result = require(material->poissonRatio() == 0.3); result != 0) { + if (const int result = require(material->id() == 700); result != 0) { return result; } - const fesa::core::ShellPropertyDefinition* property = domain.findShellProperty(500); + const fesa::property::ShellProperty* property = domain.findShellProperty(500); if (const int result = require(property != nullptr); result != 0) { return result; } @@ -229,14 +237,14 @@ int duplicate_material_and_property_ids_throw() { add_material_and_property(domain); if (const int result = require_throws([&domain]() { - domain.addMaterial(fesa::core::LinearElasticMaterialDefinition{700, 100.0, 0.25}); + domain.addMaterial(std::make_unique(700, 100.0, 0.25)); }); result != 0) { return result; } return require_throws([&domain]() { - domain.addShellProperty(fesa::core::ShellPropertyDefinition{500, 700, 0.02}); + domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.02}); }); } @@ -244,18 +252,15 @@ int shell_property_referencing_missing_material_throws() { fesa::core::Domain domain; return require_throws([&domain]() { - domain.addShellProperty(fesa::core::ShellPropertyDefinition{500, 700, 0.01}); + domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.01}); }); } int add_and_retrieve_sets() { fesa::core::Domain domain; add_four_nodes(domain); - domain.addElement(fesa::core::ElementDefinition{ - 100, - fesa::core::ElementType::Mitc4, - {1, 2, 3, 4}, - 500}); + add_material_and_property(domain); + add_element(domain); domain.addNodeSet("left-edge", {1, 4}); domain.addElementSet("shells", {100}); @@ -294,11 +299,8 @@ int add_and_retrieve_sets() { int duplicate_set_names_throw() { fesa::core::Domain domain; add_four_nodes(domain); - domain.addElement(fesa::core::ElementDefinition{ - 100, - fesa::core::ElementType::Mitc4, - {1, 2, 3, 4}, - 500}); + add_material_and_property(domain); + add_element(domain); domain.addNodeSet("left-edge", {1, 4}); domain.addElementSet("shells", {100}); @@ -316,11 +318,8 @@ int duplicate_set_names_throw() { int sets_referencing_missing_ids_throw() { fesa::core::Domain domain; add_four_nodes(domain); - domain.addElement(fesa::core::ElementDefinition{ - 100, - fesa::core::ElementType::Mitc4, - {1, 2, 3, 4}, - 500}); + add_material_and_property(domain); + add_element(domain); if (const int result = require_throws([&domain]() { domain.addNodeSet("bad-nodes", {1, 99}); @@ -338,7 +337,7 @@ int add_and_retrieve_boundary_condition() { domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0}); const std::size_t index = domain.addBoundaryCondition( - fesa::core::BoundaryCondition{1, fesa::core::Dof::U1, 0.0}); + std::make_unique(1, fesa::core::Dof::U1, 0.0)); if (const int result = require(index == 0); result != 0) { return result; @@ -347,38 +346,47 @@ int add_and_retrieve_boundary_condition() { return result; } - const fesa::core::BoundaryCondition& condition = domain.boundaryCondition(index); - if (const int result = require(condition.nodeId() == 1); result != 0) { + const fesa::boundary::BoundaryCondition& condition = domain.boundaryCondition(index); + if (const int result = require(condition.kind() == fesa::boundary::BoundaryConditionKind::SinglePointConstraint); + result != 0) { return result; } - if (const int result = require(condition.dof() == fesa::core::Dof::U1); result != 0) { + const auto& spc = static_cast(condition); + if (const int result = require(spc.nodeId() == 1); result != 0) { return result; } - return require(condition.value() == 0.0); + 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.addNodalLoad( - fesa::core::NodalLoadDefinition{1, fesa::core::Dof::U3, -100.0}); + const std::size_t index = domain.addLoad( + std::make_unique(1, fesa::core::Dof::U3, -100.0)); if (const int result = require(index == 0); result != 0) { return result; } - if (const int result = require(domain.nodalLoadCount() == 1); result != 0) { + if (const int result = require(domain.loadCount() == 1); result != 0) { return result; } - const fesa::core::NodalLoadDefinition& load = domain.nodalLoad(index); - if (const int result = require(load.nodeId() == 1); result != 0) { + const fesa::load::Load& load = domain.load(index); + if (const int result = require(load.kind() == fesa::load::LoadKind::Nodal); result != 0) { return result; } - if (const int result = require(load.dof() == fesa::core::Dof::U3); result != 0) { + const auto& nodal_load = static_cast(load); + if (const int result = require(nodal_load.nodeId() == 1); result != 0) { return result; } - return require(load.value() == -100.0); + 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() { @@ -386,15 +394,36 @@ int missing_node_boundary_condition_and_load_throw() { if (const int result = require_throws([&domain]() { (void)domain.addBoundaryCondition( - fesa::core::BoundaryCondition{99, fesa::core::Dof::U1, 0.0}); + std::make_unique(99, fesa::core::Dof::U1, 0.0)); }); result != 0) { return result; } return require_throws([&domain]() { - (void)domain.addNodalLoad( - fesa::core::NodalLoadDefinition{99, fesa::core::Dof::U3, -100.0}); + (void)domain.addLoad( + std::make_unique(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(1, fesa::core::Dof::U1, 0.0)); + domain.addLoad(std::make_unique(1, fesa::core::Dof::U3, -100.0)); + + if (const int result = require_throws([&domain]() { + (void)domain.addBoundaryCondition( + std::make_unique(1, fesa::core::Dof::U1, 1.0)); + }); + result != 0) { + return result; + } + + return require_throws([&domain]() { + (void)domain.addLoad( + std::make_unique(1, fesa::core::Dof::U3, -200.0)); }); } @@ -402,9 +431,9 @@ 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( - fesa::core::BoundaryCondition{1, fesa::core::Dof::U1, 0.0}); - const std::size_t load = domain.addNodalLoad( - fesa::core::NodalLoadDefinition{1, fesa::core::Dof::U3, -100.0}); + std::make_unique(1, fesa::core::Dof::U1, 0.0)); + const std::size_t load = domain.addLoad( + std::make_unique(1, fesa::core::Dof::U3, -100.0)); domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}}); @@ -438,9 +467,9 @@ 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( - fesa::core::BoundaryCondition{1, fesa::core::Dof::U1, 0.0}); - const std::size_t load = domain.addNodalLoad( - fesa::core::NodalLoadDefinition{1, fesa::core::Dof::U3, -100.0}); + std::make_unique(1, fesa::core::Dof::U1, 0.0)); + const std::size_t load = domain.addLoad( + std::make_unique(1, fesa::core::Dof::U3, -100.0)); domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}}); @@ -461,22 +490,17 @@ int duplicate_and_invalid_step_references_throw() { }); } -int const_domain_retrieval_returns_const_model_data() { +int const_domain_retrieval_returns_const_runtime_model_data() { fesa::core::Domain domain; add_four_nodes(domain); - domain.addMaterial(fesa::core::LinearElasticMaterialDefinition{700, 210.0, 0.3}); - domain.addShellProperty(fesa::core::ShellPropertyDefinition{500, 700, 0.01}); - domain.addElement(fesa::core::ElementDefinition{ - 100, - fesa::core::ElementType::Mitc4, - {1, 2, 3, 4}, - 500}); + 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( - fesa::core::BoundaryCondition{1, fesa::core::Dof::U1, 0.0}); - const std::size_t load = domain.addNodalLoad( - fesa::core::NodalLoadDefinition{1, fesa::core::Dof::U3, -100.0}); + std::make_unique(1, fesa::core::Dof::U1, 0.0)); + const std::size_t load = domain.addLoad( + std::make_unique(1, fesa::core::Dof::U3, -100.0)); domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}}); const fesa::core::Domain& const_domain = domain; @@ -485,15 +509,15 @@ int const_domain_retrieval_returns_const_model_data() { result != 0) { return result; } - if (const int result = require((std::is_same::value)); + if (const int result = require((std::is_same::value)); result != 0) { return result; } - if (const int result = require((std::is_same::value)); + if (const int result = require((std::is_same::value)); result != 0) { return result; } - if (const int result = require((std::is_same::value)); + if (const int result = require((std::is_same::value)); result != 0) { return result; } @@ -505,11 +529,11 @@ int const_domain_retrieval_returns_const_model_data() { result != 0) { return result; } - if (const int result = require((std::is_same::value)); + if (const int result = require((std::is_same::value)); result != 0) { return result; } - if (const int result = require((std::is_same::value)); + if (const int result = require((std::is_same::value)); result != 0) { return result; } @@ -519,27 +543,21 @@ int const_domain_retrieval_returns_const_model_data() { int failed_inserts_do_not_mutate_counts() { fesa::core::Domain domain; add_four_nodes(domain); - domain.addMaterial(fesa::core::LinearElasticMaterialDefinition{700, 210.0, 0.3}); - domain.addShellProperty(fesa::core::ShellPropertyDefinition{500, 700, 0.01}); - domain.addElement(fesa::core::ElementDefinition{ - 100, - fesa::core::ElementType::Mitc4, - {1, 2, 3, 4}, - 500}); + 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( - fesa::core::BoundaryCondition{1, fesa::core::Dof::U1, 0.0}); - const std::size_t load = domain.addNodalLoad( - fesa::core::NodalLoadDefinition{1, fesa::core::Dof::U3, -100.0}); + std::make_unique(1, fesa::core::Dof::U1, 0.0)); + const std::size_t load = domain.addLoad( + std::make_unique(1, fesa::core::Dof::U3, -100.0)); domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}}); if (const int result = require_throws([&domain]() { - domain.addElement(fesa::core::ElementDefinition{ + domain.addElement(std::make_unique( 101, - fesa::core::ElementType::Mitc4, - {1, 2, 3, 99}, - 500}); + std::array{1, 2, 3, 99}, + 500)); }); result != 0) { return result; @@ -549,7 +567,7 @@ int failed_inserts_do_not_mutate_counts() { } if (const int result = require_throws([&domain]() { - domain.addShellProperty(fesa::core::ShellPropertyDefinition{501, 404, 0.01}); + domain.addShellProperty(fesa::property::ShellProperty{501, 404, 0.01}); }); result != 0) { return result; @@ -570,7 +588,7 @@ int failed_inserts_do_not_mutate_counts() { if (const int result = require_throws([&domain]() { (void)domain.addBoundaryCondition( - fesa::core::BoundaryCondition{99, fesa::core::Dof::U1, 0.0}); + std::make_unique(99, fesa::core::Dof::U1, 0.0)); }); result != 0) { return result; @@ -609,6 +627,9 @@ int run_domain_storage_tests() { 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; } @@ -639,13 +660,16 @@ int run_domain_storage_tests() { 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_model_data(); result != 0) { + 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) { diff --git a/tests/core/model_types_test.cpp b/tests/core/model_types_test.cpp index 1900f13..4ee9760 100644 --- a/tests/core/model_types_test.cpp +++ b/tests/core/model_types_test.cpp @@ -23,6 +23,9 @@ int run_model_types_tests() { 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(Dof::U1) == 0); result != 0) { return result; }