# 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 diagnostics; }; Result> BuildFixedLoadFactorSequence( const StepDefinition& step); Result ApplyAbsolutePrescribedDisplacementTarget( const StepDefinition& step, double load_factor); Result 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.