# Step 11: equilibrium-projection-and-reference-gate > 과거 개발 계획이다. 아래 작업·상태·예시는 현재 실행 지시가 아니다. > 현재 구현과 검증 한계는 [기능 요약](../../docs/nonlinear-static-newton-raphson/README.md)을 따른다. ## 목적 USER-DECISION-004 option 1과 `AUDIT-001`을 구현·검증한다. 같은 accepted factor-one state에서 accepted free residual, constrained reaction resultant, global applied-plus-reaction balance error를 서로 다른 quantity로 재구성하고, 기존 schema-v1 `/global/equilibrium`에는 balance error만 저장한다. Fine nonlinear case는 primary, coarse nonlinear case는 companion으로 검증한다. Local nodal `RF1/RF2/RF3`와 physical spatial `RM1/RM2`는 deterministic review-only evidence이고 blocking verdict를 바꾸지 않는다. Global force와 moment balance는 각각 fixed normalized `<=1e-8` comparator gate를 사용하며 runtime configuration 또는 product solver acceptance로 노출하지 않는다. ## 읽어야 할 파일 - `AGENTS.md` - `../../docs/solver-core/README.md` - `../../docs/solver-core/README.md` - `../../docs/solver-core/README.md` - `../../docs/solver-core/README.md` - `../../docs/solver-core/README.md` - `../../docs/nonlinear-static-newton-raphson/README.md` - `../../docs/nonlinear-static-newton-raphson/README.md` - `../../docs/nonlinear-static-newton-raphson/formulation.md` §§11.4, 13.1, 13.4, 14.1 - `../../docs/nonlinear-static-newton-raphson/README.md` §§3.2, 6.2, 6.3 - `../../docs/nonlinear-static-newton-raphson/README.md` §§3–6 - `../../docs/nonlinear-static-newton-raphson/README.md` §§7.3–7.5.1, 8.1–8.7 - `../../docs/nonlinear-static-newton-raphson/README.md` - `../../docs/nonlinear-static-newton-raphson/README.md` §13 - `phases/nonlinear-static-newton-raphson/index.json` - `include/fesa/analysis/nonlinear_analysis_state.h` - `include/fesa/analysis/nonlinear_static_procedure.h` - `include/fesa/analysis/nonlinear_static_control.h` - `src/fesa/analysis/nonlinear_static_procedure.cpp` - `src/fesa/analysis/nonlinear_static_control.cpp` - `include/fesa/results/nonlinear_results_writer.h` - `include/fesa/io/hdf5/nonlinear_hdf5_results_writer.h` - `src/fesa/io/hdf5/nonlinear_hdf5_results_writer.cpp` - `tests/integration/analysis/nonlinear_static_procedure_test.cpp` - `tests/unit/io/hdf5/nonlinear_hdf5_results_writer_test.cpp` - `tests/unit/analysis/nonlinear_static_control_test.cpp` - `tests/unit/analysis/newton_iteration_log_test.cpp` - `tests/reference/reference_tolerance_policy.h` - `tests/reference/reference_tolerance_policy.cpp` - `tests/reference/nonlinear_static_reference_comparison.h` - `tests/reference/nonlinear_static_reference_comparison.cpp` - `tests/reference/nonlinear_static_reference_cases_test.cpp` - `tests/CMakeLists.txt` - 과거 검증 자료(정리됨) - `CMakePresets.json` ## 이전 Step과 보존 전제 - Steps 0–10은 completed다. 그 status, summary, timestamp, commit 또는 구현을 replay, amend, reset하거나 다시 작성하지 않는다. - 현재 worktree의 `NSNR-CTRL-018..021`, `NSNR-LOG-012`, `NSNR-PROC-019`, `NSNR-HDF5-014`, residual-selector GREEN과 `NSNR-REF-001..023` comparator/CMake 작업은 이전 blocked Step 11에서 생성된 보존 대상이다. 삭제·재생성·reset하지 말고 현재 내용에서 계속한다. - 현재 comparator의 shared `<=1e-10` global force/moment gate는 이전 blocker를 재현하는 보존된 implementation state다. 새 RED가 approved between-threshold metric을 거부하는 것을 증명한 뒤 comparator 내부 gate만 `<=1e-8`로 변경한다. - Selector RED는 이미 과거 실행 evidence에 속한다. 이 재실행에서 selector RED를 fabricate하거나 다시 실패시키지 않는다. 기존 테스트가 계속 green인지 focused VERIFY에서 확인한다. - Step 12는 pending이며 이 Step에서 시작하지 않는다. ## 소유 파일 RED test를 먼저 추가한 뒤 필요한 production file만 최소 수정한다. - Modify test first: `tests/integration/analysis/nonlinear_static_procedure_test.cpp` - Modify test first: `tests/unit/io/hdf5/nonlinear_hdf5_results_writer_test.cpp` - Preserve and extend test-private comparator: `tests/reference/nonlinear_static_reference_comparison.h` - Preserve and extend test-private comparator: `tests/reference/nonlinear_static_reference_comparison.cpp` - Preserve and extend tests: `tests/reference/nonlinear_static_reference_cases_test.cpp` - Preserve existing registration; edit only if the current sources are not registered: `tests/CMakeLists.txt` - Minimal GREEN: `src/fesa/analysis/nonlinear_static_procedure.cpp` - Minimal GREEN: `src/fesa/io/hdf5/nonlinear_hdf5_results_writer.cpp` - Preserve existing partial GREEN/tests without redesign: `src/fesa/analysis/nonlinear_static_control.cpp`, `tests/unit/analysis/nonlinear_static_control_test.cpp`, `tests/unit/analysis/newton_iteration_log_test.cpp` No public header, new CMake target, new HDF5 dataset, schema-version change or reference artifact is owned by this Step. ## Authoritative HDF5 입력과 test-private report 경계 Comparator는 아래 existing schema-v1 paths만 읽는다. ```text /model/nodes [node_count] /model/nodal_constraint_mask [node_count,6] /steps/Step-1/accepted_frames unique factor-one row /steps/Step-1/frames//nodal/displacement [node_count,6] /steps/Step-1/frames//nodal/generalized_internal_force [node_count,6] /steps/Step-1/frames//nodal/generalized_external_force [node_count,6] /steps/Step-1/frames//nodal/generalized_residual [node_count,6] /steps/Step-1/frames//nodal/reaction [node_count,6] /steps/Step-1/frames//global/equilibrium [6] ``` `/global/equilibrium` component order is `FORCE_1,FORCE_2,FORCE_3,MOMENT_1,MOMENT_2,MOMENT_3` and its required `location` is `global-balance-error`. Preserve the current test-private types and make only the extensions needed by the approved dispositions and distinct internal evidence. Add the required standard `` and `` includes to the test-private header. Candidate signature-level shape: ```cpp namespace fesa::test { enum class NonlinearReferenceDisposition { kBlocking, kWarningOnly, kReviewOnly, kExcluded, }; struct NonlinearReferencePrecheckFinding { std::string family_identity; NonlinearReferenceDisposition disposition; std::string diagnostic_code; std::vector affected_keys; }; struct NonlinearInternalEquilibriumReport { std::string family_identity; std::size_t row_count; double numerator; double scale; std::optional normalized_metric; bool passed; std::string diagnostic_code; }; struct ConstrainedReactionResultantReport { std::array force; std::array moment; }; 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 pre_run_artifacts; std::vector post_run_artifacts; std::vector families; std::vector precheck_findings; std::vector internal_equilibrium; ConstrainedReactionResultantReport constrained_reaction_resultant; bool external_blocking_passed; bool internal_blocking_passed; bool blocking_passed; }; } // namespace fesa::test ``` These are test-support interfaces only. Match the existing aggregate/data style and avoid a public solver API. For an exact-zero internal scale, leave `normalized_metric` empty and use `diagnostic_code` plus the exact-zero numerator/pass rule instead of writing NaN or Inf. ## Required physical algorithm For each node in stable Domain order at the accepted state: 1. Read `X` from Domain/model identity and `u, phi` from the accepted state. Set `x=X+u`. 2. Read generalized applied force/covector from `generalized_external_force`. 3. Build constrained generalized reaction by applying the exact component mask to `generalized_residual` before any rotational conversion. 4. Convert applied and constrained-reaction rotational covectors independently with `InverseTransposeLeftJacobianSO3(phi)`. Do not convert their sum as a shortcut and do not mix free generalized residual into reaction. 5. Accumulate, about global origin `[0,0,0]`: ```text F_applied += applied spatial force M_applied += x cross F_applied_node + applied spatial physical moment F_reaction += constrained reaction spatial force M_reaction += x cross F_reaction_node + reaction spatial physical moment global balance error = [F_applied + F_reaction, M_applied + M_reaction] ``` Store only the six-component balance error in the existing accepted-frame `global_equilibrium` and existing `/global/equilibrium` dataset. Reconstruct the constrained reaction resultant and accepted free residual separately in the comparator; do not add summary datasets or a public header. For internal blocking gates: - Force scale is `max(sum(||F_applied_node||2), sum(||F_reaction_node||2))`. - Moment scale is `max(sum(||x cross F_applied_node + M_applied_node||2), sum(||x cross F_reaction_node + M_reaction_node||2))`. - Force and moment use separate `||balance_error||2/scale <= 1e-8` gates. If scale is exact zero, only exact-zero balance passes. No floor, `max(1,scale)` or clamp is allowed. - Accepted free translation-force and rotation-moment rows use the exact free mask and reproduce NSNR-069: nonzero matching external scale uses `||residual||inf/max(||internal||inf,||external||inf) <= 1e-2`; exact-zero external scale uses typed absolute `<=1e-3`. Finite checks occur first. - `../../docs/solver-core/README.md` is not used for these internal gates. ## RED 기존 dirty `NSNR-PROC-019`, `NSNR-HDF5-014`, `NSNR-REF-020..023`은 재작성하지 않는다. 먼저 existing `NSNR-REF-022`를 실행해 old shared gate failure를 다시 관찰·기록한 뒤, `NSNR-REF-024..025`를 comparator threshold 변경 전에 test-first로 추가한다. Procedure/HDF5 behavior는 threshold rework를 위해 추가 변경하지 않는다. - Approved draft label `NSNR-PROC-011` collides with completed Step 10's historical `NSNR-PROC-011` increment-loop identity. Preserve that history and use collision-resolved test ID `NSNR-PROC-019` with exact GTest symbol `NonlinearStaticProcedure.NonzeroPrescribedMotionWritesCurrentArmSpatialAppliedPlusReactionBalance`. Use a supported nonlinear shell input with nonzero prescribed translation and rotation plus a resolved fixed-global load. Independently reconstruct the six balance components from accepted `X,u,phi`, external force and masked residual. Expected RED: the current helper uses initial coordinates, raw full-residual summation and raw rotational residual, so the current-arm/spatial expected vector or distinct constrained/free projection assertion fails. - `NSNR-HDF5-014` `NonlinearHdf5ResultsWriter.WritesSchemaV1GlobalBalanceErrorLocation` checks existing path/shape, exact component names, schema version 1 and `location=global-balance-error`. Expected RED: current writer emits `location=global-origin`. - `NSNR-REF-020` `NonlinearStaticReference.AuditsFinePrimaryAndCoarseCompanionArtifacts` checks all exact fine, coarse and linear input/CSV hashes, fine/coarse independent execution directories and case roles. Expected RED: current case inventory supports only coarse nonlinear and linear cases. - `NSNR-REF-021` `NonlinearStaticReference.ReviewOnlyLocalReactionsCannotChangeBlockingVerdict` injects or observes failing local RF/RM row/RMS decisions while U and internal blocking families pass. It requires deterministic local error/precheck reporting and an unchanged blocking pass. Expected RED: current local RF/RM dispositions are `kBlocking`. - `NSNR-REF-022` `NonlinearStaticReference.BlocksOnGlobalBalanceAndAcceptedFreeResidual` fails each coarse/fine global-force, global-moment, free-force-residual and free-moment-residual family one at a time, then passes all. It also asserts the constrained reaction resultant is a distinct review summary. Expected RED for this re-execution: the preserved comparator applies the old shared `<=1e-10` gate, so the approved coarse metric `1.413750912531167e-10` and fine metric `2.7454660159924086e-10` are rejected even though both are below `1e-8`. - `NSNR-REF-023` `NonlinearStaticReference.RejectsMissingExtraDuplicateAndNonfiniteRowsBeforeToleranceAndRecordsReviewOnlyFindings` covers blocking, warning-only and review-only families. Blocking precheck failure changes the blocking verdict; warning/review failures remain ordered findings. Expected RED: the current comparator cannot retain review-only precheck findings independently of the blocking verdict. - `NSNR-REF-024` `NonlinearStaticReference.GlobalForceAndMomentBalanceUseApprovedInclusiveOneEighthGate` uses a test-private balance evaluator for distinct force and moment family identities. It requires an approved between-threshold metric such as `5e-9` and exact `1e-8` to pass, and `nextafter(1e-8,+infinity)` to fail for both identities. Expected decisive RED: the preserved shared `<=1e-10` comparison rejects `5e-9` and `1e-8`. - `NSNR-REF-025` `NonlinearStaticReference.GlobalForceAndMomentExactZeroScaleRequiresExactZeroBalance` exercises force and moment identities independently and requires exact-zero scale plus exact-zero numerator to pass while any nonzero numerator fails without NaN/Inf or a fabricated normalized metric. This is an unchanged-contract regression and may already pass in the RED batch; it must be present before GREEN. Run and record the observed failures: ```powershell cmake --build --preset windows-msvc-ninja-debug ctest --preset windows-msvc-ninja-debug -R "^(NonlinearStaticProcedure|NonlinearHdf5ResultsWriter|NonlinearStaticReference)\." --output-on-failure ``` The new tests must fail for the stated reasons. A compile failure caused only by the test-first enum/report extension is valid RED when the diagnostic identifies the missing planned production/ test-support behavior. An unexpected pass or unrelated failure stops the Step for classification. ## Minimal GREEN 1. Replace the private raw-residual/initial-coordinate global helper in `src/fesa/analysis/nonlinear_static_procedure.cpp` with the Required physical algorithm above. Keep stable node order, mask-before-conversion, independent applied/reaction conversion and finite failure behavior. Do not change the public procedure header or accepted-state layout. 2. In `src/fesa/io/hdf5/nonlinear_hdf5_results_writer.cpp`, preserve the path, shape, component names and schema version; change the exact `/global/equilibrium` location attribute to `global-balance-error` and keep writer validation finite/fail-closed. 3. Extend the existing comparator without deleting its partial work: - add fine primary inventory and keep coarse companion/linear control independent; - add `kReviewOnly` and keep local RF/RM key/schema/inventory/finite/row/RMS evidence; - reconstruct the constrained reaction resultant and accepted free residual from the exact HDF5 paths/mask; never use `/nodal/reaction` or `/global/equilibrium` as a substitute for all three; - apply internal gates exactly as specified above, using one test-private/internal shared `1.0e-8` constant for both distinct force and moment families; - keep U blocking, UR warning-only, local RF/RM review-only, RM3/stress excluded; - reject blocking missing/extra/duplicate/nonfinite rows before tolerance and retain ordered nonblocking findings; - set overall blocking verdict from blocking external U families AND all internal global/free families only. 4. Preserve existing `NSNR-CTRL-018..021`, `NSNR-LOG-012`, Full Newton, fixed factors, iteration limit, 39-field log schema and the already corrected residual selector unchanged. 5. Do not change production solver/configuration behavior for the balance threshold. No `fesa.env`, environment variable, CLI option, public API or HDF5 schema change is part of this GREEN. ## Exact read-only artifact inventory | case | exact path | rows | SHA-256 | | --- | --- | ---: | --- | | fine primary | `reference/shellNL-meshfine/shellNL-meshfine.inp` | input | `40DF1CE77311C8D799564A21A60F3BDEBB8148D52CAE22B7292FDFAD32018220` | | fine primary | `reference/shellNL-meshfine/shellNL-meshfine displacements.csv` | 729 | `740819FF8AED0E279961581F7CDBFA12784CFBA3A488C35A652E80103F64433F` | | fine primary | `reference/shellNL-meshfine/shellNL-meshfine reactions.csv` | 729 | `AEB01C98C596792B0E5F2B86A81945CAB0BEB0C6969CFC6738C6F81DF1F94363` | | fine optional | `reference/shellNL-meshfine/shellNL-meshfine stresses.csv` | 5,408 | `6D6213AB5DB9D6B1F5FBD641367D08BACC805967B6C5A95BCE0E1A5395377782` | | coarse companion | `reference/shellNL/shellNL.inp` | input | `4BF3ECB0F7F1E8462C573E993DBC97AC5D920C91CF6F23AD4C6891D77FFBADA2` | | coarse companion | `reference/shellNL/shellNL displacements.csv` | 49 | `4E0F99CA9372B1ADF7FAF102F43BC0B887DC743FDEDA378E4342D2CF8E0C0786` | | coarse companion | `reference/shellNL/shellNL reactions.csv` | 49 | `001CB1541A9E94424E5E650509D46BC8724970DF4DE47184D9DB09C7938AB069` | | coarse optional | `reference/shellNL/shellNL stresses.csv` | 288 | `5500E5114B1C44887528E30CEC054A1EB0F68BE3F4B8CE81FA59FC74A881483F` | | linear control | `reference/shell/shell.inp` | input | `4005851E1AB22FD3A16AC17A8D5DA3E051233F69F37419079F3553AD134ECFCF` | | linear control | `reference/shell/shell displacements.csv` | 49 | `C81D94E0B4A849F87AA0F79C83A79B94D5661AC79E44ED826919AB432C87746B` | | linear optional | `reference/shell/shell reactions.csv` | 49 | `0E34EEC27E700C3C0C744C1DAFD8A3B95EFEF34B8616FE96665876586FE82D41` | | linear optional | `reference/shell/shell stresses.csv` | 288 | `2B75A5B0DEB045A283C9BB7E9B1153B79F7537FF487214BF4A23316B59B660A7` | For every case, snapshot original inventory, bytes, SHA-256, size and timestamp; copy only the `.inp` bytes to a unique build-local directory; run only the copy; consume copied-sibling `_results.h5` and `_log.txt`; compare against original read-only CSVs; then prove the original inventory is unchanged. ## External comparison and classification - Fine and coarse `U1/U2/U3` are separate blocking families. - Linear `U1/U2/U3` is a blocking regression family. - All `UR1/UR2/UR3` families are warning-only. - Fine/coarse local `RF1/RF2`, `RF3`, spatial `RM1/RM2` are separate review-only families with exact mask-derived keys and complete common-policy error reports. - `RM3` and all stress values have no numerical family. Stress files remain hash-audited optional inventory; the known fine malformed optional stress key is not repaired. - External families use exactly `../../docs/solver-core/README.md`: reference-only `S=max(abs(reference))`, `B=0.01*S`, near-zero row `abs(error)<=B`, other-row relative `<=0.05`, family `RMS(error)/S<=0.01`, exact-zero family exact-zero FESA only. No absolute floor, averaging, collapse, row-order identity, clamp or tolerance relaxation. The final gate order is literally: ```text ARTIFACT CHECK -> COMPARE -> CLASSIFY -> REPORT ``` ## Focused and full VERIFY After minimal GREEN, run in this order and retain command, exit code, duration and output tail for Step 12: ```powershell cmake --preset windows-msvc-ninja-debug cmake --build --preset windows-msvc-ninja-debug ctest --preset windows-msvc-ninja-debug -R "^(NonlinearConvergence|NewtonIterationLog)\." --output-on-failure ctest --preset windows-msvc-ninja-debug -R "^(NonlinearStaticProcedure|NonlinearHdf5ResultsWriter)\." --output-on-failure ctest --preset windows-msvc-ninja-debug -R "^NonlinearStaticReference\." --output-on-failure ctest --preset windows-msvc-ninja-debug -R "^(NonlinearStaticReference|ReferenceTolerance|Mitc4Reference)\." --output-on-failure ctest --preset windows-msvc-ninja-debug --show-only=json-v1 ctest --preset windows-msvc-ninja-debug --output-on-failure ``` The Harness launch environment must be VsDevCmd x64 and inherit `FESA_GTEST_SOURCE_DIR`, `MKL_DIR`, `TBB_DIR`, and `HDF5_DIR`. Do not substitute the solution-only MSBuild path. ## Acceptance Criteria - New RED tests fail first for their declared current behavior, then pass after minimal GREEN. - `/global/equilibrium` remains schema v1 `[6]` with the same component names and stores only the current-arm/spatial-moment applied-plus-reaction balance error with `location=global-balance-error`. - Fine/coarse global force and moment normalized metrics are each `<=1e-8`; exact-zero scale follows the exact-zero rule. - Fine/coarse accepted free force and moment residual gates pass NSNR-069 independently. - Fine/coarse U blocking and linear U regression pass `../../docs/solver-core/README.md`. - UR differences remain warning-only. Local RF/RM findings remain review-only and cannot fail the blocking verdict. RM3/stress remain excluded. - Missing/extra/duplicate/nonfinite blocking rows fail before tolerance; warning/review findings are deterministic and nonblocking. - Original reference artifact content, inventory, size, timestamp and hash are unchanged. - Focused tests, test discovery and full CTest pass without new compiler warnings. ## 상태 기록 On success, update only Step 11 to `completed` and add one concise `summary`. On an implementation, build, test or reference-comparison failure, classify it and update only the Executor-selected Step's allowed payload. Executor owns retry, timestamps, commits, advancement and top-level status. Do not start Step 12. ## 금지사항 - Do not edit any reference artifact. 이유: exact stored files are immutable external evidence. - Do not run FESA on an original reference `.inp`. 이유: derived sibling outputs would mutate the reference directory. - Do not add HDF5 datasets, change schema version/path/shape/components or add public headers. 이유: Stage 5 requires reconstruction from existing schema-v1 datasets. - Do not use initial coordinates or raw rotational covectors for global physical moment. 이유: `AUDIT-001` requires accepted current arms and spatial moment conversion. - Do not apply the constraint mask after spatial conversion. 이유: partial rotational constraints would mix free residual into reaction. - Do not use `/global/equilibrium`, local `/nodal/reaction` or a raw full-residual sum as a substitute for all three equilibrium quantities. 이유: their physical identities differ. - Do not make local reaction rows blocking or omit their review evidence. 이유: USER-DECISION-004 fixes review-only with deterministic findings. - Do not compare `RM3` or stress values, average/collapse rows, match by row order, clamp zero values, add `max(1,S)`, relax tolerance or repair CSVs. 이유: approved identity/tolerance contracts forbid it. - Do not add `fesa.env`, runtime configuration, product solver acceptance, CLI or public API behavior for the `1e-8` gate. 이유: this fixed threshold belongs only to comparator-owned internal verification. - Do not replay or fabricate selector RED, delete partial Step 11 work, reset history, commit, push, run a reference solver, start Step 12, or claim Physics/Release readiness. - Stop and return a classified blocker if the required behavior needs an upstream contract, public API, HDF5 schema, tolerance or reference-artifact change.