Compare commits
2 Commits
f4196efb10
...
87529c811a
| Author | SHA1 | Date | |
|---|---|---|---|
| 87529c811a | |||
| 7ea08441ed |
@@ -8,8 +8,10 @@ set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
add_library(fesa_core STATIC
|
||||
src/boundary/SinglePointConstraint.cpp
|
||||
src/core/AnalysisState.cpp
|
||||
src/core/Domain.cpp
|
||||
src/element/Mitc4Element.cpp
|
||||
src/io/Hdf5ResultWriter.cpp
|
||||
src/load/NodalLoad.cpp
|
||||
src/material/LinearElasticMaterial.cpp
|
||||
src/property/ShellProperty.cpp
|
||||
@@ -45,9 +47,28 @@ add_executable(fesa_model_object_tests
|
||||
tests/load/load_base_test.cpp
|
||||
tests/material/material_base_test.cpp
|
||||
tests/model_object_main.cpp
|
||||
tests/property/property_base_test.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")
|
||||
|
||||
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")
|
||||
|
||||
@@ -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.
|
||||
- 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.
|
||||
- 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
|
||||
- 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`.
|
||||
|
||||
## 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 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.
|
||||
@@ -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 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 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 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,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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "fesa/element/Element.hpp"
|
||||
#include "fesa/load/Load.hpp"
|
||||
#include "fesa/material/Material.hpp"
|
||||
#include "fesa/property/Property.hpp"
|
||||
#include "fesa/property/ShellProperty.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
@@ -22,7 +23,8 @@ public:
|
||||
void addNode(Node node);
|
||||
void addElement(std::unique_ptr<fesa::element::Element> element);
|
||||
void addMaterial(std::unique_ptr<fesa::material::Material> material);
|
||||
void addShellProperty(fesa::property::ShellProperty property);
|
||||
void 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 addElementSet(std::string name, std::vector<ElementId> element_ids);
|
||||
std::size_t addBoundaryCondition(std::unique_ptr<fesa::boundary::BoundaryCondition> boundary);
|
||||
@@ -41,6 +43,10 @@ public:
|
||||
const fesa::material::Material& material(MaterialId id) const;
|
||||
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& shellProperty(PropertyId id) const;
|
||||
std::size_t shellPropertyCount() const noexcept;
|
||||
@@ -69,7 +75,7 @@ private:
|
||||
std::unordered_map<NodeId, Node> nodes_;
|
||||
std::unordered_map<ElementId, std::unique_ptr<fesa::element::Element>> elements_;
|
||||
std::unordered_map<MaterialId, std::unique_ptr<fesa::material::Material>> materials_;
|
||||
std::unordered_map<PropertyId, fesa::property::ShellProperty> shell_properties_;
|
||||
std::unordered_map<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<ElementId>> element_sets_;
|
||||
std::vector<std::unique_ptr<fesa::boundary::BoundaryCondition>> boundary_conditions_;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,17 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
#include "fesa/property/Property.hpp"
|
||||
|
||||
namespace fesa::property {
|
||||
|
||||
using fesa::core::MaterialId;
|
||||
using fesa::core::PropertyId;
|
||||
|
||||
class ShellProperty {
|
||||
class ShellProperty final : public Property {
|
||||
public:
|
||||
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;
|
||||
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.
|
||||
@@ -11,6 +11,14 @@
|
||||
{
|
||||
"dir": "domain-runtime-storage",
|
||||
"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."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
+40
-11
@@ -172,8 +172,8 @@ void Domain::addElement(std::unique_ptr<fesa::element::Element> element) {
|
||||
throw std::invalid_argument("element references missing node id");
|
||||
}
|
||||
}
|
||||
if (findShellProperty(element->propertyId()) == nullptr) {
|
||||
throw std::invalid_argument("element references missing shell property id");
|
||||
if (findProperty(element->propertyId()) == nullptr) {
|
||||
throw std::invalid_argument("element references missing property id");
|
||||
}
|
||||
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) {
|
||||
const PropertyId id = property.id();
|
||||
if (shell_properties_.find(id) != shell_properties_.end()) {
|
||||
throw std::invalid_argument("duplicate shell property id");
|
||||
void Domain::addProperty(std::unique_ptr<fesa::property::Property> property) {
|
||||
if (!property) {
|
||||
throw std::invalid_argument("property is null");
|
||||
}
|
||||
if (findMaterial(property.materialId()) == nullptr) {
|
||||
const PropertyId id = property->id();
|
||||
if (properties_.find(id) != properties_.end()) {
|
||||
throw std::invalid_argument("duplicate property id");
|
||||
}
|
||||
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");
|
||||
}
|
||||
shell_properties_.emplace(id, std::move(property));
|
||||
}
|
||||
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) {
|
||||
@@ -338,9 +351,25 @@ std::size_t Domain::materialCount() const noexcept {
|
||||
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 auto it = shell_properties_.find(id);
|
||||
return it == shell_properties_.end() ? nullptr : &it->second;
|
||||
return dynamic_cast<const fesa::property::ShellProperty*>(findProperty(id));
|
||||
}
|
||||
|
||||
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 {
|
||||
return shell_properties_.size();
|
||||
return properties_.size();
|
||||
}
|
||||
|
||||
const std::vector<NodeId>* Domain::findNodeSet(const std::string& name) const noexcept {
|
||||
|
||||
@@ -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
|
||||
@@ -15,6 +15,10 @@ PropertyId ShellProperty::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
PropertyKind ShellProperty::kind() const noexcept {
|
||||
return PropertyKind::Shell;
|
||||
}
|
||||
|
||||
MaterialId ShellProperty::materialId() const noexcept {
|
||||
return material_id_;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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{4, 0.0, 1.0, 0.0});
|
||||
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3));
|
||||
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.01});
|
||||
domain.addShellProperty(std::make_unique<fesa::property::ShellProperty>(500, 700, 0.01));
|
||||
return domain;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ void add_four_nodes(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.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) {
|
||||
@@ -215,21 +215,29 @@ int add_and_retrieve_material_and_property() {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (const int result = require(domain.material(700).id() == 700); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(domain.shellProperty(500).id() == 500);
|
||||
return require(domain.property(500).id() == 500);
|
||||
}
|
||||
|
||||
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]() {
|
||||
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;
|
||||
|
||||
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) {
|
||||
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));
|
||||
result != 0) {
|
||||
return result;
|
||||
@@ -567,7 +587,7 @@ int failed_inserts_do_not_mutate_counts() {
|
||||
}
|
||||
|
||||
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) {
|
||||
return result;
|
||||
@@ -642,6 +662,9 @@ int run_domain_storage_tests() {
|
||||
if (const int result = shell_property_referencing_missing_material_throws(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = null_property_rejected(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = add_and_retrieve_sets(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ int run_element_base_tests();
|
||||
int run_load_base_tests();
|
||||
int run_material_base_tests();
|
||||
int run_mitc4_element_model_tests();
|
||||
int run_property_base_tests();
|
||||
int run_shell_property_tests();
|
||||
|
||||
int main() {
|
||||
@@ -18,6 +19,9 @@ int main() {
|
||||
if (const int result = run_material_base_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_property_base_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_shell_property_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "fesa/property/ShellProperty.hpp"
|
||||
#include "fesa/property/Property.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -24,10 +25,17 @@ int require_throws(Function&& function) {
|
||||
|
||||
int run_shell_property_tests() {
|
||||
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) {
|
||||
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) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user