feat: add domain model foundation
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
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/core/Domain.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_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")
|
||||||
+11
-1
@@ -8,6 +8,7 @@
|
|||||||
- Multi-Agent coordination files live under `docs/`: `docs/PLAN.md`, `docs/PROGRESS.md`, and `docs/WORKNOTE.md`.
|
- 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/`.
|
- Project plan/design documents live under `docs/project-plan/`.
|
||||||
- Common per-run AI Agent rules live in `docs/AGENT_RULES.md`.
|
- 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.
|
||||||
|
|
||||||
## Completed
|
## Completed
|
||||||
- Defined the nine-step solver development workflow.
|
- Defined the nine-step solver development workflow.
|
||||||
@@ -23,9 +24,14 @@
|
|||||||
- 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`.
|
- 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.
|
- 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.
|
- 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.
|
||||||
|
|
||||||
## In Progress
|
## 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
|
## Next Tasks
|
||||||
1. Create `docs/requirements/mitc4-linear-static-shell.md`.
|
1. Create `docs/requirements/mitc4-linear-static-shell.md`.
|
||||||
@@ -36,6 +42,10 @@
|
|||||||
6. Create `docs/implementation-plans/mitc4-linear-static-shell-implementation-plan.md`.
|
6. Create `docs/implementation-plans/mitc4-linear-static-shell-implementation-plan.md`.
|
||||||
|
|
||||||
## Last Validation
|
## Last Validation
|
||||||
|
- 2026-06-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 -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`, `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.
|
- 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
|
### 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.
|
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
|
### 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.
|
`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,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,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,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,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fesa/core/BoundaryCondition.hpp"
|
||||||
|
#include "fesa/core/ElementDefinition.hpp"
|
||||||
|
#include "fesa/core/LoadDefinition.hpp"
|
||||||
|
#include "fesa/core/MaterialDefinition.hpp"
|
||||||
|
#include "fesa/core/ModelTypes.hpp"
|
||||||
|
#include "fesa/core/Node.hpp"
|
||||||
|
#include "fesa/core/PropertyDefinition.hpp"
|
||||||
|
#include "fesa/core/StepDefinition.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace fesa::core {
|
||||||
|
|
||||||
|
class Domain {
|
||||||
|
public:
|
||||||
|
void addNode(Node node);
|
||||||
|
void addElement(ElementDefinition element);
|
||||||
|
void addMaterial(LinearElasticMaterialDefinition material);
|
||||||
|
void addShellProperty(ShellPropertyDefinition property);
|
||||||
|
void addNodeSet(std::string name, std::vector<NodeId> node_ids);
|
||||||
|
void addElementSet(std::string name, std::vector<ElementId> element_ids);
|
||||||
|
std::size_t addBoundaryCondition(BoundaryCondition condition);
|
||||||
|
std::size_t addNodalLoad(NodalLoadDefinition load);
|
||||||
|
void addStep(LinearStaticStepDefinition step);
|
||||||
|
|
||||||
|
const Node* findNode(NodeId id) const noexcept;
|
||||||
|
const Node& node(NodeId id) const;
|
||||||
|
std::size_t nodeCount() const noexcept;
|
||||||
|
|
||||||
|
const ElementDefinition* findElement(ElementId id) const noexcept;
|
||||||
|
const ElementDefinition& element(ElementId id) const;
|
||||||
|
std::size_t elementCount() const noexcept;
|
||||||
|
|
||||||
|
const LinearElasticMaterialDefinition* findMaterial(MaterialId id) const noexcept;
|
||||||
|
const LinearElasticMaterialDefinition& material(MaterialId id) const;
|
||||||
|
std::size_t materialCount() const noexcept;
|
||||||
|
|
||||||
|
const ShellPropertyDefinition* findShellProperty(PropertyId id) const noexcept;
|
||||||
|
const ShellPropertyDefinition& 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 BoundaryCondition& boundaryCondition(std::size_t index) const;
|
||||||
|
std::size_t boundaryConditionCount() const noexcept;
|
||||||
|
|
||||||
|
const NodalLoadDefinition& nodalLoad(std::size_t index) const;
|
||||||
|
std::size_t nodalLoadCount() const noexcept;
|
||||||
|
|
||||||
|
const 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, ElementDefinition> elements_;
|
||||||
|
std::unordered_map<MaterialId, LinearElasticMaterialDefinition> materials_;
|
||||||
|
std::unordered_map<PropertyId, ShellPropertyDefinition> shell_properties_;
|
||||||
|
std::unordered_map<std::string, std::vector<NodeId>> node_sets_;
|
||||||
|
std::unordered_map<std::string, std::vector<ElementId>> element_sets_;
|
||||||
|
std::vector<BoundaryCondition> boundary_conditions_;
|
||||||
|
std::vector<NodalLoadDefinition> nodal_loads_;
|
||||||
|
std::unordered_map<StepId, LinearStaticStepDefinition> steps_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa::core
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fesa/core/ModelTypes.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace fesa::core {
|
||||||
|
|
||||||
|
enum class ElementType {
|
||||||
|
Mitc4
|
||||||
|
};
|
||||||
|
|
||||||
|
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,26 @@
|
|||||||
|
#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
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::size_t kDofPerNode = 6;
|
||||||
|
|
||||||
|
} // namespace fesa::core
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fesa/core/ModelTypes.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace fesa::core {
|
||||||
|
|
||||||
|
class Node {
|
||||||
|
public:
|
||||||
|
Node(NodeId id, double x, double y, double z);
|
||||||
|
|
||||||
|
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,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,8 @@
|
|||||||
|
{
|
||||||
|
"phases": [
|
||||||
|
{
|
||||||
|
"dir": "domain-model-foundation",
|
||||||
|
"status": "completed"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -87,7 +87,62 @@ class ValidateWorkspaceTests(unittest.TestCase):
|
|||||||
with patch.object(validate_workspace.shutil, "which", return_value=None):
|
with patch.object(validate_workspace.shutil, "which", return_value=None):
|
||||||
env = validate_workspace.validation_environment({"PATH": "C:\\Windows\\System32"})
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -88,7 +88,27 @@ def discover_commands(root: Path) -> list[str]:
|
|||||||
return load_cmake_commands(root)
|
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:
|
def run_command(command: str, root: Path) -> subprocess.CompletedProcess:
|
||||||
|
command = resolve_validation_command(command)
|
||||||
return subprocess.run(
|
return subprocess.run(
|
||||||
command,
|
command,
|
||||||
cwd=root,
|
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]:
|
def validation_environment(base_env: os._Environ | dict[str, str]) -> dict[str, str]:
|
||||||
env = dict(base_env)
|
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
|
return env
|
||||||
cmake_exe = COMMON_CMAKE_BIN / "cmake.exe"
|
cmake_exe = COMMON_CMAKE_BIN / "cmake.exe"
|
||||||
if not cmake_exe.exists():
|
if not cmake_exe.exists():
|
||||||
return env
|
return env
|
||||||
|
|
||||||
current_path = env.get("PATH", "")
|
|
||||||
paths = [part for part in current_path.split(os.pathsep) if part]
|
paths = [part for part in current_path.split(os.pathsep) if part]
|
||||||
common_bin_text = str(COMMON_CMAKE_BIN)
|
common_bin_text = str(COMMON_CMAKE_BIN)
|
||||||
if not any(part.lower() == common_bin_text.lower() for part in paths):
|
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
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,381 @@
|
|||||||
|
#include "fesa/core/Domain.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(ElementDefinition element) {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elements_.emplace(id, std::move(element));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Domain::addMaterial(LinearElasticMaterialDefinition material) {
|
||||||
|
const MaterialId id = material.id();
|
||||||
|
const auto inserted = materials_.emplace(id, std::move(material));
|
||||||
|
if (!inserted.second) {
|
||||||
|
throw std::invalid_argument("duplicate material id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Domain::addShellProperty(ShellPropertyDefinition property) {
|
||||||
|
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(BoundaryCondition condition) {
|
||||||
|
if (findNode(condition.nodeId()) == nullptr) {
|
||||||
|
throw std::invalid_argument("boundary condition references missing node id");
|
||||||
|
}
|
||||||
|
const std::size_t index = boundary_conditions_.size();
|
||||||
|
boundary_conditions_.push_back(condition);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t Domain::addNodalLoad(NodalLoadDefinition load) {
|
||||||
|
if (findNode(load.nodeId()) == nullptr) {
|
||||||
|
throw std::invalid_argument("nodal load references missing node id");
|
||||||
|
}
|
||||||
|
const std::size_t index = nodal_loads_.size();
|
||||||
|
nodal_loads_.push_back(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 >= nodal_loads_.size()) {
|
||||||
|
throw std::invalid_argument("step references missing nodal 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 ElementDefinition* Domain::findElement(ElementId id) const noexcept {
|
||||||
|
const auto it = elements_.find(id);
|
||||||
|
return it == elements_.end() ? nullptr : &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ElementDefinition& Domain::element(ElementId id) const {
|
||||||
|
const ElementDefinition* 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 LinearElasticMaterialDefinition* Domain::findMaterial(MaterialId id) const noexcept {
|
||||||
|
const auto it = materials_.find(id);
|
||||||
|
return it == materials_.end() ? nullptr : &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LinearElasticMaterialDefinition& Domain::material(MaterialId id) const {
|
||||||
|
const LinearElasticMaterialDefinition* 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 ShellPropertyDefinition* Domain::findShellProperty(PropertyId id) const noexcept {
|
||||||
|
const auto it = shell_properties_.find(id);
|
||||||
|
return it == shell_properties_.end() ? nullptr : &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ShellPropertyDefinition& Domain::shellProperty(PropertyId id) const {
|
||||||
|
const ShellPropertyDefinition* found = findShellProperty(id);
|
||||||
|
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 BoundaryCondition& Domain::boundaryCondition(std::size_t index) const {
|
||||||
|
return boundary_conditions_.at(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t Domain::boundaryConditionCount() const noexcept {
|
||||||
|
return boundary_conditions_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const NodalLoadDefinition& Domain::nodalLoad(std::size_t index) const {
|
||||||
|
return nodal_loads_.at(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t Domain::nodalLoadCount() const noexcept {
|
||||||
|
return nodal_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,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,40 @@
|
|||||||
|
int run_boundary_condition_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_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,655 @@
|
|||||||
|
#include "fesa/core/BoundaryCondition.hpp"
|
||||||
|
#include "fesa/core/Domain.hpp"
|
||||||
|
#include "fesa/core/ElementDefinition.hpp"
|
||||||
|
#include "fesa/core/LoadDefinition.hpp"
|
||||||
|
#include "fesa/core/MaterialDefinition.hpp"
|
||||||
|
#include "fesa/core/Node.hpp"
|
||||||
|
#include "fesa/core/PropertyDefinition.hpp"
|
||||||
|
#include "fesa/core/StepDefinition.hpp"
|
||||||
|
|
||||||
|
#include <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (const int result = require(direct.id() == 10); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_four_nodes(fesa::core::Domain& domain) {
|
||||||
|
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||||
|
domain.addNode(fesa::core::Node{2, 1.0, 0.0, 0.0});
|
||||||
|
domain.addNode(fesa::core::Node{3, 1.0, 1.0, 0.0});
|
||||||
|
domain.addNode(fesa::core::Node{4, 0.0, 1.0, 0.0});
|
||||||
|
}
|
||||||
|
|
||||||
|
int add_and_retrieve_element_by_id() {
|
||||||
|
fesa::core::Domain domain;
|
||||||
|
add_four_nodes(domain);
|
||||||
|
|
||||||
|
domain.addElement(fesa::core::ElementDefinition{
|
||||||
|
100,
|
||||||
|
fesa::core::ElementType::Mitc4,
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
500});
|
||||||
|
|
||||||
|
if (const int result = require(domain.elementCount() == 1); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fesa::core::ElementDefinition* 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::core::ElementDefinition& direct = domain.element(100);
|
||||||
|
return require(direct.id() == 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
int duplicate_element_id_throws() {
|
||||||
|
fesa::core::Domain domain;
|
||||||
|
add_four_nodes(domain);
|
||||||
|
domain.addElement(fesa::core::ElementDefinition{
|
||||||
|
100,
|
||||||
|
fesa::core::ElementType::Mitc4,
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
500});
|
||||||
|
|
||||||
|
return require_throws<std::invalid_argument>([&domain]() {
|
||||||
|
domain.addElement(fesa::core::ElementDefinition{
|
||||||
|
100,
|
||||||
|
fesa::core::ElementType::Mitc4,
|
||||||
|
{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});
|
||||||
|
|
||||||
|
return require_throws<std::invalid_argument>([&domain]() {
|
||||||
|
domain.addElement(fesa::core::ElementDefinition{
|
||||||
|
100,
|
||||||
|
fesa::core::ElementType::Mitc4,
|
||||||
|
{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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_material_and_property(fesa::core::Domain& domain) {
|
||||||
|
domain.addMaterial(fesa::core::LinearElasticMaterialDefinition{700, 210.0, 0.3});
|
||||||
|
domain.addShellProperty(fesa::core::ShellPropertyDefinition{500, 700, 0.01});
|
||||||
|
}
|
||||||
|
|
||||||
|
int add_and_retrieve_material_and_property() {
|
||||||
|
fesa::core::Domain domain;
|
||||||
|
|
||||||
|
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::core::LinearElasticMaterialDefinition* material = domain.findMaterial(700);
|
||||||
|
if (const int result = require(material != nullptr); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (const int result = require(material->youngModulus() == 210.0); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (const int result = require(material->poissonRatio() == 0.3); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fesa::core::ShellPropertyDefinition* 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(fesa::core::LinearElasticMaterialDefinition{700, 100.0, 0.25});
|
||||||
|
});
|
||||||
|
result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return require_throws<std::invalid_argument>([&domain]() {
|
||||||
|
domain.addShellProperty(fesa::core::ShellPropertyDefinition{500, 700, 0.02});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int shell_property_referencing_missing_material_throws() {
|
||||||
|
fesa::core::Domain domain;
|
||||||
|
|
||||||
|
return require_throws<std::invalid_argument>([&domain]() {
|
||||||
|
domain.addShellProperty(fesa::core::ShellPropertyDefinition{500, 700, 0.01});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int add_and_retrieve_sets() {
|
||||||
|
fesa::core::Domain domain;
|
||||||
|
add_four_nodes(domain);
|
||||||
|
domain.addElement(fesa::core::ElementDefinition{
|
||||||
|
100,
|
||||||
|
fesa::core::ElementType::Mitc4,
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
500});
|
||||||
|
|
||||||
|
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);
|
||||||
|
domain.addElement(fesa::core::ElementDefinition{
|
||||||
|
100,
|
||||||
|
fesa::core::ElementType::Mitc4,
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
500});
|
||||||
|
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);
|
||||||
|
domain.addElement(fesa::core::ElementDefinition{
|
||||||
|
100,
|
||||||
|
fesa::core::ElementType::Mitc4,
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
500});
|
||||||
|
|
||||||
|
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(
|
||||||
|
fesa::core::BoundaryCondition{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::core::BoundaryCondition& condition = domain.boundaryCondition(index);
|
||||||
|
if (const int result = require(condition.nodeId() == 1); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (const int result = require(condition.dof() == fesa::core::Dof::U1); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return require(condition.value() == 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int add_and_retrieve_nodal_load() {
|
||||||
|
fesa::core::Domain domain;
|
||||||
|
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||||
|
|
||||||
|
const std::size_t index = domain.addNodalLoad(
|
||||||
|
fesa::core::NodalLoadDefinition{1, fesa::core::Dof::U3, -100.0});
|
||||||
|
|
||||||
|
if (const int result = require(index == 0); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (const int result = require(domain.nodalLoadCount() == 1); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fesa::core::NodalLoadDefinition& load = domain.nodalLoad(index);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return require(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(
|
||||||
|
fesa::core::BoundaryCondition{99, fesa::core::Dof::U1, 0.0});
|
||||||
|
});
|
||||||
|
result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return require_throws<std::invalid_argument>([&domain]() {
|
||||||
|
(void)domain.addNodalLoad(
|
||||||
|
fesa::core::NodalLoadDefinition{99, fesa::core::Dof::U3, -100.0});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int add_and_retrieve_linear_static_step() {
|
||||||
|
fesa::core::Domain domain;
|
||||||
|
domain.addNode(fesa::core::Node{1, 0.0, 0.0, 0.0});
|
||||||
|
const std::size_t bc = domain.addBoundaryCondition(
|
||||||
|
fesa::core::BoundaryCondition{1, fesa::core::Dof::U1, 0.0});
|
||||||
|
const std::size_t load = domain.addNodalLoad(
|
||||||
|
fesa::core::NodalLoadDefinition{1, fesa::core::Dof::U3, -100.0});
|
||||||
|
|
||||||
|
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(
|
||||||
|
fesa::core::BoundaryCondition{1, fesa::core::Dof::U1, 0.0});
|
||||||
|
const std::size_t load = domain.addNodalLoad(
|
||||||
|
fesa::core::NodalLoadDefinition{1, fesa::core::Dof::U3, -100.0});
|
||||||
|
|
||||||
|
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_model_data() {
|
||||||
|
fesa::core::Domain domain;
|
||||||
|
add_four_nodes(domain);
|
||||||
|
domain.addMaterial(fesa::core::LinearElasticMaterialDefinition{700, 210.0, 0.3});
|
||||||
|
domain.addShellProperty(fesa::core::ShellPropertyDefinition{500, 700, 0.01});
|
||||||
|
domain.addElement(fesa::core::ElementDefinition{
|
||||||
|
100,
|
||||||
|
fesa::core::ElementType::Mitc4,
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
500});
|
||||||
|
domain.addNodeSet("left-edge", {1, 4});
|
||||||
|
domain.addElementSet("shells", {100});
|
||||||
|
const std::size_t bc = domain.addBoundaryCondition(
|
||||||
|
fesa::core::BoundaryCondition{1, fesa::core::Dof::U1, 0.0});
|
||||||
|
const std::size_t load = domain.addNodalLoad(
|
||||||
|
fesa::core::NodalLoadDefinition{1, fesa::core::Dof::U3, -100.0});
|
||||||
|
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::core::ElementDefinition&>::value));
|
||||||
|
result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (const int result = require((std::is_same<decltype(const_domain.material(700)), const fesa::core::LinearElasticMaterialDefinition&>::value));
|
||||||
|
result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (const int result = require((std::is_same<decltype(const_domain.shellProperty(500)), const fesa::core::ShellPropertyDefinition&>::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::core::BoundaryCondition&>::value));
|
||||||
|
result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (const int result = require((std::is_same<decltype(const_domain.nodalLoad(0)), const fesa::core::NodalLoadDefinition&>::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);
|
||||||
|
domain.addMaterial(fesa::core::LinearElasticMaterialDefinition{700, 210.0, 0.3});
|
||||||
|
domain.addShellProperty(fesa::core::ShellPropertyDefinition{500, 700, 0.01});
|
||||||
|
domain.addElement(fesa::core::ElementDefinition{
|
||||||
|
100,
|
||||||
|
fesa::core::ElementType::Mitc4,
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
500});
|
||||||
|
domain.addNodeSet("left-edge", {1, 4});
|
||||||
|
domain.addElementSet("shells", {100});
|
||||||
|
const std::size_t bc = domain.addBoundaryCondition(
|
||||||
|
fesa::core::BoundaryCondition{1, fesa::core::Dof::U1, 0.0});
|
||||||
|
const std::size_t load = domain.addNodalLoad(
|
||||||
|
fesa::core::NodalLoadDefinition{1, fesa::core::Dof::U3, -100.0});
|
||||||
|
domain.addStep(fesa::core::LinearStaticStepDefinition{1, "load-step", {bc}, {load}});
|
||||||
|
|
||||||
|
if (const int result = require_throws<std::invalid_argument>([&domain]() {
|
||||||
|
domain.addElement(fesa::core::ElementDefinition{
|
||||||
|
101,
|
||||||
|
fesa::core::ElementType::Mitc4,
|
||||||
|
{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::core::ShellPropertyDefinition{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(
|
||||||
|
fesa::core::BoundaryCondition{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 = 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 = add_and_retrieve_linear_static_step(); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (const int result = duplicate_and_invalid_step_references_throw(); result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (const int result = const_domain_retrieval_returns_const_model_data(); result != 0) {
|
||||||
|
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,46 @@
|
|||||||
|
#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(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,37 @@
|
|||||||
|
#include "fesa/core/Node.hpp"
|
||||||
|
|
||||||
|
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(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user