# 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, 3> triad; std::array director; }; struct NonlinearStateBaselineInput { std::vector node_baselines; }; struct NonlinearTrialState { std::uint32_t increment_index; double load_factor; bool is_factor_zero_baseline; std::vector nodal_internal_order; std::vector nodal_source_ids; Vector generalized_coordinates; Vector displacement; Vector rotation_vector; std::vector, 3>> nodal_triad; std::vector> nodal_director; Vector internal_force; Vector external_force; Vector residual; Vector reaction; std::vector shell_results; double physical_strain_energy; std::array 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 nodal_internal_order; std::vector nodal_source_ids; Vector generalized_coordinates; Vector displacement; Vector rotation_vector; std::vector, 3>> nodal_triad; std::vector> nodal_director; Vector internal_force; Vector external_force; Vector residual; Vector reaction; std::vector shell_results; double physical_strain_energy; std::array global_equilibrium; }; class NonlinearAnalysisState { public: static Result Create( const Domain& domain, const DofManager& dofs, const NonlinearStateBaselineInput& baseline); Result BeginTrialFromCommitted() const; Status CommitAcceptedFrame(const AcceptedFrameState& frame); void DiscardTrial() noexcept; const std::vector& 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.