Compare commits
10 Commits
ecbdc7dc49
...
c903ff0b48
| Author | SHA1 | Date | |
|---|---|---|---|
| c903ff0b48 | |||
| e5e8ccc8ff | |||
| 1eb4dce885 | |||
| c4d1e2af2f | |||
| bf2339c6a9 | |||
| 2c91462a45 | |||
| 3262d9512c | |||
| 6817942894 | |||
| d099894e87 | |||
| 9b19cc1b18 |
@@ -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의 판정 범위 밖이다.
|
||||
@@ -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
@@ -21,7 +21,8 @@
|
||||
},
|
||||
{
|
||||
"dir": "shell-surface-pressure-load",
|
||||
"status": "pending"
|
||||
"status": "completed",
|
||||
"completed_at": "2026-09-01T17:43:57+0900"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 :
|
||||
|
||||
Reference in New Issue
Block a user