2 Commits

Author SHA1 Message Date
김경종 87529c811a feat: add analysis state and base 2026-06-09 15:12:41 +09:00
김경종 7ea08441ed feat: add property model foundation 2026-06-09 11:56:42 +09:00
38 changed files with 1820 additions and 25 deletions
+21
View File
@@ -8,8 +8,10 @@ set(CMAKE_CXX_EXTENSIONS OFF)
add_library(fesa_core STATIC add_library(fesa_core STATIC
src/boundary/SinglePointConstraint.cpp src/boundary/SinglePointConstraint.cpp
src/core/AnalysisState.cpp
src/core/Domain.cpp src/core/Domain.cpp
src/element/Mitc4Element.cpp src/element/Mitc4Element.cpp
src/io/Hdf5ResultWriter.cpp
src/load/NodalLoad.cpp src/load/NodalLoad.cpp
src/material/LinearElasticMaterial.cpp src/material/LinearElasticMaterial.cpp
src/property/ShellProperty.cpp src/property/ShellProperty.cpp
@@ -45,9 +47,28 @@ add_executable(fesa_model_object_tests
tests/load/load_base_test.cpp tests/load/load_base_test.cpp
tests/material/material_base_test.cpp tests/material/material_base_test.cpp
tests/model_object_main.cpp tests/model_object_main.cpp
tests/property/property_base_test.cpp
tests/property/shell_property_test.cpp tests/property/shell_property_test.cpp
) )
target_link_libraries(fesa_model_object_tests PRIVATE fesa_core) target_link_libraries(fesa_model_object_tests PRIVATE fesa_core)
add_test(NAME model-object.base COMMAND fesa_model_object_tests) add_test(NAME model-object.base COMMAND fesa_model_object_tests)
set_tests_properties(model-object.base PROPERTIES LABELS "model-object;core") set_tests_properties(model-object.base PROPERTIES LABELS "model-object;core")
add_executable(fesa_io_tests
tests/io/hdf5_result_writer_test.cpp
)
target_link_libraries(fesa_io_tests PRIVATE fesa_core)
add_test(NAME io.hdf5-result-writer COMMAND fesa_io_tests)
set_tests_properties(io.hdf5-result-writer PROPERTIES LABELS "io;hdf5")
add_executable(fesa_analysis_tests
tests/analysis/analysis_base_test.cpp
tests/analysis/analysis_test_main.cpp
tests/core/analysis_state_test.cpp
)
target_link_libraries(fesa_analysis_tests PRIVATE fesa_core)
add_test(NAME analysis.base COMMAND fesa_analysis_tests)
set_tests_properties(analysis.base PROPERTIES LABELS "analysis;core")
+15
View File
@@ -38,6 +38,13 @@
- Migrated `Domain` canonical storage away from parser-style definition DTOs to runtime model objects for elements, materials, shell properties, loads, and boundary conditions. - 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`. - 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. - Updated Domain storage tests so element sets and linear static step indices validate against runtime element, load, and boundary containers.
- Created `docs/implementation-plans/property-model-foundation-implementation-plan.md` and `phases/property-model-foundation/`.
- Added runtime `Property` base class and `PropertyKind`, made `ShellProperty` derive from `Property`, and migrated `Domain` property ownership to `std::unique_ptr<Property>`.
- Added a minimal `Hdf5ResultWriter` skeleton with path validation only; it does not link HDF5 or write files yet.
- Created `docs/implementation-plans/analysis-state-analysis-base-implementation-plan.md` and `phases/analysis-state-analysis-base/`.
- Added `AnalysisState` mutable vector storage for displacement, external force, internal force, residual, and reaction, plus resize, guarded setters, force clearing, and time/increment/iteration counters.
- Added `Analysis` base interface with const `Domain` input, mutable `AnalysisState`, `name()`, virtual deletion, and `run` delegation to derived analysis implementations.
- Added `fesa_analysis_tests` CTest target and analysis/core unit tests.
## In Progress ## In Progress
- Ready for the next upstream MITC4 stage: new solver feature requirements analysis for `mitc4-linear-static-shell`. - Ready for the next upstream MITC4 stage: new solver feature requirements analysis for `mitc4-linear-static-shell`.
@@ -51,6 +58,10 @@
6. Create `docs/implementation-plans/mitc4-linear-static-shell-implementation-plan.md`. 6. Create `docs/implementation-plans/mitc4-linear-static-shell-implementation-plan.md`.
## Last Validation ## Last Validation
- 2026-06-09: After analysis state and analysis base implementation, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "analysis|domain|model-object|io"` passed. 4 CTest executables ran successfully.
- 2026-06-09: After analysis state and analysis base implementation, `python -m unittest discover -s scripts -p "test_*.py"` passed. 89 tests ran successfully.
- 2026-06-09: After analysis state and analysis base 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 state and analysis base implementation, `git diff --check` passed with only Git line-ending normalization warnings.
- 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 -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, `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, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` passed. 2 CTest executables ran successfully.
@@ -59,6 +70,10 @@
- 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 -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, `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-09: After Domain runtime storage migration, `git diff --check` passed with only Git line-ending normalization warnings.
- 2026-06-09: After property model foundation implementation, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object|io"` passed. 3 CTest executables ran successfully.
- 2026-06-09: After property model foundation implementation, `python -m unittest discover -s scripts -p "test_*.py"` passed. 89 tests ran successfully.
- 2026-06-09: After property 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-09: After property model foundation implementation, `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 -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, `python scripts/validate_workspace.py` configured CMake with Visual Studio 17 2022 x64, built Debug targets, ran CTest, and passed.
- 2026-06-08: After Domain model foundation implementation, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` passed. 1 domain/core test executable ran successfully. - 2026-06-08: After Domain model foundation implementation, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` passed. 1 domain/core test executable ran successfully.
@@ -0,0 +1,65 @@
# Analysis State And Analysis Base Build/Test Report
## Metadata
- feature_id: analysis-state-analysis-base
- source_implementation_report: N/A
- source_implementation_plan: docs/implementation-plans/analysis-state-analysis-base-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 observed
- command_discovery_path: default CMake/MSVC x64 Debug through scripts/validate_workspace.py
## Command Log Summary
| order | command | exit_code | duration | stdout_stderr_tail |
| --- | --- | --- | --- | --- |
| 1 | `python scripts/validate_workspace.py` after RED tests | 1 | short | Expected compile failure: missing `fesa/analysis/Analysis.hpp` and `fesa/core/AnalysisState.hpp`. |
| 2 | `C:\Program Files\CMake\bin\ctest.exe --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis` | 0 | 0.39 sec | `analysis.base` passed. |
| 3 | `C:\Program Files\CMake\bin\ctest.exe --test-dir build/msvc-debug --output-on-failure -C Debug -R "analysis\|domain\|model-object\|io"` | 0 | 1.31 sec | 4 tests passed. |
| 4 | `python -m unittest discover -s scripts -p "test_*.py"` | 0 | 0.230 sec | 89 tests ran successfully. |
| 5 | `python scripts/validate_workspace.py` | 0 | short | Configure, build, and CTest passed; 4 CTest tests passed. |
| 6 | `git diff --check` | 0 | short | Passed with Git LF-to-CRLF normalization warnings only. |
## Validation Results
| validation_stage | result | evidence |
| --- | --- | --- |
| RED check | pass | Initial build failed because planned headers/classes were missing. |
| targeted analysis CTest | pass | `analysis.base` passed. |
| regression CTest | pass | `domain.bootstrap`, `model-object.base`, `io.hdf5-result-writer`, and `analysis.base` passed. |
| harness self-test | pass | 89 Python tests passed. |
| workspace validation | pass | Configure, Debug build, and all CTest tests passed. |
| whitespace check | pass | `git diff --check` returned exit code 0. |
## Failure Classification
- classification: N/A after implementation
- primary_failure: N/A
- first_failed_command: N/A
- evidence_tail: N/A
## Handoff Recommendation
| target_agent | reason | required_input |
| --- | --- | --- |
| AnalysisModel Implementation Agent | Analysis base and mutable state storage now exist. | `include/fesa/analysis/Analysis.hpp`, `include/fesa/core/AnalysisState.hpp`, `tests/analysis/analysis_base_test.cpp`, `tests/core/analysis_state_test.cpp` |
| DofManager Implementation Agent | State vectors are size-based but do not define equation numbering. | `AnalysisState` contract and architecture rules |
| LinearStaticAnalysis Implementation Agent | Base strategy entry point exists, but no solver algorithm is implemented. | future `AnalysisModel` and `DofManager` contracts |
## No-Change Assertion
- source_files_modified: true
- test_files_modified: true
- cmake_files_modified: true
- reference_artifacts_modified: false
- notes: No requirements, formulations, I/O contracts, reference artifacts, or tolerance policies were modified.
## Open Issues
- `AnalysisModel`, `DofManager`, and `LinearStaticAnalysis` remain separate downstream phases.
@@ -0,0 +1,188 @@
# Analysis State And Analysis Base Implementation Plan
## Metadata
- feature_id: analysis-state-analysis-base
- source_requirement: AGENTS.md; docs/PRD.md
- source_research: N/A for this architecture foundation slice
- source_formulation: N/A for this architecture foundation slice
- source_numerical_review: N/A for this architecture foundation slice
- source_io_definition: docs/ARCHITECTURE.md state ownership and analysis strategy rules
- source_reference_models: N/A for this architecture foundation slice
- status: ready-for-implementation
- owner_agent: implementation-planning-agent
- date: 2026-06-09
## Readiness Check
| input | required_status | observed_status | decision |
| --- | --- | --- | --- |
| architecture | Domain, Analysis, AnalysisState boundaries documented | documented in docs/ARCHITECTURE.md and ADR-010 | proceed |
| domain foundation | Domain runtime storage available | implemented in prior phases | proceed |
| formulation | not required for state/interface foundation | N/A | proceed |
| numerical_review | not required for state/interface foundation | N/A | proceed |
| io_definition | not required; no result output in this phase | N/A | proceed |
| reference_models | not required because this phase produces no solver result | N/A | proceed |
## Implementation Scope
- included_behavior: `AnalysisState` mutable vector state for displacement, external force, internal force, residual, and reaction.
- included_behavior: `AnalysisState` time, increment, and iteration counters.
- included_behavior: `Analysis` base interface for future analysis strategies.
- included_behavior: CMake/CTest registration for analysis-layer unit tests.
- excluded_behavior: `AnalysisModel`, `DofManager`, equation numbering, global assembly, boundary-condition elimination, linear solve, MITC4 numerical formulation, HDF5 output, and reference comparison.
- non_goals: numerical correctness claims, release readiness, reference-solver execution, and reference artifact generation.
## AnalysisState Contract
`AnalysisState` lives under `fesa::core` and owns mutable analysis quantities only. It does not reference or own `Domain`, model objects, equation maps, sparse matrices, solvers, result writers, or reference artifacts.
Required interface:
```cpp
namespace fesa::core {
class AnalysisState {
public:
AnalysisState();
explicit AnalysisState(std::size_t dof_count);
std::size_t dofCount() const noexcept;
void resize(std::size_t dof_count);
const std::vector<double>& displacement() const noexcept;
const std::vector<double>& externalForce() const noexcept;
const std::vector<double>& internalForce() const noexcept;
const std::vector<double>& residual() const noexcept;
const std::vector<double>& reaction() const noexcept;
void setDisplacement(std::vector<double> values);
void setExternalForce(std::vector<double> values);
void setInternalForce(std::vector<double> values);
void setResidual(std::vector<double> values);
void setReaction(std::vector<double> values);
void clearForces() noexcept;
double currentTime() const noexcept;
void setCurrentTime(double value) noexcept;
std::int64_t incrementIndex() const noexcept;
void setIncrementIndex(std::int64_t value) noexcept;
std::int64_t iterationIndex() const noexcept;
void setIterationIndex(std::int64_t value) noexcept;
};
} // namespace fesa::core
```
This phase intentionally defers velocity, acceleration, temperature, element state, and integration-point state until dynamic, thermal, nonlinear, or element-state phases define concrete contracts.
## Analysis Base Contract
`Analysis` lives under `fesa::analysis` and is the base strategy interface for future analysis algorithms. `Domain` is immutable input. `AnalysisState` is mutable output/state.
Required interface:
```cpp
namespace fesa::analysis {
class Analysis {
public:
virtual ~Analysis() = default;
virtual const char* name() const noexcept = 0;
void run(const fesa::core::Domain& domain, fesa::core::AnalysisState& state);
protected:
virtual void doRun(const fesa::core::Domain& domain, fesa::core::AnalysisState& state) = 0;
};
} // namespace fesa::analysis
```
`Analysis::run` is only an entry-point wrapper in this phase. It does not define assembly, solve, boundary-condition, or output hooks until `AnalysisModel` and `DofManager` exist.
## Work Breakdown
| task_id | order | purpose | upstream_trace | depends_on | expected_test_first |
| --- | --- | --- | --- | --- | --- |
| ASAB-001 | 1 | record state and base-analysis contract | ADR-010; docs/ARCHITECTURE.md | none | N/A |
| ASAB-002 | 2 | add `AnalysisState` zero-sized and sized state | ADR-010 AnalysisState | ASAB-001 | ASAB-TEST-001 |
| ASAB-003 | 3 | add `AnalysisState` mutation guards and counters | docs/ARCHITECTURE.md State Ownership | ASAB-002 | ASAB-TEST-002 |
| ASAB-004 | 4 | add `Analysis` base interface | docs/ARCHITECTURE.md Analysis strategy | ASAB-003 | ASAB-TEST-003 |
| ASAB-005 | 5 | register analysis CTest path | AGENTS.md C++ validation | ASAB-004 | ASAB-TEST-004 |
| ASAB-006 | 6 | record build/test evidence and handoff | docs/build-test-reports/README.md | ASAB-005 | ASAB-TEST-005 |
## TDD Test Plan
| test_id | order | test_type | red_condition | green_condition | linked_task | command |
| --- | --- | --- | --- | --- | --- | --- |
| ASAB-TEST-001 | 1 | unit | `AnalysisState` header/class missing | default and sized state tests pass | ASAB-002 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis` |
| ASAB-TEST-002 | 2 | unit | setter/guard methods missing | mutation guard and counter tests pass | ASAB-003 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis` |
| ASAB-TEST-003 | 3 | unit | `Analysis` header/base missing | derived recording analysis tests pass | ASAB-004 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis` |
| ASAB-TEST-004 | 4 | integration | analysis tests not registered or not built | `ctest -R analysis` runs analysis target | ASAB-005 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "analysis|domain|model-object|io"` |
| ASAB-TEST-005 | 5 | validation | report evidence missing | validation report records passing commands or classified failure | ASAB-006 | `python scripts/validate_workspace.py` |
## CMake/CTest Plan
- target_candidates: `fesa_core`, `fesa_analysis_tests`
- add_test_needs: register `analysis.base`
- labels: `analysis`, `core`
- msvc_config: Debug
- expected_feature_command: `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis`
- workspace_validation: `python scripts/validate_workspace.py`
## Candidate Files and Ownership
| file_candidate | purpose | owner_boundary | notes |
| --- | --- | --- | --- |
| `include/fesa/core/AnalysisState.hpp` | mutable analysis state public contract | core state | no Domain/model object ownership |
| `src/core/AnalysisState.cpp` | state vector/counter implementation | core state | no solver logic |
| `include/fesa/analysis/Analysis.hpp` | base analysis strategy interface | analysis | no LinearStaticAnalysis implementation |
| `tests/core/analysis_state_test.cpp` | state TDD coverage | tests | write before production changes |
| `tests/analysis/analysis_base_test.cpp` | analysis base TDD coverage | tests | write before production changes |
| `CMakeLists.txt` | source and CTest registration | build | MSVC Debug compatible |
## Acceptance Traceability Matrix
| requirement_id | task_id | test_id | reference_model_id | acceptance_criterion | status |
| --- | --- | --- | --- | --- | --- |
| ASAB-REQ-001 mutable state is outside Domain | ASAB-002 | ASAB-TEST-001 | N/A | AnalysisState tests pass and Domain remains unchanged | draft |
| ASAB-REQ-002 state vectors are size-consistent | ASAB-003 | ASAB-TEST-002 | N/A | size mismatch throws and failed setters do not mutate | draft |
| ASAB-REQ-003 Analysis takes const Domain and mutable state | ASAB-004 | ASAB-TEST-003 | N/A | recording analysis updates state through base API | draft |
| ASAB-REQ-004 C++ validation path includes analysis tests | ASAB-005 | ASAB-TEST-004 | N/A | `ctest -R analysis` runs successfully | draft |
## Validation Commands
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "analysis|domain|model-object|io"
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
git diff --check
```
## Risks and Downstream Handoff
### Implementation Agent
- Keep `AnalysisState` as storage only.
- Keep `Analysis` as a base interface only.
- Do not introduce `LinearStaticAnalysis` until `AnalysisModel` and `DofManager` contracts exist.
### 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 state headers, state sources, analysis header, analysis tests, or CMake registration.
- Upstream-contract failures include requests to add equation numbering, assembly, solver behavior, HDF5 output, or numerical MITC4 behavior 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
- `AnalysisModel`, `DofManager`, and `LinearStaticAnalysis` remain separate downstream phases.
@@ -0,0 +1,108 @@
# Property Model Foundation Implementation Plan
## Objective
Introduce a runtime `Property` base class for element property and section objects, then make `ShellProperty` and `Domain` use that base class consistently.
This phase keeps property objects as model data only. It does not implement shell section stiffness, constitutive matrices, assembly, solver logic, HDF5 output, or reference comparison.
## Phase Overview
1. `property-base-contract`
- Record the runtime property model contract and exclusions.
2. `property-base-interface`
- Add `PropertyKind` and abstract `Property`.
- Add unit tests for polymorphic use and virtual deletion.
3. `shell-property-polymorphism`
- Make `ShellProperty` derive from `Property`.
- Preserve id, material id, thickness, and positive-thickness validation.
4. `domain-property-ownership`
- Change `Domain` property storage to `std::unique_ptr<Property>`.
- Validate duplicate property ids and shell-property material references.
- Validate element property references against runtime property storage.
5. `validation-report-handoff`
- Run targeted CTest, harness self-tests, workspace validation, and whitespace checks.
- Update handoff documents.
## Runtime Property Contract
`Property` represents element property and section data that is owned by `Domain` and referenced by elements through `PropertyId`.
Required interface:
```cpp
namespace fesa::property {
enum class PropertyKind {
Shell
};
class Property {
public:
virtual ~Property() = default;
virtual PropertyId id() const noexcept = 0;
virtual PropertyKind kind() const noexcept = 0;
};
} // namespace fesa::property
```
`ShellProperty`:
- derives from `Property`;
- returns `PropertyKind::Shell`;
- stores `PropertyId`, `MaterialId`, and thickness;
- rejects non-positive thickness;
- does not compute shell stiffness in this phase.
## Domain Contract
`Domain` stores runtime property objects by ownership:
```cpp
std::unordered_map<PropertyId, std::unique_ptr<fesa::property::Property>>
```
`Domain` validates:
- null property pointer rejection;
- duplicate property id rejection;
- missing material id for `ShellProperty`;
- missing property id for elements.
## Hdf5ResultWriter Constraint
The adjacent `Hdf5ResultWriter` work requested for this slice is limited to a skeleton class only. It may expose construction and basic path access, but it must not link HDF5, create files, define result datasets, write metadata, or claim HDF5 output support.
## Non-Goals
- No MITC4 element formulation.
- No shell section stiffness.
- No material constitutive behavior.
- No DofManager work.
- No assembly, solver, sparse matrix, or reaction recovery.
- No HDF5 file writing or result schema implementation.
- No parser/factory implementation.
## Tests
Primary C++ tests:
- `/tests/property/property_base_test.cpp`
- `/tests/property/shell_property_test.cpp`
- `/tests/core/domain_storage_test.cpp`
- `/tests/core/domain_model_object_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
View File
@@ -0,0 +1,22 @@
#pragma once
#include "fesa/core/AnalysisState.hpp"
#include "fesa/core/Domain.hpp"
namespace fesa::analysis {
class Analysis {
public:
virtual ~Analysis() = default;
virtual const char* name() const noexcept = 0;
void run(const fesa::core::Domain& domain, fesa::core::AnalysisState& state) {
doRun(domain, state);
}
protected:
virtual void doRun(const fesa::core::Domain& domain, fesa::core::AnalysisState& state) = 0;
};
} // namespace fesa::analysis
+52
View File
@@ -0,0 +1,52 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <vector>
namespace fesa::core {
class AnalysisState {
public:
AnalysisState();
explicit AnalysisState(std::size_t dof_count);
std::size_t dofCount() const noexcept;
void resize(std::size_t dof_count);
const std::vector<double>& displacement() const noexcept;
const std::vector<double>& externalForce() const noexcept;
const std::vector<double>& internalForce() const noexcept;
const std::vector<double>& residual() const noexcept;
const std::vector<double>& reaction() const noexcept;
void setDisplacement(std::vector<double> values);
void setExternalForce(std::vector<double> values);
void setInternalForce(std::vector<double> values);
void setResidual(std::vector<double> values);
void setReaction(std::vector<double> values);
void clearForces() noexcept;
double currentTime() const noexcept;
void setCurrentTime(double value) noexcept;
std::int64_t incrementIndex() const noexcept;
void setIncrementIndex(std::int64_t value) noexcept;
std::int64_t iterationIndex() const noexcept;
void setIterationIndex(std::int64_t value) noexcept;
private:
void setVector(std::vector<double>& target, std::vector<double> values);
static void zero(std::vector<double>& values) noexcept;
std::size_t dof_count_;
std::vector<double> displacement_;
std::vector<double> external_force_;
std::vector<double> internal_force_;
std::vector<double> residual_;
std::vector<double> reaction_;
double current_time_;
std::int64_t increment_index_;
std::int64_t iteration_index_;
};
} // namespace fesa::core
+8 -2
View File
@@ -7,6 +7,7 @@
#include "fesa/element/Element.hpp" #include "fesa/element/Element.hpp"
#include "fesa/load/Load.hpp" #include "fesa/load/Load.hpp"
#include "fesa/material/Material.hpp" #include "fesa/material/Material.hpp"
#include "fesa/property/Property.hpp"
#include "fesa/property/ShellProperty.hpp" #include "fesa/property/ShellProperty.hpp"
#include <cstddef> #include <cstddef>
@@ -22,7 +23,8 @@ public:
void addNode(Node node); void addNode(Node node);
void addElement(std::unique_ptr<fesa::element::Element> element); void addElement(std::unique_ptr<fesa::element::Element> element);
void addMaterial(std::unique_ptr<fesa::material::Material> material); void addMaterial(std::unique_ptr<fesa::material::Material> material);
void addShellProperty(fesa::property::ShellProperty property); void addProperty(std::unique_ptr<fesa::property::Property> property);
void addShellProperty(std::unique_ptr<fesa::property::ShellProperty> property);
void addNodeSet(std::string name, std::vector<NodeId> node_ids); void addNodeSet(std::string name, std::vector<NodeId> node_ids);
void addElementSet(std::string name, std::vector<ElementId> element_ids); void addElementSet(std::string name, std::vector<ElementId> element_ids);
std::size_t addBoundaryCondition(std::unique_ptr<fesa::boundary::BoundaryCondition> boundary); std::size_t addBoundaryCondition(std::unique_ptr<fesa::boundary::BoundaryCondition> boundary);
@@ -41,6 +43,10 @@ public:
const fesa::material::Material& material(MaterialId id) const; const fesa::material::Material& material(MaterialId id) const;
std::size_t materialCount() const noexcept; std::size_t materialCount() const noexcept;
const fesa::property::Property* findProperty(PropertyId id) const noexcept;
const fesa::property::Property& property(PropertyId id) const;
std::size_t propertyCount() const noexcept;
const fesa::property::ShellProperty* findShellProperty(PropertyId id) const noexcept; const fesa::property::ShellProperty* findShellProperty(PropertyId id) const noexcept;
const fesa::property::ShellProperty& shellProperty(PropertyId id) const; const fesa::property::ShellProperty& shellProperty(PropertyId id) const;
std::size_t shellPropertyCount() const noexcept; std::size_t shellPropertyCount() const noexcept;
@@ -69,7 +75,7 @@ private:
std::unordered_map<NodeId, Node> nodes_; std::unordered_map<NodeId, Node> nodes_;
std::unordered_map<ElementId, std::unique_ptr<fesa::element::Element>> elements_; 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<MaterialId, std::unique_ptr<fesa::material::Material>> materials_;
std::unordered_map<PropertyId, fesa::property::ShellProperty> shell_properties_; std::unordered_map<PropertyId, std::unique_ptr<fesa::property::Property>> properties_;
std::unordered_map<std::string, std::vector<NodeId>> node_sets_; std::unordered_map<std::string, std::vector<NodeId>> node_sets_;
std::unordered_map<std::string, std::vector<ElementId>> element_sets_; std::unordered_map<std::string, std::vector<ElementId>> element_sets_;
std::vector<std::unique_ptr<fesa::boundary::BoundaryCondition>> boundary_conditions_; std::vector<std::unique_ptr<fesa::boundary::BoundaryCondition>> boundary_conditions_;
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <string>
namespace fesa::io {
class Hdf5ResultWriter {
public:
explicit Hdf5ResultWriter(std::string file_path);
const std::string& filePath() const noexcept;
private:
std::string file_path_;
};
} // namespace fesa::io
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "fesa/core/ModelTypes.hpp"
namespace fesa::property {
using fesa::core::PropertyId;
enum class PropertyKind {
Shell
};
class Property {
public:
virtual ~Property() = default;
virtual PropertyId id() const noexcept = 0;
virtual PropertyKind kind() const noexcept = 0;
};
} // namespace fesa::property
+4 -2
View File
@@ -1,17 +1,19 @@
#pragma once #pragma once
#include "fesa/core/ModelTypes.hpp" #include "fesa/core/ModelTypes.hpp"
#include "fesa/property/Property.hpp"
namespace fesa::property { namespace fesa::property {
using fesa::core::MaterialId; using fesa::core::MaterialId;
using fesa::core::PropertyId; using fesa::core::PropertyId;
class ShellProperty { class ShellProperty final : public Property {
public: public:
ShellProperty(PropertyId id, MaterialId material_id, double thickness); ShellProperty(PropertyId id, MaterialId material_id, double thickness);
PropertyId id() const noexcept; PropertyId id() const noexcept override;
PropertyKind kind() const noexcept override;
MaterialId materialId() const noexcept; MaterialId materialId() const noexcept;
double thickness() const noexcept; double thickness() const noexcept;
@@ -0,0 +1,42 @@
{
"project": "FESA Structural Solver",
"phase": "analysis-state-analysis-base",
"steps": [
{
"step": 0,
"name": "analysis-contract-plan",
"status": "completed",
"summary": "Created the analysis state and analysis base implementation plan with scope, contracts, TDD tasks, and exclusions."
},
{
"step": 1,
"name": "analysis-state-core",
"status": "completed",
"summary": "Added AnalysisState with zero-sized and sized vector storage for displacement, forces, residual, and reaction."
},
{
"step": 2,
"name": "analysis-state-guards",
"status": "completed",
"summary": "Added AnalysisState setters, size guards, resize reset, clearForces, and time/increment/iteration counters."
},
{
"step": 3,
"name": "analysis-base-interface",
"status": "completed",
"summary": "Added the Analysis base interface with const Domain input, mutable AnalysisState, name contract, virtual deletion, and run delegation."
},
{
"step": 4,
"name": "cmake-analysis-tests",
"status": "completed",
"summary": "Registered fesa_analysis_tests and confirmed analysis, domain, model-object, and io CTest targets pass together."
},
{
"step": 5,
"name": "validation-report-handoff",
"status": "completed",
"summary": "Recorded build/test evidence, updated project progress, and marked the phase completed."
}
]
}
@@ -0,0 +1,62 @@
# Step 0: analysis-contract-plan
## 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/analysis-model-objects-implementation-plan.md`
- `/docs/implementation-plans/property-model-foundation-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/include/fesa/core/ModelTypes.hpp`
- `/include/fesa/core/StepDefinition.hpp`
- `/CMakeLists.txt`
## Task
Create `/docs/implementation-plans/analysis-state-analysis-base-implementation-plan.md`.
The document must define the implementation contract for:
- `fesa::core::AnalysisState` as mutable analysis state storage.
- `fesa::analysis::Analysis` as the base analysis strategy interface.
- A dedicated CTest path for analysis-layer tests.
State explicitly that this phase does not implement:
- `AnalysisModel`
- `DofManager`
- MITC4 stiffness, stress, resultants, or force recovery
- global assembly
- constrained DOF elimination
- MKL/PARDISO solve
- HDF5 result writing beyond existing skeletons
- reference comparison
- Abaqus/Nastran/reference-solver execution
## 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-state-analysis-base/index.json` step 0 with `completed`, `error`, or `blocked`.
## Do Not
- Do not change C++ code in this step.
- Do not change requirements, formulations, I/O contracts, reference artifacts, or tolerance policy.
@@ -0,0 +1,69 @@
# Step 1: analysis-state-core
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/PLAN.md`
- `/docs/PROGRESS.md`
- `/docs/WORKNOTE.md`
- `/docs/AGENT_RULES.md`
- `/docs/ADR.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/analysis-state-analysis-base-implementation-plan.md`
- `/include/fesa/core/ModelTypes.hpp`
- `/include/fesa/core/Domain.hpp`
- `/CMakeLists.txt`
## Task
Add `fesa::core::AnalysisState`.
Candidate files:
- Create `/include/fesa/core/AnalysisState.hpp`
- Create `/src/core/AnalysisState.cpp`
- Create `/tests/core/analysis_state_test.cpp`
- Modify `/CMakeLists.txt`
Required initial contract:
- `AnalysisState()` starts with zero DOFs and empty vectors.
- `explicit AnalysisState(std::size_t dof_count)` allocates zero-filled vectors.
- `dofCount()` returns the current vector size.
- `displacement()`, `externalForce()`, `internalForce()`, `residual()`, and `reaction()` return const vector references.
- All state vectors have the same size as `dofCount()`.
- State is mutable analysis data only. Do not store node ids, element ids, equation ids, sparse data, solver handles, writer handles, or references to `Domain`.
## Tests To Write First
Write tests in `/tests/core/analysis_state_test.cpp` before creating production code:
- default state has zero DOFs and all vectors empty.
- sized state creates zero-filled displacement, external force, internal force, residual, and reaction vectors.
- `dofCount()` matches vector sizes.
Run the targeted test and confirm it fails because `AnalysisState` does not exist:
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis
```
Then implement the minimum code needed to pass.
## Acceptance Criteria
Run:
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis
python scripts/validate_workspace.py
```
Update `/phases/analysis-state-analysis-base/index.json` step 1 with `completed`, `error`, or `blocked`.
## Do Not
- Do not implement `AnalysisModel`, `DofManager`, assembly, solver, or result output.
- Do not add mutable solver state to `Domain`, `Node`, `Element`, `Material`, `Property`, `Load`, or `BoundaryCondition`.
@@ -0,0 +1,65 @@
# Step 2: analysis-state-guards
## 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/analysis-state-analysis-base-implementation-plan.md`
- `/include/fesa/core/AnalysisState.hpp`
- `/src/core/AnalysisState.cpp`
- `/tests/core/analysis_state_test.cpp`
- `/CMakeLists.txt`
## Task
Extend `AnalysisState` with mutation and guard behavior.
Required contract:
- `resize(std::size_t dof_count)` resets every vector to the new size and fills with zero.
- `setDisplacement(std::vector<double>)`, `setExternalForce(std::vector<double>)`, `setInternalForce(std::vector<double>)`, `setResidual(std::vector<double>)`, and `setReaction(std::vector<double>)` replace one vector.
- setter inputs must match `dofCount()`.
- size mismatch throws `std::invalid_argument`.
- a failed setter must not mutate the existing vector.
- `clearForces()` zeros external force, internal force, residual, and reaction while leaving displacement unchanged.
- `currentTime()`, `incrementIndex()`, and `iterationIndex()` accessors exist with setters.
## Tests To Write First
Add tests in `/tests/core/analysis_state_test.cpp` before production changes:
- setters replace vectors when sizes match.
- mismatched setter input throws and preserves old values.
- resize resets all vectors to zero.
- clearForces preserves displacement and zeros force-like vectors.
- time, increment, and iteration counters can be updated.
Run the targeted test and confirm it fails before implementation:
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis
```
Then implement the minimum code needed to pass.
## Acceptance Criteria
Run:
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis
python scripts/validate_workspace.py
```
Update `/phases/analysis-state-analysis-base/index.json` step 2 with `completed`, `error`, or `blocked`.
## Do Not
- Do not add dynamics-only velocity or acceleration vectors in this phase.
- Do not add thermal temperature fields in this phase.
- Do not add equation numbering or DOF mapping responsibilities.
@@ -0,0 +1,69 @@
# Step 3: analysis-base-interface
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/PLAN.md`
- `/docs/PROGRESS.md`
- `/docs/WORKNOTE.md`
- `/docs/AGENT_RULES.md`
- `/docs/ADR.md`
- `/docs/ARCHITECTURE.md`
- `/docs/implementation-plans/analysis-state-analysis-base-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/include/fesa/core/AnalysisState.hpp`
- `/CMakeLists.txt`
## Task
Add `fesa::analysis::Analysis` as a minimal base interface for future analysis strategies.
Candidate files:
- Create `/include/fesa/analysis/Analysis.hpp`
- Create `/tests/analysis/analysis_base_test.cpp`
- Modify `/CMakeLists.txt`
Required contract:
- `Analysis` has a virtual destructor.
- `Analysis` exposes `const char* name() const noexcept`.
- `Analysis` exposes `void run(const fesa::core::Domain& domain, fesa::core::AnalysisState& state)`.
- `run` delegates to a protected pure virtual `doRun(const fesa::core::Domain&, fesa::core::AnalysisState&)`.
- `Domain` is passed as const; `AnalysisState` is mutable.
- The base class must not own `Domain`, `AnalysisState`, solver objects, result writers, or reference artifacts.
## Tests To Write First
Write tests in `/tests/analysis/analysis_base_test.cpp` before production changes:
- a derived recording analysis can be used through `Analysis&`.
- `run` forwards the const `Domain` and mutable `AnalysisState` to the derived implementation.
- derived implementation can update `AnalysisState` without mutating `Domain`.
- `Analysis` can be deleted through a base pointer.
Run the targeted test and confirm it fails before implementation:
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis
```
Then implement the minimum code needed to pass.
## Acceptance Criteria
Run:
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis
python scripts/validate_workspace.py
```
Update `/phases/analysis-state-analysis-base/index.json` step 3 with `completed`, `error`, or `blocked`.
## Do Not
- Do not implement `LinearStaticAnalysis`.
- Do not add template-method hook lists for assembly, solve, boundary conditions, or output until `AnalysisModel` and `DofManager` exist.
@@ -0,0 +1,46 @@
# Step 4: cmake-analysis-tests
## 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/analysis-state-analysis-base-implementation-plan.md`
- `/CMakeLists.txt`
- `/tests/core/analysis_state_test.cpp`
- `/tests/analysis/analysis_base_test.cpp`
## Task
Make the analysis tests part of the normal MSVC CMake/CTest path.
Required contract:
- `fesa_core` includes `src/core/AnalysisState.cpp`.
- `fesa_analysis_tests` builds both analysis-state and analysis-base tests.
- CTest registers `analysis.base` or equivalent with labels `analysis;core`.
- Existing `domain`, `model-object`, and `io` tests remain registered and unchanged except for required shared library source additions.
## Tests To Write First
No new behavior tests are required in this step if steps 1 through 3 already wrote them. If CMake registration is missing, the RED condition is that `ctest -R analysis` finds no registered analysis test or build fails because sources are missing.
## Acceptance Criteria
Run:
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "analysis|domain|model-object|io"
python scripts/validate_workspace.py
```
Update `/phases/analysis-state-analysis-base/index.json` step 4 with `completed`, `error`, or `blocked`.
## Do Not
- Do not modify unrelated tests.
- Do not change dependency discovery or introduce MKL, TBB, or HDF5 linkage for this phase.
@@ -0,0 +1,48 @@
# 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/analysis-state-analysis-base-implementation-plan.md`
- `/docs/build-test-reports/README.md`
- `/phases/analysis-state-analysis-base/index.json`
## Task
Run final validation, record evidence, and update handoff files.
Required output:
- Create `/docs/build-test-reports/analysis-state-analysis-base-build-test.md`.
- Update `/docs/PROGRESS.md` with completed work and validation evidence.
- Update `/phases/analysis-state-analysis-base/index.json` step 5.
- Update `/phases/index.json` phase status to `completed` only if validation passes.
## Tests To Write First
No new tests are required in this reporting step.
## Acceptance Criteria
Run:
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "analysis|domain|model-object|io"
python -m unittest discover -s scripts -p "test_*.py"
python scripts/validate_workspace.py
git diff --check
```
Record command outcomes in `/docs/build-test-reports/analysis-state-analysis-base-build-test.md`.
## Do Not
- Do not claim numerical correctness.
- Do not claim release readiness.
- Do not run Abaqus, Nastran, or any reference solver.
+8
View File
@@ -11,6 +11,14 @@
{ {
"dir": "domain-runtime-storage", "dir": "domain-runtime-storage",
"status": "completed" "status": "completed"
},
{
"dir": "property-model-foundation",
"status": "completed"
},
{
"dir": "analysis-state-analysis-base",
"status": "completed"
} }
] ]
} }
@@ -0,0 +1,36 @@
{
"project": "FESA Structural Solver",
"phase": "property-model-foundation",
"steps": [
{
"step": 0,
"name": "property-base-contract",
"status": "completed",
"summary": "Created the property model foundation implementation plan with runtime Property ownership rules and HDF5 skeleton limits."
},
{
"step": 1,
"name": "property-base-interface",
"status": "completed",
"summary": "Added PropertyKind and abstract Property base with polymorphic ownership tests."
},
{
"step": 2,
"name": "shell-property-polymorphism",
"status": "completed",
"summary": "Made ShellProperty derive from Property while preserving material id and positive-thickness validation."
},
{
"step": 3,
"name": "domain-property-ownership",
"status": "completed",
"summary": "Migrated Domain property storage to std::unique_ptr<Property> with shell-property compatibility accessors and cross-reference validation."
},
{
"step": 4,
"name": "validation-report-handoff",
"status": "completed",
"summary": "Validated targeted CTest, workspace validation, script self-tests, and whitespace checks; updated project progress."
}
]
}
+54
View File
@@ -0,0 +1,54 @@
# Step 0: property-base-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-runtime-storage-implementation-plan.md`
- `/include/fesa/property/ShellProperty.hpp`
- `/include/fesa/core/Domain.hpp`
- `/src/core/Domain.cpp`
## Task
Create `/docs/implementation-plans/property-model-foundation-implementation-plan.md`.
The plan must define the runtime property model contract:
- `Property` is the base class for element properties and sections.
- `PropertyKind` identifies concrete property families; phase 1 supports only `Shell`.
- `ShellProperty` derives from `Property`.
- `Domain` owns property objects through RAII.
- `Domain` validates duplicate property ids and missing material ids.
- `Element::propertyId()` remains an id reference; elements do not own property objects.
Keep out of scope:
- shell section stiffness;
- material constitutive matrices;
- shear correction;
- element formulation;
- assembly, solver, HDF5 output, and reference comparison.
## 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 change MITC4 formulation or tolerance policy.
2. Confirm the plan keeps solver state out of `Domain`.
3. Update `phases/property-model-foundation/index.json` for this step result.
+52
View File
@@ -0,0 +1,52 @@
# Step 1: property-base-interface
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/implementation-plans/property-model-foundation-implementation-plan.md`
- `/include/fesa/core/ModelTypes.hpp`
- `/include/fesa/property/ShellProperty.hpp`
- `/tests/property/shell_property_test.cpp`
- `/CMakeLists.txt`
## Task
Add the runtime property base interface.
Required API:
- File: `/include/fesa/property/Property.hpp`
- Namespace: `fesa::property`
- `enum class PropertyKind { Shell };`
- `class Property`
- virtual destructor;
- `virtual PropertyId id() const noexcept = 0;`
- `virtual PropertyKind kind() const noexcept = 0;`
Rules:
- Use `fesa::core::PropertyId`.
- Do not add section stiffness or material behavior.
- Keep this header independent from HDF5, MKL, TBB, and parser headers.
## Tests To Write First
- Add `/tests/property/property_base_test.cpp`.
- Test that a small derived class can be used through `const Property&`.
- Test virtual deletion through `std::unique_ptr<Property>`.
## Acceptance Criteria
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
python scripts/validate_workspace.py
```
## Verification Notes
1. Run targeted CTest before implementation and confirm the expected compile failure.
2. Keep all code C++17/MSVC-compatible.
3. Update `phases/property-model-foundation/index.json` for this step result.
+49
View File
@@ -0,0 +1,49 @@
# Step 2: shell-property-polymorphism
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/implementation-plans/property-model-foundation-implementation-plan.md`
- `/include/fesa/property/Property.hpp`
- `/include/fesa/property/ShellProperty.hpp`
- `/src/property/ShellProperty.cpp`
- `/tests/property/shell_property_test.cpp`
## Task
Make `ShellProperty` derive from `Property`.
Required behavior:
- `ShellProperty::id()` overrides `Property::id()`.
- `ShellProperty::kind()` returns `PropertyKind::Shell`.
- `ShellProperty` keeps `materialId()` and `thickness()` accessors.
- Constructor still rejects `thickness <= 0.0`.
Rules:
- Do not rename `ShellProperty` in this phase.
- Do not add shell section stiffness.
- Do not add material lookup inside `ShellProperty`; Domain validates cross references.
## Tests To Write First
- Extend `/tests/property/shell_property_test.cpp`.
- Test `const Property& base = shell_property` exposes id and kind.
- Preserve positive-thickness validation tests.
## Acceptance Criteria
```powershell
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
python scripts/validate_workspace.py
```
## Verification Notes
1. Run targeted CTest before production edits and confirm failure.
2. Keep `ShellProperty` free of solver state.
3. Update `phases/property-model-foundation/index.json` for this step result.
+60
View File
@@ -0,0 +1,60 @@
# Step 3: domain-property-ownership
## Read First
Read these files before editing:
- `/AGENTS.md`
- `/docs/AGENT_RULES.md`
- `/docs/implementation-plans/property-model-foundation-implementation-plan.md`
- `/include/fesa/core/Domain.hpp`
- `/src/core/Domain.cpp`
- `/include/fesa/property/Property.hpp`
- `/include/fesa/property/ShellProperty.hpp`
- `/tests/core/domain_storage_test.cpp`
- `/tests/core/domain_model_object_test.cpp`
## Task
Make `Domain` own runtime property objects through the `Property` base class.
Required API shape:
- `void Domain::addProperty(std::unique_ptr<fesa::property::Property> property);`
- `const fesa::property::Property* Domain::findProperty(PropertyId id) const noexcept;`
- `const fesa::property::Property& Domain::property(PropertyId id) const;`
- `std::size_t Domain::propertyCount() const noexcept;`
Compatibility helpers may remain if useful:
- `addShellProperty(std::unique_ptr<fesa::property::ShellProperty>)`
- `findShellProperty(PropertyId)`
- `shellProperty(PropertyId)`
- `shellPropertyCount()`
Rules:
- Property storage must be `std::unique_ptr<Property>`.
- Reject null property pointers.
- Reject duplicate property ids.
- For `ShellProperty`, reject missing material id.
- `Domain::addElement` must validate `Element::propertyId()` using runtime property storage.
## Tests To Write First
- Rewrite relevant property assertions in `/tests/core/domain_storage_test.cpp` to use runtime property ownership.
- Add a test that `const Domain::property(id)` returns `const Property&`.
- Preserve direct `ShellProperty` lookup for now if compatibility helpers remain.
## 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. Do not add solver vectors, equation ids, or integration-point state to `Domain`.
3. Update `phases/property-model-foundation/index.json` for this step result.
+42
View File
@@ -0,0 +1,42 @@
# Step 4: 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/property-model-foundation-implementation-plan.md`
- `/phases/property-model-foundation/index.json`
## Task
Record validation evidence and handoff notes after the property model foundation implementation.
Required updates:
- Update `/docs/PROGRESS.md` with completed property base and Domain property ownership work.
- Update `/docs/PLAN.md` only if sequencing or acceptance criteria changed.
- Update `/docs/WORKNOTE.md` only if there were failures, repeated mistakes, or environment traps.
- Mark `/phases/property-model-foundation/index.json` steps and `/phases/index.json` 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 MITC4 formulation correctness.
2. Do not claim HDF5 result output is implemented.
3. Keep validation evidence concrete: command, result, and scope.
+115
View File
@@ -0,0 +1,115 @@
#include "fesa/core/AnalysisState.hpp"
#include <algorithm>
#include <stdexcept>
#include <utility>
namespace fesa::core {
AnalysisState::AnalysisState()
: AnalysisState(0) {}
AnalysisState::AnalysisState(std::size_t dof_count)
: dof_count_(0),
current_time_(0.0),
increment_index_(0),
iteration_index_(0) {
resize(dof_count);
}
std::size_t AnalysisState::dofCount() const noexcept {
return dof_count_;
}
void AnalysisState::resize(std::size_t dof_count) {
dof_count_ = dof_count;
displacement_.assign(dof_count_, 0.0);
external_force_.assign(dof_count_, 0.0);
internal_force_.assign(dof_count_, 0.0);
residual_.assign(dof_count_, 0.0);
reaction_.assign(dof_count_, 0.0);
}
const std::vector<double>& AnalysisState::displacement() const noexcept {
return displacement_;
}
const std::vector<double>& AnalysisState::externalForce() const noexcept {
return external_force_;
}
const std::vector<double>& AnalysisState::internalForce() const noexcept {
return internal_force_;
}
const std::vector<double>& AnalysisState::residual() const noexcept {
return residual_;
}
const std::vector<double>& AnalysisState::reaction() const noexcept {
return reaction_;
}
void AnalysisState::setDisplacement(std::vector<double> values) {
setVector(displacement_, std::move(values));
}
void AnalysisState::setExternalForce(std::vector<double> values) {
setVector(external_force_, std::move(values));
}
void AnalysisState::setInternalForce(std::vector<double> values) {
setVector(internal_force_, std::move(values));
}
void AnalysisState::setResidual(std::vector<double> values) {
setVector(residual_, std::move(values));
}
void AnalysisState::setReaction(std::vector<double> values) {
setVector(reaction_, std::move(values));
}
void AnalysisState::clearForces() noexcept {
zero(external_force_);
zero(internal_force_);
zero(residual_);
zero(reaction_);
}
double AnalysisState::currentTime() const noexcept {
return current_time_;
}
void AnalysisState::setCurrentTime(double value) noexcept {
current_time_ = value;
}
std::int64_t AnalysisState::incrementIndex() const noexcept {
return increment_index_;
}
void AnalysisState::setIncrementIndex(std::int64_t value) noexcept {
increment_index_ = value;
}
std::int64_t AnalysisState::iterationIndex() const noexcept {
return iteration_index_;
}
void AnalysisState::setIterationIndex(std::int64_t value) noexcept {
iteration_index_ = value;
}
void AnalysisState::setVector(std::vector<double>& target, std::vector<double> values) {
if (values.size() != dof_count_) {
throw std::invalid_argument("analysis state vector size mismatch");
}
target = std::move(values);
}
void AnalysisState::zero(std::vector<double>& values) noexcept {
std::fill(values.begin(), values.end(), 0.0);
}
} // namespace fesa::core
+41 -12
View File
@@ -172,8 +172,8 @@ void Domain::addElement(std::unique_ptr<fesa::element::Element> element) {
throw std::invalid_argument("element references missing node id"); throw std::invalid_argument("element references missing node id");
} }
} }
if (findShellProperty(element->propertyId()) == nullptr) { if (findProperty(element->propertyId()) == nullptr) {
throw std::invalid_argument("element references missing shell property id"); throw std::invalid_argument("element references missing property id");
} }
elements_.emplace(id, std::move(element)); elements_.emplace(id, std::move(element));
} }
@@ -189,15 +189,28 @@ void Domain::addMaterial(std::unique_ptr<fesa::material::Material> material) {
} }
} }
void Domain::addShellProperty(fesa::property::ShellProperty property) { void Domain::addProperty(std::unique_ptr<fesa::property::Property> property) {
const PropertyId id = property.id(); if (!property) {
if (shell_properties_.find(id) != shell_properties_.end()) { throw std::invalid_argument("property is null");
throw std::invalid_argument("duplicate shell property id");
} }
if (findMaterial(property.materialId()) == nullptr) { const PropertyId id = property->id();
throw std::invalid_argument("shell property references missing material id"); if (properties_.find(id) != properties_.end()) {
throw std::invalid_argument("duplicate property id");
} }
shell_properties_.emplace(id, std::move(property)); if (property->kind() == fesa::property::PropertyKind::Shell) {
const auto* shell_property = dynamic_cast<const fesa::property::ShellProperty*>(property.get());
if (shell_property == nullptr) {
throw std::invalid_argument("shell property kind does not match shell property type");
}
if (findMaterial(shell_property->materialId()) == nullptr) {
throw std::invalid_argument("shell property references missing material id");
}
}
properties_.emplace(id, std::move(property));
}
void Domain::addShellProperty(std::unique_ptr<fesa::property::ShellProperty> property) {
addProperty(std::move(property));
} }
void Domain::addNodeSet(std::string name, std::vector<NodeId> node_ids) { void Domain::addNodeSet(std::string name, std::vector<NodeId> node_ids) {
@@ -338,9 +351,25 @@ std::size_t Domain::materialCount() const noexcept {
return materials_.size(); return materials_.size();
} }
const fesa::property::Property* Domain::findProperty(PropertyId id) const noexcept {
const auto it = properties_.find(id);
return it == properties_.end() ? nullptr : it->second.get();
}
const fesa::property::Property& Domain::property(PropertyId id) const {
const fesa::property::Property* found = findProperty(id);
if (found == nullptr) {
throw std::out_of_range("property id not found");
}
return *found;
}
std::size_t Domain::propertyCount() const noexcept {
return properties_.size();
}
const fesa::property::ShellProperty* Domain::findShellProperty(PropertyId id) const noexcept { const fesa::property::ShellProperty* Domain::findShellProperty(PropertyId id) const noexcept {
const auto it = shell_properties_.find(id); return dynamic_cast<const fesa::property::ShellProperty*>(findProperty(id));
return it == shell_properties_.end() ? nullptr : &it->second;
} }
const fesa::property::ShellProperty& Domain::shellProperty(PropertyId id) const { const fesa::property::ShellProperty& Domain::shellProperty(PropertyId id) const {
@@ -352,7 +381,7 @@ const fesa::property::ShellProperty& Domain::shellProperty(PropertyId id) const
} }
std::size_t Domain::shellPropertyCount() const noexcept { std::size_t Domain::shellPropertyCount() const noexcept {
return shell_properties_.size(); return properties_.size();
} }
const std::vector<NodeId>* Domain::findNodeSet(const std::string& name) const noexcept { const std::vector<NodeId>* Domain::findNodeSet(const std::string& name) const noexcept {
+19
View File
@@ -0,0 +1,19 @@
#include "fesa/io/Hdf5ResultWriter.hpp"
#include <stdexcept>
#include <utility>
namespace fesa::io {
Hdf5ResultWriter::Hdf5ResultWriter(std::string file_path)
: file_path_(std::move(file_path)) {
if (file_path_.empty()) {
throw std::invalid_argument("HDF5 result writer path must not be empty");
}
}
const std::string& Hdf5ResultWriter::filePath() const noexcept {
return file_path_;
}
} // namespace fesa::io
+4
View File
@@ -15,6 +15,10 @@ PropertyId ShellProperty::id() const noexcept {
return id_; return id_;
} }
PropertyKind ShellProperty::kind() const noexcept {
return PropertyKind::Shell;
}
MaterialId ShellProperty::materialId() const noexcept { MaterialId ShellProperty::materialId() const noexcept {
return material_id_; return material_id_;
} }
+78
View File
@@ -0,0 +1,78 @@
#include "fesa/analysis/Analysis.hpp"
#include "fesa/core/AnalysisState.hpp"
#include "fesa/core/Domain.hpp"
#include <memory>
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
class RecordingAnalysis final : public fesa::analysis::Analysis {
public:
const char* name() const noexcept override {
return "recording";
}
bool received_domain = false;
bool ran = false;
protected:
void doRun(const fesa::core::Domain& domain, fesa::core::AnalysisState& state) override {
received_domain = domain.nodeCount() == 1;
state.setCurrentTime(2.0);
ran = true;
}
};
int derived_analysis_runs_through_base_api() {
fesa::core::Domain domain;
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
fesa::core::AnalysisState state;
RecordingAnalysis analysis;
fesa::analysis::Analysis& base = analysis;
base.run(domain, state);
if (const int result = require(analysis.ran); result != 0) {
return result;
}
if (const int result = require(analysis.received_domain); result != 0) {
return result;
}
if (const int result = require(domain.nodeCount() == 1); result != 0) {
return result;
}
return require(state.currentTime() == 2.0);
}
int analysis_exposes_name_through_base_api() {
RecordingAnalysis analysis;
const fesa::analysis::Analysis& base = analysis;
return require(base.name()[0] == 'r');
}
int analysis_can_be_deleted_through_base_pointer() {
std::unique_ptr<fesa::analysis::Analysis> analysis = std::make_unique<RecordingAnalysis>();
return require(analysis->name()[0] == 'r');
}
} // namespace
int run_analysis_base_tests() {
if (const int result = derived_analysis_runs_through_base_api(); result != 0) {
return result;
}
if (const int result = analysis_exposes_name_through_base_api(); result != 0) {
return result;
}
if (const int result = analysis_can_be_deleted_through_base_pointer(); result != 0) {
return result;
}
return 0;
}
+12
View File
@@ -0,0 +1,12 @@
int run_analysis_base_tests();
int run_analysis_state_tests();
int main() {
if (const int result = run_analysis_state_tests(); result != 0) {
return result;
}
if (const int result = run_analysis_base_tests(); result != 0) {
return result;
}
return 0;
}
+203
View File
@@ -0,0 +1,203 @@
#include "fesa/core/AnalysisState.hpp"
#include <stdexcept>
#include <vector>
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;
}
int default_state_has_no_dofs() {
const fesa::core::AnalysisState state;
if (const int result = require(state.dofCount() == 0); result != 0) {
return result;
}
if (const int result = require(state.displacement().empty()); result != 0) {
return result;
}
if (const int result = require(state.externalForce().empty()); result != 0) {
return result;
}
if (const int result = require(state.internalForce().empty()); result != 0) {
return result;
}
if (const int result = require(state.residual().empty()); result != 0) {
return result;
}
return require(state.reaction().empty());
}
int sized_state_initializes_zero_vectors() {
const fesa::core::AnalysisState state{3};
if (const int result = require(state.dofCount() == 3); result != 0) {
return result;
}
for (const auto* vector : {
&state.displacement(),
&state.externalForce(),
&state.internalForce(),
&state.residual(),
&state.reaction(),
}) {
if (const int result = require(vector->size() == state.dofCount()); result != 0) {
return result;
}
for (const double value : *vector) {
if (const int result = require(value == 0.0); result != 0) {
return result;
}
}
}
return 0;
}
int setters_replace_matching_size_vectors() {
fesa::core::AnalysisState state{2};
state.setDisplacement({1.0, 2.0});
state.setExternalForce({3.0, 4.0});
state.setInternalForce({5.0, 6.0});
state.setResidual({7.0, 8.0});
state.setReaction({9.0, 10.0});
if (const int result = require(state.displacement()[0] == 1.0 && state.displacement()[1] == 2.0); result != 0) {
return result;
}
if (const int result = require(state.externalForce()[0] == 3.0 && state.externalForce()[1] == 4.0); result != 0) {
return result;
}
if (const int result = require(state.internalForce()[0] == 5.0 && state.internalForce()[1] == 6.0); result != 0) {
return result;
}
if (const int result = require(state.residual()[0] == 7.0 && state.residual()[1] == 8.0); result != 0) {
return result;
}
return require(state.reaction()[0] == 9.0 && state.reaction()[1] == 10.0);
}
int mismatched_setter_preserves_existing_vector() {
fesa::core::AnalysisState state{2};
state.setDisplacement({1.0, 2.0});
if (const int result = require_throws<std::invalid_argument>([&state]() {
state.setDisplacement({3.0});
});
result != 0) {
return result;
}
return require(state.displacement()[0] == 1.0 && state.displacement()[1] == 2.0);
}
int resize_resets_all_vectors_to_zero() {
fesa::core::AnalysisState state{2};
state.setDisplacement({1.0, 2.0});
state.setExternalForce({3.0, 4.0});
state.resize(3);
if (const int result = require(state.dofCount() == 3); result != 0) {
return result;
}
for (const auto* vector : {
&state.displacement(),
&state.externalForce(),
&state.internalForce(),
&state.residual(),
&state.reaction(),
}) {
for (const double value : *vector) {
if (const int result = require(value == 0.0); result != 0) {
return result;
}
}
}
return 0;
}
int clear_forces_preserves_displacement() {
fesa::core::AnalysisState state{2};
state.setDisplacement({1.0, 2.0});
state.setExternalForce({3.0, 4.0});
state.setInternalForce({5.0, 6.0});
state.setResidual({7.0, 8.0});
state.setReaction({9.0, 10.0});
state.clearForces();
if (const int result = require(state.displacement()[0] == 1.0 && state.displacement()[1] == 2.0); result != 0) {
return result;
}
for (const auto* vector : {
&state.externalForce(),
&state.internalForce(),
&state.residual(),
&state.reaction(),
}) {
for (const double value : *vector) {
if (const int result = require(value == 0.0); result != 0) {
return result;
}
}
}
return 0;
}
int counters_can_be_updated() {
fesa::core::AnalysisState state;
state.setCurrentTime(1.25);
state.setIncrementIndex(3);
state.setIterationIndex(5);
if (const int result = require(state.currentTime() == 1.25); result != 0) {
return result;
}
if (const int result = require(state.incrementIndex() == 3); result != 0) {
return result;
}
return require(state.iterationIndex() == 5);
}
} // namespace
int run_analysis_state_tests() {
if (const int result = default_state_has_no_dofs(); result != 0) {
return result;
}
if (const int result = sized_state_initializes_zero_vectors(); result != 0) {
return result;
}
if (const int result = setters_replace_matching_size_vectors(); result != 0) {
return result;
}
if (const int result = mismatched_setter_preserves_existing_vector(); result != 0) {
return result;
}
if (const int result = resize_resets_all_vectors_to_zero(); result != 0) {
return result;
}
if (const int result = clear_forces_preserves_displacement(); result != 0) {
return result;
}
if (const int result = counters_can_be_updated(); result != 0) {
return result;
}
return 0;
}
+1 -1
View File
@@ -33,7 +33,7 @@ fesa::core::Domain populated_domain() {
domain.addNode(fesa::core::Node{3, 1.0, 1.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.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.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3));
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.01}); domain.addShellProperty(std::make_unique<fesa::property::ShellProperty>(500, 700, 0.01));
return domain; return domain;
} }
+31 -8
View File
@@ -39,7 +39,7 @@ void add_four_nodes(fesa::core::Domain& domain) {
void add_material_and_property(fesa::core::Domain& domain) { void add_material_and_property(fesa::core::Domain& domain) {
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3)); domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3));
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.01}); domain.addShellProperty(std::make_unique<fesa::property::ShellProperty>(500, 700, 0.01));
} }
void add_element(fesa::core::Domain& domain) { void add_element(fesa::core::Domain& domain) {
@@ -215,21 +215,29 @@ int add_and_retrieve_material_and_property() {
return result; return result;
} }
const fesa::property::ShellProperty* property = domain.findShellProperty(500); const fesa::property::Property* property = domain.findProperty(500);
if (const int result = require(property != nullptr); result != 0) { if (const int result = require(property != nullptr); result != 0) {
return result; return result;
} }
if (const int result = require(property->materialId() == 700); result != 0) { if (const int result = require(property->kind() == fesa::property::PropertyKind::Shell); result != 0) {
return result; return result;
} }
if (const int result = require(property->thickness() == 0.01); result != 0) {
const fesa::property::ShellProperty* shell_property = domain.findShellProperty(500);
if (const int result = require(shell_property != nullptr); result != 0) {
return result;
}
if (const int result = require(shell_property->materialId() == 700); result != 0) {
return result;
}
if (const int result = require(shell_property->thickness() == 0.01); result != 0) {
return result; return result;
} }
if (const int result = require(domain.material(700).id() == 700); result != 0) { if (const int result = require(domain.material(700).id() == 700); result != 0) {
return result; return result;
} }
return require(domain.shellProperty(500).id() == 500); return require(domain.property(500).id() == 500);
} }
int duplicate_material_and_property_ids_throw() { int duplicate_material_and_property_ids_throw() {
@@ -244,7 +252,7 @@ int duplicate_material_and_property_ids_throw() {
} }
return require_throws<std::invalid_argument>([&domain]() { return require_throws<std::invalid_argument>([&domain]() {
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.02}); domain.addShellProperty(std::make_unique<fesa::property::ShellProperty>(500, 700, 0.02));
}); });
} }
@@ -252,7 +260,15 @@ int shell_property_referencing_missing_material_throws() {
fesa::core::Domain domain; fesa::core::Domain domain;
return require_throws<std::invalid_argument>([&domain]() { return require_throws<std::invalid_argument>([&domain]() {
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.01}); domain.addShellProperty(std::make_unique<fesa::property::ShellProperty>(500, 700, 0.01));
});
}
int null_property_rejected() {
fesa::core::Domain domain;
return require_throws<std::invalid_argument>([&domain]() {
domain.addProperty(nullptr);
}); });
} }
@@ -517,6 +533,10 @@ int const_domain_retrieval_returns_const_runtime_model_data() {
result != 0) { result != 0) {
return result; return result;
} }
if (const int result = require((std::is_same<decltype(const_domain.property(500)), const fesa::property::Property&>::value));
result != 0) {
return result;
}
if (const int result = require((std::is_same<decltype(const_domain.shellProperty(500)), const fesa::property::ShellProperty&>::value)); if (const int result = require((std::is_same<decltype(const_domain.shellProperty(500)), const fesa::property::ShellProperty&>::value));
result != 0) { result != 0) {
return result; return result;
@@ -567,7 +587,7 @@ int failed_inserts_do_not_mutate_counts() {
} }
if (const int result = require_throws<std::invalid_argument>([&domain]() { if (const int result = require_throws<std::invalid_argument>([&domain]() {
domain.addShellProperty(fesa::property::ShellProperty{501, 404, 0.01}); domain.addShellProperty(std::make_unique<fesa::property::ShellProperty>(501, 404, 0.01));
}); });
result != 0) { result != 0) {
return result; return result;
@@ -642,6 +662,9 @@ int run_domain_storage_tests() {
if (const int result = shell_property_referencing_missing_material_throws(); result != 0) { if (const int result = shell_property_referencing_missing_material_throws(); result != 0) {
return result; return result;
} }
if (const int result = null_property_rejected(); result != 0) {
return result;
}
if (const int result = add_and_retrieve_sets(); result != 0) { if (const int result = add_and_retrieve_sets(); result != 0) {
return result; return result;
} }
+44
View File
@@ -0,0 +1,44 @@
#include "fesa/io/Hdf5ResultWriter.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;
}
int construct_writer_preserves_output_path() {
const fesa::io::Hdf5ResultWriter writer{"results.h5"};
return require(writer.filePath() == "results.h5");
}
int empty_output_path_throws() {
return require_throws<std::invalid_argument>([]() {
(void)fesa::io::Hdf5ResultWriter{""};
});
}
} // namespace
int main() {
if (const int result = construct_writer_preserves_output_path(); result != 0) {
return result;
}
if (const int result = empty_output_path_throws(); result != 0) {
return result;
}
return 0;
}
+4
View File
@@ -3,6 +3,7 @@ int run_element_base_tests();
int run_load_base_tests(); int run_load_base_tests();
int run_material_base_tests(); int run_material_base_tests();
int run_mitc4_element_model_tests(); int run_mitc4_element_model_tests();
int run_property_base_tests();
int run_shell_property_tests(); int run_shell_property_tests();
int main() { int main() {
@@ -18,6 +19,9 @@ int main() {
if (const int result = run_material_base_tests(); result != 0) { if (const int result = run_material_base_tests(); result != 0) {
return result; return result;
} }
if (const int result = run_property_base_tests(); result != 0) {
return result;
}
if (const int result = run_shell_property_tests(); result != 0) { if (const int result = run_shell_property_tests(); result != 0) {
return result; return result;
} }
+37
View File
@@ -0,0 +1,37 @@
#include "fesa/property/Property.hpp"
#include <memory>
namespace {
int require(bool condition) {
return condition ? 0 : 1;
}
class TestProperty final : public fesa::property::Property {
public:
explicit TestProperty(fesa::core::PropertyId id) : id_(id) {}
fesa::core::PropertyId id() const noexcept override {
return id_;
}
fesa::property::PropertyKind kind() const noexcept override {
return fesa::property::PropertyKind::Shell;
}
private:
fesa::core::PropertyId id_;
};
} // namespace
int run_property_base_tests() {
std::unique_ptr<fesa::property::Property> owned = std::make_unique<TestProperty>(500);
const fesa::property::Property& property = *owned;
if (const int result = require(property.id() == 500); result != 0) {
return result;
}
return require(property.kind() == fesa::property::PropertyKind::Shell);
}
+8
View File
@@ -1,4 +1,5 @@
#include "fesa/property/ShellProperty.hpp" #include "fesa/property/ShellProperty.hpp"
#include "fesa/property/Property.hpp"
#include <stdexcept> #include <stdexcept>
@@ -24,10 +25,17 @@ int require_throws(Function&& function) {
int run_shell_property_tests() { int run_shell_property_tests() {
const fesa::property::ShellProperty property{500, 700, 0.01}; const fesa::property::ShellProperty property{500, 700, 0.01};
const fesa::property::Property& base = property;
if (const int result = require(property.id() == 500); result != 0) { if (const int result = require(property.id() == 500); result != 0) {
return result; return result;
} }
if (const int result = require(base.id() == 500); result != 0) {
return result;
}
if (const int result = require(base.kind() == fesa::property::PropertyKind::Shell); result != 0) {
return result;
}
if (const int result = require(property.materialId() == 700); result != 0) { if (const int result = require(property.materialId() == 700); result != 0) {
return result; return result;
} }