Compare commits
3 Commits
e4e2f57808
...
f4196efb10
| Author | SHA1 | Date | |
|---|---|---|---|
| f4196efb10 | |||
| 8f24213ab7 | |||
| fdeac602f4 |
@@ -0,0 +1,53 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(FESA LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
add_library(fesa_core STATIC
|
||||
src/boundary/SinglePointConstraint.cpp
|
||||
src/core/Domain.cpp
|
||||
src/element/Mitc4Element.cpp
|
||||
src/load/NodalLoad.cpp
|
||||
src/material/LinearElasticMaterial.cpp
|
||||
src/property/ShellProperty.cpp
|
||||
)
|
||||
target_include_directories(fesa_core PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_executable(fesa_domain_tests
|
||||
tests/core/boundary_condition_test.cpp
|
||||
tests/core/domain_bootstrap_test.cpp
|
||||
tests/core/domain_model_object_test.cpp
|
||||
tests/core/domain_storage_test.cpp
|
||||
tests/core/element_definition_test.cpp
|
||||
tests/core/load_definition_test.cpp
|
||||
tests/core/material_definition_test.cpp
|
||||
tests/core/model_types_test.cpp
|
||||
tests/core/node_test.cpp
|
||||
tests/core/property_definition_test.cpp
|
||||
tests/core/step_definition_test.cpp
|
||||
)
|
||||
target_link_libraries(fesa_domain_tests PRIVATE fesa_core)
|
||||
|
||||
add_test(NAME domain.bootstrap COMMAND fesa_domain_tests)
|
||||
set_tests_properties(domain.bootstrap PROPERTIES LABELS "domain;core")
|
||||
|
||||
add_executable(fesa_model_object_tests
|
||||
tests/boundary/boundary_base_test.cpp
|
||||
tests/element/element_base_test.cpp
|
||||
tests/element/mitc4_element_model_test.cpp
|
||||
tests/load/load_base_test.cpp
|
||||
tests/material/material_base_test.cpp
|
||||
tests/model_object_main.cpp
|
||||
tests/property/shell_property_test.cpp
|
||||
)
|
||||
target_link_libraries(fesa_model_object_tests PRIVATE fesa_core)
|
||||
|
||||
add_test(NAME model-object.base COMMAND fesa_model_object_tests)
|
||||
set_tests_properties(model-object.base PROPERTIES LABELS "model-object;core")
|
||||
+2
-2
@@ -64,9 +64,9 @@
|
||||
**Tradeoff**: Implementation can be blocked until required reference artifacts are supplied.
|
||||
|
||||
## ADR-010: Domain, AnalysisModel, And AnalysisState Are Separate
|
||||
**Decision**: `Domain` owns parsed model definition, `AnalysisModel` owns the active step execution view, and `AnalysisState` owns mutable solution and iteration state.
|
||||
**Decision**: `Domain` owns the validated runtime model object graph created from parsed input, `AnalysisModel` owns the active step execution view, and `AnalysisState` owns mutable solution and iteration state.
|
||||
|
||||
**Reason**: This prevents equation ids, displacement vectors, residuals, and future nonlinear/time states from leaking into input model objects. It also keeps the static solver compatible with future nonlinear, dynamic, and thermal workflows.
|
||||
**Reason**: This prevents parser DTOs, equation ids, displacement vectors, residuals, and future nonlinear/time states from leaking into the persistent domain model. It also keeps the static solver compatible with future nonlinear, dynamic, and thermal workflows.
|
||||
|
||||
**Tradeoff**: The initial implementation has more object boundaries than a single monolithic model container.
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ tests/
|
||||
The current repository may not yet contain all directories. They are intended ownership boundaries for implementation planning.
|
||||
|
||||
## Core Runtime Objects
|
||||
- `Domain`: owns model definitions from input: nodes, elements, materials, properties, sets, boundary conditions, loads, and step definitions.
|
||||
- `Domain`: owns the validated runtime model object graph created from input: nodes, elements, materials, properties, sets, boundary conditions, loads, and step definitions.
|
||||
- `AnalysisModel`: step-local execution view over active elements, loads, boundary conditions, properties, materials, and equation system view.
|
||||
- `AnalysisState`: owns changing physical quantities and iteration/time state such as displacement, velocity, acceleration, temperature, forces, residual, current time, increment, and element/integration-point state.
|
||||
- `Node`: stores node id, coordinates, and six DOF values in order `U1, U2, U3, UR1, UR2, UR3`.
|
||||
@@ -100,7 +100,7 @@ The current repository may not yet contain all directories. They are intended ow
|
||||
## State Ownership
|
||||
|
||||
### Domain
|
||||
`Domain` represents parsed model definition and should not store equation ids, solver vectors, current displacement, or iteration state.
|
||||
`Domain` represents the validated model definition as runtime objects and should not store parser DTOs, equation ids, solver vectors, current displacement, or iteration state.
|
||||
|
||||
Included:
|
||||
- nodes and elements
|
||||
@@ -109,6 +109,8 @@ Included:
|
||||
- loads and boundary conditions
|
||||
- analysis step definitions
|
||||
|
||||
Parser-style definition records may exist as temporary input or factory records, but they are not persistent `Domain` storage. `Domain` owns runtime objects such as `Element`, `Material`, `ShellProperty`, `Load`, and `BoundaryCondition`.
|
||||
|
||||
### AnalysisModel
|
||||
`AnalysisModel` is built per active step. It references `Domain` objects by id or stable reference and defines what participates in the current solve.
|
||||
|
||||
@@ -152,8 +154,9 @@ Results use:
|
||||
```text
|
||||
Abaqus input file
|
||||
-> InputParser
|
||||
-> temporary parsed records
|
||||
-> Factory/Registry object creation
|
||||
-> Domain
|
||||
-> Domain runtime object graph
|
||||
-> StepDefinition loop
|
||||
-> AnalysisModel
|
||||
-> DofManager
|
||||
|
||||
+28
-1
@@ -8,6 +8,8 @@
|
||||
- Multi-Agent coordination files live under `docs/`: `docs/PLAN.md`, `docs/PROGRESS.md`, and `docs/WORKNOTE.md`.
|
||||
- Project plan/design documents live under `docs/project-plan/`.
|
||||
- Common per-run AI Agent rules live in `docs/AGENT_RULES.md`.
|
||||
- Domain model foundation phase exists under `phases/domain-model-foundation/` and the first C++/MSVC CMake/CTest core implementation slice has been added.
|
||||
- Analysis model object phase exists under `phases/analysis-model-objects/` and adds identity-only model objects for Domain ownership.
|
||||
|
||||
## Completed
|
||||
- Defined the nine-step solver development workflow.
|
||||
@@ -23,9 +25,22 @@
|
||||
- Documented the canonical git remote `https://teagit.mimi1011.synology.me/baram2584/FESADev.git`, default remote `origin`, shared baseline branch `dev`, and `codex/<short-task-name>` work branch convention in `AGENTS.md`, `docs/AGENT_RULES.md`, and `docs/PLAN.md`.
|
||||
- Confirmed local HDF5 `2.1.1` installation at `C:\Program Files\HDF_Group\HDF5\2.1.1` and recorded the `HDF5_ROOT`/`HDF5_DIR` discovery policy in architecture, agent, skill, and handoff documentation.
|
||||
- Recorded the standing user instruction in `AGENTS.md` that Agent-authored work should be validated, committed, and pushed to `origin` after completion.
|
||||
- Created `docs/implementation-plans/domain-model-foundation-implementation-plan.md`.
|
||||
- Added root CMake/CTest bootstrap for `fesa_core` and `fesa_domain_tests`.
|
||||
- Implemented `Domain` model-definition storage for nodes, MITC4 element definitions, linear elastic material definitions, shell properties, node sets, element sets, boundary conditions, nodal loads, and linear static step definitions.
|
||||
- Added Domain foundation C++ tests under `tests/core/`.
|
||||
- Fixed `scripts/validate_workspace.py` so CMake/CTest validation can use the default CMake install path when CMake is not on the shell PATH.
|
||||
- Created `docs/implementation-plans/analysis-model-objects-implementation-plan.md`.
|
||||
- Added identity-only model object base classes and concrete skeletons for `Element`/`Mitc4Element`, `Material`/`LinearElasticMaterial`, `ShellProperty`, `Load`/`NodalLoad`, and `BoundaryCondition`/`SinglePointConstraint`.
|
||||
- Extended `Domain` with RAII `std::unique_ptr` ownership APIs for polymorphic element, material, load, and boundary model objects while preserving existing `*Definition` storage APIs.
|
||||
- Added `fesa_model_object_tests` CTest target and model-object tests under `tests/element/`, `tests/material/`, `tests/property/`, `tests/load/`, and `tests/boundary/`.
|
||||
- Created `docs/implementation-plans/domain-runtime-storage-implementation-plan.md` and `phases/domain-runtime-storage/`.
|
||||
- Migrated `Domain` canonical storage away from parser-style definition DTOs to runtime model objects for elements, materials, shell properties, loads, and boundary conditions.
|
||||
- Moved `ElementType` into `ModelTypes.hpp` so runtime element interfaces no longer include `ElementDefinition.hpp`.
|
||||
- Updated Domain storage tests so element sets and linear static step indices validate against runtime element, load, and boundary containers.
|
||||
|
||||
## In Progress
|
||||
- Ready for the next 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`.
|
||||
|
||||
## Next Tasks
|
||||
1. Create `docs/requirements/mitc4-linear-static-shell.md`.
|
||||
@@ -36,6 +51,18 @@
|
||||
6. Create `docs/implementation-plans/mitc4-linear-static-shell-implementation-plan.md`.
|
||||
|
||||
## Last Validation
|
||||
- 2026-06-09: After analysis model object implementation, `python -m unittest discover -s scripts -p "test_*.py"` passed. 89 tests ran successfully.
|
||||
- 2026-06-09: After analysis model object implementation, `python scripts/validate_workspace.py` configured CMake with Visual Studio 17 2022 x64, built Debug targets, ran CTest, and passed.
|
||||
- 2026-06-09: After analysis model object implementation, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` passed. 2 CTest executables ran successfully.
|
||||
- 2026-06-09: After analysis model object implementation, `git diff --check` passed.
|
||||
- 2026-06-09: After Domain runtime storage migration, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` passed. 2 CTest executables ran successfully.
|
||||
- 2026-06-09: After Domain runtime storage migration, `python -m unittest discover -s scripts -p "test_*.py"` passed. 89 tests ran successfully.
|
||||
- 2026-06-09: After Domain runtime storage migration, `python scripts/validate_workspace.py` configured CMake with Visual Studio 17 2022 x64, built Debug targets, ran CTest, and passed.
|
||||
- 2026-06-09: After Domain runtime storage migration, `git diff --check` passed with only Git line-ending normalization warnings.
|
||||
- 2026-06-08: After Domain model foundation implementation, `python -m unittest discover -s scripts -p "test_*.py"` passed. 89 tests ran successfully.
|
||||
- 2026-06-08: After Domain model foundation implementation, `python scripts/validate_workspace.py` configured CMake with Visual Studio 17 2022 x64, built Debug targets, ran CTest, and passed.
|
||||
- 2026-06-08: After Domain model foundation implementation, `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` passed. 1 domain/core test executable ran successfully.
|
||||
- 2026-06-08: After Domain model foundation implementation, `git diff --check` passed.
|
||||
- 2026-06-08: After recording the standing commit/push instruction in `AGENTS.md`, `python -m unittest discover -s scripts -p "test_*.py"` passed. 85 tests ran successfully.
|
||||
- 2026-06-08: After recording the standing commit/push instruction in `AGENTS.md`, `python scripts/validate_workspace.py` passed through the expected no-op path because no root `CMakeLists.txt` exists yet.
|
||||
- 2026-06-08: After recording the standing commit/push instruction in `AGENTS.md`, `git diff --check` passed with only Git line-ending normalization warnings.
|
||||
|
||||
@@ -13,6 +13,9 @@ During planning, standard checks for `HDF5_ROOT`, `HDF5_DIR`, `h5dump`, and obvi
|
||||
### 2026-06-08 - HDF5 Installed Under HDF_Group
|
||||
The local HDF5 install was later confirmed at `C:\Program Files\HDF_Group\HDF5\2.1.1`, and `h5dump.exe -V` reports `2.1.1`. Future CMake work should prefer `HDF5_ROOT=C:\Program Files\HDF_Group\HDF5\2.1.1` or `HDF5_DIR=C:\Program Files\HDF_Group\HDF5\2.1.1\cmake`. Keep the old 2026-06-05 note as historical context for stale sessions, but do not treat HDF5 as currently missing on this machine.
|
||||
|
||||
### 2026-06-08 - CMake Installed But Not On Shell PATH
|
||||
`cmake.exe` exists at `C:\Program Files\CMake\bin\cmake.exe`, but the Codex shell did not resolve bare `cmake`. `scripts/validate_workspace.py` was updated to resolve bare `cmake` and `ctest` validation commands to the known CMake install path when needed. Use `python scripts/validate_workspace.py` as the canonical validation path, or call `C:\Program Files\CMake\bin\cmake.exe` / `ctest.exe` explicitly for manual commands.
|
||||
|
||||
### 2026-06-05 - Validation Currently Has No CMake Project
|
||||
`python scripts/validate_workspace.py` currently exits successfully through the no-op path because there is no root `CMakeLists.txt`. This is expected until the C++ solver project is bootstrapped. Once CMake is introduced, validation must configure, build, and run CTest.
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Analysis Model Objects Build/Test Report
|
||||
|
||||
## Metadata
|
||||
- feature_id: analysis-model-objects
|
||||
- source_implementation_report: N/A
|
||||
- source_implementation_plan: docs/implementation-plans/analysis-model-objects-implementation-plan.md
|
||||
- status: pass-for-next-implementation-stage
|
||||
- owner_agent: build-test-executor-agent
|
||||
- date: 2026-06-09
|
||||
|
||||
## Execution Environment
|
||||
- os: Windows
|
||||
- generator: Visual Studio 17 2022
|
||||
- platform: x64
|
||||
- config: Debug
|
||||
- build_dir: build/msvc-debug
|
||||
- active_override_env_vars: none
|
||||
- command_discovery_path: default CMake/MSVC x64 Debug through `python scripts/validate_workspace.py`
|
||||
|
||||
## Command Log Summary
|
||||
|
||||
| order | command | exit_code | duration | stdout_stderr_tail |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | `python -m unittest discover -s scripts -p "test_*.py"` | 0 | <1s | 89 tests passed |
|
||||
| 2 | `python scripts/validate_workspace.py` | 0 | ~4s | CMake configure, Debug build, and CTest passed |
|
||||
| 3 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` | 0 | <1s | 2 CTest executables passed |
|
||||
| 4 | `git diff --check` | 0 | <1s | no whitespace errors; line-ending normalization warnings may appear |
|
||||
|
||||
## Validation Results
|
||||
|
||||
| validation_stage | result | evidence |
|
||||
| --- | --- | --- |
|
||||
| harness self-test | pass | Python unittest discovery passed 89 tests |
|
||||
| configure | pass | CMake generated `build/msvc-debug` with Visual Studio 17 2022 x64 |
|
||||
| build | pass | `fesa_core.lib`, `fesa_domain_tests.exe`, and `fesa_model_object_tests.exe` built |
|
||||
| CTest | pass | `domain.bootstrap` and `model-object.base` passed |
|
||||
| feature-specific tests | pass | model-object and domain CTest filters passed |
|
||||
|
||||
## Failure Classification
|
||||
|
||||
- classification: N/A
|
||||
- primary_failure: N/A
|
||||
- first_failed_command: N/A
|
||||
- evidence_tail: validation succeeded
|
||||
|
||||
## Failed Test Inventory
|
||||
|
||||
| test_name | label | command | failure_summary |
|
||||
| --- | --- | --- | --- |
|
||||
| N/A | N/A | N/A | N/A |
|
||||
|
||||
## Handoff Recommendation
|
||||
|
||||
| target_agent | reason | required_input |
|
||||
| --- | --- | --- |
|
||||
| Parser/Factory Implementation Agent | New model objects can be populated from future parser/factory mappings | model object headers and compatibility note |
|
||||
| AnalysisModel Implementation Agent | Domain now exposes identity-only model objects through const base APIs | Domain object ownership APIs |
|
||||
| Build/Test Executor Agent | Re-run after downstream C++ changes | validation commands in this report |
|
||||
| Reference Verification Agent | Not ready; this phase produces no solver HDF5 result | N/A |
|
||||
|
||||
## No-Change Assertion
|
||||
|
||||
- source_files_modified: true
|
||||
- test_files_modified: true
|
||||
- cmake_files_modified: true
|
||||
- reference_artifacts_modified: false
|
||||
- tolerance_policies_modified: false
|
||||
- notes: This phase added model object classes and Domain ownership APIs only. It did not run reference solvers, generate reference artifacts, change tolerances, or claim numerical MITC4 correctness.
|
||||
|
||||
## Open Issues
|
||||
|
||||
- Parser/factory mapping from core `*Definition` DTOs to model objects remains deferred.
|
||||
- MITC4 stiffness, material constitutive behavior, shell section stiffness, assembly, solver, and HDF5 output remain future phases.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Domain Model Foundation Build/Test Report
|
||||
|
||||
## Metadata
|
||||
- feature_id: domain-model-foundation
|
||||
- source_implementation_report: N/A
|
||||
- source_implementation_plan: docs/implementation-plans/domain-model-foundation-implementation-plan.md
|
||||
- status: pass-for-next-implementation-stage
|
||||
- owner_agent: build-test-executor-agent
|
||||
- date: 2026-06-08
|
||||
|
||||
## Execution Environment
|
||||
- os: Windows
|
||||
- generator: Visual Studio 17 2022
|
||||
- platform: x64
|
||||
- config: Debug
|
||||
- build_dir: build/msvc-debug
|
||||
- active_override_env_vars: none
|
||||
- command_discovery_path: default CMake/MSVC x64 Debug
|
||||
|
||||
## Command Log Summary
|
||||
|
||||
| order | command | exit_code | duration | stdout_stderr_tail |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | `git status --short --branch` | 0 | <1s | branch `codex/domain-model-foundation`; only Agent-authored changes observed |
|
||||
| 2 | `git remote -v` | 0 | <1s | `origin` points to `https://teagit.mimi1011.synology.me/baram2584/FESADev.git` |
|
||||
| 3 | `python -m unittest discover -s scripts -p "test_*.py"` | 0 | <1s | 89 tests passed |
|
||||
| 4 | `python scripts/validate_workspace.py` | 0 | ~8s | configure, build, and CTest passed; validation succeeded |
|
||||
| 5 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` | 0 | <1s | 1 domain/core CTest passed |
|
||||
| 6 | `git diff --check` | 0 | <1s | no whitespace errors |
|
||||
|
||||
## Validation Results
|
||||
|
||||
| validation_stage | result | evidence |
|
||||
| --- | --- | --- |
|
||||
| harness self-test | pass | `python -m unittest discover -s scripts -p "test_*.py"` ran 89 tests successfully |
|
||||
| configure | pass | CMake generated `build/msvc-debug` with Visual Studio 17 2022 x64 |
|
||||
| build | pass | `fesa_core.lib` and `fesa_domain_tests.exe` built in Debug |
|
||||
| CTest | pass | `domain.bootstrap` passed with labels `domain;core` |
|
||||
| feature-specific tests | pass | Domain model storage tests are compiled into `fesa_domain_tests` |
|
||||
|
||||
## Failure Classification
|
||||
|
||||
- classification: N/A
|
||||
- primary_failure: N/A
|
||||
- first_failed_command: N/A
|
||||
- evidence_tail: validation succeeded
|
||||
|
||||
## Failed Test Inventory
|
||||
|
||||
| test_name | label | command | failure_summary |
|
||||
| --- | --- | --- | --- |
|
||||
| N/A | N/A | N/A | N/A |
|
||||
|
||||
## Handoff Recommendation
|
||||
|
||||
| target_agent | reason | required_input |
|
||||
| --- | --- | --- |
|
||||
| Implementation Agent | Continue with parser/factory or AnalysisModel work after upstream contracts are ready | Domain headers, tests, and implementation plan |
|
||||
| Build/Test Executor Agent | Re-run after downstream C++ changes | validation commands in this report |
|
||||
| Reference Verification Agent | Not ready; this phase produces no solver HDF5 result | N/A |
|
||||
|
||||
## No-Change Assertion
|
||||
|
||||
- source_files_modified: true
|
||||
- test_files_modified: true
|
||||
- cmake_files_modified: true
|
||||
- reference_artifacts_modified: false
|
||||
- tolerance_policies_modified: false
|
||||
- notes: This phase added C++ core model-definition storage only. It did not run reference solvers, generate reference artifacts, or change tolerance policy.
|
||||
|
||||
## Open Issues
|
||||
|
||||
- MITC4 requirements, research, formulation, I/O contract, reference models, and full implementation plan remain separate upstream stages.
|
||||
- Parser-to-Domain factory API is intentionally deferred.
|
||||
@@ -0,0 +1,179 @@
|
||||
# Analysis Model Objects Implementation Plan
|
||||
|
||||
## Metadata
|
||||
- feature_id: analysis-model-objects
|
||||
- source_requirement: AGENTS.md; docs/ARCHITECTURE.md; docs/ADR.md
|
||||
- source_research: N/A for this architecture/model-object slice
|
||||
- source_formulation: N/A for this architecture/model-object slice
|
||||
- source_numerical_review: N/A for this architecture/model-object slice
|
||||
- source_io_definition: docs/ARCHITECTURE.md Domain and factory/registry ownership rules
|
||||
- source_reference_models: N/A for this architecture/model-object slice
|
||||
- status: ready-for-implementation
|
||||
- owner_agent: implementation-planning-agent
|
||||
- date: 2026-06-09
|
||||
|
||||
## Readiness Check
|
||||
|
||||
| input | required_status | observed_status | decision |
|
||||
| --- | --- | --- | --- |
|
||||
| architecture | model object boundaries documented | documented in docs/ARCHITECTURE.md and ADR-004/010/011 | proceed |
|
||||
| domain foundation | base Domain storage available | implemented in `domain-model-foundation` | proceed |
|
||||
| formulation | not required for identity-only model object skeletons | N/A | proceed |
|
||||
| numerical_review | not required for identity-only model object skeletons | N/A | proceed |
|
||||
| io_definition | parser DTO compatibility sufficient | existing `*Definition` objects remain input DTOs | proceed |
|
||||
| reference_models | not required because this slice produces no solver result | N/A | proceed |
|
||||
|
||||
## Implementation Scope
|
||||
|
||||
- included_behavior: C++17 model object classes for nodes, elements, materials, shell properties, loads, and boundary conditions.
|
||||
- included_behavior: abstract base classes for element, material, load, and boundary condition model objects.
|
||||
- included_behavior: MITC4 element model skeleton with id, connectivity, property id, 4-node count, and 24-DOF count.
|
||||
- included_behavior: Domain RAII ownership for polymorphic model objects through `std::unique_ptr`.
|
||||
- included_behavior: compatibility with existing `*Definition` value objects and tests.
|
||||
- excluded_behavior: MITC4 stiffness, Jacobian, MITC shear interpolation, constitutive matrices, global force assembly, boundary-condition elimination, equation numbering, solver vectors, reaction recovery, HDF5 output, parser/factory implementation, and reference comparison.
|
||||
- non_goals: numerical correctness claims, release readiness, reference-solver execution, and reference artifact generation.
|
||||
|
||||
## Model Object Contract
|
||||
|
||||
`Domain` remains the owner of parsed model definition. This phase adds runtime model objects that can later be used by `AnalysisModel`, factories, and element/material implementations without storing analysis state in `Domain`.
|
||||
|
||||
Model object responsibilities:
|
||||
|
||||
- `Node`: id and reference coordinates only; fixed six-DOF count from project convention.
|
||||
- `Element`: identity, element type, connectivity, property reference, and node count only.
|
||||
- `Mitc4Element`: MITC4 model skeleton with four node ids and 24 element DOFs only.
|
||||
- `Material`: material identity only.
|
||||
- `LinearElasticMaterial`: id, Young's modulus, and Poisson ratio only.
|
||||
- `ShellProperty`: property id, material id, and thickness only.
|
||||
- `Load`: load kind only.
|
||||
- `NodalLoad`: node id, DOF, and scalar value only.
|
||||
- `BoundaryCondition`: boundary kind only.
|
||||
- `SinglePointConstraint`: node id, DOF, and prescribed scalar value only.
|
||||
|
||||
Excluded from all model objects:
|
||||
|
||||
- equation ids
|
||||
- sparse matrix data
|
||||
- displacement, residual, reaction, velocity, acceleration, or temperature vectors
|
||||
- current time, increment, or iteration counters
|
||||
- element integration point state
|
||||
- MKL, TBB, HDF5, or reference-comparison handles
|
||||
|
||||
Existing `*Definition` value objects remain parser/factory DTOs for now. This phase must not delete them. Later parser/factory work may either map DTOs into model objects or retire DTOs with an explicit migration plan.
|
||||
|
||||
Compatibility decision:
|
||||
|
||||
- Existing `ElementDefinition`, `LinearElasticMaterialDefinition`, `ShellPropertyDefinition`, `BoundaryCondition`, `NodalLoadDefinition`, and `LinearStaticStepDefinition` classes remain under `include/fesa/core/` as value-style input DTOs.
|
||||
- New runtime model objects live under ownership namespaces: `fesa::element`, `fesa::material`, `fesa::property`, `fesa::load`, and `fesa::boundary`.
|
||||
- `Domain` supports both existing DTO storage APIs and new polymorphic model-object ownership APIs during the transition.
|
||||
- Parser/factory work must make an explicit choice to map DTOs to model objects or replace DTOs in a separate phase. This phase does not decide parser behavior.
|
||||
|
||||
## Work Breakdown
|
||||
|
||||
| task_id | order | purpose | upstream_trace | depends_on | expected_test_first |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| AMO-001 | 1 | refine `Node` model class contract | ADR-010; docs/ARCHITECTURE.md Node | none | AMO-TEST-001 |
|
||||
| AMO-002 | 2 | add abstract `Element` base interface | ADR-004; docs/ARCHITECTURE.md Element | AMO-001 | AMO-TEST-002 |
|
||||
| AMO-003 | 3 | add MITC4 element model skeleton | AGENTS.md MITC4 scope | AMO-002 | AMO-TEST-003 |
|
||||
| AMO-004 | 4 | add material base and linear elastic material object | docs/ARCHITECTURE.md Material | AMO-002 | AMO-TEST-004 |
|
||||
| AMO-005 | 5 | add shell property model object | docs/ARCHITECTURE.md Property | AMO-004 | AMO-TEST-005 |
|
||||
| AMO-006 | 6 | add load base and nodal load object | docs/ARCHITECTURE.md loads | AMO-002 | AMO-TEST-006 |
|
||||
| AMO-007 | 7 | add boundary base and single-point constraint object | ADR-012 | AMO-002 | AMO-TEST-007 |
|
||||
| AMO-008 | 8 | extend Domain with polymorphic object ownership | ADR-010; ADR-011 | AMO-003 to AMO-007 | AMO-TEST-008 |
|
||||
| AMO-009 | 9 | preserve `*Definition` compatibility | domain-model-foundation plan | AMO-008 | AMO-TEST-009 |
|
||||
| AMO-010 | 10 | record build/test evidence and handoff | docs/build-test-reports/README.md | AMO-009 | AMO-TEST-010 |
|
||||
|
||||
## TDD Test Plan
|
||||
|
||||
| test_id | order | test_type | red_condition | green_condition | linked_task | command |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| AMO-TEST-001 | 1 | unit | `Node::dofCount()` missing | Node model tests pass | AMO-001 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
|
||||
| AMO-TEST-002 | 2 | unit | `fesa/element/Element.hpp` missing | Element base polymorphism tests pass | AMO-002 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-003 | 3 | unit | `Mitc4Element` missing | MITC4 model skeleton tests pass | AMO-003 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-004 | 4 | unit | material model headers missing | material base and linear elastic tests pass | AMO-004 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-005 | 5 | unit | shell property model missing | shell property tests pass | AMO-005 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-006 | 6 | unit | load model headers missing | load base and nodal load tests pass | AMO-006 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-007 | 7 | unit | boundary model headers missing | boundary base and SPC tests pass | AMO-007 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object` |
|
||||
| AMO-TEST-008 | 8 | unit | Domain polymorphic APIs missing | Domain owns and retrieves model objects by base interface | AMO-008 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"` |
|
||||
| AMO-TEST-009 | 9 | regression | existing definition tests fail | existing Domain definition tests remain passing | AMO-009 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
|
||||
| AMO-TEST-010 | 10 | validation | report evidence missing | validation report records passing commands or classified failure | AMO-010 | `python scripts/validate_workspace.py` |
|
||||
|
||||
## CMake/CTest Plan
|
||||
|
||||
- target_candidates: `fesa_core`, `fesa_domain_tests`, `fesa_model_object_tests`
|
||||
- labels: `domain`, `core`, `model-object`, `element`, `material`, `property`, `load`, `boundary`
|
||||
- msvc_config: Debug
|
||||
- expected_feature_command: `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"`
|
||||
- workspace_validation: `python scripts/validate_workspace.py`
|
||||
|
||||
## Candidate Files and Ownership
|
||||
|
||||
| file_candidate | purpose | owner_boundary | notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `include/fesa/core/Node.hpp` | existing node model object | core model definition | no solver state |
|
||||
| `include/fesa/element/Element.hpp` | abstract element model base | element model identity | no stiffness API |
|
||||
| `include/fesa/element/Mitc4Element.hpp` | MITC4 element model skeleton | element model identity | no formulation implementation |
|
||||
| `src/element/Mitc4Element.cpp` | MITC4 model accessors | element model identity | no numerical behavior |
|
||||
| `include/fesa/material/Material.hpp` | abstract material model base | material identity | no constitutive matrix |
|
||||
| `include/fesa/material/LinearElasticMaterial.hpp` | linear elastic material model | material input values | no stress update |
|
||||
| `src/material/LinearElasticMaterial.cpp` | linear elastic material accessors | material input values | no numerical behavior |
|
||||
| `include/fesa/property/ShellProperty.hpp` | shell property model | property input values | no section stiffness |
|
||||
| `src/property/ShellProperty.cpp` | shell property validation/accessors | property input values | thickness validation only |
|
||||
| `include/fesa/load/Load.hpp` | abstract load model base | load identity | no assembly |
|
||||
| `include/fesa/load/NodalLoad.hpp` | nodal load model | load input values | no global vector behavior |
|
||||
| `src/load/NodalLoad.cpp` | nodal load accessors | load input values | no assembly |
|
||||
| `include/fesa/boundary/BoundaryCondition.hpp` | abstract boundary model base | boundary identity | no matrix application |
|
||||
| `include/fesa/boundary/SinglePointConstraint.hpp` | SPC boundary model | boundary input values | no reaction recovery |
|
||||
| `src/boundary/SinglePointConstraint.cpp` | SPC accessors | boundary input values | no matrix application |
|
||||
| `include/fesa/core/Domain.hpp` | Domain ownership extension | core ownership | preserve existing APIs |
|
||||
| `src/core/Domain.cpp` | Domain ownership implementation | core ownership | no solver state |
|
||||
| `tests/**` | TDD coverage | tests | write before production changes |
|
||||
|
||||
## Acceptance Traceability Matrix
|
||||
|
||||
| requirement_id | task_id | test_id | reference_model_id | acceptance_criterion | status |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| AMO-REQ-001 Node is model data only | AMO-001 | AMO-TEST-001 | N/A | Node tests pass and no solver state added | draft |
|
||||
| AMO-REQ-002 Element base is identity-only | AMO-002 | AMO-TEST-002 | N/A | Element base tests pass | draft |
|
||||
| AMO-REQ-003 MITC4 model skeleton has 4 nodes and 24 DOFs | AMO-003 | AMO-TEST-003 | N/A | MITC4 model tests pass | draft |
|
||||
| AMO-REQ-004 Material model stores input values only | AMO-004 | AMO-TEST-004 | N/A | material tests pass | draft |
|
||||
| AMO-REQ-005 Shell property validates thickness only | AMO-005 | AMO-TEST-005 | N/A | shell property tests pass | draft |
|
||||
| AMO-REQ-006 Load and boundary models do not assemble/apply | AMO-006; AMO-007 | AMO-TEST-006; AMO-TEST-007 | N/A | load/boundary tests pass and APIs remain value accessors | draft |
|
||||
| AMO-REQ-007 Domain owns polymorphic model objects via RAII | AMO-008 | AMO-TEST-008 | N/A | Domain ownership tests pass | draft |
|
||||
| AMO-REQ-008 Existing definition APIs remain compatible | AMO-009 | AMO-TEST-009 | N/A | existing domain tests pass | draft |
|
||||
|
||||
## Validation Commands
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
|
||||
```
|
||||
|
||||
## Risks and Downstream Handoff
|
||||
|
||||
### Implementation Agent
|
||||
|
||||
- Keep model objects identity-only until formulation and solver stages define numerical APIs.
|
||||
- Preserve existing `*Definition` tests.
|
||||
- Prefer RAII ownership through `std::unique_ptr` and const retrieval.
|
||||
|
||||
### Build/Test Executor Agent
|
||||
|
||||
- Use Visual Studio 17 2022, x64, Debug, and `build/msvc-debug`.
|
||||
- Use `python scripts/validate_workspace.py` as canonical validation.
|
||||
|
||||
### Correction Agent
|
||||
|
||||
- Implementation-owned failures are expected to be compile or unit-test failures in model object headers, model object sources, CMake registration, or Domain ownership methods.
|
||||
- Upstream-contract failures include requests to add numerical MITC4 behavior or solver state in this phase.
|
||||
|
||||
### Reference Verification Agent
|
||||
|
||||
- No reference verification is required for this phase.
|
||||
- This phase produces no HDF5 result and consumes no reference artifacts.
|
||||
|
||||
## Open Issues
|
||||
|
||||
- Parser/factory mapping from `*Definition` DTOs to model objects remains deferred.
|
||||
- MITC4 formulation, material constitutive behavior, shell section stiffness, assembly, solver, and HDF5 output remain separate future phases.
|
||||
@@ -0,0 +1,190 @@
|
||||
# Domain Model Foundation Implementation Plan
|
||||
|
||||
## Metadata
|
||||
- feature_id: domain-model-foundation
|
||||
- source_requirement: AGENTS.md; docs/ARCHITECTURE.md; docs/ADR.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 input-model ownership rules
|
||||
- source_reference_models: N/A for this architecture foundation slice
|
||||
- status: ready-for-implementation
|
||||
- owner_agent: implementation-planning-agent
|
||||
- date: 2026-06-08
|
||||
|
||||
## Readiness Check
|
||||
|
||||
| input | required_status | observed_status | decision |
|
||||
| --- | --- | --- | --- |
|
||||
| architecture | Domain, AnalysisModel, AnalysisState, DofManager boundaries documented | documented in docs/ARCHITECTURE.md and docs/ADR.md | proceed |
|
||||
| requirements | Domain ownership rules documented | documented in AGENTS.md and docs/ARCHITECTURE.md | proceed |
|
||||
| formulation | not required for model-container storage | N/A | proceed |
|
||||
| numerical_review | not required for model-container storage | N/A | proceed |
|
||||
| io_definition | parser contract not required; semantic storage boundary documented | sufficient for Domain foundation | proceed |
|
||||
| reference_models | not required because this slice produces no solver result | N/A | proceed |
|
||||
|
||||
## Implementation Scope
|
||||
|
||||
- included_behavior: C++17 core model-definition storage for nodes, element definitions, material definitions, shell property definitions, sets, boundary conditions, nodal loads, and linear static step definitions.
|
||||
- included_behavior: CMake/CTest bootstrap for MSVC x64 Debug validation.
|
||||
- included_behavior: deterministic id validation and const retrieval APIs for stored model definitions.
|
||||
- excluded_behavior: MITC4 stiffness, shape functions, Jacobian checks, assembly, solver, reaction recovery, HDF5 output, Abaqus parser, MKL, TBB, and reference comparison.
|
||||
- non_goals: numerical correctness claims, release readiness, Abaqus/Nastran execution, and stored reference artifact generation.
|
||||
|
||||
## Domain Contract
|
||||
|
||||
`Domain` owns parsed model definitions only. It preserves the model that an input parser or factory layer creates and should be treated as immutable by analysis code after parsing/building is complete.
|
||||
|
||||
Included model definitions:
|
||||
|
||||
- nodes
|
||||
- element definitions
|
||||
- material definitions
|
||||
- shell property definitions
|
||||
- node sets
|
||||
- element sets
|
||||
- boundary conditions
|
||||
- nodal loads
|
||||
- analysis step definitions
|
||||
|
||||
Excluded state:
|
||||
|
||||
- equation ids
|
||||
- sparse matrix structure or values
|
||||
- reduced or full displacement vectors
|
||||
- residual vectors
|
||||
- reaction vectors
|
||||
- current time
|
||||
- increment counters
|
||||
- nonlinear iteration counters
|
||||
- element integration point state
|
||||
- MKL, TBB, or HDF5 handles
|
||||
|
||||
Boundary responsibilities:
|
||||
|
||||
- `DofManager` owns active DOF discovery, constrained/free DOF mapping, equation numbering, sparse pattern connectivity, and full-vector reconstruction.
|
||||
- `AnalysisModel` owns the step-local execution view over active domain objects. It references `Domain` objects by id or stable reference and does not copy the whole domain.
|
||||
- `AnalysisState` owns mutable solution, residual, load vector, time/increment, and future element state.
|
||||
|
||||
Common data rules:
|
||||
|
||||
- All ids use signed 64-bit storage.
|
||||
- Node DOF order is `U1, U2, U3, UR1, UR2, UR3`.
|
||||
- Coordinates and scalar physical values use `double`.
|
||||
- Units are user-consistent and are not enforced or converted by `Domain`.
|
||||
- Duplicate ids fail with `std::invalid_argument`.
|
||||
- Insertions that reference missing required objects fail with `std::invalid_argument`.
|
||||
- Direct lookup of a missing object fails with `std::out_of_range`.
|
||||
- Optional `find*` lookup APIs return `nullptr` for missing objects.
|
||||
- Retrieval APIs return const references or const pointers.
|
||||
|
||||
## Work Breakdown
|
||||
|
||||
| task_id | order | purpose | upstream_trace | depends_on | expected_test_first |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| TASK-001 | 1 | Bootstrap CMake, core target, and CTest for Domain tests | ADR-002 | none | TEST-001 |
|
||||
| TASK-002 | 2 | Define signed 64-bit id aliases and six-DOF ordering constants | ADR-014; AGENTS.md MITC4 scope | TASK-001 | TEST-002 |
|
||||
| TASK-003 | 3 | Add `Node` and node storage/retrieval in `Domain` | ADR-010; docs/ARCHITECTURE.md Domain section | TASK-002 | TEST-003 |
|
||||
| TASK-004 | 4 | Add MITC4 element definition storage without element computation | ADR-004; docs/ARCHITECTURE.md Core Runtime Objects | TASK-003 | TEST-004 |
|
||||
| TASK-005 | 5 | Add material, shell property, node set, and element set storage | docs/ARCHITECTURE.md Domain included data | TASK-004 | TEST-005 |
|
||||
| TASK-006 | 6 | Add boundary condition, nodal load, and linear static step definition storage | ADR-012; docs/ARCHITECTURE.md Domain included data | TASK-005 | TEST-006 |
|
||||
| TASK-007 | 7 | Lock down Domain invariants and failed-insert stability | ADR-010; docs/AGENT_RULES.md boundaries | TASK-006 | TEST-007 |
|
||||
| TASK-008 | 8 | Record build/test evidence and handoff status | docs/build-test-reports/README.md | TASK-007 | TEST-008 |
|
||||
|
||||
## TDD Test Plan
|
||||
|
||||
| test_id | order | test_type | red_condition | green_condition | linked_task | command |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| TEST-001 | 1 | CTest bootstrap | `domain.bootstrap` is not registered or cannot run | `domain.bootstrap` passes under MSVC Debug | TASK-001 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain.bootstrap` |
|
||||
| TEST-002 | 2 | unit | `fesa/core/ModelTypes.hpp` is missing | id aliases are 64-bit and DOF ordinals are 0..5 | TASK-002 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
|
||||
| TEST-003 | 3 | unit | `Node` and `Domain` node APIs are missing | add/find/direct lookup/duplicate node tests pass | TASK-003 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
|
||||
| TEST-004 | 4 | unit | element definition APIs are missing | element add/find/direct lookup/connectivity/missing-node tests pass | TASK-004 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
|
||||
| TEST-005 | 5 | unit | material, property, and set APIs are missing | material/property/set validation tests pass | TASK-005 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
|
||||
| TEST-006 | 6 | unit | boundary, load, and step APIs are missing | BC/load/step storage tests pass | TASK-006 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
|
||||
| TEST-007 | 7 | unit | failed insertion mutates Domain or mutable access leaks | invariant and failed-insert stability tests pass | TASK-007 | `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain` |
|
||||
| TEST-008 | 8 | validation | build/test evidence is missing | build/test report records passing validation or classified failure | TASK-008 | `python scripts/validate_workspace.py` |
|
||||
|
||||
## CMake/CTest Plan
|
||||
|
||||
- target_candidates: `fesa_core`, `fesa_domain_tests`
|
||||
- add_test_needs: `domain.bootstrap` initially, then Domain behavior tests under the same `domain` CTest filter.
|
||||
- labels: `domain`, `core`
|
||||
- msvc_config: Debug
|
||||
- expected_feature_command: `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain`
|
||||
- workspace_validation: `python scripts/validate_workspace.py`
|
||||
|
||||
## Candidate Files and Ownership
|
||||
|
||||
| file_candidate | purpose | owner_boundary | notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `CMakeLists.txt` | root CMake project, core library, CTest registration | build/test bootstrap | MSVC x64 Debug baseline |
|
||||
| `include/fesa/core/ModelTypes.hpp` | id aliases and DOF constants | core model definitions | no solver state |
|
||||
| `include/fesa/core/Node.hpp` | node id and coordinate definition | core model definitions | no displacement storage |
|
||||
| `include/fesa/core/ElementDefinition.hpp` | element id, type, connectivity, property reference | core model definitions | no stiffness behavior |
|
||||
| `include/fesa/core/MaterialDefinition.hpp` | linear elastic material input definition | core model definitions | no constitutive matrix |
|
||||
| `include/fesa/core/PropertyDefinition.hpp` | shell property input definition | core model definitions | no section stiffness |
|
||||
| `include/fesa/core/BoundaryCondition.hpp` | prescribed DOF value definition | core model definitions | no matrix application |
|
||||
| `include/fesa/core/LoadDefinition.hpp` | nodal load definition | core model definitions | no global vector assembly |
|
||||
| `include/fesa/core/StepDefinition.hpp` | linear static step definition | core model definitions | no analysis driver |
|
||||
| `include/fesa/core/Domain.hpp` | Domain public storage/retrieval API | core model definitions | const retrieval only |
|
||||
| `src/core/Domain.cpp` | Domain validation and storage implementation | core model definitions | no external dependencies |
|
||||
| `tests/core/domain_bootstrap_test.cpp` | CTest bootstrap executable | test | created before production code |
|
||||
| `tests/core/model_types_test.cpp` | id and DOF tests | test | optional separate executable |
|
||||
| `tests/core/domain_storage_test.cpp` | Domain behavior tests | test | required before production changes |
|
||||
|
||||
## Data Flow Contract
|
||||
|
||||
1. A future Abaqus parser and factory/registry layer creates semantic model definitions.
|
||||
2. `Domain` stores those definitions and validates duplicate ids and missing required references.
|
||||
3. A future `AnalysisModel` builds a step-local execution view from `Domain` ids or stable references.
|
||||
4. A future `DofManager` derives active DOFs and equation ids outside `Domain`.
|
||||
5. A future `AnalysisState` stores solution and iteration state outside `Domain`.
|
||||
|
||||
## Acceptance Traceability Matrix
|
||||
|
||||
| requirement_id | task_id | test_id | reference_model_id | acceptance_criterion | status |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| DOM-REQ-001 Domain owns parsed model definitions | TASK-003 to TASK-006 | TEST-003 to TEST-006 | N/A | storage and const retrieval tests pass | draft |
|
||||
| DOM-REQ-002 Domain excludes solver state | TASK-007 | TEST-007 | N/A | invariant tests and member review pass | draft |
|
||||
| DOM-REQ-003 ids use signed 64-bit storage | TASK-002 | TEST-002 | N/A | `sizeof(Id) == 8` test passes | draft |
|
||||
| DOM-REQ-004 node DOF order is fixed | TASK-002 | TEST-002 | N/A | DOF ordinal test passes | draft |
|
||||
| DOM-REQ-005 duplicate ids are rejected | TASK-003 to TASK-006 | TEST-003 to TEST-006 | N/A | duplicate tests throw `std::invalid_argument` | draft |
|
||||
| DOM-REQ-006 missing required references are rejected | TASK-004 to TASK-006 | TEST-004 to TEST-006 | N/A | missing-reference tests throw `std::invalid_argument` | draft |
|
||||
| DOM-REQ-007 direct missing lookup is deterministic | TASK-003 to TASK-007 | TEST-003 to TEST-007 | N/A | direct missing lookup throws `std::out_of_range` | draft |
|
||||
| DOM-REQ-008 validation uses MSVC CMake/CTest | TASK-001; TASK-008 | TEST-001; TEST-008 | N/A | configure/build/CTest evidence recorded | draft |
|
||||
|
||||
## Validation Commands
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
```
|
||||
|
||||
## Risks and Downstream Handoff
|
||||
|
||||
### Implementation Agent
|
||||
|
||||
- Keep each storage layer separate and test-first.
|
||||
- Do not add numerical behavior while implementing model definitions.
|
||||
- Preserve `Domain` as a model container, not a solver-state object.
|
||||
|
||||
### Build/Test Executor Agent
|
||||
|
||||
- Use Visual Studio 17 2022, x64, Debug, and `build/msvc-debug`.
|
||||
- After `CMakeLists.txt` exists, `python scripts/validate_workspace.py` must configure, build, and run CTest instead of taking the no-CMake path.
|
||||
|
||||
### Correction Agent
|
||||
|
||||
- Implementation-owned failures are expected to be compile or unit-test failures in `fesa_core` or `fesa_domain_tests`.
|
||||
- Upstream-contract failures include requests to store equation ids, displacements, reaction vectors, or integration point state inside `Domain`.
|
||||
|
||||
### Reference Verification Agent
|
||||
|
||||
- No reference verification is required for this phase.
|
||||
- This phase produces no HDF5 result and consumes no reference artifacts.
|
||||
|
||||
## Open Issues
|
||||
|
||||
- The full MITC4 requirements, formulation, I/O definition, and reference model artifacts are still separate upstream stages.
|
||||
- The parser-to-Domain factory API is intentionally deferred until the Abaqus input subset phase.
|
||||
@@ -0,0 +1,98 @@
|
||||
# Domain Runtime Storage Implementation Plan
|
||||
|
||||
## Objective
|
||||
|
||||
Make `Domain` store runtime model objects as its canonical model representation instead of persisting parser-style definition DTOs.
|
||||
|
||||
This follows the approved direction:
|
||||
|
||||
```text
|
||||
Abaqus .inp parser
|
||||
-> temporary parsed or semantic records
|
||||
-> factory / DomainBuilder
|
||||
-> Domain owns runtime objects
|
||||
```
|
||||
|
||||
The parser and factory are not implemented in this phase. Definition DTOs may remain as temporary parser/factory-local records, but `Domain` must not use them as persistent storage.
|
||||
|
||||
## Phase Overview
|
||||
|
||||
1. `runtime-storage-contract`
|
||||
- Record the migration contract and clarify which model objects are canonical in `Domain`.
|
||||
- Keep parser/factory work out of scope.
|
||||
|
||||
2. `element-material-property-runtime-storage`
|
||||
- Store elements as `fesa::element::Element`.
|
||||
- Store materials as `fesa::material::Material`.
|
||||
- Store shell properties as `fesa::property::ShellProperty`.
|
||||
- Move `ElementType` to `ModelTypes.hpp` so runtime element interfaces do not depend on `ElementDefinition`.
|
||||
|
||||
3. `load-boundary-runtime-storage`
|
||||
- Store loads as `fesa::load::Load`.
|
||||
- Store boundary conditions as `fesa::boundary::BoundaryCondition`.
|
||||
- Validate runtime `NodalLoad` and `SinglePointConstraint` node references during insertion.
|
||||
|
||||
4. `step-and-set-runtime-references`
|
||||
- Validate element sets against runtime elements.
|
||||
- Validate step load and boundary indices against runtime containers.
|
||||
- Keep `LinearStaticStepDefinition` as a step configuration record until a dedicated runtime step object is designed.
|
||||
|
||||
5. `legacy-definition-extraction`
|
||||
- Remove `core::*Definition` DTOs from `Domain` includes, fields, and public API.
|
||||
- Keep focused DTO tests only for parser/factory-local record compatibility.
|
||||
|
||||
6. `validation-report-handoff`
|
||||
- Run harness and CMake/CTest validation.
|
||||
- Update project handoff documents with concrete evidence.
|
||||
|
||||
## Canonical Domain Storage
|
||||
|
||||
`Domain` stores:
|
||||
|
||||
- `fesa::core::Node` values;
|
||||
- `std::unique_ptr<fesa::element::Element>`;
|
||||
- `std::unique_ptr<fesa::material::Material>`;
|
||||
- `fesa::property::ShellProperty` values;
|
||||
- `std::unique_ptr<fesa::load::Load>`;
|
||||
- `std::unique_ptr<fesa::boundary::BoundaryCondition>`;
|
||||
- node sets and element sets by id;
|
||||
- `LinearStaticStepDefinition` as a temporary step configuration record.
|
||||
|
||||
`Domain` must not store:
|
||||
|
||||
- `ElementDefinition`;
|
||||
- `LinearElasticMaterialDefinition`;
|
||||
- `ShellPropertyDefinition`;
|
||||
- `NodalLoadDefinition`;
|
||||
- `fesa::core::BoundaryCondition`.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No Abaqus parser implementation.
|
||||
- No factory/registry implementation.
|
||||
- No MITC4 stiffness, mass, residual, stress, or tangent implementation.
|
||||
- No equation numbering or solver state in `Domain`.
|
||||
- No HDF5, MKL, TBB, reference artifact, tolerance, or formulation changes.
|
||||
|
||||
## Tests
|
||||
|
||||
Primary C++ tests:
|
||||
|
||||
- `/tests/core/domain_storage_test.cpp`
|
||||
- `/tests/core/domain_model_object_test.cpp`
|
||||
- `/tests/core/model_types_test.cpp`
|
||||
- `/tests/element/element_base_test.cpp`
|
||||
- `/tests/element/mitc4_element_model_test.cpp`
|
||||
- `/tests/material/material_base_test.cpp`
|
||||
- `/tests/property/shell_property_test.cpp`
|
||||
- `/tests/load/load_base_test.cpp`
|
||||
- `/tests/boundary/boundary_base_test.cpp`
|
||||
|
||||
Required validation:
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
git diff --check
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace fesa::boundary {
|
||||
|
||||
enum class BoundaryConditionKind {
|
||||
SinglePointConstraint
|
||||
};
|
||||
|
||||
class BoundaryCondition {
|
||||
public:
|
||||
virtual ~BoundaryCondition() = default;
|
||||
|
||||
virtual BoundaryConditionKind kind() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace fesa::boundary
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/boundary/BoundaryCondition.hpp"
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
namespace fesa::boundary {
|
||||
|
||||
using fesa::core::Dof;
|
||||
using fesa::core::NodeId;
|
||||
|
||||
class SinglePointConstraint final : public BoundaryCondition {
|
||||
public:
|
||||
SinglePointConstraint(NodeId node_id, Dof dof, double value);
|
||||
|
||||
BoundaryConditionKind kind() const noexcept override;
|
||||
NodeId nodeId() const noexcept;
|
||||
Dof dof() const noexcept;
|
||||
double value() const noexcept;
|
||||
|
||||
private:
|
||||
NodeId node_id_;
|
||||
Dof dof_;
|
||||
double value_;
|
||||
};
|
||||
|
||||
} // namespace fesa::boundary
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
class BoundaryCondition {
|
||||
public:
|
||||
BoundaryCondition(NodeId node_id, Dof dof, double value);
|
||||
|
||||
NodeId nodeId() const noexcept;
|
||||
Dof dof() const noexcept;
|
||||
double value() const noexcept;
|
||||
|
||||
private:
|
||||
NodeId node_id_;
|
||||
Dof dof_;
|
||||
double value_;
|
||||
};
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/boundary/BoundaryCondition.hpp"
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
#include "fesa/core/Node.hpp"
|
||||
#include "fesa/core/StepDefinition.hpp"
|
||||
#include "fesa/element/Element.hpp"
|
||||
#include "fesa/load/Load.hpp"
|
||||
#include "fesa/material/Material.hpp"
|
||||
#include "fesa/property/ShellProperty.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
class Domain {
|
||||
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 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);
|
||||
std::size_t addLoad(std::unique_ptr<fesa::load::Load> load);
|
||||
void addStep(LinearStaticStepDefinition step);
|
||||
|
||||
const Node* findNode(NodeId id) const noexcept;
|
||||
const Node& node(NodeId id) const;
|
||||
std::size_t nodeCount() const noexcept;
|
||||
|
||||
const fesa::element::Element* findElement(ElementId id) const noexcept;
|
||||
const fesa::element::Element& element(ElementId id) const;
|
||||
std::size_t elementCount() const noexcept;
|
||||
|
||||
const fesa::material::Material* findMaterial(MaterialId id) const noexcept;
|
||||
const fesa::material::Material& material(MaterialId id) const;
|
||||
std::size_t materialCount() 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;
|
||||
|
||||
const std::vector<NodeId>* findNodeSet(const std::string& name) const noexcept;
|
||||
const std::vector<NodeId>& nodeSet(const std::string& name) const;
|
||||
std::size_t nodeSetCount() const noexcept;
|
||||
|
||||
const std::vector<ElementId>* findElementSet(const std::string& name) const noexcept;
|
||||
const std::vector<ElementId>& elementSet(const std::string& name) const;
|
||||
std::size_t elementSetCount() const noexcept;
|
||||
|
||||
const fesa::boundary::BoundaryCondition* findBoundaryCondition(std::size_t index) const noexcept;
|
||||
const fesa::boundary::BoundaryCondition& boundaryCondition(std::size_t index) const;
|
||||
std::size_t boundaryConditionCount() const noexcept;
|
||||
|
||||
const fesa::load::Load* findLoad(std::size_t index) const noexcept;
|
||||
const fesa::load::Load& load(std::size_t index) const;
|
||||
std::size_t loadCount() const noexcept;
|
||||
|
||||
const LinearStaticStepDefinition* findStep(StepId id) const noexcept;
|
||||
const LinearStaticStepDefinition& step(StepId id) const;
|
||||
std::size_t stepCount() const noexcept;
|
||||
|
||||
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<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_;
|
||||
std::vector<std::unique_ptr<fesa::load::Load>> loads_;
|
||||
std::unordered_map<StepId, LinearStaticStepDefinition> steps_;
|
||||
};
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
class ElementDefinition {
|
||||
public:
|
||||
ElementDefinition(
|
||||
ElementId id,
|
||||
ElementType type,
|
||||
std::array<NodeId, 4> connectivity,
|
||||
PropertyId property_id);
|
||||
|
||||
ElementId id() const noexcept;
|
||||
ElementType type() const noexcept;
|
||||
const std::array<NodeId, 4>& connectivity() const noexcept;
|
||||
PropertyId propertyId() const noexcept;
|
||||
|
||||
private:
|
||||
ElementId id_;
|
||||
ElementType type_;
|
||||
std::array<NodeId, 4> connectivity_;
|
||||
PropertyId property_id_;
|
||||
};
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
class NodalLoadDefinition {
|
||||
public:
|
||||
NodalLoadDefinition(NodeId node_id, Dof dof, double value);
|
||||
|
||||
NodeId nodeId() const noexcept;
|
||||
Dof dof() const noexcept;
|
||||
double value() const noexcept;
|
||||
|
||||
private:
|
||||
NodeId node_id_;
|
||||
Dof dof_;
|
||||
double value_;
|
||||
};
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
class LinearElasticMaterialDefinition {
|
||||
public:
|
||||
LinearElasticMaterialDefinition(MaterialId id, double young_modulus, double poisson_ratio);
|
||||
|
||||
MaterialId id() const noexcept;
|
||||
double youngModulus() const noexcept;
|
||||
double poissonRatio() const noexcept;
|
||||
|
||||
private:
|
||||
MaterialId id_;
|
||||
double young_modulus_;
|
||||
double poisson_ratio_;
|
||||
};
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
using Id = std::int64_t;
|
||||
using NodeId = Id;
|
||||
using ElementId = Id;
|
||||
using MaterialId = Id;
|
||||
using PropertyId = Id;
|
||||
using StepId = Id;
|
||||
|
||||
enum class Dof : std::uint8_t {
|
||||
U1 = 0,
|
||||
U2 = 1,
|
||||
U3 = 2,
|
||||
UR1 = 3,
|
||||
UR2 = 4,
|
||||
UR3 = 5
|
||||
};
|
||||
|
||||
enum class ElementType {
|
||||
Mitc4
|
||||
};
|
||||
|
||||
constexpr std::size_t kDofPerNode = 6;
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
class Node {
|
||||
public:
|
||||
Node(NodeId id, double x, double y, double z);
|
||||
|
||||
static constexpr std::size_t dofCount() noexcept { return kDofPerNode; }
|
||||
|
||||
NodeId id() const noexcept;
|
||||
double x() const noexcept;
|
||||
double y() const noexcept;
|
||||
double z() const noexcept;
|
||||
const std::array<double, 3>& coordinates() const noexcept;
|
||||
|
||||
private:
|
||||
NodeId id_;
|
||||
std::array<double, 3> coordinates_;
|
||||
};
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
class ShellPropertyDefinition {
|
||||
public:
|
||||
ShellPropertyDefinition(PropertyId id, MaterialId material_id, double thickness);
|
||||
|
||||
PropertyId id() const noexcept;
|
||||
MaterialId materialId() const noexcept;
|
||||
double thickness() const noexcept;
|
||||
|
||||
private:
|
||||
PropertyId id_;
|
||||
MaterialId material_id_;
|
||||
double thickness_;
|
||||
};
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
class LinearStaticStepDefinition {
|
||||
public:
|
||||
LinearStaticStepDefinition(
|
||||
StepId id,
|
||||
std::string name,
|
||||
std::vector<std::size_t> boundary_condition_indices,
|
||||
std::vector<std::size_t> load_indices);
|
||||
|
||||
StepId id() const noexcept;
|
||||
const std::string& name() const noexcept;
|
||||
const std::vector<std::size_t>& boundaryConditionIndices() const noexcept;
|
||||
const std::vector<std::size_t>& loadIndices() const noexcept;
|
||||
|
||||
private:
|
||||
StepId id_;
|
||||
std::string name_;
|
||||
std::vector<std::size_t> boundary_condition_indices_;
|
||||
std::vector<std::size_t> load_indices_;
|
||||
};
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
namespace fesa::element {
|
||||
|
||||
using fesa::core::ElementId;
|
||||
using fesa::core::ElementType;
|
||||
using fesa::core::NodeId;
|
||||
using fesa::core::PropertyId;
|
||||
|
||||
class Element {
|
||||
public:
|
||||
virtual ~Element() = default;
|
||||
|
||||
virtual ElementId id() const noexcept = 0;
|
||||
virtual ElementType type() const noexcept = 0;
|
||||
virtual std::size_t nodeCount() const noexcept = 0;
|
||||
virtual const std::array<NodeId, 4>& connectivity() const noexcept = 0;
|
||||
virtual PropertyId propertyId() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace fesa::element
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/element/Element.hpp"
|
||||
|
||||
namespace fesa::element {
|
||||
|
||||
class Mitc4Element final : public Element {
|
||||
public:
|
||||
Mitc4Element(ElementId id, std::array<NodeId, 4> connectivity, PropertyId property_id);
|
||||
|
||||
ElementId id() const noexcept override;
|
||||
ElementType type() const noexcept override;
|
||||
std::size_t nodeCount() const noexcept override;
|
||||
std::size_t dofCount() const noexcept;
|
||||
const std::array<NodeId, 4>& connectivity() const noexcept override;
|
||||
PropertyId propertyId() const noexcept override;
|
||||
|
||||
private:
|
||||
ElementId id_;
|
||||
std::array<NodeId, 4> connectivity_;
|
||||
PropertyId property_id_;
|
||||
};
|
||||
|
||||
} // namespace fesa::element
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace fesa::load {
|
||||
|
||||
enum class LoadKind {
|
||||
Nodal
|
||||
};
|
||||
|
||||
class Load {
|
||||
public:
|
||||
virtual ~Load() = default;
|
||||
|
||||
virtual LoadKind kind() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace fesa::load
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
#include "fesa/load/Load.hpp"
|
||||
|
||||
namespace fesa::load {
|
||||
|
||||
using fesa::core::Dof;
|
||||
using fesa::core::NodeId;
|
||||
|
||||
class NodalLoad final : public Load {
|
||||
public:
|
||||
NodalLoad(NodeId node_id, Dof dof, double value);
|
||||
|
||||
LoadKind kind() const noexcept override;
|
||||
NodeId nodeId() const noexcept;
|
||||
Dof dof() const noexcept;
|
||||
double value() const noexcept;
|
||||
|
||||
private:
|
||||
NodeId node_id_;
|
||||
Dof dof_;
|
||||
double value_;
|
||||
};
|
||||
|
||||
} // namespace fesa::load
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/material/Material.hpp"
|
||||
|
||||
namespace fesa::material {
|
||||
|
||||
class LinearElasticMaterial final : public Material {
|
||||
public:
|
||||
LinearElasticMaterial(MaterialId id, double young_modulus, double poisson_ratio);
|
||||
|
||||
MaterialId id() const noexcept override;
|
||||
double youngModulus() const noexcept;
|
||||
double poissonRatio() const noexcept;
|
||||
|
||||
private:
|
||||
MaterialId id_;
|
||||
double young_modulus_;
|
||||
double poisson_ratio_;
|
||||
};
|
||||
|
||||
} // namespace fesa::material
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
namespace fesa::material {
|
||||
|
||||
using fesa::core::MaterialId;
|
||||
|
||||
class Material {
|
||||
public:
|
||||
virtual ~Material() = default;
|
||||
|
||||
virtual MaterialId id() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // namespace fesa::material
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
namespace fesa::property {
|
||||
|
||||
using fesa::core::MaterialId;
|
||||
using fesa::core::PropertyId;
|
||||
|
||||
class ShellProperty {
|
||||
public:
|
||||
ShellProperty(PropertyId id, MaterialId material_id, double thickness);
|
||||
|
||||
PropertyId id() const noexcept;
|
||||
MaterialId materialId() const noexcept;
|
||||
double thickness() const noexcept;
|
||||
|
||||
private:
|
||||
PropertyId id_;
|
||||
MaterialId material_id_;
|
||||
double thickness_;
|
||||
};
|
||||
|
||||
} // namespace fesa::property
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"project": "FESA Structural Solver",
|
||||
"phase": "analysis-model-objects",
|
||||
"steps": [
|
||||
{
|
||||
"step": 0,
|
||||
"name": "model-object-contract",
|
||||
"status": "completed",
|
||||
"summary": "Created analysis model object implementation plan with base-class contracts, exclusions, and TDD traceability."
|
||||
},
|
||||
{
|
||||
"step": 1,
|
||||
"name": "node-model-class",
|
||||
"status": "completed",
|
||||
"summary": "Added Node DOF count contract and tests while keeping Node free of solver state."
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "element-base-interface",
|
||||
"status": "completed",
|
||||
"summary": "Added identity-only Element base interface and model-object CTest target."
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "mitc4-element-model",
|
||||
"status": "completed",
|
||||
"summary": "Added MITC4 element model skeleton with four-node connectivity and 24-DOF count tests."
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "material-base-interface",
|
||||
"status": "completed",
|
||||
"summary": "Added Material base and LinearElasticMaterial model object with ownership tests."
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "property-section-model",
|
||||
"status": "completed",
|
||||
"summary": "Added ShellProperty model object with positive-thickness validation tests."
|
||||
},
|
||||
{
|
||||
"step": 6,
|
||||
"name": "load-base-interface",
|
||||
"status": "completed",
|
||||
"summary": "Added Load base and NodalLoad model object with polymorphic access tests."
|
||||
},
|
||||
{
|
||||
"step": 7,
|
||||
"name": "boundary-base-interface",
|
||||
"status": "completed",
|
||||
"summary": "Added BoundaryCondition base and SinglePointConstraint model object with polymorphic access tests."
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
"name": "domain-polymorphic-ownership",
|
||||
"status": "completed",
|
||||
"summary": "Extended Domain with unique_ptr ownership APIs for element, material, load, and boundary model objects while preserving existing definition APIs."
|
||||
},
|
||||
{
|
||||
"step": 9,
|
||||
"name": "legacy-definition-compatibility",
|
||||
"status": "completed",
|
||||
"summary": "Documented compatibility between core definition DTOs and new runtime model object ownership APIs."
|
||||
},
|
||||
{
|
||||
"step": 10,
|
||||
"name": "validation-report-handoff",
|
||||
"status": "completed",
|
||||
"summary": "Recorded build/test evidence, updated project progress, and marked the phase completed."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# Step 0: model-object-contract
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PLAN.md`
|
||||
- `/docs/PROGRESS.md`
|
||||
- `/docs/WORKNOTE.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/PRD.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Create `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`.
|
||||
|
||||
The document must define the model object layer that will be stored by `Domain`:
|
||||
|
||||
- `Node` model object.
|
||||
- abstract `Element` base and MITC4 model object skeleton.
|
||||
- abstract `Material` base and linear elastic material model object.
|
||||
- shell property/section model object.
|
||||
- abstract `Load` base and nodal load model object.
|
||||
- abstract `BoundaryCondition` base and single-point constraint model object.
|
||||
- `Domain` RAII ownership of polymorphic model objects through `std::unique_ptr`.
|
||||
|
||||
State explicitly that this phase does not implement MITC4 stiffness, material constitutive matrices, global force assembly, boundary-condition matrix application, equation numbering, solver vectors, HDF5 output, reference comparison, or reference-solver execution.
|
||||
|
||||
Document how existing `*Definition` value objects remain compatible as input DTOs until parser/factory work replaces or maps them.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
This is a documentation planning step. Do not write C++ production code in this step.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
Update `/phases/analysis-model-objects/index.json` step 0 with `completed`, `error`, or `blocked`.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not change C++ code in this step.
|
||||
- Do not change MITC4 formulation, I/O contracts, reference artifacts, or tolerance policy.
|
||||
@@ -0,0 +1,53 @@
|
||||
# Step 1: node-model-class
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/Node.hpp`
|
||||
- `/tests/core/node_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Refine the existing `Node` model class contract without adding solver state.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Modify `/include/fesa/core/Node.hpp`
|
||||
- Modify `/src/core/Domain.cpp` only if constructor/accessor definitions move or change
|
||||
- Modify `/tests/core/node_test.cpp`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `Node` exposes `id()`, `coordinates()`, `x()`, `y()`, `z()`.
|
||||
- `Node` exposes `static constexpr std::size_t dofCount()` or equivalent compile-time DOF count of 6.
|
||||
- `Node` does not store equation ids, displacements, residuals, reactions, or constraints.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Extend `/tests/core/node_test.cpp` before production changes:
|
||||
|
||||
- Verify `Node::dofCount() == 6`.
|
||||
- Verify coordinate order is preserved.
|
||||
- Verify the returned coordinate data is const from a const `Node`.
|
||||
|
||||
Run the targeted build and confirm RED before implementation.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
```
|
||||
|
||||
Update `/phases/analysis-model-objects/index.json` step 1 with status and summary.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not add equation ids or solver state to `Node`.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Step 10: validation-report-handoff
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/build-test-reports/README.md`
|
||||
- `/docs/PROGRESS.md`
|
||||
- `/docs/WORKNOTE.md`
|
||||
- `/phases/analysis-model-objects/index.json`
|
||||
|
||||
## Task
|
||||
|
||||
Run final verification and write handoff evidence.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/docs/build-test-reports/analysis-model-objects-build-test.md`
|
||||
- Modify `/docs/PROGRESS.md`
|
||||
- Modify `/docs/WORKNOTE.md` only if new traps or failed approaches occurred
|
||||
- Modify `/phases/analysis-model-objects/index.json`
|
||||
- Modify `/phases/index.json`
|
||||
|
||||
The build/test report must include command log summary, exit codes, configure/build/CTest status, failure classification or `N/A`, and no-change assertions for reference artifacts and tolerance policy.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
This is a validation/reporting step. Do not write new C++ production behavior.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
git status --short --branch
|
||||
git remote -v
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Update:
|
||||
|
||||
- `/phases/analysis-model-objects/index.json` step 10 with `completed`, `error`, or `blocked`.
|
||||
- `/phases/index.json` phase status to `completed` only if all steps are completed.
|
||||
- `/docs/PROGRESS.md` with validation evidence.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not claim MITC4 numerical implementation, reference comparison success, or release readiness.
|
||||
- Do not run Abaqus, Nastran, or any reference solver.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Step 2: element-base-interface
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/ElementDefinition.hpp`
|
||||
- `/CMakeLists.txt`
|
||||
|
||||
## Task
|
||||
|
||||
Add the abstract element model interface.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/element/Element.hpp`
|
||||
- Create `/tests/element/element_base_test.cpp`
|
||||
- Modify `/tests/core/domain_bootstrap_test.cpp` only if the current test executable remains the single test runner
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required interface:
|
||||
|
||||
- virtual destructor.
|
||||
- `ElementId id() const noexcept`.
|
||||
- `ElementType type() const noexcept`.
|
||||
- `std::size_t nodeCount() const noexcept`.
|
||||
- `std::span` is not allowed because this project targets C++17.
|
||||
- Use const reference return for connectivity data.
|
||||
- no stiffness, residual, stress, state, or equation API in this phase.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/element/element_base_test.cpp` before production changes. Use a small test subclass to prove:
|
||||
|
||||
- base pointer dispatch works.
|
||||
- id/type/connectivity/property id are readable through base API.
|
||||
- deleting through `std::unique_ptr<Element>` is valid.
|
||||
|
||||
Run targeted build and confirm RED because `Element.hpp` is missing.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 2 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not implement MITC4 stiffness or numerical element behavior.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Step 3: mitc4-element-model
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/element/Element.hpp`
|
||||
- `/include/fesa/core/ElementDefinition.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add a MITC4 element model object skeleton.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/element/Mitc4Element.hpp`
|
||||
- Create `/src/element/Mitc4Element.cpp`
|
||||
- Create `/tests/element/mitc4_element_model_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `Mitc4Element` derives from `Element`.
|
||||
- Stores element id, four node ids, and property id.
|
||||
- Reports `ElementType::Mitc4`.
|
||||
- Reports `nodeCount() == 4`.
|
||||
- Reports `dofCount() == 24`.
|
||||
- Preserves connectivity order.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/element/mitc4_element_model_test.cpp` before production changes:
|
||||
|
||||
- Construct `Mitc4Element`.
|
||||
- Access it through `const Element&`.
|
||||
- Verify id, type, node count, DOF count, connectivity, and property id.
|
||||
|
||||
Run targeted build and confirm RED because `Mitc4Element.hpp` is missing.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 3 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not implement element stiffness, Jacobians, MITC shear interpolation, resultants, stress, or integration-point state.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Step 4: material-base-interface
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/MaterialDefinition.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add material model base and linear elastic material model object.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/material/Material.hpp`
|
||||
- Create `/include/fesa/material/LinearElasticMaterial.hpp`
|
||||
- Create `/src/material/LinearElasticMaterial.cpp`
|
||||
- Create `/tests/material/material_base_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `Material` has virtual destructor and `MaterialId id() const noexcept`.
|
||||
- `LinearElasticMaterial` derives from `Material`.
|
||||
- Stores Young's modulus and Poisson ratio.
|
||||
- No constitutive matrix or stress update in this phase.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/material/material_base_test.cpp` before production changes:
|
||||
|
||||
- Access `LinearElasticMaterial` through `const Material&`.
|
||||
- Verify id, E, and nu.
|
||||
- Verify deletion through `std::unique_ptr<Material>`.
|
||||
|
||||
Run targeted build and confirm RED.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 4 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not implement constitutive matrices, stress updates, or tangent stiffness.
|
||||
@@ -0,0 +1,53 @@
|
||||
# Step 5: property-section-model
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/PropertyDefinition.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add shell property/section model object.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/property/ShellProperty.hpp`
|
||||
- Create `/src/property/ShellProperty.cpp`
|
||||
- Create `/tests/property/shell_property_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- Stores property id, material id, and thickness.
|
||||
- Exposes const accessors.
|
||||
- Rejects non-positive thickness with `std::invalid_argument`.
|
||||
- Does not compute shell section stiffness.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/property/shell_property_test.cpp` before production changes:
|
||||
|
||||
- Valid property stores id/material/thickness.
|
||||
- Zero or negative thickness throws.
|
||||
|
||||
Run targeted build and confirm RED.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 5 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not implement membrane, bending, or shear constitutive stiffness.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Step 6: load-base-interface
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/LoadDefinition.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add load model base and nodal load object.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/load/Load.hpp`
|
||||
- Create `/include/fesa/load/NodalLoad.hpp`
|
||||
- Create `/src/load/NodalLoad.cpp`
|
||||
- Create `/tests/load/load_base_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `Load` has virtual destructor and a load kind enum.
|
||||
- `NodalLoad` derives from `Load`.
|
||||
- Stores node id, DOF, and value.
|
||||
- No global vector assembly in this phase.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/load/load_base_test.cpp` before production changes:
|
||||
|
||||
- Access `NodalLoad` through `const Load&`.
|
||||
- Verify kind, node id, DOF, value.
|
||||
- Verify deletion through `std::unique_ptr<Load>`.
|
||||
|
||||
Run targeted build and confirm RED.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 6 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not assemble forces or apply time/load factors.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Step 7: boundary-base-interface
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/BoundaryCondition.hpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add boundary condition model base and single-point constraint object.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/boundary/BoundaryCondition.hpp`
|
||||
- Create `/include/fesa/boundary/SinglePointConstraint.hpp`
|
||||
- Create `/src/boundary/SinglePointConstraint.cpp`
|
||||
- Create `/tests/boundary/boundary_base_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `BoundaryCondition` has virtual destructor and a boundary kind enum.
|
||||
- `SinglePointConstraint` derives from `BoundaryCondition`.
|
||||
- Stores node id, DOF, prescribed value.
|
||||
- No matrix elimination or reaction recovery in this phase.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/boundary/boundary_base_test.cpp` before production changes:
|
||||
|
||||
- Access `SinglePointConstraint` through `const BoundaryCondition&`.
|
||||
- Verify kind, node id, DOF, value.
|
||||
- Verify deletion through `std::unique_ptr<BoundaryCondition>`.
|
||||
|
||||
Run targeted build and confirm RED.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
```
|
||||
|
||||
Update step 7 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not apply constraints to matrices or compute reactions.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Step 8: domain-polymorphic-ownership
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- all model object headers created by previous steps
|
||||
|
||||
## Task
|
||||
|
||||
Extend `Domain` with RAII ownership APIs for polymorphic model objects while preserving existing definition-storage tests.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Modify `/include/fesa/core/Domain.hpp`
|
||||
- Modify `/src/core/Domain.cpp`
|
||||
- Create `/tests/core/domain_model_object_test.cpp`
|
||||
- Modify `/tests/core/domain_bootstrap_test.cpp`
|
||||
- Modify `/CMakeLists.txt`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `Domain::addElementObject(std::unique_ptr<Element>)`
|
||||
- `Domain::findElementObject(ElementId)` and `Domain::elementObject(ElementId)`
|
||||
- equivalent add/find/direct APIs for `Material`, `Load`, and model `BoundaryCondition`.
|
||||
- duplicate ids or duplicate load/boundary keys throw `std::invalid_argument`.
|
||||
- missing direct lookup throws `std::out_of_range`.
|
||||
- retrieval returns const base references or const base pointers.
|
||||
- existing `Definition` APIs remain available.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/core/domain_model_object_test.cpp` before production changes:
|
||||
|
||||
- Domain owns `Mitc4Element`, `LinearElasticMaterial`, `NodalLoad`, and `SinglePointConstraint` through `std::unique_ptr`.
|
||||
- Find/direct lookup through base APIs works.
|
||||
- Duplicate ids throw for element/material.
|
||||
- Missing direct lookup throws.
|
||||
|
||||
Run targeted build and confirm RED.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model-object
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
```
|
||||
|
||||
Update step 8 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not remove existing definition APIs in this step.
|
||||
- Do not add solver state to Domain.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Step 9: legacy-definition-compatibility
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- `/tests/core/domain_storage_test.cpp`
|
||||
- `/tests/core/domain_model_object_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Preserve and document compatibility between existing `*Definition` value objects and new model-object base classes.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Modify `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- Modify tests only if compatibility gaps are found
|
||||
- Modify production code only for narrow compatibility fixes
|
||||
|
||||
Required behavior:
|
||||
|
||||
- Existing Domain definition-storage tests still pass.
|
||||
- New model-object ownership tests still pass.
|
||||
- The implementation plan clearly states that `*Definition` objects remain parser/factory DTOs until the parser phase decides whether to map or replace them.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
If production code must change, write or extend a failing C++ test before the change. If this is documentation-only, do not add production code.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
|
||||
```
|
||||
|
||||
Update step 9 status.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not delete existing `*Definition` headers or tests.
|
||||
- Do not rewrite parser or factory behavior.
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"project": "FESA Structural Solver",
|
||||
"phase": "domain-model-foundation",
|
||||
"steps": [
|
||||
{
|
||||
"step": 0,
|
||||
"name": "domain-contract",
|
||||
"status": "completed",
|
||||
"summary": "Created the Domain model foundation implementation plan with ownership boundaries, TDD tasks, and validation commands."
|
||||
},
|
||||
{
|
||||
"step": 1,
|
||||
"name": "cmake-test-bootstrap",
|
||||
"status": "completed",
|
||||
"summary": "Added root CMake/CTest bootstrap for fesa_domain_tests and fixed validation CMake path resolution on Windows."
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "core-id-types",
|
||||
"status": "completed",
|
||||
"summary": "Added ModelTypes.hpp with 64-bit ids and fixed six-DOF ordering covered by C++ tests."
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "node-and-domain-storage",
|
||||
"status": "completed",
|
||||
"summary": "Implemented Node and Domain node storage with duplicate and missing lookup tests."
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "element-definition-storage",
|
||||
"status": "completed",
|
||||
"summary": "Added MITC4 element definition storage with connectivity preservation and missing-node validation tests."
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "model-attribute-storage",
|
||||
"status": "completed",
|
||||
"summary": "Added material, shell property, node set, and element set storage with duplicate and missing-reference tests."
|
||||
},
|
||||
{
|
||||
"step": 6,
|
||||
"name": "boundary-load-step-storage",
|
||||
"status": "completed",
|
||||
"summary": "Added boundary condition, nodal load, and linear static step storage with missing-reference tests."
|
||||
},
|
||||
{
|
||||
"step": 7,
|
||||
"name": "domain-invariants",
|
||||
"status": "completed",
|
||||
"summary": "Locked down const retrieval and failed-insert count stability with invariant tests."
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
"name": "validation-report-handoff",
|
||||
"status": "completed",
|
||||
"summary": "Recorded build/test evidence, updated handoff docs, and marked the phase completed."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
# Step 0: domain-contract
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PLAN.md`
|
||||
- `/docs/PROGRESS.md`
|
||||
- `/docs/WORKNOTE.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/PRD.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/README.md`
|
||||
|
||||
## Task
|
||||
|
||||
Create `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`.
|
||||
|
||||
The document must define the first C++ implementation slice for the `Domain` model container. It must explicitly state:
|
||||
|
||||
- `Domain` owns parsed model definitions only.
|
||||
- Included model definitions: nodes, elements, materials, shell properties, node sets, element sets, boundary conditions, nodal loads, and analysis step definitions.
|
||||
- Excluded state: equation ids, sparse matrix state, displacement vectors, residuals, reactions, current time, iteration counters, and element integration point state.
|
||||
- `DofManager` owns equation numbering.
|
||||
- `AnalysisModel` owns step-local execution views.
|
||||
- `AnalysisState` owns mutable solution and iteration state.
|
||||
- All ids use signed 64-bit storage.
|
||||
- Node DOF order is `U1, U2, U3, UR1, UR2, UR3`.
|
||||
- Units are user-consistent and not enforced by `Domain`.
|
||||
- No Abaqus, Nastran, reference solver, HDF5 result, MKL, or TBB behavior is implemented in this phase.
|
||||
|
||||
Use the implementation-plan README template. Set:
|
||||
|
||||
- `feature_id: domain-model-foundation`
|
||||
- `status: ready-for-implementation`
|
||||
- `owner_agent: implementation-planning-agent`
|
||||
- `date: 2026-06-08`
|
||||
|
||||
Include a work breakdown and TDD test plan for the following downstream steps:
|
||||
|
||||
1. CMake/CTest bootstrap.
|
||||
2. Core id and DOF types.
|
||||
3. Node and node storage.
|
||||
4. Element definition storage.
|
||||
5. Material, property, and set storage.
|
||||
6. Boundary condition, nodal load, and step storage.
|
||||
7. Domain invariant tests.
|
||||
8. Validation report and handoff.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
The current repository may still take the no-CMake informational path until Step 1 creates CMake files.
|
||||
|
||||
Update `/phases/domain-model-foundation/index.json` step 0:
|
||||
|
||||
- On success: `"status": "completed"` and a one-line `"summary"`.
|
||||
- On repeated failure: `"status": "error"` and a concrete `"error_message"`.
|
||||
- On user decision needed: `"status": "blocked"` and a concrete `"blocked_reason"`.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not create C++ headers, sources, tests, or CMake files in this step.
|
||||
- Do not change requirements, formulation, I/O contracts, reference artifacts, or tolerance policy.
|
||||
- Do not run Abaqus, Nastran, or any reference solver.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Step 1: cmake-test-bootstrap
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
|
||||
- `/phases/domain-model-foundation/index.json`
|
||||
- `/phases/domain-model-foundation/step0.md`
|
||||
|
||||
## Task
|
||||
|
||||
Create the minimum C++17/MSVC/CMake/CTest project structure needed to run Domain tests.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/CMakeLists.txt`
|
||||
- Create `/include/fesa/core/.gitkeep` only if needed to preserve the directory before headers exist
|
||||
- Create `/src/core/.gitkeep` only if needed to preserve the directory before sources exist
|
||||
- Create `/tests/core/domain_bootstrap_test.cpp`
|
||||
|
||||
CMake requirements:
|
||||
|
||||
- `cmake_minimum_required(VERSION 3.20)`
|
||||
- Project name: `FESA`
|
||||
- Language: CXX
|
||||
- Set C++ standard to 17 and require it.
|
||||
- Create interface or library target `fesa_core`.
|
||||
- Add include directory `/include`.
|
||||
- Enable testing.
|
||||
- Create test executable `fesa_domain_tests`.
|
||||
- Register CTest name `domain.bootstrap`.
|
||||
- Apply CTest labels `domain;core`.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write `/tests/core/domain_bootstrap_test.cpp` before adding any production C++ code. It should be a minimal self-contained executable with `main()` returning 0 only when the C++ test harness is running.
|
||||
|
||||
Example intended behavior:
|
||||
|
||||
```cpp
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
This step does not implement Domain behavior yet. The purpose is to prove the MSVC/CTest path exists.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
cmake -S . -B build/msvc-debug -G "Visual Studio 17 2022" -A x64
|
||||
cmake --build build/msvc-debug --config Debug
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain.bootstrap
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
Update `/phases/domain-model-foundation/index.json` step 1 with `completed`, `error`, or `blocked`.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not add Domain, Node, Element, solver, parser, MKL, TBB, or HDF5 implementation in this step.
|
||||
- Do not add JavaScript, TypeScript, npm, or non-MSVC fallback tooling.
|
||||
@@ -0,0 +1,76 @@
|
||||
# Step 2: core-id-types
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
|
||||
- `/CMakeLists.txt`
|
||||
- `/tests/core/domain_bootstrap_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Introduce core id aliases and DOF ordering constants.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/core/ModelTypes.hpp`
|
||||
- Modify `/tests/core/domain_bootstrap_test.cpp` or create `/tests/core/model_types_test.cpp`
|
||||
- Modify `/CMakeLists.txt` only to register the new test file if a separate test executable is created
|
||||
|
||||
Required API:
|
||||
|
||||
```cpp
|
||||
namespace fesa::core {
|
||||
using Id = std::int64_t;
|
||||
using NodeId = Id;
|
||||
using ElementId = Id;
|
||||
using MaterialId = Id;
|
||||
using PropertyId = Id;
|
||||
using StepId = Id;
|
||||
|
||||
enum class Dof : std::uint8_t {
|
||||
U1 = 0,
|
||||
U2 = 1,
|
||||
U3 = 2,
|
||||
UR1 = 3,
|
||||
UR2 = 4,
|
||||
UR3 = 5
|
||||
};
|
||||
|
||||
constexpr std::size_t kDofPerNode = 6;
|
||||
}
|
||||
```
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write a failing C++ test that includes `fesa/core/ModelTypes.hpp` before creating the header.
|
||||
|
||||
The test must verify:
|
||||
|
||||
- `sizeof(fesa::core::Id) == 8`
|
||||
- `kDofPerNode == 6`
|
||||
- `Dof::U1` through `Dof::UR3` have ordinal values 0 through 5
|
||||
|
||||
Run the targeted CTest and verify it fails because the header is missing. Then implement the header.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
cmake --build build/msvc-debug --config Debug
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
Update `/phases/domain-model-foundation/index.json` step 2 with `completed`, `error`, or `blocked`.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not add model storage in this step.
|
||||
- Do not add equation numbering or solver state.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Step 3: node-and-domain-storage
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
|
||||
- `/include/fesa/core/ModelTypes.hpp`
|
||||
- `/CMakeLists.txt`
|
||||
|
||||
## Task
|
||||
|
||||
Implement the first useful `Node` and `Domain` slice.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/core/Node.hpp`
|
||||
- Create `/include/fesa/core/Domain.hpp`
|
||||
- Create `/src/core/Domain.cpp`
|
||||
- Create or modify `/tests/core/domain_storage_test.cpp`
|
||||
- Modify `/CMakeLists.txt` to compile the source and register the test
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `Node` stores `NodeId` and three coordinates as `double`.
|
||||
- `Domain::addNode(Node)` inserts a node.
|
||||
- Duplicate node ids throw `std::invalid_argument`.
|
||||
- `Domain::findNode(NodeId)` returns a pointer or `nullptr`.
|
||||
- `Domain::node(NodeId)` returns a const reference or throws `std::out_of_range`.
|
||||
- `Domain::nodeCount()` returns the stored node count.
|
||||
- `Domain` must not expose mutable node references.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write failing tests before creating production headers/sources:
|
||||
|
||||
- Add and retrieve one node by id.
|
||||
- Missing node returns `nullptr` from `findNode`.
|
||||
- Missing node throws from `node`.
|
||||
- Duplicate node id throws.
|
||||
|
||||
Run the targeted CTest and verify failure due to missing API. Then implement the minimal code.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
cmake --build build/msvc-debug --config Debug
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
Update `/phases/domain-model-foundation/index.json` step 3 with `completed`, `error`, or `blocked`.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not add elements, materials, properties, sets, loads, or steps yet.
|
||||
- Do not store equation ids, displacement vectors, residuals, reactions, or solver state.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Step 4: element-definition-storage
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- `/include/fesa/core/Node.hpp`
|
||||
- `/include/fesa/core/ModelTypes.hpp`
|
||||
- `/tests/core/domain_storage_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add element definition storage without implementing element stiffness or MITC4 formulation.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/core/ElementDefinition.hpp`
|
||||
- Modify `/include/fesa/core/Domain.hpp`
|
||||
- Modify `/src/core/Domain.cpp`
|
||||
- Modify `/tests/core/domain_storage_test.cpp`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `ElementDefinition` stores `ElementId`, type enum/string for `MITC4`, four `NodeId` connectivity entries, and a `PropertyId`.
|
||||
- `Domain::addElement(ElementDefinition)` inserts an element.
|
||||
- Duplicate element ids throw `std::invalid_argument`.
|
||||
- Element connectivity must contain exactly four node ids for the MITC4 definition.
|
||||
- Adding an element with a missing node id throws `std::invalid_argument`.
|
||||
- `Domain::findElement(ElementId)` returns pointer or `nullptr`.
|
||||
- `Domain::element(ElementId)` returns const reference or throws `std::out_of_range`.
|
||||
- `Domain::elementCount()` returns the stored element count.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write failing tests before production changes:
|
||||
|
||||
- Add an element with four existing nodes and retrieve connectivity in order.
|
||||
- Duplicate element id throws.
|
||||
- Element referencing a missing node throws.
|
||||
- Missing element lookup follows the `findElement` and `element` contracts.
|
||||
|
||||
Run targeted CTest and verify RED before implementation.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
cmake --build build/msvc-debug --config Debug
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
Update `/phases/domain-model-foundation/index.json` step 4 with `completed`, `error`, or `blocked`.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not implement element stiffness, Jacobians, shape functions, sparse assembly, or solver behavior.
|
||||
- Do not add parser behavior.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Step 5: model-attribute-storage
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- `/include/fesa/core/ElementDefinition.hpp`
|
||||
- `/tests/core/domain_storage_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add minimal material, shell property, node set, and element set storage.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/core/MaterialDefinition.hpp`
|
||||
- Create `/include/fesa/core/PropertyDefinition.hpp`
|
||||
- Modify `/include/fesa/core/Domain.hpp`
|
||||
- Modify `/src/core/Domain.cpp`
|
||||
- Modify `/tests/core/domain_storage_test.cpp`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- `LinearElasticMaterialDefinition` stores material id, Young's modulus, and Poisson's ratio.
|
||||
- `ShellPropertyDefinition` stores property id, material id, and thickness.
|
||||
- `Domain` stores node sets and element sets by string name.
|
||||
- Duplicate material ids, property ids, or set names throw `std::invalid_argument`.
|
||||
- Adding a shell property with missing material id throws `std::invalid_argument`.
|
||||
- Node sets can only reference existing nodes.
|
||||
- Element sets can only reference existing elements.
|
||||
- All retrieval APIs return const data.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write failing tests before production changes:
|
||||
|
||||
- Add material and shell property, then retrieve them.
|
||||
- Duplicate material/property id throws.
|
||||
- Shell property referencing missing material throws.
|
||||
- Node set stores existing node ids in input order.
|
||||
- Element set stores existing element ids in input order.
|
||||
- Sets referencing missing ids throw.
|
||||
|
||||
Run targeted CTest and verify RED before implementation.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
cmake --build build/msvc-debug --config Debug
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
Update `/phases/domain-model-foundation/index.json` step 5 with `completed`, `error`, or `blocked`.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not implement constitutive matrices, shell section stiffness, units conversion, or parser behavior.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Step 6: boundary-load-step-storage
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- `/include/fesa/core/ModelTypes.hpp`
|
||||
- `/tests/core/domain_storage_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add boundary condition, nodal load, and analysis step definition storage.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/include/fesa/core/BoundaryCondition.hpp`
|
||||
- Create `/include/fesa/core/LoadDefinition.hpp`
|
||||
- Create `/include/fesa/core/StepDefinition.hpp`
|
||||
- Modify `/include/fesa/core/Domain.hpp`
|
||||
- Modify `/src/core/Domain.cpp`
|
||||
- Modify `/tests/core/domain_storage_test.cpp`
|
||||
|
||||
Required behavior:
|
||||
|
||||
- A boundary condition stores node id, constrained `Dof`, and prescribed value.
|
||||
- A nodal load stores node id, `Dof`, and value.
|
||||
- A linear static step definition stores step id, name, boundary condition indices or ids, and load indices or ids.
|
||||
- Adding BC/load for a missing node throws `std::invalid_argument`.
|
||||
- Duplicate step ids throw `std::invalid_argument`.
|
||||
- Stored BCs, loads, and steps are returned as const data.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write failing tests before production changes:
|
||||
|
||||
- Add and retrieve a boundary condition on an existing node.
|
||||
- Add and retrieve a nodal load on an existing node.
|
||||
- Missing-node BC/load throws.
|
||||
- Add a linear static step referencing stored BC/load entries.
|
||||
- Duplicate step id throws.
|
||||
|
||||
Run targeted CTest and verify RED before implementation.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
cmake --build build/msvc-debug --config Debug
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
Update `/phases/domain-model-foundation/index.json` step 6 with `completed`, `error`, or `blocked`.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not apply boundary conditions to matrices.
|
||||
- Do not compute reactions.
|
||||
- Do not add solver state, displacements, equation ids, or sparse matrix behavior.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Step 7: domain-invariants
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- `/src/core/Domain.cpp`
|
||||
- `/tests/core/domain_storage_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Add tests and small API cleanup to lock down `Domain` invariants.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Modify `/include/fesa/core/Domain.hpp`
|
||||
- Modify `/src/core/Domain.cpp`
|
||||
- Modify `/tests/core/domain_storage_test.cpp`
|
||||
- Modify `/docs/implementation-plans/domain-model-foundation-implementation-plan.md` only if a test traceability table needs correction
|
||||
|
||||
Required invariants:
|
||||
|
||||
- `Domain` does not expose mutable references to stored objects.
|
||||
- `Domain` has no equation id, displacement, residual, reaction, current time, iteration, sparse matrix, MKL, TBB, or HDF5 members.
|
||||
- Retrieval does not create missing objects implicitly.
|
||||
- Insert operations preserve input ids and ordering where the API promises ordering.
|
||||
- Error types remain deterministic: duplicate ids use `std::invalid_argument`; missing required references use `std::invalid_argument`; direct missing lookup uses `std::out_of_range`.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Write failing compile/runtime tests before API cleanup:
|
||||
|
||||
- A const `Domain` can retrieve model definitions.
|
||||
- Missing direct lookup throws without changing counts.
|
||||
- Failed insert due to missing reference does not change the relevant count.
|
||||
- Existing node, element, material, property, set, BC, load, and step counts remain stable after failed inserts.
|
||||
|
||||
Run targeted CTest and verify RED before implementation.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
cmake --build build/msvc-debug --config Debug
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
Update `/phases/domain-model-foundation/index.json` step 7 with `completed`, `error`, or `blocked`.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not add new solver features beyond invariant enforcement.
|
||||
- Do not refactor unrelated files.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Step 8: validation-report-handoff
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/build-test-reports/README.md`
|
||||
- `/docs/PROGRESS.md`
|
||||
- `/docs/WORKNOTE.md`
|
||||
- `/phases/domain-model-foundation/index.json`
|
||||
- All Domain files created by previous steps
|
||||
|
||||
## Task
|
||||
|
||||
Run final verification and write the handoff evidence.
|
||||
|
||||
Allowed files:
|
||||
|
||||
- Create `/docs/build-test-reports/domain-model-foundation-build-test.md`
|
||||
- Modify `/docs/PROGRESS.md`
|
||||
- Modify `/docs/WORKNOTE.md` only if a failed approach, environment issue, or trap was encountered
|
||||
- Modify `/phases/domain-model-foundation/index.json`
|
||||
- Modify `/phases/index.json`
|
||||
|
||||
The build/test report must include:
|
||||
|
||||
- command log summary with exit codes
|
||||
- command discovery path
|
||||
- configure/build/CTest status
|
||||
- failure classification or `N/A`
|
||||
- failed test inventory or `N/A`
|
||||
- no-change assertion for reference artifacts and tolerance policies
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
This is a validation/reporting step. Do not write new C++ production behavior.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
git status --short --branch
|
||||
git remote -v
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Update:
|
||||
|
||||
- `/phases/domain-model-foundation/index.json` step 8 with `completed`, `error`, or `blocked`.
|
||||
- `/phases/index.json` phase status to `completed` only if all steps are completed.
|
||||
- `/docs/PROGRESS.md` with the executed validation evidence.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Do not claim MITC4 implementation, numerical correctness, reference comparison success, or release readiness.
|
||||
- Do not run Abaqus, Nastran, or any reference solver.
|
||||
- Do not edit reference artifacts.
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"project": "FESA Structural Solver",
|
||||
"phase": "domain-runtime-storage",
|
||||
"steps": [
|
||||
{
|
||||
"step": 0,
|
||||
"name": "runtime-storage-contract",
|
||||
"status": "completed",
|
||||
"summary": "Created the Domain runtime storage implementation plan and recorded the parser-record-to-factory-to-runtime-Domain contract."
|
||||
},
|
||||
{
|
||||
"step": 1,
|
||||
"name": "element-material-property-runtime-storage",
|
||||
"status": "completed",
|
||||
"summary": "Migrated Domain element, material, and shell property storage to runtime objects and moved ElementType to ModelTypes."
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "load-boundary-runtime-storage",
|
||||
"status": "completed",
|
||||
"summary": "Migrated Domain load and boundary storage to runtime polymorphic objects with node-reference and duplicate-key validation."
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "step-and-set-runtime-references",
|
||||
"status": "completed",
|
||||
"summary": "Updated element set and linear static step validation to reference runtime element, load, and boundary containers."
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "legacy-definition-extraction",
|
||||
"status": "completed",
|
||||
"summary": "Removed core definition DTOs from Domain public API and persistent storage while keeping focused definition tests for parser/factory records."
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "validation-report-handoff",
|
||||
"status": "completed",
|
||||
"summary": "Validated with script self-tests, targeted CTest, workspace validation, and git diff whitespace checks."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# Step 0: runtime-storage-contract
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PLAN.md`
|
||||
- `/docs/PROGRESS.md`
|
||||
- `/docs/WORKNOTE.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/implementation-plans/domain-model-foundation-implementation-plan.md`
|
||||
- `/docs/implementation-plans/analysis-model-objects-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- `/src/core/Domain.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Create `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`.
|
||||
|
||||
The plan must state that `Domain` owns runtime model objects as its canonical storage:
|
||||
|
||||
- nodes remain value objects under `fesa::core::Node`;
|
||||
- elements are stored as `fesa::element::Element` objects;
|
||||
- materials are stored as `fesa::material::Material` objects;
|
||||
- shell properties are stored as `fesa::property::ShellProperty` runtime objects;
|
||||
- loads are stored as `fesa::load::Load` objects;
|
||||
- boundary conditions are stored as `fesa::boundary::BoundaryCondition` objects;
|
||||
- steps may remain `LinearStaticStepDefinition` until an analysis-step object is introduced, but step indices must reference runtime load and boundary containers;
|
||||
- `core::*Definition` classes may remain as parser/factory-local records, but `Domain` must not persist them.
|
||||
|
||||
The plan must also list the migration order and identify the C++ tests that will be rewritten or added.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- Documentation-only step. No C++ test is required in this step.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm the plan does not introduce parser implementation work.
|
||||
2. Confirm the plan does not change MITC4 formulation, reference artifacts, or tolerance policy.
|
||||
3. Update `phases/domain-runtime-storage/index.json` for this step result.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Step 1: element-material-property-runtime-storage
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- `/include/fesa/core/ModelTypes.hpp`
|
||||
- `/include/fesa/core/ElementDefinition.hpp`
|
||||
- `/include/fesa/element/Element.hpp`
|
||||
- `/include/fesa/element/Mitc4Element.hpp`
|
||||
- `/include/fesa/material/Material.hpp`
|
||||
- `/include/fesa/material/LinearElasticMaterial.hpp`
|
||||
- `/include/fesa/property/ShellProperty.hpp`
|
||||
- `/src/core/Domain.cpp`
|
||||
- `/tests/core/domain_storage_test.cpp`
|
||||
- `/tests/core/domain_model_object_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Make runtime element, material, and shell property objects the canonical Domain storage.
|
||||
|
||||
Required API shape:
|
||||
|
||||
- `void Domain::addElement(std::unique_ptr<fesa::element::Element> element)`
|
||||
- `const fesa::element::Element* Domain::findElement(ElementId id) const noexcept`
|
||||
- `const fesa::element::Element& Domain::element(ElementId id) const`
|
||||
- `std::size_t Domain::elementCount() const noexcept`
|
||||
- `void Domain::addMaterial(std::unique_ptr<fesa::material::Material> material)`
|
||||
- `const fesa::material::Material* Domain::findMaterial(MaterialId id) const noexcept`
|
||||
- `const fesa::material::Material& Domain::material(MaterialId id) const`
|
||||
- `std::size_t Domain::materialCount() const noexcept`
|
||||
- `void Domain::addShellProperty(fesa::property::ShellProperty property)`
|
||||
- `const fesa::property::ShellProperty* Domain::findShellProperty(PropertyId id) const noexcept`
|
||||
- `const fesa::property::ShellProperty& Domain::shellProperty(PropertyId id) const`
|
||||
- `std::size_t Domain::shellPropertyCount() const noexcept`
|
||||
|
||||
Rules:
|
||||
|
||||
- Move `ElementType` to `/include/fesa/core/ModelTypes.hpp` so runtime `Element` no longer includes `/include/fesa/core/ElementDefinition.hpp`.
|
||||
- `Domain::addShellProperty` must reject duplicate property ids and missing material ids.
|
||||
- `Domain::addElement` must reject null pointers, duplicate element ids, missing node ids, and missing shell property ids.
|
||||
- Keep C++17/MSVC compatibility and RAII ownership.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- Rewrite or extend `/tests/core/domain_storage_test.cpp` so element, material, and shell property storage uses runtime objects instead of `ElementDefinition`, `LinearElasticMaterialDefinition`, and `ShellPropertyDefinition`.
|
||||
- Add or adjust a test that proves `Element` no longer depends on `ElementDefinition` for `ElementType`.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Run the targeted CTest before and after implementation to capture RED then GREEN.
|
||||
2. Do not delete definition classes in this step; only remove them from Domain storage.
|
||||
3. Update `phases/domain-runtime-storage/index.json` for this step result.
|
||||
@@ -0,0 +1,59 @@
|
||||
# Step 2: load-boundary-runtime-storage
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- `/include/fesa/load/Load.hpp`
|
||||
- `/include/fesa/load/NodalLoad.hpp`
|
||||
- `/include/fesa/boundary/BoundaryCondition.hpp`
|
||||
- `/include/fesa/boundary/SinglePointConstraint.hpp`
|
||||
- `/src/core/Domain.cpp`
|
||||
- `/tests/core/domain_storage_test.cpp`
|
||||
- `/tests/core/domain_model_object_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Make runtime load and boundary objects the canonical Domain storage.
|
||||
|
||||
Required API shape:
|
||||
|
||||
- `std::size_t Domain::addLoad(std::unique_ptr<fesa::load::Load> load)`
|
||||
- `const fesa::load::Load* Domain::findLoad(std::size_t index) const noexcept`
|
||||
- `const fesa::load::Load& Domain::load(std::size_t index) const`
|
||||
- `std::size_t Domain::loadCount() const noexcept`
|
||||
- `std::size_t Domain::addBoundaryCondition(std::unique_ptr<fesa::boundary::BoundaryCondition> boundary)`
|
||||
- `const fesa::boundary::BoundaryCondition* Domain::findBoundaryCondition(std::size_t index) const noexcept`
|
||||
- `const fesa::boundary::BoundaryCondition& Domain::boundaryCondition(std::size_t index) const`
|
||||
- `std::size_t Domain::boundaryConditionCount() const noexcept`
|
||||
|
||||
Rules:
|
||||
|
||||
- Reject null load and boundary pointers.
|
||||
- `NodalLoad` must reference an existing node.
|
||||
- `SinglePointConstraint` must reference an existing node.
|
||||
- Duplicate `(node, dof)` nodal loads must throw.
|
||||
- Duplicate `(node, dof)` single-point constraints must throw.
|
||||
- Runtime load and boundary storage must not use `core::NodalLoadDefinition` or `core::BoundaryCondition`.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- Rewrite or extend `/tests/core/domain_storage_test.cpp` so load and boundary storage uses runtime objects.
|
||||
- Add missing-node tests for `NodalLoad` and `SinglePointConstraint` runtime insertion.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "domain|model-object"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Run targeted CTest before production edits and confirm failure.
|
||||
2. Keep solver state out of `Domain`.
|
||||
3. Update `phases/domain-runtime-storage/index.json` for this step result.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Step 3: step-and-set-runtime-references
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- `/include/fesa/core/StepDefinition.hpp`
|
||||
- `/src/core/Domain.cpp`
|
||||
- `/tests/core/domain_storage_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Make Domain set and step validation reference runtime storage.
|
||||
|
||||
Rules:
|
||||
|
||||
- `Domain::addElementSet` must validate ids against runtime elements stored by `Domain::findElement`.
|
||||
- `Domain::addStep(LinearStaticStepDefinition)` may remain for now, but its boundary and load indices must validate against runtime boundary and load containers.
|
||||
- Failed insertions must not mutate Domain counts.
|
||||
- No equation ids, solver vectors, residuals, reactions, current time, or iteration state may be added to Domain.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- Rewrite `/tests/core/domain_storage_test.cpp` set and step tests so they build elements, loads, and boundary conditions through runtime object APIs.
|
||||
- Preserve count-stability tests for failed insertions.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R domain
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Run targeted CTest before production edits and confirm failure.
|
||||
2. Keep `LinearStaticStepDefinition` as the only remaining step record until a separate step-model phase exists.
|
||||
3. Update `phases/domain-runtime-storage/index.json` for this step result.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Step 4: legacy-definition-extraction
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`
|
||||
- `/include/fesa/core/Domain.hpp`
|
||||
- `/src/core/Domain.cpp`
|
||||
- `/tests/core/domain_storage_test.cpp`
|
||||
- `/CMakeLists.txt`
|
||||
|
||||
## Task
|
||||
|
||||
Remove parser-definition DTO persistence from `Domain`.
|
||||
|
||||
Rules:
|
||||
|
||||
- `Domain` must no longer include or store:
|
||||
- `fesa/core/BoundaryCondition.hpp`
|
||||
- `fesa/core/ElementDefinition.hpp`
|
||||
- `fesa/core/LoadDefinition.hpp`
|
||||
- `fesa/core/MaterialDefinition.hpp`
|
||||
- `fesa/core/PropertyDefinition.hpp`
|
||||
- `Domain` may still include `StepDefinition.hpp` until a runtime step object is designed.
|
||||
- Definition classes may remain in the repository and their focused tests may remain, but they must not be part of Domain storage or Domain public API.
|
||||
- Do not implement parser or factory behavior in this step.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- Add or update a compile-time assertion in `/tests/core/domain_storage_test.cpp` or a dedicated domain API test proving Domain retrieval returns runtime types for elements, materials, properties, loads, and boundary conditions.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
git diff --check
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm no `Domain` method returns a definition DTO except `LinearStaticStepDefinition`.
|
||||
2. Confirm `include/fesa/element/Element.hpp` no longer includes `ElementDefinition.hpp`.
|
||||
3. Update `phases/domain-runtime-storage/index.json` for this step result.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Step 5: validation-report-handoff
|
||||
|
||||
## Read First
|
||||
|
||||
Read these files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PLAN.md`
|
||||
- `/docs/PROGRESS.md`
|
||||
- `/docs/WORKNOTE.md`
|
||||
- `/docs/AGENT_RULES.md`
|
||||
- `/docs/implementation-plans/domain-runtime-storage-implementation-plan.md`
|
||||
- `/phases/domain-runtime-storage/index.json`
|
||||
|
||||
## Task
|
||||
|
||||
Record validation evidence and handoff notes after the runtime Domain storage migration.
|
||||
|
||||
Required updates:
|
||||
|
||||
- Update `/docs/PROGRESS.md` with completed runtime Domain storage work and validation commands.
|
||||
- Update `/docs/PLAN.md` only if this migration changes sequencing or acceptance criteria.
|
||||
- Update `/docs/WORKNOTE.md` only if there were failures, repeated mistakes, or environment traps.
|
||||
- Mark `/phases/domain-runtime-storage/index.json` steps and `/phases/index.json` phase status according to actual results.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- Documentation/reporting step. No new C++ test is required.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
git diff --check
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Do not claim numerical solver correctness.
|
||||
2. Do not mark the phase completed unless all previous steps are complete and validation passed.
|
||||
3. Keep validation evidence concrete: command, result, and scope.
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"phases": [
|
||||
{
|
||||
"dir": "domain-model-foundation",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"dir": "analysis-model-objects",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"dir": "domain-runtime-storage",
|
||||
"status": "completed"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -87,7 +87,62 @@ class ValidateWorkspaceTests(unittest.TestCase):
|
||||
with patch.object(validate_workspace.shutil, "which", return_value=None):
|
||||
env = validate_workspace.validation_environment({"PATH": "C:\\Windows\\System32"})
|
||||
|
||||
self.assertTrue(env["PATH"].startswith(str(common_bin)))
|
||||
path_key = "Path" if os.name == "nt" else "PATH"
|
||||
self.assertTrue(env[path_key].startswith(str(common_bin)))
|
||||
|
||||
def test_common_cmake_install_path_updates_existing_windows_path_key(self):
|
||||
validate_workspace = load_validate_workspace()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
common_bin = Path(tmp) / "CMake" / "bin"
|
||||
common_bin.mkdir(parents=True)
|
||||
(common_bin / "cmake.exe").write_text("", encoding="utf-8")
|
||||
with patch.object(validate_workspace, "COMMON_CMAKE_BIN", common_bin):
|
||||
with patch.object(validate_workspace.shutil, "which", return_value=None):
|
||||
env = validate_workspace.validation_environment({"Path": "C:\\Windows\\System32"})
|
||||
|
||||
self.assertIn("Path", env)
|
||||
self.assertNotIn("PATH", env)
|
||||
self.assertTrue(env["Path"].startswith(str(common_bin)))
|
||||
|
||||
def test_common_cmake_install_path_normalizes_uppercase_path_on_windows(self):
|
||||
if os.name != "nt":
|
||||
self.skipTest("Windows-specific subprocess environment behavior")
|
||||
validate_workspace = load_validate_workspace()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
common_bin = Path(tmp) / "CMake" / "bin"
|
||||
common_bin.mkdir(parents=True)
|
||||
(common_bin / "cmake.exe").write_text("", encoding="utf-8")
|
||||
with patch.object(validate_workspace, "COMMON_CMAKE_BIN", common_bin):
|
||||
with patch.object(validate_workspace.shutil, "which", return_value=None):
|
||||
env = validate_workspace.validation_environment({"PATH": "C:\\Windows\\System32"})
|
||||
|
||||
self.assertIn("Path", env)
|
||||
self.assertNotIn("PATH", env)
|
||||
self.assertTrue(env["Path"].startswith(str(common_bin)))
|
||||
|
||||
def test_common_cmake_executable_is_used_when_command_tool_is_not_on_path(self):
|
||||
validate_workspace = load_validate_workspace()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
common_bin = Path(tmp) / "CMake" / "bin"
|
||||
common_bin.mkdir(parents=True)
|
||||
(common_bin / "cmake.exe").write_text("", encoding="utf-8")
|
||||
with patch.object(validate_workspace, "COMMON_CMAKE_BIN", common_bin):
|
||||
with patch.object(validate_workspace.shutil, "which", return_value=None):
|
||||
command = validate_workspace.resolve_validation_command("cmake --version")
|
||||
|
||||
self.assertEqual(command, f'"{common_bin / "cmake.exe"}" --version')
|
||||
|
||||
def test_common_ctest_executable_is_used_when_command_tool_is_not_on_path(self):
|
||||
validate_workspace = load_validate_workspace()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
common_bin = Path(tmp) / "CMake" / "bin"
|
||||
common_bin.mkdir(parents=True)
|
||||
(common_bin / "ctest.exe").write_text("", encoding="utf-8")
|
||||
with patch.object(validate_workspace, "COMMON_CMAKE_BIN", common_bin):
|
||||
with patch.object(validate_workspace.shutil, "which", return_value=None):
|
||||
command = validate_workspace.resolve_validation_command("ctest --version")
|
||||
|
||||
self.assertEqual(command, f'"{common_bin / "ctest.exe"}" --version')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -88,7 +88,27 @@ def discover_commands(root: Path) -> list[str]:
|
||||
return load_cmake_commands(root)
|
||||
|
||||
|
||||
def resolve_validation_command(command: str) -> str:
|
||||
parts = command.split(maxsplit=1)
|
||||
if not parts:
|
||||
return command
|
||||
|
||||
tool = parts[0].lower()
|
||||
if tool not in {"cmake", "ctest"}:
|
||||
return command
|
||||
if shutil.which(tool) is not None:
|
||||
return command
|
||||
|
||||
exe = COMMON_CMAKE_BIN / f"{tool}.exe"
|
||||
if not exe.exists():
|
||||
return command
|
||||
|
||||
suffix = f" {parts[1]}" if len(parts) > 1 else ""
|
||||
return f'"{exe}"{suffix}'
|
||||
|
||||
|
||||
def run_command(command: str, root: Path) -> subprocess.CompletedProcess:
|
||||
command = resolve_validation_command(command)
|
||||
return subprocess.run(
|
||||
command,
|
||||
cwd=root,
|
||||
@@ -103,17 +123,25 @@ def run_command(command: str, root: Path) -> subprocess.CompletedProcess:
|
||||
|
||||
def validation_environment(base_env: os._Environ | dict[str, str]) -> dict[str, str]:
|
||||
env = dict(base_env)
|
||||
if shutil.which("cmake") is not None:
|
||||
path_key = "Path" if os.name == "nt" else "PATH"
|
||||
path_values = []
|
||||
for key in list(env):
|
||||
if key.lower() == "path":
|
||||
path_values.append(env.pop(key))
|
||||
|
||||
current_path = os.pathsep.join(part for part in path_values if part)
|
||||
env[path_key] = current_path
|
||||
|
||||
if shutil.which("cmake", path=current_path) is not None:
|
||||
return env
|
||||
cmake_exe = COMMON_CMAKE_BIN / "cmake.exe"
|
||||
if not cmake_exe.exists():
|
||||
return env
|
||||
|
||||
current_path = env.get("PATH", "")
|
||||
paths = [part for part in current_path.split(os.pathsep) if part]
|
||||
common_bin_text = str(COMMON_CMAKE_BIN)
|
||||
if not any(part.lower() == common_bin_text.lower() for part in paths):
|
||||
env["PATH"] = common_bin_text + (os.pathsep + current_path if current_path else "")
|
||||
env[path_key] = common_bin_text + (os.pathsep + current_path if current_path else "")
|
||||
return env
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "fesa/boundary/SinglePointConstraint.hpp"
|
||||
|
||||
namespace fesa::boundary {
|
||||
|
||||
SinglePointConstraint::SinglePointConstraint(NodeId node_id, Dof dof, double value)
|
||||
: node_id_(node_id), dof_(dof), value_(value) {}
|
||||
|
||||
BoundaryConditionKind SinglePointConstraint::kind() const noexcept {
|
||||
return BoundaryConditionKind::SinglePointConstraint;
|
||||
}
|
||||
|
||||
NodeId SinglePointConstraint::nodeId() const noexcept {
|
||||
return node_id_;
|
||||
}
|
||||
|
||||
Dof SinglePointConstraint::dof() const noexcept {
|
||||
return dof_;
|
||||
}
|
||||
|
||||
double SinglePointConstraint::value() const noexcept {
|
||||
return value_;
|
||||
}
|
||||
|
||||
} // namespace fesa::boundary
|
||||
@@ -0,0 +1,441 @@
|
||||
#include "fesa/core/Domain.hpp"
|
||||
|
||||
#include "fesa/boundary/SinglePointConstraint.hpp"
|
||||
#include "fesa/core/BoundaryCondition.hpp"
|
||||
#include "fesa/core/ElementDefinition.hpp"
|
||||
#include "fesa/core/LoadDefinition.hpp"
|
||||
#include "fesa/core/MaterialDefinition.hpp"
|
||||
#include "fesa/core/PropertyDefinition.hpp"
|
||||
#include "fesa/load/NodalLoad.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
BoundaryCondition::BoundaryCondition(NodeId node_id, Dof dof, double value)
|
||||
: node_id_(node_id), dof_(dof), value_(value) {}
|
||||
|
||||
NodeId BoundaryCondition::nodeId() const noexcept {
|
||||
return node_id_;
|
||||
}
|
||||
|
||||
Dof BoundaryCondition::dof() const noexcept {
|
||||
return dof_;
|
||||
}
|
||||
|
||||
double BoundaryCondition::value() const noexcept {
|
||||
return value_;
|
||||
}
|
||||
|
||||
ElementDefinition::ElementDefinition(
|
||||
ElementId id,
|
||||
ElementType type,
|
||||
std::array<NodeId, 4> connectivity,
|
||||
PropertyId property_id)
|
||||
: id_(id), type_(type), connectivity_(connectivity), property_id_(property_id) {}
|
||||
|
||||
ElementId ElementDefinition::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
ElementType ElementDefinition::type() const noexcept {
|
||||
return type_;
|
||||
}
|
||||
|
||||
const std::array<NodeId, 4>& ElementDefinition::connectivity() const noexcept {
|
||||
return connectivity_;
|
||||
}
|
||||
|
||||
PropertyId ElementDefinition::propertyId() const noexcept {
|
||||
return property_id_;
|
||||
}
|
||||
|
||||
LinearElasticMaterialDefinition::LinearElasticMaterialDefinition(
|
||||
MaterialId id,
|
||||
double young_modulus,
|
||||
double poisson_ratio)
|
||||
: id_(id), young_modulus_(young_modulus), poisson_ratio_(poisson_ratio) {}
|
||||
|
||||
MaterialId LinearElasticMaterialDefinition::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
double LinearElasticMaterialDefinition::youngModulus() const noexcept {
|
||||
return young_modulus_;
|
||||
}
|
||||
|
||||
double LinearElasticMaterialDefinition::poissonRatio() const noexcept {
|
||||
return poisson_ratio_;
|
||||
}
|
||||
|
||||
LinearStaticStepDefinition::LinearStaticStepDefinition(
|
||||
StepId id,
|
||||
std::string name,
|
||||
std::vector<std::size_t> boundary_condition_indices,
|
||||
std::vector<std::size_t> load_indices)
|
||||
: id_(id),
|
||||
name_(std::move(name)),
|
||||
boundary_condition_indices_(std::move(boundary_condition_indices)),
|
||||
load_indices_(std::move(load_indices)) {}
|
||||
|
||||
StepId LinearStaticStepDefinition::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
const std::string& LinearStaticStepDefinition::name() const noexcept {
|
||||
return name_;
|
||||
}
|
||||
|
||||
const std::vector<std::size_t>& LinearStaticStepDefinition::boundaryConditionIndices() const noexcept {
|
||||
return boundary_condition_indices_;
|
||||
}
|
||||
|
||||
const std::vector<std::size_t>& LinearStaticStepDefinition::loadIndices() const noexcept {
|
||||
return load_indices_;
|
||||
}
|
||||
|
||||
NodalLoadDefinition::NodalLoadDefinition(NodeId node_id, Dof dof, double value)
|
||||
: node_id_(node_id), dof_(dof), value_(value) {}
|
||||
|
||||
NodeId NodalLoadDefinition::nodeId() const noexcept {
|
||||
return node_id_;
|
||||
}
|
||||
|
||||
Dof NodalLoadDefinition::dof() const noexcept {
|
||||
return dof_;
|
||||
}
|
||||
|
||||
double NodalLoadDefinition::value() const noexcept {
|
||||
return value_;
|
||||
}
|
||||
|
||||
Node::Node(NodeId id, double x, double y, double z)
|
||||
: id_(id), coordinates_{x, y, z} {}
|
||||
|
||||
NodeId Node::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
double Node::x() const noexcept {
|
||||
return coordinates_[0];
|
||||
}
|
||||
|
||||
double Node::y() const noexcept {
|
||||
return coordinates_[1];
|
||||
}
|
||||
|
||||
double Node::z() const noexcept {
|
||||
return coordinates_[2];
|
||||
}
|
||||
|
||||
const std::array<double, 3>& Node::coordinates() const noexcept {
|
||||
return coordinates_;
|
||||
}
|
||||
|
||||
ShellPropertyDefinition::ShellPropertyDefinition(
|
||||
PropertyId id,
|
||||
MaterialId material_id,
|
||||
double thickness)
|
||||
: id_(id), material_id_(material_id), thickness_(thickness) {}
|
||||
|
||||
PropertyId ShellPropertyDefinition::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
MaterialId ShellPropertyDefinition::materialId() const noexcept {
|
||||
return material_id_;
|
||||
}
|
||||
|
||||
double ShellPropertyDefinition::thickness() const noexcept {
|
||||
return thickness_;
|
||||
}
|
||||
|
||||
void Domain::addNode(Node node) {
|
||||
const NodeId id = node.id();
|
||||
const auto inserted = nodes_.emplace(id, std::move(node));
|
||||
if (!inserted.second) {
|
||||
throw std::invalid_argument("duplicate node id");
|
||||
}
|
||||
}
|
||||
|
||||
void Domain::addElement(std::unique_ptr<fesa::element::Element> element) {
|
||||
if (!element) {
|
||||
throw std::invalid_argument("element is null");
|
||||
}
|
||||
const ElementId id = element->id();
|
||||
if (elements_.find(id) != elements_.end()) {
|
||||
throw std::invalid_argument("duplicate element id");
|
||||
}
|
||||
for (const NodeId node_id : element->connectivity()) {
|
||||
if (findNode(node_id) == nullptr) {
|
||||
throw std::invalid_argument("element references missing node id");
|
||||
}
|
||||
}
|
||||
if (findShellProperty(element->propertyId()) == nullptr) {
|
||||
throw std::invalid_argument("element references missing shell property id");
|
||||
}
|
||||
elements_.emplace(id, std::move(element));
|
||||
}
|
||||
|
||||
void Domain::addMaterial(std::unique_ptr<fesa::material::Material> material) {
|
||||
if (!material) {
|
||||
throw std::invalid_argument("material is null");
|
||||
}
|
||||
const MaterialId id = material->id();
|
||||
const auto inserted = materials_.emplace(id, std::move(material));
|
||||
if (!inserted.second) {
|
||||
throw std::invalid_argument("duplicate material id");
|
||||
}
|
||||
}
|
||||
|
||||
void Domain::addShellProperty(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");
|
||||
}
|
||||
if (findMaterial(property.materialId()) == nullptr) {
|
||||
throw std::invalid_argument("shell property references missing material id");
|
||||
}
|
||||
shell_properties_.emplace(id, std::move(property));
|
||||
}
|
||||
|
||||
void Domain::addNodeSet(std::string name, std::vector<NodeId> node_ids) {
|
||||
if (node_sets_.find(name) != node_sets_.end()) {
|
||||
throw std::invalid_argument("duplicate node set name");
|
||||
}
|
||||
for (const NodeId node_id : node_ids) {
|
||||
if (findNode(node_id) == nullptr) {
|
||||
throw std::invalid_argument("node set references missing node id");
|
||||
}
|
||||
}
|
||||
node_sets_.emplace(std::move(name), std::move(node_ids));
|
||||
}
|
||||
|
||||
void Domain::addElementSet(std::string name, std::vector<ElementId> element_ids) {
|
||||
if (element_sets_.find(name) != element_sets_.end()) {
|
||||
throw std::invalid_argument("duplicate element set name");
|
||||
}
|
||||
for (const ElementId element_id : element_ids) {
|
||||
if (findElement(element_id) == nullptr) {
|
||||
throw std::invalid_argument("element set references missing element id");
|
||||
}
|
||||
}
|
||||
element_sets_.emplace(std::move(name), std::move(element_ids));
|
||||
}
|
||||
|
||||
std::size_t Domain::addBoundaryCondition(std::unique_ptr<fesa::boundary::BoundaryCondition> boundary) {
|
||||
if (!boundary) {
|
||||
throw std::invalid_argument("boundary condition is null");
|
||||
}
|
||||
if (const auto* spc = dynamic_cast<const fesa::boundary::SinglePointConstraint*>(boundary.get())) {
|
||||
if (findNode(spc->nodeId()) == nullptr) {
|
||||
throw std::invalid_argument("boundary condition references missing node id");
|
||||
}
|
||||
for (const auto& existing : boundary_conditions_) {
|
||||
const auto* existing_spc =
|
||||
dynamic_cast<const fesa::boundary::SinglePointConstraint*>(existing.get());
|
||||
if (existing_spc != nullptr &&
|
||||
existing_spc->nodeId() == spc->nodeId() &&
|
||||
existing_spc->dof() == spc->dof()) {
|
||||
throw std::invalid_argument("duplicate boundary condition key");
|
||||
}
|
||||
}
|
||||
}
|
||||
const std::size_t index = boundary_conditions_.size();
|
||||
boundary_conditions_.push_back(std::move(boundary));
|
||||
return index;
|
||||
}
|
||||
|
||||
std::size_t Domain::addLoad(std::unique_ptr<fesa::load::Load> load) {
|
||||
if (!load) {
|
||||
throw std::invalid_argument("load is null");
|
||||
}
|
||||
if (const auto* nodal = dynamic_cast<const fesa::load::NodalLoad*>(load.get())) {
|
||||
if (findNode(nodal->nodeId()) == nullptr) {
|
||||
throw std::invalid_argument("nodal load references missing node id");
|
||||
}
|
||||
for (const auto& existing : loads_) {
|
||||
const auto* existing_nodal = dynamic_cast<const fesa::load::NodalLoad*>(existing.get());
|
||||
if (existing_nodal != nullptr &&
|
||||
existing_nodal->nodeId() == nodal->nodeId() &&
|
||||
existing_nodal->dof() == nodal->dof()) {
|
||||
throw std::invalid_argument("duplicate nodal load key");
|
||||
}
|
||||
}
|
||||
}
|
||||
const std::size_t index = loads_.size();
|
||||
loads_.push_back(std::move(load));
|
||||
return index;
|
||||
}
|
||||
|
||||
void Domain::addStep(LinearStaticStepDefinition step) {
|
||||
const StepId id = step.id();
|
||||
if (steps_.find(id) != steps_.end()) {
|
||||
throw std::invalid_argument("duplicate step id");
|
||||
}
|
||||
for (const std::size_t index : step.boundaryConditionIndices()) {
|
||||
if (index >= boundary_conditions_.size()) {
|
||||
throw std::invalid_argument("step references missing boundary condition");
|
||||
}
|
||||
}
|
||||
for (const std::size_t index : step.loadIndices()) {
|
||||
if (index >= loads_.size()) {
|
||||
throw std::invalid_argument("step references missing load");
|
||||
}
|
||||
}
|
||||
steps_.emplace(id, std::move(step));
|
||||
}
|
||||
|
||||
const Node* Domain::findNode(NodeId id) const noexcept {
|
||||
const auto it = nodes_.find(id);
|
||||
return it == nodes_.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
const Node& Domain::node(NodeId id) const {
|
||||
const Node* found = findNode(id);
|
||||
if (found == nullptr) {
|
||||
throw std::out_of_range("node id not found");
|
||||
}
|
||||
return *found;
|
||||
}
|
||||
|
||||
std::size_t Domain::nodeCount() const noexcept {
|
||||
return nodes_.size();
|
||||
}
|
||||
|
||||
const fesa::element::Element* Domain::findElement(ElementId id) const noexcept {
|
||||
const auto it = elements_.find(id);
|
||||
return it == elements_.end() ? nullptr : it->second.get();
|
||||
}
|
||||
|
||||
const fesa::element::Element& Domain::element(ElementId id) const {
|
||||
const fesa::element::Element* found = findElement(id);
|
||||
if (found == nullptr) {
|
||||
throw std::out_of_range("element id not found");
|
||||
}
|
||||
return *found;
|
||||
}
|
||||
|
||||
std::size_t Domain::elementCount() const noexcept {
|
||||
return elements_.size();
|
||||
}
|
||||
|
||||
const fesa::material::Material* Domain::findMaterial(MaterialId id) const noexcept {
|
||||
const auto it = materials_.find(id);
|
||||
return it == materials_.end() ? nullptr : it->second.get();
|
||||
}
|
||||
|
||||
const fesa::material::Material& Domain::material(MaterialId id) const {
|
||||
const fesa::material::Material* found = findMaterial(id);
|
||||
if (found == nullptr) {
|
||||
throw std::out_of_range("material id not found");
|
||||
}
|
||||
return *found;
|
||||
}
|
||||
|
||||
std::size_t Domain::materialCount() const noexcept {
|
||||
return materials_.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;
|
||||
}
|
||||
|
||||
const fesa::property::ShellProperty& Domain::shellProperty(PropertyId id) const {
|
||||
const fesa::property::ShellProperty* found = findShellProperty(id);
|
||||
if (found == nullptr) {
|
||||
throw std::out_of_range("shell property id not found");
|
||||
}
|
||||
return *found;
|
||||
}
|
||||
|
||||
std::size_t Domain::shellPropertyCount() const noexcept {
|
||||
return shell_properties_.size();
|
||||
}
|
||||
|
||||
const std::vector<NodeId>* Domain::findNodeSet(const std::string& name) const noexcept {
|
||||
const auto it = node_sets_.find(name);
|
||||
return it == node_sets_.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
const std::vector<NodeId>& Domain::nodeSet(const std::string& name) const {
|
||||
const std::vector<NodeId>* found = findNodeSet(name);
|
||||
if (found == nullptr) {
|
||||
throw std::out_of_range("node set not found");
|
||||
}
|
||||
return *found;
|
||||
}
|
||||
|
||||
std::size_t Domain::nodeSetCount() const noexcept {
|
||||
return node_sets_.size();
|
||||
}
|
||||
|
||||
const std::vector<ElementId>* Domain::findElementSet(const std::string& name) const noexcept {
|
||||
const auto it = element_sets_.find(name);
|
||||
return it == element_sets_.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
const std::vector<ElementId>& Domain::elementSet(const std::string& name) const {
|
||||
const std::vector<ElementId>* found = findElementSet(name);
|
||||
if (found == nullptr) {
|
||||
throw std::out_of_range("element set not found");
|
||||
}
|
||||
return *found;
|
||||
}
|
||||
|
||||
std::size_t Domain::elementSetCount() const noexcept {
|
||||
return element_sets_.size();
|
||||
}
|
||||
|
||||
const fesa::boundary::BoundaryCondition* Domain::findBoundaryCondition(std::size_t index) const noexcept {
|
||||
return index < boundary_conditions_.size() ? boundary_conditions_[index].get() : nullptr;
|
||||
}
|
||||
|
||||
const fesa::boundary::BoundaryCondition& Domain::boundaryCondition(std::size_t index) const {
|
||||
const fesa::boundary::BoundaryCondition* found = findBoundaryCondition(index);
|
||||
if (found == nullptr) {
|
||||
throw std::out_of_range("boundary condition index not found");
|
||||
}
|
||||
return *found;
|
||||
}
|
||||
|
||||
std::size_t Domain::boundaryConditionCount() const noexcept {
|
||||
return boundary_conditions_.size();
|
||||
}
|
||||
|
||||
const fesa::load::Load* Domain::findLoad(std::size_t index) const noexcept {
|
||||
return index < loads_.size() ? loads_[index].get() : nullptr;
|
||||
}
|
||||
|
||||
const fesa::load::Load& Domain::load(std::size_t index) const {
|
||||
const fesa::load::Load* found = findLoad(index);
|
||||
if (found == nullptr) {
|
||||
throw std::out_of_range("load index not found");
|
||||
}
|
||||
return *found;
|
||||
}
|
||||
|
||||
std::size_t Domain::loadCount() const noexcept {
|
||||
return loads_.size();
|
||||
}
|
||||
|
||||
const LinearStaticStepDefinition* Domain::findStep(StepId id) const noexcept {
|
||||
const auto it = steps_.find(id);
|
||||
return it == steps_.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
const LinearStaticStepDefinition& Domain::step(StepId id) const {
|
||||
const LinearStaticStepDefinition* found = findStep(id);
|
||||
if (found == nullptr) {
|
||||
throw std::out_of_range("step id not found");
|
||||
}
|
||||
return *found;
|
||||
}
|
||||
|
||||
std::size_t Domain::stepCount() const noexcept {
|
||||
return steps_.size();
|
||||
}
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "fesa/element/Mitc4Element.hpp"
|
||||
|
||||
#include "fesa/core/Node.hpp"
|
||||
|
||||
namespace fesa::element {
|
||||
|
||||
Mitc4Element::Mitc4Element(
|
||||
ElementId id,
|
||||
std::array<NodeId, 4> connectivity,
|
||||
PropertyId property_id)
|
||||
: id_(id), connectivity_(connectivity), property_id_(property_id) {}
|
||||
|
||||
ElementId Mitc4Element::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
ElementType Mitc4Element::type() const noexcept {
|
||||
return ElementType::Mitc4;
|
||||
}
|
||||
|
||||
std::size_t Mitc4Element::nodeCount() const noexcept {
|
||||
return connectivity_.size();
|
||||
}
|
||||
|
||||
std::size_t Mitc4Element::dofCount() const noexcept {
|
||||
return connectivity_.size() * fesa::core::Node::dofCount();
|
||||
}
|
||||
|
||||
const std::array<NodeId, 4>& Mitc4Element::connectivity() const noexcept {
|
||||
return connectivity_;
|
||||
}
|
||||
|
||||
PropertyId Mitc4Element::propertyId() const noexcept {
|
||||
return property_id_;
|
||||
}
|
||||
|
||||
} // namespace fesa::element
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "fesa/load/NodalLoad.hpp"
|
||||
|
||||
namespace fesa::load {
|
||||
|
||||
NodalLoad::NodalLoad(NodeId node_id, Dof dof, double value)
|
||||
: node_id_(node_id), dof_(dof), value_(value) {}
|
||||
|
||||
LoadKind NodalLoad::kind() const noexcept {
|
||||
return LoadKind::Nodal;
|
||||
}
|
||||
|
||||
NodeId NodalLoad::nodeId() const noexcept {
|
||||
return node_id_;
|
||||
}
|
||||
|
||||
Dof NodalLoad::dof() const noexcept {
|
||||
return dof_;
|
||||
}
|
||||
|
||||
double NodalLoad::value() const noexcept {
|
||||
return value_;
|
||||
}
|
||||
|
||||
} // namespace fesa::load
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "fesa/material/LinearElasticMaterial.hpp"
|
||||
|
||||
namespace fesa::material {
|
||||
|
||||
LinearElasticMaterial::LinearElasticMaterial(
|
||||
MaterialId id,
|
||||
double young_modulus,
|
||||
double poisson_ratio)
|
||||
: id_(id), young_modulus_(young_modulus), poisson_ratio_(poisson_ratio) {}
|
||||
|
||||
MaterialId LinearElasticMaterial::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
double LinearElasticMaterial::youngModulus() const noexcept {
|
||||
return young_modulus_;
|
||||
}
|
||||
|
||||
double LinearElasticMaterial::poissonRatio() const noexcept {
|
||||
return poisson_ratio_;
|
||||
}
|
||||
|
||||
} // namespace fesa::material
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "fesa/property/ShellProperty.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace fesa::property {
|
||||
|
||||
ShellProperty::ShellProperty(PropertyId id, MaterialId material_id, double thickness)
|
||||
: id_(id), material_id_(material_id), thickness_(thickness) {
|
||||
if (thickness <= 0.0) {
|
||||
throw std::invalid_argument("shell property thickness must be positive");
|
||||
}
|
||||
}
|
||||
|
||||
PropertyId ShellProperty::id() const noexcept {
|
||||
return id_;
|
||||
}
|
||||
|
||||
MaterialId ShellProperty::materialId() const noexcept {
|
||||
return material_id_;
|
||||
}
|
||||
|
||||
double ShellProperty::thickness() const noexcept {
|
||||
return thickness_;
|
||||
}
|
||||
|
||||
} // namespace fesa::property
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "fesa/boundary/BoundaryCondition.hpp"
|
||||
#include "fesa/boundary/SinglePointConstraint.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_boundary_base_tests() {
|
||||
std::unique_ptr<fesa::boundary::BoundaryCondition> owned =
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::UR3, 0.25);
|
||||
const fesa::boundary::BoundaryCondition& boundary = *owned;
|
||||
const auto& spc = static_cast<const fesa::boundary::SinglePointConstraint&>(boundary);
|
||||
|
||||
if (const int result = require(boundary.kind() == fesa::boundary::BoundaryConditionKind::SinglePointConstraint);
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(spc.nodeId() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(spc.dof() == fesa::core::Dof::UR3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(spc.value() == 0.25);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "fesa/core/BoundaryCondition.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_boundary_condition_tests() {
|
||||
const fesa::core::BoundaryCondition condition{1, fesa::core::Dof::UR3, 0.25};
|
||||
|
||||
if (const int result = require(condition.nodeId() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(condition.dof() == fesa::core::Dof::UR3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(condition.value() == 0.25); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
int run_boundary_condition_tests();
|
||||
int run_domain_model_object_tests();
|
||||
int run_domain_storage_tests();
|
||||
int run_element_definition_tests();
|
||||
int run_load_definition_tests();
|
||||
int run_material_definition_tests();
|
||||
int run_model_types_tests();
|
||||
int run_node_tests();
|
||||
int run_property_definition_tests();
|
||||
int run_step_definition_tests();
|
||||
|
||||
int main() {
|
||||
if (const int result = run_model_types_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_node_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_element_definition_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_material_definition_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_property_definition_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_boundary_condition_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_domain_model_object_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_load_definition_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_step_definition_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_domain_storage_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
#include "fesa/boundary/SinglePointConstraint.hpp"
|
||||
#include "fesa/core/Domain.hpp"
|
||||
#include "fesa/element/Mitc4Element.hpp"
|
||||
#include "fesa/load/NodalLoad.hpp"
|
||||
#include "fesa/material/LinearElasticMaterial.hpp"
|
||||
#include "fesa/property/ShellProperty.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
template <typename Exception, typename Function>
|
||||
int require_throws(Function&& function) {
|
||||
try {
|
||||
function();
|
||||
} catch (const Exception&) {
|
||||
return 0;
|
||||
} catch (...) {
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
fesa::core::Domain populated_domain() {
|
||||
fesa::core::Domain domain;
|
||||
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||
domain.addNode(fesa::core::Node{2, 1.0, 0.0, 0.0});
|
||||
domain.addNode(fesa::core::Node{3, 1.0, 1.0, 0.0});
|
||||
domain.addNode(fesa::core::Node{4, 0.0, 1.0, 0.0});
|
||||
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3));
|
||||
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.01});
|
||||
return domain;
|
||||
}
|
||||
|
||||
int domain_owns_element_and_material_objects() {
|
||||
fesa::core::Domain domain = populated_domain();
|
||||
|
||||
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
|
||||
100,
|
||||
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
|
||||
500));
|
||||
|
||||
const fesa::element::Element* element = domain.findElement(100);
|
||||
if (const int result = require(element != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element->type() == fesa::core::ElementType::Mitc4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.element(100).propertyId() == 500); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const fesa::material::Material* material = domain.findMaterial(700);
|
||||
if (const int result = require(material != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(domain.material(700).id() == 700);
|
||||
}
|
||||
|
||||
int duplicate_element_and_material_object_ids_throw() {
|
||||
fesa::core::Domain domain = populated_domain();
|
||||
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
|
||||
100,
|
||||
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
|
||||
500));
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
|
||||
100,
|
||||
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
|
||||
500));
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 100.0, 0.25));
|
||||
});
|
||||
}
|
||||
|
||||
int missing_model_object_direct_lookup_throws() {
|
||||
const fesa::core::Domain domain;
|
||||
|
||||
if (const int result = require(domain.findElement(404) == nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.findMaterial(404) == nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require_throws<std::out_of_range>([&domain]() {
|
||||
(void)domain.element(404);
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require_throws<std::out_of_range>([&domain]() {
|
||||
(void)domain.material(404);
|
||||
});
|
||||
}
|
||||
|
||||
int domain_owns_load_and_boundary_objects_by_index() {
|
||||
fesa::core::Domain domain = populated_domain();
|
||||
|
||||
const std::size_t load_index = domain.addLoad(
|
||||
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
|
||||
const std::size_t boundary_index = domain.addBoundaryCondition(
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
|
||||
|
||||
if (const int result = require(load_index == 0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(boundary_index == 0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.findLoad(load_index) != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.findBoundaryCondition(boundary_index) != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.load(load_index).kind() == fesa::load::LoadKind::Nodal); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(
|
||||
domain.boundaryCondition(boundary_index).kind() ==
|
||||
fesa::boundary::BoundaryConditionKind::SinglePointConstraint);
|
||||
}
|
||||
|
||||
int duplicate_load_and_boundary_keys_throw() {
|
||||
fesa::core::Domain domain = populated_domain();
|
||||
domain.addLoad(std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
|
||||
domain.addBoundaryCondition(std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addLoad(std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -200.0));
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addBoundaryCondition(std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 1.0));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_domain_model_object_tests() {
|
||||
if (const int result = domain_owns_element_and_material_objects(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = duplicate_element_and_material_object_ids_throw(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = missing_model_object_direct_lookup_throws(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = domain_owns_load_and_boundary_objects_by_index(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = duplicate_load_and_boundary_keys_throw(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,679 @@
|
||||
#include "fesa/boundary/SinglePointConstraint.hpp"
|
||||
#include "fesa/core/Domain.hpp"
|
||||
#include "fesa/core/Node.hpp"
|
||||
#include "fesa/core/StepDefinition.hpp"
|
||||
#include "fesa/element/Mitc4Element.hpp"
|
||||
#include "fesa/load/NodalLoad.hpp"
|
||||
#include "fesa/material/LinearElasticMaterial.hpp"
|
||||
#include "fesa/property/ShellProperty.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void add_four_nodes(fesa::core::Domain& domain) {
|
||||
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||
domain.addNode(fesa::core::Node{2, 1.0, 0.0, 0.0});
|
||||
domain.addNode(fesa::core::Node{3, 1.0, 1.0, 0.0});
|
||||
domain.addNode(fesa::core::Node{4, 0.0, 1.0, 0.0});
|
||||
}
|
||||
|
||||
void add_material_and_property(fesa::core::Domain& domain) {
|
||||
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3));
|
||||
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.01});
|
||||
}
|
||||
|
||||
void add_element(fesa::core::Domain& domain) {
|
||||
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
|
||||
100,
|
||||
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
|
||||
500));
|
||||
}
|
||||
|
||||
int add_and_retrieve_node_by_id() {
|
||||
fesa::core::Domain domain;
|
||||
|
||||
domain.addNode(fesa::core::Node{10, 1.0, 2.0, 3.0});
|
||||
|
||||
if (const int result = require(domain.nodeCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const fesa::core::Node* found = domain.findNode(10);
|
||||
if (const int result = require(found != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->id() == 10); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->x() == 1.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->y() == 2.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->z() == 3.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const fesa::core::Node& direct = domain.node(10);
|
||||
return require(direct.id() == 10);
|
||||
}
|
||||
|
||||
int missing_node_lookup_contracts() {
|
||||
const fesa::core::Domain domain;
|
||||
|
||||
if (const int result = require(domain.findNode(99) == nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return require_throws<std::out_of_range>([&domain]() {
|
||||
(void)domain.node(99);
|
||||
});
|
||||
}
|
||||
|
||||
int duplicate_node_id_throws() {
|
||||
fesa::core::Domain domain;
|
||||
domain.addNode(fesa::core::Node{10, 0.0, 0.0, 0.0});
|
||||
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addNode(fesa::core::Node{10, 1.0, 0.0, 0.0});
|
||||
});
|
||||
}
|
||||
|
||||
int add_and_retrieve_element_by_id() {
|
||||
fesa::core::Domain domain;
|
||||
add_four_nodes(domain);
|
||||
add_material_and_property(domain);
|
||||
|
||||
add_element(domain);
|
||||
|
||||
if (const int result = require(domain.elementCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const fesa::element::Element* found = domain.findElement(100);
|
||||
if (const int result = require(found != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->id() == 100); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->type() == fesa::core::ElementType::Mitc4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->propertyId() == 500); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->connectivity()[0] == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->connectivity()[1] == 2); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->connectivity()[2] == 3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->connectivity()[3] == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const fesa::element::Element& direct = domain.element(100);
|
||||
return require(direct.id() == 100);
|
||||
}
|
||||
|
||||
int duplicate_element_id_throws() {
|
||||
fesa::core::Domain domain;
|
||||
add_four_nodes(domain);
|
||||
add_material_and_property(domain);
|
||||
add_element(domain);
|
||||
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
|
||||
100,
|
||||
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
|
||||
500));
|
||||
});
|
||||
}
|
||||
|
||||
int element_referencing_missing_node_throws() {
|
||||
fesa::core::Domain domain;
|
||||
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||
domain.addNode(fesa::core::Node{2, 1.0, 0.0, 0.0});
|
||||
domain.addNode(fesa::core::Node{3, 1.0, 1.0, 0.0});
|
||||
add_material_and_property(domain);
|
||||
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
|
||||
100,
|
||||
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
|
||||
500));
|
||||
});
|
||||
}
|
||||
|
||||
int element_referencing_missing_property_throws() {
|
||||
fesa::core::Domain domain;
|
||||
add_four_nodes(domain);
|
||||
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3));
|
||||
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
|
||||
100,
|
||||
std::array<fesa::core::NodeId, 4>{1, 2, 3, 4},
|
||||
500));
|
||||
});
|
||||
}
|
||||
|
||||
int missing_element_lookup_contracts() {
|
||||
const fesa::core::Domain domain;
|
||||
|
||||
if (const int result = require(domain.findElement(404) == nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return require_throws<std::out_of_range>([&domain]() {
|
||||
(void)domain.element(404);
|
||||
});
|
||||
}
|
||||
|
||||
int add_and_retrieve_material_and_property() {
|
||||
fesa::core::Domain domain;
|
||||
|
||||
add_material_and_property(domain);
|
||||
|
||||
if (const int result = require(domain.materialCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.shellPropertyCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const fesa::material::Material* material = domain.findMaterial(700);
|
||||
if (const int result = require(material != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(material->id() == 700); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const fesa::property::ShellProperty* property = domain.findShellProperty(500);
|
||||
if (const int result = require(property != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(property->materialId() == 700); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(property->thickness() == 0.01); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (const int result = require(domain.material(700).id() == 700); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(domain.shellProperty(500).id() == 500);
|
||||
}
|
||||
|
||||
int duplicate_material_and_property_ids_throw() {
|
||||
fesa::core::Domain domain;
|
||||
add_material_and_property(domain);
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addMaterial(std::make_unique<fesa::material::LinearElasticMaterial>(700, 100.0, 0.25));
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addShellProperty(fesa::property::ShellProperty{500, 700, 0.02});
|
||||
});
|
||||
}
|
||||
|
||||
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});
|
||||
});
|
||||
}
|
||||
|
||||
int add_and_retrieve_sets() {
|
||||
fesa::core::Domain domain;
|
||||
add_four_nodes(domain);
|
||||
add_material_and_property(domain);
|
||||
add_element(domain);
|
||||
|
||||
domain.addNodeSet("left-edge", {1, 4});
|
||||
domain.addElementSet("shells", {100});
|
||||
|
||||
const auto* node_set = domain.findNodeSet("left-edge");
|
||||
if (const int result = require(node_set != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(node_set->size() == 2); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require((*node_set)[0] == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require((*node_set)[1] == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto* element_set = domain.findElementSet("shells");
|
||||
if (const int result = require(element_set != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element_set->size() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require((*element_set)[0] == 100); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (const int result = require(domain.nodeSetCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(domain.elementSetCount() == 1);
|
||||
}
|
||||
|
||||
int duplicate_set_names_throw() {
|
||||
fesa::core::Domain domain;
|
||||
add_four_nodes(domain);
|
||||
add_material_and_property(domain);
|
||||
add_element(domain);
|
||||
domain.addNodeSet("left-edge", {1, 4});
|
||||
domain.addElementSet("shells", {100});
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addNodeSet("left-edge", {1});
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addElementSet("shells", {100});
|
||||
});
|
||||
}
|
||||
|
||||
int sets_referencing_missing_ids_throw() {
|
||||
fesa::core::Domain domain;
|
||||
add_four_nodes(domain);
|
||||
add_material_and_property(domain);
|
||||
add_element(domain);
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addNodeSet("bad-nodes", {1, 99});
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addElementSet("bad-elements", {100, 404});
|
||||
});
|
||||
}
|
||||
|
||||
int add_and_retrieve_boundary_condition() {
|
||||
fesa::core::Domain domain;
|
||||
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||
|
||||
const std::size_t index = domain.addBoundaryCondition(
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
|
||||
|
||||
if (const int result = require(index == 0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.boundaryConditionCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const fesa::boundary::BoundaryCondition& condition = domain.boundaryCondition(index);
|
||||
if (const int result = require(condition.kind() == fesa::boundary::BoundaryConditionKind::SinglePointConstraint);
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
const auto& spc = static_cast<const fesa::boundary::SinglePointConstraint&>(condition);
|
||||
if (const int result = require(spc.nodeId() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(spc.dof() == fesa::core::Dof::U1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(spc.value() == 0.0);
|
||||
}
|
||||
|
||||
int add_and_retrieve_nodal_load() {
|
||||
fesa::core::Domain domain;
|
||||
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||
|
||||
const std::size_t index = domain.addLoad(
|
||||
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
|
||||
|
||||
if (const int result = require(index == 0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.loadCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const fesa::load::Load& load = domain.load(index);
|
||||
if (const int result = require(load.kind() == fesa::load::LoadKind::Nodal); result != 0) {
|
||||
return result;
|
||||
}
|
||||
const auto& nodal_load = static_cast<const fesa::load::NodalLoad&>(load);
|
||||
if (const int result = require(nodal_load.nodeId() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(nodal_load.dof() == fesa::core::Dof::U3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(nodal_load.value() == -100.0);
|
||||
}
|
||||
|
||||
int missing_node_boundary_condition_and_load_throw() {
|
||||
fesa::core::Domain domain;
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||
(void)domain.addBoundaryCondition(
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(99, fesa::core::Dof::U1, 0.0));
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
(void)domain.addLoad(
|
||||
std::make_unique<fesa::load::NodalLoad>(99, fesa::core::Dof::U3, -100.0));
|
||||
});
|
||||
}
|
||||
|
||||
int duplicate_load_and_boundary_keys_throw() {
|
||||
fesa::core::Domain domain;
|
||||
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||
domain.addBoundaryCondition(
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
|
||||
domain.addLoad(std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||
(void)domain.addBoundaryCondition(
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 1.0));
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return require_throws<std::invalid_argument>([&domain]() {
|
||||
(void)domain.addLoad(
|
||||
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -200.0));
|
||||
});
|
||||
}
|
||||
|
||||
int add_and_retrieve_linear_static_step() {
|
||||
fesa::core::Domain domain;
|
||||
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||
const std::size_t bc = domain.addBoundaryCondition(
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
|
||||
const std::size_t load = domain.addLoad(
|
||||
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
|
||||
|
||||
domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}});
|
||||
|
||||
if (const int result = require(domain.stepCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const fesa::core::LinearStaticStepDefinition* found = domain.findStep(1);
|
||||
if (const int result = require(found != nullptr); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->id() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->name() == "load-step"); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->boundaryConditionIndices().size() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->boundaryConditionIndices()[0] == bc); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(found->loadIndices().size() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(found->loadIndices()[0] == load);
|
||||
}
|
||||
|
||||
int duplicate_and_invalid_step_references_throw() {
|
||||
fesa::core::Domain domain;
|
||||
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||
const std::size_t bc = domain.addBoundaryCondition(
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
|
||||
const std::size_t load = domain.addLoad(
|
||||
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
|
||||
|
||||
domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}});
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain, bc, load]() {
|
||||
domain.addStep(fesa::core::LinearStaticStepDefinition{1, "duplicate", {bc}, {load}});
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain, load]() {
|
||||
domain.addStep(fesa::core::LinearStaticStepDefinition{2, "bad-bc", {99}, {load}});
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require_throws<std::invalid_argument>([&domain, bc]() {
|
||||
domain.addStep(fesa::core::LinearStaticStepDefinition{2, "bad-load", {bc}, {99}});
|
||||
});
|
||||
}
|
||||
|
||||
int const_domain_retrieval_returns_const_runtime_model_data() {
|
||||
fesa::core::Domain domain;
|
||||
add_four_nodes(domain);
|
||||
add_material_and_property(domain);
|
||||
add_element(domain);
|
||||
domain.addNodeSet("left-edge", {1, 4});
|
||||
domain.addElementSet("shells", {100});
|
||||
const std::size_t bc = domain.addBoundaryCondition(
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
|
||||
const std::size_t load = domain.addLoad(
|
||||
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
|
||||
domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}});
|
||||
|
||||
const fesa::core::Domain& const_domain = domain;
|
||||
|
||||
if (const int result = require((std::is_same<decltype(const_domain.node(1)), const fesa::core::Node&>::value));
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require((std::is_same<decltype(const_domain.element(100)), const fesa::element::Element&>::value));
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require((std::is_same<decltype(const_domain.material(700)), const fesa::material::Material&>::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;
|
||||
}
|
||||
if (const int result = require((std::is_same<decltype(const_domain.nodeSet("left-edge")), const std::vector<fesa::core::NodeId>&>::value));
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require((std::is_same<decltype(const_domain.elementSet("shells")), const std::vector<fesa::core::ElementId>&>::value));
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require((std::is_same<decltype(const_domain.boundaryCondition(0)), const fesa::boundary::BoundaryCondition&>::value));
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require((std::is_same<decltype(const_domain.load(0)), const fesa::load::Load&>::value));
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require((std::is_same<decltype(const_domain.step(1)), const fesa::core::LinearStaticStepDefinition&>::value));
|
||||
}
|
||||
|
||||
int failed_inserts_do_not_mutate_counts() {
|
||||
fesa::core::Domain domain;
|
||||
add_four_nodes(domain);
|
||||
add_material_and_property(domain);
|
||||
add_element(domain);
|
||||
domain.addNodeSet("left-edge", {1, 4});
|
||||
domain.addElementSet("shells", {100});
|
||||
const std::size_t bc = domain.addBoundaryCondition(
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(1, fesa::core::Dof::U1, 0.0));
|
||||
const std::size_t load = domain.addLoad(
|
||||
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0));
|
||||
domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}});
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addElement(std::make_unique<fesa::element::Mitc4Element>(
|
||||
101,
|
||||
std::array<fesa::core::NodeId, 4>{1, 2, 3, 99},
|
||||
500));
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.elementCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addShellProperty(fesa::property::ShellProperty{501, 404, 0.01});
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.shellPropertyCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||
domain.addNodeSet("bad-nodes", {1, 99});
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.nodeSetCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||
(void)domain.addBoundaryCondition(
|
||||
std::make_unique<fesa::boundary::SinglePointConstraint>(99, fesa::core::Dof::U1, 0.0));
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(domain.boundaryConditionCount() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (const int result = require_throws<std::invalid_argument>([&domain, bc]() {
|
||||
domain.addStep(fesa::core::LinearStaticStepDefinition{2, "bad-load", {bc}, {99}});
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(domain.stepCount() == 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_domain_storage_tests() {
|
||||
if (const int result = add_and_retrieve_node_by_id(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = missing_node_lookup_contracts(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = duplicate_node_id_throws(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = add_and_retrieve_element_by_id(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = duplicate_element_id_throws(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = element_referencing_missing_node_throws(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = element_referencing_missing_property_throws(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = missing_element_lookup_contracts(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = add_and_retrieve_material_and_property(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = duplicate_material_and_property_ids_throw(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = shell_property_referencing_missing_material_throws(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = add_and_retrieve_sets(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = duplicate_set_names_throw(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = sets_referencing_missing_ids_throw(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = add_and_retrieve_boundary_condition(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = add_and_retrieve_nodal_load(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = missing_node_boundary_condition_and_load_throw(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = duplicate_load_and_boundary_keys_throw(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = add_and_retrieve_linear_static_step(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = duplicate_and_invalid_step_references_throw(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = const_domain_retrieval_returns_const_runtime_model_data(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = failed_inserts_do_not_mutate_counts(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "fesa/core/ElementDefinition.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_element_definition_tests() {
|
||||
const fesa::core::ElementDefinition element{
|
||||
100,
|
||||
fesa::core::ElementType::Mitc4,
|
||||
{1, 2, 3, 4},
|
||||
500};
|
||||
|
||||
if (const int result = require(element.id() == 100); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.type() == fesa::core::ElementType::Mitc4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[0] == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[1] == 2); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[2] == 3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[3] == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.propertyId() == 500); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "fesa/core/LoadDefinition.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_load_definition_tests() {
|
||||
const fesa::core::NodalLoadDefinition load{1, fesa::core::Dof::U3, -100.0};
|
||||
|
||||
if (const int result = require(load.nodeId() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(load.dof() == fesa::core::Dof::U3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(load.value() == -100.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "fesa/core/MaterialDefinition.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_material_definition_tests() {
|
||||
const fesa::core::LinearElasticMaterialDefinition material{700, 210.0, 0.3};
|
||||
|
||||
if (const int result = require(material.id() == 700); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(material.youngModulus() == 210.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(material.poissonRatio() == 0.3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#include "fesa/core/ModelTypes.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_model_types_tests() {
|
||||
using namespace fesa::core;
|
||||
|
||||
if (const int result = require(sizeof(Id) == 8); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(std::is_same<Id, std::int64_t>::value); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(kDofPerNode == 6); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(ElementType::Mitc4 == ElementType::Mitc4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(static_cast<int>(Dof::U1) == 0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(static_cast<int>(Dof::U2) == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(static_cast<int>(Dof::U3) == 2); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(static_cast<int>(Dof::UR1) == 3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(static_cast<int>(Dof::UR2) == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(static_cast<int>(Dof::UR3) == 5); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "fesa/core/Node.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_node_tests() {
|
||||
const fesa::core::Node node{42, 1.0, 2.0, 3.0};
|
||||
|
||||
if (const int result = require(fesa::core::Node::dofCount() == 6); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(node.id() == 42); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(node.x() == 1.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(node.y() == 2.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(node.z() == 3.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(node.coordinates()[0] == 1.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(node.coordinates()[1] == 2.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(node.coordinates()[2] == 3.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require((std::is_same<decltype(node.coordinates()), const std::array<double, 3>&>::value));
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "fesa/core/PropertyDefinition.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_property_definition_tests() {
|
||||
const fesa::core::ShellPropertyDefinition property{500, 700, 0.01};
|
||||
|
||||
if (const int result = require(property.id() == 500); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(property.materialId() == 700); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(property.thickness() == 0.01); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "fesa/core/StepDefinition.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_step_definition_tests() {
|
||||
const fesa::core::LinearStaticStepDefinition step{1, "load-step", {0, 2}, {1}};
|
||||
|
||||
if (const int result = require(step.id() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(step.name() == "load-step"); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(step.boundaryConditionIndices().size() == 2); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(step.boundaryConditionIndices()[0] == 0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(step.boundaryConditionIndices()[1] == 2); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(step.loadIndices().size() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(step.loadIndices()[0] == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "fesa/element/Element.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
using fesa::element::ElementId;
|
||||
using fesa::element::ElementType;
|
||||
using fesa::element::NodeId;
|
||||
using fesa::element::PropertyId;
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
class TestElement final : public fesa::element::Element {
|
||||
public:
|
||||
ElementId id() const noexcept override { return 100; }
|
||||
ElementType type() const noexcept override { return ElementType::Mitc4; }
|
||||
std::size_t nodeCount() const noexcept override { return connectivity_.size(); }
|
||||
const std::array<NodeId, 4>& connectivity() const noexcept override { return connectivity_; }
|
||||
PropertyId propertyId() const noexcept override { return 500; }
|
||||
|
||||
private:
|
||||
std::array<NodeId, 4> connectivity_{1, 2, 3, 4};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_element_base_tests() {
|
||||
std::unique_ptr<fesa::element::Element> owned = std::make_unique<TestElement>();
|
||||
const fesa::element::Element& element = *owned;
|
||||
|
||||
if (const int result = require(element.id() == 100); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.type() == fesa::core::ElementType::Mitc4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.nodeCount() == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[0] == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[3] == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(element.propertyId() == 500);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "fesa/element/Mitc4Element.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_mitc4_element_model_tests() {
|
||||
const fesa::element::Mitc4Element mitc4{100, {1, 2, 3, 4}, 500};
|
||||
const fesa::element::Element& element = mitc4;
|
||||
|
||||
if (const int result = require(element.id() == 100); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.type() == fesa::core::ElementType::Mitc4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.nodeCount() == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(mitc4.dofCount() == 24); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[0] == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[1] == 2); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[2] == 3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(element.connectivity()[3] == 4); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(element.propertyId() == 500);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "fesa/load/Load.hpp"
|
||||
#include "fesa/load/NodalLoad.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_load_base_tests() {
|
||||
std::unique_ptr<fesa::load::Load> owned =
|
||||
std::make_unique<fesa::load::NodalLoad>(1, fesa::core::Dof::U3, -100.0);
|
||||
const fesa::load::Load& load = *owned;
|
||||
const auto& nodal = static_cast<const fesa::load::NodalLoad&>(load);
|
||||
|
||||
if (const int result = require(load.kind() == fesa::load::LoadKind::Nodal); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(nodal.nodeId() == 1); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(nodal.dof() == fesa::core::Dof::U3); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(nodal.value() == -100.0);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "fesa/material/LinearElasticMaterial.hpp"
|
||||
#include "fesa/material/Material.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_material_base_tests() {
|
||||
std::unique_ptr<fesa::material::Material> owned =
|
||||
std::make_unique<fesa::material::LinearElasticMaterial>(700, 210.0, 0.3);
|
||||
const fesa::material::Material& material = *owned;
|
||||
const auto& elastic = static_cast<const fesa::material::LinearElasticMaterial&>(material);
|
||||
|
||||
if (const int result = require(material.id() == 700); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(elastic.youngModulus() == 210.0); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require(elastic.poissonRatio() == 0.3);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
int run_boundary_base_tests();
|
||||
int run_element_base_tests();
|
||||
int run_load_base_tests();
|
||||
int run_material_base_tests();
|
||||
int run_mitc4_element_model_tests();
|
||||
int run_shell_property_tests();
|
||||
|
||||
int main() {
|
||||
if (const int result = run_boundary_base_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_element_base_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_mitc4_element_model_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_material_base_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_shell_property_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = run_load_base_tests(); result != 0) {
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "fesa/property/ShellProperty.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
int require(bool condition) {
|
||||
return condition ? 0 : 1;
|
||||
}
|
||||
|
||||
template <typename Exception, typename Function>
|
||||
int require_throws(Function&& function) {
|
||||
try {
|
||||
function();
|
||||
} catch (const Exception&) {
|
||||
return 0;
|
||||
} catch (...) {
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_shell_property_tests() {
|
||||
const fesa::property::ShellProperty property{500, 700, 0.01};
|
||||
|
||||
if (const int result = require(property.id() == 500); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(property.materialId() == 700); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require(property.thickness() == 0.01); result != 0) {
|
||||
return result;
|
||||
}
|
||||
if (const int result = require_throws<std::invalid_argument>([]() {
|
||||
(void)fesa::property::ShellProperty{501, 700, 0.0};
|
||||
});
|
||||
result != 0) {
|
||||
return result;
|
||||
}
|
||||
return require_throws<std::invalid_argument>([]() {
|
||||
(void)fesa::property::ShellProperty{502, 700, -0.01};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user