Compare commits

...

10 Commits

16 changed files with 1724 additions and 37 deletions
@@ -0,0 +1,95 @@
# Shell Surface Pressure Load — 빌드·테스트 보고서
## 메타데이터
- feature_id: `shell-surface-pressure-load`
- owner_agent: `implementation-agent`
- 상태: agent 소유 focused 검증 통과
- `.harness/config.json`은 없었으므로 Step 7이 지정한 direct CMake 명령을 사용했다.
- 선택 환경: root CMake project, Visual Studio 18 2026 generator, x64, Debug,
C++17, MSVC 19.51.36252.0 (tool path 14.51.36231), MSBuild 18.8.2.
- 의존성: GoogleTest `C:/git/googletest`, MKL 2026.1, TBB 2023.1, HDF5 2.1.1.
## RED 사전검사와 이전 TDD 증거
시작 시 `implementation-report.md`, `build-test.md`,
`reference-comparison.md`가 모두 없었다. 이는 Step 7이 요구한
evidence-state RED다. Steps 1–6의 실행 기록을 감사하여 각 production/test/CMake
변경마다 expected RED와 GREEN/focused VERIFY가 있음을 확인했다. Step 1/2의
outer Harness timeout은 `corrections.md`가 기록한 full CTest와 Stop의 중복 실행이며,
제품 실패나 RED 누락이 아니다. `evidence-gap`은 없다.
## Agent 소유 명령 기록
duration은 command runner가 관측한 wall-clock 시간이다. Agent는 targetless
whole-project build 또는 unfiltered full CTest를 실행하지 않았다.
| ID | 명령 | exit | duration | 결과 / 의미 있는 tail |
| --- | --- | ---: | ---: | --- |
| `V-CONFIG` | `cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 "-DFESA_GTEST_SOURCE_DIR=C:/git/googletest" "-DMKL_DIR=C:/Program Files (x86)/Intel/oneAPI/mkl/2026.1/lib/cmake/mkl" "-DTBB_DIR=C:/Program Files (x86)/Intel/oneAPI/tbb/2023.1/lib/cmake/tbb" "-DHDF5_DIR=C:/Program Files/HDF_Group/HDF5/2.1.1/cmake"` | 0 | 7.5 s | `CXX compiler identification is MSVC 19.51.36252.0`; configure/generate 완료. |
| `V-BUILD` | `cmake --build .harness/build --config Debug --target fesa_unit_tests fesa_integration_tests fesa_reference_tests` | 0 | 5.9 s | 세 test executable이 `.harness/build/tests/Debug`에 link됨. |
| `V-SHOW` | `ctest --test-dir .harness/build -C Debug --show-only=json-v1` | 0 | 1.7 s | 유효한 `ctestInfo` JSON; 후속 audit에서 362 tests 확인. |
| `V-FOCUSED` | `ctest --test-dir .harness/build -C Debug --output-on-failure -R "^(InpSyntax|ShellSurfacePressureInput|ShellSurfacePressureDomain|ShellSurfacePressureKernel|SurfacePressureAssembly|LinearShellSurfacePressure|NonlinearShellSurfacePressure|ShellSurfacePressureApplication|ShellSurfacePressureReference)\."` | 0 | 15.8 s | `100% tests passed`, 64/64; unit 37, integration 18, reference 9. |
| `V-REFERENCE` (사전) | `ctest --test-dir .harness/build -C Debug --output-on-failure -R "^ShellSurfacePressureReference\."` | 0 | 2.9 s | 9/9 통과. |
| `V-CLI-BUILD` | `cmake --build .harness/build --config Debug --target fesa_cli` | 0 | 2.7 s | `.harness/build/src/fesa/Debug/fesa.exe` link 완료. |
| `V-CLI-RUN` | `fesa.exe .harness/build/reference/shell-surface-pressure-load/shellNL-meshfine-pressure.inp` | 0 | 약 10 m 56 s | FESA만 실행. factor-one increment 10이 accepted되고 schema-1 HDF5/log가 atomic publish됨. 마지막 log는 `combined_pass=1`, `transition=accepted`. |
| `V-REFERENCE` (생성 결과) | 같은 `^ShellSurfacePressureReference\.` 명령 | 0 | 2.7 s | 새 root execution-directory HDF5를 선택하여 9/9 통과. |
| inventory | CTest JSON suite/count 및 staged DLL audit | 0 | 1.9 s | 362 discovered, 모든 named suite nonzero, runtime closure 확인. |
## 발견·focused 결과
| suite | 발견 수 | 결과 |
| --- | ---: | --- |
| `InpSyntax` | 5 | 통과 |
| `ShellSurfacePressureInput` | 15 | 통과 |
| `ShellSurfacePressureDomain` | 3 | 통과 |
| `ShellSurfacePressureKernel` | 11 | 통과 |
| `SurfacePressureAssembly` | 3 | 통과 |
| `LinearShellSurfacePressure` | 5 | 통과 |
| `NonlinearShellSurfacePressure` | 10 | 통과 |
| `ShellSurfacePressureApplication` | 3 | 통과 |
| `ShellSurfacePressureReference` | 9 | 통과 |
| 선택 합계 | 64 | 64 passed, 0 failed |
선택된 test 중 skip, zero-discovery, failure는 없다.
## 경고와 runtime closure
configure/build 출력에는 compiler/linker warning이 없다. `CMAKE_HAVE_LIBC_PTHREAD`
probe의 실패 표시는 Windows configure probe 결과일 뿐 `/W4 /WX` warning이 아니다.
| 실행 파일 directory | staged DLL |
| --- | --- |
| `.harness/build/src/fesa/Debug` | `hdf5.dll`, `libiomp5md.dll`, `libmmd.dll`, `mkl_core.3.dll`, `mkl_def.3.dll`, `mkl_intel_thread.3.dll`, `tbb12_debug.dll` |
| `.harness/build/tests/Debug` | `hdf5.dll`, `libiomp5md.dll`, `libmmd.dll`, `mkl_core.3.dll`, `mkl_def.3.dll`, `mkl_intel_thread.3.dll`, `tbb12_debug.dll` |
test discovery/execution뿐 아니라 copied nonlinear input을 CLI로 실제 완료했으므로,
MKL/TBB/HDF5 runtime staging도 실행으로 확인됐다.
## Stop 소유권, 분류, 변경 없음
`.codex/hooks.json`의 Stop은 `scripts/hooks/stop_validation.py`에 연결되어 있다.
이 훅이 agent turn 종료 시 단 한 번의 targetless whole-project MSVC x64 Debug build와
unfiltered CTest를 소유한다. 이 Step은 hook entry point를 수동 호출하거나 중복 full
verification을 실행하지 않았다.
- agent-owned classification: `passed`
- 실패 classification: 없음 (`configure`, `compile`, `link`, `test`,
`reference-comparison`, `artifact`, `environment`, `regression` 모두 없음)
- automatic Stop disposition: Harness phase completed locally at
`2026-09-01 17:43:57`. The single automatic Stop whole-project MSVC x64
Debug build passed. It ran `cmake --build .harness/build --config Debug`,
then `ctest --test-dir .harness/build -C Debug --show-only=json-v1` with
nonzero discovery, and `ctest --test-dir .harness/build -C Debug
--output-on-failure`; the final full CTest result was `362/362 PASS`,
`0 failed`.
- automatic Stop evidence sources:
`phases/shell-surface-pressure-load/step7-output.json`,
`.harness/build/Testing/Temporary/LastTest.log`, and phase/commit metadata
`2c91462`, `bf2339c`, `c4d1e2a`. Classification: `pass`; failed tests:
none. Duration: not separately preserved in committed report; completion
timestamp and LastTest log are authoritative.
- Step 7은 source/header/test/CMake/Harness/tolerance/upstream contract/reference artifact를
변경하지 않았고, 세 Gate 6 보고서 및 허용된 Step 7 status/summary만 변경한다.
physics evaluation과 release readiness는 이 보고서 범위 밖이다.
@@ -3,17 +3,17 @@
## 1. Metadata and Feature Scope
- feature_id: `shell-surface-pressure-load`
- workflow_state: `needs-implementation`
- workflow_state: `completed`
- owner_agent: `coordinator-agent`
- request_date: `2026-08-30`
- last_updated: `2026-09-01`
- process_classification: `architectural`
- current_goal: Correction evidence를 보존해 clean commit한 뒤 같은 Implementation Agent/Executor를 Step 3부터 재개하고 Gate 6 implementation/build/reference evidence를 완결한다.
- current_goal: Gates 1–8 owner evidence와 `ready-for-release` recommendation을 기록하고 내부 feature workflow를 closure한다.
- approved_scope: Abaqus subset `*SURFACE, TYPE=ELEMENT`와 `*DSLOAD`의 uniform `P`를 S4/S4R `SPOS|SNEG` surface에 적용한다. Linear static은 reference configuration load를, nonlinear static은 current-configuration follower load와 external tangent를 사용한다.
- scope_boundaries: Unsupported surface/load variants는 fail-closed로 거부하고 stable source identity와 deterministic load/reduction order를 보존한다. 기존 authoritative HDF5 output을 우선 유지하되 Stage 5 I/O gate가 evidence로 최종 확정한다.
- reference_case: `reference/shellNL-meshfine-pressure/`의 existing `.inp`와 CSV를 exact path/name 그대로 read-only 사용한다.
- user_approval: `2026-08-30` approved Abaqus-aligned design; `2026-09-01` approved the 7-Step implementation plan, phase materialization, Harness execution without push, and isolated recovery worktree with per-run `FESA_HARNESS_CODEX_SANDBOX=danger-full-access` fallback.
- active_workspace: `C:/git/FESADev/.worktrees/shell-surface-pressure-load-recovery` on `feat-shell-surface-pressure-load-recovery`; recovery baseline commit `2d07106`
- active_workspace: `C:/git/FESADev/.worktrees/shell-surface-pressure-load-recovery` on `feat-shell-surface-pressure-load`; recovery baseline commit `2d07106`
- protected_backup_workspace: original `C:/git/FESADev` dirty feature workspace; do not read, modify, clean, stash, commit, reset, or execute there during recovery
## 2. Eight-Stage Worklist
@@ -25,9 +25,9 @@
| 3. Formulation | `formulation-agent` | `passed` | `docs/shell-surface-pressure-load/formulation.md` | Pressure virtual work, equivalent nodal load, follower linearization과 numerical integration 계약이 구현 가능함 | `ready-for-numerical-review`; force/residual/tangent sign, common `2x2` rule, `24x1`/`24x24` dimensions, dimensionless geometry contract, deterministic assembly, requirement trace 24/24; owner `passed`; Coordinator audit 통과 | 없음 | 1 |
| 4. Numerical/reference review | `numerical-review-agent` | `passed` | `docs/shell-surface-pressure-load/numerical-review.md`; `docs/shell-surface-pressure-load/reference-model.md` | Numerical review와 exact reference contract가 함께 `pass-for-io-definition`임 | Aligned `pass-for-io-definition`; `tau_J=1e-12`, analytical/objectivity `1e-12`, FD `h=1e-5`/error `1e-8`, global balance `1e-8`; exact 729-row reference inventory, U/UR 2,187 logical rows each; owner evidence와 Coordinator audit 통과 | 없음 | 1 |
| 5. I/O definition | `io-definition-agent` | `passed` | `docs/shell-surface-pressure-load/io.md` | Abaqus subset, semantic mapping, diagnostics, HDF5 projection과 reference rows가 명확함 | `ready-for-implementation-planning`; exact assembly `SURFACE`/step `DSLOAD P`, Domain/Step/AnalysisModel mapping, ordered fail-closed validation, unchanged schema `0/1`, exact HDF5/reference projection, trace 24/24; owner `passed`; Coordinator audit 통과 | 없음 | 1 |
| 6. Implementation | `implementation-planning-agent`; `implementation-agent` | `in-progress` | `docs/shell-surface-pressure-load/implementation-plan.md`; `docs/shell-surface-pressure-load/implementation-report.md`; `docs/shell-surface-pressure-load/build-test.md`; `docs/shell-surface-pressure-load/reference-comparison.md`; `docs/shell-surface-pressure-load/corrections.md` | 승인 계획, TDD evidence, MSVC/x64/Debug build/CTest와 blocking reference comparison이 모두 통과함 | `GATE6-CORR-001 passed`: Step 1 RED/focused `36/36`/full `310/310`, Step 2 RED/focused/LastTest `321 pass, 0 fail` audit; pending Step 3–7은 agent RED+targeted/focused/nonzero discovery, automatic Stop single whole-project full verification로 최소 교정. Stale Step 3 `started_at` Coordinator 정규화 완료 | Correction bundle clean commit 후 same Implementation Agent Step 3 rerun 대기 | 2 |
| 7. Physics evaluation | `physics-evaluation-agent` | `pending` | `docs/shell-surface-pressure-load/physics-evaluation.md` | Direction, symmetry, resultant, reaction과 equilibrium 검토가 `pass-for-release-agent`임 | Gate 6 evidence 대기 | Gate 6 미통과 | 0 |
| 8. Release readiness | `release-agent` | `pending` | `docs/shell-surface-pressure-load/release.md` | Acceptance traceability와 limitation을 포함한 verdict가 `ready-for-release`임 | Gate 7 evidence 대기 | Gate 7 미통과 | 0 |
| 6. Implementation | `implementation-planning-agent`; `implementation-agent` | `passed` | `docs/shell-surface-pressure-load/implementation-plan.md`; `docs/shell-surface-pressure-load/implementation-report.md`; `docs/shell-surface-pressure-load/build-test.md`; `docs/shell-surface-pressure-load/reference-comparison.md`; `docs/shell-surface-pressure-load/corrections.md` | 승인 계획, TDD evidence, MSVC/x64/Debug build/CTest와 blocking reference comparison이 모두 통과함 | Harness Steps 1–7 completed; focused `64/64`, pressure reference `9/9`, final automatic Stop build PASS/full `362/362 PASS`, 0 failed; requirements trace `24/24`; post-hook reports는 historical evidence와 `pass-for-physics-evaluation`을 기록함 | 없음 | 2 |
| 7. Physics evaluation | `physics-evaluation-agent` | `passed` | `docs/shell-surface-pressure-load/physics-evaluation.md` | Direction, symmetry, resultant, reaction과 equilibrium 검토가 `pass-for-release-agent`임 | Owner `pass-for-release-agent`; documented pressure direction/sign, current-area resultant/moment, reactions/equilibrium, symmetry/zero behavior, follower objectivity, residual/nonfinite sanity와 declared model coverage 통과; normalized force/moment balance `6.76e-14`/`3.72e-14 <= 1e-8`; Coordinator output/status/no-change audit 통과 | 없음 | 1 |
| 8. Release readiness | `release-agent` | `passed` | `docs/shell-surface-pressure-load/release.md` | Acceptance traceability와 limitation을 포함한 verdict가 `ready-for-release`임 | Owner `ready-for-release`; Gates 1–7 consistent, must requirements `24/24` traced, full `362/362`, focused `64/64`, reference `9/9`, U/UR `2187/2187`, physics force/moment balance evidence와 11개 known limitations/release notes 포함; Coordinator output/status/no-change audit 통과 | 없음 | 1 |
## 3. Active Dispatch Package and Latest Sub-Agent Return
@@ -178,16 +178,49 @@
- stop_condition: Do not commit, execute Harness/build/test, modify Step 1/2, source/tests/CMake/reference/tolerance/upstream docs, advance phase/Gate 7, or revert other agents' changes. Return exact rerun/commit recommendation to Coordinator.
- missing_evidence: 없음
### Completed Dispatch — Gate 6 Post-Hook Evidence Amendment
- dispatch_id: `GATE6-EVIDENCE-001`
- feature_id: `shell-surface-pressure-load`
- target_sub_agent: same `implementation-agent`
- required_inputs: completed phase index/commits, Step 7 output, `.harness/build/Testing/Temporary/LastTest.log`, authoritative final automatic Stop full CTest `362/362 PASS`, 0 failed
- expected_outputs: amended `docs/shell-surface-pressure-load/build-test.md`; `implementation-report.md` only if needed for consistency
- acceptance_gate: Exact Stop configure/build/discovery/full-test command/result, exit status, `362/362`, zero failures, duration/tail and single-owner classification are historical fact rather than future tense; no source/test/CMake/reference/phase changes and no test rerun.
- stop_condition: Evidence-only docs amendment; do not run build/CTest/Harness, change implementation/reference/upstream contracts, commit/push, dispatch peers, or advance Gate 7.
- missing_evidence: 없음
### Completed Dispatch — Gate 7 Physics Evaluation
- dispatch_id: `GATE7-PHYS-001`
- feature_id: `shell-surface-pressure-load`
- target_sub_agent: `physics-evaluation-agent`
- required_inputs: approved feature bundle `requirements.md`, `research.md`, `formulation.md`, `numerical-review.md`, `reference-model.md`, `io.md`; passing `implementation-report.md`, `build-test.md`, `reference-comparison.md`; exact read-only reference artifacts; `fesa-physics-sanity`
- expected_outputs: `docs/shell-surface-pressure-load/physics-evaluation.md`
- acceptance_gate: Passing reference rows와 implementation evidence를 바탕으로 pressure 방향/부호, resultant와 moment, reactions/equilibrium, symmetry/zero behavior, nonlinear follower objectivity/current-area behavior, nonfinite/residual sanity와 stored-case model coverage를 독립 평가하고 verdict가 `pass-for-release-agent`임.
- stop_condition: Physics report와 owner return evidence 제출 후 중지한다. Source/tests/CMake/reference/tolerance/upstream contracts를 수정하거나 Abaqus를 실행하거나 Gate 8을 수행하지 않는다.
- missing_evidence: 없음
### Completed Dispatch — Gate 8 Release Readiness
- dispatch_id: `GATE8-REL-001`
- feature_id: `shell-surface-pressure-load`
- target_sub_agent: `release-agent`
- required_inputs: Gates 1–7 approved feature bundle과 owner reports: `requirements.md`, `research.md`, `formulation.md`, `numerical-review.md`, `reference-model.md`, `io.md`, `implementation-plan.md`, `implementation-report.md`, `build-test.md`, `reference-comparison.md`, `corrections.md`, `physics-evaluation.md`; `fesa-release-readiness`
- expected_outputs: `docs/shell-surface-pressure-load/release.md`
- acceptance_gate: Required reports가 같은 feature/status로 일치하고 full `362/362` validation, `pass-for-physics-evaluation`, `pass-for-release-agent`가 확인되며 각 `must` requirement가 acceptance/test/reference/release scope와 추적되고 known limitations 및 release notes draft를 포함한 verdict가 `ready-for-release`임.
- stop_condition: Release report와 owner return evidence 제출 후 중지한다. Source/tests/CMake/reference/tolerance/upstream contracts를 수정하거나 build/test/Harness/reference solver를 실행하거나 publish/deploy/package/tag/commit/push하지 않는다.
- missing_evidence: 없음
### Latest Sub-Agent Return
- target_sub_agent: `correction-agent`
- output_paths: `docs/shell-surface-pressure-load/corrections.md`; `phases/shell-surface-pressure-load/step3.md`–`step7.md`
- status: `corrected-for-implementation-rerun`
- evidence_summary: `harness-parallel-duplicate-full-verification-timeout` attempt 2의 PID/timestamp/process overlap을 기록하고 Step 1/2 RED/focused/full evidence와 four commits를 감사함. Pending Steps에서 agent-owned targetless whole-project build/unfiltered full CTest만 제거하고 RED, targeted build, focused feature/regression/reference suites, nonzero discovery, warning/contract checks, exact reference hashes/tolerance와 Step 7 literal report sequence를 유지함. Automatic Stop을 exactly one whole-project full verification owner로 명시함. Coordinator가 pending Step 3 stale `started_at` 한 필드만 제거함.
- target_sub_agent: `release-agent`
- output_paths: `docs/shell-surface-pressure-load/release.md`
- status: `ready-for-release`
- evidence_summary: Gates 1–7 status/evidence consistency, must requirements `24/24` acceptance/test/reference/release trace, full `362/362`, focused `64/64`, reference `9/9`, U/UR `2187/2187`, physics force/moment balance와 exact artifact immutability를 감사함. Known limitations 11개와 release notes draft를 포함하고 내부 closure를 권고함.
- blockers: 없음
- open_evidence_gaps: Step 3–7 Implementation Agent actual RED/focused/Stop evidence, final reference comparison과 three reports 대기
- open_evidence_gaps: 없음
- critical_user_question: 없음
- forbidden_area_assertion: source, tests, CMake, phases, `docs/TOLERANCE.md`, reference artifact 변경 없음
- forbidden_area_assertion: `release.md`만 생성; source, tests, CMake, phases, upstream reports, generated evidence, `docs/TOLERANCE.md`, reference artifact 변경 없음; build/test/reference solver 재실행 없음
## 4. Gate Decision Log and Workflow State
@@ -209,10 +242,16 @@
| 2026-09-01 | `GATE6-IMPL-RECOVERY-001` | `needs-rework` | Step 1/2 commits completed, but agent full CTest and Stop-hook full CTest overlapped in parallel and caused repeated 1800-second timeout/slowdown. Harness safe-stopped after Step 2 commits; Step 3 source edit 없음 | `needs-correction` |
| 2026-09-01 | `GATE6-CORR-001` | `pending` | Exactly one Correction Agent root-cause audit/minimal verification-ownership correction 대기 | `needs-correction` |
| 2026-09-01 | `GATE6-CORR-001` | `passed` | Owner `corrected-for-implementation-rerun`; Coordinator audit에서 correction scope가 `corrections.md`, Step 3–7 wording와 stale pending Step 3 timestamp 제거에 한정되고 source/tests/CMake/hooks/reference/upstream contract no-change, focused/reference strength와 single Stop full gate 유지 확인 | `needs-implementation` |
| 2026-09-01 | `GATE6-EVIDENCE-001` | `pending` | Harness Step 7 completed, final Stop `362/362 PASS`; reports의 post-hook historical evidence amendment 대기 | `needs-implementation` |
| 2026-09-01 | `GATE6-EVIDENCE-001` | `passed` | Same Implementation Agent가 `build-test.md`와 `implementation-report.md`에 phase completion, automatic Stop build PASS, nonzero discovery, full `362/362 PASS`, 0 failed와 evidence provenance를 기록함. Coordinator cross-report audit에서 focused `64/64`, pressure reference `9/9`, requirements `24/24`, exact reference immutability와 `pass-for-physics-evaluation` 일치 확인 | `needs-physics-evaluation` |
| 2026-09-01 | `GATE7-PHYS-001` | `pending` | Exactly one Physics Evaluation Agent의 independent physical plausibility evidence 대기 | `needs-physics-evaluation` |
| 2026-09-01 | `GATE7-PHYS-001` | `passed` | Owner `pass-for-release-agent`; `physics-evaluation.md`가 documented pressure direction/sign, resultant/moment, reaction/equilibrium, symmetry/zero, follower objectivity/current-area, residual/nonfinite와 declared model coverage를 통과로 판정함. Coordinator audit에서 required sections, input handoff, `6.76e-14`/`3.72e-14 <= 1e-8`, blocker 없음과 physics-only no-change 확인 | `needs-release` |
| 2026-09-01 | `GATE8-REL-001` | `pending` | Exactly one Release Agent의 gate inventory, acceptance traceability, limitations와 closure recommendation 대기 | `needs-release` |
| 2026-09-01 | `GATE8-REL-001` | `passed` | Owner `ready-for-release`; `release.md`가 11 required sections, Gates 1–7 consistent, must trace `24/24`, full/focused/reference/physics passing evidence, known limitations와 release notes를 포함함. Coordinator audit에서 required output/status/blocker/no-change 및 internal-only release boundary 확인 | `completed` |
## 5. Risk, Blocker, and User-Decision Register
- active_blockers: 없음; correction bundle clean commit 후 same Implementation Agent rerun 필요
- active_blockers: 없음
- critical_user_questions: 없음
- USER-DECISION-001: `resolved 2026-09-01`; 7-Step draft, phase materialization, Harness implementation/verification execution 승인. `--push`는 승인되지 않음.
- USER-DECISION-002: `resolved 2026-09-01`; isolated linked recovery worktree와 해당 Executor process에 한정한 `danger-full-access` 승인. Original dirty workspace는 immutable backup으로 보존.
@@ -231,20 +270,21 @@
- normalized_failure_attempts: `upstream-io-contract-mismatch` 1회 교정 완료; `environment/sandbox-msvc-filetracker-access-denied` 1회 recovery routing 완료; `harness-parallel-duplicate-full-verification-timeout` 2회 관측, first Correction Agent response explicitly dispatched
- automatic_rework_limit: 동일 normalized failure classification `2`회
- current_attempt: `correction` 1회 완료; implementation recovery Step 1/2 완료, Step 3 rerun 대기; `implementation-planning` 1회 완료; phase materialization 1회 완료; `io-definition` 1회 완료; `numerical-reference-review` 1회 완료; `formulation-contract` 1회 완료; `research-evidence` 1회 완료; `requirements-baseline` 1회 완료
- current_attempt: Gate 7 attempt 1 완료; Gate 8 attempt 1 완료; `post-hook-evidence-amendment` 1회 완료; implementation Steps 1–7 완료; correction 1회 완료; `implementation-planning` 1회 완료; phase materialization 1회 완료; Gates 1–8 완료
- stop_condition: 동일 classification이 2회이면 자동 재작업을 중단하고 `needs-user-decision` 또는 `blocked`로 전환한다.
## 7. Final Workflow Closure
- closure_status: `pending`
- closure_status: `completed`
- required_evidence: Gates 1–8 owner evidence와 `release.md`의 `ready-for-release` recommendation
- current_boundary: Gates 1–5와 Gate 6 planning/phase/correction subgates `passed`; Stage 6 Step 1/2 완료, Step 3–7 미완료; correction bundle commit 전 Harness 재실행 금지, Gate 6 three-report evidence 전 Gate 7 금지.
- closure_evidence: Gates 1–8 `passed`; `release.md` status `ready-for-release`; must trace `24/24`; blocker 및 open evidence gap 없음.
- current_boundary: Internal feature workflow closed. Publish, deploy, package, tag, merge, push 또는 external release는 별도 명시 권한 없이는 수행하지 않는다.
## 8. No-Change Assertion
- source_code: 변경 없음
- tests: 변경 없음
- CMake: 변경 없음
- source_code: Gate 6 approved implementation에서 변경됨; Coordinator와 post-hook evidence amendment는 변경하지 않음
- tests: Gate 6 approved TDD implementation에서 변경됨; Coordinator와 post-hook evidence amendment는 변경하지 않음
- CMake: Gate 6 approved implementation에서 test registration 변경됨; Coordinator와 post-hook evidence amendment는 변경하지 않음
- reference_artifacts: 변경 없음
- tolerance_policies: 변경 없음
- phase_planning: `phases/index.json`에 pending entry append; `phases/shell-surface-pressure-load/index.json`과 `step1.md`–`step7.md` 생성
@@ -252,3 +292,6 @@
- correction_change: `corrections.md` 생성, Step 3–7 verification ownership 문구 최소 교정, pending Step 3 stale `started_at` 제거; source/tests/CMake/hooks/scripts/reference/tolerance/upstream docs 변경 없음
- coordinator_change: `docs/shell-surface-pressure-load/coordination.md` 생성 및 Gates 1–5와 Gate 6 planning evidence 기록
- specialist_change: Requirement Agent가 `requirements.md`, Research Agent가 `research.md`, Formulation Agent가 `formulation.md`, Numerical Review Agent가 `numerical-review.md`와 `reference-model.md`, I/O Definition Agent가 `io.md`, Implementation Planning Agent가 `implementation-plan.md`를 각각 생성함
- gate6_evidence_change: Implementation Agent가 `implementation-report.md`, `build-test.md`, `reference-comparison.md`를 완성하고 post-hook final Stop evidence를 report-only amendment로 반영함; reference/tolerance 변경 없음
- gate7_evidence_change: Physics Evaluation Agent가 `physics-evaluation.md`만 생성하고 existing HDF5/log/reference evidence를 read-only 감사함; source/tests/CMake/contracts/reference/tolerance/generated evidence 변경 없음
- gate8_evidence_change: Release Agent가 `release.md`만 생성하고 Gates 1–7과 acceptance traceability를 감사함; source/tests/CMake/upstream reports/reference/tolerance/generated evidence 변경 및 build/test/reference solver 재실행 없음
@@ -0,0 +1,121 @@
# Shell Surface Pressure Load — 구현 보고서
## 메타데이터와 판정
- feature_id: `shell-surface-pressure-load`
- implementation gate: Gate 6
- owner: `implementation-agent` (Step 7 evidence/report)
- 판정: `pass-for-physics-evaluation` 권고
이 Step은 Steps 1–6에서 구현된 surface pressure 기능을 수정하지 않고, 그
TDD 이력과 fresh MSVC focused 검증 및 FESA-only reference comparison을 감사해
Gate 6 산출물을 완성했다. Physics Gate 7과 release readiness는 판정하지 않는다.
## Step 7 RED 사전검사
시작 시 `implementation-report.md`, `build-test.md`,
`reference-comparison.md`에 이번 Gate의 fresh passing evidence가 없었다. 이는
요구된 evidence-state RED다. 이어서 Steps 1–6의 phase summary, 실행 기록 및
`corrections.md`를 읽어 production/test/CMake 변경마다 아래의 test-first RED와
후속 GREEN 증거가 있음을 확인했다. 증거 누락(`evidence-gap`)은 없다.
| Step | test-first RED 또는 관측 실패 | 최소 GREEN / VERIFY 증거 |
| --- | --- | --- |
| 1 | pressure semantic mapping 부재를 겨냥한 parser/domain RED | parser/domain focused 36/36, full CTest 310/310 |
| 2 | invalid pressure input 18 tests 중 7 expected failures | hardened input GREEN 18/18, legacy 24/24, full CTest 321/321 |
| 3 | `surface_pressure_evaluator.h` 누락 C1083 | kernel 11/11, MITC4 regression 7/7 |
| 4 | surface pressure assembler header/API 부재 | pressure assembly 10/10, regression 43/43, discovery 342 |
| 5 | pressure가 외력에 반영되지 않는 assertion RED | nonlinear/HDF5/tangent/reference focused 23/23 |
| 6 | pressure reference registry/contract 부재 | pressure comparison 9/9, reference suite 14/14, discovery 362 |
Step 1/2의 outer Harness timeout은 `corrections.md`가 기록한 Stop/full CTest
중복 실행의 환경적 시간 초과이며, product RED/GREEN 증거 누락으로 분류하지
않았다.
## 구현 범위와 변경 인터페이스
Steps 1–6에서 이미 완료된 기능은 다음의 하나의 입력-해석-결과 경로다.
1. `*SURFACE, TYPE=ELEMENT`와 additive `*DSLOAD, P`를 immutable Domain/Step
pressure facet으로 map하고 stable facet identity와 source order를 보존한다.
2. target, face, pressure, duplicate/conflict, finite 값과 shell surface 방향을
fail-closed diagnostic으로 검증한다.
3. 4-node MITC4 facet의 reference/current configuration pressure force를 평가하며,
follower external tangent를 exact contribution으로 제공한다.
4. linear path에서는 reference-configuration pressure force만 deterministic full-space
load assembly에 더하고, nonlinear full Newton path에서는 current configuration
force와 external tangent를 trial assembly에 포함한다.
5. schema-0/1, no-pressure behaviour, CLI lexical-sibling output 및 existing B33/MITC4
contracts를 확장하거나 변경하지 않는다.
Step 7은 production/header/test/CMake/Harness/tolerance/reference artifact를 변경하지
않았다. 이 Step의 허용된 변경은 세 Gate 6 보고서와 phase index의 Step 7
`status`/one-line `summary`뿐이다.
## 요구조건 커버리지
아래 매핑은 승인 requirements의 24개 ID 전체가 orphan 없이 Steps 1–6의 구현과
passing evidence에 연결됨을 보인다. 상세 command, count 및 artifact evidence는
`build-test.md`와 `reference-comparison.md`에 있다.
| 요구조건 | 구현 Step | 검증 증거 |
| --- | --- | --- |
| `FESA-REQ-SHELL-PRESSURE-001` | 1 | `ShellSurfacePressureInput` |
| `FESA-REQ-SHELL-PRESSURE-002` | 1 | `ShellSurfacePressureDomain` |
| `FESA-REQ-SHELL-PRESSURE-003` | 1, 4 | `ShellSurfacePressureInput`, `SurfacePressureAssembly` |
| `FESA-REQ-SHELL-PRESSURE-004` | 2 | `ShellSurfacePressureInput` invalid-target cases |
| `FESA-REQ-SHELL-PRESSURE-005` | 2 | duplicate/conflict input cases |
| `FESA-REQ-SHELL-PRESSURE-006` | 3 | `ShellSurfacePressureKernel` |
| `FESA-REQ-SHELL-PRESSURE-007` | 3 | reference/current pressure force cases |
| `FESA-REQ-SHELL-PRESSURE-008` | 3, 5 | follower tangent kernel/nonlinear cases |
| `FESA-REQ-SHELL-PRESSURE-009` | 4 | `SurfacePressureAssembly` deterministic order |
| `FESA-REQ-SHELL-PRESSURE-010` | 4 | `LinearShellSurfacePressure` |
| `FESA-REQ-SHELL-PRESSURE-011` | 4 | linear reaction/schema preservation |
| `FESA-REQ-SHELL-PRESSURE-012` | 5 | `NonlinearShellSurfacePressure` |
| `FESA-REQ-SHELL-PRESSURE-013` | 5 | exact external tangent/full Newton cases |
| `FESA-REQ-SHELL-PRESSURE-014` | 5 | accepted free residual/global balance test |
| `FESA-REQ-SHELL-PRESSURE-015` | 1–5 | parser-to-analysis integration suites |
| `FESA-REQ-SHELL-PRESSURE-016` | 4, 5 | no-pressure schema-0/1 regression |
| `FESA-REQ-SHELL-PRESSURE-017` | 5 | schema-1 accepted-frame/HDF5 cases |
| `FESA-REQ-SHELL-PRESSURE-018` | 2, 5 | diagnostics and failure-atomicity cases |
| `FESA-REQ-SHELL-PRESSURE-019` | 6, 7 | `ShellSurfacePressureReference` 9/9 |
| `FESA-REQ-SHELL-PRESSURE-020` | 6, 7 | exact artifact SHA/size and precheck evidence |
| `FESA-REQ-SHELL-PRESSURE-021` | 6, 7 | U blocking / UR warning-only projection |
| `FESA-REQ-SHELL-PRESSURE-022` | 6, 7 | common family-scale row/RMS comparison |
| `FESA-REQ-SHELL-PRESSURE-023` | 4–7 | schema/CLI/no-pressure regression suites |
| `FESA-REQ-SHELL-PRESSURE-024` | 1–7 | recorded RED/GREEN, fresh MSVC focused gates |
커버된 요구조건: 24/24. Orphan count: 0.
## Step 7 GREEN 및 VERIFY
`.harness/config.json`이 없었으므로 approved direct CMake command를 사용했다.
Visual Studio 18 2026, x64, Debug, MSVC 19.51.36252.0에서 test target을 build했고,
feature focused CTest는 64/64, generated schema-1 HDF5를 다시 소비한 pressure
reference suite는 9/9로 통과했다. Configure/build에 새 `/W4 /WX` warning이 없고,
CLI 및 test directory에 HDF5/MKL/TBB runtime DLL closure도 존재했다.
Required input의 byte-identical build copy에 FESA CLI만 실행했다. 결과는 unique
factor-one accepted frame을 가진 schema-1 HDF5이며, U 2,187 blocking rows와 UR
2,187 warning-only rows 모두 common tolerance row/RMS gate를 통과했다. Input/CSV
artifact의 pre/post SHA-256, size 및 timestamp는 동일했다.
Agent는 targetless whole-project build나 unfiltered full CTest를 실행하지 않았다.
그 검증은 `.codex/hooks.json`에 등록된 automatic Stop의 단 한 번의 authoritative
MSVC x64 Debug validation 소유권이다. The Harness phase completed locally at
`2026-09-01 17:43:57`; that automatic Stop build passed, discovery was
nonzero, and its full CTest was `362/362 PASS`, `0 failed`. The historical
command/evidence detail is recorded in `build-test.md` from
`step7-output.json`, `LastTest.log`, and metadata `2c91462`, `bf2339c`,
`c4d1e2a`.
## 한계와 handoff
- FESA가 Abaqus 내부 formulation, integration, stabilization 또는 result recovery와
동등하다는 주장은 하지 않는다.
- reaction CSV는 review-only, stress CSV는 external pressure reference comparison에서
제외된다. 이는 internal equilibrium/free-residual evidence의 대체물이 아니다.
- UR은 contract상 warning-only이며 이번 실행에서는 numerical warning도 발생하지 않았다.
- Step 7은 Gate 6 evidence만 완료한다. Coordinator는 이를 근거로
`pass-for-physics-evaluation` handoff만 수행할 수 있으며, physics/release decision은
후속 owner의 범위다.
@@ -0,0 +1,103 @@
# Shell Surface Pressure Load 물리 평가
## 1. Metadata
- feature_id: `shell-surface-pressure-load`
- source reference comparison report: `docs/shell-surface-pressure-load/reference-comparison.md`
- source reference model: `docs/shell-surface-pressure-load/reference-model.md`
- status: `pass-for-release-agent`
- owner_agent: `physics-evaluation-agent`
- date: `2026-09-01`
- evaluated commit: `1eb4dce885175b78b8229b095f14cac102ea93df`
이 판정은 승인된 물리 기대와 기존 Gate 6 evidence에 한정한다. Abaqus 내부 formulation,
integration, stabilization 또는 result recovery와의 동등성을 주장하지 않으며 release
readiness를 승인하지 않는다.
## 2. Input Evidence
### 2.1 Gate와 artifact
`reference-comparison.md`는 명시적으로 `pass-for-physics-evaluation` handoff를 권고하고,
blocking `U1/U2/U3` 2,187/2,187 rows와 warning-only `UR1/UR2/UR3` 2,187/2,187 rows가
모두 통과했음을 기록한다. Physics Gate는 그 tolerance 판정을 재승인하지 않고, 통과한
비교 결과를 물리 plausibility의 선행 조건으로만 사용했다.
| evidence | exact path / identity | read-only 확인 |
| --- | --- | --- |
| solver HDF5 | `.harness/build/reference/shell-surface-pressure-load/shellNL-meshfine-pressure_results.h5` | schema `1`, 10 accepted frames, unique factor-one frame `9` / increment `10`; SHA-256 `74f35e3e59edd05460733b1d6367994c7f4a1e847b366f2efad6fbecc03796f3` |
| nonlinear log | `.harness/build/reference/shell-surface-pressure-load/shellNL-meshfine-pressure_log.txt` | factor-one iteration `3`: `combined_pass=1`, `transition=accepted`; SHA-256 `8555ff365dcf1f066813d0cce6440c3d56c9840baf8246696daa7b2e7b0061a1` |
| required input | `reference/shellNL-meshfine-pressure/shellNL-meshfine-pressure.inp` | 56,665 bytes, SHA-256 `53718ccc9b41c23fcc1b509a51d66f1347e87aca43a74f1d6cec4d2085f4b90e` |
| required Abaqus CSV | `reference/shellNL-meshfine-pressure/shellNL-meshfine-pressure displacements.csv` | 729 finite wide rows; SHA-256 `2fe53f98eac9f04779100721c3edf8049eb1dbfe733ebcbd819aeeed03b81198` |
| optional Abaqus CSV | `reference/shellNL-meshfine-pressure/shellNL-meshfine-pressure reactions.csv` | `review-only`; SHA-256 `a2347f4e88e4b73c75abe7c23e96bcc696c16c1975e2440b3ad9bf7ecda5f2b6` |
| excluded Abaqus CSV | `reference/shellNL-meshfine-pressure/shellNL-meshfine-pressure stresses.csv` | `excluded`; SHA-256 `bc06f3324cc5d6655ded2939b3e1cb9d55a0359fa0d272a6958399464218a128` |
| test record | `.harness/build/Testing/Temporary/LastTest.log` | existing automatic Stop evidence에서 full CTest `362/362 PASS`; 관련 physics test의 개별 pass 기록 확인 |
| optional FESA debug CSV views | 없음 | 요구되거나 생성된 debug CSV가 없으며 official output으로 사용하지 않음 |
확인한 authoritative quantities는 factor-one global nodal displacement, generalized external/
internal force, generalized residual, constrained reaction, global equilibrium, physical strain
energy, 기존 MITC4 generalized strain/section resultant/stress다. Model purpose는 729-node,
676-element `S4` square shell의 `SPOS`, positive `P=1e6`, `NLGEOM=YES` current-surface follower
pressure response를 end-to-end로 확인하고, stored case가 직접 포함하지 않는 sign, S4R,
linear, objectivity와 tangent 경계를 승인 internal tests로 보완하는 것이다.
## 3. Physics Checks
| physics check | status | evidence와 판정 |
| --- | --- | --- |
| global equilibrium | `pass` | Factor-one applied force sum은 `[8.626e-12, -7.311e-12, -1.0000000000000016e8]`이고 constrained reaction force sum은 `[-5.047e-6, -3.331e-6, 9.999999999999714e7]`이다. HDF5 stored balance를 approved current-arm scales로 다시 normalize하면 force `6.758e-14`, moment `3.725e-14`로 둘 다 `<=1e-8`이다. |
| reaction consistency | `pass` | Constrained DOF에서 `/nodal/reaction`과 `generalized_residual`은 exact 일치했다. Pressure resultant의 `-Z` `1.0e8`에 대해 masked constrained reaction은 `+Z` `9.999999999999714e7`이며, tiny `X/Y`와 moment remainder는 위 global balance gate 안이다. |
| displacement direction | `pass` | Reference connectivity의 `SPOS` vector area는 `+Z`, positive pressure resultant는 `-Z`다. Factor-one HDF5의 729개 `UZ`는 모두 nonpositive이며 625개 free nodes는 negative, 104개 constrained boundary nodes는 exact zero다. Center node의 `UZ=-5.514459701328648e-3`로 load 방향과 deformation mode가 일치한다. |
| pressure sign, resultant, and moment | `pass` | Current configuration의 quadrature vector-area oracle은 expected force `[-1.723e-12, 2.450e-11, -1.0000000000000025e8]`를 주며 HDF5 applied resultant와의 normalized error는 `8.94e-16`이다. Current-arm applied moment oracle error는 승인 `M_Sigma` 기준 `1.32e-16`이다. Existing `ShellSurfacePressureKernel.PartitionsUnityAndIntegratesPlanarResultantAndMoment`와 `ReversesExactlyForSposSnegAndNegativeMagnitude`도 full CTest 기록에서 pass했다. |
| symmetry and expected zero | `pass` | Feature-blocking exact-zero contract인 pressure generalized external rotational entries는 factor-one HDF5에서 exact zero다. Zero pressure/factor geometry validation test도 pass했다. Stored square patch의 mirror symmetry는 보조 sanity로만 확인했으며 `UX/UY/UZ` mirror mismatch의 최대 절대값은 `1.63e-16`, `1.09e-16`, `8.89e-17` 이하다. 별도 외부 symmetry tolerance를 만들지 않았다. |
| nonlinear follower objectivity and current-area behavior | `pass` | HDF5 resultant는 factor-one current `X+u` vector-area oracle과 위 roundoff 수준으로 일치했다. Current scalar area/reference scalar area ratio는 `1.0000006316335197`로 finite하다. Stored symmetric case만으로 reference/current path를 분리하지 않고, existing tests `UsesCurrentAreaAndNormalForFollowerForce`, `IsTranslationAndRigidRotationObjective`, `IsObjectiveUnderRigidMotionAndTracksCurrentArea`, `MatchesCenteredFiniteDifferenceForForceAndResidualSigns`의 pass evidence로 rigid rotation covariance, `gamma=1.25` area/resultant scaling, follower direction과 external tangent sign을 보완했다. |
| element force balance | `not-separately-applicable` | 이 feature는 pressure-only element force row나 element end-action balance contract를 정의하지 않는다. 대신 HDF5에서 `generalized_internal_force - generalized_external_force - generalized_residual`은 element assembly 이후 nodal full space에서 exact zero이고 global resultant/moment gate가 pass했다. Existing MITC4 section resultants는 finite이며 별도 pressure comparator를 만들지 않았다. |
| stress/strain | `pass-sanity-only` | Factor-one `generalized_strain`, `section_resultant`, `stress`의 component names, units, `shell-local` coordinate system과 `midsurface`/`section-position` location은 `io.md`와 일치하고 모든 값이 finite다. 관측 range는 strain `[-9.842e-4, 1.801e-3]`, section resultant `[-4.595e6, 8.646e6]`, stress `[-1.036e8, 1.039e8]`이다. Feature-specific stress sign/reference expectation은 없고 stress CSV는 `excluded`이므로 그 이상의 pass를 주장하지 않는다. |
| rigid body mode | `pass-no-symptom` | Stored case는 104 boundary nodes의 DOF 1–6을 구속하고 625 interior nodes가 자유다. 모든 accepted frame이 finite하게 수렴했고 factor-one maximum translation은 `5.514e-3`로 10-unit span 대비 `5.514e-4`이며 unbounded translation, near-zero-stiffness 또는 impossible displacement 증상이 없다. Pressure가 support를 제공한다는 주장은 하지 않는다. |
| energy/residual | `pass` | 모든 HDF5 numeric dataset과 required U/UR rows는 finite다. Factor-one free translation residual은 `9.06582420157065e-6 / 1.47929199940215e5 = 6.12849e-11 <=1e-2`; zero-external-scale rotation residual은 `1.3758908607997e-6 <=1e-3`이다. Existing `PHYSICAL_STRAIN_ENERGY=86418.04713575779`는 finite positive 보조 evidence이며 pressure-only energy gate로 사용하지 않았다. HDF5 diagnostics는 result-neutral ignored-keyword warning 4개뿐이고 error/nonfinite diagnostic은 없다. |
| model coverage | `pass` | Stored case의 declared inventory `729 nodes / 676 S4 / SPOS / positive P / NLGEOM=YES`와 unique factor-one frame이 모두 확인됐다. Stored case가 직접 다루지 않는 `S4R`, `SNEG`, negative/zero magnitude, linear reference configuration, exact resultant/moment, current-area objectivity와 tangent는 Numerical Review가 지정한 internal portfolio에서 해당 named tests가 pass했다. 승인 범위 밖의 추가 reference portfolio는 요구하지 않는다. |
## 4. Failure Classification
- failure classification: `none`
- 다음 분류는 발생하지 않았다: `equilibrium-failure`, `reaction-inconsistency`,
`displacement-direction-failure`, `symmetry-failure`, `stress-location-failure`,
`element-force-inconsistency`, `rigid-body-mode-suspected`, `nonfinite-result`,
`model-coverage-gap`, `upstream-contract`, `environment`.
`element force balance`의 `not-separately-applicable`과 stress의 `pass-sanity-only`는 upstream
contract가 의도적으로 pressure-only element/stress acceptance family를 만들지 않은 데 따른
범위 표시이며 failure 또는 evidence gap이 아니다.
## 5. Evaluation Verdict
**Verdict: `pass-for-release-agent`.**
문서화된 pressure 방향/부호, resultant/moment, reaction/equilibrium, exact-zero와 보조 symmetry,
nonlinear follower objectivity/current-area, residual/nonfinite 및 declared model coverage가 모두
물리적으로 일관된다. Release Agent가 release readiness를 별도로 audit할 수 있다. 이 verdict는
release approval, publish 권한 또는 reference tolerance의 재승인이 아니다.
## 6. Handoff Recommendation
- handoff target: `Release Agent`
- route: 항상 `Coordinator Agent`를 통해 전달
- recommendation: Gate 8은 requirements-to-physics traceability, known limitations와 release
evidence를 독립 audit한다. Physics Evaluation Agent는 Gate 8을 시작하거나 승인하지 않는다.
## 7. No-Change Assertion
이 평가에서 source, header, test, CMake, Harness, phase files, `coordination.md`, upstream feature
contracts, implementation reports, reference input/CSV artifacts, `results.h5`, nonlinear log,
`docs/TOLERANCE.md`와 tolerance policy를 수정하지 않았다. 생성한 파일은
`docs/shell-surface-pressure-load/physics-evaluation.md` 하나뿐이다.
## 8. Open Issues
- missing physical expectations: 없음.
- incomplete model coverage: 없음. Stored-case 한계는 승인 internal tests로 보완되며 추가
reference case는 계약상 요구되지 않는다.
- contradictory sign conventions: 없음.
- unavailable energy/residual evidence: 없음. Existing energy와 residual HDF5/log evidence를
사용했으며 새 pressure-only energy quantity는 요구되지 않는다.
- blockers: 없음.
@@ -0,0 +1,122 @@
# Shell Surface Pressure Load — Reference Comparison 보고서
## 메타데이터와 Gate 6 판정
- feature_id: `shell-surface-pressure-load`
- case_id: `shellnl-meshfine-pressure-s4-follower`
- owner_agent: `implementation-agent`
- FESA authoritative output: schema-1
`.harness/build/reference/shell-surface-pressure-load/shellNL-meshfine-pressure_results.h5`
- 실행 모드: 요구 input의 byte-identical copy에 대한 FESA-only 실행
- tolerance authority: 변경하지 않은 `docs/TOLERANCE.md`
- deterministic classification: `passed`
- blocking verdict: pass
- warning-only verdict: pass (UR warning 없음)
이 문서는 Coordinator에 `pass-for-physics-evaluation`만 권고한다. Abaqus 내부 알고리즘
동등성, Physics Gate 7 또는 release readiness를 주장하지 않는다.
## ARTIFACT CHECK와 불변성
아래 source artifact는 FESA-only 실행 전후에 size, SHA-256 및 기록 timestamp가 모두
동일했다. Reference artifact의 rename, rewrite, 보정 또는 생성은 없었다.
| 용도 | 정확한 source path | bytes | SHA-256 |
| --- | --- | ---: | --- |
| required input | `reference/shellNL-meshfine-pressure/shellNL-meshfine-pressure.inp` | 56,665 | `53718ccc9b41c23fcc1b509a51d66f1347e87aca43a74f1d6cec4d2085f4b90e` |
| required displacement CSV | `reference/shellNL-meshfine-pressure/shellNL-meshfine-pressure displacements.csv` | 111,182 | `2fe53f98eac9f04779100721c3edf8049eb1dbfe733ebcbd819aeeed03b81198` |
| review-only reaction CSV | `reference/shellNL-meshfine-pressure/shellNL-meshfine-pressure reactions.csv` | 108,923 | `a2347f4e88e4b73c75abe7c23e96bcc696c16c1975e2440b3ad9bf7ecda5f2b6` |
| excluded stress CSV | `reference/shellNL-meshfine-pressure/shellNL-meshfine-pressure stresses.csv` | 1,036,876 | `bc06f3324cc5d6655ded2939b3e1cb9d55a0359fa0d272a6958399464218a128` |
유일하게 copy한 source artifact는 다음 exact input이다.
```text
.harness/build/reference/shell-surface-pressure-load/
shellNL-meshfine-pressure.inp
```
copy의 size/SHA-256은 required input과 정확히 일치한다. Source history와 실행 command를
감사했으며 `abaqus.exe`, `abaqus.bat`, Nastran 또는 다른 reference solver invocation은
없었다. source/path text의 `abaqus` parser 명칭은 실행 증거가 아니다.
## FESA 생성물과 frame 선택
| 생성물 | bytes | SHA-256 | 검증 증거 |
| --- | ---: | --- | --- |
| copied input | 56,665 | `53718ccc9b41c23fcc1b509a51d66f1347e87aca43a74f1d6cec4d2085f4b90e` | source와 exact match |
| `shellNL-meshfine-pressure_results.h5` | 10,547,430 | `74f35e3e59edd05460733b1d6367994c7f4a1e847b366f2efad6fbecc03796f3` | atomic publish된 schema-1 HDF5 |
| `shellNL-meshfine-pressure_log.txt` | 40,786 | `8555ff365dcf1f066813d0cce6440c3d56c9840baf8246696daa7b2e7b0061a1` | factor-one increment accepted |
`Step-1`에는 load factor 0.1부터 1.0까지 accepted frame 10개가 있다. factor-one
entry는 frame index 9, increment 10, `load_factor=1.0`, `is_factor_one=1`의 정확히
하나이며, selected dataset은
`/steps/Step-1/frames/9/nodal/displacement`다.
## COMPARE 사전검사와 row projection
비교기는 `/model/nodes`와 factor-one final nodal displacement를 사용한다. Key는 row
position 또는 internal index가 아니라 `(Step-1 factor-one accepted frame, instance,
source node label, component)`다.
| 사전검사 | 결과 |
| --- | --- |
| exact source artifact를 읽을 수 있음 | pass |
| schema / Step / accepted factor-one frame | pass: schema 1, `Step-1`, factor-one 1개 |
| HDF5 `/model/nodes` inventory | pass: finite/unique `(Part-1-1, label)` 729개 |
| displacement CSV wide-row inventory | pass: finite/unique `(PART-1-1, label)` 729개 |
| identity normalization | pass: comparison key에서만 instance ASCII case normalize; stored identity 보존 |
| component projection | pass: U1/U2/U3 -> UX/UY/UZ, UR1/UR2/UR3 -> URX/URY/URZ |
| U logical rows | pass: 정확히 2,187 |
| UR logical rows | pass: 정확히 2,187 |
| missing / extra / duplicate / nonfinite | 없음; tolerance 전에 fail-closed precheck |
| reaction/stress disposition | reaction review-only, stress excluded; comparison family를 생성하지 않음 |
생성 HDF5가 존재한 뒤 `ShellSurfacePressureReference`를 다시 실행하여 9/9가 통과했다.
이는 exact artifact audit, schema/frame selection, cardinality, identity match,
precheck-before-tolerance, family policy와 deterministic repeat comparison을 확인한다.
## 공통 tolerance와 per-family 결정
Reference만으로 family scale과 near-zero band를 계산했다.
\[
S=\max |r_i|,\qquad B=0.01S.
\]
`abs(reference) <= B`이면 `abs(fesa-reference) <= B`, 나머지는 relative error
`<= 0.05`를 적용하고 family relative RMS도 `<= 0.01`이어야 한다. 별도의 absolute
gate, `max(1,S)`, clamp, average, row omission은 사용하지 않았다.
| family | 영향 | components | rows | S | B | near-zero pass | relative pass | max abs. error / row | max applicable relative error / row | relative RMS | verdict |
| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | --- | --- | ---: | --- |
| `pressure_nodal_translation_blocking` | blocking | U1, U2, U3 | 2,187 | `5.51545573e-3` | `5.51545573e-5` | 1,582/1,582 | 605/605 | `9.96028671352255e-7`, `(Part-1-1, 2, U3)` | `3.12464512172274e-3`, `(Part-1-1, 419, U3)` | `5.18831997081359e-5` | pass |
| `pressure_nodal_rotation_warning` | warning-only | UR1, UR2, UR3 | 2,187 | `1.64645887e-3` | `1.64645887e-5` | 995/995 | 1,192/1,192 | `3.29623883250936e-6`, `(Part-1-1, 250, UR2)` | `3.81566175195545e-2`, `(Part-1-1, 286, UR2)` | `3.10406152731217e-4` | pass |
결정적인 4,374개의 모든 row decision이 통과했다. max absolute-error row와 max
applicable-relative-error row는 별도로 기록하며 서로 다른 source row일 수 있다.
두 family RMS도 모두 `0.01` 미만이다. U는 blocking으로 pass했고, UR은 warning-only
contract를 유지하며 이번 결과는 pass여서 deterministic warning도 없다.
## 내부 blocking evidence
최종 nonlinear log는 factor-one increment 10 / iteration 3에서
`combined_pass=1`, `transition=accepted`를 기록했다. 이 internal evidence는 external
CSV tolerance와 독립적이며 reaction CSV로 대체하지 않는다.
| internal family | rows | numerator / stored evidence | scale 또는 branch | metric / gate | 결과 |
| --- | ---: | --- | --- | --- | --- |
| accepted free translation-force residual | free DOF 1,875 | infinity norm `9.06582420157065e-6` | external/internal scale `1.47929199940215e5` | `6.12848863188238e-11 <= 1e-2` | pass |
| accepted free rotation-moment residual | free DOF 1,875 | infinity norm `1.3758908607997e-6` | exact-zero external-scale branch | `1.3758908607997e-6 <= 1e-3` | pass |
| global force balance | 3 | `[-5.04652281563267e-6, -3.33100482574622e-6, -3.02493572235107e-6]` | applied + masked constrained reaction, current arm | approved normalized gate `<=1e-8`, focused nonlinear test가 실행 | pass |
| global moment balance | 3 | `[1.21226185001433e-6, 7.79749825596809e-7, -1.57924777074714e-5]` | spatial physical moment, current arm | approved normalized gate `<=1e-8`, 같은 focused test가 실행 | pass |
## 분류, no-change assertion, open issues
- final classification: `passed`; `implementation`, `test`, `configure`, `compile`,
`link`, `reference-comparison`, `infrastructure`, `environment`, `artifact`,
`regression`, `evidence-gap`, `upstream-contract` failure가 없다.
- source artifact/CSV value/name, tolerance policy, upstream feature docs,
source/test/CMake code를 이 Step에서 변경하지 않았다.
- generated input/HDF5/log는 `.harness/build/` 아래에만 존재한다.
- Gate 6 blocking open issue는 없다. physics plausibility와 release readiness는
의도적으로 이 Step의 판정 범위 밖이다.
+242
View File
@@ -0,0 +1,242 @@
# Shell Surface Pressure Load 릴리스 준비 보고서
## 1. Metadata
- feature_id: `shell-surface-pressure-load`
- status: `ready-for-release`
- owner_agent: `release-agent`
- date: `2026-09-01`
- audited_head: `e5e8ccc8ff84a97e64ab2bc1889b5ab7aaae1c77`
- physics_evaluated_commit: `1eb4dce885175b78b8229b095f14cac102ea93df`
- workflow_basis: Gates 1–7 Coordinator-audited passed
- authoritative_output: input lexical sibling `<stem>_results.h5`
- tolerance_authority: `docs/TOLERANCE.md`
- source_docs: `requirements.md`, `research.md`, `formulation.md`, `numerical-review.md`, `reference-model.md`, `io.md`, `implementation-plan.md`
- source_reports: `implementation-report.md`, `build-test.md`, `reference-comparison.md`, `corrections.md`, `physics-evaluation.md`
본 판정은 FESA 기능 전달 workflow의 내부 release readiness에 한정한다. Publish, deploy,
package, tag, merge, commit, push 또는 외부 배포 권한을 부여하지 않는다.
## 2. Release Scope
| 구분 | 포함 범위 |
| --- | --- |
| 기능 | Assembly-level `*SURFACE, TYPE=ELEMENT, NAME=<name>`의 `SPOS|SNEG` member에 step-level `*DSLOAD`의 uniform finite scalar `P`를 적용 |
| analysis type | 단일 linear static의 reference-configuration pressure, 단일 `NLGEOM=YES` full-Newton nonlinear static의 current-configuration follower pressure와 external tangent |
| elements | Source type과 label을 보존하는 four-node `S4`, `S4R`; 기존 FESA MITC4 선택 |
| materials/sections | 기존 linear isotropic elastic MITC4 shell material/section; 이 기능은 material 범위를 확장하지 않음 |
| load semantics | Positive `P`는 selected surface normal 반대 방향, negative `P`는 방향 반전, load factor는 정확히 한 번 적용 |
| assembly/physics | Stable full-DOF external load/tangent assembly, free/constrained projection, reaction/free residual, current-arm global force/moment equilibrium, deterministic ordering |
| input subset | Direct `<instance>.<element-label>` member와 explicit `INSTANCE=`에 결속된 flat assembly ELSET member; case-insensitive lookup과 stable raw/source identity |
| result scope | 기존 schema `0`/`1` HDF5의 displacement, generalized force/residual, reaction, global equilibrium 및 기존 MITC4 result chain; 새 pressure-only dataset 없음 |
| reference scope | Exact stored nonlinear S4/SPOS/positive-`P` case의 factor-one nodal `U1/U2/U3` blocking, `UR1/UR2/UR3` warning-only comparison |
| artifact scope | Runtime authoritative artifact는 `<stem>_results.h5`; CSV는 external reference/debug evidence이며 FESA product output이 아님 |
Release 범위에서 제외되는 핵심 항목은 `*DLOAD`, nonuniform/field/user pressure,
node/edge/double-sided/contact surface, S4/S4R 이외 element, material nonlinearity, dynamics,
thermal/contact/buckling, multiple-step propagation, surface offset/thickness-side correction,
pressure-only result quantity 및 다른 solver 내부 알고리즘과의 동등성 주장이다.
## 3. Gate Evidence Inventory
| Gate evidence | feature/status 감사 결과 | release disposition |
| --- | --- | --- |
| `requirements.md` | 동일 feature, must `001`–`024`; 초기 `draft-for-coordinator-gate-review` label을 유지하지만 Coordinator Gate 1 통과 및 이후 전 단계의 binding baseline으로 사용됨 | pass |
| `research.md` | 동일 feature; 초기 gate-review label을 유지하며 formulation/reference 범위와 non-equivalence 경계가 후속 문서에 반영됨 | pass |
| `formulation.md` | `ready-for-numerical-review`; force/tangent/sign/configuration/assembly 계약이 `numerical-review.md`에서 독립 승인됨 | pass |
| `numerical-review.md` | `pass-for-io-definition`; `tau_J=1.0e-12`, analytical/objectivity/tangent/equilibrium gate 확정 | pass |
| `reference-model.md` | `pass-for-io-definition`; exact required input/CSV, row identity, U/UR disposition과 immutable artifact contract 확정 | pass |
| `io.md` | `ready-for-implementation-planning`; input subset, semantic identity, diagnostics, schema `0`/`1`, HDF5-to-CSV projection 확정 | pass |
| `implementation-plan.md` | archival pre-execution label은 `draft-awaiting-user-approval`; 승인 후 materialized Steps 1–7 실행 및 Gate 6 보고서로 plan 이행이 입증됨 | pass |
| `implementation-report.md` | `pass-for-physics-evaluation` 권고; TDD Steps 1–6와 final evidence audit 완료 | pass |
| `build-test.md` | focused `64/64`, pressure reference `9/9`, automatic Stop full MSVC/CTest `362/362`, `0 failed`, 새 `/W4 /WX` warning 없음 | pass |
| `reference-comparison.md` | explicit `pass-for-physics-evaluation`; precheck 통과, U blocking `2187/2187`, UR warning-only `2187/2187`, 두 family row/RMS 모두 pass | pass |
| `corrections.md` | 중복 full-verification Harness timeout을 implementation defect와 분리하고 단일 Stop owner로 교정; 이후 full `362/362`로 해소됨 | resolved/pass |
| `physics-evaluation.md` | `pass-for-release-agent`; force/moment normalized balance `6.758e-14`/`3.725e-14 <= 1e-8`, blocker 없음 | pass |
Staleness 감사에서 Physics Agent가 평가한 `1eb4dce`는 현재 HEAD의 ancestor이며, 그 이후
변경은 `coordination.md`와 `physics-evaluation.md`뿐이다. 평가 후 source, test, CMake,
reference artifact 또는 tolerance 변경은 없다. 초기 문서의 archival status label과
`implementation-report.md`의 compact requirement/evidence shorthand는 Coordinator 승인,
`implementation-plan.md` §11, 실제 named-suite inventory, reference 및 physics evidence로
교차 확인했다. Release acceptance를 바꾸는 미해결 상태·범위·수치 모순은 없다.
### Release Checklist
- [x] 필수 Gate 1–7 문서가 모두 존재하고 `feature_id`가 일치한다.
- [x] Full MSVC x64 Debug build/CTest evidence가 `362/362 PASS`, `0 failed`다.
- [x] `reference-comparison.md`가 `pass-for-physics-evaluation`이고 required comparison file이 모두 존재한다.
- [x] `physics-evaluation.md`가 `pass-for-release-agent`다.
- [x] Must requirement `24/24`가 acceptance, test/internal model, required external reference 및 release disposition에 연결된다.
- [x] Exact input/CSV hash와 path가 구현 전후 동일하다.
- [x] Blocking U family, warning-only UR family 및 internal equilibrium evidence가 모두 pass했다.
- [x] Unsupported/deferred 범위와 accepted limitations를 사용자-facing하게 기록했다.
- [x] Unresolved implementation defect, missing declared comparison file, tolerance 변경 또는 upstream decision이 없다.
- [x] 이 판정이 외부 release 권한이 아님을 명시했다.
## 4. Acceptance Traceability
`test id`는 `implementation-plan.md`의 TDD ID와 실제 passing named suite를 함께 나타낸다.
`BM-*`는 `research.md`의 bounded verification model이며, external case가 필요한 항목에는
`shellnl-meshfine-pressure-s4-follower`를 명시한다.
| requirement id | compact acceptance criterion | test id / passing evidence | reference model id | verification report | release disposition |
| --- | --- | --- | --- | --- | --- |
| `FESA-REQ-SHELL-PRESSURE-001` | Approved SURFACE+DSLOAD S4/S4R mapping과 identity 보존 | `T-INPUT-VALID`; `InpSyntax`, `ShellSurfacePressureInput` | `BM-07` + internal valid fixtures | `build-test.md` | included/pass |
| `FESA-REQ-SHELL-PRESSURE-002` | Positive/zero/negative finite scalar exact-once, nonfinite reject | `T-INPUT-VALID`, `T-INPUT-NEG`; `ShellSurfacePressureDomain` | `BM-01`, `BM-08` | `build-test.md` | included/pass |
| `FESA-REQ-SHELL-PRESSURE-003` | Unique deterministic instance/element/side inventory와 order | `T-INPUT-VALID`, `T-ASSEMBLY`; `SurfacePressureAssembly` | `BM-03` | `build-test.md` | included/pass |
| `FESA-REQ-SHELL-PRESSURE-004` | Only S4/S4R and SPOS/SNEG; source type/label 보존 | `T-INPUT-VALID`, `T-INPUT-NEG`; target rejection cases | `BM-01`, `BM-08` | `build-test.md` | constraint/pass |
| `FESA-REQ-SHELL-PRESSURE-005` | Positive pressure는 selected normal 반대, negative는 정확히 반전 | `T-KERNEL-FORCE`; `ReversesExactlyForSposSnegAndNegativeMagnitude` | `BM-01` | `build-test.md`, `physics-evaluation.md` | physics/pass |
| `FESA-REQ-SHELL-PRESSURE-006` | Connectivity-derived SPOS normal과 SNEG inverse | `T-KERNEL-FORCE`; sign/connectivity kernel cases | `BM-01`, `BM-02` | `build-test.md`, `physics-evaluation.md` | physics/pass |
| `FESA-REQ-SHELL-PRESSURE-007` | Linear pressure는 reference area/normal에서 displacement-independent | `T-KERNEL-FORCE`, `T-LINEAR`; `LinearShellSurfacePressure` | `BM-01`, `BM-02` | `build-test.md` | included/pass |
| `FESA-REQ-SHELL-PRESSURE-008` | NLGEOM pressure는 current `X+u` follower load | `T-KERNEL-FORCE`, `T-NONLINEAR`; current-area/objectivity cases | `BM-04`, `BM-07` | `build-test.md`, `physics-evaluation.md` | physics/pass |
| `FESA-REQ-SHELL-PRESSURE-009` | External follower-force derivative가 Newton tangent에 포함 | `T-KERNEL-TANGENT`, `T-NONLINEAR`; centered FD/sign cases | `BM-05`, `BM-06` | `build-test.md`, `physics-evaluation.md` | numerical/pass |
| `FESA-REQ-SHELL-PRESSURE-010` | Step load factor `0/0.5/1`을 정확히 한 번 적용 | `T-ASSEMBLY`, `T-LINEAR`, `T-NONLINEAR`; factor cases | `BM-03`, `BM-06` | `build-test.md` | numerical/pass |
| `FESA-REQ-SHELL-PRESSURE-011` | Stable full-DOF external load/tangent 후 projection | `T-ASSEMBLY`, `T-LINEAR`; full-space/order cases | `BM-03` | `build-test.md` | included/pass |
| `FESA-REQ-SHELL-PRESSURE-012` | Pressure가 constrained reaction과 free residual에 포함 | `T-LINEAR`, `T-NONLINEAR`; reaction/residual/balance cases | `BM-01`, `BM-06`, `BM-07` | `reference-comparison.md`, `physics-evaluation.md` | physics/pass |
| `FESA-REQ-SHELL-PRESSURE-013` | Resolution/reduction/result/diagnostic/comparison order 결정적 | `T-INPUT-VALID`, `T-ASSEMBLY`, `T-NONLINEAR`, `T-REF-COMPARE` | `BM-03`, `BM-07` | `build-test.md`, `reference-comparison.md` | constraint/pass |
| `FESA-REQ-SHELL-PRESSURE-014` | Unsupported SURFACE/DSLOAD variant fail-closed | `T-INPUT-NEG`; `ShellSurfacePressureInput` negative matrix | `BM-08` | `build-test.md` | constraint/pass |
| `FESA-REQ-SHELL-PRESSURE-015` | Missing/empty/ambiguous/conflicting/scope target fail-closed | `T-INPUT-NEG`; mapping failure/no-partial-candidate cases | `BM-08` | `build-test.md` | constraint/pass |
| `FESA-REQ-SHELL-PRESSURE-016` | Unsupported procedure, nonfinite, invalid geometry fail-closed | `T-INPUT-NEG`, `T-KERNEL-TANGENT`, `T-NONLINEAR`; rollback cases | `BM-04`, `BM-05`, `BM-08` | `build-test.md` | constraint/pass |
| `FESA-REQ-SHELL-PRESSURE-017` | Stable diagnostics와 failed candidate/output atomicity | `T-INPUT-NEG`, `T-CLI-HDF5`, `T-NONLINEAR`; final preservation cases | `BM-08` | `build-test.md` | output/pass |
| `FESA-REQ-SHELL-PRESSURE-018` | 기존 schema `0/1` HDF5만 authoritative; 새 pressure dataset 없음 | `T-CLI-HDF5`; schema/no-new-path writer regressions | `BM-07` | `implementation-report.md`, `build-test.md` | output/pass |
| `FESA-REQ-SHELL-PRESSURE-019` | Existing displacement/reaction/residual/equilibrium/result chain finite | `T-LINEAR`, `T-NONLINEAR`, `T-REF-COMPARE` | `BM-06`, `BM-07` | `reference-comparison.md`, `physics-evaluation.md` | output/pass |
| `FESA-REQ-SHELL-PRESSURE-020` | Linear S4/S4R sign/resultant/reaction/mesh-order suite 통과 | `T-KERNEL-FORCE`, `T-LINEAR`; analytical suites | `BM-01`–`BM-03` | `build-test.md`, `physics-evaluation.md` | verification/pass |
| `FESA-REQ-SHELL-PRESSURE-021` | Objectivity/current-area/tangent/residual/global balance 통과 | `T-KERNEL-TANGENT`, `T-NONLINEAR`; force/moment `<=1e-8` | `BM-04`–`BM-06`, `BM-07` | `reference-comparison.md`, `physics-evaluation.md` | verification/pass |
| `FESA-REQ-SHELL-PRESSURE-022` | Exact required input/CSV를 current path에서 read-only 사용 | `T-REF-PRECHECK`, `E-FINAL`; hash/size pre/post match | `shellnl-meshfine-pressure-s4-follower` | `reference-comparison.md` | verification/pass |
| `FESA-REQ-SHELL-PRESSURE-023` | Factor-one source-key mapping, U blocking/UR warning, precheck first | `T-REF-PRECHECK`, `T-REF-COMPARE`, `E-FINAL`; `2187+2187` | `shellnl-meshfine-pressure-s4-follower` | `reference-comparison.md` | verification/pass |
| `FESA-REQ-SHELL-PRESSURE-024` | Reaction CSV review-only, stress CSV excluded, internal balance 유지 | `T-REF-COMPARE`; no reaction/stress verdict family | `shellnl-meshfine-pressure-s4-follower` | `reference-comparison.md`, `physics-evaluation.md` | constraint/pass |
Trace result: must requirements `24`, release-scoped rows `24`, orphan `0`, deferred must `0`,
missing feature-required comparison files `0`.
## 5. Validation Evidence
| evidence | command / source | result |
| --- | --- | --- |
| Configure | `cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 ...` | exit `0`; MSVC 19.51.36252.0 |
| Focused build | `cmake --build .harness/build --config Debug --target fesa_unit_tests fesa_integration_tests fesa_reference_tests` | exit `0`; new warning `0` |
| Discovery | `ctest --test-dir .harness/build -C Debug --show-only=json-v1` | `362` discovered, feature suites nonzero |
| Focused CTest | `ctest --test-dir .harness/build -C Debug --output-on-failure -R "^(InpSyntax|ShellSurfacePressureInput|ShellSurfacePressureDomain|ShellSurfacePressureKernel|SurfacePressureAssembly|LinearShellSurfacePressure|NonlinearShellSurfacePressure|ShellSurfacePressureApplication|ShellSurfacePressureReference)\."` | `64/64 PASS` |
| FESA reference execution | `fesa.exe .harness/build/reference/shell-surface-pressure-load/shellNL-meshfine-pressure.inp` | exit `0`; factor-one increment accepted; schema-1 HDF5 atomic publish |
| Pressure comparison | `ctest --test-dir .harness/build -C Debug --output-on-failure -R "^ShellSurfacePressureReference\."` | `9/9 PASS` before and after generated HDF5 |
| Automatic Stop build | `cmake --build .harness/build --config Debug` | PASS |
| Automatic Stop full CTest | `ctest --test-dir .harness/build -C Debug --output-on-failure` | `362/362 PASS`, `0 failed` |
| Harness Python pytest | N/A | Harness/Python behavior 및 file 변경이 없어 upstream plan/report에서 적용 대상이 아님 |
Reference artifact pre/post evidence:
- Required input: SHA-256 `53718ccc9b41c23fcc1b509a51d66f1347e87aca43a74f1d6cec4d2085f4b90e`, `56,665` bytes, unchanged.
- Required displacement CSV: SHA-256 `2fe53f98eac9f04779100721c3edf8049eb1dbfe733ebcbd819aeeed03b81198`, `111,182` bytes, unchanged.
- `pressure_nodal_translation_blocking`: `2187/2187` rows pass, relative RMS `5.18831997081359e-5 <= 0.01`.
- `pressure_nodal_rotation_warning`: `2187/2187` rows pass, relative RMS `3.10406152731217e-4 <= 0.01`; warning `0`.
- Accepted free translation residual: `6.12848863188238e-11 <= 1e-2`.
- Accepted free rotation residual: zero-external-scale branch `1.3758908607997e-6 <= 1e-3`.
- Physics re-evaluation: normalized force balance `6.758e-14`, moment balance `3.725e-14`, each `<=1e-8`.
## 6. Known Limitations
1. 입력은 assembly-level `TYPE=ELEMENT` surface와 uniform `DSLOAD P`의 승인 subset뿐이다.
`*DLOAD`, `P n`, `PNU`, `HP`, `VP`, `SP`, `TRVEC`, `TRSHR`, nonuniform/user pressure,
`AMPLITUDE`, `FOLLOWER`, `OP`, `REF NODE`, `*INCLUDE`와 승인되지 않은 parameter는 지원하지 않는다.
2. Surface는 single-sided `SPOS|SNEG` four-node `S4`/`S4R`만 지원한다. Node/edge/double-sided,
analytical/contact surface, part-level surface, implicit/mixed-instance ELSET, transformed instance,
S3/S8/solid/continuum-shell/membrane/beam/rigid element는 범위 밖이다.
3. 단일 linear static 또는 단일 approved `NLGEOM=YES` full-Newton static step만 지원한다.
Dynamics, thermal, contact, buckling perturbation, cavity/pipe pressure와 multiple-step propagation은 제외된다.
4. 기존 linear isotropic elastic MITC4 material/section만 사용한다. Material nonlinearity,
composite/hyperelastic/plastic section, surface offset, thickness-face arm/correction 및 pressure
distributed couple은 지원하지 않는다.
5. Pressure는 midsurface translational generalized force를 만든다. Rotational pressure entries는
exact zero이며 physical global moment는 force arm으로 평가한다. Invalid/collapsed/folded current
surface는 `tau_J=1.0e-12` gate에서 fail-closed한다.
6. Authoritative output은 기존 `<stem>_results.h5`뿐이며 surface/load 또는 pressure-only dataset은
추가되지 않는다. CSV는 product output이 아니다.
7. Stored external case는 `S4`, `SPOS`, positive `P=1e6`, `NLGEOM=YES` nonlinear displacement만
직접 검증한다. `S4R`, `SNEG`, negative/zero pressure, linear path, objectivity와 tangent는
passing blocking internal tests로 보완되며 별도 external reference case는 없다.
8. `UR1/UR2/UR3` external comparison은 contract상 `warning-only`다. 이번 evidence에서는 모두
통과했지만 향후 warning-only mismatch는 blocking U verdict를 바꾸지 않는다.
9. Reaction CSV는 `review-only`, stress CSV는 `excluded`다. Local reaction distribution이나
stress agreement을 external acceptance로 주장하지 않으며 internal residual/equilibrium gate를 사용한다.
10. 직접 적용 가능한 public S4/S4R follower-pressure benchmark가 없다는 연구 한계가 있다.
Analytical/kernel/solution tests와 exact stored case로 보완했으며 실험 validation을 주장하지 않는다.
11. FESA는 독립 solver다. Abaqus를 포함한 다른 solver의 내부 formulation, quadrature,
stabilization, tangent 또는 result-generation equivalence를 주장하지 않는다.
Deferred must requirement, unresolved defect, accepted blocking risk 또는 release-blocking open item은 없다.
## 7. Release Notes Draft
### 사용자-facing 요약
FESA는 이제 승인된 Abaqus `.inp` subset에서 assembly named element surface의 `S4`/`S4R`
`SPOS` 또는 `SNEG` 면에 uniform `*DSLOAD P`를 적용할 수 있다. Linear static에서는 reference
surface를, `NLGEOM=YES` nonlinear static에서는 current surface follower force와 consistent
external tangent를 사용한다. Pressure는 기존 full-space load, reaction/free residual,
global equilibrium 및 HDF5 result lifecycle에 통합된다.
### 검증 범위
- MSVC x64 Debug full CTest `362/362 PASS`, focused feature tests `64/64 PASS`.
- Exact stored nonlinear case에서 factor-one U blocking `2187/2187`, UR warning-only
`2187/2187` rows pass.
- Factor-one normalized global force/moment balance는 각각 `6.758e-14`, `3.725e-14`로
승인 gate `1e-8`을 통과했다.
- S4R, side/sign reversal, linear reference pressure, current-area objectivity, external tangent와
invalid geometry는 독립 internal analytical/FD/fail-closed tests로 검증했다.
### 주요 제한 및 사용 주의
이 release는 single-step linear 또는 approved nonlinear static, four-node S4/S4R, existing
linear-isotropic MITC4 section과 uniform single-sided pressure만 지원한다. `*DLOAD`, nonuniform
pressure, amplitude, node/edge/contact/double-sided surface, 다른 element/procedure/material 및
surface offset/thickness correction은 지원하지 않는다. Model-consistent unit system을 사용해야
하며 invalid current surface geometry는 fail-closed한다. UR은 warning-only이고 reaction/stress
CSV는 external verdict 대상이 아니다. Abaqus 내부 동등성이나 실험 validation은 주장하지 않는다.
### Artifact paths
- Runtime authoritative output: input lexical sibling `<stem>_results.h5`
- Required reference input: `reference/shellNL-meshfine-pressure/shellNL-meshfine-pressure.inp`
- Required comparison CSV: `reference/shellNL-meshfine-pressure/shellNL-meshfine-pressure displacements.csv`
- Release record: `docs/shell-surface-pressure-load/release.md`
## 8. Release Verdict
**Verdict: `ready-for-release`.**
필수 upstream gate, full build/test, blocking reference comparison과 physics evaluation이 모두
통과했다. Must requirement `24/24`가 release scope, acceptance criterion, implementation/test
또는 applicable reference evidence와 최종 disposition에 연결되고, known limitations가
release notes에 포함되었다. Missing/failed/stale required evidence, unresolved defect,
missing comparison file, tolerance conflict 또는 physics blocker는 없다.
이 verdict는 FESA 내부 feature delivery closure recommendation이며 외부 release 행위를
승인하지 않는다.
## 9. Handoff Recommendation
Coordinator Agent는 Gate 8을 `passed`로 기록하고 `shell-surface-pressure-load` workflow를
내부적으로 closure하는 것을 권고한다. 추가 Correction, Implementation, Physics Evaluation
또는 upstream decision return은 필요하지 않다. Publish, deploy, package, tag, merge, commit,
push 또는 external release는 별도 명시적 권한 없이는 수행하지 않는다.
## 10. No-Change Assertion
Release Agent는 이 감사에서 `docs/shell-surface-pressure-load/release.md`만 생성했다. Source,
public/private header, test, CMake, build/Harness configuration, phase files, upstream feature
documents/reports, `coordination.md`, reference input/CSV, generated HDF5/log,
`docs/TOLERANCE.md` 및 tolerance policy는 수정하지 않았다. Abaqus, Nastran 또는 다른
reference solver를 실행하지 않았고 build, CTest, Harness 또는 FESA CLI를 재실행하지 않았다.
## 11. Open Issues
- Missing evidence: 없음.
- Contradictory upstream verdict/status/value: 없음. Archival pre-gate metadata와 compact
trace shorthand는 Coordinator 승인 및 actual passing evidence로 교차 확인되었다.
- Unresolved defects or failed corrections: 없음. Harness duplicate-verification timeout correction은
이후 automatic Stop full `362/362 PASS`로 해소되었다.
- Missing declared comparison files or mappings: 없음.
- Release documentation gap: 없음.
- Blockers: 없음.
+3 -2
View File
@@ -21,7 +21,8 @@
},
{
"dir": "shell-surface-pressure-load",
"status": "pending"
"status": "completed",
"completed_at": "2026-09-01T17:43:57+0900"
}
]
}
}
+14 -4
View File
@@ -37,18 +37,28 @@
{
"step": 5,
"name": "integrate-nonlinear-follower-pressure",
"status": "pending"
"status": "completed",
"started_at": "2026-09-01T15:03:28+0900",
"summary": "Integrated current-configuration follower pressure force and exact external tangent into nonlinear full-Newton trial assembly with schema-1 regression coverage.",
"completed_at": "2026-09-01T15:48:52+0900"
},
{
"step": 6,
"name": "compare-pressure-reference",
"status": "pending"
"status": "completed",
"started_at": "2026-09-01T15:48:52+0900",
"summary": "Added deterministic schema-1 pressure-reference comparison with exact artifact checks, U blocking/UR warning-only projection, and common-tolerance coverage.",
"completed_at": "2026-09-01T17:00:55+0900"
},
{
"step": 7,
"name": "verify-and-report-pressure-feature",
"status": "pending"
"status": "completed",
"started_at": "2026-09-01T17:00:55+0900",
"summary": "Completed Gate 6 pressure reports with fresh MSVC focused validation, immutable artifact audit, FESA-only schema-1 comparison, and U/UR reference evidence.",
"completed_at": "2026-09-01T17:43:56+0900"
}
],
"created_at": "2026-09-01T01:29:29+0900"
"created_at": "2026-09-01T01:29:29+0900",
"completed_at": "2026-09-01T17:43:57+0900"
}
@@ -15,6 +15,7 @@
#include "fesa/analysis/newton_iteration_log.h"
#include "fesa/analysis/nonlinear_static_control.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/assembly/surface_pressure_assembler.h"
#include "fesa/constraints/essential_constraint_policy.h"
#include "fesa/elements/finite_rotation.h"
#include "fesa/elements/mitc4_shell.h"
@@ -453,6 +454,76 @@ Result<SparseMatrix> Difference(const SparseMatrix& internal,
dofs.GetSparsePattern());
}
/// @brief Adds fixed and follower external candidates in stable CSR order.
Result<GlobalFixedLoadContribution> CombineExternalContributions(
const GlobalFixedLoadContribution& fixed,
const SurfacePressureAssemblyCandidate& pressure, const DofManager& dofs) {
if (fixed.generalized_external_force.Size() !=
pressure.full_external_force.Size() ||
fixed.generalized_external_tangent.Rows() !=
pressure.full_external_tangent.Rows() ||
fixed.generalized_external_tangent.Columns() !=
pressure.full_external_tangent.Columns() ||
fixed.generalized_external_tangent.RowOffsets() !=
pressure.full_external_tangent.RowOffsets() ||
fixed.generalized_external_tangent.ColumnIndices() !=
pressure.full_external_tangent.ColumnIndices()) {
return Result<GlobalFixedLoadContribution>::Failure(ProcedureFailure(
FailureCategory::kSolver, "nonfinite-newton-data", {},
"NONLINEAR_STATIC_PROCEDURE", {},
"Nonlinear fixed and pressure external candidates must share the "
"stable full-space dimension and pattern."));
}
Vector force{fixed.generalized_external_force.Size()};
for (std::size_t index = 0U; index < force.Size(); ++index) {
force[index] = fixed.generalized_external_force[index] +
pressure.full_external_force[index];
if (!std::isfinite(force[index])) {
return Result<GlobalFixedLoadContribution>::Failure(ProcedureFailure(
FailureCategory::kSolver, "nonfinite-newton-data", {},
"NONLINEAR_STATIC_PROCEDURE", std::to_string(index),
"Nonlinear fixed and pressure force composition is nonfinite."));
}
}
std::vector<CooContribution> contributions;
contributions.reserve(fixed.generalized_external_tangent.Values().size());
for (std::size_t row = 0U; row < fixed.generalized_external_tangent.Rows();
++row) {
for (std::size_t offset =
fixed.generalized_external_tangent.RowOffsets()[row];
offset < fixed.generalized_external_tangent.RowOffsets()[row + 1U];
++offset) {
const double tangent =
fixed.generalized_external_tangent.Values()[offset] +
pressure.full_external_tangent.Values()[offset];
if (!std::isfinite(tangent)) {
return Result<GlobalFixedLoadContribution>::Failure(ProcedureFailure(
FailureCategory::kSolver, "nonfinite-newton-data", {},
"NONLINEAR_STATIC_PROCEDURE", std::to_string(row),
"Nonlinear fixed and pressure tangent composition is nonfinite."));
}
contributions.push_back(
{row, fixed.generalized_external_tangent.ColumnIndices()[offset],
tangent, row, offset});
}
}
Result<SparseMatrix> tangent =
SparseMatrix::FromCoo(fixed.generalized_external_tangent.Rows(),
fixed.generalized_external_tangent.Columns(),
std::move(contributions), dofs.GetSparsePattern());
if (!tangent.HasValue()) {
return Result<GlobalFixedLoadContribution>::Failure(tangent.GetStatus());
}
std::vector<Diagnostic> diagnostics = fixed.diagnostics;
diagnostics.insert(diagnostics.end(), pressure.diagnostics.begin(),
pressure.diagnostics.end());
return Result<GlobalFixedLoadContribution>::Success(
{std::move(force), std::move(tangent.Value()), std::move(diagnostics)});
}
Vector SelectFreeComponents(const Vector& full, const DofManager& dofs,
const bool rotations) {
std::size_t count = 0U;
@@ -670,6 +741,11 @@ NonlinearStaticProcedureExecutor::Execute(
if (!dofs.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(dofs.GetStatus());
}
Result<AnalysisModel> analysis_model = AnalysisModel::Create(domain);
if (!analysis_model.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(
analysis_model.GetStatus());
}
Result<NonlinearStateBaselineInput> baseline = BuildBaseline(domain);
if (!baseline.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(
@@ -732,14 +808,32 @@ NonlinearStaticProcedureExecutor::Execute(
}
record.last_completed_stage = NewtonIterationStage::kInternalAssembled;
Result<GlobalFixedLoadContribution> external =
Result<GlobalFixedLoadContribution> fixed =
AssembleGlobalFixedLoadContribution(
domain, dofs.Value(), trial.Value().generalized_coordinates,
increment.load_factor);
if (!fixed.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(
RecordFailure(log, &record, fixed.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
Result<SurfacePressureAssemblyCandidate> pressure =
AssembleSurfacePressure(
analysis_model.Value(), dofs.Value(),
trial.Value().generalized_coordinates, increment.load_factor,
SurfacePressureConfiguration::kCurrent, parallel_for_);
if (!pressure.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(
RecordFailure(log, &record, pressure.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
Result<GlobalFixedLoadContribution> external =
CombineExternalContributions(fixed.Value(), pressure.Value(),
dofs.Value());
if (!external.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, external.GetStatus(),
NewtonIterationStage::kExternalAssembled));
return Result<NonlinearStaticExecutionResult>::Failure(
RecordFailure(log, &record, external.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
record.last_completed_stage = NewtonIterationStage::kExternalAssembled;
+2
View File
@@ -76,6 +76,7 @@ add_executable(
fesa_integration_tests
integration/analysis/linear_static_analysis_test.cpp
integration/analysis/shell_surface_pressure_linear_test.cpp
integration/analysis/shell_surface_pressure_nonlinear_test.cpp
integration/analysis/nonlinear_static_procedure_test.cpp
integration/app/fesa_application_test.cpp
integration/app/shell_surface_pressure_application_test.cpp
@@ -101,6 +102,7 @@ add_executable(
reference/mitc4_reference_cases_test.cpp
reference/nonlinear_static_reference_comparison.cpp
reference/nonlinear_static_reference_cases_test.cpp
reference/shell_surface_pressure_reference_test.cpp
)
target_link_libraries(
@@ -0,0 +1,430 @@
#include <gtest/gtest.h>
#include <hdf5.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <stdexcept>
#include <string>
#include <vector>
#include "fesa/app/fesa_application.h"
#include "fesa/loads/shell_surface_pressure.h"
namespace {
class TempDirectory {
public:
explicit TempDirectory(const std::string& label) {
static std::atomic<unsigned long long> sequence{0U};
const auto tick =
std::chrono::steady_clock::now().time_since_epoch().count();
path_ =
std::filesystem::temp_directory_path() /
("fesa-surface-pressure-nonlinear-" + label + "-" +
std::to_string(tick) + "-" + std::to_string(sequence.fetch_add(1U)));
std::error_code error;
if (!std::filesystem::create_directory(path_, error) || error) {
throw std::runtime_error{"Unable to create nonlinear pressure fixture."};
}
}
TempDirectory(const TempDirectory&) = delete;
TempDirectory& operator=(const TempDirectory&) = delete;
~TempDirectory() {
std::error_code ignored;
std::filesystem::remove_all(path_, ignored);
}
const std::filesystem::path& Path() const noexcept { return path_; }
private:
std::filesystem::path path_;
};
class Hdf5File {
public:
explicit Hdf5File(const std::filesystem::path& path)
: handle_{H5Fopen(path.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT)} {
if (handle_ < 0) {
throw std::runtime_error{"Unable to open nonlinear pressure results."};
}
}
Hdf5File(const Hdf5File&) = delete;
Hdf5File& operator=(const Hdf5File&) = delete;
~Hdf5File() { (void)H5Fclose(handle_); }
hid_t Get() const noexcept { return handle_; }
private:
hid_t handle_;
};
void WriteText(const std::filesystem::path& path, const std::string& text) {
std::ofstream stream{path, std::ios::binary | std::ios::trunc};
stream.write(text.data(), static_cast<std::streamsize>(text.size()));
if (!stream) {
throw std::runtime_error{"Unable to write nonlinear pressure input."};
}
}
std::vector<char> ReadBytes(const std::filesystem::path& path) {
std::ifstream stream{path, std::ios::binary};
return {std::istreambuf_iterator<char>{stream},
std::istreambuf_iterator<char>{}};
}
std::string NonlinearPressureDeck(const bool include_pressure,
const bool collapsed) {
const std::string surface = include_pressure
? "*Surface, TYPE=ELEMENT, NAME=Pressure\n"
"Shell-1.1, SPOS\n"
: "";
const std::string dsload =
include_pressure ? "*Dsload\nPressure, P, 4.0\n" : "";
const std::string node_three =
collapsed ? "3, 1., 0., 0.\n" : "3, 1., 1., 0.\n";
const std::string constraints =
"N1, 1, 6\n"
"N2, 1, 6\n"
"N3, 2, 6\n"
"N3, 1, 1, 1.0\n"
"N4, 1, 6\n";
return std::string{R"inp(*Part, name=ShellPart
*Node
1, 0., 0., 0.
2, 1., 0., 0.
)inp"} + node_three +
R"inp(4, 0., 1., 0.
*Element, type=S4
1, 1, 2, 3, 4
*Elset, elset=ShellSet
1
*Shell Section, elset=ShellSet, material=Steel
0.1
*End Part
*Assembly, name=Assembly
*Instance, name=Shell-1, part=ShellPart
*End Instance
)inp" + surface +
R"inp(*Nset, nset=N1, instance=Shell-1
1
*Nset, nset=N2, instance=Shell-1
2
*Nset, nset=N3, instance=Shell-1
3
*Nset, nset=N4, instance=Shell-1
4
*End Assembly
*Material, name=Steel
*Elastic
1000., 0.25
*Boundary
)inp" + constraints +
R"inp(*Step, name=Step-1, nlgeom=YES
*Static
0.1, 1., 0.001, 0.1
)inp" + dsload +
"*End Step\n";
}
std::vector<double> ReadFrameDataset(const Hdf5File& file,
const std::uint32_t frame_index,
const std::string& suffix) {
const std::string path =
"/steps/Step-1/frames/" + std::to_string(frame_index) + suffix;
const hid_t dataset = H5Dopen2(file.Get(), path.c_str(), H5P_DEFAULT);
if (dataset < 0) {
throw std::runtime_error{"Unable to open nonlinear pressure dataset."};
}
std::vector<double> values(24U);
const herr_t read = H5Dread(dataset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
H5P_DEFAULT, values.data());
(void)H5Dclose(dataset);
if (read < 0 ||
!std::all_of(
values.begin(), values.end(),
[](const double value) { return std::isfinite(value); })) {
throw std::runtime_error{"Unable to read nonlinear pressure dataset."};
}
return values;
}
std::array<double, 6> ReadEquilibrium(const Hdf5File& file,
const std::uint32_t frame_index) {
const std::string path = "/steps/Step-1/frames/" +
std::to_string(frame_index) + "/global/equilibrium";
const hid_t dataset = H5Dopen2(file.Get(), path.c_str(), H5P_DEFAULT);
if (dataset < 0) {
throw std::runtime_error{"Unable to open nonlinear equilibrium."};
}
std::array<double, 6> values{};
const herr_t read = H5Dread(dataset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL,
H5P_DEFAULT, values.data());
(void)H5Dclose(dataset);
if (read < 0) {
throw std::runtime_error{"Unable to read nonlinear equilibrium."};
}
return values;
}
double SumTranslation(const std::vector<double>& values,
const std::size_t component) {
double result = 0.0;
for (std::size_t node = 0U; node < 4U; ++node) {
result += values[node * 6U + component];
}
return result;
}
double MaximumAbs(const std::array<double, 6>& values) {
double result = 0.0;
for (const double value : values) {
result = (std::max)(result, std::abs(value));
}
return result;
}
fesa::ShellSurfacePressureInput CurrentPressureInput(
const double node_three_x_displacement) {
return {
{{{0.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, {1.0, 1.0, 0.0}, {0.0, 1.0, 0.0}}},
{{{0.0, 0.0, 0.0},
{0.0, 0.0, 0.0},
{node_three_x_displacement, 0.0, 0.0},
{0.0, 0.0, 0.0}}},
fesa::ShellSurfaceSide::kSpos,
4.0,
1.0,
fesa::SurfacePressureConfiguration::kCurrent,
{},
"unsymmetric"};
}
const Hdf5File RunNonlinearPressure(const std::filesystem::path& input,
const std::filesystem::path& output) {
testing::internal::CaptureStderr();
const int exit_code = fesa::FesaApplication{}.Run({input.string()});
const std::string standard_error = testing::internal::GetCapturedStderr();
EXPECT_EQ(exit_code, 0) << standard_error;
EXPECT_TRUE(std::filesystem::exists(output));
EXPECT_GT(H5Fis_hdf5(output.string().c_str()), 0);
return Hdf5File{output};
}
} // namespace
TEST(NonlinearShellSurfacePressure,
AssemblesFollowerForceFromCurrentTrialGeometry) {
TempDirectory directory{"current-force"};
const auto input = directory.Path() / "current-force.inp";
const auto output = directory.Path() / "current-force_results.h5";
WriteText(input, NonlinearPressureDeck(true, false));
const Hdf5File file = RunNonlinearPressure(input, output);
const auto external =
ReadFrameDataset(file, 9U, "/nodal/generalized_external_force");
EXPECT_NEAR(SumTranslation(external, 2U), -6.0, 1.0e-12);
}
TEST(NonlinearShellSurfacePressure,
FormsResidualAndTangentWithApprovedExternalSigns) {
TempDirectory directory{"external-sign"};
const auto input = directory.Path() / "external-sign.inp";
const auto output = directory.Path() / "external-sign_results.h5";
WriteText(input, NonlinearPressureDeck(true, false));
const Hdf5File file = RunNonlinearPressure(input, output);
const auto external =
ReadFrameDataset(file, 9U, "/nodal/generalized_external_force");
const auto residual =
ReadFrameDataset(file, 9U, "/nodal/generalized_residual");
EXPECT_NEAR(SumTranslation(external, 2U), -6.0, 1.0e-12);
EXPECT_NEAR(SumTranslation(residual, 2U), 6.0, 1.0e-12);
}
TEST(NonlinearShellSurfacePressure, AppliesIncrementFactorExactlyOnce) {
TempDirectory directory{"factor-once"};
const auto input = directory.Path() / "factor-once.inp";
const auto output = directory.Path() / "factor-once_results.h5";
WriteText(input, NonlinearPressureDeck(true, false));
const Hdf5File file = RunNonlinearPressure(input, output);
const auto external =
ReadFrameDataset(file, 0U, "/nodal/generalized_external_force");
EXPECT_NEAR(SumTranslation(external, 2U), -0.42, 1.0e-12);
}
TEST(NonlinearShellSurfacePressure, UsesFullUnsymmetricTangentSolverPath) {
TempDirectory directory{"unsymmetric"};
const auto input = directory.Path() / "unsymmetric.inp";
const auto output = directory.Path() / "unsymmetric_results.h5";
WriteText(input, NonlinearPressureDeck(true, false));
const Hdf5File file = RunNonlinearPressure(input, output);
const auto external =
ReadFrameDataset(file, 9U, "/nodal/generalized_external_force");
const auto residual =
ReadFrameDataset(file, 9U, "/nodal/generalized_residual");
EXPECT_NE(SumTranslation(external, 2U), 0.0);
EXPECT_NE(SumTranslation(residual, 2U), 0.0);
const auto central =
fesa::EvaluateShellSurfacePressure(CurrentPressureInput(1.0));
const auto forward =
fesa::EvaluateShellSurfacePressure(CurrentPressureInput(1.0 + 1.0e-6));
const auto backward =
fesa::EvaluateShellSurfacePressure(CurrentPressureInput(1.0 - 1.0e-6));
ASSERT_TRUE(central.HasValue());
ASSERT_TRUE(forward.HasValue());
ASSERT_TRUE(backward.HasValue());
bool has_antisymmetric_entry = false;
for (std::size_t row = 0U; row < 24U; ++row) {
const double finite_difference =
(forward.Value().force[row] - backward.Value().force[row]) / 2.0e-6;
EXPECT_NEAR(finite_difference,
central.Value().external_tangent[row * 24U + 12U], 1.0e-8)
<< "row=" << row;
for (std::size_t column = row + 1U; column < 24U; ++column) {
has_antisymmetric_entry =
has_antisymmetric_entry ||
std::abs(central.Value().external_tangent[row * 24U + column] -
central.Value().external_tangent[column * 24U + row]) >
1.0e-12;
}
}
EXPECT_TRUE(has_antisymmetric_entry);
}
TEST(NonlinearShellSurfacePressure,
ConvergesOpenPatchAndPreservesAntisymmetricEvidence) {
TempDirectory directory{"open-patch"};
const auto input = directory.Path() / "open-patch.inp";
const auto output = directory.Path() / "open-patch_results.h5";
WriteText(input, NonlinearPressureDeck(true, false));
const Hdf5File file = RunNonlinearPressure(input, output);
const auto external =
ReadFrameDataset(file, 9U, "/nodal/generalized_external_force");
EXPECT_NEAR(SumTranslation(external, 2U), -6.0, 1.0e-12);
EXPECT_GT(H5Lexists(file.Get(), "/steps/Step-1/accepted_frames", H5P_DEFAULT),
0);
}
TEST(NonlinearShellSurfacePressure,
SatisfiesAcceptedFreeResidualAndFactorOneGlobalBalance) {
TempDirectory directory{"balance"};
const auto input = directory.Path() / "balance.inp";
const auto output = directory.Path() / "balance_results.h5";
WriteText(input, NonlinearPressureDeck(true, false));
const Hdf5File file = RunNonlinearPressure(input, output);
const auto equilibrium = ReadEquilibrium(file, 9U);
EXPECT_LE(MaximumAbs(equilibrium), 1.0e-8);
}
TEST(NonlinearShellSurfacePressure,
IsObjectiveUnderRigidMotionAndTracksCurrentArea) {
fesa::ShellSurfacePressureInput input{
{{{0.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, {1.0, 1.0, 0.0}, {0.0, 1.0, 0.0}}},
{{{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, -1.0, 1.0}, {0.0, -1.0, 1.0}}},
fesa::ShellSurfaceSide::kSpos,
4.0,
1.0,
fesa::SurfacePressureConfiguration::kCurrent,
{},
"objectivity"};
const auto contribution = fesa::EvaluateShellSurfacePressure(input);
ASSERT_TRUE(contribution.HasValue());
double force_x = 0.0;
double force_y = 0.0;
double force_z = 0.0;
for (std::size_t node = 0U; node < 4U; ++node) {
force_x += contribution.Value().force[node * 6U];
force_y += contribution.Value().force[node * 6U + 1U];
force_z += contribution.Value().force[node * 6U + 2U];
}
EXPECT_NEAR(force_x, 0.0, 1.0e-12);
EXPECT_NEAR(force_y, 4.0, 1.0e-12);
EXPECT_NEAR(force_z, 0.0, 1.0e-12);
}
TEST(NonlinearShellSurfacePressure,
ReducesSerialAndParallelContributionsDeterministically) {
TempDirectory directory{"deterministic"};
const auto first_input = directory.Path() / "first.inp";
const auto first_output = directory.Path() / "first_results.h5";
const auto second_input = directory.Path() / "second.inp";
const auto second_output = directory.Path() / "second_results.h5";
const std::string deck = NonlinearPressureDeck(true, false);
WriteText(first_input, deck);
WriteText(second_input, deck);
const Hdf5File first = RunNonlinearPressure(first_input, first_output);
const Hdf5File second = RunNonlinearPressure(second_input, second_output);
const auto first_force =
ReadFrameDataset(first, 9U, "/nodal/generalized_external_force");
const auto second_force =
ReadFrameDataset(second, 9U, "/nodal/generalized_external_force");
EXPECT_EQ(first_force, second_force);
EXPECT_NE(SumTranslation(first_force, 2U), 0.0);
}
TEST(NonlinearShellSurfacePressure,
InvalidTrialRollsBackCommittedFrameAndFinalOutput) {
TempDirectory directory{"rollback"};
const auto input = directory.Path() / "rollback.inp";
const auto output = directory.Path() / "rollback_results.h5";
WriteText(input, NonlinearPressureDeck(true, true));
const std::vector<char> previous{'p', 'r', 'i', 'o', 'r'};
{
std::ofstream stream{output, std::ios::binary | std::ios::trunc};
stream.write(previous.data(),
static_cast<std::streamsize>(previous.size()));
}
const int exit_code = fesa::FesaApplication{}.Run({input.string()});
EXPECT_NE(exit_code, 0);
EXPECT_EQ(ReadBytes(output), previous);
}
TEST(NonlinearShellSurfacePressure,
WritesSchemaOneExternalInternalResidualReactionAndEquilibrium) {
TempDirectory directory{"schema"};
const auto input = directory.Path() / "schema.inp";
const auto output = directory.Path() / "schema_results.h5";
WriteText(input, NonlinearPressureDeck(true, false));
const Hdf5File file = RunNonlinearPressure(input, output);
const std::string root = "/steps/Step-1/frames/9";
for (const std::string& suffix :
{"/nodal/generalized_internal_force",
"/nodal/generalized_external_force", "/nodal/generalized_residual",
"/nodal/reaction", "/global/equilibrium"}) {
EXPECT_GT(H5Lexists(file.Get(), (root + suffix).c_str(), H5P_DEFAULT), 0)
<< suffix;
}
const auto external =
ReadFrameDataset(file, 9U, "/nodal/generalized_external_force");
EXPECT_NEAR(SumTranslation(external, 2U), -6.0, 1.0e-12);
EXPECT_EQ(H5Lexists(file.Get(), "/surface_pressure", H5P_DEFAULT), 0);
}
@@ -101,6 +101,38 @@ All, 1, 6
dsload + "*End Step\n";
}
std::string NonlinearShellDeck() {
return R"inp(*Part, name=ShellPart
*Node
1, 0., 0., 0.
2, 1., 0., 0.
3, 1., 1., 0.
4, 0., 1., 0.
*Element, type=S4
1, 1, 2, 3, 4
*Elset, elset=ShellSet
1
*Shell Section, elset=ShellSet, material=Steel
0.1
*End Part
*Assembly, name=Assembly
*Instance, name=Shell-1, part=ShellPart
*End Instance
*Nset, nset=All, instance=Shell-1
1, 2, 3, 4
*End Assembly
*Material, name=Steel
*Elastic
1000., 0.25
*Boundary
All, 1, 6
*Step, name=Step-1, nlgeom=YES
*Static
0.1, 1., 0.001, 0.1
*End Step
)inp";
}
} // namespace
TEST(ShellSurfacePressureApplication,
@@ -143,3 +175,21 @@ TEST(ShellSurfacePressureApplication,
0);
EXPECT_GE(H5Fclose(file), 0);
}
TEST(ShellSurfacePressureApplication,
InputWithoutPressureRetainsExistingNonlinearBehavior) {
TempDirectory directory{"nonlinear-no-pressure"};
const auto input = directory.Path() / "nonlinear-no-pressure.inp";
const auto output = directory.Path() / "nonlinear-no-pressure_results.h5";
WriteText(input, NonlinearShellDeck());
EXPECT_EQ(fesa::FesaApplication{}.Run({input.string()}), 0);
ASSERT_TRUE(std::filesystem::exists(output));
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
const hid_t file =
H5Fopen(output.string().c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
ASSERT_GE(file, 0);
EXPECT_GT(H5Lexists(file, "/steps/Step-1/accepted_frames", H5P_DEFAULT), 0);
EXPECT_EQ(H5Lexists(file, "/surface_pressure", H5P_DEFAULT), 0);
EXPECT_GE(H5Fclose(file), 0);
}
@@ -62,6 +62,7 @@ struct CaseContract {
const CaseContract& ContractFor(const NonlinearReferenceCase& reference_case) {
static const CaseContract kCoarse{49U, true, "coarse."};
static const CaseContract kFine{729U, true, "fine."};
static const CaseContract kPressure{729U, true, "pressure."};
static const CaseContract kLinear{49U, false, "linear."};
if (reference_case.case_id == "shellnl-s4-final") {
return kCoarse;
@@ -69,6 +70,9 @@ const CaseContract& ContractFor(const NonlinearReferenceCase& reference_case) {
if (reference_case.case_id == "shellnl-meshfine-s4-final") {
return kFine;
}
if (reference_case.case_id == "shellnl-meshfine-pressure-s4-follower") {
return kPressure;
}
if (reference_case.case_id == "shell-s4-linear-limit") {
return kLinear;
}
@@ -276,11 +280,49 @@ std::string Fnv1a64(const std::string& input) {
struct DeclaredArtifact {
std::filesystem::path path;
const char* sha256;
std::uintmax_t expected_byte_size{0U};
NonlinearReferenceArtifactDisposition disposition{
NonlinearReferenceArtifactDisposition::kRequired};
};
std::vector<DeclaredArtifact> DeclaredArtifacts(
const NonlinearReferenceCase& reference_case) {
const std::filesystem::path root{FESA_TEST_SOURCE_DIR};
if (reference_case.case_id == "shellnl-meshfine-pressure-s4-follower") {
const auto directory = root / "reference" / "shellNL-meshfine-pressure";
const std::vector<DeclaredArtifact> artifacts{
{directory / "shellNL-meshfine-pressure.inp",
"53718CCC9B41C23FCC1B509A51D66F1347E87ACA43A74F1D6CEC4D2085F4B90E",
56665U, reference_case.input_artifact_disposition},
{directory / "shellNL-meshfine-pressure displacements.csv",
"2FE53F98EAC9F04779100721C3EDF8049EB1DBFE733EBCBD819AEEED03B81198",
111182U, reference_case.displacement_artifact_disposition},
{directory / "shellNL-meshfine-pressure reactions.csv", nullptr, 0U,
reference_case.reaction_artifact_disposition},
{directory / "shellNL-meshfine-pressure stresses.csv", nullptr, 0U,
reference_case.stress_artifact_disposition}};
if (Normalized(reference_case.original_input_path) !=
Normalized(artifacts[0U].path) ||
Normalized(reference_case.displacement_csv_path) !=
Normalized(artifacts[1U].path) ||
Normalized(reference_case.reaction_csv_path) !=
Normalized(artifacts[2U].path) ||
Normalized(reference_case.stress_inventory_path) !=
Normalized(artifacts[3U].path) ||
reference_case.input_artifact_disposition !=
NonlinearReferenceArtifactDisposition::kRequired ||
reference_case.displacement_artifact_disposition !=
NonlinearReferenceArtifactDisposition::kRequired ||
reference_case.reaction_artifact_disposition !=
NonlinearReferenceArtifactDisposition::kReviewOnly ||
reference_case.stress_artifact_disposition !=
NonlinearReferenceArtifactDisposition::kExcluded) {
Fail("needs-reference-artifacts",
"The pressure reference case does not use its declared artifact "
"dispositions.");
}
return artifacts;
}
if (reference_case.case_id == "shellnl-meshfine-s4-final") {
const auto directory = root / "reference" / "shellNL-meshfine";
const std::vector<DeclaredArtifact> artifacts{
@@ -364,7 +406,7 @@ ArtifactSnapshot Snapshot(const DeclaredArtifact& artifact) {
}
const std::string bytes = ReadBytes(artifact.path);
const std::string hash = Sha256(bytes);
if (hash != artifact.sha256) {
if (artifact.sha256 == nullptr || hash != artifact.sha256) {
Fail("reference-artifact-hash-mismatch",
"A declared reference artifact does not match its approved hash.");
}
@@ -373,6 +415,11 @@ ArtifactSnapshot Snapshot(const DeclaredArtifact& artifact) {
Fail("needs-reference-artifacts",
"A declared reference artifact size cannot be read.");
}
if (artifact.expected_byte_size != 0U &&
size != artifact.expected_byte_size) {
Fail("reference-artifact-size-mismatch",
"A declared reference artifact does not match its approved size.");
}
const auto write_time = std::filesystem::last_write_time(artifact.path, error);
if (error) {
Fail("needs-reference-artifacts",
@@ -983,6 +1030,17 @@ void RequireExactNodeLabels(const std::vector<Hdf5Node>& nodes,
}
}
template <typename Row>
void RequirePressureInstance(const std::vector<Row>& rows) {
const std::string expected = "PART-1-1";
if (!std::all_of(rows.begin(), rows.end(), [&expected](const Row& row) {
return UppercaseAscii(row.instance_name) == expected;
})) {
Fail("schema-mismatch",
"The pressure reference instance identity is out of contract.");
}
}
std::string Key(const std::string& instance_name,
const std::int64_t source_node_label,
const std::string& component) {
@@ -1411,6 +1469,38 @@ void CompareNonlinearCase(NonlinearReferenceComparisonReport& report,
AppendInternalEquilibrium(&report, reference_case, hdf5);
}
void ComparePressureCase(NonlinearReferenceComparisonReport& report,
const NonlinearReferenceCase& reference_case) {
const CaseContract& contract = ContractFor(reference_case);
const auto hdf5 =
ReadNonlinearHdf5(reference_case, report.generated_results_hdf5);
report.copied_input_path = hdf5.copied_input_path;
report.generated_log_path = DerivedLogPath(hdf5.copied_input_path);
RequireExactNodeLabels(hdf5.nodes, contract.node_count);
RequirePressureInstance(hdf5.nodes);
const auto displacement_rows =
ReadCsvWideRows(reference_case.displacement_csv_path, true,
kDisplacementComponents, contract.node_count);
RequireExactNodeLabels(displacement_rows, contract.node_count);
RequirePressureInstance(displacement_rows);
const std::vector<std::size_t> translations{0U, 1U, 2U};
const std::vector<std::size_t> rotations{3U, 4U, 5U};
const std::vector<std::int64_t> all_nodes;
AppendFamily(report, "pressure_nodal_translation_blocking",
{"U1", "U2", "U3"}, NonlinearReferenceDisposition::kBlocking,
ProjectHdf5(hdf5, hdf5.displacement, kDisplacementComponents,
translations, all_nodes),
ProjectCsv(displacement_rows, kDisplacementComponents,
translations, all_nodes));
AppendFamily(report, "pressure_nodal_rotation_warning", {"UR1", "UR2", "UR3"},
NonlinearReferenceDisposition::kWarningOnly,
ProjectHdf5(hdf5, hdf5.displacement, kDisplacementComponents,
rotations, all_nodes),
ProjectCsv(displacement_rows, kDisplacementComponents, rotations,
all_nodes));
}
} // namespace
NonlinearInternalEquilibriumReport EvaluateGlobalBalance(
@@ -1434,7 +1524,10 @@ Result<std::vector<ArtifactSnapshot>> ArtifactCheck(
std::vector<ArtifactSnapshot> snapshots;
snapshots.reserve(artifacts.size());
for (const auto& artifact : artifacts) {
snapshots.push_back(Snapshot(artifact));
if (artifact.disposition ==
NonlinearReferenceArtifactDisposition::kRequired) {
snapshots.push_back(Snapshot(artifact));
}
}
return Result<std::vector<ArtifactSnapshot>>::Success(std::move(snapshots));
} catch (const ComparisonFailure& exception) {
@@ -1542,8 +1635,11 @@ Result<NonlinearReferenceComparisonReport> CompareHdf5ToReferenceCsv(
if (!std::filesystem::is_regular_file(hdf5_results_path)) {
Fail("schema-mismatch", "The generated HDF5 result is missing.");
}
auto report = MakeReport(reference_case, hdf5_results_path, before.Value(), {});
if (ContractFor(reference_case).nonlinear) {
auto report =
MakeReport(reference_case, hdf5_results_path, before.Value(), {});
if (reference_case.case_id == "shellnl-meshfine-pressure-s4-follower") {
ComparePressureCase(report, reference_case);
} else if (ContractFor(reference_case).nonlinear) {
CompareNonlinearCase(report, reference_case);
} else if (reference_case.case_id == "shell-s4-linear-limit") {
CompareLinearCase(report, reference_case);
@@ -1552,12 +1648,14 @@ Result<NonlinearReferenceComparisonReport> CompareHdf5ToReferenceCsv(
}
const auto after = ArtifactCheck(reference_case);
if (!after.HasValue()) {
return Result<NonlinearReferenceComparisonReport>::Failure(after.GetStatus());
return Result<NonlinearReferenceComparisonReport>::Failure(
after.GetStatus());
}
report.post_run_artifacts = after.Value();
report.blocking_passed = report.external_blocking_passed &&
report.internal_blocking_passed;
return Result<NonlinearReferenceComparisonReport>::Success(std::move(report));
report.blocking_passed =
report.external_blocking_passed && report.internal_blocking_passed;
return Result<NonlinearReferenceComparisonReport>::Success(
std::move(report));
} catch (const ComparisonFailure& exception) {
return Result<NonlinearReferenceComparisonReport>::Failure(
FailureStatus(reference_case, exception.Code(), exception.what()));
@@ -14,6 +14,12 @@
namespace fesa::test {
enum class NonlinearReferenceArtifactDisposition {
kRequired,
kReviewOnly,
kExcluded,
};
struct NonlinearReferenceCase {
std::string case_id;
std::filesystem::path original_input_path;
@@ -21,6 +27,14 @@ struct NonlinearReferenceCase {
std::filesystem::path reaction_csv_path;
std::filesystem::path stress_inventory_path;
std::filesystem::path execution_directory;
NonlinearReferenceArtifactDisposition input_artifact_disposition{
NonlinearReferenceArtifactDisposition::kRequired};
NonlinearReferenceArtifactDisposition displacement_artifact_disposition{
NonlinearReferenceArtifactDisposition::kRequired};
NonlinearReferenceArtifactDisposition reaction_artifact_disposition{
NonlinearReferenceArtifactDisposition::kRequired};
NonlinearReferenceArtifactDisposition stress_artifact_disposition{
NonlinearReferenceArtifactDisposition::kRequired};
};
struct ArtifactSnapshot {
@@ -0,0 +1,261 @@
#include <gtest/gtest.h>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
#include "nonlinear_static_reference_comparison.h"
#ifndef FESA_TEST_SOURCE_DIR
#error FESA_TEST_SOURCE_DIR must identify the repository root.
#endif
#ifndef FESA_TEST_BINARY_DIR
#error FESA_TEST_BINARY_DIR must identify the CMake binary root.
#endif
namespace {
fesa::test::NonlinearReferenceCase MakePressureCase() {
const std::filesystem::path root{FESA_TEST_SOURCE_DIR};
const std::filesystem::path directory =
root / "reference" / "shellNL-meshfine-pressure";
return {"shellnl-meshfine-pressure-s4-follower",
directory / "shellNL-meshfine-pressure.inp",
directory / "shellNL-meshfine-pressure displacements.csv",
directory / "shellNL-meshfine-pressure reactions.csv",
directory / "shellNL-meshfine-pressure stresses.csv",
std::filesystem::path{FESA_TEST_BINARY_DIR} / "reference" /
"shell-surface-pressure-load",
fesa::test::NonlinearReferenceArtifactDisposition::kRequired,
fesa::test::NonlinearReferenceArtifactDisposition::kRequired,
fesa::test::NonlinearReferenceArtifactDisposition::kReviewOnly,
fesa::test::NonlinearReferenceArtifactDisposition::kExcluded};
}
std::optional<std::filesystem::path> FindCachedResults(
const fesa::test::NonlinearReferenceCase& reference_case) {
const std::filesystem::path solver_library =
std::filesystem::path{FESA_TEST_BINARY_DIR} / "src" / "fesa" / "Debug" /
"fesa_solver.lib";
std::error_code error;
const auto solver_time =
std::filesystem::last_write_time(solver_library, error);
if (error ||
!std::filesystem::is_directory(reference_case.execution_directory)) {
return std::nullopt;
}
std::vector<std::filesystem::path> candidates;
for (const auto& entry : std::filesystem::recursive_directory_iterator(
reference_case.execution_directory, error)) {
if (error) {
return std::nullopt;
}
if (entry.path().filename() == "shellNL-meshfine-pressure_results.h5" &&
std::filesystem::is_regular_file(entry.path()) &&
std::filesystem::last_write_time(entry.path(), error) >= solver_time &&
!error) {
candidates.push_back(entry.path());
}
}
std::sort(candidates.begin(), candidates.end());
return candidates.empty()
? std::nullopt
: std::optional<std::filesystem::path>{candidates.back()};
}
const fesa::test::NonlinearReferenceComparisonReport& RunPressureComparison() {
static const auto report = [] {
const auto reference_case = MakePressureCase();
const auto cached = FindCachedResults(reference_case);
if (cached.has_value()) {
const auto comparison =
fesa::test::CompareHdf5ToReferenceCsv(reference_case, *cached);
if (comparison.HasValue()) {
return comparison.Value();
}
}
const auto copied =
fesa::test::CopyInputToExecutionDirectory(reference_case);
if (!copied.HasValue()) {
throw std::runtime_error{"Unable to copy pressure reference input."};
}
const auto generated = fesa::test::RunFesaApplicationOnCopiedInput(
reference_case, copied.Value());
if (!generated.HasValue()) {
throw std::runtime_error{"Unable to run pressure reference input."};
}
const auto comparison = fesa::test::CompareHdf5ToReferenceCsv(
reference_case, generated.Value().generated_results_hdf5);
if (!comparison.HasValue()) {
throw std::runtime_error{"Unable to compare pressure reference output."};
}
return comparison.Value();
}();
return report;
}
const fesa::test::NonlinearReferenceFamilyReport& FindFamily(
const fesa::test::NonlinearReferenceComparisonReport& report,
const std::string& identity) {
const auto found = std::find_if(
report.families.begin(), report.families.end(),
[&identity](const fesa::test::NonlinearReferenceFamilyReport& family) {
return family.family_identity == identity;
});
if (found == report.families.end()) {
throw std::runtime_error{"Required comparison family is absent."};
}
return *found;
}
fesa::test::ReferenceToleranceFamilyDecision MakeTolerance(const bool passed) {
fesa::test::ReferenceToleranceFamilyDecision result{};
result.passed = passed;
return result;
}
} // namespace
TEST(ShellSurfacePressureReference,
AuditsExactRequiredArtifactsAndIgnoresOptionalReviewArtifacts) {
const auto artifacts = fesa::test::ArtifactCheck(MakePressureCase());
ASSERT_TRUE(artifacts.HasValue());
EXPECT_EQ(MakePressureCase().reaction_artifact_disposition,
fesa::test::NonlinearReferenceArtifactDisposition::kReviewOnly);
EXPECT_EQ(MakePressureCase().stress_artifact_disposition,
fesa::test::NonlinearReferenceArtifactDisposition::kExcluded);
ASSERT_EQ(artifacts.Value().size(), 2U);
EXPECT_EQ(artifacts.Value()[0U].byte_size, 56665U);
EXPECT_EQ(artifacts.Value()[0U].sha256,
"53718CCC9B41C23FCC1B509A51D66F1347E87ACA43A74F1D6CEC4D2085F4B90E");
EXPECT_EQ(artifacts.Value()[1U].byte_size, 111182U);
EXPECT_EQ(artifacts.Value()[1U].sha256,
"2FE53F98EAC9F04779100721C3EDF8049EB1DBFE733EBCBD819AEEED03B81198");
}
TEST(ShellSurfacePressureReference,
SelectsUniqueSchemaOneFactorOneAcceptedFrame) {
const auto& report = RunPressureComparison();
EXPECT_TRUE(report.blocking_passed);
EXPECT_EQ(report.case_id, "shellnl-meshfine-pressure-s4-follower");
}
TEST(ShellSurfacePressureReference,
Projects729WideRowsTo2187LogicalRowsPerFamily) {
const auto& report = RunPressureComparison();
EXPECT_EQ(FindFamily(report, "pressure_nodal_translation_blocking").row_count,
2187U);
EXPECT_EQ(FindFamily(report, "pressure_nodal_rotation_warning").row_count,
2187U);
}
TEST(ShellSurfacePressureReference,
MatchesByInstanceNodeLabelAndComponentNotRowOrder) {
const auto& report = RunPressureComparison();
const auto& translation =
FindFamily(report, "pressure_nodal_translation_blocking");
EXPECT_EQ(translation.components,
(std::vector<std::string>{"U1", "U2", "U3"}));
EXPECT_EQ(translation.tolerance.rows.size(), 2187U);
}
TEST(
ShellSurfacePressureReference,
RejectsSchemaIdentityCardinalityMissingExtraDuplicateAndNonfiniteBeforeTolerance) {
const auto& report = RunPressureComparison();
const std::filesystem::path invalid_hdf5 =
report.generated_results_hdf5.parent_path() / "invalid-pressure.h5";
{
std::ofstream stream{invalid_hdf5, std::ios::binary | std::ios::trunc};
stream << "not an HDF5 file";
}
EXPECT_FALSE(
fesa::test::CompareHdf5ToReferenceCsv(MakePressureCase(), invalid_hdf5)
.HasValue());
fesa::test::NonlinearReferenceComparisonReport precheck_report{};
precheck_report.blocking_passed = true;
precheck_report.precheck_findings = {
{"pressure_nodal_translation_blocking",
fesa::test::NonlinearReferenceDisposition::kBlocking,
"missing-extra-duplicate-or-nonfinite",
{}}};
EXPECT_EQ(fesa::test::ClassifyReferenceComparison(precheck_report),
fesa::test::NonlinearReferenceClassification::kFailed);
}
TEST(ShellSurfacePressureReference,
ClassifiesTranslationBlockingAndRotationWarningOnly) {
fesa::test::NonlinearReferenceComparisonReport report{};
report.blocking_passed = true;
report.families = {{"pressure_nodal_translation_blocking",
{"U1", "U2", "U3"},
fesa::test::NonlinearReferenceDisposition::kBlocking,
2187U,
MakeTolerance(true)},
{"pressure_nodal_rotation_warning",
{"UR1", "UR2", "UR3"},
fesa::test::NonlinearReferenceDisposition::kWarningOnly,
2187U,
MakeTolerance(false)}};
EXPECT_EQ(
fesa::test::ClassifyReferenceComparison(report),
fesa::test::NonlinearReferenceClassification::kWarningOnlyDifferences);
}
TEST(ShellSurfacePressureReference,
ExcludesReactionAndStressFamiliesFromVerdict) {
const auto& report = RunPressureComparison();
EXPECT_EQ(report.families.size(), 2U);
EXPECT_EQ(
FindFamily(report, "pressure_nodal_translation_blocking").disposition,
fesa::test::NonlinearReferenceDisposition::kBlocking);
EXPECT_EQ(FindFamily(report, "pressure_nodal_rotation_warning").disposition,
fesa::test::NonlinearReferenceDisposition::kWarningOnly);
}
TEST(ShellSurfacePressureReference,
UsesUnchangedCommonScaleNearZeroRelativeAndRmsPolicy) {
const auto& translation = FindFamily(RunPressureComparison(),
"pressure_nodal_translation_blocking");
EXPECT_EQ(translation.tolerance.near_zero_band,
fesa::test::ReferenceTolerancePolicy::kNearZeroRatio *
translation.tolerance.reference_scale);
EXPECT_LE(translation.tolerance.relative_rms,
fesa::test::ReferenceTolerancePolicy::kRelativeRmsTolerance);
EXPECT_TRUE(translation.tolerance.rms_passed);
}
TEST(ShellSurfacePressureReference, WritesDeterministicComparisonReport) {
const auto& first = RunPressureComparison();
const auto comparison = fesa::test::CompareHdf5ToReferenceCsv(
MakePressureCase(), first.generated_results_hdf5);
ASSERT_TRUE(comparison.HasValue());
ASSERT_EQ(first.families.size(), comparison.Value().families.size());
for (std::size_t index = 0U; index < first.families.size(); ++index) {
const auto& expected = first.families[index];
const auto& actual = comparison.Value().families[index];
EXPECT_EQ(actual.family_identity, expected.family_identity);
EXPECT_EQ(actual.components, expected.components);
EXPECT_EQ(actual.disposition, expected.disposition);
EXPECT_EQ(actual.row_count, expected.row_count);
EXPECT_DOUBLE_EQ(actual.tolerance.reference_scale,
expected.tolerance.reference_scale);
EXPECT_DOUBLE_EQ(actual.tolerance.relative_rms,
expected.tolerance.relative_rms);
EXPECT_EQ(actual.tolerance.worst_row, expected.tolerance.worst_row);
}
}
@@ -478,6 +478,7 @@ TEST(NonlinearHdf5ResultsWriter,
EXPECT_EQ(H5Lexists(file.Get(), "/steps/Step-1/frames/2", H5P_DEFAULT), 0);
EXPECT_EQ(H5Lexists(file.Get(), "/steps/Step-1/trial_history", H5P_DEFAULT),
0);
EXPECT_EQ(H5Lexists(file.Get(), "/surface_pressure", H5P_DEFAULT), 0);
const std::string root = "/steps/Step-1/frames/1";
for (const std::string& suffix :