Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bab84beb6 | |||
| 13cf2af899 | |||
| 825e03dbaf | |||
| cbd1a6c5d7 |
@@ -0,0 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(FESA LANGUAGES CXX)
|
||||
|
||||
file(GLOB_RECURSE FESA_CORE_SOURCES CONFIGURE_DEPENDS src/fesa/*.cpp)
|
||||
|
||||
add_library(fesa_core STATIC ${FESA_CORE_SOURCES})
|
||||
target_compile_features(fesa_core PUBLIC cxx_std_17)
|
||||
target_include_directories(fesa_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
@@ -0,0 +1,80 @@
|
||||
# Solver Core Skeleton Build/Test Report
|
||||
|
||||
## Metadata
|
||||
- phase: solver-core-skeleton
|
||||
- scope: C++ skeleton classes only
|
||||
- status: passed
|
||||
|
||||
## Commands Run
|
||||
|
||||
```powershell
|
||||
python -m unittest scripts.test_header_declaration_only
|
||||
```
|
||||
|
||||
- exit_code: 0
|
||||
- summary: Solver headers contain declarations only; function bodies are implemented in `.cpp` files.
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
```
|
||||
|
||||
- exit_code: 0
|
||||
- summary: 99 Python Harness tests passed.
|
||||
|
||||
```powershell
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
- exit_code: 0
|
||||
- summary: CMake configure, MSVC Debug build, and full CTest suite passed.
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R solver_core_skeleton_integration_test
|
||||
```
|
||||
|
||||
- exit_code: 0
|
||||
- summary: `solver_core_skeleton_integration_test` passed.
|
||||
|
||||
## CTest Tests Added
|
||||
|
||||
- `harness_smoke_test`
|
||||
- `core_diagnostic_test`
|
||||
- `core_ids_test`
|
||||
- `core_primitives_test`
|
||||
- `core_status_test`
|
||||
- `model_analysis_step_test`
|
||||
- `model_boundary_condition_test`
|
||||
- `model_domain_test`
|
||||
- `model_element_test`
|
||||
- `model_load_test`
|
||||
- `model_material_test`
|
||||
- `model_node_test`
|
||||
- `model_property_test`
|
||||
- `analysis_model_view_test`
|
||||
- `dof_manager_dof_key_test`
|
||||
- `dof_manager_numbering_test`
|
||||
- `analysis_state_vectors_test`
|
||||
- `analysis_flow_linear_static_analysis_test`
|
||||
- `analysis_flow_template_test`
|
||||
- `results_containers_test`
|
||||
- `solver_core_skeleton_integration_test`
|
||||
|
||||
## Structural Tests Added
|
||||
|
||||
- `scripts.test_header_declaration_only`
|
||||
|
||||
## Build Structure
|
||||
|
||||
- `fesa_core` now builds as a static library from `src/fesa/**/*.cpp`.
|
||||
- Solver headers under `src/fesa/**/*.hpp` declare functions only; method bodies live in matching `.cpp` translation units.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- No element stiffness, residual, tangent, or stress recovery calculation is implemented.
|
||||
- No material law evaluation is implemented.
|
||||
- No sparse assembly implementation is implemented beyond `DofManager` sparse pattern ownership.
|
||||
- No linear solver backend is implemented.
|
||||
- No HDF5 writer or reader is implemented.
|
||||
- No Abaqus `.inp` parser is implemented.
|
||||
- No reference comparison against Abaqus CSV artifacts is implemented.
|
||||
- `LinearStaticAnalysis` currently prepares the analysis model, DOF map, and zero-valued state only.
|
||||
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"project": "FESA Structural Solver",
|
||||
"phase": "euler-beam-3d",
|
||||
"steps": [
|
||||
{
|
||||
"step": 0,
|
||||
"name": "requirements-baseline",
|
||||
"status": "pending",
|
||||
"allowed_paths": [
|
||||
"docs/requirements/euler-beam-3d.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 1,
|
||||
"name": "research-evidence",
|
||||
"status": "pending",
|
||||
"allowed_paths": [
|
||||
"docs/research/euler-beam-3d-research.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "formulation-spec",
|
||||
"status": "pending",
|
||||
"allowed_paths": [
|
||||
"docs/formulations/euler-beam-3d-formulation.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "numerical-review",
|
||||
"status": "pending",
|
||||
"allowed_paths": [
|
||||
"docs/numerical-reviews/euler-beam-3d-review.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "io-reference-contract",
|
||||
"status": "pending",
|
||||
"allowed_paths": [
|
||||
"docs/io-definitions/euler-beam-3d-io.md",
|
||||
"docs/reference-models/euler-beam-3d-reference-models.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "implementation-plan",
|
||||
"status": "pending",
|
||||
"allowed_paths": [
|
||||
"docs/implementation-plans/euler-beam-3d-implementation-plan.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 6,
|
||||
"name": "model-beam-topology",
|
||||
"status": "pending",
|
||||
"allowed_paths": [
|
||||
"src/fesa/model/element.hpp",
|
||||
"src/fesa/model/element.cpp",
|
||||
"tests/unit/model_element_test.cpp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 7,
|
||||
"name": "local-stiffness-kernel",
|
||||
"status": "pending",
|
||||
"allowed_paths": [
|
||||
"src/fesa/elements/",
|
||||
"tests/unit/euler_beam_3d_*_test.cpp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
"name": "global-transform-recovery",
|
||||
"status": "pending",
|
||||
"allowed_paths": [
|
||||
"src/fesa/elements/",
|
||||
"tests/unit/euler_beam_3d_*_test.cpp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 9,
|
||||
"name": "build-test-report",
|
||||
"status": "pending",
|
||||
"allowed_paths": [
|
||||
"docs/build-test-reports/euler-beam-3d-build-test.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 10,
|
||||
"name": "release-readiness-note",
|
||||
"status": "pending",
|
||||
"allowed_paths": [
|
||||
"docs/releases/euler-beam-3d-release.md"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
# Step 0: requirements-baseline
|
||||
|
||||
## Read These Files First
|
||||
|
||||
Read the following files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PRD.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/requirements/README.md`
|
||||
|
||||
## Task
|
||||
|
||||
Create `/docs/requirements/euler-beam-3d.md` for a kernel-first 3D Euler-Bernoulli beam feature.
|
||||
|
||||
Scope the first implementable increment narrowly:
|
||||
|
||||
- two-node, straight, prismatic, small-displacement 3D Euler-Bernoulli beam element
|
||||
- six mechanical DOFs per node in this order: `ux, uy, uz, rx, ry, rz`
|
||||
- linear elastic section constants: `E`, `G`, `A`, `J`, `Iy`, `Iz`
|
||||
- local and global 12x12 stiffness matrix support
|
||||
- local and global element end-force recovery from nodal displacement vectors
|
||||
- no shear deformation, warping, end releases, offsets, distributed loads, mass matrix, geometric stiffness, nonlinear kinematics, dynamics, or thermal coupling in this increment
|
||||
- no Abaqus reference CSV generation in this increment
|
||||
- no full Abaqus compatibility claim
|
||||
|
||||
The document must contain:
|
||||
|
||||
- metadata: `feature_id: euler-beam-3d`, owner agent, status
|
||||
- explicit assumptions and non-goals
|
||||
- `must` requirements with stable IDs such as `EB3D-REQ-001`
|
||||
- verification quantities: displacement DOFs, reactions/end forces, stiffness symmetry, rigid body modes, local/global transformation consistency
|
||||
- acceptance criteria that distinguish kernel completion from full solver release readiness
|
||||
- open issues for parser integration, reference artifact availability, and full end-to-end assembly
|
||||
|
||||
Do not create C++ files in this step.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- No C++ test is required in this documentation-only step.
|
||||
- Still run the harness validation commands in the acceptance criteria.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm the requirements do not claim full Abaqus compatibility.
|
||||
2. Confirm each `must` requirement has an acceptance criterion or a downstream verification hook.
|
||||
3. Update `phases/euler-beam-3d/index.json` step 0:
|
||||
- success: `"status": "completed"`, `"summary": "3D Euler beam kernel requirements baseline added"`
|
||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not create or modify reference CSV files.
|
||||
- Do not modify source or test files.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Step 1: research-evidence
|
||||
|
||||
## Read These Files First
|
||||
|
||||
Read the following files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PRD.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/requirements/euler-beam-3d.md`
|
||||
- `/docs/research/README.md`
|
||||
|
||||
## Task
|
||||
|
||||
Create `/docs/research/euler-beam-3d-research.md`.
|
||||
|
||||
Summarize the evidence needed to implement the approved kernel-first 3D Euler-Bernoulli beam increment. The document must be implementation-oriented and must include:
|
||||
|
||||
- supported theory: straight prismatic Euler-Bernoulli beam, axial, torsion, and two uncoupled bending planes
|
||||
- assumptions and applicability limits
|
||||
- local DOF ordering and sign convention used by the planned matrix
|
||||
- source reliability classification
|
||||
- benchmark-style checks that do not require external reference solver execution:
|
||||
- local stiffness symmetry
|
||||
- axial-only response
|
||||
- torsion-only response
|
||||
- bending about local `y`
|
||||
- bending about local `z`
|
||||
- rigid body translation/rotation zero internal forces in local coordinates
|
||||
- global transform identity for an axis-aligned beam
|
||||
- risks: orientation vector parallel to element axis, near-zero length, nonpositive section constants, ill-conditioning for very slender elements
|
||||
|
||||
If internet access or a FEM wiki is used, cite sources briefly. Do not include long copyrighted excerpts.
|
||||
|
||||
Do not create C++ files in this step.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- No C++ test is required in this documentation-only step.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm the research document ties each source or theory point to a planned implementation check.
|
||||
2. Confirm unresolved items are listed as risks or open issues instead of silently assumed.
|
||||
3. Update `phases/euler-beam-3d/index.json` step 1:
|
||||
- success: `"status": "completed"`, `"summary": "3D Euler beam research evidence added"`
|
||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not modify source, test, reference, or I/O contract files.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Step 10: release-readiness-note
|
||||
|
||||
## Read These Files First
|
||||
|
||||
Read the following files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PRD.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/releases/README.md`
|
||||
- `/docs/requirements/euler-beam-3d.md`
|
||||
- `/docs/io-definitions/euler-beam-3d-io.md`
|
||||
- `/docs/reference-models/euler-beam-3d-reference-models.md`
|
||||
- `/docs/build-test-reports/euler-beam-3d-build-test.md`
|
||||
|
||||
## Task
|
||||
|
||||
Create `/docs/releases/euler-beam-3d-release.md`.
|
||||
|
||||
This is a readiness note, not a release approval. It must state:
|
||||
|
||||
- status: `not-release-ready-kernel-increment-complete` unless all upstream gates and reference artifacts somehow exist
|
||||
- completed scope: local/global stiffness and end-force kernel for two-node 3D Euler-Bernoulli beam
|
||||
- missing for full feature release:
|
||||
- parser implementation for the approved Abaqus subset
|
||||
- section/property semantic model integration
|
||||
- assembler/static solver integration
|
||||
- HDF5 result emission for beam quantities
|
||||
- stored Abaqus reference artifacts
|
||||
- reference comparison report
|
||||
- physics sanity report
|
||||
- known limitations from requirements
|
||||
- next recommended phase or phase dependencies
|
||||
|
||||
Do not change source or tests in this step.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- No C++ test is required in this documentation-only step.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm the note does not claim full release readiness.
|
||||
2. Confirm all missing gates are explicit.
|
||||
3. Update `phases/euler-beam-3d/index.json` step 10:
|
||||
- success: `"status": "completed"`, `"summary": "3D Euler beam release readiness note added"`
|
||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not create or modify reference artifacts.
|
||||
- Do not modify source, tests, requirements, formulations, or I/O contracts.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Step 2: formulation-spec
|
||||
|
||||
## Read These Files First
|
||||
|
||||
Read the following files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PRD.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/requirements/euler-beam-3d.md`
|
||||
- `/docs/research/euler-beam-3d-research.md`
|
||||
- `/docs/formulations/README.md`
|
||||
|
||||
## Task
|
||||
|
||||
Create `/docs/formulations/euler-beam-3d-formulation.md`.
|
||||
|
||||
The formulation must define the implementation contract for the C++ kernel. Include:
|
||||
|
||||
- local coordinate system: local `x` from node 1 to node 2; local `y` from the user orientation vector projected normal to local `x`; local `z = x cross y`
|
||||
- local DOF order: `[u1, v1, w1, rx1, ry1, rz1, u2, v2, w2, rx2, ry2, rz2]`
|
||||
- section constants: `E`, `G`, `A`, `J`, `Iy`, `Iz`
|
||||
- local 12x12 stiffness matrix terms for:
|
||||
- axial: `EA/L`
|
||||
- torsion: `GJ/L`
|
||||
- bending in local `x-y` using `EIz`
|
||||
- bending in local `x-z` using `EIy`
|
||||
- transformation matrix convention and global stiffness equation `K_global = T^T K_local T`
|
||||
- end-force recovery equation `f_local = K_local u_local`
|
||||
- validation tolerances for unit tests, using deterministic double comparisons
|
||||
- singular and invalid input handling:
|
||||
- zero or near-zero length throws `std::invalid_argument`
|
||||
- nonpositive `E`, `G`, `A`, `J`, `Iy`, or `Iz` throws `std::invalid_argument`
|
||||
- orientation vector parallel to beam axis throws `std::invalid_argument`
|
||||
|
||||
Use compact matrices and named scalar coefficients so the implementation step can transcribe directly.
|
||||
|
||||
Do not create C++ files in this step.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- No C++ test is required in this documentation-only step.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm the matrix is symmetric by construction.
|
||||
2. Confirm local/global transformation convention is unambiguous.
|
||||
3. Update `phases/euler-beam-3d/index.json` step 2:
|
||||
- success: `"status": "completed"`, `"summary": "3D Euler beam formulation contract added"`
|
||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not modify requirements, source, tests, or reference artifacts.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Step 3: numerical-review
|
||||
|
||||
## Read These Files First
|
||||
|
||||
Read the following files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PRD.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/requirements/euler-beam-3d.md`
|
||||
- `/docs/research/euler-beam-3d-research.md`
|
||||
- `/docs/formulations/euler-beam-3d-formulation.md`
|
||||
- `/docs/numerical-reviews/README.md`
|
||||
|
||||
## Task
|
||||
|
||||
Create `/docs/numerical-reviews/euler-beam-3d-review.md`.
|
||||
|
||||
Review the formulation for implementation readiness. The review must include:
|
||||
|
||||
- dimension and units check for each stiffness coefficient
|
||||
- symmetry and rigid body mode expectations
|
||||
- local axis construction risks
|
||||
- positive semi-definite local stiffness expectation before constraints
|
||||
- test obligations before production code
|
||||
- known numerical limits for slender beams and very small section constants
|
||||
- pass/fail verdict for kernel implementation planning
|
||||
|
||||
If the formulation is not ready, mark the document status as `needs-upstream-decision` and update this phase step as blocked with a concrete reason.
|
||||
|
||||
Do not create C++ files in this step.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- No C++ test is required in this documentation-only step.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm the review does not change requirements or formulation.
|
||||
2. Confirm implementation-owned risks are turned into concrete tests.
|
||||
3. Update `phases/euler-beam-3d/index.json` step 3:
|
||||
- success: `"status": "completed"`, `"summary": "3D Euler beam numerical review added"`
|
||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not modify requirements, formulation, source, tests, or reference artifacts.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Step 4: io-reference-contract
|
||||
|
||||
## Read These Files First
|
||||
|
||||
Read the following files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PRD.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/requirements/euler-beam-3d.md`
|
||||
- `/docs/formulations/euler-beam-3d-formulation.md`
|
||||
- `/docs/numerical-reviews/euler-beam-3d-review.md`
|
||||
- `/docs/io-definitions/README.md`
|
||||
- `/docs/reference-models/README.md`
|
||||
|
||||
## Task
|
||||
|
||||
Create two documents:
|
||||
|
||||
- `/docs/io-definitions/euler-beam-3d-io.md`
|
||||
- `/docs/reference-models/euler-beam-3d-reference-models.md`
|
||||
|
||||
The I/O contract must describe the planned Abaqus subset without claiming it is implemented in this kernel increment:
|
||||
|
||||
- element keyword mapping candidate: two-node beam topology equivalent to Abaqus `B31`
|
||||
- section keyword candidate: beam section constants sufficient for `A`, `J`, `Iy`, `Iz`, `E`, and `G`
|
||||
- orientation data requirement for constructing local axes
|
||||
- boundary/load DOFs: `1..6` map to `ux, uy, uz, rx, ry, rz`
|
||||
- HDF5 output quantities expected after solver integration: nodal displacement, reaction, element internal force, stress placeholders if stress recovery is later approved
|
||||
- unsupported input cases and diagnostics
|
||||
|
||||
The reference model contract must list required future models without generating artifacts:
|
||||
|
||||
- axial cantilever bar-as-beam
|
||||
- torsion cantilever
|
||||
- bending cantilever about local `y`
|
||||
- bending cantilever about local `z`
|
||||
- skew-oriented beam transform check
|
||||
|
||||
For each future model, specify expected files under `reference/<model-id>/` and which CSV quantities are required. State that Abaqus reference CSVs must not be generated or modified in this phase.
|
||||
|
||||
Do not create C++ files in this step.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- No C++ test is required in this documentation-only step.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm reference artifacts are specified, not created.
|
||||
2. Confirm unsupported parser or solver paths are explicit open issues.
|
||||
3. Update `phases/euler-beam-3d/index.json` step 4:
|
||||
- success: `"status": "completed"`, `"summary": "3D Euler beam I/O and reference model contracts added"`
|
||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not create or modify files under `/reference/`.
|
||||
- Do not modify source or tests.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Step 5: implementation-plan
|
||||
|
||||
## Read These Files First
|
||||
|
||||
Read the following files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/PRD.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/SOLVER_AGENT_DESIGN.md`
|
||||
- `/docs/implementation-plans/README.md`
|
||||
- `/docs/requirements/euler-beam-3d.md`
|
||||
- `/docs/research/euler-beam-3d-research.md`
|
||||
- `/docs/formulations/euler-beam-3d-formulation.md`
|
||||
- `/docs/numerical-reviews/euler-beam-3d-review.md`
|
||||
- `/docs/io-definitions/euler-beam-3d-io.md`
|
||||
- `/docs/reference-models/euler-beam-3d-reference-models.md`
|
||||
- `/src/fesa/model/element.hpp`
|
||||
- `/tests/unit/model_element_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Create `/docs/implementation-plans/euler-beam-3d-implementation-plan.md`.
|
||||
|
||||
The implementation plan must be ready for C++ TDD work and must include:
|
||||
|
||||
- readiness check for requirements, research, formulation, numerical review, I/O, reference model contract
|
||||
- explicit kernel-first implementation scope
|
||||
- tasks matching the remaining phase steps:
|
||||
- model beam topology
|
||||
- local stiffness kernel
|
||||
- global transform and end-force recovery
|
||||
- build/test report
|
||||
- release readiness note
|
||||
- test IDs and RED/GREEN conditions for each production change
|
||||
- candidate files:
|
||||
- `src/fesa/model/element.hpp`
|
||||
- `src/fesa/model/element.cpp`
|
||||
- `tests/unit/model_element_test.cpp`
|
||||
- `src/fesa/elements/euler_beam_3d.hpp`
|
||||
- `src/fesa/elements/euler_beam_3d.cpp`
|
||||
- `tests/unit/euler_beam_3d_local_stiffness_test.cpp`
|
||||
- `tests/unit/euler_beam_3d_transform_recovery_test.cpp`
|
||||
- CTest commands:
|
||||
- `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model_element_test`
|
||||
- `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_local_stiffness_test`
|
||||
- `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_transform_recovery_test`
|
||||
- acceptance traceability from requirements to tests
|
||||
|
||||
Do not create C++ files in this step.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- No C++ test is required in this documentation-only step.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm every production file named in the plan has a related test.
|
||||
2. Confirm the plan does not ask Implementation Agent to change requirements, formulation, I/O contracts, reference artifacts, or tolerance policies.
|
||||
3. Update `phases/euler-beam-3d/index.json` step 5:
|
||||
- success: `"status": "completed"`, `"summary": "3D Euler beam implementation plan added"`
|
||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not modify source, tests, reference artifacts, or CMake files.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Step 6: model-beam-topology
|
||||
|
||||
## Read These Files First
|
||||
|
||||
Read the following files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/implementation-plans/euler-beam-3d-implementation-plan.md`
|
||||
- `/src/fesa/model/element.hpp`
|
||||
- `/src/fesa/model/element.cpp`
|
||||
- `/tests/unit/model_element_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Use TDD to add a semantic model topology for a two-node 3D beam.
|
||||
|
||||
Required production behavior:
|
||||
|
||||
- Add `beam2` to `fesa::model::ElementTopology`.
|
||||
- Preserve existing `truss2`, `bar2`, and `unknown` behavior.
|
||||
- Do not store equation IDs on `Element`.
|
||||
- Do not add section constants to `Element` in this step.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Modify `/tests/unit/model_element_test.cpp` first.
|
||||
|
||||
Add a failing assertion that constructs an element with:
|
||||
|
||||
- id `ElementId{10}`
|
||||
- topology `ElementTopology::beam2`
|
||||
- node ids `{NodeId{1}, NodeId{2}}`
|
||||
- property id `PropertyId{7}`
|
||||
|
||||
The test must verify:
|
||||
|
||||
- `element.topology() == ElementTopology::beam2`
|
||||
- `element.node_ids().size() == 2`
|
||||
- existing `bar2` behavior still works or the test still covers it
|
||||
|
||||
RED command:
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model_element_test
|
||||
```
|
||||
|
||||
Expected RED: compile failure because `ElementTopology::beam2` is not defined.
|
||||
|
||||
Then implement the minimal enum addition.
|
||||
|
||||
GREEN command:
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model_element_test
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model_element_test
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm the test failed before editing production code.
|
||||
2. Confirm no unrelated model refactor was made.
|
||||
3. Update `phases/euler-beam-3d/index.json` step 6:
|
||||
- success: `"status": "completed"`, `"summary": "beam2 model topology added with unit test"`
|
||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not create parser code.
|
||||
- Do not add beam stiffness code in this step.
|
||||
@@ -0,0 +1,110 @@
|
||||
# Step 7: local-stiffness-kernel
|
||||
|
||||
## Read These Files First
|
||||
|
||||
Read the following files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/formulations/euler-beam-3d-formulation.md`
|
||||
- `/docs/numerical-reviews/euler-beam-3d-review.md`
|
||||
- `/docs/implementation-plans/euler-beam-3d-implementation-plan.md`
|
||||
|
||||
Also read any files created by previous steps in this phase.
|
||||
|
||||
## Task
|
||||
|
||||
Use TDD to create the local 12x12 stiffness kernel for the 3D Euler-Bernoulli beam.
|
||||
|
||||
Create:
|
||||
|
||||
- `/src/fesa/elements/euler_beam_3d.hpp`
|
||||
- `/src/fesa/elements/euler_beam_3d.cpp`
|
||||
- `/tests/unit/euler_beam_3d_local_stiffness_test.cpp`
|
||||
|
||||
Required API:
|
||||
|
||||
```cpp
|
||||
namespace fesa::elements {
|
||||
|
||||
using Vector12 = std::array<double, 12>;
|
||||
using Matrix12 = std::array<double, 144>;
|
||||
|
||||
struct EulerBeam3DSection {
|
||||
double young_modulus;
|
||||
double shear_modulus;
|
||||
double area;
|
||||
double torsion_constant;
|
||||
double second_moment_y;
|
||||
double second_moment_z;
|
||||
};
|
||||
|
||||
Matrix12 euler_beam_3d_local_stiffness(double length, const EulerBeam3DSection& section);
|
||||
Vector12 euler_beam_3d_local_end_forces(double length,
|
||||
const EulerBeam3DSection& section,
|
||||
const Vector12& local_displacements);
|
||||
|
||||
} // namespace fesa::elements
|
||||
```
|
||||
|
||||
Implementation rules:
|
||||
|
||||
- Matrix storage is row-major: index `(row, col)` is `row * 12 + col`.
|
||||
- Validate `length > 0` and all section constants are positive; throw `std::invalid_argument` otherwise.
|
||||
- Use only C++17 standard library.
|
||||
- Do not introduce an external linear algebra dependency.
|
||||
- Do not edit CMake files; source and test globs should pick up new files.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Create `/tests/unit/euler_beam_3d_local_stiffness_test.cpp` before production code.
|
||||
|
||||
Test behavior:
|
||||
|
||||
- For `L = 2.0`, `E = 210.0`, `G = 80.0`, `A = 3.0`, `J = 4.0`, `Iy = 5.0`, `Iz = 6.0`, verify representative matrix entries:
|
||||
- axial: `K(0,0) = EA/L`, `K(0,6) = -EA/L`, `K(6,6) = EA/L`
|
||||
- torsion: `K(3,3) = GJ/L`, `K(3,9) = -GJ/L`, `K(9,9) = GJ/L`
|
||||
- local `x-y` bending uses `EIz`: `K(1,1) = 12*E*Iz/L^3`, `K(1,5) = 6*E*Iz/L^2`, `K(5,5) = 4*E*Iz/L`
|
||||
- local `x-z` bending uses `EIy`: `K(2,2) = 12*E*Iy/L^3`, `K(2,4) = -6*E*Iy/L^2`, `K(4,4) = 4*E*Iy/L`
|
||||
- Verify all entries are symmetric within `1.0e-10`.
|
||||
- Verify `euler_beam_3d_local_end_forces` returns `K * u` for a displacement vector with at least three nonzero components.
|
||||
- Verify invalid length and nonpositive section constants throw `std::invalid_argument`.
|
||||
|
||||
RED command:
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_local_stiffness_test
|
||||
```
|
||||
|
||||
Expected RED: test executable missing or compile failure because the header/API does not exist.
|
||||
|
||||
Then implement the minimal API.
|
||||
|
||||
GREEN command:
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_local_stiffness_test
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_local_stiffness_test
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm RED failed before production code was added.
|
||||
2. Confirm no parser, assembly, or results writer code was changed.
|
||||
3. Update `phases/euler-beam-3d/index.json` step 7:
|
||||
- success: `"status": "completed"`, `"summary": "local 3D Euler beam stiffness and local end-force kernel added"`
|
||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not add shear deformation or Timoshenko terms.
|
||||
- Do not modify reference artifacts.
|
||||
@@ -0,0 +1,97 @@
|
||||
# Step 8: global-transform-recovery
|
||||
|
||||
## Read These Files First
|
||||
|
||||
Read the following files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/ARCHITECTURE.md`
|
||||
- `/docs/ADR.md`
|
||||
- `/docs/formulations/euler-beam-3d-formulation.md`
|
||||
- `/docs/numerical-reviews/euler-beam-3d-review.md`
|
||||
- `/docs/implementation-plans/euler-beam-3d-implementation-plan.md`
|
||||
- `/src/fesa/elements/euler_beam_3d.hpp`
|
||||
- `/src/fesa/elements/euler_beam_3d.cpp`
|
||||
- `/tests/unit/euler_beam_3d_local_stiffness_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Use TDD to add local/global transformation and global end-force recovery to the 3D Euler-Bernoulli beam kernel.
|
||||
|
||||
Extend the API in `/src/fesa/elements/euler_beam_3d.hpp`:
|
||||
|
||||
```cpp
|
||||
using Vector3 = std::array<double, 3>;
|
||||
|
||||
struct EulerBeam3DGeometry {
|
||||
Vector3 node1;
|
||||
Vector3 node2;
|
||||
Vector3 orientation;
|
||||
};
|
||||
|
||||
Matrix12 euler_beam_3d_global_stiffness(const EulerBeam3DGeometry& geometry,
|
||||
const EulerBeam3DSection& section);
|
||||
Vector12 euler_beam_3d_global_end_forces(const EulerBeam3DGeometry& geometry,
|
||||
const EulerBeam3DSection& section,
|
||||
const Vector12& global_displacements);
|
||||
```
|
||||
|
||||
Implementation rules:
|
||||
|
||||
- local `x` is normalized `node2 - node1`
|
||||
- local `y` is the normalized projection of `orientation` onto the plane normal to local `x`
|
||||
- local `z = x cross y`
|
||||
- use the same 3x3 rotation block for translational and rotational DOFs
|
||||
- compute `K_global = T^T K_local T`
|
||||
- compute global end forces by transforming global displacements to local, recovering local forces, then transforming forces back to global
|
||||
- throw `std::invalid_argument` for zero-length element, zero orientation vector, or orientation parallel to the beam axis
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
Create `/tests/unit/euler_beam_3d_transform_recovery_test.cpp` before production code.
|
||||
|
||||
Test behavior:
|
||||
|
||||
- Axis-aligned beam from `(0,0,0)` to `(2,0,0)` with orientation `(0,1,0)` gives global stiffness equal to local stiffness.
|
||||
- A rotated beam preserves stiffness symmetry.
|
||||
- A rigid global translation vector produces near-zero global end forces.
|
||||
- A simple global axial extension on the axis-aligned beam produces equal and opposite axial end forces.
|
||||
- Parallel orientation vector throws `std::invalid_argument`.
|
||||
|
||||
RED command:
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_transform_recovery_test
|
||||
```
|
||||
|
||||
Expected RED: test executable missing or compile failure because the transform API does not exist.
|
||||
|
||||
Then implement the minimal API.
|
||||
|
||||
GREEN command:
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R euler_beam_3d_transform_recovery_test
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "euler_beam_3d_(local_stiffness|transform_recovery)_test"
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Confirm RED failed before production code was added.
|
||||
2. Confirm local stiffness tests still pass.
|
||||
3. Update `phases/euler-beam-3d/index.json` step 8:
|
||||
- success: `"status": "completed"`, `"summary": "global transform and global end-force recovery added for 3D Euler beam"`
|
||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not add assembly or solver integration in this step.
|
||||
- Do not modify reference artifacts.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Step 9: build-test-report
|
||||
|
||||
## Read These Files First
|
||||
|
||||
Read the following files before editing:
|
||||
|
||||
- `/AGENTS.md`
|
||||
- `/docs/build-test-reports/README.md`
|
||||
- `/docs/implementation-plans/euler-beam-3d-implementation-plan.md`
|
||||
- `/src/fesa/elements/euler_beam_3d.hpp`
|
||||
- `/src/fesa/elements/euler_beam_3d.cpp`
|
||||
- `/tests/unit/euler_beam_3d_local_stiffness_test.cpp`
|
||||
- `/tests/unit/euler_beam_3d_transform_recovery_test.cpp`
|
||||
|
||||
## Task
|
||||
|
||||
Create `/docs/build-test-reports/euler-beam-3d-build-test.md`.
|
||||
|
||||
The report must record:
|
||||
|
||||
- feature id
|
||||
- changed files observed for this phase
|
||||
- command log summary with exit codes and short evidence tails
|
||||
- validation results for:
|
||||
- harness self-test
|
||||
- CMake configure/build
|
||||
- CTest
|
||||
- feature-specific tests
|
||||
- failure classification if anything failed
|
||||
- explicit statement that this report does not approve reference verification or release readiness
|
||||
|
||||
Do not change source or tests in this step.
|
||||
|
||||
## Tests To Write First
|
||||
|
||||
- No C++ test is required in this documentation-only step.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
```powershell
|
||||
ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R "model_element_test|euler_beam_3d_(local_stiffness|transform_recovery)_test"
|
||||
python -m unittest discover -s scripts -p "test_*.py"
|
||||
python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
## Verification Notes
|
||||
|
||||
1. Record actual command exit codes and concise output evidence.
|
||||
2. If validation fails for an environment reason, mark the report `needs-environment-fix` and update this phase step as blocked.
|
||||
3. Update `phases/euler-beam-3d/index.json` step 9:
|
||||
- success: `"status": "completed"`, `"summary": "3D Euler beam build/test report added"`
|
||||
- failure after retries: `"status": "error"`, `"error_message": "<specific error>"`
|
||||
- blocked: `"status": "blocked"`, `"blocked_reason": "<specific reason>"`
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not modify source, tests, requirements, formulations, I/O contracts, or reference artifacts.
|
||||
@@ -2,6 +2,10 @@
|
||||
"phases": [
|
||||
{
|
||||
"dir": "solver-core-skeleton",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"dir": "euler-beam-3d",
|
||||
"status": "pending"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -9,79 +9,92 @@
|
||||
"allowed_paths": [
|
||||
"CMakeLists.txt",
|
||||
"tests/"
|
||||
]
|
||||
],
|
||||
"started_at": "2026-06-12T02:09:10+0900",
|
||||
"summary": "CMake/CTest bootstrap with fesa_core interface target and smoke test",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"step": 1,
|
||||
"name": "core-primitives",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"allowed_paths": [
|
||||
"src/fesa/core/",
|
||||
"tests/unit/core_*_test.cpp"
|
||||
]
|
||||
],
|
||||
"summary": "Core ID, diagnostic, and status primitives added with tests"
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "domain-model-entities",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"allowed_paths": [
|
||||
"src/fesa/model/",
|
||||
"tests/unit/model_*_test.cpp"
|
||||
]
|
||||
],
|
||||
"summary": "Model entities and Domain ownership API added with tests"
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "analysis-model-view",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"allowed_paths": [
|
||||
"src/fesa/analysis/",
|
||||
"tests/unit/analysis_model_*_test.cpp"
|
||||
]
|
||||
],
|
||||
"summary": "AnalysisModel step view added without Domain copies"
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "dof-manager",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"allowed_paths": [
|
||||
"src/fesa/fem/",
|
||||
"tests/unit/dof_manager_*_test.cpp"
|
||||
]
|
||||
],
|
||||
"summary": "DofManager deterministic numbering and constrained/free mapping added"
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "analysis-state",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"allowed_paths": [
|
||||
"src/fesa/analysis/",
|
||||
"tests/unit/analysis_state_*_test.cpp"
|
||||
]
|
||||
],
|
||||
"summary": "AnalysisState vector ownership and residual update added"
|
||||
},
|
||||
{
|
||||
"step": 6,
|
||||
"name": "analysis-template-flow",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"allowed_paths": [
|
||||
"src/fesa/analysis/",
|
||||
"tests/unit/analysis_flow_*_test.cpp"
|
||||
]
|
||||
],
|
||||
"summary": "Analysis template method and LinearStaticAnalysis skeleton added"
|
||||
},
|
||||
{
|
||||
"step": 7,
|
||||
"name": "results-containers",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"allowed_paths": [
|
||||
"src/fesa/results/",
|
||||
"tests/unit/results_*_test.cpp"
|
||||
]
|
||||
],
|
||||
"summary": "ResultStep, ResultFrame, FieldOutput, and HistoryOutput containers added"
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
"name": "solver-skeleton-integration-report",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"allowed_paths": [
|
||||
"tests/integration/",
|
||||
"docs/build-test-reports/"
|
||||
]
|
||||
],
|
||||
"summary": "Solver skeleton integration test and build/test report added"
|
||||
}
|
||||
]
|
||||
],
|
||||
"created_at": "2026-06-12T02:09:10+0900",
|
||||
"completed_at": "2026-06-12T02:42:00+0900"
|
||||
}
|
||||
|
||||
+2
-2
@@ -373,8 +373,8 @@ class StepExecutor:
|
||||
|
||||
prompt = preamble + step_file.read_text(encoding="utf-8")
|
||||
result = subprocess.run(
|
||||
["codex", "exec", "--dangerously-bypass-approvals-and-sandbox", "--json", prompt],
|
||||
cwd=self._root, capture_output=True, text=True, timeout=1800,
|
||||
["codex", "exec", "--dangerously-bypass-approvals-and-sandbox", "--json", "-"],
|
||||
cwd=self._root, capture_output=True, text=True, input=prompt, timeout=1800,
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
|
||||
@@ -281,6 +281,30 @@ class ExecuteRunnerSafetyTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(cm.exception.code, 1)
|
||||
|
||||
def test_invoke_codex_passes_prompt_through_stdin(self):
|
||||
execute = load_execute()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
write_phase(root)
|
||||
executor = make_executor(execute, root)
|
||||
step = {"step": 1, "name": "Docs"}
|
||||
long_preamble = "x" * 40000
|
||||
|
||||
def fake_run(cmd, **kwargs):
|
||||
return subprocess.CompletedProcess(cmd, 0, '{"event":"done"}\n', "")
|
||||
|
||||
with patch.object(execute.subprocess, "run", side_effect=fake_run) as run_mock:
|
||||
executor._invoke_codex(step, long_preamble)
|
||||
|
||||
cmd = run_mock.call_args.args[0]
|
||||
kwargs = run_mock.call_args.kwargs
|
||||
self.assertEqual(
|
||||
cmd,
|
||||
["codex", "exec", "--dangerously-bypass-approvals-and-sandbox", "--json", "-"],
|
||||
)
|
||||
self.assertEqual(kwargs["input"], long_preamble + "# Step 1\n")
|
||||
self.assertEqual(kwargs["cwd"], str(root))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import re
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
HEADER_ROOT = ROOT / "src" / "fesa"
|
||||
|
||||
|
||||
def _looks_like_function_definition(prefix):
|
||||
if "(" not in prefix or ")" not in prefix:
|
||||
return False
|
||||
|
||||
stripped = prefix.strip()
|
||||
control_prefixes = ("if ", "for ", "while ", "switch ", "catch ")
|
||||
declaration_prefixes = ("namespace ", "class ", "struct ", "enum ")
|
||||
return not stripped.startswith(control_prefixes + declaration_prefixes)
|
||||
|
||||
|
||||
class HeaderDeclarationOnlyTests(unittest.TestCase):
|
||||
def test_solver_headers_do_not_contain_function_bodies(self):
|
||||
violations = []
|
||||
|
||||
for header in sorted(HEADER_ROOT.rglob("*.hpp")):
|
||||
candidate = ""
|
||||
for line_number, line in enumerate(header.read_text(encoding="utf-8").splitlines(), start=1):
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
continue
|
||||
|
||||
candidate = f"{candidate} {stripped}".strip()
|
||||
if "{" in stripped and _looks_like_function_definition(candidate.split("{", 1)[0]):
|
||||
violations.append(f"{header.relative_to(ROOT)}:{line_number}: function body in header")
|
||||
|
||||
if re.search(r"\([^;{}]*\)\s*=\s*(default|delete)\s*;", stripped):
|
||||
violations.append(f"{header.relative_to(ROOT)}:{line_number}: function definition in header")
|
||||
|
||||
if stripped.endswith(";") or stripped.endswith("}") or stripped.endswith(":"):
|
||||
candidate = ""
|
||||
|
||||
self.assertEqual([], violations)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <fesa/analysis/analysis.hpp>
|
||||
|
||||
namespace fesa::analysis {
|
||||
|
||||
Analysis::~Analysis() = default;
|
||||
|
||||
void Analysis::run()
|
||||
{
|
||||
initialize();
|
||||
build_analysis_model();
|
||||
build_dof_map();
|
||||
build_sparse_pattern();
|
||||
assemble();
|
||||
apply_boundary_conditions();
|
||||
solve();
|
||||
update_state();
|
||||
write_results();
|
||||
}
|
||||
|
||||
void Analysis::initialize() {}
|
||||
void Analysis::build_analysis_model() {}
|
||||
void Analysis::build_dof_map() {}
|
||||
void Analysis::build_sparse_pattern() {}
|
||||
void Analysis::assemble() {}
|
||||
void Analysis::apply_boundary_conditions() {}
|
||||
void Analysis::solve() {}
|
||||
void Analysis::update_state() {}
|
||||
void Analysis::write_results() {}
|
||||
|
||||
} // namespace fesa::analysis
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
namespace fesa::analysis {
|
||||
|
||||
class Analysis {
|
||||
public:
|
||||
virtual ~Analysis();
|
||||
|
||||
void run();
|
||||
|
||||
protected:
|
||||
virtual void initialize();
|
||||
virtual void build_analysis_model();
|
||||
virtual void build_dof_map();
|
||||
virtual void build_sparse_pattern();
|
||||
virtual void assemble();
|
||||
virtual void apply_boundary_conditions();
|
||||
virtual void solve();
|
||||
virtual void update_state();
|
||||
virtual void write_results();
|
||||
};
|
||||
|
||||
} // namespace fesa::analysis
|
||||
@@ -0,0 +1,60 @@
|
||||
#include <fesa/analysis/analysis_model.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace fesa::analysis {
|
||||
|
||||
AnalysisModel::AnalysisModel(const model::Domain& domain, core::StepId step_id)
|
||||
: domain_(domain), step_(domain.find_step(step_id))
|
||||
{
|
||||
if (step_ == nullptr) {
|
||||
throw std::invalid_argument("analysis step not found");
|
||||
}
|
||||
|
||||
for (const auto& element : domain_.elements()) {
|
||||
active_elements_.push_back(&element);
|
||||
}
|
||||
for (const auto& boundary_condition : step_->boundary_conditions()) {
|
||||
active_boundary_conditions_.push_back(&boundary_condition);
|
||||
}
|
||||
for (const auto& load : step_->loads()) {
|
||||
active_loads_.push_back(&load);
|
||||
}
|
||||
}
|
||||
|
||||
const model::Domain& AnalysisModel::domain() const
|
||||
{
|
||||
return domain_;
|
||||
}
|
||||
|
||||
const model::AnalysisStep& AnalysisModel::step() const
|
||||
{
|
||||
return *step_;
|
||||
}
|
||||
|
||||
const std::vector<const model::Element*>& AnalysisModel::active_elements() const
|
||||
{
|
||||
return active_elements_;
|
||||
}
|
||||
|
||||
const std::vector<const model::BoundaryCondition*>& AnalysisModel::active_boundary_conditions() const
|
||||
{
|
||||
return active_boundary_conditions_;
|
||||
}
|
||||
|
||||
const std::vector<const model::Load*>& AnalysisModel::active_loads() const
|
||||
{
|
||||
return active_loads_;
|
||||
}
|
||||
|
||||
const model::Property* AnalysisModel::property_for(const model::Element& element) const
|
||||
{
|
||||
return domain_.find_property(element.property_id());
|
||||
}
|
||||
|
||||
const model::Material* AnalysisModel::material_for(const model::Property& property) const
|
||||
{
|
||||
return domain_.find_material(property.material_id());
|
||||
}
|
||||
|
||||
} // namespace fesa::analysis
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/model/domain.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::analysis {
|
||||
|
||||
class AnalysisModel {
|
||||
public:
|
||||
AnalysisModel(const model::Domain& domain, core::StepId step_id);
|
||||
|
||||
const model::Domain& domain() const;
|
||||
const model::AnalysisStep& step() const;
|
||||
const std::vector<const model::Element*>& active_elements() const;
|
||||
const std::vector<const model::BoundaryCondition*>& active_boundary_conditions() const;
|
||||
const std::vector<const model::Load*>& active_loads() const;
|
||||
const model::Property* property_for(const model::Element& element) const;
|
||||
const model::Material* material_for(const model::Property& property) const;
|
||||
|
||||
private:
|
||||
const model::Domain& domain_;
|
||||
const model::AnalysisStep* step_;
|
||||
std::vector<const model::Element*> active_elements_;
|
||||
std::vector<const model::BoundaryCondition*> active_boundary_conditions_;
|
||||
std::vector<const model::Load*> active_loads_;
|
||||
};
|
||||
|
||||
} // namespace fesa::analysis
|
||||
@@ -0,0 +1,124 @@
|
||||
#include <fesa/analysis/analysis_state.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace fesa::analysis {
|
||||
|
||||
AnalysisState::AnalysisState(int total_dof_count)
|
||||
: displacement_(vector_of(total_dof_count)),
|
||||
velocity_(vector_of(total_dof_count)),
|
||||
acceleration_(vector_of(total_dof_count)),
|
||||
temperature_(vector_of(total_dof_count)),
|
||||
external_force_(vector_of(total_dof_count)),
|
||||
internal_force_(vector_of(total_dof_count)),
|
||||
residual_(vector_of(total_dof_count))
|
||||
{
|
||||
}
|
||||
|
||||
const std::vector<double>& AnalysisState::displacement() const
|
||||
{
|
||||
return displacement_;
|
||||
}
|
||||
|
||||
const std::vector<double>& AnalysisState::velocity() const
|
||||
{
|
||||
return velocity_;
|
||||
}
|
||||
|
||||
const std::vector<double>& AnalysisState::acceleration() const
|
||||
{
|
||||
return acceleration_;
|
||||
}
|
||||
|
||||
const std::vector<double>& AnalysisState::temperature() const
|
||||
{
|
||||
return temperature_;
|
||||
}
|
||||
|
||||
const std::vector<double>& AnalysisState::external_force() const
|
||||
{
|
||||
return external_force_;
|
||||
}
|
||||
|
||||
const std::vector<double>& AnalysisState::internal_force() const
|
||||
{
|
||||
return internal_force_;
|
||||
}
|
||||
|
||||
const std::vector<double>& AnalysisState::residual() const
|
||||
{
|
||||
return residual_;
|
||||
}
|
||||
|
||||
void AnalysisState::set_displacement(std::vector<double> values)
|
||||
{
|
||||
assign_same_size(displacement_, std::move(values));
|
||||
}
|
||||
|
||||
void AnalysisState::set_external_force(std::vector<double> values)
|
||||
{
|
||||
assign_same_size(external_force_, std::move(values));
|
||||
}
|
||||
|
||||
void AnalysisState::set_internal_force(std::vector<double> values)
|
||||
{
|
||||
assign_same_size(internal_force_, std::move(values));
|
||||
}
|
||||
|
||||
void AnalysisState::update_residual()
|
||||
{
|
||||
for (std::size_t index = 0; index < residual_.size(); ++index) {
|
||||
residual_[index] = external_force_[index] - internal_force_[index];
|
||||
}
|
||||
}
|
||||
|
||||
IterationState& AnalysisState::iteration_state()
|
||||
{
|
||||
return iteration_state_;
|
||||
}
|
||||
|
||||
const IterationState& AnalysisState::iteration_state() const
|
||||
{
|
||||
return iteration_state_;
|
||||
}
|
||||
|
||||
void AnalysisState::set_element_state(core::ElementId element_id, std::vector<double> state)
|
||||
{
|
||||
for (auto& entry : element_states_) {
|
||||
if (entry.first.value == element_id.value) {
|
||||
entry.second = std::move(state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
element_states_.push_back({element_id, std::move(state)});
|
||||
}
|
||||
|
||||
const std::vector<double>* AnalysisState::element_state(core::ElementId element_id) const
|
||||
{
|
||||
for (const auto& entry : element_states_) {
|
||||
if (entry.first.value == element_id.value) {
|
||||
return &entry.second;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<double> AnalysisState::vector_of(int size)
|
||||
{
|
||||
if (size < 0) {
|
||||
throw std::invalid_argument("negative dof count");
|
||||
}
|
||||
return std::vector<double>(static_cast<std::size_t>(size), 0.0);
|
||||
}
|
||||
|
||||
void AnalysisState::assign_same_size(std::vector<double>& target, std::vector<double> values)
|
||||
{
|
||||
if (target.size() != values.size()) {
|
||||
throw std::invalid_argument("vector size mismatch");
|
||||
}
|
||||
target = std::move(values);
|
||||
}
|
||||
|
||||
} // namespace fesa::analysis
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/core/ids.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::analysis {
|
||||
|
||||
struct IterationState {
|
||||
double time = 0.0;
|
||||
int increment = 0;
|
||||
int iteration = 0;
|
||||
};
|
||||
|
||||
class AnalysisState {
|
||||
public:
|
||||
explicit AnalysisState(int total_dof_count);
|
||||
|
||||
const std::vector<double>& displacement() const;
|
||||
const std::vector<double>& velocity() const;
|
||||
const std::vector<double>& acceleration() const;
|
||||
const std::vector<double>& temperature() const;
|
||||
const std::vector<double>& external_force() const;
|
||||
const std::vector<double>& internal_force() const;
|
||||
const std::vector<double>& residual() const;
|
||||
|
||||
void set_displacement(std::vector<double> values);
|
||||
void set_external_force(std::vector<double> values);
|
||||
void set_internal_force(std::vector<double> values);
|
||||
void update_residual();
|
||||
|
||||
IterationState& iteration_state();
|
||||
const IterationState& iteration_state() const;
|
||||
|
||||
void set_element_state(core::ElementId element_id, std::vector<double> state);
|
||||
const std::vector<double>* element_state(core::ElementId element_id) const;
|
||||
|
||||
private:
|
||||
static std::vector<double> vector_of(int size);
|
||||
static void assign_same_size(std::vector<double>& target, std::vector<double> values);
|
||||
|
||||
std::vector<double> displacement_;
|
||||
std::vector<double> velocity_;
|
||||
std::vector<double> acceleration_;
|
||||
std::vector<double> temperature_;
|
||||
std::vector<double> external_force_;
|
||||
std::vector<double> internal_force_;
|
||||
std::vector<double> residual_;
|
||||
IterationState iteration_state_;
|
||||
std::vector<std::pair<core::ElementId, std::vector<double>>> element_states_;
|
||||
};
|
||||
|
||||
} // namespace fesa::analysis
|
||||
@@ -0,0 +1,48 @@
|
||||
#include <fesa/analysis/linear_static_analysis.hpp>
|
||||
|
||||
namespace fesa::analysis {
|
||||
|
||||
LinearStaticAnalysis::LinearStaticAnalysis(const model::Domain& domain, core::StepId step_id)
|
||||
: domain_(domain), step_id_(step_id)
|
||||
{
|
||||
}
|
||||
|
||||
const AnalysisModel* LinearStaticAnalysis::analysis_model() const
|
||||
{
|
||||
return analysis_model_.get();
|
||||
}
|
||||
|
||||
const AnalysisState* LinearStaticAnalysis::state() const
|
||||
{
|
||||
return state_.get();
|
||||
}
|
||||
|
||||
void LinearStaticAnalysis::build_analysis_model()
|
||||
{
|
||||
analysis_model_ = std::make_unique<AnalysisModel>(domain_, step_id_);
|
||||
}
|
||||
|
||||
void LinearStaticAnalysis::build_dof_map()
|
||||
{
|
||||
dof_manager_ = std::make_unique<fem::DofManager>();
|
||||
for (const auto* element : analysis_model_->active_elements()) {
|
||||
for (const auto node_id : element->node_ids()) {
|
||||
dof_manager_->define_node_dofs(node_id, {
|
||||
model::DofComponent::ux,
|
||||
model::DofComponent::uy,
|
||||
model::DofComponent::uz
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const auto* boundary_condition : analysis_model_->active_boundary_conditions()) {
|
||||
dof_manager_->apply_boundary_condition(*boundary_condition);
|
||||
}
|
||||
dof_manager_->number_equations();
|
||||
}
|
||||
|
||||
void LinearStaticAnalysis::update_state()
|
||||
{
|
||||
state_ = std::make_unique<AnalysisState>(dof_manager_->total_dof_count());
|
||||
}
|
||||
|
||||
} // namespace fesa::analysis
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/analysis/analysis.hpp>
|
||||
#include <fesa/analysis/analysis_model.hpp>
|
||||
#include <fesa/analysis/analysis_state.hpp>
|
||||
#include <fesa/fem/dof_manager.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace fesa::analysis {
|
||||
|
||||
class LinearStaticAnalysis : public Analysis {
|
||||
public:
|
||||
LinearStaticAnalysis(const model::Domain& domain, core::StepId step_id);
|
||||
|
||||
const AnalysisModel* analysis_model() const;
|
||||
const AnalysisState* state() const;
|
||||
|
||||
protected:
|
||||
void build_analysis_model() override;
|
||||
void build_dof_map() override;
|
||||
void update_state() override;
|
||||
|
||||
private:
|
||||
const model::Domain& domain_;
|
||||
core::StepId step_id_;
|
||||
std::unique_ptr<AnalysisModel> analysis_model_;
|
||||
std::unique_ptr<fem::DofManager> dof_manager_;
|
||||
std::unique_ptr<AnalysisState> state_;
|
||||
};
|
||||
|
||||
} // namespace fesa::analysis
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
enum class Severity {
|
||||
info,
|
||||
warning,
|
||||
error
|
||||
};
|
||||
|
||||
struct Diagnostic {
|
||||
Severity severity;
|
||||
std::string code;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
struct NodeId {
|
||||
int value;
|
||||
};
|
||||
|
||||
struct ElementId {
|
||||
int value;
|
||||
};
|
||||
|
||||
struct MaterialId {
|
||||
int value;
|
||||
};
|
||||
|
||||
struct PropertyId {
|
||||
int value;
|
||||
};
|
||||
|
||||
struct StepId {
|
||||
int value;
|
||||
};
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,34 @@
|
||||
#include <fesa/core/status.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
Status Status::ok()
|
||||
{
|
||||
return Status{};
|
||||
}
|
||||
|
||||
Status Status::failure(Diagnostic diagnostic)
|
||||
{
|
||||
Status status;
|
||||
status.add(std::move(diagnostic));
|
||||
return status;
|
||||
}
|
||||
|
||||
bool Status::is_ok() const
|
||||
{
|
||||
return diagnostics_.empty();
|
||||
}
|
||||
|
||||
const std::vector<Diagnostic>& Status::diagnostics() const
|
||||
{
|
||||
return diagnostics_;
|
||||
}
|
||||
|
||||
void Status::add(Diagnostic diagnostic)
|
||||
{
|
||||
diagnostics_.push_back(std::move(diagnostic));
|
||||
}
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/core/diagnostic.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::core {
|
||||
|
||||
class Status {
|
||||
public:
|
||||
static Status ok();
|
||||
static Status failure(Diagnostic diagnostic);
|
||||
|
||||
bool is_ok() const;
|
||||
const std::vector<Diagnostic>& diagnostics() const;
|
||||
void add(Diagnostic diagnostic);
|
||||
|
||||
private:
|
||||
std::vector<Diagnostic> diagnostics_;
|
||||
};
|
||||
|
||||
} // namespace fesa::core
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <fesa/fem/dof_key.hpp>
|
||||
|
||||
namespace fesa::fem {
|
||||
|
||||
bool operator==(const DofKey& lhs, const DofKey& rhs)
|
||||
{
|
||||
return lhs.node_id.value == rhs.node_id.value && lhs.component == rhs.component;
|
||||
}
|
||||
|
||||
} // namespace fesa::fem
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/core/ids.hpp>
|
||||
#include <fesa/model/boundary_condition.hpp>
|
||||
|
||||
namespace fesa::fem {
|
||||
|
||||
struct DofKey {
|
||||
core::NodeId node_id;
|
||||
model::DofComponent component;
|
||||
};
|
||||
|
||||
bool operator==(const DofKey& lhs, const DofKey& rhs);
|
||||
|
||||
} // namespace fesa::fem
|
||||
@@ -0,0 +1,133 @@
|
||||
#include <fesa/fem/dof_manager.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace fesa::fem {
|
||||
|
||||
void DofManager::define_node_dofs(core::NodeId node_id, std::vector<model::DofComponent> components)
|
||||
{
|
||||
for (const auto component : components) {
|
||||
DofKey key{node_id, component};
|
||||
if (find_record(key) == records_.end()) {
|
||||
records_.push_back({key});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DofManager::apply_boundary_condition(const model::BoundaryCondition& bc)
|
||||
{
|
||||
auto record = find_record({bc.node_id(), bc.component()});
|
||||
if (record == records_.end()) {
|
||||
throw std::invalid_argument("boundary condition references undefined dof");
|
||||
}
|
||||
record->constrained = true;
|
||||
}
|
||||
|
||||
void DofManager::number_equations()
|
||||
{
|
||||
std::sort(records_.begin(), records_.end(), [](const Record& lhs, const Record& rhs) {
|
||||
if (lhs.key.node_id.value != rhs.key.node_id.value) {
|
||||
return lhs.key.node_id.value < rhs.key.node_id.value;
|
||||
}
|
||||
return static_cast<int>(lhs.key.component) < static_cast<int>(rhs.key.component);
|
||||
});
|
||||
|
||||
int free_id = 0;
|
||||
for (int equation_id = 0; equation_id < static_cast<int>(records_.size()); ++equation_id) {
|
||||
records_[equation_id].equation_id = equation_id;
|
||||
if (records_[equation_id].constrained) {
|
||||
records_[equation_id].free_equation_id = std::nullopt;
|
||||
} else {
|
||||
records_[equation_id].free_equation_id = free_id++;
|
||||
}
|
||||
}
|
||||
|
||||
sparse_pattern_.clear();
|
||||
for (int row = 0; row < free_id; ++row) {
|
||||
for (int column = 0; column < free_id; ++column) {
|
||||
sparse_pattern_.push_back({row, column});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DofManager::total_dof_count() const
|
||||
{
|
||||
return static_cast<int>(records_.size());
|
||||
}
|
||||
|
||||
int DofManager::free_dof_count() const
|
||||
{
|
||||
return static_cast<int>(
|
||||
std::count_if(records_.begin(), records_.end(), [](const Record& record) {
|
||||
return !record.constrained;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
int DofManager::constrained_dof_count() const
|
||||
{
|
||||
return total_dof_count() - free_dof_count();
|
||||
}
|
||||
|
||||
bool DofManager::is_constrained(DofKey key) const
|
||||
{
|
||||
return require_record(key).constrained;
|
||||
}
|
||||
|
||||
int DofManager::equation_id(DofKey key) const
|
||||
{
|
||||
return require_record(key).equation_id;
|
||||
}
|
||||
|
||||
std::optional<int> DofManager::free_equation_id(DofKey key) const
|
||||
{
|
||||
return require_record(key).free_equation_id;
|
||||
}
|
||||
|
||||
std::vector<double> DofManager::expand_free_vector(const std::vector<double>& free_values) const
|
||||
{
|
||||
if (free_values.size() != static_cast<std::size_t>(free_dof_count())) {
|
||||
throw std::invalid_argument("free vector size does not match dof manager");
|
||||
}
|
||||
|
||||
std::vector<double> full(records_.size(), 0.0);
|
||||
for (const auto& record : records_) {
|
||||
if (record.free_equation_id.has_value()) {
|
||||
full[static_cast<std::size_t>(record.equation_id)] =
|
||||
free_values[static_cast<std::size_t>(*record.free_equation_id)];
|
||||
}
|
||||
}
|
||||
return full;
|
||||
}
|
||||
|
||||
const std::vector<std::pair<int, int>>& DofManager::sparse_pattern() const
|
||||
{
|
||||
return sparse_pattern_;
|
||||
}
|
||||
|
||||
std::vector<DofManager::Record>::iterator DofManager::find_record(DofKey key)
|
||||
{
|
||||
return std::find_if(records_.begin(), records_.end(), [key](const Record& record) {
|
||||
return record.key == key;
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<DofManager::Record>::const_iterator DofManager::find_record(DofKey key) const
|
||||
{
|
||||
return std::find_if(records_.begin(), records_.end(), [key](const Record& record) {
|
||||
return record.key == key;
|
||||
});
|
||||
}
|
||||
|
||||
const DofManager::Record& DofManager::require_record(DofKey key) const
|
||||
{
|
||||
const auto record = find_record(key);
|
||||
if (record == records_.end()) {
|
||||
throw std::invalid_argument("dof is not defined");
|
||||
}
|
||||
return *record;
|
||||
}
|
||||
|
||||
} // namespace fesa::fem
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/fem/dof_key.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::fem {
|
||||
|
||||
class DofManager {
|
||||
public:
|
||||
void define_node_dofs(core::NodeId node_id, std::vector<model::DofComponent> components);
|
||||
void apply_boundary_condition(const model::BoundaryCondition& bc);
|
||||
void number_equations();
|
||||
|
||||
int total_dof_count() const;
|
||||
int free_dof_count() const;
|
||||
int constrained_dof_count() const;
|
||||
bool is_constrained(DofKey key) const;
|
||||
int equation_id(DofKey key) const;
|
||||
std::optional<int> free_equation_id(DofKey key) const;
|
||||
std::vector<double> expand_free_vector(const std::vector<double>& free_values) const;
|
||||
const std::vector<std::pair<int, int>>& sparse_pattern() const;
|
||||
|
||||
private:
|
||||
struct Record {
|
||||
DofKey key;
|
||||
bool constrained = false;
|
||||
int equation_id = -1;
|
||||
std::optional<int> free_equation_id;
|
||||
};
|
||||
|
||||
std::vector<Record>::iterator find_record(DofKey key);
|
||||
std::vector<Record>::const_iterator find_record(DofKey key) const;
|
||||
const Record& require_record(DofKey key) const;
|
||||
|
||||
std::vector<Record> records_;
|
||||
std::vector<std::pair<int, int>> sparse_pattern_;
|
||||
};
|
||||
|
||||
} // namespace fesa::fem
|
||||
@@ -0,0 +1,42 @@
|
||||
#include <fesa/model/analysis_step.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
AnalysisStep::AnalysisStep(core::StepId id, std::string name)
|
||||
: id_(id), name_(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
core::StepId AnalysisStep::id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
const std::string& AnalysisStep::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
void AnalysisStep::add_boundary_condition(BoundaryCondition bc)
|
||||
{
|
||||
boundary_conditions_.push_back(std::move(bc));
|
||||
}
|
||||
|
||||
void AnalysisStep::add_load(Load load)
|
||||
{
|
||||
loads_.push_back(std::move(load));
|
||||
}
|
||||
|
||||
const std::vector<BoundaryCondition>& AnalysisStep::boundary_conditions() const
|
||||
{
|
||||
return boundary_conditions_;
|
||||
}
|
||||
|
||||
const std::vector<Load>& AnalysisStep::loads() const
|
||||
{
|
||||
return loads_;
|
||||
}
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/model/boundary_condition.hpp>
|
||||
#include <fesa/model/load.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
class AnalysisStep {
|
||||
public:
|
||||
AnalysisStep(core::StepId id, std::string name);
|
||||
|
||||
core::StepId id() const;
|
||||
const std::string& name() const;
|
||||
void add_boundary_condition(BoundaryCondition bc);
|
||||
void add_load(Load load);
|
||||
const std::vector<BoundaryCondition>& boundary_conditions() const;
|
||||
const std::vector<Load>& loads() const;
|
||||
|
||||
private:
|
||||
core::StepId id_;
|
||||
std::string name_;
|
||||
std::vector<BoundaryCondition> boundary_conditions_;
|
||||
std::vector<Load> loads_;
|
||||
};
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,25 @@
|
||||
#include <fesa/model/boundary_condition.hpp>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
BoundaryCondition::BoundaryCondition(core::NodeId node_id, DofComponent component, double value)
|
||||
: node_id_(node_id), component_(component), value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
core::NodeId BoundaryCondition::node_id() const
|
||||
{
|
||||
return node_id_;
|
||||
}
|
||||
|
||||
DofComponent BoundaryCondition::component() const
|
||||
{
|
||||
return component_;
|
||||
}
|
||||
|
||||
double BoundaryCondition::value() const
|
||||
{
|
||||
return value_;
|
||||
}
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/core/ids.hpp>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
enum class DofComponent {
|
||||
ux,
|
||||
uy,
|
||||
uz,
|
||||
rx,
|
||||
ry,
|
||||
rz,
|
||||
temperature
|
||||
};
|
||||
|
||||
class BoundaryCondition {
|
||||
public:
|
||||
BoundaryCondition(core::NodeId node_id, DofComponent component, double value);
|
||||
|
||||
core::NodeId node_id() const;
|
||||
DofComponent component() const;
|
||||
double value() const;
|
||||
|
||||
private:
|
||||
core::NodeId node_id_;
|
||||
DofComponent component_;
|
||||
double value_;
|
||||
};
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,107 @@
|
||||
#include <fesa/model/domain.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
void Domain::add_node(Node node)
|
||||
{
|
||||
nodes_.push_back(std::move(node));
|
||||
}
|
||||
|
||||
void Domain::add_element(Element element)
|
||||
{
|
||||
elements_.push_back(std::move(element));
|
||||
}
|
||||
|
||||
void Domain::add_material(Material material)
|
||||
{
|
||||
materials_.push_back(std::move(material));
|
||||
}
|
||||
|
||||
void Domain::add_property(Property property)
|
||||
{
|
||||
properties_.push_back(std::move(property));
|
||||
}
|
||||
|
||||
void Domain::add_step(AnalysisStep step)
|
||||
{
|
||||
steps_.push_back(std::move(step));
|
||||
}
|
||||
|
||||
const std::vector<Node>& Domain::nodes() const
|
||||
{
|
||||
return nodes_;
|
||||
}
|
||||
|
||||
const std::vector<Element>& Domain::elements() const
|
||||
{
|
||||
return elements_;
|
||||
}
|
||||
|
||||
const std::vector<Material>& Domain::materials() const
|
||||
{
|
||||
return materials_;
|
||||
}
|
||||
|
||||
const std::vector<Property>& Domain::properties() const
|
||||
{
|
||||
return properties_;
|
||||
}
|
||||
|
||||
const std::vector<AnalysisStep>& Domain::steps() const
|
||||
{
|
||||
return steps_;
|
||||
}
|
||||
|
||||
const Node* Domain::find_node(core::NodeId id) const
|
||||
{
|
||||
for (const auto& node : nodes_) {
|
||||
if (node.id().value == id.value) {
|
||||
return &node;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Element* Domain::find_element(core::ElementId id) const
|
||||
{
|
||||
for (const auto& element : elements_) {
|
||||
if (element.id().value == id.value) {
|
||||
return &element;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Material* Domain::find_material(core::MaterialId id) const
|
||||
{
|
||||
for (const auto& material : materials_) {
|
||||
if (material.id().value == id.value) {
|
||||
return &material;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Property* Domain::find_property(core::PropertyId id) const
|
||||
{
|
||||
for (const auto& property : properties_) {
|
||||
if (property.id().value == id.value) {
|
||||
return &property;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const AnalysisStep* Domain::find_step(core::StepId id) const
|
||||
{
|
||||
for (const auto& step : steps_) {
|
||||
if (step.id().value == id.value) {
|
||||
return &step;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/model/analysis_step.hpp>
|
||||
#include <fesa/model/element.hpp>
|
||||
#include <fesa/model/material.hpp>
|
||||
#include <fesa/model/node.hpp>
|
||||
#include <fesa/model/property.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
class Domain {
|
||||
public:
|
||||
void add_node(Node node);
|
||||
void add_element(Element element);
|
||||
void add_material(Material material);
|
||||
void add_property(Property property);
|
||||
void add_step(AnalysisStep step);
|
||||
|
||||
const std::vector<Node>& nodes() const;
|
||||
const std::vector<Element>& elements() const;
|
||||
const std::vector<Material>& materials() const;
|
||||
const std::vector<Property>& properties() const;
|
||||
const std::vector<AnalysisStep>& steps() const;
|
||||
|
||||
const Node* find_node(core::NodeId id) const;
|
||||
const Element* find_element(core::ElementId id) const;
|
||||
const Material* find_material(core::MaterialId id) const;
|
||||
const Property* find_property(core::PropertyId id) const;
|
||||
const AnalysisStep* find_step(core::StepId id) const;
|
||||
|
||||
private:
|
||||
std::vector<Node> nodes_;
|
||||
std::vector<Element> elements_;
|
||||
std::vector<Material> materials_;
|
||||
std::vector<Property> properties_;
|
||||
std::vector<AnalysisStep> steps_;
|
||||
};
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,38 @@
|
||||
#include <fesa/model/element.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
Element::Element(core::ElementId id,
|
||||
ElementTopology topology,
|
||||
std::vector<core::NodeId> node_ids,
|
||||
core::PropertyId property_id)
|
||||
: id_(id),
|
||||
topology_(topology),
|
||||
node_ids_(std::move(node_ids)),
|
||||
property_id_(property_id)
|
||||
{
|
||||
}
|
||||
|
||||
core::ElementId Element::id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
ElementTopology Element::topology() const
|
||||
{
|
||||
return topology_;
|
||||
}
|
||||
|
||||
const std::vector<core::NodeId>& Element::node_ids() const
|
||||
{
|
||||
return node_ids_;
|
||||
}
|
||||
|
||||
core::PropertyId Element::property_id() const
|
||||
{
|
||||
return property_id_;
|
||||
}
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/core/ids.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
enum class ElementTopology {
|
||||
truss2,
|
||||
bar2,
|
||||
unknown
|
||||
};
|
||||
|
||||
class Element {
|
||||
public:
|
||||
Element(core::ElementId id,
|
||||
ElementTopology topology,
|
||||
std::vector<core::NodeId> node_ids,
|
||||
core::PropertyId property_id);
|
||||
|
||||
core::ElementId id() const;
|
||||
ElementTopology topology() const;
|
||||
const std::vector<core::NodeId>& node_ids() const;
|
||||
core::PropertyId property_id() const;
|
||||
|
||||
private:
|
||||
core::ElementId id_;
|
||||
ElementTopology topology_;
|
||||
std::vector<core::NodeId> node_ids_;
|
||||
core::PropertyId property_id_;
|
||||
};
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,25 @@
|
||||
#include <fesa/model/load.hpp>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
Load::Load(core::NodeId node_id, DofComponent component, double value)
|
||||
: node_id_(node_id), component_(component), value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
core::NodeId Load::node_id() const
|
||||
{
|
||||
return node_id_;
|
||||
}
|
||||
|
||||
DofComponent Load::component() const
|
||||
{
|
||||
return component_;
|
||||
}
|
||||
|
||||
double Load::value() const
|
||||
{
|
||||
return value_;
|
||||
}
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/model/boundary_condition.hpp>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
class Load {
|
||||
public:
|
||||
Load(core::NodeId node_id, DofComponent component, double value);
|
||||
|
||||
core::NodeId node_id() const;
|
||||
DofComponent component() const;
|
||||
double value() const;
|
||||
|
||||
private:
|
||||
core::NodeId node_id_;
|
||||
DofComponent component_;
|
||||
double value_;
|
||||
};
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <fesa/model/material.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
Material::Material(core::MaterialId id, std::string name)
|
||||
: id_(id), name_(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
core::MaterialId Material::id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
const std::string& Material::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/core/ids.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
class Material {
|
||||
public:
|
||||
Material(core::MaterialId id, std::string name);
|
||||
|
||||
core::MaterialId id() const;
|
||||
const std::string& name() const;
|
||||
|
||||
private:
|
||||
core::MaterialId id_;
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <fesa/model/node.hpp>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
Node::Node(core::NodeId id, std::array<double, 3> coordinates)
|
||||
: id_(id), coordinates_(coordinates)
|
||||
{
|
||||
}
|
||||
|
||||
core::NodeId Node::id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
const std::array<double, 3>& Node::coordinates() const
|
||||
{
|
||||
return coordinates_;
|
||||
}
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/core/ids.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
class Node {
|
||||
public:
|
||||
Node(core::NodeId id, std::array<double, 3> coordinates);
|
||||
|
||||
core::NodeId id() const;
|
||||
const std::array<double, 3>& coordinates() const;
|
||||
|
||||
private:
|
||||
core::NodeId id_;
|
||||
std::array<double, 3> coordinates_;
|
||||
};
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,27 @@
|
||||
#include <fesa/model/property.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
Property::Property(core::PropertyId id, std::string name, core::MaterialId material_id)
|
||||
: id_(id), name_(std::move(name)), material_id_(material_id)
|
||||
{
|
||||
}
|
||||
|
||||
core::PropertyId Property::id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
const std::string& Property::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
core::MaterialId Property::material_id() const
|
||||
{
|
||||
return material_id_;
|
||||
}
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <fesa/core/ids.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace fesa::model {
|
||||
|
||||
class Property {
|
||||
public:
|
||||
Property(core::PropertyId id, std::string name, core::MaterialId material_id);
|
||||
|
||||
core::PropertyId id() const;
|
||||
const std::string& name() const;
|
||||
core::MaterialId material_id() const;
|
||||
|
||||
private:
|
||||
core::PropertyId id_;
|
||||
std::string name_;
|
||||
core::MaterialId material_id_;
|
||||
};
|
||||
|
||||
} // namespace fesa::model
|
||||
@@ -0,0 +1,70 @@
|
||||
#include <fesa/results/results.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace fesa::results {
|
||||
|
||||
ResultFrame::ResultFrame(int frame_id, double time)
|
||||
: frame_id_(frame_id), time_(time)
|
||||
{
|
||||
}
|
||||
|
||||
int ResultFrame::frame_id() const
|
||||
{
|
||||
return frame_id_;
|
||||
}
|
||||
|
||||
double ResultFrame::time() const
|
||||
{
|
||||
return time_;
|
||||
}
|
||||
|
||||
void ResultFrame::add_field_output(FieldOutput output)
|
||||
{
|
||||
if (output.components.empty()) {
|
||||
throw std::invalid_argument("field output must have components");
|
||||
}
|
||||
if (output.entity_ids.size() * output.components.size() != output.values.size()) {
|
||||
throw std::invalid_argument("field output values do not match row shape");
|
||||
}
|
||||
field_outputs_.push_back(std::move(output));
|
||||
}
|
||||
|
||||
void ResultFrame::add_history_output(HistoryOutput output)
|
||||
{
|
||||
history_outputs_.push_back(std::move(output));
|
||||
}
|
||||
|
||||
const std::vector<FieldOutput>& ResultFrame::field_outputs() const
|
||||
{
|
||||
return field_outputs_;
|
||||
}
|
||||
|
||||
const std::vector<HistoryOutput>& ResultFrame::history_outputs() const
|
||||
{
|
||||
return history_outputs_;
|
||||
}
|
||||
|
||||
ResultStep::ResultStep(std::string name)
|
||||
: name_(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
const std::string& ResultStep::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
ResultFrame& ResultStep::add_frame(int frame_id, double time)
|
||||
{
|
||||
frames_.push_back(ResultFrame{frame_id, time});
|
||||
return frames_.back();
|
||||
}
|
||||
|
||||
const std::vector<ResultFrame>& ResultStep::frames() const
|
||||
{
|
||||
return frames_;
|
||||
}
|
||||
|
||||
} // namespace fesa::results
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa::results {
|
||||
|
||||
enum class FieldLocation {
|
||||
nodal,
|
||||
element,
|
||||
integration_point
|
||||
};
|
||||
|
||||
struct FieldOutput {
|
||||
std::string name;
|
||||
FieldLocation location;
|
||||
std::vector<std::string> components;
|
||||
std::vector<int> entity_ids;
|
||||
std::vector<double> values;
|
||||
};
|
||||
|
||||
struct HistoryOutput {
|
||||
std::string name;
|
||||
std::vector<double> time;
|
||||
std::vector<double> values;
|
||||
};
|
||||
|
||||
class ResultFrame {
|
||||
public:
|
||||
ResultFrame(int frame_id, double time);
|
||||
|
||||
int frame_id() const;
|
||||
double time() const;
|
||||
void add_field_output(FieldOutput output);
|
||||
void add_history_output(HistoryOutput output);
|
||||
const std::vector<FieldOutput>& field_outputs() const;
|
||||
const std::vector<HistoryOutput>& history_outputs() const;
|
||||
|
||||
private:
|
||||
int frame_id_;
|
||||
double time_;
|
||||
std::vector<FieldOutput> field_outputs_;
|
||||
std::vector<HistoryOutput> history_outputs_;
|
||||
};
|
||||
|
||||
class ResultStep {
|
||||
public:
|
||||
explicit ResultStep(std::string name);
|
||||
|
||||
const std::string& name() const;
|
||||
ResultFrame& add_frame(int frame_id, double time);
|
||||
const std::vector<ResultFrame>& frames() const;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::vector<ResultFrame> frames_;
|
||||
};
|
||||
|
||||
} // namespace fesa::results
|
||||
@@ -0,0 +1,2 @@
|
||||
add_subdirectory(unit)
|
||||
add_subdirectory(integration)
|
||||
@@ -0,0 +1,8 @@
|
||||
file(GLOB FESA_INTEGRATION_TEST_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*_test.cpp")
|
||||
|
||||
foreach(test_source IN LISTS FESA_INTEGRATION_TEST_SOURCES)
|
||||
get_filename_component(test_name "${test_source}" NAME_WE)
|
||||
add_executable("${test_name}" "${test_source}")
|
||||
target_link_libraries("${test_name}" PRIVATE fesa_core)
|
||||
add_test(NAME "${test_name}" COMMAND "${test_name}")
|
||||
endforeach()
|
||||
@@ -0,0 +1,64 @@
|
||||
#include <fesa/analysis/linear_static_analysis.hpp>
|
||||
#include <fesa/results/results.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
int fail()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
fesa::model::Domain make_domain()
|
||||
{
|
||||
fesa::model::Domain domain;
|
||||
domain.add_node({fesa::core::NodeId{1}, {0.0, 0.0, 0.0}});
|
||||
domain.add_node({fesa::core::NodeId{2}, {1.0, 0.0, 0.0}});
|
||||
domain.add_material({fesa::core::MaterialId{3}, "steel"});
|
||||
domain.add_property({fesa::core::PropertyId{4}, "bar", fesa::core::MaterialId{3}});
|
||||
domain.add_element({
|
||||
fesa::core::ElementId{5},
|
||||
fesa::model::ElementTopology::truss2,
|
||||
{fesa::core::NodeId{1}, fesa::core::NodeId{2}},
|
||||
fesa::core::PropertyId{4}
|
||||
});
|
||||
fesa::model::AnalysisStep step{fesa::core::StepId{6}, "static-step"};
|
||||
step.add_boundary_condition({fesa::core::NodeId{1}, fesa::model::DofComponent::ux, 0.0});
|
||||
step.add_load({fesa::core::NodeId{2}, fesa::model::DofComponent::ux, 10.0});
|
||||
domain.add_step(step);
|
||||
return domain;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
const auto domain = make_domain();
|
||||
fesa::analysis::LinearStaticAnalysis analysis{domain, fesa::core::StepId{6}};
|
||||
analysis.run();
|
||||
|
||||
if (analysis.analysis_model() == nullptr || analysis.state() == nullptr) {
|
||||
return fail();
|
||||
}
|
||||
if (analysis.analysis_model()->active_elements().size() != 1 ||
|
||||
analysis.analysis_model()->active_boundary_conditions().size() != 1 ||
|
||||
analysis.analysis_model()->active_loads().size() != 1) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
fesa::results::ResultStep result_step{"static-step"};
|
||||
auto& frame = result_step.add_frame(0, 0.0);
|
||||
frame.add_field_output({
|
||||
"U",
|
||||
fesa::results::FieldLocation::nodal,
|
||||
{"ux", "uy", "uz"},
|
||||
{1, 2},
|
||||
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0}
|
||||
});
|
||||
|
||||
if (result_step.frames().size() != 1 ||
|
||||
result_step.frames()[0].field_outputs().size() != 1) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
file(GLOB FESA_UNIT_TEST_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*_test.cpp")
|
||||
|
||||
foreach(test_source IN LISTS FESA_UNIT_TEST_SOURCES)
|
||||
get_filename_component(test_name "${test_source}" NAME_WE)
|
||||
add_executable("${test_name}" "${test_source}")
|
||||
target_link_libraries("${test_name}" PRIVATE fesa_core)
|
||||
add_test(NAME "${test_name}" COMMAND "${test_name}")
|
||||
endforeach()
|
||||
@@ -0,0 +1,37 @@
|
||||
#include <fesa/analysis/linear_static_analysis.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
fesa::model::Domain make_domain()
|
||||
{
|
||||
fesa::model::Domain domain;
|
||||
domain.add_node({fesa::core::NodeId{1}, {0.0, 0.0, 0.0}});
|
||||
domain.add_node({fesa::core::NodeId{2}, {1.0, 0.0, 0.0}});
|
||||
domain.add_material({fesa::core::MaterialId{3}, "steel"});
|
||||
domain.add_property({fesa::core::PropertyId{4}, "bar", fesa::core::MaterialId{3}});
|
||||
domain.add_element({
|
||||
fesa::core::ElementId{5},
|
||||
fesa::model::ElementTopology::truss2,
|
||||
{fesa::core::NodeId{1}, fesa::core::NodeId{2}},
|
||||
fesa::core::PropertyId{4}
|
||||
});
|
||||
fesa::model::AnalysisStep step{fesa::core::StepId{6}, "static"};
|
||||
step.add_boundary_condition({fesa::core::NodeId{1}, fesa::model::DofComponent::ux, 0.0});
|
||||
step.add_load({fesa::core::NodeId{2}, fesa::model::DofComponent::ux, 10.0});
|
||||
domain.add_step(step);
|
||||
return domain;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
const auto domain = make_domain();
|
||||
fesa::analysis::LinearStaticAnalysis analysis{domain, fesa::core::StepId{6}};
|
||||
analysis.run();
|
||||
|
||||
if (analysis.analysis_model() == nullptr || analysis.state() == nullptr) {
|
||||
return 1;
|
||||
}
|
||||
return analysis.state()->displacement().size() == 6 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#include <fesa/analysis/analysis.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class RecordingAnalysis : public fesa::analysis::Analysis {
|
||||
public:
|
||||
const std::vector<std::string>& calls() const
|
||||
{
|
||||
return calls_;
|
||||
}
|
||||
|
||||
protected:
|
||||
void initialize() override { calls_.push_back("initialize"); }
|
||||
void build_analysis_model() override { calls_.push_back("build_analysis_model"); }
|
||||
void build_dof_map() override { calls_.push_back("build_dof_map"); }
|
||||
void build_sparse_pattern() override { calls_.push_back("build_sparse_pattern"); }
|
||||
void assemble() override { calls_.push_back("assemble"); }
|
||||
void apply_boundary_conditions() override { calls_.push_back("apply_boundary_conditions"); }
|
||||
void solve() override { calls_.push_back("solve"); }
|
||||
void update_state() override { calls_.push_back("update_state"); }
|
||||
void write_results() override { calls_.push_back("write_results"); }
|
||||
|
||||
private:
|
||||
std::vector<std::string> calls_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
RecordingAnalysis analysis;
|
||||
analysis.run();
|
||||
const std::vector<std::string> expected{
|
||||
"initialize",
|
||||
"build_analysis_model",
|
||||
"build_dof_map",
|
||||
"build_sparse_pattern",
|
||||
"assemble",
|
||||
"apply_boundary_conditions",
|
||||
"solve",
|
||||
"update_state",
|
||||
"write_results"
|
||||
};
|
||||
return analysis.calls() == expected ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#include <fesa/analysis/analysis_model.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
fesa::model::Domain make_domain()
|
||||
{
|
||||
fesa::model::Domain domain;
|
||||
domain.add_node({fesa::core::NodeId{1}, {0.0, 0.0, 0.0}});
|
||||
domain.add_node({fesa::core::NodeId{2}, {1.0, 0.0, 0.0}});
|
||||
domain.add_material({fesa::core::MaterialId{3}, "steel"});
|
||||
domain.add_property({fesa::core::PropertyId{4}, "bar", fesa::core::MaterialId{3}});
|
||||
domain.add_element({
|
||||
fesa::core::ElementId{5},
|
||||
fesa::model::ElementTopology::truss2,
|
||||
{fesa::core::NodeId{1}, fesa::core::NodeId{2}},
|
||||
fesa::core::PropertyId{4}
|
||||
});
|
||||
|
||||
fesa::model::AnalysisStep step{fesa::core::StepId{6}, "static"};
|
||||
step.add_boundary_condition({fesa::core::NodeId{1}, fesa::model::DofComponent::ux, 0.0});
|
||||
step.add_load({fesa::core::NodeId{2}, fesa::model::DofComponent::ux, 10.0});
|
||||
domain.add_step(step);
|
||||
return domain;
|
||||
}
|
||||
|
||||
int fail()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
const auto domain = make_domain();
|
||||
const fesa::analysis::AnalysisModel model{domain, fesa::core::StepId{6}};
|
||||
|
||||
if (&model.domain() != &domain) {
|
||||
return fail();
|
||||
}
|
||||
if (&model.step() != domain.find_step(fesa::core::StepId{6})) {
|
||||
return fail();
|
||||
}
|
||||
if (model.active_elements().size() != 1 ||
|
||||
model.active_elements()[0] != domain.find_element(fesa::core::ElementId{5})) {
|
||||
return fail();
|
||||
}
|
||||
if (model.active_boundary_conditions().size() != 1 ||
|
||||
model.active_loads().size() != 1) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
const auto* property = model.property_for(*model.active_elements()[0]);
|
||||
if (property == nullptr || property != domain.find_property(fesa::core::PropertyId{4})) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
const auto* material = model.material_for(*property);
|
||||
if (material == nullptr || material != domain.find_material(fesa::core::MaterialId{3})) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
try {
|
||||
const fesa::analysis::AnalysisModel missing{domain, fesa::core::StepId{99}};
|
||||
(void)missing;
|
||||
return fail();
|
||||
} catch (const std::invalid_argument&) {
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <fesa/analysis/analysis_state.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
int fail()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool all_zero(const std::vector<double>& values)
|
||||
{
|
||||
for (const double value : values) {
|
||||
if (value != 0.0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
fesa::analysis::AnalysisState state{3};
|
||||
if (state.displacement().size() != 3 ||
|
||||
state.velocity().size() != 3 ||
|
||||
state.acceleration().size() != 3 ||
|
||||
state.temperature().size() != 3 ||
|
||||
state.external_force().size() != 3 ||
|
||||
state.internal_force().size() != 3 ||
|
||||
state.residual().size() != 3) {
|
||||
return fail();
|
||||
}
|
||||
if (!all_zero(state.displacement()) || !all_zero(state.residual())) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
state.set_displacement({1.0, 2.0, 3.0});
|
||||
if (state.displacement()[2] != 3.0) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
state.set_external_force({10.0, 20.0, 30.0});
|
||||
state.set_internal_force({1.0, 2.0, 3.0});
|
||||
state.update_residual();
|
||||
if (state.residual()[0] != 9.0 ||
|
||||
state.residual()[1] != 18.0 ||
|
||||
state.residual()[2] != 27.0) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
try {
|
||||
state.set_displacement({1.0});
|
||||
return fail();
|
||||
} catch (const std::invalid_argument&) {
|
||||
}
|
||||
|
||||
state.iteration_state().time = 1.25;
|
||||
state.iteration_state().increment = 2;
|
||||
state.iteration_state().iteration = 3;
|
||||
if (state.iteration_state().time != 1.25 ||
|
||||
state.iteration_state().increment != 2 ||
|
||||
state.iteration_state().iteration != 3) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
state.set_element_state(fesa::core::ElementId{7}, {4.0, 5.0});
|
||||
const auto* element_state = state.element_state(fesa::core::ElementId{7});
|
||||
if (element_state == nullptr || element_state->size() != 2 || (*element_state)[1] != 5.0) {
|
||||
return fail();
|
||||
}
|
||||
if (state.element_state(fesa::core::ElementId{8}) != nullptr) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <fesa/core/diagnostic.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
const fesa::core::Diagnostic diagnostic{
|
||||
fesa::core::Severity::info,
|
||||
"core.info",
|
||||
"diagnostic"
|
||||
};
|
||||
return diagnostic.code == "core.info" ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#include <fesa/core/ids.hpp>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
int main()
|
||||
{
|
||||
static_assert(!std::is_same_v<fesa::core::NodeId, fesa::core::ElementId>);
|
||||
return fesa::core::NodeId{7}.value == 7 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#include <fesa/core/diagnostic.hpp>
|
||||
#include <fesa/core/ids.hpp>
|
||||
#include <fesa/core/status.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace {
|
||||
|
||||
int fail()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
static_assert(!std::is_same_v<fesa::core::NodeId, fesa::core::ElementId>);
|
||||
|
||||
const auto ok = fesa::core::Status::ok();
|
||||
if (!ok.is_ok() || !ok.diagnostics().empty()) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
fesa::core::Diagnostic error{
|
||||
fesa::core::Severity::error,
|
||||
"core.error",
|
||||
"core failure"
|
||||
};
|
||||
auto failed = fesa::core::Status::failure(error);
|
||||
if (failed.is_ok() || failed.diagnostics().size() != 1) {
|
||||
return fail();
|
||||
}
|
||||
if (failed.diagnostics()[0].code != "core.error" ||
|
||||
failed.diagnostics()[0].message != "core failure") {
|
||||
return fail();
|
||||
}
|
||||
|
||||
failed.add({fesa::core::Severity::warning, "core.warning", "check warning"});
|
||||
if (failed.diagnostics().size() != 2) {
|
||||
return fail();
|
||||
}
|
||||
if (failed.diagnostics()[0].code != "core.error" ||
|
||||
failed.diagnostics()[1].code != "core.warning") {
|
||||
return fail();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <fesa/core/status.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
const auto status = fesa::core::Status::ok();
|
||||
return status.is_ok() ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include <fesa/fem/dof_key.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
const fesa::fem::DofKey lhs{fesa::core::NodeId{1}, fesa::model::DofComponent::ux};
|
||||
const fesa::fem::DofKey rhs{fesa::core::NodeId{1}, fesa::model::DofComponent::ux};
|
||||
return lhs == rhs ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#include <fesa/fem/dof_manager.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
int fail()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
fesa::fem::DofManager dofs;
|
||||
dofs.define_node_dofs(fesa::core::NodeId{2}, {
|
||||
fesa::model::DofComponent::ux,
|
||||
fesa::model::DofComponent::uy
|
||||
});
|
||||
dofs.define_node_dofs(fesa::core::NodeId{1}, {
|
||||
fesa::model::DofComponent::uy,
|
||||
fesa::model::DofComponent::ux
|
||||
});
|
||||
dofs.apply_boundary_condition({
|
||||
fesa::core::NodeId{1},
|
||||
fesa::model::DofComponent::ux,
|
||||
0.0
|
||||
});
|
||||
dofs.number_equations();
|
||||
|
||||
const fesa::fem::DofKey n1ux{fesa::core::NodeId{1}, fesa::model::DofComponent::ux};
|
||||
const fesa::fem::DofKey n1uy{fesa::core::NodeId{1}, fesa::model::DofComponent::uy};
|
||||
const fesa::fem::DofKey n2ux{fesa::core::NodeId{2}, fesa::model::DofComponent::ux};
|
||||
const fesa::fem::DofKey n2uy{fesa::core::NodeId{2}, fesa::model::DofComponent::uy};
|
||||
|
||||
if (dofs.total_dof_count() != 4 ||
|
||||
dofs.constrained_dof_count() != 1 ||
|
||||
dofs.free_dof_count() != 3) {
|
||||
return fail();
|
||||
}
|
||||
if (dofs.equation_id(n1ux) != 0 ||
|
||||
dofs.equation_id(n1uy) != 1 ||
|
||||
dofs.equation_id(n2ux) != 2 ||
|
||||
dofs.equation_id(n2uy) != 3) {
|
||||
return fail();
|
||||
}
|
||||
if (!dofs.is_constrained(n1ux) ||
|
||||
dofs.free_equation_id(n1ux).has_value()) {
|
||||
return fail();
|
||||
}
|
||||
if (!dofs.free_equation_id(n1uy).has_value() ||
|
||||
*dofs.free_equation_id(n1uy) != 0 ||
|
||||
*dofs.free_equation_id(n2ux) != 1 ||
|
||||
*dofs.free_equation_id(n2uy) != 2) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
const auto full = dofs.expand_free_vector({11.0, 22.0, 33.0});
|
||||
if (full.size() != 4 ||
|
||||
full[0] != 0.0 ||
|
||||
full[1] != 11.0 ||
|
||||
full[2] != 22.0 ||
|
||||
full[3] != 33.0) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
const auto& pattern = dofs.sparse_pattern();
|
||||
if (pattern.size() != 9 ||
|
||||
pattern.front() != std::pair<int, int>{0, 0} ||
|
||||
pattern.back() != std::pair<int, int>{2, 2}) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
try {
|
||||
(void)dofs.equation_id({fesa::core::NodeId{99}, fesa::model::DofComponent::ux});
|
||||
return fail();
|
||||
} catch (const std::invalid_argument&) {
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <string>
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::string project = "fesa";
|
||||
return project.size() == 4 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <fesa/model/analysis_step.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
const fesa::model::AnalysisStep step{fesa::core::StepId{1}, "static"};
|
||||
return step.name() == "static" ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <fesa/model/boundary_condition.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
const fesa::model::BoundaryCondition bc{
|
||||
fesa::core::NodeId{1},
|
||||
fesa::model::DofComponent::ux,
|
||||
0.0
|
||||
};
|
||||
return bc.node_id().value == 1 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#include <fesa/model/domain.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
int fail()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
fesa::model::Domain domain;
|
||||
|
||||
domain.add_node(fesa::model::Node{fesa::core::NodeId{1}, {1.0, 2.0, 3.0}});
|
||||
domain.add_element(fesa::model::Element{
|
||||
fesa::core::ElementId{10},
|
||||
fesa::model::ElementTopology::truss2,
|
||||
{fesa::core::NodeId{1}, fesa::core::NodeId{2}},
|
||||
fesa::core::PropertyId{20}
|
||||
});
|
||||
domain.add_material(fesa::model::Material{fesa::core::MaterialId{30}, "steel"});
|
||||
domain.add_property(fesa::model::Property{
|
||||
fesa::core::PropertyId{20},
|
||||
"bar",
|
||||
fesa::core::MaterialId{30}
|
||||
});
|
||||
|
||||
fesa::model::AnalysisStep step{fesa::core::StepId{40}, "load"};
|
||||
step.add_boundary_condition({fesa::core::NodeId{1}, fesa::model::DofComponent::ux, 0.0});
|
||||
step.add_load({fesa::core::NodeId{2}, fesa::model::DofComponent::ux, 10.0});
|
||||
domain.add_step(step);
|
||||
|
||||
const auto* node = domain.find_node(fesa::core::NodeId{1});
|
||||
if (node == nullptr || node->coordinates()[2] != 3.0) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
const auto* element = domain.find_element(fesa::core::ElementId{10});
|
||||
if (element == nullptr ||
|
||||
element->topology() != fesa::model::ElementTopology::truss2 ||
|
||||
element->node_ids().size() != 2 ||
|
||||
element->property_id().value != 20) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
const auto* material = domain.find_material(fesa::core::MaterialId{30});
|
||||
if (material == nullptr || material->name() != "steel") {
|
||||
return fail();
|
||||
}
|
||||
|
||||
const auto* property = domain.find_property(fesa::core::PropertyId{20});
|
||||
if (property == nullptr ||
|
||||
property->name() != "bar" ||
|
||||
property->material_id().value != 30) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
const auto* analysis_step = domain.find_step(fesa::core::StepId{40});
|
||||
if (analysis_step == nullptr ||
|
||||
analysis_step->boundary_conditions().size() != 1 ||
|
||||
analysis_step->loads().size() != 1) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
if (domain.find_node(fesa::core::NodeId{999}) != nullptr ||
|
||||
domain.find_element(fesa::core::ElementId{999}) != nullptr ||
|
||||
domain.find_material(fesa::core::MaterialId{999}) != nullptr ||
|
||||
domain.find_property(fesa::core::PropertyId{999}) != nullptr ||
|
||||
domain.find_step(fesa::core::StepId{999}) != nullptr) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include <fesa/model/element.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
const fesa::model::Element element{
|
||||
fesa::core::ElementId{1},
|
||||
fesa::model::ElementTopology::bar2,
|
||||
{fesa::core::NodeId{1}, fesa::core::NodeId{2}},
|
||||
fesa::core::PropertyId{3}
|
||||
};
|
||||
return element.node_ids().size() == 2 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <fesa/model/load.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
const fesa::model::Load load{
|
||||
fesa::core::NodeId{2},
|
||||
fesa::model::DofComponent::ux,
|
||||
5.0
|
||||
};
|
||||
return load.value() == 5.0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <fesa/model/material.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
const fesa::model::Material material{fesa::core::MaterialId{1}, "steel"};
|
||||
return material.name() == "steel" ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <fesa/model/node.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
const fesa::model::Node node{fesa::core::NodeId{1}, {0.0, 1.0, 2.0}};
|
||||
return node.coordinates()[1] == 1.0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <fesa/model/property.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
const fesa::model::Property property{
|
||||
fesa::core::PropertyId{2},
|
||||
"section",
|
||||
fesa::core::MaterialId{3}
|
||||
};
|
||||
return property.material_id().value == 3 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#include <fesa/results/results.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
int fail()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
fesa::results::ResultStep step{"static"};
|
||||
auto& frame = step.add_frame(1, 0.0);
|
||||
if (step.name() != "static" ||
|
||||
step.frames().size() != 1 ||
|
||||
frame.frame_id() != 1 ||
|
||||
frame.time() != 0.0) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
frame.add_field_output({
|
||||
"U",
|
||||
fesa::results::FieldLocation::nodal,
|
||||
{"ux", "uy"},
|
||||
{1, 2},
|
||||
{0.0, 0.1, 1.0, 1.1}
|
||||
});
|
||||
if (frame.field_outputs().size() != 1 ||
|
||||
frame.field_outputs()[0].entity_ids[1] != 2 ||
|
||||
frame.field_outputs()[0].values[3] != 1.1) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
frame.add_history_output({"load-factor", {0.0, 1.0}, {0.0, 10.0}});
|
||||
if (frame.history_outputs().size() != 1 ||
|
||||
frame.history_outputs()[0].values[1] != 10.0) {
|
||||
return fail();
|
||||
}
|
||||
|
||||
try {
|
||||
frame.add_field_output({
|
||||
"bad",
|
||||
fesa::results::FieldLocation::nodal,
|
||||
{},
|
||||
{1},
|
||||
{0.0}
|
||||
});
|
||||
return fail();
|
||||
} catch (const std::invalid_argument&) {
|
||||
}
|
||||
|
||||
try {
|
||||
frame.add_field_output({
|
||||
"bad-shape",
|
||||
fesa::results::FieldLocation::nodal,
|
||||
{"ux", "uy"},
|
||||
{1},
|
||||
{0.0}
|
||||
});
|
||||
return fail();
|
||||
} catch (const std::invalid_argument&) {
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user