Files
FESADev/docs/ADR.md
T
2026-08-12 02:22:01 +09:00

18 KiB
Raw Blame History

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 수가 늘어난다. V0에서는 interface를 얇게 유지하고 실제 선형 정적 frame에 필요한 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, generator/version, units, coordinate system, step/frame identity, schema, tolerance와 limitations는 승인된 기능별 Reference Model Contract에 기록한다. metadata.json은 선택 reference artifact이며, 부재만으로 bundle을 불완전하다고 판정하지 않는다. 파일이 존재하면 read-only 보조 자료로 inventory하고 계약 및 실제 artifact와 일치하는지 확인하며, 충돌은 숨기지 않고 upstream 계약 문제로 보고한다. 승인된 cantilever-beam-b33 legacy baseline의 space-containing filename과 README.md N/A 예외는 유지한다.

이유: reference 결과는 solver correctness의 기준이다. 생성 절차가 불명확하면 구현 결함과 reference artifact 오류를 구분할 수 없다.

트레이드오프: Reference Model Contract가 필수 provenance의 단일 source of truth가 되어 별도 JSON 파일 없이도 bundle을 사용할 수 있다. 선택 metadata.json이 계약과 중복될 수 있으므로 존재 시 일관성 검사가 필요하다. Legacy filename과 README.md 예외는 일반 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 EulerBernoulli 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을 포함하므로 EulerBernoulli 정식화와 의미가 다르다. 단일 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 전에 실패시킨다.

ADR-015: 외부 CMake target을 FESA 경계로 정규화하고 Windows runtime closure를 명시한다

결정: MKL, TBB, HDF5 CONFIG package가 제공하는 target은 dependency module에서 Fesa::MKL, Fesa::TBB, Fesa::HDF5로 정규화한다. Product target은 이 경계만 PRIVATE link하고 public header에 vendor/Win32 type을 노출하지 않는다. Approved Windows 환경에서는 packaged shared HDF5 C target을 우선하며, CLI와 test executable 옆에 TBB, MKL, OpenMP, Intel math runtime, HDF5를 포함한 실제 runtime DLL closure를 POST_BUILD로 stage한다.

이유: Package version이나 shared/static variant에 따라 imported target 이름과 숨은 runtime dependency가 달라질 수 있다. Configure와 link 성공만 확인하면 GoogleTest discovery 또는 배포된 CLI가 0xc0000135로 실패할 수 있다. FESA-owned target과 명시적 runtime closure는 module code와 실행 환경을 package 세부사항에서 분리한다.

트레이드오프: Windows staging CMake가 길어지고 새 dynamic dependency마다 runtime audit가 필요하다. 대신 개인 설치 경로를 source에 하드코딩하거나 developer PATH에 의존하지 않고 build tree executable을 재현 가능하게 실행할 수 있다.

ADR-016: 결정론과 failure atomicity를 correctness contract로 취급한다

결정: Parallel element 계산은 index별 독립 buffer만 쓰고, COO tuple ordering과 floating-point sum은 join 이후 고정된 serial 순서로 수행한다. Result recovery, solver output, HDF5 writer는 candidate를 완성·검증한 뒤 caller-owned state 또는 final file에 commit한다. HDF5는 same-directory temporary file을 checked close/reopen한 후에만 atomic finalization한다.

이유: Reference comparison과 물리 검토는 row identity와 작은 수치 차이에 민감하다. Thread scheduling 또는 중간 실패가 sparse bytes, result order, 이전 output을 바꾸면 같은 입력에 대한 evidence를 신뢰할 수 없다.

트레이드오프: Serial reduction과 candidate copy가 throughput과 memory를 더 사용할 수 있다. 성능 최적화는 동일한 ordering, failure preservation, reference 결과를 증명할 때만 대체할 수 있으며, nondeterministic global sparse write는 허용하지 않는다.

ADR-017: Essential constraint는 stable elimination으로 적용하고 reaction은 full residual로 정의한다

결정: V0는 DofManager의 stable free/constrained order로 full stiffness를 Kff/Kfc/Kcf/Kcc로 분할하고 Kff * df = Ff - Kfc * dc를 푼 뒤 full displacement를 복구한다. 모든 DOF가 constrained인 모델의 0 x 0 Kff는 유효한 trivial factorization과 empty substitution이다. Recovery의 full residual r = K*d - F를 reaction vector로 보존하며 constrained entry는 physical reaction, free entry는 equilibrium evidence다.

이유: Equation numbering과 prescribed displacement를 한 owner가 관리하면 nonzero constraint, no/all/mixed constraint가 같은 수식과 stable order를 사용한다. Full residual은 element end action을 별도로 합산하는 경로보다 조립·하중·해법 전체의 equilibrium을 직접 검증한다.

트레이드오프: Full K와 네 partition을 유지하는 메모리 비용이 있고 reaction vector의 free entry가 정확한 0이라고 가정할 수 없다. V0에는 penalty, MPC, Lagrange multiplier를 혼합하지 않으며, 추가 constraint policy는 별도 formulation과 equation/output 계약을 요구한다.

ADR-018: Kernel 존재와 제품 노출을 분리하고 계층형 검증을 요구한다

결정: Local element/load/recovery kernel이 존재해도 syntax, semantic Domain mapping, analysis orchestration, mandatory HDF5와 diagnostic 계약이 연결되지 않으면 CLI 지원으로 간주하지 않는다. 검증은 unit, integration, reference comparison, physics sanity를 서로 대체할 수 없는 계층으로 유지한다. Reference가 가능하지 않은 quantity는 명시적 N/A와 analytical/schema/physics 대체 evidence를 요구한다.

이유: Unit test는 수식과 validation을, integration test는 lifetime과 호출 순서를, reference comparison은 외부 solver와의 수치 일치를, physics sanity는 공통-mode 오류와 model adequacy를 검출한다. 한 계층의 성공만으로 parser exposure 또는 물리 correctness를 추론하면 지원 범위와 검증 강도가 과장된다.

트레이드오프: 작은 기능도 여러 계약과 evidence를 함께 준비해야 하므로 개발 속도가 느려진다. 대신 *DLOAD처럼 kernel은 있지만 입력에 노출되지 않은 기능, stress처럼 mandatory output이지만 Abaqus reference가 N/A인 기능을 정확하게 표현할 수 있다.