refactor: store runtime objects in domain

This commit is contained in:
김경종
2026-06-09 10:08:34 +09:00
parent 8f24213ab7
commit f4196efb10
20 changed files with 754 additions and 368 deletions
+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
+8
View File
@@ -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.
@@ -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
```
+22 -48
View File
@@ -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 <cstddef>
#include <memory>
@@ -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<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(BoundaryCondition condition);
std::size_t addNodalLoad(NodalLoadDefinition load);
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);
void addElementObject(std::unique_ptr<fesa::element::Element> element);
void addMaterialObject(std::unique_ptr<fesa::material::Material> material);
std::size_t addLoadObject(std::unique_ptr<fesa::load::Load> load);
std::size_t addBoundaryObject(std::unique_ptr<fesa::boundary::BoundaryCondition> 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<NodeId>* findNodeSet(const std::string& name) const noexcept;
@@ -61,46 +53,28 @@ public:
const std::vector<ElementId>& 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<NodeId, Node> nodes_;
std::unordered_map<ElementId, ElementDefinition> elements_;
std::unordered_map<MaterialId, LinearElasticMaterialDefinition> materials_;
std::unordered_map<PropertyId, ShellPropertyDefinition> shell_properties_;
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<BoundaryCondition> boundary_conditions_;
std::vector<NodalLoadDefinition> nodal_loads_;
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_;
std::unordered_map<ElementId, std::unique_ptr<fesa::element::Element>> element_objects_;
std::unordered_map<MaterialId, std::unique_ptr<fesa::material::Material>> material_objects_;
std::vector<std::unique_ptr<fesa::load::Load>> load_objects_;
std::vector<std::unique_ptr<fesa::boundary::BoundaryCondition>> boundary_objects_;
};
} // namespace fesa::core
-4
View File
@@ -6,10 +6,6 @@
namespace fesa::core {
enum class ElementType {
Mitc4
};
class ElementDefinition {
public:
ElementDefinition(
+4
View File
@@ -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
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "fesa/core/ElementDefinition.hpp"
#include "fesa/core/ModelTypes.hpp"
#include <array>
#include <cstddef>
+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.
+4
View File
@@ -7,6 +7,10 @@
{
"dir": "analysis-model-objects",
"status": "completed"
},
{
"dir": "domain-runtime-storage",
"status": "completed"
}
]
}
+91 -166
View File
@@ -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 <stdexcept>
@@ -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<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()) {
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<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(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<ElementId> 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<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(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<fesa::load::Load> 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<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;
}
@@ -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<fesa::element::Element> 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<fesa::material::Material> 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<fesa::load::Load> load) {
if (!load) {
throw std::invalid_argument("load object is null");
}
if (const auto* nodal = dynamic_cast<const fesa::load::NodalLoad*>(load.get())) {
for (const auto& existing : load_objects_) {
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 = load_objects_.size();
load_objects_.push_back(std::move(load));
return index;
}
std::size_t Domain::addBoundaryObject(std::unique_ptr<fesa::boundary::BoundaryCondition> boundary) {
if (!boundary) {
throw std::invalid_argument("boundary object is null");
}
if (const auto* spc = dynamic_cast<const fesa::boundary::SinglePointConstraint*>(boundary.get())) {
for (const auto& existing : boundary_objects_) {
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 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
+25 -24
View File
@@ -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 <memory>
#include <stdexcept>
@@ -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<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.addElementObject(std::make_unique<fesa::element::Mitc4Element>(
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
500));
domain.addMaterialObject(std::make_unique<fesa::material::LinearElasticMaterial>(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<fesa::element::Mitc4Element>(
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
500));
domain.addMaterialObject(std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3));
if (const int result = require_throws<std::invalid_argument>([&domain]() {
domain.addElementObject(std::make_unique<fesa::element::Mitc4Element>(
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
500));
@@ -79,36 +80,36 @@ int duplicate_element_and_material_object_ids_throw() {
return result;
}
return require_throws<std::invalid_argument>([&domain]() {
domain.addMaterialObject(std::make_unique<fesa::material::LinearElasticMaterial>(700, 100.0, 0.25));
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.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<std::out_of_range>([&domain]() {
(void)domain.elementObject(404);
(void)domain.element(404);
});
result != 0) {
return result;
}
return require_throws<std::out_of_range>([&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<fesa::load::NodalLoad>(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<fesa::boundary::SinglePointConstraint>(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<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
domain.addBoundaryObject(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));
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.addLoadObject(std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -200.0));
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.addBoundaryObject(std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 1.0));
domain.addBoundaryCondition(std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 1.0));
});
}
+144 -120
View File
@@ -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 <array>
#include <memory>
#include <stdexcept>
#include <type_traits>
@@ -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<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;
@@ -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<std::invalid_argument>([&domain]() {
domain.addElement(fesa::core::ElementDefinition{
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
fesa::core::ElementType::Mitc4,
{1, 2, 3, 4},
500});
std::array<fesa::core::NodeId, 4>{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<std::invalid_argument>([&domain]() {
domain.addElement(fesa::core::ElementDefinition{
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
100,
fesa::core::ElementType::Mitc4,
{1, 2, 3, 4},
500});
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));
});
}
@@ -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<std::invalid_argument>([&domain]() {
domain.addMaterial(fesa::core::LinearElasticMaterialDefinition{700, 100.0, 0.25});
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::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<std::invalid_argument>([&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<std::invalid_argument>([&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<fesa::boundary::SinglePointConstraint>(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<const fesa::boundary::SinglePointConstraint&>(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<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.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<const fesa::load::NodalLoad&>(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<std::invalid_argument>([&domain]() {
(void)domain.addBoundaryCondition(
fesa::core::BoundaryCondition{99, fesa::core::Dof::U1, 0.0});
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.addNodalLoad(
fesa::core::NodalLoadDefinition{99, fesa::core::Dof::U3, -100.0});
(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));
});
}
@@ -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<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}});
@@ -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<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}});
@@ -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<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;
@@ -485,15 +509,15 @@ int const_domain_retrieval_returns_const_model_data() {
result != 0) {
return result;
}
if (const int result = require((std::is_same<decltype(const_domain.element(100)), const fesa::core::ElementDefinition&>::value));
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::core::LinearElasticMaterialDefinition&>::value));
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::core::ShellPropertyDefinition&>::value));
if (const int result = require((std::is_same<decltype(const_domain.shellProperty(500)), const fesa::property::ShellProperty&>::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<decltype(const_domain.boundaryCondition(0)), const fesa::core::BoundaryCondition&>::value));
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.nodalLoad(0)), const fesa::core::NodalLoadDefinition&>::value));
if (const int result = require((std::is_same<decltype(const_domain.load(0)), const fesa::load::Load&>::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<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(fesa::core::ElementDefinition{
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
101,
fesa::core::ElementType::Mitc4,
{1, 2, 3, 99},
500});
std::array<fesa::core::NodeId, 4>{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<std::invalid_argument>([&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<std::invalid_argument>([&domain]() {
(void)domain.addBoundaryCondition(
fesa::core::BoundaryCondition{99, fesa::core::Dof::U1, 0.0});
std::make_unique<fesa::boundary::SinglePointConstraint>(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) {
+3
View File
@@ -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<int>(Dof::U1) == 0); result != 0) {
return result;
}