feat: add analysis model objects
This commit is contained in:
@@ -7,7 +7,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
add_library(fesa_core STATIC
|
||||
src/boundary/SinglePointConstraint.cpp
|
||||
src/core/Domain.cpp
|
||||
src/element/Mitc4Element.cpp
|
||||
src/load/NodalLoad.cpp
|
||||
src/material/LinearElasticMaterial.cpp
|
||||
src/property/ShellProperty.cpp
|
||||
)
|
||||
target_include_directories(fesa_core PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
@@ -18,6 +23,7 @@ enable_testing()
|
||||
add_executable(fesa_domain_tests
|
||||
tests/core/boundary_condition_test.cpp
|
||||
tests/core/domain_bootstrap_test.cpp
|
||||
tests/core/domain_model_object_test.cpp
|
||||
tests/core/domain_storage_test.cpp
|
||||
tests/core/element_definition_test.cpp
|
||||
tests/core/load_definition_test.cpp
|
||||
@@ -31,3 +37,17 @@ target_link_libraries(fesa_domain_tests PRIVATE fesa_core)
|
||||
|
||||
add_test(NAME domain.bootstrap COMMAND fesa_domain_tests)
|
||||
set_tests_properties(domain.bootstrap PROPERTIES LABELS "domain;core")
|
||||
|
||||
add_executable(fesa_model_object_tests
|
||||
tests/boundary/boundary_base_test.cpp
|
||||
tests/element/element_base_test.cpp
|
||||
tests/element/mitc4_element_model_test.cpp
|
||||
tests/load/load_base_test.cpp
|
||||
tests/material/material_base_test.cpp
|
||||
tests/model_object_main.cpp
|
||||
tests/property/shell_property_test.cpp
|
||||
)
|
||||
target_link_libraries(fesa_model_object_tests PRIVATE fesa_core)
|
||||
|
||||
add_test(NAME model-object.base COMMAND fesa_model_object_tests)
|
||||
set_tests_properties(model-object.base PROPERTIES LABELS "model-object;core")
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
- Project plan/design documents live under `docs/project-plan/`.
|
||||
- Common per-run AI Agent rules live in `docs/AGENT_RULES.md`.
|
||||
- Domain model foundation phase exists under `phases/domain-model-foundation/` and the first C++/MSVC CMake/CTest core implementation slice has been added.
|
||||
- Analysis model object phase exists under `phases/analysis-model-objects/` and adds identity-only model objects for Domain ownership.
|
||||
|
||||
## Completed
|
||||
- Defined the nine-step solver development workflow.
|
||||
@@ -29,6 +30,10 @@
|
||||
- Implemented `Domain` model-definition storage for nodes, MITC4 element definitions, linear elastic material definitions, shell properties, node sets, element sets, boundary conditions, nodal loads, and linear static step definitions.
|
||||
- Added Domain foundation C++ tests under `tests/core/`.
|
||||
- Fixed `scripts/validate_workspace.py` so CMake/CTest validation can use the default CMake install path when CMake is not on the shell PATH.
|
||||
- Created `docs/implementation-plans/analysis-model-objects-implementation-plan.md`.
|
||||
- Added identity-only model object base classes and concrete skeletons for `Element`/`Mitc4Element`, `Material`/`LinearElasticMaterial`, `ShellProperty`, `Load`/`NodalLoad`, and `BoundaryCondition`/`SinglePointConstraint`.
|
||||
- Extended `Domain` with RAII `std::unique_ptr` ownership APIs for polymorphic element, material, load, and boundary model objects while preserving existing `*Definition` storage APIs.
|
||||
- Added `fesa_model_object_tests` CTest target and model-object tests under `tests/element/`, `tests/material/`, `tests/property/`, `tests/load/`, and `tests/boundary/`.
|
||||
|
||||
## In Progress
|
||||
- Ready for the next upstream MITC4 stage: new solver feature requirements analysis for `mitc4-linear-static-shell`.
|
||||
@@ -42,6 +47,10 @@
|
||||
6. Create `docs/implementation-plans/mitc4-linear-static-shell-implementation-plan.md`.
|
||||
|
||||
## Last Validation
|
||||
- 2026-06-09: After analysis model object implementation, `python -m unittest discover -s scripts -p "test_*.py"` passed. 89 tests ran successfully.
|
||||
- 2026-06-09: After analysis model object implementation, `python scripts/validate_workspace.py` configured CMake with Visual Studio 17 2022 x64, built Debug targets, ran CTest, and passed.
|
||||
- 2026-06-09: After analysis model object implementation, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` passed. 2 CTest executables ran successfully.
|
||||
- 2026-06-09: After analysis model object implementation, `git diff --check` passed.
|
||||
- 2026-06-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,73 @@
|
||||
# Analysis Model Objects Build/Test Report
|
||||
|
||||
## Metadata
|
||||
- feature_id: analysis-model-objects
|
||||
- source_implementation_report: N/A
|
||||
- source_implementation_plan: docs/implementation-plans/analysis-model-objects-implementation-plan.md
|
||||
- status: pass-for-next-implementation-stage
|
||||
- owner_agent: build-test-executor-agent
|
||||
- date: 2026-06-09
|
||||
|
||||
## Execution Environment
|
||||
- os: Windows
|
||||
- generator: Visual Studio 17 2022
|
||||
- platform: x64
|
||||
- config: Debug
|
||||
- build_dir: build/msvc-debug
|
||||
- active_override_env_vars: none
|
||||
- command_discovery_path: default CMake/MSVC x64 Debug through `python scripts/validate_workspace.py`
|
||||
|
||||
## Command Log Summary
|
||||
|
||||
| order | command | exit_code | duration | stdout_stderr_tail |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | `python -m unittest discover -s scripts -p "test_*.py"` | 0 | <1s | 89 tests passed |
|
||||
| 2 | `python scripts/validate_workspace.py` | 0 | ~4s | CMake configure, Debug build, and CTest passed |
|
||||
| 3 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` | 0 | <1s | 2 CTest executables passed |
|
||||
| 4 | `git diff --check` | 0 | <1s | no whitespace errors; line-ending normalization warnings may appear |
|
||||
|
||||
## Validation Results
|
||||
|
||||
| validation_stage | result | evidence |
|
||||
| --- | --- | --- |
|
||||
| harness self-test | pass | Python unittest discovery passed 89 tests |
|
||||
| configure | pass | CMake generated `build/msvc-debug` with Visual Studio 17 2022 x64 |
|
||||
| build | pass | `fesa_core.lib`, `fesa_domain_tests.exe`, and `fesa_model_object_tests.exe` built |
|
||||
| CTest | pass | `domain.bootstrap` and `model-object.base` passed |
|
||||
| feature-specific tests | pass | model-object and domain CTest filters passed |
|
||||
|
||||
## Failure Classification
|
||||
|
||||
- classification: N/A
|
||||
- primary_failure: N/A
|
||||
- first_failed_command: N/A
|
||||
- evidence_tail: validation succeeded
|
||||
|
||||
## Failed Test Inventory
|
||||
|
||||
| test_name | label | command | failure_summary |
|
||||
| --- | --- | --- | --- |
|
||||
| N/A | N/A | N/A | N/A |
|
||||
|
||||
## Handoff Recommendation
|
||||
|
||||
| target_agent | reason | required_input |
|
||||
| --- | --- | --- |
|
||||
| Parser/Factory Implementation Agent | New model objects can be populated from future parser/factory mappings | model object headers and compatibility note |
|
||||
| AnalysisModel Implementation Agent | Domain now exposes identity-only model objects through const base APIs | Domain object ownership APIs |
|
||||
| Build/Test Executor Agent | Re-run after downstream C++ changes | validation commands in this report |
|
||||
| Reference Verification Agent | Not ready; this phase produces no solver HDF5 result | N/A |
|
||||
|
||||
## No-Change Assertion
|
||||
|
||||
- source_files_modified: true
|
||||
- test_files_modified: true
|
||||
- cmake_files_modified: true
|
||||
- reference_artifacts_modified: false
|
||||
- tolerance_policies_modified: false
|
||||
- notes: This phase added model object classes and Domain ownership APIs only. It did not run reference solvers, generate reference artifacts, change tolerances, or claim numerical MITC4 correctness.
|
||||
|
||||
## Open Issues
|
||||
|
||||
- Parser/factory mapping from core `*Definition` DTOs to model objects remains deferred.
|
||||
- MITC4 stiffness, material constitutive behavior, shell section stiffness, assembly, solver, and HDF5 output remain future phases.
|
||||
@@ -0,0 +1,179 @@
|
||||
# Analysis Model Objects Implementation Plan
|
||||
|
||||
## Metadata
|
||||
- feature_id: analysis-model-objects
|
||||
- source_requirement: AGENTS.md; docs/ARCHITECTURE.md; docs/ADR.md
|
||||
- source_research: N/A for this architecture/model-object slice
|
||||
- source_formulation: N/A for this architecture/model-object slice
|
||||
- source_numerical_review: N/A for this architecture/model-object slice
|
||||
- source_io_definition: docs/ARCHITECTURE.md Domain and factory/registry ownership rules
|
||||
- source_reference_models: N/A for this architecture/model-object slice
|
||||
- status: ready-for-implementation
|
||||
- owner_agent: implementation-planning-agent
|
||||
- date: 2026-06-09
|
||||
|
||||
## Readiness Check
|
||||
|
||||
| input | required_status | observed_status | decision |
|
||||
| --- | --- | --- | --- |
|
||||
| architecture | model object boundaries documented | documented in docs/ARCHITECTURE.md and ADR-004/010/011 | proceed |
|
||||
| domain foundation | base Domain storage available | implemented in `domain-model-foundation` | proceed |
|
||||
| formulation | not required for identity-only model object skeletons | N/A | proceed |
|
||||
| numerical_review | not required for identity-only model object skeletons | N/A | proceed |
|
||||
| io_definition | parser DTO compatibility sufficient | existing `*Definition` objects remain input DTOs | proceed |
|
||||
| reference_models | not required because this slice produces no solver result | N/A | proceed |
|
||||
|
||||
## Implementation Scope
|
||||
|
||||
- included_behavior: C++17 model object classes for nodes, elements, materials, shell properties, loads, and boundary conditions.
|
||||
- included_behavior: abstract base classes for element, material, load, and boundary condition model objects.
|
||||
- included_behavior: MITC4 element model skeleton with id, connectivity, property id, 4-node count, and 24-DOF count.
|
||||
- included_behavior: Domain RAII ownership for polymorphic model objects through `std::unique_ptr`.
|
||||
- included_behavior: compatibility with existing `*Definition` value objects and tests.
|
||||
- excluded_behavior: MITC4 stiffness, Jacobian, MITC shear interpolation, constitutive matrices, global force assembly, boundary-condition elimination, equation numbering, solver vectors, reaction recovery, HDF5 output, parser/factory implementation, and reference comparison.
|
||||
- non_goals: numerical correctness claims, release readiness, reference-solver execution, and reference artifact generation.
|
||||
|
||||
## Model Object Contract
|
||||
|
||||
`Domain` remains the owner of parsed model definition. This phase adds runtime model objects that can later be used by `AnalysisModel`, factories, and element/material implementations without storing analysis state in `Domain`.
|
||||
|
||||
Model object responsibilities:
|
||||
|
||||
- `Node`: id and reference coordinates only; fixed six-DOF count from project convention.
|
||||
- `Element`: identity, element type, connectivity, property reference, and node count only.
|
||||
- `Mitc4Element`: MITC4 model skeleton with four node ids and 24 element DOFs only.
|
||||
- `Material`: material identity only.
|
||||
- `LinearElasticMaterial`: id, Young's modulus, and Poisson ratio only.
|
||||
- `ShellProperty`: property id, material id, and thickness only.
|
||||
- `Load`: load kind only.
|
||||
- `NodalLoad`: node id, DOF, and scalar value only.
|
||||
- `BoundaryCondition`: boundary kind only.
|
||||
- `SinglePointConstraint`: node id, DOF, and prescribed scalar value only.
|
||||
|
||||
Excluded from all model objects:
|
||||
|
||||
- equation ids
|
||||
- sparse matrix data
|
||||
- displacement, residual, reaction, velocity, acceleration, or temperature vectors
|
||||
- current time, increment, or iteration counters
|
||||
- element integration point state
|
||||
- MKL, TBB, HDF5, or reference-comparison handles
|
||||
|
||||
Existing `*Definition` value objects remain parser/factory DTOs for now. This phase must not delete them. Later parser/factory work may either map DTOs into model objects or retire DTOs with an explicit migration plan.
|
||||
|
||||
Compatibility decision:
|
||||
|
||||
- Existing `ElementDefinition`, `LinearElasticMaterialDefinition`, `ShellPropertyDefinition`, `BoundaryCondition`, `NodalLoadDefinition`, and `LinearStaticStepDefinition` classes remain under `include/fesa/core/` as value-style input DTOs.
|
||||
- New runtime model objects live under ownership namespaces: `fesa::element`, `fesa::material`, `fesa::property`, `fesa::load`, and `fesa::boundary`.
|
||||
- `Domain` supports both existing DTO storage APIs and new polymorphic model-object ownership APIs during the transition.
|
||||
- Parser/factory work must make an explicit choice to map DTOs to model objects or replace DTOs in a separate phase. This phase does not decide parser behavior.
|
||||
|
||||
## Work Breakdown
|
||||
|
||||
| task_id | order | purpose | upstream_trace | depends_on | expected_test_first |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| AMO-001 | 1 | refine `Node` model class contract | ADR-010; docs/ARCHITECTURE.md Node | none | AMO-TEST-001 |
|
||||
| AMO-002 | 2 | add abstract `Element` base interface | ADR-004; docs/ARCHITECTURE.md Element | AMO-001 | AMO-TEST-002 |
|
||||
| AMO-003 | 3 | add MITC4 element model skeleton | AGENTS.md MITC4 scope | AMO-002 | AMO-TEST-003 |
|
||||
| AMO-004 | 4 | add material base and linear elastic material object | docs/ARCHITECTURE.md Material | AMO-002 | AMO-TEST-004 |
|
||||
| AMO-005 | 5 | add shell property model object | docs/ARCHITECTURE.md Property | AMO-004 | AMO-TEST-005 |
|
||||
| AMO-006 | 6 | add load base and nodal load object | docs/ARCHITECTURE.md loads | AMO-002 | AMO-TEST-006 |
|
||||
| AMO-007 | 7 | add boundary base and single-point constraint object | ADR-012 | AMO-002 | AMO-TEST-007 |
|
||||
| AMO-008 | 8 | extend Domain with polymorphic object ownership | ADR-010; ADR-011 | AMO-003 to AMO-007 | AMO-TEST-008 |
|
||||
| AMO-009 | 9 | preserve `*Definition` compatibility | domain-model-foundation plan | AMO-008 | AMO-TEST-009 |
|
||||
| AMO-010 | 10 | record build/test evidence and handoff | docs/build-test-reports/README.md | AMO-009 | AMO-TEST-010 |
|
||||
|
||||
## TDD Test Plan
|
||||
|
||||
| test_id | order | test_type | red_condition | green_condition | linked_task | command |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| AMO-TEST-001 | 1 | unit | `Node::dofCount()` missing | Node model tests pass | AMO-001 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
|
||||
| AMO-TEST-002 | 2 | unit | `fesa/element/Element.hpp` missing | Element base polymorphism tests pass | AMO-002 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-003 | 3 | unit | `Mitc4Element` missing | MITC4 model skeleton tests pass | AMO-003 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-004 | 4 | unit | material model headers missing | material base and linear elastic tests pass | AMO-004 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-005 | 5 | unit | shell property model missing | shell property tests pass | AMO-005 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-006 | 6 | unit | load model headers missing | load base and nodal load tests pass | AMO-006 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-007 | 7 | unit | boundary model headers missing | boundary base and SPC tests pass | AMO-007 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-008 | 8 | unit | Domain polymorphic APIs missing | Domain owns and retrieves model objects by base interface | AMO-008 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` |
|
||||
| AMO-TEST-009 | 9 | regression | existing definition tests fail | existing Domain definition tests remain passing | AMO-009 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
|
||||
| AMO-TEST-010 | 10 | validation | report evidence missing | validation report records passing commands or classified failure | AMO-010 | `python scripts/validate_workspace.py` |
|
||||
|
||||
## CMake/CTest Plan
|
||||
|
||||
- target_candidates: `fesa_core`, `fesa_domain_tests`, `fesa_model_object_tests`
|
||||
- labels: `domain`, `core`, `model-object`, `element`, `material`, `property`, `load`, `boundary`
|
||||
- msvc_config: Debug
|
||||
- expected_feature_command: `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"`
|
||||
- workspace_validation: `python scripts/validate_workspace.py`
|
||||
|
||||
## Candidate Files and Ownership
|
||||
|
||||
| file_candidate | purpose | owner_boundary | notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `include/fesa/core/Node.hpp` | existing node model object | core model definition | no solver state |
|
||||
| `include/fesa/element/Element.hpp` | abstract element model base | element model identity | no stiffness API |
|
||||
| `include/fesa/element/Mitc4Element.hpp` | MITC4 element model skeleton | element model identity | no formulation implementation |
|
||||
| `src/element/Mitc4Element.cpp` | MITC4 model accessors | element model identity | no numerical behavior |
|
||||
| `include/fesa/material/Material.hpp` | abstract material model base | material identity | no constitutive matrix |
|
||||
| `include/fesa/material/LinearElasticMaterial.hpp` | linear elastic material model | material input values | no stress update |
|
||||
| `src/material/LinearElasticMaterial.cpp` | linear elastic material accessors | material input values | no numerical behavior |
|
||||
| `include/fesa/property/ShellProperty.hpp` | shell property model | property input values | no section stiffness |
|
||||
| `src/property/ShellProperty.cpp` | shell property validation/accessors | property input values | thickness validation only |
|
||||
| `include/fesa/load/Load.hpp` | abstract load model base | load identity | no assembly |
|
||||
| `include/fesa/load/NodalLoad.hpp` | nodal load model | load input values | no global vector behavior |
|
||||
| `src/load/NodalLoad.cpp` | nodal load accessors | load input values | no assembly |
|
||||
| `include/fesa/boundary/BoundaryCondition.hpp` | abstract boundary model base | boundary identity | no matrix application |
|
||||
| `include/fesa/boundary/SinglePointConstraint.hpp` | SPC boundary model | boundary input values | no reaction recovery |
|
||||
| `src/boundary/SinglePointConstraint.cpp` | SPC accessors | boundary input values | no matrix application |
|
||||
| `include/fesa/core/Domain.hpp` | Domain ownership extension | core ownership | preserve existing APIs |
|
||||
| `src/core/Domain.cpp` | Domain ownership implementation | core ownership | no solver state |
|
||||
| `tests/**` | TDD coverage | tests | write before production changes |
|
||||
|
||||
## Acceptance Traceability Matrix
|
||||
|
||||
| requirement_id | task_id | test_id | reference_model_id | acceptance_criterion | status |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| AMO-REQ-001 Node is model data only | AMO-001 | AMO-TEST-001 | N/A | Node tests pass and no solver state added | draft |
|
||||
| AMO-REQ-002 Element base is identity-only | AMO-002 | AMO-TEST-002 | N/A | Element base tests pass | draft |
|
||||
| AMO-REQ-003 MITC4 model skeleton has 4 nodes and 24 DOFs | AMO-003 | AMO-TEST-003 | N/A | MITC4 model tests pass | draft |
|
||||
| AMO-REQ-004 Material model stores input values only | AMO-004 | AMO-TEST-004 | N/A | material tests pass | draft |
|
||||
| AMO-REQ-005 Shell property validates thickness only | AMO-005 | AMO-TEST-005 | N/A | shell property tests pass | draft |
|
||||
| AMO-REQ-006 Load and boundary models do not assemble/apply | AMO-006; AMO-007 | AMO-TEST-006; AMO-TEST-007 | N/A | load/boundary tests pass and APIs remain value accessors | draft |
|
||||
| AMO-REQ-007 Domain owns polymorphic model objects via RAII | AMO-008 | AMO-TEST-008 | N/A | Domain ownership tests pass | draft |
|
||||
| AMO-REQ-008 Existing definition APIs remain compatible | AMO-009 | AMO-TEST-009 | N/A | existing domain tests pass | draft |
|
||||
|
||||
## Validation Commands
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
|
||||
```
|
||||
|
||||
## Risks and Downstream Handoff
|
||||
|
||||
### Implementation Agent
|
||||
|
||||
- Keep model objects identity-only until formulation and solver stages define numerical APIs.
|
||||
- Preserve existing `*Definition` tests.
|
||||
- Prefer RAII ownership through `std::unique_ptr` and const retrieval.
|
||||
|
||||
### Build/Test Executor Agent
|
||||
|
||||
- Use Visual Studio 17 2022, x64, Debug, and `build/msvc-debug`.
|
||||
- Use `python scripts/validate_workspace.py` as canonical validation.
|
||||
|
||||
### Correction Agent
|
||||
|
||||
- Implementation-owned failures are expected to be compile or unit-test failures in model object headers, model object sources, CMake registration, or Domain ownership methods.
|
||||
- Upstream-contract failures include requests to add numerical MITC4 behavior or solver state in this phase.
|
||||
|
||||
### Reference Verification Agent
|
||||
|
||||
- No reference verification is required for this phase.
|
||||
- This phase produces no HDF5 result and consumes no reference artifacts.
|
||||
|
||||
## Open Issues
|
||||
|
||||
- Parser/factory mapping from `*Definition` DTOs to model objects remains deferred.
|
||||
- MITC4 formulation, material constitutive behavior, shell section stiffness, assembly, solver, and HDF5 output remain separate future phases.
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace fesa::boundary {
|
||||
|
||||
enum class BoundaryConditionKind {
|
||||
SinglePointConstraint
|
||||
};
|
||||
|
||||
class BoundaryCondition {
|
||||
public:
|
||||
virtual ~BoundaryCondition() = default;
|
||||
|
||||
virtual BoundaryConditionKind kind() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace fesa::boundary
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/boundary/BoundaryCondition.hpp"
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
namespace fesa::boundary {
|
||||
|
||||
using fesa::core::Dof;
|
||||
using fesa::core::NodeId;
|
||||
|
||||
class SinglePointConstraint final : public BoundaryCondition {
|
||||
public:
|
||||
SinglePointConstraint(NodeId node_id, Dof dof, double value);
|
||||
|
||||
BoundaryConditionKind kind() const noexcept override;
|
||||
NodeId nodeId() const noexcept;
|
||||
Dof dof() const noexcept;
|
||||
double value() const noexcept;
|
||||
|
||||
private:
|
||||
NodeId node_id_;
|
||||
Dof dof_;
|
||||
double value_;
|
||||
};
|
||||
|
||||
} // namespace fesa::boundary
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/boundary/BoundaryCondition.hpp"
|
||||
#include "fesa/core/BoundaryCondition.hpp"
|
||||
#include "fesa/core/ElementDefinition.hpp"
|
||||
#include "fesa/core/LoadDefinition.hpp"
|
||||
@@ -8,8 +9,12 @@
|
||||
#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 <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -27,6 +32,10 @@ public:
|
||||
std::size_t addBoundaryCondition(BoundaryCondition condition);
|
||||
std::size_t addNodalLoad(NodalLoadDefinition 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;
|
||||
@@ -62,6 +71,22 @@ public:
|
||||
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_;
|
||||
@@ -72,6 +97,10 @@ private:
|
||||
std::vector<BoundaryCondition> boundary_conditions_;
|
||||
std::vector<NodalLoadDefinition> nodal_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
|
||||
|
||||
@@ -10,6 +10,8 @@ class Node {
|
||||
public:
|
||||
Node(NodeId id, double x, double y, double z);
|
||||
|
||||
static constexpr std::size_t dofCount() noexcept { return kDofPerNode; }
|
||||
|
||||
NodeId id() const noexcept;
|
||||
double x() const noexcept;
|
||||
double y() const noexcept;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ElementDefinition.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
namespace fesa::element {
|
||||
|
||||
using fesa::core::ElementId;
|
||||
using fesa::core::ElementType;
|
||||
using fesa::core::NodeId;
|
||||
using fesa::core::PropertyId;
|
||||
|
||||
class Element {
|
||||
public:
|
||||
virtual ~Element() = default;
|
||||
|
||||
virtual ElementId id() const noexcept = 0;
|
||||
virtual ElementType type() const noexcept = 0;
|
||||
virtual std::size_t nodeCount() const noexcept = 0;
|
||||
virtual const std::array<NodeId, 4>& connectivity() const noexcept = 0;
|
||||
virtual PropertyId propertyId() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace fesa::element
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/element/Element.hpp"
|
||||
|
||||
namespace fesa::element {
|
||||
|
||||
class Mitc4Element final : public Element {
|
||||
public:
|
||||
Mitc4Element(ElementId id, std::array<NodeId, 4> connectivity, PropertyId property_id);
|
||||
|
||||
ElementId id() const noexcept override;
|
||||
ElementType type() const noexcept override;
|
||||
std::size_t nodeCount() const noexcept override;
|
||||
std::size_t dofCount() const noexcept;
|
||||
const std::array<NodeId, 4>& connectivity() const noexcept override;
|
||||
PropertyId propertyId() const noexcept override;
|
||||
|
||||
private:
|
||||
ElementId id_;
|
||||
std::array<NodeId, 4> connectivity_;
|
||||
PropertyId property_id_;
|
||||
};
|
||||
|
||||
} // namespace fesa::element
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace fesa::load {
|
||||
|
||||
enum class LoadKind {
|
||||
Nodal
|
||||
};
|
||||
|
||||
class Load {
|
||||
public:
|
||||
virtual ~Load() = default;
|
||||
|
||||
virtual LoadKind kind() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace fesa::load
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
#include "fesa/load/Load.hpp"
|
||||
|
||||
namespace fesa::load {
|
||||
|
||||
using fesa::core::Dof;
|
||||
using fesa::core::NodeId;
|
||||
|
||||
class NodalLoad final : public Load {
|
||||
public:
|
||||
NodalLoad(NodeId node_id, Dof dof, double value);
|
||||
|
||||
LoadKind kind() const noexcept override;
|
||||
NodeId nodeId() const noexcept;
|
||||
Dof dof() const noexcept;
|
||||
double value() const noexcept;
|
||||
|
||||
private:
|
||||
NodeId node_id_;
|
||||
Dof dof_;
|
||||
double value_;
|
||||
};
|
||||
|
||||
} // namespace fesa::load
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/material/Material.hpp"
|
||||
|
||||
namespace fesa::material {
|
||||
|
||||
class LinearElasticMaterial final : public Material {
|
||||
public:
|
||||
LinearElasticMaterial(MaterialId id, double young_modulus, double poisson_ratio);
|
||||
|
||||
MaterialId id() const noexcept override;
|
||||
double youngModulus() const noexcept;
|
||||
double poissonRatio() const noexcept;
|
||||
|
||||
private:
|
||||
MaterialId id_;
|
||||
double young_modulus_;
|
||||
double poisson_ratio_;
|
||||
};
|
||||
|
||||
} // namespace fesa::material
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
namespace fesa::material {
|
||||
|
||||
using fesa::core::MaterialId;
|
||||
|
||||
class Material {
|
||||
public:
|
||||
virtual ~Material() = default;
|
||||
|
||||
virtual MaterialId id() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace fesa::material
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
namespace fesa::property {
|
||||
|
||||
using fesa::core::MaterialId;
|
||||
using fesa::core::PropertyId;
|
||||
|
||||
class ShellProperty {
|
||||
public:
|
||||
ShellProperty(PropertyId id, MaterialId material_id, double thickness);
|
||||
|
||||
PropertyId id() const noexcept;
|
||||
MaterialId materialId() const noexcept;
|
||||
double thickness() const noexcept;
|
||||
|
||||
private:
|
||||
PropertyId id_;
|
||||
MaterialId material_id_;
|
||||
double thickness_;
|
||||
};
|
||||
|
||||
} // namespace fesa::property
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"project": "FESA Structural Solver",
|
||||
"phase": "analysis-model-objects",
|
||||
"steps": [
|
||||
{
|
||||
"step": 0,
|
||||
"name": "model-object-contract",
|
||||
"status": "completed",
|
||||
"summary": "Created analysis model object implementation plan with base-class contracts, exclusions, and TDD traceability."
|
||||
},
|
||||
{
|
||||
"step": 1,
|
||||
"name": "node-model-class",
|
||||
"status": "completed",
|
||||
"summary": "Added Node DOF count contract and tests while keeping Node free of solver state."
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "element-base-interface",
|
||||
"status": "completed",
|
||||
"summary": "Added identity-only Element base interface and model-object CTest target."
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "mitc4-element-model",
|
||||
"status": "completed",
|
||||
"summary": "Added MITC4 element model skeleton with four-node connectivity and 24-DOF count tests."
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "material-base-interface",
|
||||
"status": "completed",
|
||||
"summary": "Added Material base and LinearElasticMaterial model object with ownership tests."
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "property-section-model",
|
||||
"status": "completed",
|
||||
"summary": "Added ShellProperty model object with positive-thickness validation tests."
|
||||
},
|
||||
{
|
||||
"step": 6,
|
||||
"name": "load-base-interface",
|
||||
"status": "completed",
|
||||
"summary": "Added Load base and NodalLoad model object with polymorphic access tests."
|
||||
},
|
||||
{
|
||||
"step": 7,
|
||||
"name": "boundary-base-interface",
|
||||
"status": "completed",
|
||||
"summary": "Added BoundaryCondition base and SinglePointConstraint model object with polymorphic access tests."
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
"name": "domain-polymorphic-ownership",
|
||||
"status": "completed",
|
||||
"summary": "Extended Domain with unique_ptr ownership APIs for element, material, load, and boundary model objects while preserving existing definition APIs."
|
||||
},
|
||||
{
|
||||
"step": 9,
|
||||
"name": "legacy-definition-compatibility",
|
||||
"status": "completed",
|
||||
"summary": "Documented compatibility between core definition DTOs and new runtime model object ownership APIs."
|
||||
},
|
||||
{
|
||||
"step": 10,
|
||||
"name": "validation-report-handoff",
|
||||
"status": "completed",
|
||||
"summary": "Recorded build/test evidence, updated project progress, and marked the phase completed."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# Step 0: model-object-contract
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PLAN.md`
|
||||
- `/docs/PROGRESS.md`
|
||||
- `/docs/WORKNOTE.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/PRD.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Create `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`.
|
||||
|
||||
The document must define the model object layer that will be stored by `Domain`:
|
||||
|
||||
- `Node` model object.
|
||||
- abstract `Element` base and MITC4 model object skeleton.
|
||||
- abstract `Material` base and linear elastic material model object.
|
||||
- shell property/section model object.
|
||||
- abstract `Load` base and nodal load model object.
|
||||
- abstract `BoundaryCondition` base and single-point constraint model object.
|
||||
- `Domain` RAII ownership of polymorphic model objects through `std::unique_ptr`.
|
||||
|
||||
State explicitly that this phase does not implement MITC4 stiffness, material constitutive matrices, global force assembly, boundary-condition matrix application, equation numbering, solver vectors, HDF5 output, reference comparison, or reference-solver execution.
|
||||
|
||||
Document how existing `*Definition` value objects remain compatible as input DTOs until parser/factory work replaces or maps them.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
This is a documentation planning step. Do not write C++ production code in this step.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
Update `/phases/analysis-model-objects/index.json` step 0 with `completed`, `error`, or `blocked`.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not change C++ code in this step.
|
||||
- Do not change MITC4 formulation, I/O contracts, reference artifacts, or tolerance policy.
|
||||
@@ -0,0 +1,53 @@
|
||||
# Step 1: node-model-class
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/Node.hpp`
|
||||
- `/tests/core/node_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Refine the existing `Node` model class contract without adding solver state.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Modify `/include/fesa/core/Node.hpp`
|
||||
- Modify `/src/core/Domain.cpp` only if constructor/accessor definitions move or change
|
||||
- Modify `/tests/core/node_test.cpp`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `Node` exposes `id()`, `coordinates()`, `x()`, `y()`, `z()`.
|
||||
- `Node` exposes `static constexpr std::size_t dofCount()` or equivalent compile-time DOF count of 6.
|
||||
- `Node` does not store equation ids, displacements, residuals, reactions, or constraints.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Extend `/tests/core/node_test.cpp` before production changes:
|
||||
|
||||
- Verify `Node::dofCount() == 6`.
|
||||
- Verify coordinate order is preserved.
|
||||
- Verify the returned coordinate data is const from a const `Node`.
|
||||
|
||||
Run the targeted build and confirm RED before implementation.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
```
|
||||
|
||||
Update `/phases/analysis-model-objects/index.json` step 1 with status and summary.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not add equation ids or solver state to `Node`.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Step 10: validation-report-handoff
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/build-test-reports/README.md`
|
||||
- `/docs/PROGRESS.md`
|
||||
- `/docs/WORKNOTE.md`
|
||||
- `/phases/analysis-model-objects/index.json`
|
||||
|
||||
## Task
|
||||
|
||||
Run final verification and write handoff evidence.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/docs/build-test-reports/analysis-model-objects-build-test.md`
|
||||
- Modify `/docs/PROGRESS.md`
|
||||
- Modify `/docs/WORKNOTE.md` only if new traps or failed approaches occurred
|
||||
- Modify `/phases/analysis-model-objects/index.json`
|
||||
- Modify `/phases/index.json`
|
||||
|
||||
The build/test report must include command log summary, exit codes, configure/build/CTest status, failure classification or `N/A`, and no-change assertions for reference artifacts and tolerance policy.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
This is a validation/reporting step. Do not write new C++ production behavior.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
git status --short --branch
|
||||
git remote -v
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Update:
|
||||
|
||||
- `/phases/analysis-model-objects/index.json` step 10 with `completed`, `error`, or `blocked`.
|
||||
- `/phases/index.json` phase status to `completed` only if all steps are completed.
|
||||
- `/docs/PROGRESS.md` with validation evidence.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not claim MITC4 numerical implementation, reference comparison success, or release readiness.
|
||||
- Do not run Abaqus, Nastran, or any reference solver.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Step 2: element-base-interface
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/ElementDefinition.hpp`
|
||||
- `/CMakeLists.txt`
|
||||
|
||||
## Task
|
||||
|
||||
Add the abstract element model interface.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/element/Element.hpp`
|
||||
- Create `/tests/element/element_base_test.cpp`
|
||||
- Modify `/tests/core/domain_bootstrap_test.cpp` only if the current test executable remains the single test runner
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required interface:
|
||||
|
||||
- virtual destructor.
|
||||
- `ElementId id() const noexcept`.
|
||||
- `ElementType type() const noexcept`.
|
||||
- `std::size_t nodeCount() const noexcept`.
|
||||
- `std::span` is not allowed because this project targets C++17.
|
||||
- Use const reference return for connectivity data.
|
||||
- no stiffness, residual, stress, state, or equation API in this phase.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/element/element_base_test.cpp` before production changes. Use a small test subclass to prove:
|
||||
|
||||
- base pointer dispatch works.
|
||||
- id/type/connectivity/property id are readable through base API.
|
||||
- deleting through `std::unique_ptr<Element>` is valid.
|
||||
|
||||
Run targeted build and confirm RED because `Element.hpp` is missing.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 2 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not implement MITC4 stiffness or numerical element behavior.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Step 3: mitc4-element-model
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/element/Element.hpp`
|
||||
- `/include/fesa/core/ElementDefinition.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add a MITC4 element model object skeleton.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/element/Mitc4Element.hpp`
|
||||
- Create `/src/element/Mitc4Element.cpp`
|
||||
- Create `/tests/element/mitc4_element_model_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `Mitc4Element` derives from `Element`.
|
||||
- Stores element id, four node ids, and property id.
|
||||
- Reports `ElementType::Mitc4`.
|
||||
- Reports `nodeCount() == 4`.
|
||||
- Reports `dofCount() == 24`.
|
||||
- Preserves connectivity order.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/element/mitc4_element_model_test.cpp` before production changes:
|
||||
|
||||
- Construct `Mitc4Element`.
|
||||
- Access it through `const Element&`.
|
||||
- Verify id, type, node count, DOF count, connectivity, and property id.
|
||||
|
||||
Run targeted build and confirm RED because `Mitc4Element.hpp` is missing.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 3 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not implement element stiffness, Jacobians, MITC shear interpolation, resultants, stress, or integration-point state.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Step 4: material-base-interface
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/MaterialDefinition.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add material model base and linear elastic material model object.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/material/Material.hpp`
|
||||
- Create `/include/fesa/material/LinearElasticMaterial.hpp`
|
||||
- Create `/src/material/LinearElasticMaterial.cpp`
|
||||
- Create `/tests/material/material_base_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `Material` has virtual destructor and `MaterialId id() const noexcept`.
|
||||
- `LinearElasticMaterial` derives from `Material`.
|
||||
- Stores Young's modulus and Poisson ratio.
|
||||
- No constitutive matrix or stress update in this phase.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/material/material_base_test.cpp` before production changes:
|
||||
|
||||
- Access `LinearElasticMaterial` through `const Material&`.
|
||||
- Verify id, E, and nu.
|
||||
- Verify deletion through `std::unique_ptr<Material>`.
|
||||
|
||||
Run targeted build and confirm RED.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 4 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not implement constitutive matrices, stress updates, or tangent stiffness.
|
||||
@@ -0,0 +1,53 @@
|
||||
# Step 5: property-section-model
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/PropertyDefinition.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add shell property/section model object.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/property/ShellProperty.hpp`
|
||||
- Create `/src/property/ShellProperty.cpp`
|
||||
- Create `/tests/property/shell_property_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- Stores property id, material id, and thickness.
|
||||
- Exposes const accessors.
|
||||
- Rejects non-positive thickness with `std::invalid_argument`.
|
||||
- Does not compute shell section stiffness.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/property/shell_property_test.cpp` before production changes:
|
||||
|
||||
- Valid property stores id/material/thickness.
|
||||
- Zero or negative thickness throws.
|
||||
|
||||
Run targeted build and confirm RED.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 5 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not implement membrane, bending, or shear constitutive stiffness.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Step 6: load-base-interface
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/LoadDefinition.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add load model base and nodal load object.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/load/Load.hpp`
|
||||
- Create `/include/fesa/load/NodalLoad.hpp`
|
||||
- Create `/src/load/NodalLoad.cpp`
|
||||
- Create `/tests/load/load_base_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `Load` has virtual destructor and a load kind enum.
|
||||
- `NodalLoad` derives from `Load`.
|
||||
- Stores node id, DOF, and value.
|
||||
- No global vector assembly in this phase.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/load/load_base_test.cpp` before production changes:
|
||||
|
||||
- Access `NodalLoad` through `const Load&`.
|
||||
- Verify kind, node id, DOF, value.
|
||||
- Verify deletion through `std::unique_ptr<Load>`.
|
||||
|
||||
Run targeted build and confirm RED.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 6 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not assemble forces or apply time/load factors.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Step 7: boundary-base-interface
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/BoundaryCondition.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add boundary condition model base and single-point constraint object.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/boundary/BoundaryCondition.hpp`
|
||||
- Create `/include/fesa/boundary/SinglePointConstraint.hpp`
|
||||
- Create `/src/boundary/SinglePointConstraint.cpp`
|
||||
- Create `/tests/boundary/boundary_base_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `BoundaryCondition` has virtual destructor and a boundary kind enum.
|
||||
- `SinglePointConstraint` derives from `BoundaryCondition`.
|
||||
- Stores node id, DOF, prescribed value.
|
||||
- No matrix elimination or reaction recovery in this phase.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/boundary/boundary_base_test.cpp` before production changes:
|
||||
|
||||
- Access `SinglePointConstraint` through `const BoundaryCondition&`.
|
||||
- Verify kind, node id, DOF, value.
|
||||
- Verify deletion through `std::unique_ptr<BoundaryCondition>`.
|
||||
|
||||
Run targeted build and confirm RED.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 7 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not apply constraints to matrices or compute reactions.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Step 8: domain-polymorphic-ownership
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- all model object headers created by previous steps
|
||||
|
||||
## Task
|
||||
|
||||
Extend `Domain` with RAII ownership APIs for polymorphic model objects while preserving existing definition-storage tests.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Modify `/include/fesa/core/Domain.hpp`
|
||||
- Modify `/src/core/Domain.cpp`
|
||||
- Create `/tests/core/domain_model_object_test.cpp`
|
||||
- Modify `/tests/core/domain_bootstrap_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `Domain::addElementObject(std::unique_ptr<Element>)`
|
||||
- `Domain::findElementObject(ElementId)` and `Domain::elementObject(ElementId)`
|
||||
- equivalent add/find/direct APIs for `Material`, `Load`, and model `BoundaryCondition`.
|
||||
- duplicate ids or duplicate load/boundary keys throw `std::invalid_argument`.
|
||||
- missing direct lookup throws `std::out_of_range`.
|
||||
- retrieval returns const base references or const base pointers.
|
||||
- existing `Definition` APIs remain available.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/core/domain_model_object_test.cpp` before production changes:
|
||||
|
||||
- Domain owns `Mitc4Element`, `LinearElasticMaterial`, `NodalLoad`, and `SinglePointConstraint` through `std::unique_ptr`.
|
||||
- Find/direct lookup through base APIs works.
|
||||
- Duplicate ids throw for element/material.
|
||||
- Missing direct lookup throws.
|
||||
|
||||
Run targeted build and confirm RED.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
```
|
||||
|
||||
Update step 8 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not remove existing definition APIs in this step.
|
||||
- Do not add solver state to Domain.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Step 9: legacy-definition-compatibility
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- `/tests/core/domain_storage_test.cpp`
|
||||
- `/tests/core/domain_model_object_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Preserve and document compatibility between existing `*Definition` value objects and new model-object base classes.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Modify `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- Modify tests only if compatibility gaps are found
|
||||
- Modify production code only for narrow compatibility fixes
|
||||
|
||||
Required behavior:
|
||||
|
||||
- Existing Domain definition-storage tests still pass.
|
||||
- New model-object ownership tests still pass.
|
||||
- The implementation plan clearly states that `*Definition` objects remain parser/factory DTOs until the parser phase decides whether to map or replace them.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
If production code must change, write or extend a failing C++ test before the change. If this is documentation-only, do not add production code.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
|
||||
```
|
||||
|
||||
Update step 9 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not delete existing `*Definition` headers or tests.
|
||||
- Do not rewrite parser or factory behavior.
|
||||
@@ -3,6 +3,10 @@
|
||||
{
|
||||
"dir": "domain-model-foundation",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"dir": "analysis-model-objects",
|
||||
"status": "completed"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "fesa/boundary/SinglePointConstraint.hpp"
|
||||
|
||||
namespace fesa::boundary {
|
||||
|
||||
SinglePointConstraint::SinglePointConstraint(NodeId node_id, Dof dof, double value)
|
||||
: node_id_(node_id), dof_(dof), value_(value) {}
|
||||
|
||||
BoundaryConditionKind SinglePointConstraint::kind() const noexcept {
|
||||
return BoundaryConditionKind::SinglePointConstraint;
|
||||
}
|
||||
|
||||
NodeId SinglePointConstraint::nodeId() const noexcept {
|
||||
return node_id_;
|
||||
}
|
||||
|
||||
Dof SinglePointConstraint::dof() const noexcept {
|
||||
return dof_;
|
||||
}
|
||||
|
||||
double SinglePointConstraint::value() const noexcept {
|
||||
return value_;
|
||||
}
|
||||
|
||||
} // namespace fesa::boundary
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "fesa/core/Domain.hpp"
|
||||
|
||||
#include "fesa/boundary/SinglePointConstraint.hpp"
|
||||
#include "fesa/load/NodalLoad.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
@@ -243,6 +246,72 @@ void Domain::addStep(LinearStaticStepDefinition step) {
|
||||
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;
|
||||
@@ -378,4 +447,70 @@ 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
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "fesa/element/Mitc4Element.hpp"
|
||||
|
||||
#include "fesa/core/Node.hpp"
|
||||
|
||||
namespace fesa::element {
|
||||
|
||||
Mitc4Element::Mitc4Element(
|
||||
ElementId id,
|
||||
std::array<NodeId, 4> connectivity,
|
||||
PropertyId property_id)
|
||||
: id_(id), connectivity_(connectivity), property_id_(property_id) {}
|
||||
|
||||
ElementId Mitc4Element::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
ElementType Mitc4Element::type() const noexcept {
|
||||
return ElementType::Mitc4;
|
||||
}
|
||||
|
||||
std::size_t Mitc4Element::nodeCount() const noexcept {
|
||||
return connectivity_.size();
|
||||
}
|
||||
|
||||
std::size_t Mitc4Element::dofCount() const noexcept {
|
||||
return connectivity_.size() * fesa::core::Node::dofCount();
|
||||
}
|
||||
|
||||
const std::array<NodeId, 4>& Mitc4Element::connectivity() const noexcept {
|
||||
return connectivity_;
|
||||
}
|
||||
|
||||
PropertyId Mitc4Element::propertyId() const noexcept {
|
||||
return property_id_;
|
||||
}
|
||||
|
||||
} // namespace fesa::element
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "fesa/load/NodalLoad.hpp"
|
||||
|
||||
namespace fesa::load {
|
||||
|
||||
NodalLoad::NodalLoad(NodeId node_id, Dof dof, double value)
|
||||
: node_id_(node_id), dof_(dof), value_(value) {}
|
||||
|
||||
LoadKind NodalLoad::kind() const noexcept {
|
||||
return LoadKind::Nodal;
|
||||
}
|
||||
|
||||
NodeId NodalLoad::nodeId() const noexcept {
|
||||
return node_id_;
|
||||
}
|
||||
|
||||
Dof NodalLoad::dof() const noexcept {
|
||||
return dof_;
|
||||
}
|
||||
|
||||
double NodalLoad::value() const noexcept {
|
||||
return value_;
|
||||
}
|
||||
|
||||
} // namespace fesa::load
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "fesa/material/LinearElasticMaterial.hpp"
|
||||
|
||||
namespace fesa::material {
|
||||
|
||||
LinearElasticMaterial::LinearElasticMaterial(
|
||||
MaterialId id,
|
||||
double young_modulus,
|
||||
double poisson_ratio)
|
||||
: id_(id), young_modulus_(young_modulus), poisson_ratio_(poisson_ratio) {}
|
||||
|
||||
MaterialId LinearElasticMaterial::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
double LinearElasticMaterial::youngModulus() const noexcept {
|
||||
return young_modulus_;
|
||||
}
|
||||
|
||||
double LinearElasticMaterial::poissonRatio() const noexcept {
|
||||
return poisson_ratio_;
|
||||
}
|
||||
|
||||
} // namespace fesa::material
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "fesa/property/ShellProperty.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace fesa::property {
|
||||
|
||||
ShellProperty::ShellProperty(PropertyId id, MaterialId material_id, double thickness)
|
||||
: id_(id), material_id_(material_id), thickness_(thickness) {
|
||||
if (thickness <= 0.0) {
|
||||
throw std::invalid_argument("shell property thickness must be positive");
|
||||
}
|
||||
}
|
||||
|
||||
PropertyId ShellProperty::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
MaterialId ShellProperty::materialId() const noexcept {
|
||||
return material_id_;
|
||||
}
|
||||
|
||||
double ShellProperty::thickness() const noexcept {
|
||||
return thickness_;
|
||||
}
|
||||
|
||||
} // namespace fesa::property
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "fesa/boundary/BoundaryCondition.hpp"
|
||||
#include "fesa/boundary/SinglePointConstraint.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_boundary_base_tests() {
|
||||
std::unique_ptr<fesa::boundary::BoundaryCondition> owned =
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::UR3, 0.25);
|
||||
const fesa::boundary::BoundaryCondition& boundary = *owned;
|
||||
const auto& spc = static_cast<const fesa::boundary::SinglePointConstraint&>(boundary);
|
||||
|
||||
if (const int result = require(boundary.kind() == fesa::boundary::BoundaryConditionKind::SinglePointConstraint);
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(spc.nodeId() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(spc.dof() == fesa::core::Dof::UR3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(spc.value() == 0.25);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
int run_boundary_condition_tests();
|
||||
int run_domain_model_object_tests();
|
||||
int run_domain_storage_tests();
|
||||
int run_element_definition_tests();
|
||||
int run_load_definition_tests();
|
||||
@@ -27,6 +28,9 @@ int main() {
|
||||
if (const int result = run_boundary_condition_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_domain_model_object_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_load_definition_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
#include "fesa/boundary/SinglePointConstraint.hpp"
|
||||
#include "fesa/core/Domain.hpp"
|
||||
#include "fesa/element/Mitc4Element.hpp"
|
||||
#include "fesa/load/NodalLoad.hpp"
|
||||
#include "fesa/material/LinearElasticMaterial.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
template <typename Exception, typename Function>
|
||||
int require_throws(Function&& function) {
|
||||
try {
|
||||
function();
|
||||
} catch (const Exception&) {
|
||||
return 0;
|
||||
} catch (...) {
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
fesa::core::Domain populated_domain() {
|
||||
fesa::core::Domain domain;
|
||||
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||
domain.addNode(fesa::core::Node{2, 1.0, 0.0, 0.0});
|
||||
domain.addNode(fesa::core::Node{3, 1.0, 1.0, 0.0});
|
||||
domain.addNode(fesa::core::Node{4, 0.0, 1.0, 0.0});
|
||||
return domain;
|
||||
}
|
||||
|
||||
int domain_owns_element_and_material_objects() {
|
||||
fesa::core::Domain domain = populated_domain();
|
||||
|
||||
domain.addElementObject(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);
|
||||
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) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const fesa::material::Material* material = domain.findMaterialObject(700);
|
||||
if (const int result = require(material != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(domain.materialObject(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>(
|
||||
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>(
|
||||
100,
|
||||
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
|
||||
500));
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addMaterialObject(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) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.findMaterialObject(404) == nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require_throws<std::out_of_range>([&domain]() {
|
||||
(void)domain.elementObject(404);
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require_throws<std::out_of_range>([&domain]() {
|
||||
(void)domain.materialObject(404);
|
||||
});
|
||||
}
|
||||
|
||||
int domain_owns_load_and_boundary_objects_by_index() {
|
||||
fesa::core::Domain domain = populated_domain();
|
||||
|
||||
const std::size_t load_index = domain.addLoadObject(
|
||||
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
|
||||
const std::size_t boundary_index = domain.addBoundaryObject(
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
|
||||
|
||||
if (const int result = require(load_index == 0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(boundary_index == 0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.findLoadObject(load_index) != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.findBoundaryObject(boundary_index) != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.loadObject(load_index).kind() == fesa::load::LoadKind::Nodal); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(
|
||||
domain.boundaryObject(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));
|
||||
|
||||
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));
|
||||
});
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_domain_model_object_tests() {
|
||||
if (const int result = domain_owns_element_and_material_objects(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = duplicate_element_and_material_object_ids_throw(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = missing_model_object_direct_lookup_throws(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = domain_owns_load_and_boundary_objects_by_index(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = duplicate_load_and_boundary_keys_throw(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "fesa/core/Node.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
@@ -11,6 +14,9 @@ int require(bool condition) {
|
||||
int run_node_tests() {
|
||||
const fesa::core::Node node{42, 1.0, 2.0, 3.0};
|
||||
|
||||
if (const int result = require(fesa::core::Node::dofCount() == 6); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(node.id() == 42); result != 0) {
|
||||
return result;
|
||||
}
|
||||
@@ -32,6 +38,10 @@ int run_node_tests() {
|
||||
if (const int result = require(node.coordinates()[2] == 3.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require((std::is_same<decltype(node.coordinates()), const std::array<double, 3>&>::value));
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "fesa/element/Element.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
using fesa::element::ElementId;
|
||||
using fesa::element::ElementType;
|
||||
using fesa::element::NodeId;
|
||||
using fesa::element::PropertyId;
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
class TestElement final : public fesa::element::Element {
|
||||
public:
|
||||
ElementId id() const noexcept override { return 100; }
|
||||
ElementType type() const noexcept override { return ElementType::Mitc4; }
|
||||
std::size_t nodeCount() const noexcept override { return connectivity_.size(); }
|
||||
const std::array<NodeId, 4>& connectivity() const noexcept override { return connectivity_; }
|
||||
PropertyId propertyId() const noexcept override { return 500; }
|
||||
|
||||
private:
|
||||
std::array<NodeId, 4> connectivity_{1, 2, 3, 4};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_element_base_tests() {
|
||||
std::unique_ptr<fesa::element::Element> owned = std::make_unique<TestElement>();
|
||||
const fesa::element::Element& element = *owned;
|
||||
|
||||
if (const int result = require(element.id() == 100); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.type() == fesa::core::ElementType::Mitc4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.nodeCount() == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[0] == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[3] == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(element.propertyId() == 500);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "fesa/element/Mitc4Element.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_mitc4_element_model_tests() {
|
||||
const fesa::element::Mitc4Element mitc4{100, {1, 2, 3, 4}, 500};
|
||||
const fesa::element::Element& element = mitc4;
|
||||
|
||||
if (const int result = require(element.id() == 100); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.type() == fesa::core::ElementType::Mitc4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.nodeCount() == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(mitc4.dofCount() == 24); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[0] == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[1] == 2); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[2] == 3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[3] == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(element.propertyId() == 500);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "fesa/load/Load.hpp"
|
||||
#include "fesa/load/NodalLoad.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_load_base_tests() {
|
||||
std::unique_ptr<fesa::load::Load> owned =
|
||||
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0);
|
||||
const fesa::load::Load& load = *owned;
|
||||
const auto& nodal = static_cast<const fesa::load::NodalLoad&>(load);
|
||||
|
||||
if (const int result = require(load.kind() == fesa::load::LoadKind::Nodal); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(nodal.nodeId() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(nodal.dof() == fesa::core::Dof::U3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(nodal.value() == -100.0);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "fesa/material/LinearElasticMaterial.hpp"
|
||||
#include "fesa/material/Material.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_material_base_tests() {
|
||||
std::unique_ptr<fesa::material::Material> owned =
|
||||
std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3);
|
||||
const fesa::material::Material& material = *owned;
|
||||
const auto& elastic = static_cast<const fesa::material::LinearElasticMaterial&>(material);
|
||||
|
||||
if (const int result = require(material.id() == 700); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(elastic.youngModulus() == 210.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(elastic.poissonRatio() == 0.3);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
int run_boundary_base_tests();
|
||||
int run_element_base_tests();
|
||||
int run_load_base_tests();
|
||||
int run_material_base_tests();
|
||||
int run_mitc4_element_model_tests();
|
||||
int run_shell_property_tests();
|
||||
|
||||
int main() {
|
||||
if (const int result = run_boundary_base_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_element_base_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_mitc4_element_model_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_material_base_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_shell_property_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_load_base_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "fesa/property/ShellProperty.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
template <typename Exception, typename Function>
|
||||
int require_throws(Function&& function) {
|
||||
try {
|
||||
function();
|
||||
} catch (const Exception&) {
|
||||
return 0;
|
||||
} catch (...) {
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_shell_property_tests() {
|
||||
const fesa::property::ShellProperty property{500, 700, 0.01};
|
||||
|
||||
if (const int result = require(property.id() == 500); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(property.materialId() == 700); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(property.thickness() == 0.01); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require_throws<std::invalid_argument>([]() {
|
||||
(void)fesa::property::ShellProperty{501, 700, 0.0};
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require_throws<std::invalid_argument>([]() {
|
||||
(void)fesa::property::ShellProperty{502, 700, -0.01};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user