13 KiB
Architecture Decision Records
철학
FESA의 architecture decision은 solver correctness, verification traceability, implementation testability를 우선한다. Harness는 제품이 아니라 C++/MSVC 기반 FEM 구조해석 솔버 개발을 통제하는 운영 인프라이다.
ADR-001: FESA는 구조해석 솔버 프로젝트이고 Harness는 운영 인프라로 둔다
결정: 저장소의 주 목적은 유한요소법 기반 구조해석 솔버 개발이다. Harness는 승인된 Step 계획, 독립 세션 실행, PreToolUse guardrail, Stop MSVC build/test 검증을 제공하는 보조 계층으로 유지한다.
이유: 기존 문서가 Harness 중심이면 agent가 solver architecture, FEM verification, Abaqus/HDF5 계약보다 운영 스크립트에 과도하게 맞춰 행동한다.
트레이드오프: Harness 문서의 비중은 낮아지지만, docs/HARNESS_WORKFLOW.md의 실행 계약과 .codex/hooks.json의 검증 정책은 계속 필수 운영 규칙으로 유지한다.
ADR-002: C++17/MSVC/CMake/CTest를 기본 구현 환경으로 둔다
결정: 기본 solver 구현과 validation은 C++17 이상, Visual Studio 17 2022 또는
Visual Studio 18 2026 generator, x64 platform, Debug config, CMake, CTest로 수행한다.
.harness/config.json이 없으면 CMake가 설치된 두 승인 generator 중 하나를 자동
선택할 수 있으며, build/test evidence에는 실제 generator와 compiler version을 기록한다.
이유: FESA의 목표 환경은 Windows/MSVC 기반 C++이다. CMake/CTest는 solver source tree가 추가되거나 확장될 때 가장 일관된 build/test entry point다. VS17과 VS18을 승인하면 현재 지원되는 MSVC 개발 환경에서 같은 C++17/x64/Debug 계약을 검증할 수 있다.
트레이드오프: FESA solver source는 CMake/CTest를 기본으로 유지한다. Generator별
compiler 차이는 각 build/test report에 명시하고, 두 generator 중 하나에서만 관찰되는
실패를 다른 generator의 성공으로 숨기지 않는다. Harness 자체는 .harness/config.json에
solution과 test command를 명시한 직접 MSBuild 프로젝트도 검증할 수 있지만, 이는 FESA
제품이 solution-only workflow를 지원한다는 의미가 아니다.
ADR-003: Abaqus .inp full compatibility가 아니라 기능별 keyword subset을 지원한다
결정: FESA parser는 Abaqus keyword/data/comment line 규칙을 따르되, 기능별로 승인된 keyword subset만 지원한다. 미지원 keyword는 명확한 diagnostic을 남긴다.
이유: full Abaqus compatibility는 초기 solver 범위와 검증 비용을 과도하게 키운다. 기능별 subset은 요구조건, I/O contract, reference validation을 추적 가능하게 만든다.
트레이드오프: 사용자는 기존 Abaqus input file을 그대로 사용할 수 없을 수 있다. 대신 지원 범위와 실패 원인이 명확해진다.
ADR-004: Domain, AnalysisModel, DofManager, AnalysisState를 분리한다
결정: Domain은 입력 모델 정의를 소유하고, AnalysisModel은 현재 step의 실행 view를 제공하며, DofManager는 equation numbering과 constrained/free mapping을 전담하고, AnalysisState는 해석 중 변하는 물리량을 소유한다.
이유: 모델 정의, step activation, equation system, transient/nonlinear state가 섞이면 parser, assembler, solver, result writer가 강하게 결합된다. 분리된 상태 모델은 선형 정적 해석에서 시작해 비선형, 동적, thermal coupling으로 확장하기 쉽다.
트레이드오프: 초기 class 수가 늘어난다. Phase 1에서는 interface를 얇게 유지하고 displacement 중심 state부터 구현한다.
ADR-005: 공식 결과 파일은 HDF5로 하고 reference 결과는 Abaqus CSV로 둔다
결정: FESA solver의 authoritative result output은 results.h5 HDF5이다. Abaqus reference results는 기능별 reference model contract가 지정한 reference/<model-id>/ 아래 CSV 파일로 저장하며, verification은 FESA HDF5 rows와 Abaqus reference CSV rows를 documented IDs, components, units, coordinate system, step/frame identity, tolerance 기준으로 비교한다. 신규 reference는 canonical 파일명을 사용하고, 승인된 기존 bundle의 legacy alias는 해당 기능 계약에 정확한 경로를 기록한 경우에만 허용한다.
이유: 구조해석 결과는 step/frame, field/history, node/element/integration point location, units, coordinate system, schema version을 함께 가져야 한다. HDF5는 이 계층 구조와 metadata를 안정적으로 표현한다.
트레이드오프: reference comparison은 FESA HDF5 dataset identity와 Abaqus CSV row identity를 모두 관리해야 한다. FESA HDF5에서 추출한 deterministic CSV view는 디버깅/검토용 보조 artifact로 허용하지만, 공식 solver output이나 reference artifact로 취급하지 않는다.
ADR-006: 해석 알고리즘과 수치 backend는 Strategy와 Adapter 경계 뒤에 둔다
결정: Analysis, LinearSolver, TimeIntegrator, ConvergenceCriteria는 Strategy로 구성하고, MKL, TBB, HDF5 API는 adapter 계층 뒤에 둔다. Vector는 contiguous double, Matrix는 row-major contiguous double을 소유하고 dense 연산은 MKL CBLAS를 사용한다. SparseMatrix는 이들과 상속 관계가 없는 0-based CSR 전용 타입이며, PARDISO factorization과 substitution은 LinearSolver 경계 뒤에서 분리한다.
이유: 선형 정적, 비선형 정적, 동적, frequency, heat transfer 해석은 공통 흐름을 공유하지만 알고리즘과 backend가 다르다. 외부 API를 core에 노출하면 테스트 double, backend 교체, dependency review가 어려워진다.
트레이드오프: 단일 기능만 구현할 때는 adapter가 다소 장황해 보일 수 있다. Row-major dense storage와 CSR sparse storage를 따로 유지해야 하지만 backend 의존성과 dense/sparse 의미가 core 모델에 섞이지 않는다.
ADR-007: Analysis 실행 흐름은 Template Method로 고정한다
결정: Analysis::run()은 공통 lifecycle을 고정한다. 선형 정적 V0의 순서는 parse input -> initialize Domain -> build AnalysisModel -> build DOF map/sparse pattern -> assemble stiffness -> partition constraints -> factorize Kff -> assemble load -> form effective RHS -> substitute -> reconstruct displacement -> recover results -> write HDF5다. 강성행렬 factorization은 하중벡터 조립보다 먼저 수행하고, factorization과 substitution을 하나의 불투명한 solve 호출로 합치지 않는다.
이유: 해석 procedure가 늘어나도 공통 실행 순서가 유지되어야 검증, logging, result writing, failure classification이 일관된다. Factorization과 substitution을 분리하면 동일 강성행렬에 여러 RHS를 적용할 수 있고 각 실패 단계를 구조화된 diagnostic으로 분류할 수 있다.
트레이드오프: 특수 해석 절차가 공통 흐름에 맞지 않는 경우 hook point가 필요하다. 초기에는 선형 정적 해석을 기준으로 최소 hook만 둔다.
ADR-008: Sparse assembly는 deterministic COO-to-CSR 경로로 시작한다
결정: 초기 assembly는 element-local contribution을 COO triplet으로 수집한 뒤 CSR로 finalize한다. MKL PARDISO backend는 CSR input contract를 받는다.
이유: CSR은 MKL PARDISO와 잘 맞고, COO-to-CSR 경로는 구현과 테스트가 명확하다. deterministic reduction은 reference comparison의 재현성을 지킨다.
트레이드오프: 대규모 모델에서는 메모리와 변환 비용이 생길 수 있다. 성능 문제가 실제로 확인되면 typed batch assembly 또는 kernel 분리를 추가한다.
ADR-009: TBB 병렬화는 element-local computation부터 적용한다
결정: 첫 oneTBB 적용 지점은 element-local matrix/residual 계산이다. 전역 sparse write는 thread-local buffer 또는 deterministic reduction으로 제한한다.
이유: element-local 계산은 독립성이 높고 병렬화 효과가 명확하다. 전역 sparse matrix에 직접 병렬 write하면 재현성, race, ordering 문제가 생긴다.
트레이드오프: 초기 병렬화 범위가 제한된다. MKL 내부 thread와 TBB task arena의 oversubscription 정책을 별도로 문서화해야 한다.
ADR-010: Abaqus reference artifact는 사람이 생성하거나 명시 승인된 절차로만 갱신한다
결정: Agent는 Abaqus, Nastran 또는 reference solver를 직접 실행하지 않는다. reference artifact 생성, 수정, 복원은 명시 승인된 phase에서만 수행한다. 신규 bundle의 provenance는 metadata.json에 기록한다. 승인된 cantilever-beam-b33 legacy baseline은 source commit과 generator, units, coordinate system, schema, tolerance를 설계 및 Reference Model Contract가 대신 기록하므로 이 baseline에 한해서 metadata.json과 README.md가 N/A다.
이유: reference 결과는 solver correctness의 기준이다. 생성 절차가 불명확하면 구현 결함과 reference artifact 오류를 구분할 수 없다.
트레이드오프: reference 준비가 느려질 수 있다. Legacy 예외는 일반 artifact 규칙을 복잡하게 하지만 exact path와 source commit을 고정하고 read-only로 취급해 감사 가능성을 유지한다.
ADR-011: 구형 단일 검증 진입점 계약을 폐기한다
상태: ADR-012로 대체됨.
결정: 삭제된 legacy validation entry point, legacy Python test discovery, 환경 변수 기반 command override를 Harness의 기본 검증 계약으로 사용하지 않는다.
ADR-012: Harness는 계획, 실행, Hook 검증의 세 계층으로 운영한다
결정: 계획은 .agents/skills/harness, Step 실행과 Git 상태 관리는 scripts/execute.py, 도구 호출 전 정책과 종료 전 검증은 .codex/hooks.json에 연결된 scripts/hooks/가 담당한다. C++ production 변경은 관련 테스트 파일이 있어야 하며, 실제 RED 실패와 GREEN 성공은 같은 Step 안에서 확인한다. Stop은 .harness/config.json 또는 자동 감지 결과로 전체 MSVC build/test를 검증한다. Harness Python 변경은 uv run --with pytest python -m pytest -v -rs로 검증한다.
이유: 테스트 파일 존재 검사, TDD 실행 증거, 전체 build/test는 서로 다른 책임이다. 이를 분리하면 Hook이 보장하는 범위를 과장하지 않으면서 Step 종료 시 green 상태를 강제할 수 있다.
트레이드오프: PreToolUse만으로 구현 전 RED 실행을 증명할 수 없으므로 Implementation report에 RED/GREEN 명령과 결과를 기록해야 한다. C/C++가 없는 저장소는 Stop이 통과하므로 Harness Python 검증은 별도 acceptance command로 유지한다.
ADR-013: V0 선형 정적 해석은 단일 step B33 Euler beam으로 제한한다
결정: 첫 end-to-end 제품 기능은 입력 파일당 하나의 *STEP, *STATIC을 해석하는 2절점 3D Euler–Bernoulli beam이다. Abaqus TYPE=B33만 이 요소로 매핑하고 B31은 unsupported-element-formulation으로 거부한다. *PART/*ASSEMBLY/*INSTANCE는 여러 identity instance와 stable source identity를 지원하지만 instance transform, nested assembly, 다중 step은 지원하지 않는다. Output-request keyword는 승인 allowlist에서 warning 후 no-op 처리하고 FESA 기본 HDF5 결과는 항상 생성한다.
이유: B31은 transverse shear deformation을 포함하므로 Euler–Bernoulli 정식화와 의미가 다르다. 단일 step과 identity instance로 범위를 제한하면 parser wrapper, source identity, assembly, 해법 및 결과 복구를 end-to-end로 검증하면서 의미를 왜곡하지 않는다.
트레이드오프: 기존 Abaqus 모델 중 B31, instance transform, 다중 step 또는 *DLOAD를 사용하는 입력은 거부된다. 이후 기능은 별도 요구조건과 formulation/reference gate를 거쳐 추가한다.
ADR-014: B33 cantilever baseline은 component-scale 혼합 tolerance로 비교한다
결정: reference/cantilever beam/의 B33 input, displacement CSV, reaction CSV, elemental-force CSV를 V0 reference baseline으로 사용한다. Displacement와 reaction은 node identity로, section resultant는 CSV node station과 정규화된 FESA element endpoint로 비교한다. 같은 model, step/frame, quantity, component에 대해 reference_scale = max(abs(reference rows))를 계산하고 모든 row에 absolute_floor + 1e-6 * reference_scale을 적용한다. SI displacement/rotation absolute floor는 1e-9, force/moment floor는 1e-3이다. Beam stress는 출력하되 Abaqus stress reference comparison은 N/A다.
이유: 자유단 moment처럼 이론적으로 0인 값에는 행별 상대오차가 정의되지 않으며 Abaqus 결과에 작은 수치 잔차가 남을 수 있다. Component scale을 사용하면 전체 물리량 크기에 비해 작은 잔차를 허용하면서 각 row를 결정적으로 판정할 수 있다.
트레이드오프: 큰 component scale은 zero row의 허용폭을 넓힌다. 이를 완화하기 위해 quantity와 component를 섞지 않고 Abaqus 값만 scale에 사용하며, 모든 row의 absolute/normalized error와 worst row를 보고하고 누락·추가·nonfinite row를 tolerance 전에 실패시킨다.