feat(nonlinear-static-newton-raphson): step 0 - cli-output-lease-and-derived-names

This commit is contained in:
KOKO\Mimi
2026-08-24 10:42:14 +09:00
parent 41816c8aef
commit fc16ef6221
61 changed files with 10890 additions and 115 deletions
+5 -1
View File
@@ -13,6 +13,10 @@
"dir": "cpp-object-oriented-modular-refactoring",
"status": "completed",
"completed_at": "2026-08-16T13:54:51+0900"
},
{
"dir": "nonlinear-static-newton-raphson",
"status": "pending"
}
]
}
}
@@ -0,0 +1,136 @@
# Step 0: cli-output-lease-and-derived-names
## 읽어야 할 파일
- `AGENTS.md`
- `docs/SOLVER_AGENT_DESIGN.md`
- `docs/HARNESS.md`
- `docs/HARNESS_WORKFLOW.md`
- `.codex/hooks.json`
- `docs/nonlinear-static-newton-raphson/implementation-plan.md`
- `docs/nonlinear-static-newton-raphson/io.md`
- `docs/nonlinear-static-newton-raphson/requirements.md`
- `src/fesa/app/fesa_application.cpp`
- `include/fesa/app/fesa_application.h`
- `include/fesa/analysis/analysis.h`
- `tests/integration/app/fesa_application_test.cpp`
- `src/fesa/CMakeLists.txt`
- `tests/CMakeLists.txt`
## 이전 Step 전제
없음. 이 Step은 app/CLI output lifecycle의 첫 변경이다.
## 소유 파일
- Create: `include/fesa/app/output_paths.h`
- Create: `src/fesa/app/output_paths.cpp`
- Create: `tests/unit/app/output_paths_test.cpp`
- Modify: `include/fesa/analysis/analysis.h`
- Modify: `include/fesa/app/fesa_application.h`
- Modify: `src/fesa/app/fesa_application.cpp`
- Modify: `src/fesa/CMakeLists.txt`
- Modify: `tests/CMakeLists.txt`
- Test: `tests/integration/app/fesa_application_test.cpp`
## 인터페이스
```cpp
namespace fesa {
struct DerivedOutputPaths {
std::filesystem::path input_path;
std::filesystem::path results_path;
std::filesystem::path log_path;
};
class OutputWriteLease {
public:
OutputWriteLease(const OutputWriteLease&) = delete;
OutputWriteLease& operator=(const OutputWriteLease&) = delete;
OutputWriteLease(OutputWriteLease&&) noexcept;
OutputWriteLease& operator=(OutputWriteLease&&) noexcept;
~OutputWriteLease();
private:
class Impl;
friend Result<OutputWriteLease> AcquireOutputWriteLease(
const DerivedOutputPaths& paths);
explicit OutputWriteLease(std::unique_ptr<Impl> impl) noexcept;
std::unique_ptr<Impl> impl_;
};
Result<DerivedOutputPaths> DeriveSingleInputOutputPaths(
const std::vector<std::string>& arguments,
const std::filesystem::path& invocation_cwd);
Result<OutputWriteLease> AcquireOutputWriteLease(
const DerivedOutputPaths& paths);
struct AnalysisRequest {
std::filesystem::path input_path;
std::filesystem::path output_path;
std::filesystem::path log_path;
};
} // namespace fesa
```
`OutputWriteLease`는 acquisition-only private constructor와 private pimpl storage로 lock lifetime을 보존한다. Win32 handle, file handle, 또는 platform lock 표현은 `Impl` 안에만 둔다. `FesaApplication`은 lease를 log close와 HDF5 finalization이 끝날 때까지 보관한다.
## RED
테스트를 먼저 추가한다.
- `NSNR-CLI-001`: argv의 모든 token을 왼쪽에서 오른쪽으로 검사하고, case-sensitive substring `--output`을 포함한 첫 token에서 exit code `2`와 diagnostic code `cli-usage`가 하나만 출력된다.
- `NSNR-CLI-002`: `abc--outputdef`, `--output-path`, `--output=foo`, `--output` 모두 input path resolution 전에 실패한다.
- `NSNR-CLI-003`: `--output` 실패는 parser, Analysis construction, lease acquisition, log create/truncate, HDF5 candidate creation을 호출하지 않는다.
- `NSNR-CLI-004`: relative `.inp` path는 invocation CWD 기준으로 lexical normalization만 하고 `<stem>_results.h5`, `<stem>_log.txt`를 같은 directory에 만든다.
- `NSNR-CLI-005`: final extension `.inp`는 case-insensitive이고 stem은 마지막 extension만 제거하며 basename case와 preceding dots를 보존한다.
- `NSNR-CLI-006`: output-busy lease collision은 exit code `6`이며 log와 HDF5를 만들거나 바꾸지 않는다.
- `NSNR-CLI-007`: previous result/log files가 있는 상태에서 `--output` 실패 후 bytes와 timestamps가 변하지 않는다.
- `NSNR-CLI-008`: existing default `fesa.exe <model.inp>` invocation은 더 이상 cwd `results.h5`를 기본값으로 쓰지 않는다.
- `NSNR-CLI-009`: `AnalysisRequest`가 `input_path`, `output_path`, `log_path`를 함께 전달한다.
실행:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "FesaApplication|OutputPath" --output-on-failure
```
기대: 새 테스트가 컴파일 실패 또는 assertion 실패로 실패한다. 새 테스트가 예상 없이 통과하면 구현을 중단하고 Coordinator에게 보고한다.
## GREEN
- `output_paths` module에 argv 검사, lexical path derivation, input extension validation, lease acquisition을 구현한다.
- `FesaApplication`은 `DeriveSingleInputOutputPaths` 성공 뒤 `AcquireOutputWriteLease`를 호출한다.
- 이 Step에서는 nonlinear procedure를 만들지 않는다.
- `LinearStaticAnalysis` 호출은 기존 방식대로 유지하되 `AnalysisRequest::output_path`가 `<stem>_results.h5`를 가리키게 한다.
## VERIFY
실행 전 `Test-Path .harness\config.json`으로 Harness config를 재확인한다. 있으면 그 설정을 우선한다. 없으면 다음 순서로 실행한다.
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "FesaApplication|OutputPath" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
## 상태 기록
Executor가 선택한 current Step 0만 `completed`, `error`, `blocked` 중 하나로 갱신한다. 성공 시 summary 한 줄만 기록한다. timestamp, retry, commit, advancement는 Executor 소유다.
## 금지사항
- `--output` alias 또는 deprecation path를 남기지 마라. 이유: 승인된 public compatibility가 fail-closed removal이다.
- path를 symlink, junction, short-name, case-normalized realpath로 resolve하지 마라. 이유: io.md가 lexical user-addressed identity를 요구한다.
- log/HDF5 작업이 끝나기 전에 `OutputWriteLease`를 파기하지 마라. 이유: exclusive ownership 보장이 깨진다.
- nonlinear analysis 분기를 `LinearStaticAnalysis` private lifecycle에 넣지 마라. 이유: Step 10의 별도 prepared flow가 소유한다.
@@ -0,0 +1,135 @@
# Step 1: nonlinear-static-domain-contract
## 읽어야 할 파일
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/ADR.md`
- `docs/nonlinear-static-newton-raphson/requirements.md`
- `docs/nonlinear-static-newton-raphson/io.md`
- `docs/nonlinear-static-newton-raphson/reference-model.md`
- `docs/nonlinear-static-newton-raphson/implementation-plan.md`
- `include/fesa/model/model_types.h`
- `include/fesa/model/domain.h`
- `src/fesa/model/domain.cpp`
- `src/fesa/io/abaqus/step_definition_mapper.cpp`
- `src/fesa/io/abaqus/domain_mapping_context.h`
- `src/fesa/io/abaqus/domain_mapping_context.cpp`
- `tests/unit/model/model_types_test.cpp`
- `tests/unit/model/domain_test.cpp`
- `tests/unit/io/abaqus/domain_mapper_test.cpp`
- `tests/unit/io/abaqus/domain_mapping_components_test.cpp`
- Step 0 files: `include/fesa/app/output_paths.h`, `include/fesa/analysis/analysis.h`, `src/fesa/app/fesa_application.cpp`
## 이전 Step 전제
Step 0이 app-level path/log request fields를 만들었고 기존 linear invocation이 `<stem>_results.h5`를 사용한다.
## 소유 파일
- Modify: `include/fesa/model/model_types.h`
- Modify: `include/fesa/model/domain.h`
- Modify: `src/fesa/model/domain.cpp`
- Modify: `src/fesa/io/abaqus/step_definition_mapper.cpp`
- Modify: `src/fesa/io/abaqus/domain_mapping_context.h`
- Modify: `src/fesa/io/abaqus/domain_mapping_context.cpp`
- Test: `tests/unit/model/model_types_test.cpp`
- Test: `tests/unit/model/domain_test.cpp`
- Test: `tests/unit/io/abaqus/domain_mapper_test.cpp`
- Test: `tests/unit/io/abaqus/domain_mapping_components_test.cpp`
## 인터페이스
```cpp
namespace fesa {
enum class StaticProcedureKind {
kLinearSmallDisplacement,
kNonlinearFullNewton
};
struct StaticStepDefinition {
std::string name;
std::vector<PrescribedDisplacementDefinition> boundaries;
std::vector<NodalLoad> loads;
StaticProcedureKind procedure_kind;
bool nlgeom;
double initial_increment;
double time_period;
double minimum_increment;
double maximum_increment;
SourceLocation location;
};
class StepDefinition {
public:
StaticProcedureKind ProcedureKind() const noexcept;
bool IsNonlinearStatic() const noexcept;
double InitialIncrement() const noexcept;
double TimePeriod() const noexcept;
double MinimumIncrement() const noexcept;
double MaximumIncrement() const noexcept;
};
} // namespace fesa
```
Domain stores procedure identity and raw four `*STATIC` values only. Factor sequence is derived in Step 5. PRESELECT output requests are warning no-ops and do not create Domain, Analysis, or HDF5 state.
## RED
Add tests first.
- `NSNR-IO-001`: `*STEP, NLGEOM=YES` maps to `StaticProcedureKind::kNonlinearFullNewton`.
- `NSNR-IO-002`: `*STEP, NLGEOM=NO` maps to `kLinearSmallDisplacement` and existing shell linear tests keep passing.
- `NSNR-IO-003`: missing `NLGEOM` keeps current linear behavior.
- `NSNR-IO-004`: nonlinear `*STATIC` requires four finite positive values and time period exactly `1.0`.
- `NSNR-IO-005`: raw values are preserved in `StepDefinition` without derived factor list.
- `NSNR-IO-006`: unsupported nonlinear procedure or mixed linear/nonlinear steps fail closed with `unsupported-analysis-procedure`.
- `NSNR-IO-007`: S4 and S4R shell definitions remain approved nonlinear semantic inputs.
- `NSNR-IO-008`: mixed B33/S4 nonlinear model fails with `unsupported-mixed-element-model`.
- `NSNR-IO-009`: `*OUTPUT, FIELD, VARIABLE=PRESELECT` is accepted with deterministic warning and no Domain state.
- `NSNR-IO-010`: `*OUTPUT, HISTORY, VARIABLE=PRESELECT` is accepted with deterministic warning and no Domain state.
- `NSNR-IO-011`: malformed PRESELECT parameters fail as input errors.
Run:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "Domain|DomainMapping|Abaqus" --output-on-failure
```
Expected: new tests fail. Unexpected pass stops the Step.
## GREEN
- Extend `StaticStepDefinition` and `StepDefinition` exactly with the fields and accessors above.
- Update domain construction so source-order boundaries and loads remain deterministic.
- Update `domain_mapping_context` to parse `NLGEOM=YES`, preserve raw `*STATIC`, emit PRESELECT warnings, and keep PRESELECT out of Domain state.
- Preserve existing linear shell and beam mappings.
## VERIFY
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "Domain|DomainMapping|Abaqus" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
Recheck `.harness/config.json` before using these defaults.
## 상태 기록
Only Step 1 status and one-line summary/error/blocked payload may be updated by the Implementation Agent. Executor owns timestamps, commits, retry, and next Step selection.
## 금지사항
- Do not add `LoadFactorSequence()` to Domain. 이유: factor sequence ownership belongs to Step 5 control.
- Do not store PRESELECT flags in Domain or HDF5 state. 이유: approved I/O contract makes PRESELECT an ignored warning no-op.
- Do not alter requirements or I/O docs. 이유: upstream contracts are approved.
@@ -0,0 +1,177 @@
# Step 10: prepared-analysis-flow-and-full-procedure
## 읽어야 할 파일
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/ADR.md`
- `docs/nonlinear-static-newton-raphson/requirements.md`
- `docs/nonlinear-static-newton-raphson/formulation.md`
- `docs/nonlinear-static-newton-raphson/numerical-review.md`
- `docs/nonlinear-static-newton-raphson/io.md`
- `include/fesa/analysis/analysis.h`
- `include/fesa/analysis/linear_static_analysis.h`
- `src/fesa/analysis/linear_static_analysis.cpp`
- `src/fesa/app/fesa_application.cpp`
- `include/fesa/io/hdf5/hdf5_results_writer.h`
- `include/fesa/results/results_writer.h`
- `include/fesa/results/nonlinear_results_writer.h`
- Step 0 files: `include/fesa/app/output_paths.h`, `src/fesa/app/output_paths.cpp`
- Step 1 files: `include/fesa/model/domain.h`, `src/fesa/io/abaqus/domain_mapping_context.cpp`
- Step 5 files: `include/fesa/analysis/nonlinear_static_control.h`
- Step 6 files: `include/fesa/solvers/linear/tangent_solver.h`
- Step 7 files: `include/fesa/analysis/newton_iteration_log.h`
- Step 8 files: `include/fesa/analysis/nonlinear_analysis_state.h`
- Step 9 files: `include/fesa/results/nonlinear_results_writer.h`, `include/fesa/io/hdf5/nonlinear_hdf5_results_writer.h`
## 이전 Step 전제
Steps 0-9 provide path/lease, Domain procedure identity, nonlinear element/control/solver/log/state, and nonlinear HDF5 writer.
## 소유 파일
- Create: `include/fesa/analysis/prepared_analysis.h`
- Create: `src/fesa/analysis/prepared_analysis.cpp`
- Create: `include/fesa/analysis/linear_static_procedure.h`
- Create: `src/fesa/analysis/linear_static_procedure.cpp`
- Create: `include/fesa/analysis/nonlinear_static_procedure.h`
- Create: `src/fesa/analysis/nonlinear_static_procedure.cpp`
- Modify: `include/fesa/analysis/linear_static_analysis.h`
- Modify: `src/fesa/analysis/linear_static_analysis.cpp`
- Modify: `src/fesa/app/fesa_application.cpp`
- Test: `tests/integration/analysis/linear_static_analysis_test.cpp`
- Create: `tests/integration/analysis/nonlinear_static_procedure_test.cpp`
- Test: `tests/integration/app/fesa_application_test.cpp`
- Modify: `src/fesa/CMakeLists.txt`
- Modify: `tests/CMakeLists.txt`
## 인터페이스
```cpp
namespace fesa {
struct PreparedAnalysisInput {
Domain domain;
std::vector<Diagnostic> diagnostics;
};
Result<PreparedAnalysisInput> PrepareAnalysisInput(
const std::filesystem::path& input_path);
struct LinearStaticExecutionResult {
AnalysisState state;
std::vector<Diagnostic> diagnostics;
};
class LinearStaticProcedureExecutor {
public:
LinearStaticProcedureExecutor(const ParallelFor& parallel_for,
LinearSolver& linear_solver);
Result<LinearStaticExecutionResult> Execute(const Domain& domain) const;
private:
const ParallelFor& parallel_for_;
LinearSolver& linear_solver_;
};
struct NonlinearStaticExecutionResult {
NonlinearAnalysisState state;
std::vector<Diagnostic> diagnostics;
};
class NonlinearStaticProcedureExecutor {
public:
NonlinearStaticProcedureExecutor(const ParallelFor& parallel_for,
TangentSolver& tangent_solver);
Result<NonlinearStaticExecutionResult> Execute(
const Domain& domain, NewtonIterationLogWriter& log) const;
private:
const ParallelFor& parallel_for_;
TangentSolver& tangent_solver_;
};
using PreparedAnalysisOutcome =
std::variant<LinearStaticExecutionResult, NonlinearStaticExecutionResult>;
Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
const Domain& domain,
LinearStaticProcedureExecutor& linear_executor,
NonlinearStaticProcedureExecutor& nonlinear_executor,
NewtonIterationLogWriter& log);
Status FinalizePreparedAnalysisResult(
const DerivedOutputPaths& paths,
const Domain& domain,
const PreparedAnalysisOutcome& outcome,
const std::vector<Diagnostic>& diagnostics,
ResultsWriter& linear_writer,
NonlinearResultsWriter& nonlinear_writer);
} // namespace fesa
```
## RED
Add tests first.
- `NSNR-PROC-001`: app validates argv, derives paths, acquires `OutputWriteLease`, opens log, then parses/maps exactly once.
- `NSNR-PROC-002`: parse/map diagnostics are sorted and preserved in prepared input.
- `NSNR-PROC-003`: linear `NLGEOM=NO` run uses `LinearStaticProcedureExecutor` and preserves existing numerical/HDF5 meaning except output names and log lifecycle.
- `NSNR-PROC-004`: nonlinear `NLGEOM=YES` run uses `NonlinearStaticProcedureExecutor`.
- `NSNR-PROC-005`: linear executor receives injected `ParallelFor` and existing `LinearSolver`; nonlinear executor receives injected `ParallelFor` and `TangentSolver`.
- `NSNR-PROC-006`: app constructs and owns concrete `TbbParallelFor`, `MklPardisoSolver`, `MklPardisoTangentSolver`, `Hdf5ResultsWriter`, and `NonlinearHdf5ResultsWriter`.
- `NSNR-PROC-007`: procedure computation/recovery returns a validated outcome without opening HDF5.
- `NSNR-PROC-008`: app closes log successfully before calling injected linear or nonlinear writer.
- `NSNR-PROC-009`: log close failure blocks HDF5 candidate creation.
- `NSNR-PROC-010`: nonlinear executor builds `NonlinearStateBaselineInput` from Domain nodes in stable internal order, preserving each node's `EntityIndex` and full `SourceEntityId`, then constructs state through `NonlinearAnalysisState::Create(domain, dofs, baseline)` before the first increment; the factor-zero baseline is committed state only and is not an accepted frame.
- `NSNR-PROC-011`: nonlinear increment loop uses factors from Step 5 and iterations `1..100`.
- `NSNR-PROC-012`: each entered iteration performs fresh tangent factorization and solves `Kff * delta = -r_f`.
- `NSNR-PROC-013`: global external vector/tangent from Step 5 is assembled once and combined with approved signs `r = f_int - f_ext`, `K_T = K_int - K_ext`; the fixed-moment tangent is subtracted once and is never duplicated per incident element.
- `NSNR-PROC-014`: iteration 100 can accept if gates pass; failure at 100 emits `nonlinear-nonconvergence` with no retry.
- `NSNR-PROC-015`: nonfinite data, factorization failure, solve failure, invalid accepted state, and log failure map to approved diagnostics.
- `NSNR-PROC-016`: constrained correction is zero and reactions/free residuals come from full residual `r = f_int - f_ext`.
- `NSNR-PROC-017`: failed nonlinear run preserves prior final HDF5 according to output lifecycle.
- `NSNR-PROC-018`: deterministic repeat produces same sparse structure, nodal source-identity order, result row order, diagnostics, and log line ordering.
Run:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "LinearStaticAnalysis|NonlinearStaticProcedure|FesaApplication" --output-on-failure
```
Expected: new tests fail. Unexpected pass stops.
## GREEN
Refactor current parse/map and linear compute/recovery out of `LinearStaticAnalysis::Run()` into `PrepareAnalysisInput` and `LinearStaticProcedureExecutor`. Keep `LinearStaticAnalysis` as a compatibility wrapper that delegates through the new flow and does not contain nonlinear conditions. Implement `NonlinearStaticProcedureExecutor` in `nonlinear_static_procedure.*` using Steps 2-8, constructing state only through `NonlinearAnalysisState::Create(...)` with one baseline per Domain node in stable internal order, forming `r = f_int - f_ext` and `K_T = K_int - K_ext`, preserving `EntityIndex` plus full `SourceEntityId` into final writer inputs, and delaying all HDF5 work until `FinalizePreparedAnalysisResult`. The app owns concrete adapters and passes them through interfaces.
## VERIFY
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "LinearStaticAnalysis|NonlinearStaticProcedure|FesaApplication" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
Recheck `.harness/config.json` before default commands.
## 상태 기록
Update only Step 10 status and one-line payload. Executor owns timestamps, commits, retry, and advancement.
## 금지사항
- Do not parse/map twice. 이유: source identity, diagnostics, and Domain lifetime must be deterministic.
- Do not reconstruct nodal identity from source labels alone. 이유: labels can repeat across instances and HDF5/reference matching requires full `SourceEntityId`.
- Do not open HDF5 before log close succeeds. 이유: io.md requires log close success before HDF5 candidate open/write/final replace.
- Do not insert nonlinear branches into `LinearStaticAnalysis` private lifecycle. 이유: new procedure owns its own lifecycle.
- Do not replace final HDF5 on failed analysis, log failure, or HDF5 failure. 이유: asymmetric preservation contract requires prior valid final preservation.
- Do not construct MKL/TBB/HDF5 backends inside procedure executors. 이유: dependencies are injected so tests can observe lifecycle and no backend type crosses analysis/core public boundaries.
@@ -0,0 +1,191 @@
# Step 11: reference-comparison-gate
## 읽어야 할 파일
- `AGENTS.md`
- `docs/TOLERANCE.md`
- `docs/nonlinear-static-newton-raphson/reference-model.md`
- `docs/nonlinear-static-newton-raphson/io.md`
- `docs/nonlinear-static-newton-raphson/numerical-review.md`
- `tests/reference/reference_tolerance_policy.h`
- `tests/reference/reference_tolerance_policy.cpp`
- `tests/reference/reference_comparison.h`
- `tests/reference/reference_comparison.cpp`
- `tests/reference/mitc4_reference_comparison.h`
- `tests/reference/mitc4_reference_comparison.cpp`
- Step 9 files: `include/fesa/results/nonlinear_results_writer.h`, `include/fesa/io/hdf5/nonlinear_hdf5_results_writer.h`
- Step 10 files: `include/fesa/analysis/prepared_analysis.h`, `src/fesa/app/fesa_application.cpp`
## 이전 Step 전제
Step 10 runs copied input files through FESA and writes authoritative HDF5 outputs after log close.
## 소유 파일
- Create: `tests/reference/nonlinear_static_reference_comparison.h`
- Create: `tests/reference/nonlinear_static_reference_comparison.cpp`
- Create: `tests/reference/nonlinear_static_reference_cases_test.cpp`
- Modify: `tests/CMakeLists.txt`
## 인터페이스
```cpp
namespace fesa::test {
struct NonlinearReferenceCase {
std::string case_id;
std::filesystem::path original_input_path;
std::filesystem::path displacement_csv_path;
std::filesystem::path reaction_csv_path;
std::filesystem::path stress_inventory_path;
std::filesystem::path execution_directory;
};
struct ArtifactSnapshot {
std::filesystem::path path;
std::string sha256;
std::uintmax_t byte_size;
std::filesystem::file_time_type last_write_time;
};
enum class NonlinearReferenceDisposition {
kBlocking,
kWarningOnly,
kExcluded
};
struct NonlinearReferenceRowKey {
std::string case_id;
std::string accepted_state;
std::string instance_name;
std::int64_t source_node_label;
std::string component;
};
struct NonlinearReferenceFamilyReport {
std::string family_identity;
std::vector<std::string> components;
NonlinearReferenceDisposition disposition;
std::size_t row_count;
ReferenceToleranceFamilyDecision tolerance;
};
struct GeneratedRunArtifacts {
std::filesystem::path copied_input_path;
std::filesystem::path generated_results_hdf5;
std::filesystem::path generated_log_path;
};
struct NonlinearReferenceComparisonReport {
std::string case_id;
std::filesystem::path original_input_path;
std::filesystem::path copied_input_path;
std::filesystem::path generated_results_hdf5;
std::filesystem::path generated_log_path;
std::vector<ArtifactSnapshot> pre_run_artifacts;
std::vector<ArtifactSnapshot> post_run_artifacts;
std::vector<NonlinearReferenceFamilyReport> families;
bool blocking_passed;
};
enum class NonlinearReferenceClassification {
kPassed,
kFailed,
kWarningOnlyDifferences
};
Result<std::vector<ArtifactSnapshot>> ArtifactCheck(
const NonlinearReferenceCase& reference_case);
Result<std::filesystem::path> CopyInputToExecutionDirectory(
const NonlinearReferenceCase& reference_case);
Result<GeneratedRunArtifacts> RunFesaApplicationOnCopiedInput(
const NonlinearReferenceCase& reference_case,
const std::filesystem::path& copied_input_path);
Result<NonlinearReferenceComparisonReport> CompareHdf5ToReferenceCsv(
const NonlinearReferenceCase& reference_case,
const std::filesystem::path& hdf5_results_path);
NonlinearReferenceClassification ClassifyReferenceComparison(
const NonlinearReferenceComparisonReport& report);
NonlinearReferenceComparisonReport BuildReferenceComparisonReport(
const NonlinearReferenceCase& reference_case,
const std::filesystem::path& hdf5_results_path);
} // namespace fesa::test
```
## RED
Add tests first.
- `NSNR-REF-001`: artifact check verifies exact pre-run SHA-256 for every declared input/CSV path.
- `NSNR-REF-002`: original `reference/shellNL/shellNL.inp` bytes are copied unchanged into a unique build-local temp case directory and only the copied `.inp` is passed to `FesaApplication`.
- `NSNR-REF-003`: original `reference/shell/shell.inp` bytes are copied unchanged into a unique build-local temp case directory and only the copied `.inp` is passed to `FesaApplication`.
- `NSNR-REF-004`: copied shellNL run produces copied-sibling `shellNL_results.h5` and `shellNL_log.txt`; copied shell run produces copied-sibling `shell_results.h5` and `shell_log.txt`.
- `NSNR-REF-005`: HDF5 source path identity matches the copied user-addressed input path, and source content identity matches the approved original input hash.
- `NSNR-REF-006`: exact post-run SHA-256, byte size, timestamp, and inventory for all original declared reference artifacts match pre-run snapshots.
- `NSNR-REF-007`: shellNL displacement/reaction rows require exact `Frame` value `Increment 10: Step Time = 1.000`.
- `NSNR-REF-008`: required CSV wide rows expand to unique `(case_id, accepted_state, instance, source_node_label, component)` keys.
- `NSNR-REF-009`: HDF5 rows and CSV rows reject missing, extra, duplicate, and nonfinite required rows before tolerance.
- `NSNR-REF-010`: `linear.translation` has 147 blocking rows.
- `NSNR-REF-011`: `linear.rotation` has 147 warning-only rows.
- `NSNR-REF-012`: `nonlinear.translation` has 147 blocking rows.
- `NSNR-REF-013`: `nonlinear.rotation` has 147 warning-only rows.
- `NSNR-REF-014`: shellNL constrained nodes select only labels `1,3,4,5,6,7,8,9,14,15,16,17,22,23,24,25,26,27,28,29,30,31,32,33`.
- `NSNR-REF-015`: `RF1/RF2` has 48 blocking rows, `RF3` has 24 blocking rows, and `RM1/RM2` has 48 blocking rows.
- `NSNR-REF-016`: `RM3` and stress quantities create no numeric comparison family.
- `NSNR-REF-017`: docs/TOLERANCE.md family policy is applied exactly and warning-only failures do not change blocking verdict.
- `NSNR-REF-018`: stress CSV files are hash-audited as nonblocking inventory artifacts and their schema/order is not a blocking verdict.
Run:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearStaticReference|ReferenceTolerance|Mitc4Reference" --output-on-failure
```
Expected: new tests fail. Unexpected pass stops.
## GREEN
Implement `ARTIFACT CHECK -> COPY -> RUN -> COMPARE -> CLASSIFY -> REPORT` while preserving the required public gate wording `ARTIFACT CHECK -> COMPARE -> CLASSIFY -> REPORT` in reports. The runner never passes the original reference input path to `FesaApplication`; it copies input bytes unchanged into a unique build-local temp case directory, runs the copied `.inp`, consumes copied-sibling `_results.h5` and `_log.txt`, and compares HDF5 data to original read-only CSVs. Use exact declared paths and hashes:
| path | rows | SHA-256 |
| --- | ---: | --- |
| `reference/shellNL/shellNL.inp` | input | `4BF3ECB0F7F1E8462C573E993DBC97AC5D920C91CF6F23AD4C6891D77FFBADA2` |
| `reference/shellNL/shellNL displacements.csv` | 49 | `4E0F99CA9372B1ADF7FAF102F43BC0B887DC743FDEDA378E4342D2CF8E0C0786` |
| `reference/shellNL/shellNL reactions.csv` | 49 | `001CB1541A9E94424E5E650509D46BC8724970DF4DE47184D9DB09C7938AB069` |
| `reference/shellNL/shellNL stresses.csv` | 288 | `5500E5114B1C44887528E30CEC054A1EB0F68BE3F4B8CE81FA59FC74A881483F` |
| `reference/shell/shell.inp` | input | `4005851E1AB22FD3A16AC17A8D5DA3E051233F69F37419079F3553AD134ECFCF` |
| `reference/shell/shell displacements.csv` | 49 | `C81D94E0B4A849F87AA0F79C83A79B94D5661AC79E44ED826919AB432C87746B` |
| `reference/shell/shell reactions.csv` | 49 | `0E34EEC27E700C3C0C744C1DAFD8A3B95EFEF34B8616FE96665876586FE82D41` |
| `reference/shell/shell stresses.csv` | 288 | `2B75A5B0DEB045A283C9BB7E9B1153B79F7537FF487214BF4A23316B59B660A7` |
Use docs/TOLERANCE.md exactly: family scale `S=max(abs(reference))`, near-zero band `0.01*S`, non-near relative error `<=0.05`, family `RMS(error)/S<=0.01`, zero-scale exact zero only. Original reference directory inventory, bytes, timestamps, and hashes must be unchanged after the run.
## VERIFY
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearStaticReference|ReferenceTolerance|Mitc4Reference" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
Recheck `.harness/config.json` before default commands.
## 상태 기록
Update only Step 11 status and one-line payload. Executor owns timestamps, commits, retry, and advancement.
## 금지사항
- Do not rewrite, rename, normalize, or repair `.inp` or CSV artifacts. 이유: reference artifacts are immutable external evidence.
- Do not run `FesaApplication` on original `reference/shellNL/shellNL.inp` or `reference/shell/shell.inp`. 이유: CLI derives sibling outputs and would mutate the reference directory.
- Do not compare `RM3`. 이유: approved contract excludes stabilization-dependent drilling reaction.
- Do not numerically compare stress CSV data. 이유: stress artifacts are nonblocking inventory only in this feature.
- Do not average, omit, synthesize, or row-order match reference rows. 이유: source/component identity prechecks are mandatory.
@@ -0,0 +1,96 @@
# Step 12: execution-closure-and-evidence-docs
## 읽어야 할 파일
- `AGENTS.md`
- `docs/PRD.md`
- `docs/ARCHITECTURE.md`
- `docs/ADR.md`
- `docs/CODINGSTYLE.md`
- `docs/TOLERANCE.md`
- `docs/nonlinear-static-newton-raphson/implementation-plan.md`
- `docs/nonlinear-static-newton-raphson/requirements.md`
- `docs/nonlinear-static-newton-raphson/numerical-review.md`
- `docs/nonlinear-static-newton-raphson/reference-model.md`
- `docs/nonlinear-static-newton-raphson/io.md`
- `phases/nonlinear-static-newton-raphson/index.json`
- Step 0-11 modified source/test/CMake files
## 이전 Step 전제
Steps 0-11 have implemented and verified CLI lifecycle, Domain contract, nonlinear MITC4, controls, tangent solver, log, state, HDF5 schema v1, full prepared procedure, and reference comparison tests.
## 소유 파일
- Modify: `docs/PRD.md`
- Modify: `docs/ARCHITECTURE.md`
- Modify: `docs/ADR.md`
- Create: `docs/nonlinear-static-newton-raphson/implementation-report.md`
- Create: `docs/nonlinear-static-newton-raphson/build-test.md`
- Create: `docs/nonlinear-static-newton-raphson/reference-comparison.md`
No C++ production file is owned by this Step.
## 인터페이스
Evidence documents must record actual observed outputs:
```text
implementation-report.md: RED/GREEN/VERIFY command log, Step summaries, diagnostics, implementation-owned decisions.
build-test.md: fresh configure/build/discovery/full CTest commands, exit codes, duration, stdout/stderr tail, warning audit, failure classification.
reference-comparison.md: exact artifact inventory, HDF5 paths, projection, row prechecks, tolerance, per-family decisions, classification, no-change assertion.
```
## RED
Before writing evidence docs, run an evidence freshness audit by checking that the three evidence docs do not already contain current command outputs from this final Step.
- `NSNR-EVID-001`: missing or stale `implementation-report.md` is a failing precondition.
- `NSNR-EVID-002`: missing or stale `build-test.md` is a failing precondition.
- `NSNR-EVID-003`: missing or stale `reference-comparison.md` is a failing precondition.
- `NSNR-EVID-004`: PRD/ARCHITECTURE/ADR still contain directly superseded global CLI/procedure/output statements.
- `NSNR-EVID-005`: reference artifact pre-run SHA inventory must match Step 11 values.
Expected: evidence freshness audit fails before this Step writes docs. If it unexpectedly passes with current evidence already present, stop and report to Coordinator.
## GREEN
- Run fresh MSVC x64 Debug configure/build/discovery/full CTest.
- Run focused nonlinear reference comparison repeat.
- Run deterministic repeat/reference checks required by Step 11.
- Run warning audit from build output.
- Run `git diff --check` and `git status --short`.
- Run reference artifact post-run SHA audit and compare to pre-run values.
- Update only directly superseded PRD/ARCHITECTURE/ADR statements for CLI removal, prepared linear/nonlinear procedure boundary, output naming, log lifecycle, and nonlinear HDF5 schema.
- Create the three Stage 6 evidence docs from actual command outputs only.
## VERIFY
Recheck `.harness/config.json`. If absent, use:
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
ctest --test-dir .harness/build -C Debug -R "NonlinearStaticReference" --output-on-failure
git diff --check
git status --short
```
Record command, exit code, duration, stdout/stderr tail, and failure classification for every command in the evidence docs.
## 상태 기록
Update only Step 12 status and one-line payload. Executor owns timestamps, commits, retry, top-level phase completion, and advancement.
## 금지사항
- Do not claim success before the commands above actually pass. 이유: Stage 6 evidence docs must be output-backed.
- Do not broadly rewrite global docs. 이유: Step 12 only synchronizes directly superseded CLI/procedure/output statements.
- Do not change reference artifact content. 이유: SHA pre/post immutability is part of the acceptance evidence.
- Do not run `scripts/execute.py` from this Step. 이유: Executor already owns Harness execution.
@@ -0,0 +1,101 @@
# Step 2: finite-rotation-state
## 읽어야 할 파일
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/ADR.md`
- `docs/nonlinear-static-newton-raphson/formulation.md`
- `docs/nonlinear-static-newton-raphson/numerical-review.md`
- `docs/nonlinear-static-newton-raphson/implementation-plan.md`
- `include/fesa/math/vector3.h`
- `include/fesa/math/matrix.h`
- `tests/unit/math/vector3_test.cpp`
- `tests/unit/math/matrix_test.cpp`
- Step 1 files: `include/fesa/model/model_types.h`, `include/fesa/model/domain.h`
## 이전 Step 전제
Step 1 exposes nonlinear static semantic selection. This Step adds finite rotation math used by nonlinear MITC4 and state recovery.
## 소유 파일
- Create: `include/fesa/elements/finite_rotation.h`
- Create: `src/fesa/elements/finite_rotation.cpp`
- Create: `tests/unit/elements/finite_rotation_test.cpp`
- Modify: `src/fesa/CMakeLists.txt`
- Modify: `tests/CMakeLists.txt`
## 인터페이스
```cpp
namespace fesa {
struct FiniteRotationState {
Vector3 phi;
Matrix q;
Vector3 director;
};
Matrix ExpSkew(const Vector3& phi);
Matrix LeftJacobianSO3(const Vector3& phi);
Matrix InverseTransposeLeftJacobianSO3(const Vector3& phi);
Result<FiniteRotationState> MakeFiniteRotationState(
const Vector3& phi, const Matrix& initial_frame);
Result<FiniteRotationState> ApplyGlobalRotationIncrement(
const FiniteRotationState& committed,
const Vector3& delta_phi,
const Matrix& initial_frame);
} // namespace fesa
```
## RED
Add tests first.
- `NSNR-ROT-001`: zero `phi` returns `Q0` and `director=Q0*e3`.
- `NSNR-ROT-002`: finite `phi` satisfies `Q=exp(phi^)Q0`, `Q^T Q=I`, `det(Q)=1`, `director=Qe3`.
- `NSNR-ROT-003`: update uses `Q+ = exp((phi + delta_phi)^)Q0`.
- `NSNR-ROT-004`: principal branch margin `sqrt(epsilon_double)` rejects ambiguous rotations near `pi`.
- `NSNR-ROT-005`: `LeftJacobianSO3` and inverse-transpose are finite and derivative-consistent for small, medium, and near-branch-safe rotations.
- `NSNR-ROT-006`: nonfinite rotation components fail with `invalid-accepted-state` or the Step-introduced finite-rotation diagnostic.
Run:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "FiniteRotation" --output-on-failure
```
Expected: tests fail because the rotation module does not exist. Unexpected pass stops.
## GREEN
Implement the SO(3) helper functions with finite checks and principal-branch validation. Use existing FESA math types and keep MKL/vendor types out of public headers.
## VERIFY
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "FiniteRotation" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
Recheck `.harness/config.json` before using default commands.
## 상태 기록
Update only Step 2 status and summary/error/blocked payload. Executor owns timestamps, commits, retry, and advancement.
## 금지사항
- Do not normalize invalid rotation matrices to make tests pass. 이유: invalid nonlinear state must fail closed.
- Do not add element residual or tangent code here. 이유: this Step owns only finite rotation state.
- Do not expose backend/vendor types. 이유: public solver core must remain backend-neutral.
@@ -0,0 +1,124 @@
# Step 3: nonlinear-mitc4-kinematics-and-recovery
## 읽어야 할 파일
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/ADR.md`
- `docs/nonlinear-static-newton-raphson/formulation.md`
- `docs/nonlinear-static-newton-raphson/numerical-review.md`
- `docs/nonlinear-static-newton-raphson/io.md`
- `include/fesa/elements/mitc4_shell.h`
- `src/fesa/elements/mitc4_shell.cpp`
- `tests/unit/elements/mitc4_shell_test.cpp`
- `include/fesa/model/shell_geometry.h`
- `src/fesa/model/shell_geometry.cpp`
- `include/fesa/model/model_types.h`
- `include/fesa/results/result_records.h`
- Step 2 files: `include/fesa/elements/finite_rotation.h`, `src/fesa/elements/finite_rotation.cpp`
## 이전 Step 전제
Step 2 provides finite rotation state and SO(3) Jacobian helpers.
## 소유 파일
- Create: `include/fesa/elements/nonlinear_mitc4_shell.h`
- Create: `src/fesa/elements/nonlinear_mitc4_shell.cpp`
- Create: `tests/unit/elements/nonlinear_mitc4_shell_test.cpp`
- Modify: `src/fesa/CMakeLists.txt`
- Modify: `tests/CMakeLists.txt`
## 인터페이스
```cpp
namespace fesa {
struct NonlinearMitc4TrialDofs {
std::array<Vector3, 4> displacement;
std::array<Vector3, 4> rotation_vector;
};
struct NonlinearMitc4RecoveryPoint {
ShellMidsurfaceLocation location;
std::array<double, 2> natural_coordinates;
Mitc4LocalFrame local_frame;
std::array<double, 8> generalized_strain;
std::array<double, 8> section_resultant;
std::array<ShellSectionStressRow, 3> stress;
};
struct NonlinearMitc4Recovery {
std::array<NonlinearMitc4RecoveryPoint, 4> gauss_points;
double physical_strain_energy;
};
class NonlinearMitc4Shell {
public:
static Result<NonlinearMitc4Shell> Create(
std::array<const Node*, 4> nodes,
std::array<ShellNodeInitialFrame, 4> initial_frames,
const ShellSection& section,
const LinearElasticMaterial& material);
Result<NonlinearMitc4Recovery> EvaluateKinematics(
const NonlinearMitc4TrialDofs& trial) const;
Result<NonlinearMitc4Recovery> RecoverState(
const NonlinearMitc4TrialDofs& trial) const;
};
} // namespace fesa
```
## RED
Add tests first.
- `NSNR-ELEM-KIN-001`: initial undeformed state produces zero generalized strain and zero material stress.
- `NSNR-ELEM-KIN-001A`: `NonlinearMitc4Shell::Create` requires exactly four `Node` pointers, four corresponding `ShellNodeInitialFrame` records, one `ShellSection`, and one `LinearElasticMaterial`; invalid or mismatched inputs fail closed.
- `NSNR-ELEM-KIN-002`: fixed-initial Total Lagrangian geometry uses initial midsurface and initial directors.
- `NSNR-ELEM-KIN-003`: current director interpolation for strain uses unnormalized interpolated director where formulation requires it.
- `NSNR-ELEM-KIN-004`: opposed or degenerate director state fails closed.
- `NSNR-ELEM-KIN-005`: small-rotation linear limit matches existing `Mitc4Shell` recovery within numerical-review tolerance.
- `NSNR-ELEM-KIN-006`: finite rigid translation/rotation produces objective zero physical strain/stress.
- `NSNR-ELEM-KIN-007`: recovery emits exactly four midsurface Gauss rows in `ShellMidsurfaceLocation` order; each row has generalized strain `[E11,E22,2E12,K11,K22,2K12,G13,G23]`, section resultant `[N11,N22,N12,M11,M22,M12,Q13,Q23]`, and stress positions `[BOTTOM,MIDDLE,TOP]` with components `[S11,S22,S12]`.
- `NSNR-ELEM-KIN-008`: recovery emits no S33, S13, S23 and no nodal extrapolated/corner/averaged stress.
Run:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearMitc4Shell|NonlinearMitc4Recovery" --output-on-failure
```
Expected: compile or test failure because nonlinear MITC4 files do not exist. Unexpected pass stops.
## GREEN
Implement nonlinear MITC4 creation, kinematics, and recovery using approved 2x2x2 integration, MITC4 edge-midpoint shear tying, initial shell frames, homogeneous isotropic materially linear response, fixed-size row shapes, and source-order recovery rows.
## VERIFY
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearMitc4Shell|NonlinearMitc4Recovery" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
Recheck `.harness/config.json` before default commands.
## 상태 기록
Update only Step 3 status and one-line result payload. Executor owns timestamps, commits, retry, and advancement.
## 금지사항
- Do not change existing `Mitc4Shell` behavior. 이유: linear MITC4 reference regression must remain stable.
- Do not create stress quantities outside approved schema. 이유: I/O excludes S33/S13/S23 and nodal stress projections.
- Do not add residual/tangent solve logic here. 이유: Step 4 owns residual and tangent.
@@ -0,0 +1,99 @@
# Step 4: nonlinear-mitc4-residual-and-tangent
## 읽어야 할 파일
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/ADR.md`
- `docs/nonlinear-static-newton-raphson/formulation.md`
- `docs/nonlinear-static-newton-raphson/numerical-review.md`
- `include/fesa/elements/nonlinear_mitc4_shell.h`
- `src/fesa/elements/nonlinear_mitc4_shell.cpp`
- `tests/unit/elements/nonlinear_mitc4_shell_test.cpp`
- Step 2 files: `include/fesa/elements/finite_rotation.h`
- Step 3 files: `include/fesa/elements/nonlinear_mitc4_shell.h`
## 이전 Step 전제
Step 3 provides nonlinear MITC4 creation, kinematics, and recovery rows.
## 소유 파일
- Modify: `include/fesa/elements/nonlinear_mitc4_shell.h`
- Modify: `src/fesa/elements/nonlinear_mitc4_shell.cpp`
- Create: `tests/unit/elements/nonlinear_mitc4_shell_tangent_test.cpp`
- Modify: `tests/CMakeLists.txt`
## 인터페이스
```cpp
namespace fesa {
struct NonlinearMitc4InternalEquilibrium {
Vector internal_residual;
Matrix internal_tangent;
Vector internal_force;
NonlinearMitc4Recovery recovery;
double physical_strain_energy;
};
Result<NonlinearMitc4InternalEquilibrium> EvaluateInternalResidualAndTangent(
const NonlinearMitc4Shell& element,
const NonlinearMitc4TrialDofs& trial);
} // namespace fesa
```
## RED
Add tests first.
- `NSNR-ELEM-TAN-001`: residual and tangent are evaluated at the same trial state.
- `NSNR-ELEM-TAN-002`: directional derivative schedule uses `h={1e-3,3e-4,1e-4,3e-5,1e-5,3e-6,1e-6,3e-7}` and requires at least two observed orders `>=1.5`.
- `NSNR-ELEM-TAN-003`: one interior scaled derivative error is `<=1e-6`.
- `NSNR-ELEM-TAN-004`: zero analytical directions produce scaled absolute error `<=1e-10`.
- `NSNR-ELEM-TAN-005`: no-moment tangent satisfies scaled Frobenius symmetry `<=1e-12` after `Dq^T K_T Dq`.
- `NSNR-ELEM-TAN-006`: internal tangent contains no external fixed-load contribution; applying the same element to two incident-element load-sharing scenarios does not change internal residual/tangent.
- `NSNR-ELEM-TAN-007`: finite rigid motion keeps physical residual/objectivity invariants.
- `NSNR-ELEM-TAN-008`: patch/sign recovery identities pass relative `<=1e-9`.
- `NSNR-ELEM-TAN-009`: nonfinite trial/residual/tangent data fails closed.
Run:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearMitc4Residual|NonlinearMitc4Tangent|NonlinearMitc4Objectivity|NonlinearMitc4Patch" --output-on-failure
```
Expected: new tests fail. Unexpected pass stops.
## GREEN
Implement same-trial internal residual/tangent, exact rotation derivative terms through `J_l`, map Hessian terms, objective drilling contribution, and physical recovery in the element-local contract. External CLOAD vector and tangent are not element inputs and are assembled globally in Step 5.
## VERIFY
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearMitc4Residual|NonlinearMitc4Tangent|NonlinearMitc4Objectivity|NonlinearMitc4Patch" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
Recheck `.harness/config.json` before default commands.
## 상태 기록
Update only Step 4 status and one-line payload. Executor owns timestamps, commits, retry, and advancement.
## 금지사항
- Do not symmetrize the internal tangent as a cleanup step. 이유: derivative and symmetry tests must observe the mathematically assembled operator; Step 5 owns external moment tangent nonsymmetry.
- Do not add solver factorization in this Step. 이유: Step 6 owns tangent solver backend.
- Do not pass nodal CLOAD force or moment into each incident element. 이유: it duplicates source-node loads; Step 5 owns global-once external assembly.
- Do not claim Abaqus internal equivalence. 이유: FESA only uses Abaqus CSV as external numeric reference.
@@ -0,0 +1,179 @@
# Step 5: load-boundary-path-and-convergence-gates
## 읽어야 할 파일
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/nonlinear-static-newton-raphson/requirements.md`
- `docs/nonlinear-static-newton-raphson/formulation.md`
- `docs/nonlinear-static-newton-raphson/numerical-review.md`
- `docs/nonlinear-static-newton-raphson/io.md`
- `include/fesa/core/diagnostic.h`
- `include/fesa/fem/dof_manager.h`
- `include/fesa/math/sparse_matrix.h`
- `include/fesa/math/vector3.h`
- Step 1 files: `include/fesa/model/domain.h`, `include/fesa/model/model_types.h`
- Step 4 files: `include/fesa/elements/nonlinear_mitc4_shell.h`
## 이전 Step 전제
Step 1 stores raw nonlinear static values in Domain. Step 4 provides internal-only element residual/tangent. This Step owns global-once fixed CLOAD, absolute boundary path assembly, and the external contribution sign contract used by Step 10: `r = f_int - f_ext`, `K_T = K_int - K_ext`.
## 소유 파일
- Create: `include/fesa/analysis/nonlinear_static_control.h`
- Create: `src/fesa/analysis/nonlinear_static_control.cpp`
- Create: `tests/unit/analysis/nonlinear_static_control_test.cpp`
- Modify: `src/fesa/CMakeLists.txt`
- Modify: `tests/CMakeLists.txt`
## 인터페이스
```cpp
namespace fesa {
struct LoadFactorIncrement {
std::uint32_t increment_index;
double load_factor;
bool is_factor_one;
};
enum class NewtonChannelUnit {
kForce,
kForceLength,
kLength,
kDimensionless
};
inline constexpr double kNewtonRelativeThreshold = 1.0e-2;
inline constexpr double kNewtonZeroScaleThreshold = 1.0e-3;
struct NewtonResidualChannelInput {
std::string identity;
Vector free_residual;
Vector free_internal;
Vector free_external;
};
struct NewtonCorrectionChannelInput {
std::string identity;
Vector fresh_correction;
Vector current_increment;
};
struct NewtonConvergenceInput {
NewtonResidualChannelInput force_residual;
NewtonResidualChannelInput moment_residual;
NewtonCorrectionChannelInput translation_correction;
NewtonCorrectionChannelInput rotation_correction;
};
enum class NewtonConvergenceBranch {
kRelative,
kZeroScale
};
struct NewtonChannelDecision {
std::string identity;
double numerator_norm;
double denominator_scale;
NewtonConvergenceBranch branch;
double threshold;
NewtonChannelUnit threshold_unit;
bool passed;
};
struct NewtonConvergenceResult {
NewtonChannelDecision force_residual;
NewtonChannelDecision moment_residual;
NewtonChannelDecision translation_correction;
NewtonChannelDecision rotation_correction;
bool combined_passed;
};
struct GlobalFixedLoadContribution {
Vector generalized_external_force;
SparseMatrix generalized_external_tangent;
std::vector<Diagnostic> diagnostics;
};
Result<std::vector<LoadFactorIncrement>> BuildFixedLoadFactorSequence(
const StepDefinition& step);
Result<Vector> ApplyAbsolutePrescribedDisplacementTarget(
const StepDefinition& step, double load_factor);
Result<GlobalFixedLoadContribution> AssembleGlobalFixedLoadContribution(
const Domain& domain,
const DofManager& dofs,
const Vector& trial_generalized_coordinates,
double load_factor);
Status CheckMomentAdmissibility(const Vector3& director,
const Vector3& source_moment);
NewtonConvergenceResult EvaluateNewtonConvergence(
const NewtonConvergenceInput& input);
} // namespace fesa
```
## RED
Add tests first.
- `NSNR-CTRL-001`: raw `initial_increment=0.1` and `time_period=1.0` produce factors `0.1..1.0`.
- `NSNR-CTRL-002`: final factor is assigned exact `1.0`, not accumulated past one.
- `NSNR-CTRL-003`: positive minimum/maximum values are accepted but not used for runtime stepping.
- `NSNR-CTRL-004`: nonzero `*BOUNDARY` value is absolute factor-one target from step-start committed state.
- `NSNR-CTRL-005`: constrained Newton correction remains exactly zero.
- `NSNR-CTRL-006`: `*CLOAD` force and moment directions remain fixed global dead loads and scale by factor.
- `NSNR-CTRL-007`: moment admissibility accepts `rho_M <= 1e-12` and rejects larger drilling-aligned moment.
- `NSNR-CTRL-008`: `AssembleGlobalFixedLoadContribution` contributes each aggregated source node load exactly once, independent of incident element count.
- `NSNR-CTRL-009`: source-order load rows produce deterministic global vector and tangent order.
- `NSNR-CTRL-010`: fixed-spatial moment projection and `J_l`/map-Hessian tangent terms pass directional derivative checks and may make `K_ext` nonsymmetric.
- `NSNR-CTRL-011`: global fixed-load assembly returns external quantities consumed with approved signs `r = f_int - f_ext` and `K_T = K_int - K_ext`; the fixed-moment tangent is subtracted once and is never duplicated per incident element.
- `NSNR-CTRL-012`: `NewtonConvergenceInput` has no caller-supplied threshold or unit fields; `EvaluateNewtonConvergence` owns exact immutable constants `kNewtonRelativeThreshold=1.0e-2` and `kNewtonZeroScaleThreshold=1.0e-3`.
- `NSNR-CTRL-013`: four convergence gates expose identity, `numerator_norm`, `denominator_scale`, branch, typed threshold, `threshold_unit`, pass, and combined pass.
- `NSNR-CTRL-014`: nonzero-denominator relative branches use threshold `1.0e-2` and threshold unit `kDimensionless` for all four channels.
- `NSNR-CTRL-015`: zero-scale branches use threshold `1.0e-3` with units `kForce`, `kForceLength`, `kLength`, and `kDimensionless` for residual force, residual moment, correction translation, and correction rotation respectively.
- `NSNR-CTRL-016`: empty free channels evaluate with `numerator_norm=0`, `denominator_scale=0`, branch `kZeroScale`, exact zero-scale threshold/unit, and pass.
- `NSNR-CTRL-017`: iteration numbering remains `1..100` with no iteration `101`.
Run:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearStaticControl|NonlinearConvergence|NonlinearLoadBoundary" --output-on-failure
```
Expected: new tests fail. Unexpected pass stops.
## GREEN
Implement factor sequence derivation, load/BC scaling, global-once fixed-load external vector `f_ext` and external tangent `K_ext` assembly, moment admissibility, and typed convergence-channel evaluation. Residual denominator scale is derived from the provided free internal/external channel values, correction denominator scale from the provided current increment channel values, and zero-scale branches use only `kZeroScale`. Thresholds and threshold units are not caller inputs; `EvaluateNewtonConvergence` assigns the exact branch/channel constants from `requirements.md` and `io.md`. Return structured diagnostics for invalid nonlinear static data, unsupported drilling load, nonfinite Newton data, and nonconvergence readiness.
## VERIFY
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearStaticControl|NonlinearConvergence|NonlinearLoadBoundary" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
Recheck `.harness/config.json` before default commands.
## 상태 기록
Update only Step 5 status and one-line payload. Executor owns timestamps, commits, retry, and advancement.
## 금지사항
- Do not put factor sequence storage into Domain. 이유: control layer derives runtime factors.
- Do not implement cutback, line search, damping, or automatic growth. 이유: approved Full Newton path excludes them.
- Do not mutate source load vectors. 이유: fixed-global source identity must remain immutable.
- Do not distribute full source-node CLOAD into every incident element. 이유: this would multiply loads by incident element count and violate formulation §11.2.
- Do not emit convergence branch labels outside `relative` and `zero-scale`. 이유: io.md exposes exactly those two branches.
- Do not accept per-call convergence thresholds or units. 이유: NSNR-069/070 fix branch thresholds and typed units globally.
@@ -0,0 +1,105 @@
# Step 6: backend-neutral-tangent-solver
## 읽어야 할 파일
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/ADR.md`
- `docs/nonlinear-static-newton-raphson/formulation.md`
- `docs/nonlinear-static-newton-raphson/numerical-review.md`
- `include/fesa/solvers/linear/linear_solver.h`
- `include/fesa/solvers/linear/mkl_pardiso_solver.h`
- `src/fesa/solvers/linear/mkl_pardiso_solver.cpp`
- `tests/unit/solvers/linear/linear_solver_test.cpp`
- `tests/unit/solvers/linear/mkl_pardiso_solver_test.cpp`
- Step 4 files: `include/fesa/elements/nonlinear_mitc4_shell.h`
## 이전 Step 전제
Step 4 defines generally nonsymmetric nonlinear tangent matrices. Current SPD `MklPardisoSolver` must remain unchanged.
## 소유 파일
- Create: `include/fesa/solvers/linear/tangent_solver.h`
- Create: `include/fesa/solvers/linear/mkl_pardiso_tangent_solver.h`
- Create: `src/fesa/solvers/linear/mkl_pardiso_tangent_solver.cpp`
- Create: `tests/unit/solvers/linear/tangent_solver_test.cpp`
- Test: `tests/unit/solvers/linear/mkl_pardiso_solver_test.cpp`
- Modify: `src/fesa/CMakeLists.txt`
- Modify: `tests/CMakeLists.txt`
## 인터페이스
```cpp
namespace fesa {
class TangentSolver {
public:
virtual ~TangentSolver() = default;
virtual Status FactorizeFresh(const SparseMatrix& matrix) = 0;
virtual Status SolveCorrection(const Vector& rhs,
Vector& correction) const = 0;
};
class MklPardisoTangentSolver final : public TangentSolver {
public:
MklPardisoTangentSolver();
~MklPardisoTangentSolver() override;
Status FactorizeFresh(const SparseMatrix& matrix) override;
Status SolveCorrection(const Vector& rhs, Vector& correction) const override;
};
} // namespace fesa
```
The MKL tangent adapter uses unsymmetric PARDISO full CSR with `mtype=11`. Existing `MklPardisoSolver` keeps `mtype=2`, SPD symmetry validation, and upper-triangle copy unchanged.
## RED
Add tests first.
- `NSNR-SOLVER-001`: nonsymmetric 2x2 matrix factorizes and solves a known correction.
- `NSNR-SOLVER-002`: `0x0` matrix factorize/solve succeeds with zero-size vectors.
- `NSNR-SOLVER-003`: nonfinite matrix or RHS values fail without modifying output correction.
- `NSNR-SOLVER-004`: singular/factorization failure maps to deterministic solver diagnostic.
- `NSNR-SOLVER-005`: solve before successful factorization fails with solver diagnostic.
- `NSNR-SOLVER-006`: existing SPD `MklPardisoSolver` still rejects nonsymmetric public CSR and passes prior tests.
Run:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "TangentSolver|MklPardiso" --output-on-failure
```
Expected: new tests fail. Unexpected pass stops.
## GREEN
Implement backend-neutral tangent solver boundary and MKL full-CSR adapter. Factorization is fresh per call, releases previous backend state, validates dimensions and finite values, and translates PARDISO errors to FESA `Status`.
## VERIFY
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "TangentSolver|MklPardiso" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
Recheck `.harness/config.json` before default commands.
## 상태 기록
Update only Step 6 status and one-line payload. Executor owns timestamps, commits, retry, and advancement.
## 금지사항
- Do not modify SPD `MklPardisoSolver` behavior except tests proving it is unchanged. 이유: existing linear solver contract must remain stable.
- Do not symmetrize or regularize tangent matrices. 이유: approved tangent may be nonsymmetric and failures must surface.
- Do not expose MKL types in public headers. 이유: backend types remain behind solver boundaries.
@@ -0,0 +1,132 @@
# Step 7: newton-iteration-log-writer
## 읽어야 할 파일
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/nonlinear-static-newton-raphson/io.md`
- `docs/nonlinear-static-newton-raphson/formulation.md`
- `docs/nonlinear-static-newton-raphson/implementation-plan.md`
- Step 0 files: `include/fesa/app/output_paths.h`, `src/fesa/app/output_paths.cpp`, `include/fesa/analysis/analysis.h`
- Step 5 files: `include/fesa/analysis/nonlinear_static_control.h`
## 이전 Step 전제
Step 0 gives app-owned log path and lease lifetime. Step 5 defines convergence-channel data that log records consume.
## 소유 파일
- Create: `include/fesa/analysis/newton_iteration_log.h`
- Create: `src/fesa/analysis/newton_iteration_log.cpp`
- Create: `tests/unit/analysis/newton_iteration_log_test.cpp`
- Modify: `src/fesa/CMakeLists.txt`
- Modify: `tests/CMakeLists.txt`
## 인터페이스
```cpp
namespace fesa {
enum class NewtonIterationStage {
kEntered,
kKinematicsEvaluated,
kInternalAssembled,
kExternalAssembled,
kSolvedCorrection,
kConvergenceEvaluated,
kAccepted,
kFailed
};
struct NewtonIterationFailure {
bool present;
NewtonIterationStage stage;
std::string diagnostic_code;
std::string reason;
};
struct NewtonIterationLogRecord {
std::string step_name;
std::uint32_t increment_index;
double load_factor;
std::uint32_t iteration;
NewtonIterationStage last_completed_stage;
NewtonIterationFailure failure;
std::string transition;
std::string outcome;
std::string diagnostic_code;
NewtonChannelDecision force_residual;
NewtonChannelDecision moment_residual;
NewtonChannelDecision translation_correction;
NewtonChannelDecision rotation_correction;
bool combined_passed;
};
class NewtonIterationLogWriter {
public:
Status Open(const std::filesystem::path& log_path);
Status Write(const NewtonIterationLogRecord& record);
Status Flush();
Status Close();
};
} // namespace fesa
```
The serializer, not callers, owns the exact 39 labels and fixed order from `io.md`. For evaluated convergence channels it serializes only `relative` or `zero-scale` branch labels from `NewtonConvergenceBranch`. For unevaluated typed fields it writes `not-evaluated(reason)` using `NewtonIterationFailure::reason`. Callers cannot inject arbitrary fields.
## RED
Add tests first.
- `NSNR-LOG-001`: a complete Newton iteration writes exactly one LF-terminated line.
- `NSNR-LOG-002`: line is UTF-8 without BOM, no header, no trailer.
- `NSNR-LOG-003`: line has exactly 39 TAB-separated `label=value` fields in approved order.
- `NSNR-LOG-004`: finite numeric values round-trip without locale dependence.
- `NSNR-LOG-005`: each convergence channel serializes identity, `numerator_norm`, `denominator_scale`, branch, threshold, `threshold_unit`, and pass from `NewtonChannelDecision`.
- `NSNR-LOG-006`: evaluated empty free-channel decisions serialize `numerator_norm=0`, `denominator_scale=0`, threshold `1.0e-3`, the channel-specific zero-scale `threshold_unit`, and branch `zero-scale`; no other branch label appears.
- `NSNR-LOG-007`: combined pass, transition, outcome, failure stage, and diagnostic code are serialized from explicit record fields.
- `NSNR-LOG-008`: early failure fields after the failed stage use `not-evaluated(reason)`.
- `NSNR-LOG-009`: embedded tab, LF, CR, and backslash are escaped deterministically.
- `NSNR-LOG-010`: write/flush/close failure returns `log-output-failure` and prevents success classification.
- `NSNR-LOG-011`: non-Newton controlled failure path can leave a zero-byte log owned by app lifecycle.
Run:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NewtonIterationLog" --output-on-failure
```
Expected: new tests fail. Unexpected pass stops.
## GREEN
Implement a small log writer with explicit open/write/flush/close state, serializer-owned 39-field order, deterministic escaping, finite numeric formatting, `relative`/`zero-scale` convergence branch mapping, typed `not-evaluated(reason)` substitution, and structured `Status` failures.
## VERIFY
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NewtonIterationLog" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
Recheck `.harness/config.json` before default commands.
## 상태 기록
Update only Step 7 status and one-line payload. Executor owns timestamps, commits, retry, and advancement.
## 금지사항
- Do not duplicate per-iteration log lines to stderr. 이유: approved log is file-only.
- Do not accept arbitrary caller-provided field vectors. 이유: io.md requires an exact fixed schema and order.
- Do not write HDF5 trial history. 이유: log is diagnostic and HDF5 stores accepted frames only.
- Do not continue after log write/flush/close failure. 이유: log failure blocks result commit.
@@ -0,0 +1,169 @@
# Step 8: nonlinear-state-commit-rollback
## 읽어야 할 파일
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/ADR.md`
- `docs/nonlinear-static-newton-raphson/requirements.md`
- `docs/nonlinear-static-newton-raphson/formulation.md`
- `docs/nonlinear-static-newton-raphson/io.md`
- `include/fesa/analysis/analysis_state.h`
- `src/fesa/analysis/analysis_state.cpp`
- `include/fesa/core/source_identity.h`
- `include/fesa/fem/dof_manager.h`
- `include/fesa/model/domain.h`
- `include/fesa/results/result_records.h`
- Step 3 files: `include/fesa/elements/nonlinear_mitc4_shell.h`
- Step 4 files: `include/fesa/elements/nonlinear_mitc4_shell.h`
- Step 5 files: `include/fesa/analysis/nonlinear_static_control.h`
- Step 6 files: `include/fesa/solvers/linear/tangent_solver.h`
- Step 7 files: `include/fesa/analysis/newton_iteration_log.h`
## 이전 Step 전제
Steps 3-7 provide nonlinear element recovery, residual/tangent, controls, tangent solver, and Newton log record types.
## 소유 파일
- Create: `include/fesa/analysis/nonlinear_analysis_state.h`
- Create: `src/fesa/analysis/nonlinear_analysis_state.cpp`
- Create: `tests/unit/analysis/nonlinear_analysis_state_test.cpp`
- Modify: `src/fesa/CMakeLists.txt`
- Modify: `tests/CMakeLists.txt`
## 인터페이스
```cpp
namespace fesa {
struct NonlinearNodeBaseline {
EntityIndex node_index;
SourceEntityId source_id;
std::array<std::array<double, 3>, 3> triad;
std::array<double, 3> director;
};
struct NonlinearStateBaselineInput {
std::vector<NonlinearNodeBaseline> node_baselines;
};
struct NonlinearTrialState {
std::uint32_t increment_index;
double load_factor;
bool is_factor_zero_baseline;
std::vector<EntityIndex> nodal_internal_order;
std::vector<SourceEntityId> nodal_source_ids;
Vector generalized_coordinates;
Vector displacement;
Vector rotation_vector;
std::vector<std::array<std::array<double, 3>, 3>> nodal_triad;
std::vector<std::array<double, 3>> nodal_director;
Vector internal_force;
Vector external_force;
Vector residual;
Vector reaction;
std::vector<ShellResultRow> shell_results;
double physical_strain_energy;
std::array<double, 6> global_equilibrium;
};
struct AcceptedFrameState {
std::uint32_t frame_index;
std::uint32_t increment_index;
double load_factor;
std::uint32_t iteration_count;
bool is_factor_one;
std::vector<EntityIndex> nodal_internal_order;
std::vector<SourceEntityId> nodal_source_ids;
Vector generalized_coordinates;
Vector displacement;
Vector rotation_vector;
std::vector<std::array<std::array<double, 3>, 3>> nodal_triad;
std::vector<std::array<double, 3>> nodal_director;
Vector internal_force;
Vector external_force;
Vector residual;
Vector reaction;
std::vector<ShellResultRow> shell_results;
double physical_strain_energy;
std::array<double, 6> global_equilibrium;
};
class NonlinearAnalysisState {
public:
static Result<NonlinearAnalysisState> Create(
const Domain& domain,
const DofManager& dofs,
const NonlinearStateBaselineInput& baseline);
Result<NonlinearTrialState> BeginTrialFromCommitted() const;
Status CommitAcceptedFrame(const AcceptedFrameState& frame);
void DiscardTrial() noexcept;
const std::vector<AcceptedFrameState>& AcceptedFrames() const noexcept;
const NonlinearTrialState& CommittedState() const noexcept;
};
} // namespace fesa
```
## RED
Add tests first.
- `NSNR-STATE-000`: `NonlinearAnalysisState::Create` requires exactly one `NonlinearNodeBaseline` per `Domain.Nodes()` entry in stable internal order; each entry has `node_index` equal to its vector offset and `source_id` equal to `Domain.Nodes()[node_index].source_id`.
- `NSNR-STATE-001`: `NonlinearAnalysisState::Create` rejects missing, extra, duplicate, out-of-order, or instance-label mismatched node identities before any mutable state is produced.
- `NSNR-STATE-002`: `NonlinearAnalysisState::Create` validates node/DOF/frame inventory, sizes all vectors, preserves `nodal_internal_order` and `nodal_source_ids`, and creates a committed factor-zero baseline with `increment_index=0`, `load_factor=0.0`, `is_factor_zero_baseline=true`, and no accepted frames.
- `NSNR-STATE-003`: duplicate source labels across different instances remain distinct when `SourceEntityId::instance_name` differs.
- `NSNR-STATE-004`: trial state starts from committed state and can be discarded without mutation.
- `NSNR-STATE-005`: accepted frames are appended in stable accepted-factor order.
- `NSNR-STATE-006`: frame index is zero-based accepted-frame order; the factor-zero committed baseline is metadata/state only and is never appended as an accepted frame.
- `NSNR-STATE-007`: final frame at load factor `1.0` has `is_factor_one=true`.
- `NSNR-STATE-008`: residual and reaction are stored from full residual `r = f_int - f_ext`.
- `NSNR-STATE-009`: accepted frame owns generalized coordinates, displacement, rotation vector, per-node identity/order, per-node triad/director, internal/external/residual/reaction, stable `ShellResultRow` recovery, physical strain energy, and global equilibrium.
- `NSNR-STATE-010`: all accepted-frame vectors match node/DOF counts and all `ShellResultRow` rows are stable element/midsurface order.
- `NSNR-STATE-011`: commit validates finite values for every approved scalar and atomically rejects nonfinite frames with `invalid-accepted-state`.
- `NSNR-STATE-012`: failed trial preserves prior committed state.
- `NSNR-STATE-013`: no unapproved verification data is stored.
- `NSNR-STATE-014`: committed and accepted nodal identity vectors are preserved for Step 9 HDF5 node datasets and Step 11 HDF5-to-reference source mapping; source label alone is never used as identity.
Run:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearAnalysisState|CommitRollback" --output-on-failure
```
Expected: new tests fail. Unexpected pass stops.
## GREEN
Implement `NonlinearAnalysisState::Create` as the only construction seam for a valid nonlinear state. It uses `Domain`, `DofManager`, and `NonlinearStateBaselineInput` to require one baseline per Domain node in stable internal order, match both `EntityIndex` and full `SourceEntityId`, allocate zero generalized/displacement/rotation/internal/external/residual/reaction vectors, preserve nodal order/source identity for HDF5/reference mapping, install finite per-node triads/directors, and establish the committed factor-zero baseline without adding an accepted frame. Commit validates inventory, order, shape, identity, and finiteness before mutating committed state.
## VERIFY
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearAnalysisState|CommitRollback" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
Recheck `.harness/config.json` before default commands.
## 상태 기록
Update only Step 8 status and one-line payload. Executor owns timestamps, commits, retry, and advancement.
## 금지사항
- Do not put equation ids into Node or Element objects. 이유: `DofManager` owns equation numbering.
- Do not identify baseline nodes by source label alone. 이유: labels are not unique across instances and cannot support stable HDF5/reference mapping.
- Do not commit trial data before convergence gates pass. 이유: rollback/preservation contract depends on committed-state isolation.
- Do not expand existing `AnalysisState` for future nonlinear features. 이유: this Step owns only approved NSNR state.
- Do not store stress projections or verification data outside approved `ShellResultRow`, physical energy, and global equilibrium fields. 이유: schema v1 has a bounded accepted-state contract.
@@ -0,0 +1,118 @@
# Step 9: nonlinear-hdf5-schema-v1-writer
## 읽어야 할 파일
- `AGENTS.md`
- `docs/ARCHITECTURE.md`
- `docs/ADR.md`
- `docs/nonlinear-static-newton-raphson/io.md`
- `docs/nonlinear-static-newton-raphson/reference-model.md`
- `include/fesa/io/hdf5/hdf5_results_writer.h`
- `include/fesa/results/results_writer.h`
- `src/fesa/io/hdf5/hdf5_results_writer.cpp`
- `src/fesa/io/hdf5/hdf5_primitives.h`
- `src/fesa/io/hdf5/hdf5_primitives.cpp`
- `src/fesa/io/hdf5/hdf5_self_check.h`
- `src/fesa/io/hdf5/hdf5_self_check.cpp`
- `tests/unit/io/hdf5/hdf5_results_writer_test.cpp`
- Step 8 files: `include/fesa/analysis/nonlinear_analysis_state.h`
## 이전 Step 전제
Step 8 provides committed nonlinear accepted-frame state. Step 0 and Step 10 own the rule that log close succeeds before this writer is invoked.
## 소유 파일
- Create: `include/fesa/io/hdf5/nonlinear_hdf5_results_writer.h`
- Create: `src/fesa/io/hdf5/nonlinear_hdf5_results_writer.cpp`
- Create: `include/fesa/results/nonlinear_results_writer.h`
- Create: `tests/unit/io/hdf5/nonlinear_hdf5_results_writer_test.cpp`
- Modify: `src/fesa/io/hdf5/hdf5_primitives.h`
- Modify: `src/fesa/io/hdf5/hdf5_primitives.cpp`
- Modify: `src/fesa/io/hdf5/hdf5_self_check.h`
- Modify: `src/fesa/io/hdf5/hdf5_self_check.cpp`
- Modify: `src/fesa/CMakeLists.txt`
- Modify: `tests/CMakeLists.txt`
## 인터페이스
```cpp
namespace fesa {
class NonlinearResultsWriter {
public:
virtual ~NonlinearResultsWriter() = default;
virtual Status Write(const std::filesystem::path& output_path,
const Domain& domain,
const NonlinearAnalysisState& state,
const std::vector<Diagnostic>& diagnostics) = 0;
};
class NonlinearHdf5ResultsWriter final : public NonlinearResultsWriter {
public:
Status Write(const std::filesystem::path& output_path,
const Domain& domain,
const NonlinearAnalysisState& state,
const std::vector<Diagnostic>& diagnostics) override;
};
} // namespace fesa
```
## RED
Add tests first.
- `NSNR-HDF5-001`: `/metadata` has `schema_version=1`, `feature_id=nonlinear-static-newton-raphson`, and approved procedure/formulation/integration attributes.
- `NSNR-HDF5-002`: `/model/nodes`, `/model/elements`, shell section/material/frame/director/constraint datasets match I/O schema.
- `NSNR-HDF5-003`: `/steps/Step-1/accepted_frames` has one row per committed increment.
- `NSNR-HDF5-004`: final row has load factor `1.0` and `is_factor_one=1`.
- `NSNR-HDF5-005`: factor zero metadata exists and factor zero is not a frame.
- `NSNR-HDF5-006`: per-frame nodal displacement, triad, director, internal, external, residual, reaction datasets have approved shapes and column ordering.
- `NSNR-HDF5-007`: reaction rotational columns store `J_l(phi)^(-T)` spatial physical moment.
- `NSNR-HDF5-008`: element shell local frame, generalized strain, section resultant, stress datasets exist with approved shape and identity.
- `NSNR-HDF5-009`: `/global/energy` and `/global/equilibrium` exist for each accepted frame.
- `NSNR-HDF5-010`: no trial history dataset is written.
- `NSNR-HDF5-011`: HDF5 candidate failure preserves prior final file.
- `NSNR-HDF5-012`: writer consumes `std::vector<Diagnostic>` and writes deterministic diagnostics.
- `NSNR-HDF5-013`: `NonlinearHdf5ResultsWriter` implements backend-neutral `NonlinearResultsWriter`, and no HDF5 handle/type appears in analysis/core public headers.
Run:
```powershell
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearHdf5|Hdf5ResultsWriter" --output-on-failure
```
Expected: new tests fail. Unexpected pass stops.
## GREEN
Implement `include/fesa/results/nonlinear_results_writer.h` as the backend-neutral boundary and implement schema-v1 nonlinear HDF5 writer using existing HDF5 RAII/primitives/self-check style. Keep HDF5 and platform types private to `src/fesa/io/hdf5`.
## VERIFY
```powershell
cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 `
"-DFESA_GTEST_SOURCE_DIR=$gtestSource" `
"-DMKL_DIR=$mklDir" `
"-DTBB_DIR=$tbbDir" `
"-DHDF5_DIR=$hdf5Dir"
cmake --build .harness/build --config Debug
ctest --test-dir .harness/build -C Debug -R "NonlinearHdf5|Hdf5ResultsWriter" --output-on-failure
ctest --test-dir .harness/build -C Debug --show-only=json-v1
ctest --test-dir .harness/build -C Debug --output-on-failure
```
Recheck `.harness/config.json` before default commands.
## 상태 기록
Update only Step 9 status and one-line payload. Executor owns timestamps, commits, retry, and advancement.
## 금지사항
- Do not open, write, or replace HDF5 before log close succeeds. 이유: Step 10 app lifecycle enforces log-before-HDF5 finalization.
- Do not write compression, chunking, or trial history. 이유: io.md requires contiguous accepted-state storage.
- Do not make CSV an authoritative output. 이유: official solver output is HDF5.
- Do not include HDF5 headers from analysis/core public headers. 이유: output backend types stay behind writer interfaces.