48 KiB
FESA Phase 1 Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Build and internally qualify a C++20/MSVC linear-static finite-element solver that reads the agreed Abaqus .inp subset, solves 2-node 3D Timoshenko Beam models, and writes self-contained HDF5 results.
Architecture: Implement one end-to-end vertical slice first, then complete the input, numerical, parallel, result, and reference-verification contracts behind the module boundaries in docs/ARCHITECTURE.md. Keep the semantic model independent of Abaqus syntax and isolate oneMKL, oneTBB, and HDF5 behind adapters.
Tech Stack: C++20, Visual Studio 2022 MSVC v143 x64, CMake/CMake Presets/CTest, GoogleTest/GoogleMock, Intel oneAPI MKL PARDISO, Intel oneAPI TBB, HDF5 C API, Python 3 Harness.
Global Constraints
- Only the Phase 1 scope in
docs/PRD.mdmay be implemented. - Public headers live under
include/fesa/; implementations live undersrc/fesa/; tests live undertests/. - Write a failing test before every production behavior change.
- Do not add a fake stiffness matrix or a test-only solver path.
- Do not introduce empty future modules, a generic registry, MPC, iterative solvers, nonlinear state, or additional element types.
- FESA performs no unit conversion.
- Abaqus inputs may use a flat mesh or one untransformed Part/Assembly/Instance.
- When transverse shear stiffness is omitted, use (A_{sy}=A_{sz}=5A/6) and
SCF=0. - Reference comparison requests name their quantities and CSV paths; no per-model metadata file is required.
- A pipeline milestone is not a numerically qualified release.
- No new MSVC warnings are allowed.
- The existing Harness contract in
docs/HARNESS.mdand.agents/skills/harness/SKILL.mdremains unchanged.
Environment Audit
The planning environment currently has:
- CMake 4.4.0
- oneMKL CMake package at
C:/Program Files (x86)/Intel/oneAPI/2026.1/lib/cmake/mkl - oneTBB CMake package at
C:/Program Files (x86)/Intel/oneAPI/2026.1/lib/cmake/tbb - no HDF5 or GoogleTest CMake package found in the standard Program Files trees
MSBuild.exenot currently available onPATH
Task 1 must stop as blocked rather than downloading packages if HDF5, GoogleTest, or the MSVC toolchain is still unavailable.
Required Research Record
Before the related production task begins, record the relevant equations, assumptions, API contracts, and FESA decisions from these sources:
- K. J. Bathe, Finite Element Procedures, 2nd edition: finite-element discretization, assembly, constraints, and verification.
- T. J. R. Hughes, The Finite Element Method: Linear Static and Dynamic Finite Element Analysis: variational formulation and numerical integration.
- K. J. Bathe and S. Bolourchi, “Large Displacement Analysis of Three-Dimensional Beam Structures,” 1979: three-dimensional isoparametric Beam coordinates and transformations. Phase 1 uses only the linearized subset.
- T. J. R. Hughes, R. L. Taylor, and W. Kanoknukulchai, “A Simple and Efficient Finite Element for Plate Bending,” 1977: selective reduced integration rationale. Do not copy its plate kinematics into the Beam formulation.
- Abaqus 2024—Choosing a Beam Element: B31 shear-flexible behavior and slenderness compensation.
- Abaqus 2024—BEAM GENERAL SECTION: supported general-section data and orientation.
- Intel oneMKL PARDISO reference: matrix type, CSR indexing, phases, checks, and error codes.
- oneTBB reduction guide: deterministic floating-point reduction.
- HDF5 data model: groups, datasets, dataspaces, and attributes.
- CMake FindHDF5: installed C-library discovery and imported targets.
docs/formulation/timoshenko-beam-3d.md, docs/HDF5_SCHEMA.md, and
docs/VALIDATION.md must cite the applicable source and state where FESA
intentionally differs.
Harness Phase Map
| Order | Harness phase | Plan tasks | Independent deliverable |
|---|---|---|---|
| 0 | solver-bootstrap |
1-2 | Reproducible C++20 build, dependency smoke tests, core IDs and diagnostics |
| 1 | domain-and-input-skeleton |
3-4 | Flat or single-Instance B31 input becomes an immutable normalized Domain |
| 2 | fem-and-beam-kernel |
5-6 | Real Timoshenko Beam local stiffness with analytical sanity tests |
| 3 | equation-and-linear-solve |
7-8 | Deterministic serial CSR system solved by PARDISO |
| 4 | results-and-pipeline |
9-10 | CLI runs one deck end-to-end and writes readable HDF5 |
| 5 | abaqus-subset-completion |
11 | Full agreed scoped keyword, set, material, section, load, and BC subset |
| 6 | deterministic-parallel-assembly |
12 | oneTBB assembly matches serial output across thread counts |
| 7 | result-contract-completion |
13 | Complete self-contained HDF5 schema and Beam result recovery |
| 8 | beam-reference-qualification |
14 | Analytical suite and available Abaqus displacement/reaction data pass tolerance |
| 9 | internal-release |
15 | Debug/Release validation, 100k-DOF benchmark, install tree, reports |
Create phases/index.json, phase indexes, and step files only after this plan and its phase split are approved.
Planned File Map
CMakeLists.txt root targets and project policies
CMakePresets.json windows-debug/windows-release workflows
cmake/FesaDependencies.cmake installed dependency discovery
.harness/config.json Harness CMake preset selection
include/fesa/core/ IDs, vectors, source locations, diagnostics
include/fesa/model/ immutable semantic entities and Domain
include/fesa/io/abaqus/ deck records, parser, semantic mapper
include/fesa/fem/ quadrature, shape functions, frames, DOFs
include/fesa/elements/beam/ Beam3D2 input, contribution, recovery contract
include/fesa/assembly/ symmetric COO/CSR and assembly
include/fesa/constraints/ essential-BC elimination and reconstruction
include/fesa/solvers/linear/ backend contract and PARDISO adapter
include/fesa/results/ step/frame/field/diagnostic result model
include/fesa/io/hdf5/ schema constants, writer, reader
include/fesa/analysis/ analysis lifecycle and linear-static procedure
include/fesa/validation/ comparison metrics and CSV mapping
src/fesa/ implementations mirroring public modules
src/fesa/cli/main.cpp thin `fesa` command-line executable
tests/unit/ single-module behavior
tests/integration/ public input-to-output path
tests/reference/ FESA HDF5 to golden CSV comparisons
tests/fixtures/ small invalid and valid decks
reference/cantilever beam/ supplied Abaqus input and available golden CSV data
docs/formulation/ signed-off equations and conventions
docs/HDF5_SCHEMA.md versioned output contract
docs/VALIDATION.md benchmark matrix and qualification result
Task 1: CMake, Installed Dependencies, and Test Bootstrap
Files:
- Create:
CMakeLists.txt - Create:
CMakePresets.json - Create:
cmake/FesaDependencies.cmake - Create:
.harness/config.json - Create:
include/fesa/core/version.hpp - Create:
src/fesa/core/version.cpp - Create:
src/fesa/cli/main.cpp - Create:
tests/CMakeLists.txt - Create:
tests/unit/core/version_test.cpp - Modify:
.gitignore
Interfaces:
-
Produces:
std::string_view fesa::version() noexcept -
Produces CMake targets:
fesa_core,fesa_cli,fesa_unit_tests -
Produces presets:
windows-debug,windows-release -
Later tasks consume the common warning and include-directory policies.
-
Step 1: Verify required installed packages without changing the machine
Run:
cmake --version
Get-Command MSBuild.exe
Get-ChildItem "C:\Program Files (x86)\Intel\oneAPI" -Recurse -Filter MKLConfig.cmake
Get-ChildItem "C:\Program Files (x86)\Intel\oneAPI" -Recurse -Filter TBBConfig.cmake
Get-ChildItem "C:\Program Files" -Recurse -Filter hdf5-config.cmake
Get-ChildItem "C:\Program Files" -Recurse -Filter GTestConfig.cmake
Expected: MSVC, oneMKL, oneTBB, HDF5, and GoogleTest are all discoverable. If any are missing, mark the Harness step blocked and name the missing package; do not download it.
- Step 2: Write the failing version test
#include <gtest/gtest.h>
#include <fesa/core/version.hpp>
TEST(Version, ReportsPhaseOneSemanticVersion) {
EXPECT_EQ(fesa::version(), "0.1.0");
}
- Step 3: Add configure files and verify the test fails before implementation
cmake/FesaDependencies.cmake must set the oneMKL choices before package discovery:
cmake_minimum_required(VERSION 3.30)
set(MKL_LINK dynamic)
set(MKL_THREADING tbb_thread)
set(MKL_INTERFACE lp64)
find_package(MKL CONFIG REQUIRED)
find_package(TBB CONFIG REQUIRED COMPONENTS tbb)
find_package(HDF5 REQUIRED COMPONENTS C)
find_package(GTest CONFIG REQUIRED)
Link MKL::MKL, TBB::tbb, HDF5::HDF5, and GTest::gtest_main only to targets that use them. Configure and build:
Compile FESA targets with /W4 /permissive- /EHsc; do not apply FESA warning
flags to imported targets.
cmake --preset windows-debug
cmake --build --preset windows-debug
Expected: build fails because fesa::version() has no definition.
- Step 4: Implement the minimum version API and thin CLI
namespace fesa {
std::string_view version() noexcept;
}
The CLI accepts only --version in this task. Any other command prints usage and returns a nonzero exit code.
- Step 5: Run focused and full bootstrap validation
cmake --build --preset windows-debug
ctest --preset windows-debug --output-on-failure
.\out\build\windows-debug\Debug\fesa.exe --version
Expected: one test passes and the CLI prints 0.1.0.
- Step 6: Commit
git add CMakeLists.txt CMakePresets.json cmake .harness/config.json include/fesa/core/version.hpp src/fesa/core/version.cpp src/fesa/cli/main.cpp tests/CMakeLists.txt tests/unit/core/version_test.cpp .gitignore
git commit -m "build: bootstrap FESA CMake project"
Task 2: Core IDs, Vectors, Source Locations, and Diagnostics
Files:
- Create:
include/fesa/core/entity_id.hpp - Create:
include/fesa/core/vec3.hpp - Create:
include/fesa/core/source_location.hpp - Create:
include/fesa/core/diagnostic.hpp - Create:
tests/unit/core/entity_id_test.cpp - Create:
tests/unit/core/vec3_test.cpp - Create:
tests/unit/core/diagnostic_test.cpp - Modify:
CMakeLists.txt - Modify:
tests/CMakeLists.txt
Interfaces:
template<class Tag>
class EntityId final {
public:
explicit constexpr EntityId(std::int64_t value);
[[nodiscard]] constexpr std::int64_t value() const noexcept;
auto operator<=>(const EntityId&) const = default;
};
struct Vec3 final {
double x;
double y;
double z;
};
[[nodiscard]] bool is_finite(Vec3 value) noexcept;
struct SourceLocation final {
std::filesystem::path file;
std::size_t line;
std::size_t column;
};
enum class DiagnosticStage {
io, lexical, syntax, semantic, model, equation, solver, results
};
struct Diagnostic final {
DiagnosticStage stage;
std::string code;
std::string message;
std::optional<SourceLocation> source;
std::optional<std::int64_t> entity_id;
};
- Step 1: Write failing tests
Cover negative/zero entity-ID rejection, typed-ID non-interchangeability at compile time, finite Vec3 validation, and full diagnostic context preservation.
- Step 2: Run focused tests and confirm compile or assertion failure
cmake --build --preset windows-debug
ctest --preset windows-debug -R "Core" --output-on-failure
Expected: failure because the core types do not exist.
- Step 3: Implement only the declared core types
Use double for all Phase 1 real values and std::int64_t for external Abaqus IDs. Do not introduce a unit library, generic error monad, matrix class, or logging framework.
- Step 4: Run all tests
cmake --build --preset windows-debug
ctest --preset windows-debug --output-on-failure
Expected: all tests pass with no new MSVC warnings.
- Step 5: Commit
git add include/fesa/core tests/unit/core CMakeLists.txt tests/CMakeLists.txt
git commit -m "feat(core): add typed IDs and diagnostics"
Task 3: Immutable Semantic Domain
Files:
- Create:
include/fesa/model/ids.hpp - Create:
include/fesa/model/entity_origin.hpp - Create:
include/fesa/model/node.hpp - Create:
include/fesa/model/material.hpp - Create:
include/fesa/model/beam_section.hpp - Create:
include/fesa/model/beam_element.hpp - Create:
include/fesa/model/entity_set.hpp - Create:
include/fesa/model/step_definition.hpp - Create:
include/fesa/model/domain.hpp - Create:
include/fesa/model/domain_builder.hpp - Create:
src/fesa/model/domain.cpp - Create:
src/fesa/model/domain_builder.cpp - Create:
tests/unit/model/domain_builder_test.cpp - Modify: root and test CMake files
Interfaces:
using NodeId = EntityId<struct NodeTag>;
using ElementId = EntityId<struct ElementTag>;
using MaterialId = EntityId<struct MaterialTag>;
using SectionId = EntityId<struct SectionTag>;
struct EntityOrigin final {
std::string part_name;
std::string instance_name;
std::int64_t local_label;
};
struct Node final { NodeId id; EntityOrigin origin; Vec3 position; };
struct IsotropicElastic final { MaterialId id; std::string name; double young; double poisson; };
struct BeamSection final {
SectionId id;
std::string name;
double area;
double iy;
double iz;
double torsion_j;
double shear_area_y;
double shear_area_z;
Vec3 orientation;
std::vector<std::array<double, 2>> recovery_points;
};
struct BeamElement final {
ElementId id;
EntityOrigin origin;
std::array<NodeId, 2> nodes;
MaterialId material;
SectionId section;
};
struct NodeSet final { std::string name; std::vector<NodeId> members; };
struct ElementSet final { std::string name; std::vector<ElementId> members; };
struct PrescribedDof final { NodeId node; std::uint8_t dof; double value; };
struct NodalLoad final { NodeId node; std::array<double, 6> values; };
struct StepDefinition final {
std::string name;
std::vector<PrescribedDof> prescribed_dofs;
std::vector<NodalLoad> nodal_loads;
};
class Domain final {
public:
[[nodiscard]] std::span<const Node> nodes() const noexcept;
[[nodiscard]] std::span<const BeamElement> beam_elements() const noexcept;
[[nodiscard]] const Node& node(NodeId id) const;
[[nodiscard]] const Node& node(const EntityOrigin& origin) const;
};
struct DomainBuildResult final {
std::optional<Domain> domain;
std::vector<Diagnostic> diagnostics;
};
class DomainBuilder final {
public:
void add_node(Node value);
void add_material(IsotropicElastic value);
void add_section(BeamSection value);
void add_beam_element(BeamElement value);
void add_node_set(NodeSet value);
void add_element_set(ElementSet value);
void set_step(StepDefinition value);
[[nodiscard]] DomainBuildResult build() &&;
};
DomainBuilder::build() returns either one immutable Domain or a nonempty
diagnostic list. It must resolve every reference and reject duplicate internal
IDs, duplicate (instance_name, local_label) origins, nonfinite values, invalid
material constants, invalid section properties, zero-length elements, missing
assignments, and invalid orientation vectors. Empty Part/Instance names denote
the flat global scope.
- Step 1: Write failing builder tests
Write one valid two-node domain test and one test for every rejection listed above.
- Step 2: Run the model tests and confirm failure
cmake --build --preset windows-debug
ctest --preset windows-debug -R "Domain" --output-on-failure
- Step 3: Implement the minimum immutable storage and validation
Preserve external IDs and create private dense lookup maps. Do not store equation numbers in Node or BeamElement.
- Step 4: Run all tests and commit
cmake --build --preset windows-debug
ctest --preset windows-debug --output-on-failure
git add include/fesa/model src/fesa/model tests/unit/model CMakeLists.txt tests/CMakeLists.txt
git commit -m "feat(model): add immutable beam domain"
Task 4: Minimal Scoped Abaqus Deck to Domain
Files:
- Create:
include/fesa/io/abaqus/deck_record.hpp - Create:
include/fesa/io/abaqus/parser.hpp - Create:
include/fesa/io/abaqus/semantic_mapper.hpp - Create:
src/fesa/io/abaqus/parser.cpp - Create:
src/fesa/io/abaqus/semantic_mapper.cpp - Create:
tests/fixtures/abaqus/minimal_cantilever.inp - Create:
tests/fixtures/abaqus/minimal_part_instance_cantilever.inp - Create:
tests/fixtures/abaqus/unsupported_keyword.inp - Create:
tests/unit/io/abaqus/parser_test.cpp - Create:
tests/integration/io/minimal_deck_to_domain_test.cpp - Modify: CMake files
Interfaces:
struct DeckRecord final {
std::string keyword;
std::map<std::string, std::string, std::less<>> parameters;
std::vector<std::vector<std::string>> data;
SourceLocation source;
};
struct ParsedPart final {
std::string name;
std::vector<DeckRecord> records;
SourceLocation source;
};
struct ParsedInstance final {
std::string name;
std::string part_name;
std::vector<std::vector<std::string>> transform_data;
SourceLocation source;
};
struct ParsedAssembly final {
std::string name;
std::vector<ParsedInstance> instances;
std::vector<DeckRecord> records;
SourceLocation source;
};
struct ParsedDeck final {
std::vector<DeckRecord> global_records;
std::vector<ParsedPart> parts;
std::optional<ParsedAssembly> assembly;
};
struct ParseDeckResult final {
std::optional<ParsedDeck> deck;
std::vector<Diagnostic> diagnostics;
};
[[nodiscard]] ParseDeckResult parse_deck(const std::filesystem::path& path);
[[nodiscard]] DomainBuildResult map_deck_to_domain(const ParsedDeck& deck);
Each minimal fixture contains two nodes, one B31 element, one material, one general section, one node set, one element set, one boundary definition, one concentrated load, and one static step. One fixture is flat; the other contains one Part, one Assembly, and one untransformed Instance.
- Step 1: Write failing parser and integration tests
Tests must call parse_deck() and map_deck_to_domain(); they may not
construct the Domain directly. Assert that both organizations produce
equivalent active analysis entities and that the hierarchical Domain retains
Part/Instance provenance.
- Step 2: Confirm failure
cmake --build --preset windows-debug
ctest --preset windows-debug -R "Abaqus|Deck" --output-on-failure
- Step 3: Implement the smallest case-insensitive keyword parser
Support comments, blank lines, comma-separated parameters and data, UTF-8
input, exact source lines, *PART/*END PART, *ASSEMBLY/*END ASSEMBLY, and
*INSTANCE/*END INSTANCE. In this task, accept only the keywords used by the
two minimal fixtures. Treat *INCLUDE and every other keyword as an explicit
unsupported-keyword error.
- Step 4: Implement semantic mapping for the fixture
For hierarchical input, require exactly one Assembly and one Instance, reject
nonempty Instance transform data, activate only the referenced Part, and map
the result to the same flat Domain used by orphan meshes. When transverse
stiffness is omitted, set (A_{sy}=A_{sz}=5A/6) and SCF=0; supported
explicit values override the default.
- Step 5: Validate and commit
cmake --build --preset windows-debug
ctest --preset windows-debug --output-on-failure
git add include/fesa/io src/fesa/io tests/fixtures tests/unit/io tests/integration/io CMakeLists.txt tests/CMakeLists.txt
git commit -m "feat(input): parse minimal Abaqus B31 deck"
Task 5: FEM Primitives and DOF Management
Files:
- Create:
include/fesa/fem/gauss_rule.hpp - Create:
include/fesa/fem/line2_shape.hpp - Create:
include/fesa/fem/local_frame.hpp - Create:
include/fesa/fem/dof_manager.hpp - Create:
src/fesa/fem/local_frame.cpp - Create:
src/fesa/fem/dof_manager.cpp - Create:
tests/unit/fem/gauss_rule_test.cpp - Create:
tests/unit/fem/line2_shape_test.cpp - Create:
tests/unit/fem/local_frame_test.cpp - Create:
tests/unit/fem/dof_manager_test.cpp
Interfaces:
struct GaussPoint1D final { double xi; double weight; };
[[nodiscard]] std::array<GaussPoint1D, 1> gauss_rule_1();
[[nodiscard]] std::array<GaussPoint1D, 2> gauss_rule_2();
[[nodiscard]] std::array<double, 2> line2_shape(double xi);
[[nodiscard]] std::array<double, 2> line2_shape_derivative();
struct LocalFrame final { Vec3 ex; Vec3 ey; Vec3 ez; double length; };
[[nodiscard]] LocalFrame make_beam_frame(Vec3 first, Vec3 second, Vec3 orientation);
class DofManager final {
public:
explicit DofManager(const Domain& domain);
[[nodiscard]] std::size_t full_dof_count() const noexcept;
[[nodiscard]] std::array<std::size_t, 12> beam_dofs(ElementId id) const;
};
- Step 1: Write failing mathematical invariant tests
Test Gauss exactness through degree 3, shape-function partition of unity, derivative sum zero, right-handed orthonormal frames, nearly parallel orientation rejection, stable external-ID ordering, and 12-DOF element maps.
- Step 2: Confirm failure, implement, and rerun
cmake --build --preset windows-debug
ctest --preset windows-debug -R "Fem|Dof|Frame" --output-on-failure
- Step 3: Run full validation and commit
ctest --preset windows-debug --output-on-failure
git add include/fesa/fem src/fesa/fem tests/unit/fem CMakeLists.txt tests/CMakeLists.txt
git commit -m "feat(fem): add quadrature frames and DOF mapping"
Task 6: Minimal Real Timoshenko Beam Kernel
Files:
- Create:
docs/formulation/timoshenko-beam-3d.md - Create:
include/fesa/elements/beam/beam3d2.hpp - Create:
src/fesa/elements/beam/beam3d2.cpp - Create:
tests/unit/elements/beam3d2_test.cpp - Modify: CMake files
Interfaces:
struct Beam3D2Input final {
std::array<Vec3, 2> coordinates;
IsotropicElastic material;
BeamSection section;
};
struct Beam3D2Contribution final {
std::array<double, 144> stiffness;
std::array<double, 12> equivalent_load;
LocalFrame frame;
};
[[nodiscard]] Beam3D2Contribution evaluate_beam3d2(const Beam3D2Input& input);
- Step 1: Write and review the formulation document before production code
The document must define DOF order, local axes, strain measures, constitutive diagonal, Jacobian, transformation, Gauss rules, matrix storage order, force/moment signs, and reference sources. It must show that axial/bending/torsion use two points and shear uses one point.
- Step 2: Write failing kernel tests
Test symmetry, six rigid-body modes, positive strain energy for non-rigid modes, analytical axial stiffness (EA/L), analytical torsional stiffness (GJ/L), coordinate-rotation invariance, and finite values.
- Step 3: Confirm the tests fail
cmake --build --preset windows-debug
ctest --preset windows-debug -R "Beam3D2" --output-on-failure
- Step 4: Implement the minimum kernel from the signed-off equations
Use fixed-size std::array<double, 144> storage and small explicit loops. Do not add a dynamic matrix abstraction or copy Abaqus slenderness compensation.
- Step 5: Validate and commit
cmake --build --preset windows-debug
ctest --preset windows-debug --output-on-failure
git add docs/formulation include/fesa/elements src/fesa/elements tests/unit/elements CMakeLists.txt tests/CMakeLists.txt
git commit -m "feat(elements): add 3D Timoshenko beam kernel"
Task 7: Deterministic Serial Assembly and Essential BC Elimination
Files:
- Create:
include/fesa/assembly/symmetric_coo.hpp - Create:
include/fesa/assembly/symmetric_csr.hpp - Create:
include/fesa/assembly/assembler.hpp - Create:
src/fesa/assembly/assembler.cpp - Create:
include/fesa/constraints/essential_bc.hpp - Create:
src/fesa/constraints/essential_bc.cpp - Create:
tests/unit/assembly/assembler_test.cpp - Create:
tests/unit/constraints/essential_bc_test.cpp
Interfaces:
struct CooEntry final {
std::size_t row;
std::size_t column;
ElementId source_element;
std::size_t local_order;
double value;
};
struct SymmetricCsr final {
std::vector<std::int32_t> row_offsets;
std::vector<std::int32_t> column_indices;
std::vector<double> values;
};
struct EquationSystem final {
SymmetricCsr stiffness;
std::vector<double> load;
};
[[nodiscard]] EquationSystem assemble_serial(
const Domain& domain,
const DofManager& dofs);
struct ReducedSystem final {
SymmetricCsr stiffness;
std::vector<double> rhs;
std::vector<std::size_t> free_dofs;
std::vector<double> prescribed_full_values;
};
- Step 1: Write failing assembly tests
Cover a one-element matrix, a two-element shared-node chain, stable sort/reduction order, upper-triangle storage with every diagonal present, nonzero prescribed values, reduced RHS correction, and full-vector reconstruction.
- Step 2: Confirm failure and implement serial baseline
Sort COO entries by (row, column, source_element, local_order) and then sum. This serial result is the oracle for Task 12.
- Step 3: Verify and commit
cmake --build --preset windows-debug
ctest --preset windows-debug -R "Assembly|EssentialBc" --output-on-failure
ctest --preset windows-debug --output-on-failure
git add include/fesa/assembly src/fesa/assembly include/fesa/constraints src/fesa/constraints tests/unit/assembly tests/unit/constraints CMakeLists.txt tests/CMakeLists.txt
git commit -m "feat(assembly): assemble and constrain beam systems"
Task 8: MKL PARDISO Linear Solver Adapter
Files:
- Create:
include/fesa/solvers/linear/linear_solver.hpp - Create:
include/fesa/solvers/linear/pardiso_solver.hpp - Create:
src/fesa/solvers/linear/pardiso_solver.cpp - Create:
tests/unit/solvers/pardiso_solver_test.cpp - Modify:
cmake/FesaDependencies.cmake - Modify: CMake files
Interfaces:
struct LinearSolveResult final {
std::vector<double> solution;
double relative_residual;
std::vector<Diagnostic> diagnostics;
};
class LinearSolver {
public:
virtual ~LinearSolver() = default;
[[nodiscard]] virtual LinearSolveResult solve(
const SymmetricCsr& matrix,
std::span<const double> rhs) = 0;
};
class PardisoLinearSolver final : public LinearSolver {
public:
PardisoLinearSolver();
~PardisoLinearSolver() override;
PardisoLinearSolver(const PardisoLinearSolver&) = delete;
PardisoLinearSolver& operator=(const PardisoLinearSolver&) = delete;
[[nodiscard]] LinearSolveResult solve(
const SymmetricCsr& matrix,
std::span<const double> rhs) override;
};
- Step 1: Write failing adapter tests
Use a hand-calculated 3x3 SPD system, multiple RHS calls on one adapter, invalid CSR, dimension mismatch, and a singular matrix. Assert mtype=2, LP64-compatible index checks, 0-based indexing, and residual reporting through observable behavior.
- Step 2: Confirm failure
cmake --build --preset windows-debug
ctest --preset windows-debug -R "Pardiso" --output-on-failure
- Step 3: Implement RAII PARDISO phases
Set iparm[34]=1 for zero-based indexing and enable the matrix checker. Run symbolic analysis, numerical factorization, solve, and release. Convert every MKL error to DiagnosticStage::solver.
- Step 4: Validate and commit
cmake --build --preset windows-debug
ctest --preset windows-debug --output-on-failure
git add include/fesa/solvers src/fesa/solvers tests/unit/solvers cmake/FesaDependencies.cmake CMakeLists.txt tests/CMakeLists.txt
git commit -m "feat(solver): add PARDISO linear backend"
Task 9: Result Model and Minimal HDF5 Round Trip
Files:
- Create:
docs/HDF5_SCHEMA.md - Create:
include/fesa/results/result_database.hpp - Create:
include/fesa/io/hdf5/schema.hpp - Create:
include/fesa/io/hdf5/writer.hpp - Create:
include/fesa/io/hdf5/reader.hpp - Create:
src/fesa/io/hdf5/writer.cpp - Create:
src/fesa/io/hdf5/reader.cpp - Create:
tests/unit/io/hdf5_round_trip_test.cpp - Modify: CMake files
Interfaces:
struct NodalFrame final {
std::vector<NodeId> node_ids;
std::vector<std::array<double, 6>> displacement;
std::vector<std::array<double, 6>> reaction;
};
struct ResultFrame final {
double step_time;
NodalFrame nodal;
std::vector<Diagnostic> diagnostics;
};
struct ResultStep final {
std::string name;
std::vector<ResultFrame> frames;
};
struct ResultDatabase final {
std::string schema_version;
std::vector<ResultStep> steps;
};
struct Hdf5ReadResult final {
std::optional<ResultDatabase> database;
std::vector<Diagnostic> diagnostics;
};
[[nodiscard]] std::vector<Diagnostic> write_hdf5(
const std::filesystem::path&,
const Domain&,
const ResultDatabase&);
[[nodiscard]] Hdf5ReadResult read_hdf5_results(const std::filesystem::path&);
- Step 1: Write schema version
1.0.0before writer code
Define exact group paths, dataset ranks, scalar types, dense ID mappings, Part/Instance/local-label origins, coordinate-system attributes, applied transverse-shear values and their input/default source, required HDF5 metadata, and compatibility rules.
- Step 2: Write a failing round-trip test
Use a two-node hierarchical-origin Domain and one result frame. Reopen
through the FESA reader and compare every stored value, origin mapping,
transverse-shear source, and schema attribute.
- Step 3: Confirm failure and implement minimum C-API RAII wrappers
Do not use global HDF5 handles. Convert every failing HDF5 call into a results-stage diagnostic or exception caught at the adapter boundary.
- Step 4: Validate with tests and HDF5 tools
cmake --build --preset windows-debug
ctest --preset windows-debug -R "Hdf5" --output-on-failure
h5ls -r .\out\build\windows-debug\Testing\Temporary\fesa-round-trip.h5
ctest --preset windows-debug --output-on-failure
- Step 5: Commit
git add docs/HDF5_SCHEMA.md include/fesa/results include/fesa/io/hdf5 src/fesa/io/hdf5 tests/unit/io CMakeLists.txt tests/CMakeLists.txt
git commit -m "feat(results): add versioned HDF5 result adapter"
Task 10: Linear Static Analysis and End-to-End CLI Slice
Files:
- Create:
include/fesa/analysis/analysis.hpp - Create:
include/fesa/analysis/linear_static_analysis.hpp - Create:
src/fesa/analysis/analysis.cpp - Create:
src/fesa/analysis/linear_static_analysis.cpp - Create:
include/fesa/analysis/run_solver.hpp - Create:
src/fesa/analysis/run_solver.cpp - Modify:
src/fesa/cli/main.cpp - Create:
tests/integration/pipeline/minimal_cantilever_test.cpp - Modify: CMake files
Interfaces:
struct AnalysisRequest final {
std::filesystem::path input_path;
std::filesystem::path output_path;
};
struct AnalysisRunResult final {
bool succeeded;
std::vector<Diagnostic> diagnostics;
};
[[nodiscard]] AnalysisRunResult run_solver(const AnalysisRequest& request);
The CLI contract is:
fesa solve <model.inp> --output <results.h5>
fesa --version
- Step 1: Write the failing end-to-end test
Invoke only run_solver() or the CLI with tests/fixtures/abaqus/minimal_cantilever.inp, then use the public HDF5 reader to assert node IDs, finite displacement, equilibrium residual, and result paths.
- Step 2: Confirm the pipeline test fails
cmake --build --preset windows-debug
ctest --preset windows-debug -R "MinimalCantileverPipeline" --output-on-failure
- Step 3: Implement the analysis lifecycle
Connect parser, Domain, DofManager, Beam kernel, serial assembly, essential BC, PARDISO, full-vector reconstruction, reaction recovery, result model, and HDF5 writer. Keep CLI parsing out of fesa_core.
- Step 4: Validate the vertical slice
cmake --build --preset windows-debug
ctest --preset windows-debug --output-on-failure
.\out\build\windows-debug\Debug\fesa.exe solve tests\fixtures\abaqus\minimal_cantilever.inp --output out\minimal-cantilever.h5
h5ls -r out\minimal-cantilever.h5
Expected: full pipeline succeeds. Do not label the Beam numerically qualified yet.
- Step 5: Commit
git add include/fesa/analysis src/fesa/analysis src/fesa/cli/main.cpp tests/integration/pipeline CMakeLists.txt tests/CMakeLists.txt
git commit -m "feat(analysis): connect linear static pipeline"
Task 11: Complete the Agreed Abaqus Input Subset
Files:
- Modify: Abaqus parser and mapper files from Task 4
- Create:
tests/unit/io/abaqus/set_resolution_test.cpp - Create:
tests/unit/io/abaqus/scope_resolution_test.cpp - Create:
tests/unit/io/abaqus/semantic_validation_test.cpp - Create:
tests/integration/io/multiple_properties_test.cpp - Create:
tests/integration/io/supplied_cantilever_to_domain_test.cpp - Create fixtures under:
tests/fixtures/abaqus/valid/ - Create fixtures under:
tests/fixtures/abaqus/invalid/ - Create:
docs/ABAQUS_INPUT_SUBSET.md
Interfaces:
-
Existing parser and mapper signatures remain unchanged.
-
docs/ABAQUS_INPUT_SUBSET.mdbecomes the normative keyword/parameter/data-line contract. -
Step 1: Write the contract document and failing fixture matrix
Cover *NODE, B31 *ELEMENT, *PART/*END PART,
*ASSEMBLY/*END ASSEMBLY, *INSTANCE/*END INSTANCE, *NSET, *ELSET,
explicit members, GENERATE, nested set references, INSTANCE=,
*MATERIAL, *ELASTIC, general Beam section, optional transverse shear
stiffness, *BOUNDARY, *CLOAD, and one static step. Document
*HEADING, *PREPRINT, *RESTART, and *OUTPUT as recognized no-op
directives.
- Step 2: Add invalid tests before parser changes
Cover duplicate IDs and origins, missing references, set cycles, invalid
ranges, multiple section assignments, missing material, conflicting prescribed
values, unsupported options, *INCLUDE, multiple Assemblies, multiple
Instances, Instance translation/rotation data, instance-local mesh changes,
mixed flat/hierarchical meshes, nonzero SCF, multiple steps, and source-line
accuracy.
- Step 3: Run and confirm the new tests fail
cmake --build --preset windows-debug
ctest --preset windows-debug -R "Abaqus|SetResolution|MultipleProperties" --output-on-failure
- Step 4: Implement only the documented subset
Resolve Part and Assembly scopes before normalizing only the active Part and
Instance. Resolve nested sets with explicit cycle detection and canonical
sorted-unique membership. Recognized no-op directives must be consumed
deliberately; do not add a general ignore-unknown path. Verify the supplied
reference/cantilever beam/cantilever beam.inp maps to the expected 11 nodes,
10 elements, one active material/section, six fixed DOFs, and one nodal load.
- Step 5: Validate and commit
cmake --build --preset windows-debug
ctest --preset windows-debug --output-on-failure
git add docs/ABAQUS_INPUT_SUBSET.md include/fesa/io/abaqus src/fesa/io/abaqus tests/fixtures/abaqus tests/unit/io/abaqus tests/integration/io
git commit -m "feat(input): complete Phase 1 Abaqus subset"
Task 12: Deterministic oneTBB Assembly
Files:
- Modify:
include/fesa/assembly/assembler.hpp - Modify:
src/fesa/assembly/assembler.cpp - Create:
tests/unit/assembly/parallel_assembler_test.cpp - Create:
tests/integration/assembly/thread_count_determinism_test.cpp - Create:
tests/performance/assembly_benchmark.cpp - Modify: CMake files
Interfaces:
struct AssemblyOptions final {
std::size_t max_threads;
std::size_t grain_size;
};
[[nodiscard]] EquationSystem assemble_parallel(
const Domain& domain,
const DofManager& dofs,
AssemblyOptions options);
- Step 1: Write failing serial-versus-parallel tests
Generate fixed chain and branched Beam domains. Compare CSR row offsets and column indices exactly and values bit-for-bit for thread counts 1, 2, and the available concurrency.
- Step 2: Confirm failure
cmake --build --preset windows-debug
ctest --preset windows-debug -R "ParallelAssembly|ThreadCount" --output-on-failure
- Step 3: Implement parallel element evaluation with deterministic merge
Use oneTBB for independent element evaluation. Each contribution retains (row, column, element ID, local order). Sort and reduce in the same order as the serial oracle. Do not perform concurrent unordered writes to CSR values.
- Step 4: Validate correctness before measuring performance
cmake --build --preset windows-debug
ctest --preset windows-debug --output-on-failure
.\out\build\windows-debug\Debug\fesa_assembly_benchmark.exe
Record timings without asserting a speedup in unit tests.
- Step 5: Commit
git add include/fesa/assembly src/fesa/assembly tests/unit/assembly tests/integration/assembly tests/performance CMakeLists.txt tests/CMakeLists.txt
git commit -m "perf(assembly): add deterministic TBB assembly"
Task 13: Complete Beam Recovery and Self-Contained HDF5 Contract
Files:
- Modify:
include/fesa/elements/beam/beam3d2.hpp - Modify:
src/fesa/elements/beam/beam3d2.cpp - Modify: result-model and HDF5 files from Task 9
- Modify:
docs/HDF5_SCHEMA.md - Create:
tests/unit/elements/beam3d2_recovery_test.cpp - Create:
tests/integration/results/self_contained_hdf5_test.cpp
Interfaces:
struct BeamSectionResult final {
double xi;
NodeId end_node;
std::array<double, 6> section_strain;
std::array<double, 6> section_force;
double centroid_sigma_xx;
std::vector<double> sigma_xx;
};
[[nodiscard]] std::vector<BeamSectionResult> recover_beam3d2(
const Beam3D2Input& input,
std::span<const double, 12> element_displacement,
std::span<const std::array<double, 2>> recovery_points);
- Step 1: Write failing recovery tests
Cover pure axial force, pure torsion, bending about each principal axis,
combined axial/biaxial bending, force sign at both element ends,
centroid_sigma_xx=N/A, and recovery-point ordering.
- Step 2: Write the failing self-contained-file test
Reopen one result file and reconstruct node coordinates, connectivity, Part/Instance origins, sets, material, section, applied shear values and their source, step, solver settings, local frame, nodal fields, element section fields including centroid stress, ID maps, and diagnostics.
- Step 3: Implement recovery and schema additions
Do not output point shear or point torsional stress. Store section shear resultants and torsional moment as generalized quantities.
- Step 4: Validate and commit
cmake --build --preset windows-debug
ctest --preset windows-debug -R "Beam3D2Recovery|SelfContainedHdf5" --output-on-failure
ctest --preset windows-debug --output-on-failure
git add include/fesa/elements src/fesa/elements include/fesa/results include/fesa/io/hdf5 src/fesa/io/hdf5 docs/HDF5_SCHEMA.md tests/unit/elements tests/integration/results
git commit -m "feat(results): recover and store beam section results"
Task 14: Analytical and Available Abaqus 2024 Qualification
Files:
- Create:
include/fesa/validation/comparison.hpp - Create:
include/fesa/validation/reference_csv.hpp - Create:
src/fesa/validation/comparison.cpp - Create:
src/fesa/validation/reference_csv.cpp - Create:
src/fesa/validation/reference_compare_main.cpp - Create:
tests/unit/validation/comparison_test.cpp - Create:
tests/unit/validation/reference_csv_test.cpp - Create:
tests/fixtures/reference/internalforces.csv - Create:
tests/fixtures/reference/stresses.csv - Create:
tests/reference/CMakeLists.txt - Create:
tests/reference/cantilever_reference_test.cpp - Create:
docs/VALIDATION.md - Modify: CMake files
Interfaces:
struct Tolerance final {
double relative;
double absolute_scale;
};
enum class ReferenceQuantity {
displacement,
reaction,
internal_force,
centroid_stress
};
struct ResultPosition final {
std::string instance_name;
std::int64_t entity_label;
std::optional<std::int64_t> end_node_label;
};
struct ComparisonSample final {
ReferenceQuantity quantity;
ResultPosition position;
std::vector<double> reference;
std::vector<double> actual;
Tolerance tolerance;
};
struct ReferenceRow final {
ReferenceQuantity quantity;
ResultPosition position;
std::vector<double> values;
};
struct ComparisonReport final {
bool passed;
double maximum_normalized_error;
std::vector<Diagnostic> failures;
};
[[nodiscard]] ComparisonReport compare_samples(
std::span<const ComparisonSample> samples);
struct ReferenceCsvReadResult final {
std::vector<ReferenceRow> rows;
std::vector<Diagnostic> diagnostics;
};
[[nodiscard]] ReferenceCsvReadResult read_reference_csv(
ReferenceQuantity quantity,
const std::filesystem::path& path,
std::string_view single_instance_name,
Tolerance tolerance);
For each scalar component, define [ e_n=\frac{|a-r|}{a_\mathrm{scale}+r_\mathrm{tol}|r|} ] and pass only when (e_n\leq1). Reject nonfinite inputs before computing the metric.
- Step 1: Write failing metric and entity-matching tests
Test the normalized error, near-zero absolute scale, nonfinite values, duplicate positions, unknown entities, invalid element-node pairs, and component-count mismatches.
- Step 2: Write failing CSV adapter tests for all four quantities
Accept the supplied displacement/reaction headers after trimming whitespace.
For a single Instance, allow the Part Instance Name column to be absent.
Use these exact element schemas:
Part Instance Name, Element Label, Node Label,
SF-SF1, SF-SF2, SF-SF3, SM-SM1, SM-SM2, SM-SM3
Part Instance Name, Element Label, Node Label, Sxx
Map SF1,SF2,SF3,SM1,SM2,SM3 to (N,V_y,V_z,T,M_y,M_z).
Compare Sxx with the element-end section-centroid value (N/A). The
synthetic fixtures exercise both element result schemas before real files
exist.
- Step 3: Write the failing supplied-cantilever reference test
Run FESA through the public parser, analysis, and HDF5 reader. Create an explicit request selecting only:
reference/cantilever beam/cantilever beam displacements.csv
reference/cantilever beam/cantilever beam reactions.csv
Use relative tolerance (10^{-5}) and test-registered absolute scales. Do not look for metadata, internal-force CSV, or stress CSV in this reference test. The equivalent command-line contract is:
fesa-reference-compare `
--results out\cantilever-beam.h5 `
--instance Part-1-1 `
--displacements "reference\cantilever beam\cantilever beam displacements.csv" `
--reactions "reference\cantilever beam\cantilever beam reactions.csv" `
--relative-tolerance 1e-5 `
--displacement-absolute-scale 1e-10 `
--reaction-absolute-scale 1e-8
- Step 4: Confirm failures before numerical corrections
cmake --preset windows-debug
cmake --build --preset windows-debug
ctest --preset windows-debug -R "Reference" --output-on-failure
- Step 5: Correct only evidenced formulation or recovery defects
For every change, add or tighten the smallest analytical test that reproduces
the discrepancy. Do not widen tolerance to hide a defect. Record justified
tolerance differences in docs/VALIDATION.md and the CTest registration.
- Step 6: Run the full qualification suite
ctest --preset windows-debug --output-on-failure
ctest --preset windows-debug -R "Reference" --output-on-failure
Expected: all analytical, physics-sanity, pipeline, deterministic-parallel, CSV-adapter, and available Abaqus displacement/reaction comparisons pass. Do not claim Abaqus qualification of element internal force or stress until those CSV files are supplied and selected.
- Step 7: Complete the validation report and commit
docs/VALIDATION.md must list each benchmark, analytical/reference source,
Abaqus configuration, selected quantities, tolerance, maximum observed error,
and disposition. It must distinguish synthetic adapter coverage from
Abaqus-backed quantity qualification.
git add include/fesa/validation src/fesa/validation tests/unit/validation tests/fixtures/reference tests/reference docs/VALIDATION.md CMakeLists.txt tests/CMakeLists.txt
git commit -m "test(validation): qualify Beam solver against Abaqus"
Task 15: Internal Release Gate
Files:
- Create:
cmake/install.cmake - Create:
cmake/FESAConfig.cmake.in - Create:
docs/BUILDING.md - Create:
docs/INPUT_FORMAT.md - Create:
docs/RELEASE_CHECKLIST.md - Create:
tests/performance/phase1_scale_benchmark.cpp - Modify: root CMake files
- Modify:
docs/VALIDATION.md
Interfaces:
-
Produces install tree containing
fesa.exe, the static library, public headers, required runtime DLL inventory, example input, schema, and validation report. -
No public ABI compatibility promise is made for Phase 1.
-
Step 1: Write the release checklist before packaging changes
Include environment versions, Debug/Release builds, zero-warning requirement, CTest count, selected reference quantities, synthetic four-quantity adapter coverage, HDF5 inspection, 100k-DOF memory/time measurement, runtime DLL inventory, example run, and clean install-tree smoke test.
- Step 2: Add a failing install-tree smoke test
The test configures a small consumer against installed headers and the static library, runs fesa --version, solves the example, and opens its HDF5 output.
- Step 3: Implement CMake install rules
Use cmake --install; do not add an installer, registry writes, package download, or external-customer SDK promise.
- Step 4: Run Debug, Release, performance, and install validation
cmake --preset windows-debug
cmake --build --preset windows-debug
ctest --preset windows-debug --output-on-failure
cmake --preset windows-release
cmake --build --preset windows-release
ctest --preset windows-release --output-on-failure
.\out\build\windows-release\Release\fesa_phase1_scale_benchmark.exe
cmake --install out\build\windows-release --config Release --prefix out\package\fesa
Expected: all tests pass, no new MSVC warnings exist, the benchmark completes within documented resources, and the clean install-tree smoke test passes.
- Step 5: Verify every PRD release criterion
Map every item in docs/PRD.md section 8 to fresh command output or a validation-report entry. Stop the release if any item lacks evidence.
- Step 6: Commit
git add cmake/install.cmake docs/BUILDING.md docs/INPUT_FORMAT.md docs/RELEASE_CHECKLIST.md docs/VALIDATION.md tests/performance CMakeLists.txt
git commit -m "chore(release): prepare FESA Phase 1 internal package"
Plan Execution Gate
Before implementation:
- Review and approve this phase split.
- Use the Harness skill to draft
phases/index.json, each phase index, and self-contained step files. - Review the phase-file draft before creating it.
- Execute one Harness phase at a time.
- Do not begin the next phase until its tests, review, and acceptance commands pass.