605 lines
35 KiB
Markdown
605 lines
35 KiB
Markdown
# MITC4 구현 회고 및 보충 기록
|
|
|
|
## 1. 문서 목적과 범위
|
|
|
|
이 문서는 `linear-static-mitc4-shell` 기능을 요구조건부터 `dev` 병합 검증까지
|
|
진행하면서 실제로 겪은 시행착오, 실수, 어려움과 그 해결 방법을 기록한다. 새 계약을
|
|
정의하는 문서가 아니라, 이미 승인된 요구조건·정식화·I/O·reference·release 문서를
|
|
보충하는 회고 자료다. 계약이 충돌하면 이 문서가 아니라 다음 문서를 우선한다.
|
|
|
|
- `docs/requirements/linear-static-mitc4-shell.md`
|
|
- `docs/formulations/mitc4-shell-formulation.md`
|
|
- `docs/io-definitions/linear-static-mitc4-shell-io.md`
|
|
- `docs/reference-models/linear-static-mitc4-shell-reference-models.md`
|
|
- `docs/numerical-reviews/linear-static-mitc4-shell-review.md`
|
|
- `docs/releases/linear-static-mitc4-shell-release.md`
|
|
|
|
라인 참조는 회고 작성 시점의 기준 커밋 `6c41cde41af4d6cd474b008e5ae81769fff0aa79`
|
|
을 사용한다. `path:line`은 이 기준 커밋의 파일과 라인을 뜻한다. 중간 실패가 최종
|
|
phase ledger에서 정상 완료 상태로 교체된 경우에는 `commit:path:line`으로 historical
|
|
snapshot을 표시한다. 이후 파일이 수정되면 라인이 이동할 수 있으므로 커밋과 검색어를
|
|
함께 확인해야 한다.
|
|
|
|
이 문서는 다음 세 범주를 구분한다.
|
|
|
|
1. **실수 또는 결함**: 승인된 의미와 다른 동작, 잘못된 가정, 환경 실패의 오분류.
|
|
2. **계약 변경에 따른 재작업**: 구현 결함이 아니라 승인 범위나 tolerance가 바뀌어
|
|
이미 작성한 문서·테스트·비교기를 수정한 경우.
|
|
3. **예상된 TDD RED와 수치적 어려움**: 계획된 실패로 아직 없는 동작을 확인하거나,
|
|
shell 요소 특성상 조심해서 해결해야 했던 문제. 이것을 구현 실수로 과장하지 않는다.
|
|
|
|
## 2. 주요 시행착오 요약
|
|
|
|
| ID | 분류 | 문제 또는 어려움 | 핵심 해결 | 대표 근거 |
|
|
| --- | --- | --- | --- | --- |
|
|
| `RET-01` | 프로세스 실수 | numerical review가 수치 정식화 외의 bundle 행정 정보를 readiness blocker로 취급함 | FESA 독립 솔버 원칙과 실제 비교에 필요한 최소 계약으로 gate를 재정의함 | `docs/ADR.md:189`, `docs/numerical-reviews/linear-static-mitc4-shell-review.md:80` |
|
|
| `RET-02` | 프로세스 어려움 | Planning Agent, Implementation Agent, Executor, hook의 소유권이 모호했음 | 승인·materialize·Step 실행·상태/커밋 책임을 분리함 | `.codex/agents/implementation-agent.toml:46`, `.agents/skills/harness/SKILL.md:16` |
|
|
| `RET-03` | 환경 결함 | Windows sandbox helper 실패가 프로세스 종료 코드 `0` 안에 숨고 Step 0이 갱신되지 않음 | 진단 marker를 종료 코드보다 먼저 검사하고 승인된 sandbox override를 추가함 | `scripts/execute.py:247`, `scripts/execute.py:306` |
|
|
| `RET-04` | 수치적 어려움 | 20 physical DOF MITC4를 외부 24 DOF shell 계약에 넣으면서 drilling을 분리해야 했음 | physical과 drilling congruence를 분리하고 회전 대각항만으로 고정 안정화를 계산함 | `docs/formulations/mitc4-shell-formulation.md:759`, `src/fesa/elements/mitc4_shell.cpp:597` |
|
|
| `RET-05` | 기하 처리 어려움 | warped mesh와 공유 절점에서 initial director의 방향·순서·유효성을 결정해야 했음 | source-order 정렬, 면적 가중 평균, 양의 incident-normal 방향, 전 적분점 `J>0` 검사를 사용함 | `src/fesa/model/shell_geometry.cpp:275`, `src/fesa/model/shell_geometry.cpp:317` |
|
|
| `RET-06` | 범위 경계 | 선형 구현에 future geometric-nonlinear tangent 정식화가 섞일 위험이 있었음 | 선형 24 DOF 구현과 조건부 nonlinear 20-to-24 pullback을 명시적으로 분리함 | `docs/formulations/mitc4-shell-formulation.md:1112`, `docs/numerical-reviews/linear-static-mitc4-shell-review.md:71` |
|
|
| `RET-07` | parser 결함 | 같은 이름의 Abaqus `NSET`과 `ELSET`을 중복 entity로 잘못 거부함 | node-set과 element-set namespace를 분리함 | `src/fesa/io/abaqus/domain_mapper.cpp:401`, `tests/unit/io/abaqus/domain_mapper_test.cpp:439` |
|
|
| `RET-08` | comparator 결함 | Abaqus CSV와 HDF5 instance name의 대소문자 차이가 row identity mismatch를 만듦 | 비교 key만 ASCII 대문자로 정규화하고 source identity 출력은 보존함 | `tests/reference/mitc4_reference_comparison.cpp:122`, `tests/reference/mitc4_reference_comparison_test.cpp:539` |
|
|
| `RET-09` | 계약 재작업 | 동일한 FESA kernel 결과를 Abaqus S4와 S4R 두 reference에 동시에 맞출 수 없었음 | full-integration FESA의 blocking reference를 S4 하나로 제한하고 S4R은 비-reference test로 검증함 | `docs/requirements/linear-static-mitc4-shell.md:201`, `docs/requirements/linear-static-mitc4-shell.md:205` |
|
|
| `RET-10` | 계약 재작업 | B33 혼합 tolerance가 독립 shell 정식화 비교에 지나치게 엄격해 kernel 결함과 계약 차이를 혼동함 | invariant/patch 검증 후 MITC4 고정 절대 tolerance `1.0e-5`로 계약과 비교기를 함께 변경함 | `docs/ADR.md:215`, `tests/reference/mitc4_reference_comparison.cpp:35` |
|
|
| `RET-11` | 통합 어려움 | prescribed-only shell에서 이미 상쇄된 `K*d`만으로 residual을 정규화하면 정상 roundoff가 단위 residual이 됨 | `Kff*df`, `Kfc*dc`, `Ff`의 물리적 항 크기로 정규화함 | `src/fesa/results/result_recovery.cpp:674`, `phases/linear-static-mitc4-shell/index.json:102` |
|
|
| `RET-12` | 상태/출력 안전성 | recovery 또는 HDF5 inventory가 뒤에서 실패할 때 기존 정상 state/output을 손상할 위험이 있었음 | candidate state와 temporary HDF5를 완전히 검증한 뒤 atomic commit/replace함 | `src/fesa/results/result_recovery.cpp:925`, `src/fesa/io/hdf5/hdf5_results_writer.cpp:2579` |
|
|
| `RET-13` | 병합 환경 오염 | `dev` worktree의 ignored B33 `.h5`가 exact reference inventory test를 실패시킴 | reference를 수정하지 않고 ignored 파일을 quarantine으로 이동한 뒤 전체 검증함 | `.gitignore:20`, `tests/reference/b33_reference_comparison_test.cpp:178` |
|
|
|
|
## 3. 계약과 개발 프로세스에서의 시행착오
|
|
|
|
### 3.1 Numerical Review가 행정 메타데이터에 과도하게 의존함 (`RET-01`)
|
|
|
|
#### 문제
|
|
|
|
초기 numerical review는 정식화의 수학적 준비 상태뿐 아니라 canonical bundle 이름,
|
|
`README.md`, provenance, units, step/frame 설명까지 갖춰야 implementation planning으로
|
|
넘어갈 수 있다고 판단했다. `metadata.json` 자체는 optional로 정리했지만, 그 주변의
|
|
행정 정보가 여전히 primary verdict를 `needs-reference-model`로 유지했다.
|
|
|
|
Historical evidence는 다음과 같다.
|
|
|
|
- `ebb2657:docs/numerical-reviews/linear-static-mitc4-shell-review.md:12` — status가
|
|
`needs-reference-model`이었다.
|
|
- `0428759:docs/numerical-reviews/linear-static-mitc4-shell-review.md:94` — alias,
|
|
README, provenance, unit, step/frame, schema, tolerance를 open blocker로 묶었다.
|
|
- `0428759:docs/numerical-reviews/linear-static-mitc4-shell-review.md:702` — `NR-O01`부터
|
|
`NR-O04`까지 calibration을 implementation-planning blocker로 남겼다.
|
|
|
|
#### 원인
|
|
|
|
Reference artifact의 재현성 감사와 현재 기능의 수치 readiness를 같은 gate로 취급했다.
|
|
또한 Abaqus가 제공하는 bundle 관리 방식과 FESA가 실제로 비교에 필요한 observable
|
|
quantity 계약을 충분히 분리하지 못했다. 결과적으로 정식화 결함이 아닌 문서 형식이
|
|
수치 검토를 막았다.
|
|
|
|
#### 해결
|
|
|
|
프로젝트 정책을 다음처럼 다시 고정했다.
|
|
|
|
- FESA는 Abaqus의 내부 알고리즘을 재현하는 솔버가 아니다
|
|
(`docs/ADR.md:189-195`).
|
|
- readiness에 필요한 것은 선언된 input/CSV, deterministic source row mapping,
|
|
component와 tolerance다 (`docs/ADR.md:197-202`).
|
|
- `NR-O01`은 고정 drilling 계수 결정으로 닫고, `NR-O02`부터 `NR-O04`까지는 승인
|
|
범위에서 제거했다 (`docs/numerical-reviews/linear-static-mitc4-shell-review.md:74-77`).
|
|
- 과거 blocker였던 canonical naming, README, `metadata.json`, provenance와 expanded
|
|
portfolio가 formulation verdict를 막지 않는다고 명시했다
|
|
(`docs/numerical-reviews/linear-static-mitc4-shell-review.md:80-83`).
|
|
|
|
관련 정책 변경 커밋은 `5c08f1c`(independent reference validation policy),
|
|
`73df844`(MITC4 verification/drilling scope 단순화), `60b42f4`(numerical review pass)다.
|
|
|
|
#### 교훈
|
|
|
|
Reference gate에는 “이 정보가 실제 row matching 또는 수치 판정에 사용되는가?”를 먼저
|
|
물어야 한다. 사용되지 않는 행정 메타데이터를 모든 기능의 blocking requirement로
|
|
자동 승격하면 독립 솔버의 물리 검증보다 형식 준수가 우선된다.
|
|
|
|
### 3.2 Harness 역할과 Step 소유권이 처음부터 충분히 명확하지 않았음 (`RET-02`)
|
|
|
|
#### 문제
|
|
|
|
구현 계획 승인, phase 파일 materialize, Harness 실행, Step 선택, 구현, timestamp와
|
|
commit 기록의 주체가 여러 agent 문서에 분산되어 있었다. 이 상태에서는 Planning Agent가
|
|
실행까지 시작하거나, Implementation Agent가 다음 Step으로 넘어가거나, hook을 수동
|
|
검증 명령처럼 실행하는 실수가 생길 수 있었다.
|
|
|
|
#### 해결
|
|
|
|
다음 소유권을 agent와 Harness 문서에 중복 없이 고정했다.
|
|
|
|
- Planning Agent는 multi-Step draft 승인 후에만 phase 파일을 만들고 Step을 실행하지
|
|
않는다 (`.codex/agents/implementation-planning-agent.toml:52-60`).
|
|
- Implementation Agent는 Executor가 선택한 현재 `stepN.md` 하나만
|
|
`RED -> GREEN -> VERIFY`로 수행한다 (`.codex/agents/implementation-agent.toml:46-60`).
|
|
- branch, retry, timestamp, commit과 next-Step selection은 Executor 소유다
|
|
(`.codex/agents/implementation-agent.toml:64-67`).
|
|
- hook은 등록된 PreToolUse/Stop lifecycle로 자동 실행하며 수동 호출로 대체하지 않는다
|
|
(`.agents/skills/harness/SKILL.md:16-25`).
|
|
- 계획 승인은 실행 승인이 아니며 별도의 사용자 요청이 있어야 한다
|
|
(`docs/HARNESS_WORKFLOW.md:41-42`).
|
|
|
|
이 개선은 `85cd17d`, `ab69a3d`, `713b41b`, `65d5e07`, `18296a1`, `a058ef7`
|
|
커밋에 걸쳐 정리됐다.
|
|
|
|
#### 교훈
|
|
|
|
Agentic phase에서 “무엇을 할 것인가”뿐 아니라 “누가 상태를 바꾸는가”를 계약해야 한다.
|
|
특히 implementation summary와 executor timestamp/commit을 한 주체가 모두 소유하게 하면
|
|
재시도와 감사 이력이 쉽게 꼬인다.
|
|
|
|
### 3.3 Windows sandbox 실패가 정상 종료처럼 보임 (`RET-03`)
|
|
|
|
#### 문제
|
|
|
|
Step 0의 첫 Harness 실행은 세 번 재시도한 뒤에도 Step status를 갱신하지 못했다.
|
|
historical ledger는 `8dd4d72:phases/linear-static-mitc4-shell/index.json:10`에
|
|
`[3회 시도 후 실패] Step did not update status`를 기록한다. 실제 원인은 구현 코드가
|
|
아니라 Windows sandbox helper의 `orchestrator_helper_launch_failed`였다. 더 까다로운 점은
|
|
이 진단이 Codex JSON output 안에 있으면서 process exit code는 `0`일 수 있었다는 것이다.
|
|
|
|
#### 해결
|
|
|
|
- `_codex_environment_failure()`가 exit code를 보기 전에 diagnostic text에서 sandbox
|
|
helper marker를 검사하도록 바꿨다 (`scripts/execute.py:306-314`).
|
|
- 기본 `workspace-write`는 유지하되, 승인된 격리 worktree에 한해서
|
|
`FESA_HARNESS_CODEX_SANDBOX=danger-full-access`를 허용했다
|
|
(`scripts/execute.py:247-254`, `docs/HARNESS_WORKFLOW.md:247-252`).
|
|
- unknown mode는 fail-closed로 거부하고, exit code `0` 안의 helper failure도 잡는
|
|
회귀 테스트를 추가했다 (`tests/test_execute.py:77-111`).
|
|
|
|
수정 커밋은 `0d50625`이며, 이후 Harness Python suite `7/7`이 통과했다
|
|
(`docs/releases/linear-static-mitc4-shell-release.md:176-178`).
|
|
|
|
#### 교훈
|
|
|
|
Agent runner에서는 OS process exit code만 신뢰하면 안 된다. 하위 orchestrator가 구조화된
|
|
출력 안에 fatal environment error를 기록하는 경우가 있으므로, 알려진 환경 실패 marker와
|
|
상태 전이를 함께 검사해야 한다.
|
|
|
|
## 4. 정식화와 수치 구현의 어려움
|
|
|
|
### 4.1 20 physical DOF와 24 global DOF 사이의 경계 (`RET-04`)
|
|
|
|
#### 어려움
|
|
|
|
MITC4의 physical field는 절점당 translation 3개와 director-tangent rotation 2개, 즉
|
|
총 20 DOF다. 반면 FESA shell 외부 계약은 절점당 6 DOF, 총 24 DOF다. 남는 네 개의
|
|
director-parallel 회전은 drilling 좌표이며 physical MITC4 strain에 들어가면 안 된다.
|
|
|
|
초기의 “작은 diagonal stiffness를 더한다”는 수준의 설명만으로는 다음 실수가 가능했다.
|
|
|
|
- translation과 rotation diagonal을 섞어 단위가 다른 값으로 `k_d`를 정하는 문제;
|
|
- drilling energy를 physical shell energy나 stress recovery에 섞는 문제;
|
|
- 20 DOF weak form에 24 DOF external work를 직접 결합하는 문제;
|
|
- 작은 값이므로 rigid mode나 rank 검증을 생략하는 문제.
|
|
|
|
Numerical Review에서 이 위험은 `NR-C02`부터 `NR-C04`로 추적됐다
|
|
(`docs/numerical-reviews/linear-static-mitc4-shell-review.md:68-70`).
|
|
|
|
#### 해결
|
|
|
|
정식화와 코드를 두 경로로 나눴다.
|
|
|
|
1. `K20`은 MITC tying shear와 physical constitutive matrix만으로 적분한다
|
|
(`src/fesa/elements/mitc4_shell.cpp:597-636`).
|
|
2. `k_d` reference는 절점당 두 개, 총 여덟 개 physical tangent-rotation diagonal의
|
|
finite positive 값만 사용한다 (`src/fesa/elements/mitc4_shell.cpp:638-655`).
|
|
3. `k_d = 1.0e-3 * min(R+)`를 적용한다
|
|
(`src/fesa/elements/mitc4_shell.cpp:662-676`).
|
|
4. physical과 drilling을 별도 congruence로 24 DOF에 올린 후 마지막에만 합한다
|
|
(`src/fesa/elements/mitc4_shell.cpp:669-681`).
|
|
5. recovery는 global 24 DOF를 physical 20 DOF로 projection한 뒤 계산하므로 pure drill이
|
|
strain/resultant/stress/physical energy에 기여하지 않는다
|
|
(`src/fesa/elements/mitc4_shell.cpp:699-714`).
|
|
|
|
이를 energy congruence, rank와 six rigid modes, patch field, fixed drilling factor,
|
|
pure-drill exclusion 테스트로 각각 검증했다
|
|
(`tests/unit/elements/mitc4_shell_test.cpp:618`, `:647`, `:697`, `:752`, `:828`).
|
|
|
|
#### 교훈
|
|
|
|
수치 안정화는 크기만 작은 physical stiffness가 아니다. coordinate map, 단위, virtual
|
|
work와 output 의미를 별도 경계로 정의해야 한다. 안정화 항을 physical result에서 제외하는
|
|
테스트가 coefficient 자체의 테스트만큼 중요하다.
|
|
|
|
### 4.2 Initial director와 warped/shared-node geometry의 결정성 (`RET-05`)
|
|
|
|
#### 어려움
|
|
|
|
“초기 법선벡터를 두께 방향으로 사용한다”는 결정은 단일 평면 요소에는 단순하지만,
|
|
공유 절점과 warped mesh에서는 다음 선택을 추가로 요구했다.
|
|
|
|
- element normal의 부호를 어느 source connectivity 순서로 정할지;
|
|
- 여러 incident element normal을 어떤 순서와 weight로 평균할지;
|
|
- 반대 방향 normal, fold, zero-area, local reversal을 허용할지;
|
|
- stiffness, tying, recovery가 서로 다른 geometry inventory를 사용하지 않게 할지.
|
|
|
|
#### 해결
|
|
|
|
- source connectivity로 center normal과 element orientation을 결정했다.
|
|
- required surface points에서 finite/nonzero measure와 positive orientation을 먼저
|
|
검사했다 (`src/fesa/model/shell_geometry.cpp:275-300`).
|
|
- 공유 절점의 incident element를 stable source identity 순서로 정렬했다
|
|
(`src/fesa/model/shell_geometry.cpp:317-327`).
|
|
- 모든 incident normal 쌍이 같은 positive hemisphere에 있는지 확인한 다음 면적 가중
|
|
평균을 계산했다 (`src/fesa/model/shell_geometry.cpp:328-364`).
|
|
- geometry validity를 center, stiffness, tying, recovery point inventory 전체에서
|
|
fail-closed로 검사했다. Calibration angle이나 distortion cutoff를 새로 만들지는 않았다
|
|
(`docs/formulations/mitc4-shell-formulation.md:593`,
|
|
`docs/numerical-reviews/linear-static-mitc4-shell-review.md:76-77`).
|
|
|
|
평면·회전·warped geometry, stable area weighting, invalid Jacobian/opposed normal,
|
|
validation-point inventory 테스트가 각각 존재한다
|
|
(`tests/unit/model/shell_geometry_test.cpp:101`, `:153`, `:184`, `:244`).
|
|
|
|
#### 교훈
|
|
|
|
자동 normal 생성은 단순한 vector 계산이 아니라 mesh topology와 deterministic reduction
|
|
계약이다. 순서를 명시하지 않으면 병렬화나 input order 변화가 director와 결과에 영향을
|
|
줄 수 있다.
|
|
|
|
### 4.3 Geometric-nonlinear tangent를 선형 구현으로 오인할 위험 (`RET-06`)
|
|
|
|
#### 어려움
|
|
|
|
정식화 문서에는 future geometric-nonlinear tangent도 포함했지만, 현재 제품 범위는 선형
|
|
정적이다. 20-coordinate director chart의 material/geometric tangent만 적는 것으로
|
|
global 24 DOF nonlinear element가 완성되는 것은 아니다. finite rotation update,
|
|
`Phi: R24 -> R20`, 그 1·2차 미분, chart recentering과 objective drilling potential이
|
|
필요하다.
|
|
|
|
#### 해결
|
|
|
|
- nonlinear 식이 Section 15.3까지 20-coordinate physical chart에만 존재하며 완전한
|
|
global 24 DOF element가 아니라고 명시했다
|
|
(`docs/formulations/mitc4-shell-formulation.md:1112-1113`).
|
|
- conditional pullback에는 map-curvature Hessian 항을 유지하고, 미정인 coordinate map과
|
|
drilling을 future-only blocker로 남겼다
|
|
(`docs/numerical-reviews/linear-static-mitc4-shell-review.md:71`).
|
|
- current review의 open question은 nonlinear rotation/map/output/state 계약뿐이며 현재
|
|
선형 planning을 차단하지 않는다
|
|
(`docs/numerical-reviews/linear-static-mitc4-shell-review.md:85-90`).
|
|
- 구현 phase에는 linear `K20 -> K24`와 고정 drilling만 넣고 nonlinear state나 tangent
|
|
API를 만들지 않았다.
|
|
|
|
#### 교훈
|
|
|
|
문서에 수식이 있다는 사실과 제품 계약이 닫혔다는 사실은 다르다. 특히 nonlinear
|
|
coordinate map의 Hessian을 생략한 채 “consistent tangent”라고 부르면 이후 Newton
|
|
iteration의 일관성을 잘못 주장하게 된다.
|
|
|
|
## 5. Parser와 결과 파이프라인의 실제 결함
|
|
|
|
### 5.1 Abaqus NSET/ELSET namespace를 하나로 처리함 (`RET-07`)
|
|
|
|
#### 증상
|
|
|
|
Step 13 reference E2E는 comparator contract test가 통과한 뒤에도 모든 S4/S4R case에서
|
|
HDF5 생성 전에 `duplicate-entity`로 실패했다. historical ledger
|
|
`32f0e59:phases/linear-static-mitc4-shell/index.json:113`은 part `NSET Set-1`과
|
|
`ELSET Set-1`을 같은 namespace로 검사한 것이 원인임을 기록한다. Step 13의 test-only
|
|
소유 범위에서 production parser를 고칠 수 없었기 때문에 세 번 재시도 후 upstream
|
|
correction이 필요했다.
|
|
|
|
#### 원인과 해결
|
|
|
|
기존 `containsSetName()`은 part의 node set과 element set 양쪽을 함께 검색했다.
|
|
Abaqus input에서는 두 종류가 별도 namespace이므로 같은 이름이 유효하다.
|
|
|
|
- part helper가 전달받은 set 종류 하나만 검색하도록 변경했다
|
|
(`src/fesa/io/abaqus/domain_mapper.cpp:401-408`).
|
|
- assembly helper도 `isNodeSet`이 같은 항목만 비교하도록 변경했다
|
|
(`src/fesa/io/abaqus/domain_mapper.cpp:410-418`).
|
|
- part와 assembly에서 같은 이름의 NSET/ELSET을 허용하되 같은 종류의 실제 중복은
|
|
계속 거부하는 회귀 테스트를 추가했다
|
|
(`tests/unit/io/abaqus/domain_mapper_test.cpp:439`).
|
|
|
|
수정 커밋은 `b805683`이다.
|
|
|
|
#### 교훈
|
|
|
|
Source label text가 같다는 이유만으로 semantic identity가 같은 것은 아니다. parser의
|
|
duplicate 검사는 `(entity kind, scope, source name)` 전체 identity를 사용해야 한다.
|
|
|
|
### 5.2 Reference instance identity의 대소문자 차이를 그대로 비교함 (`RET-08`)
|
|
|
|
#### 증상과 원인
|
|
|
|
Parser namespace를 고친 뒤 reference precheck에서 Abaqus CSV의 `part-1-1`과 FESA HDF5의
|
|
`Part-1-1`이 다른 key로 취급됐다. CSV field whitespace는 이미 trim했지만 Abaqus name의
|
|
case-insensitive 의미를 row identity comparator에 적용하지 않았다.
|
|
|
|
#### 해결
|
|
|
|
- comparator ordering/equality key에서 instance name을 ASCII uppercase로 정규화했다
|
|
(`tests/reference/mitc4_reference_comparison.cpp:122-142`).
|
|
- report에는 HDF5가 보존한 stable source identity를 그대로 남겼다. 즉 비교를 위해 source
|
|
artifact를 rename하거나 rewrite하지 않았다.
|
|
- lowercase CSV identity가 정상 match되는 회귀 테스트를 추가했다
|
|
(`tests/reference/mitc4_reference_comparison_test.cpp:539`).
|
|
|
|
수정 커밋은 `56833ab`이다.
|
|
|
|
#### 교훈
|
|
|
|
Identity normalization은 어느 계층에서 어떤 목적으로 하는지 제한해야 한다. comparator
|
|
key의 case normalization과 source identity 보존을 분리하면 matching은 견고해지면서도
|
|
진단·HDF5 identity가 조용히 바뀌는 문제를 피할 수 있다.
|
|
|
|
### 5.3 Prescribed-only free residual normalization (`RET-11`)
|
|
|
|
#### 증상
|
|
|
|
Step 12의 nonzero prescribed-displacement shell case는 실제 equilibrium을 만족했지만
|
|
free-residual 검증에서 실패했다. 이미 상쇄된 `K*d` 또는 외력만 normalization scale로
|
|
쓰면 `Ff=0`이고 `Kff*df`와 `Kfc*dc`가 서로 상쇄되는 case에서 작은 roundoff가 상대적으로
|
|
`1`에 가까운 residual처럼 보일 수 있었다. Phase ledger가 이 실패와 해결을 기록한다
|
|
(`phases/linear-static-mitc4-shell/index.json:102-107`).
|
|
|
|
#### 해결
|
|
|
|
free residual `Kff*df + Kfc*dc - Ff`의 세 물리 항을 따로 평가하고 그 최대 norm을
|
|
denominator로 사용했다 (`src/fesa/results/result_recovery.cpp:674-697`). 임의의
|
|
`max(1, ...)` floor는 넣지 않았고, scale과 residual이 둘 다 정확히 0일 때만 normalized
|
|
residual을 0으로 처리했다.
|
|
|
|
Integration test는 `Ff=0`인데 `-Kfc*dc`만으로 effective RHS가 생기는 것을 직접 확인한다
|
|
(`tests/integration/analysis/linear_static_analysis_test.cpp:440-464`).
|
|
|
|
#### 교훈
|
|
|
|
Residual normalization은 최종 합의 크기만 보면 안 된다. 서로 상쇄되는 원래 방정식 항의
|
|
물리적 scale을 보존해야 prescribed load, zero load와 mixed constraint를 같은 기준으로
|
|
검증할 수 있다.
|
|
|
|
### 5.4 Recovery state와 HDF5 output의 실패 원자성 (`RET-12`)
|
|
|
|
#### 어려움
|
|
|
|
Shell 결과는 displacement 하나가 아니라 residual/reaction, GP strain/resultant,
|
|
BOTTOM/MIDDLE/TOP stress, frame, physical energy와 equilibrium evidence를 함께 commit한다.
|
|
뒤쪽 row가 invalid일 때 일부 vector만 기존 `AnalysisState`에 반영하거나, 기존 정상
|
|
`results.h5`를 불완전한 새 파일로 바꾸면 실패가 이전 성공 상태를 손상한다.
|
|
|
|
TDD 과정에서 다음 문제가 드러났다.
|
|
|
|
- invalid shell candidate가 이전 state를 덮을 수 있음
|
|
(`phases/linear-static-mitc4-shell/index.json:78-83`);
|
|
- beam-only recovery 뒤 stale shell evidence가 남을 수 있음
|
|
(`phases/linear-static-mitc4-shell/index.json:85-91`);
|
|
- invalid HDF5 inventory가 기존 final output을 안전하지 않게 교체할 수 있음
|
|
(`phases/linear-static-mitc4-shell/index.json:93-99`).
|
|
|
|
#### 해결
|
|
|
|
- recovery는 완전한 `candidateState`를 복사해 모든 shell row를 검증하고 마지막에 한 번만
|
|
move-commit한다 (`src/fesa/results/result_recovery.cpp:925-940`).
|
|
- HDF5 writer는 unique temporary file에 쓰고 닫은 뒤 read-only로 reopen/self-check한
|
|
파일만 final path로 교체한다 (`src/fesa/io/hdf5/hdf5_results_writer.cpp:2579-2592`).
|
|
- drilling energy와 unsupported point stress가 accidental output으로 생기지 않았는지도
|
|
self-check한다 (`src/fesa/io/hdf5/hdf5_results_writer.cpp:2449-2455`).
|
|
- invalid shell inventory가 기존 final bytes를 보존하는 회귀 테스트를 추가했다
|
|
(`tests/unit/io/hdf5/hdf5_results_writer_test.cpp:1359`).
|
|
|
|
#### 교훈
|
|
|
|
Solver output의 원자성은 HDF5 backend만의 책임이 아니다. recovery candidate의 의미적
|
|
완전성, temporary file의 구조적 완전성, final replacement 순서를 모두 지켜야 한다.
|
|
|
|
## 6. Reference comparison에서의 재작업
|
|
|
|
### 6.1 S4와 S4R을 동시에 blocking reference로 사용한 모순 (`RET-09`)
|
|
|
|
#### 증상
|
|
|
|
초기 reference plan은 S4와 S4R 두 Abaqus bundle을 모두 blocking comparison으로
|
|
사용했다. 그러나 FESA 계약은 두 source type을 같은 full-integration FESA-MITC4 kernel에
|
|
매핑한다. 실제 Abaqus S4와 S4R은 동일한 내부 정식화가 아니므로 두 Abaqus reference
|
|
결과도 달랐다.
|
|
|
|
Historical ledger `cf769aa:phases/linear-static-mitc4-shell/index.json:113`은 다음 불가능한
|
|
상태를 수치로 기록한다.
|
|
|
|
- 두 FESA deck은 heading과 `TYPE=S4/S4R` 외에는 같고 동일 stiffness를 생성함;
|
|
- Abaqus center `U3`는 두 reference 사이에 `8.190036e-7` 차이가 남;
|
|
- 당시 더 큰 tolerance도 약 `1.02456e-9`여서 하나의 공통 FESA 결과가 두 reference를
|
|
동시에 통과할 수 없음.
|
|
|
|
이는 kernel을 S4와 S4R에 따라 다르게 만들라는 신호가 아니었다. 그렇게 하면 “둘 다
|
|
동일한 FESA formulation”이라는 승인 계약과 Abaqus 비동등성 원칙을 위반한다.
|
|
|
|
#### 해결
|
|
|
|
- blocking Abaqus comparison은 `reference/shell/`의 S4 input/displacement CSV 하나로
|
|
고정했다 (`docs/requirements/linear-static-mitc4-shell.md:201-205`).
|
|
- `reference/shellR/`은 acceptance에서 소비하지 않는다
|
|
(`docs/requirements/linear-static-mitc4-shell.md:202`).
|
|
- S4R 지원은 parser identity, 동일 sparse stiffness, HDF5 source metadata test로
|
|
검증한다 (`tests/unit/io/abaqus/domain_mapper_test.cpp:538`,
|
|
`tests/unit/assembly/sparse_assembler_test.cpp:324`,
|
|
`tests/unit/io/hdf5/hdf5_results_writer_test.cpp:1167`).
|
|
- S4 E2E test만 유지했다 (`tests/reference/mitc4_reference_cases_test.cpp:159-169`).
|
|
|
|
문서 변경은 `91b2df9`, test 변경은 `f92c17d`에 기록됐다.
|
|
|
|
#### 교훈
|
|
|
|
입력 label을 지원하는 것과 그 label 이름을 가진 외부 solver element를 reference로
|
|
사용하는 것은 별도 결정이다. 공통 internal formulation이면 acceptance reference도 그
|
|
formulation의 observable 목적에 맞는 하나를 선택해야 한다.
|
|
|
|
### 6.2 Reference 실패를 kernel 결함과 tolerance 결함으로 분리하기 (`RET-10`)
|
|
|
|
#### 증상
|
|
|
|
S4-only gate로 줄인 뒤에도 25개의 nonzero `U3` row가 당시 B33식 tolerance를 넘었다.
|
|
historical ledger `8994ce9:phases/linear-static-mitc4-shell/index.json:113`은 worst row
|
|
`Part-1-1/node 2/U3`에서 다음을 기록한다.
|
|
|
|
- absolute error: `1.90378534915144e-7`;
|
|
- 당시 tolerance: `1.0237408203e-9`;
|
|
- normalized error: `185.9636`.
|
|
|
|
이 시점에 tolerance만 즉시 키우면 실제 MITC4 kernel 부호·tying·Jacobian 결함을 숨길 수
|
|
있고, 반대로 Abaqus S4와 동일하지 않은 FESA-MITC4에 B33 수준의 엄격한 tolerance를 계속
|
|
강제하면 올바른 독립 정식화를 실패로 오판할 수 있었다.
|
|
|
|
#### 독립 조사와 해결
|
|
|
|
먼저 reference 결과와 독립적인 다음 검사를 통과시켰다.
|
|
|
|
- 20-to-24 energy congruence와 six rigid modes/rank
|
|
(`tests/unit/elements/mitc4_shell_test.cpp:618-647`);
|
|
- membrane, bending, transverse shear와 twist patch
|
|
(`tests/unit/elements/mitc4_shell_test.cpp:697`);
|
|
- pure drilling의 physical recovery/energy exclusion
|
|
(`tests/unit/elements/mitc4_shell_test.cpp:828`);
|
|
- S4/S4R common stiffness와 deterministic assembly
|
|
(`tests/unit/assembly/sparse_assembler_test.cpp:324`);
|
|
- authoritative FESA HDF5에서 같은 stable error가 발생하며 comparator 자체의 row mismatch가
|
|
아님을 확인함.
|
|
|
|
그 뒤 user-approved contract를 MITC4 고정 절대 tolerance `1.0e-5`로 변경했다.
|
|
|
|
- ADR은 U blocking, UR warning-only와 reference scale의 diagnostic-only 성격을 고정한다
|
|
(`docs/ADR.md:215-222`).
|
|
- comparator는 모든 row에 하나의 fixed tolerance를 적용한다
|
|
(`tests/reference/mitc4_reference_comparison.cpp:35`, `:755-767`).
|
|
- 경계 바로 아래와 위, UR warning-only를 회귀 테스트한다
|
|
(`tests/reference/mitc4_reference_comparison_test.cpp:662`, `:712`).
|
|
- final reference verification에서 같은 worst absolute error는 tolerance의 약 `0.0190`이고
|
|
U `147/147`, UR `147/147`, warning `0`으로 통과했다
|
|
(`docs/reference-verifications/linear-static-mitc4-shell-reference-verification.md:138-157`).
|
|
|
|
문서 변경 커밋은 `f1be807`, comparator 완료 커밋은 `426cce1`이다.
|
|
|
|
#### 교훈
|
|
|
|
Reference mismatch가 보이면 먼저 element invariant와 patch test로 kernel을 독립 검증하고,
|
|
row identity/schema와 authoritative HDF5를 확인한 다음 tolerance 계약을 판단해야 한다.
|
|
Tolerance 완화가 kernel 조사보다 먼저 오면 결함 은폐가 되고, 외부 solver 내부 동등성을
|
|
가정한 과도한 tolerance는 false failure가 된다.
|
|
|
|
## 7. 정상적인 TDD RED와 실제 재작업의 구분
|
|
|
|
Phase ledger의 대부분 RED는 계획된 TDD 증거다. 다음 실패들은 “처음부터 구현이 잘못됐다”는
|
|
뜻이 아니라, 해당 Step이 소유한 behavior가 아직 없음을 테스트가 정확히 검출한 것이다.
|
|
|
|
| Step | 예상된 RED | GREEN에서 추가한 핵심 동작 | 근거 |
|
|
| --- | --- | --- | --- |
|
|
| 0-1 | shell semantic type과 mapping 부재 | S4/S4R source identity와 공통 FESA-MITC4 model | `phases/linear-static-mitc4-shell/index.json:5-19` |
|
|
| 2 | `shell_geometry.hpp` 부재 | deterministic normal/director와 geometry validation | `phases/linear-static-mitc4-shell/index.json:21-27` |
|
|
| 3 | `mitc4_shell.hpp` 부재 | 20 DOF kinematics, tying, constitutive, fixed quadrature | `phases/linear-static-mitc4-shell/index.json:29-35` |
|
|
| 4-5 | stiffness와 recovery API 부재 | physical/drilling stiffness 분리와 pure-drill-free recovery | `phases/linear-static-mitc4-shell/index.json:37-51` |
|
|
| 6-7 | shell scatter/assembly 부재 | 24-entry scatter와 source-ordered 576-entry COO reduction | `phases/linear-static-mitc4-shell/index.json:53-67` |
|
|
| 8 | drilling direction moment를 허용함 | aggregate moment projection 검사와 RHS 이전 거부 | `phases/linear-static-mitc4-shell/index.json:69-75` |
|
|
| 9-11 | state/recovery/HDF5 shell inventory 부재 | atomic state commit과 self-checked HDF5 replace | `phases/linear-static-mitc4-shell/index.json:77-99` |
|
|
| 12 | prescribed-only normalization 실패 | `Kff/Kfc/Ff` term-scale normalization | `phases/linear-static-mitc4-shell/index.json:101-107` |
|
|
|
|
반면 `RET-03`, `RET-07`, `RET-08`은 실제 환경 또는 코드 결함이었다. `RET-01`,
|
|
`RET-09`, `RET-10`은 upstream 정책·범위·tolerance 결정이 바뀌어 발생한 재작업이다.
|
|
이 구분을 유지해야 TDD의 의도된 실패를 품질 문제로 잘못 집계하지 않는다.
|
|
|
|
## 8. `dev` 병합 검증에서 발견한 ignored artifact 오염 (`RET-13`)
|
|
|
|
### 8.1 증상
|
|
|
|
Feature branch 자체와 병합 직후 MITC4 test는 통과했지만, `C:\git\FESADev`의 `dev`
|
|
worktree에서 full CTest를 실행하자 기존 B33 reference test 하나가 실패했다.
|
|
|
|
```text
|
|
B33ReferenceComparison.GeneratesAuthoritativeHdf5AndComparisonEvidence
|
|
referenceBefore.size() = 5, expected = 4
|
|
```
|
|
|
|
MITC4 변경으로 B33 reference가 바뀐 것이 아니라, 해당 worktree의
|
|
`reference/cantilever beam/`에 pre-existing `cantilever beam.h5`가 남아 있었다.
|
|
`.gitignore`가 모든 `.h5`를 무시하므로 일반 `git status`는 이 파일을 보여주지 않았다
|
|
(`.gitignore:20`). B33 test는 reference tree를 snapshot하고 정확히 네 파일만 있어야
|
|
한다고 검사한다 (`tests/reference/b33_reference_comparison_test.cpp:73`, `:178-191`).
|
|
|
|
### 8.2 해결
|
|
|
|
Reference artifact를 삭제하거나 수정하지 않았다. ignored HDF5를 다음 복구 가능한 위치로
|
|
이동했다.
|
|
|
|
```text
|
|
C:\git\FESADev\.harness\quarantine\preexisting-reference-artifacts\cantilever beam.h5
|
|
SHA-256: 233DF10D6A13941A477BC5133A0D686D939FEF598B3748B6DE69D2684320E01F
|
|
```
|
|
|
|
이동 뒤 focused B33 test `1/1`, full CTest `144/144`, Harness pytest `7/7`을 다시 실행했다.
|
|
Tracked reference path에는 변경이 없었다.
|
|
|
|
### 8.3 교훈
|
|
|
|
- clean worktree 감사에는 `git status`뿐 아니라 reference directory의 ignored file inventory도
|
|
포함해야 한다.
|
|
- 테스트가 exact artifact count를 요구하면 build/output extension이 ignore되어 있는지
|
|
함께 확인해야 한다.
|
|
- 사용자 또는 이전 실행이 만든 ignored file은 바로 삭제하지 말고 hash를 기록해 workspace
|
|
내부 quarantine으로 이동하는 것이 안전하다.
|
|
|
|
## 9. 재사용할 개발 체크리스트
|
|
|
|
### 9.1 Requirements와 Numerical Review
|
|
|
|
- [ ] 외부 solver의 input label, internal algorithm과 observable reference quantity를
|
|
분리했는가?
|
|
- [ ] readiness blocker가 실제 수치 판정에 필요한 정보인지 확인했는가?
|
|
- [ ] optional metadata, provenance, bundle naming을 자동으로 blocking gate로 올리지 않았는가?
|
|
- [ ] future nonlinear 식과 현재 executable scope를 명확히 분리했는가?
|
|
|
|
### 9.2 Shell kernel
|
|
|
|
- [ ] physical DOF와 numerical stabilization DOF의 transform, energy와 output을 분리했는가?
|
|
- [ ] translation/rotation 혼합 단위의 raw diagonal 또는 spectrum으로 계수를 정하지 않는가?
|
|
- [ ] normal/director reduction order와 invalid orientation predicate가 deterministic한가?
|
|
- [ ] reference 전에 rigid mode, rank, energy congruence와 independent patch를 통과하는가?
|
|
|
|
### 9.3 Parser와 reference comparison
|
|
|
|
- [ ] identity가 `(kind, scope, source label)`을 모두 포함하는가?
|
|
- [ ] source format의 case/whitespace 규칙을 comparator key에만 정확히 적용하는가?
|
|
- [ ] missing, extra, duplicate, nonfinite row를 tolerance 전에 거부하는가?
|
|
- [ ] source type 지원과 그 source type의 Abaqus artifact consumption을 별도 계약으로
|
|
판단하는가?
|
|
- [ ] tolerance 변경 전에 authoritative HDF5와 kernel invariant를 확인했는가?
|
|
|
|
### 9.4 State, HDF5와 Harness
|
|
|
|
- [ ] complete candidate를 검증한 뒤 state를 한 번만 commit하는가?
|
|
- [ ] HDF5 temporary file을 close/reopen/self-check한 뒤 final을 교체하는가?
|
|
- [ ] Agent는 Executor가 선택한 Step 하나만 수행하고 Executor-owned 상태를 쓰지 않는가?
|
|
- [ ] process exit code와 structured diagnostic의 fatal error를 모두 검사하는가?
|
|
- [ ] 병합 전후 reference directory에 ignored generated artifact가 없는가?
|
|
|
|
## 10. 최종 결과와 남은 한계
|
|
|
|
최종 release readiness는 `ready-for-release`다
|
|
(`docs/releases/linear-static-mitc4-shell-release.md:250`). 근거는 다음과 같다.
|
|
|
|
- clean MSVC x64 Debug build와 compiler/linker warning `0`;
|
|
- full CTest `144/144`, Harness pytest `7/7`;
|
|
- Harness Step `0-13` 완료와 output `14/14` exit code `0`;
|
|
- S4 reference U `147/147` pass, UR `147/147` 비교와 warning `0`;
|
|
- exact 294 comparison rows, invalid row `0`;
|
|
- physics equilibrium, direction, symmetry, recovery와 energy sanity pass
|
|
(`docs/releases/linear-static-mitc4-shell-release.md:168-181`).
|
|
|
|
다만 이 결과가 다음을 의미하지는 않는다.
|
|
|
|
- Abaqus S4/S4R 내부 정식화 동등성;
|
|
- reduced integration 또는 hourglass control 지원;
|
|
- S4R Abaqus reference acceptance;
|
|
- curved/distorted/thin-thick mesh 전체의 convergence 보장;
|
|
- geometric-nonlinear global 24 DOF tangent 구현 완료.
|
|
|
|
이 한계는 release 문서에 승인된 boundary로 남아 있다
|
|
(`docs/releases/linear-static-mitc4-shell-release.md:201-209`). 이번 작업의 가장 중요한
|
|
결론은 “Abaqus처럼 작동하게 만들기”가 아니라, FESA의 독립 정식화와 외부 observable
|
|
comparison 사이의 경계를 먼저 고정하고 각 계층의 invariant로 결함을 좁혀야 한다는
|
|
점이다.
|