# 솔버 공통 구조와 계약 FESA는 C++17 이상·Windows MSVC x64 Debug·직접 MSBuild와 CMake/CTest를 기준으로 하는 개인 연구용 솔버다. 승인된 Abaqus 입력 subset을 독립 모델로 해석하고 HDF5를 쓴다. Source spelling의 수용은 Abaqus 내부 요소·적분·알고리즘과의 동등성을 뜻하지 않는다. 외부 배포 형태·지원 환경·dependency 재배포 조건은 아직 정해지지 않았다. ## 책임과 수명 [FesaApplication](../../include/fesa/app/fesa_application.h)과 [구현](../../src/fesa/app/fesa_application.cpp)이 입력·모델·해석·출력을 조합한다. [AbaqusDomainMapper](../../include/fesa/io/abaqus/domain_mapper.h)는 source 문법을 [Domain](../../include/fesa/model/domain.h)의 의미 모델로 연결한다. Domain은 node/element/material/property/step 정의와 source identity를 소유한다. [AnalysisModel::Create](../../include/fesa/analysis/analysis_model.h)는 복사하지 않는 active-step view이므로 Domain이 더 오래 살아야 한다. [DofManager](../../include/fesa/fem/dof_manager.h)가 DOF profile·equation·full/free/constrained mapping·sparse pattern을 소유한다. Node·Element·backend가 equation을 따로 발급하지 않는다. [ElementFactory](../../include/fesa/elements/element_factory.h)는 정의를 local 계산 객체로 연결한다. Element/material/load는 local 응답, [SparseAssembler](../../src/fesa/assembly/sparse_assembler.cpp)는 전역 누적, procedure executor는 trial·해법·수렴·accepted state, [ResultRecovery](../../include/fesa/results/result_recovery.h)는 물리량, writer는 직렬화·readback·finalize를 담당한다. 병렬 local 계산은 자기 index buffer만 쓴다. Global CSR·진단·결과는 stable source/element 순서로 누적하며 hash 순서나 worker 완료 순서에 의존하지 않는다. Public core는 MKL/TBB/HDF5/Win32 타입을 노출하지 않는다. [PARDISO](../../src/fesa/solvers/linear/mkl_pardiso_solver.cpp)·[FEAST](../../src/fesa/solvers/eigen/mkl_feast_eigen_solver.cpp)·HDF5 handle은 private adapter가 관리하며 expected failure는 `Status`/`Result`로 변환한다. 선형 정적은 full K를 stable partition하고 Kff를 load assembly보다 먼저 factorize한다. 유효한 all-constrained의 빈 free system은 singular가 아니다. 반력과 free 평형은 같은 full residual에서 복구한다. 비선형·동해석은 같은 trial의 geometry/force/tangent를 쓰고 모든 gate를 통과한 candidate만 commit한다. 실패 trial의 cache·chart·history가 accepted state나 final HDF5를 바꾸지 않는다. 불변 reference cache와 trial-dependent 응답을 구분하며 다른 trial의 factorization을 추정 재사용하지 않는다. ## CLI와 입력 `fesa.exe `는 비어 있지 않은 입력 경로 하나만 받는다. `.inp`는 대소문자를 구별하지 않으며 `--output`은 없다. 상대 경로는 호출 cwd 기준 lexical normalization하고 symlink/junction/short name을 resolve해 결과 위치를 바꾸지 않는다. 결과는 입력 lexical sibling `_results.h5`, 로그는 `_log.txt`다. 결과 경로에 `.fesa.lock`을 붙인 lease는 정상 handle 종료 때 제거된다. 점유 또는 lease 생성 실패는 output failure다. Candidate 기록·self-check·close가 끝난 뒤 final을 교체하며 실패하면 이전 final을 보존한다. 로그는 실패 진단으로 갱신될 수 있고, 기존 HDF5 존재만으로 새 실행 성공을 판정하지 않는다. | Exit | 의미 | | ---: | --- | | 0 | 선택 해석과 최종 저장 성공 | | 2 | Usage·입력 확장자 오류 | | 3 | 읽기·문법·미지원 입력 | | 4 | 모델 의미·구성·유효성 | | 5 | 해법 실패 | | 6 | Lease·로그·HDF5 저장 실패 | stderr 진단은 결정적 순서의 탭 구분 `severity=... code=... file=... line=... keyword=... entity_identity=... message=...` field이며 빈 source field도 보존한다. Backend 번호를 CLI exit로 그대로 반환하지 않는다. 경고가 있는 성공도 가능하다. 입력은 physical `.inp` 하나다. 종결자 제외256 ASCII bytes 이내, printable ASCII/tab, LF/CRLF·빈 줄·`**` comment·keyword parameter continuation을 처리한다. Keyword/parameter/enum/name lookup은 ASCII case-insensitive지만 원문·line은 보존한다. 같은 값의 중복 parameter도 거부하며 comma의 빈 field를 없애지 않는다. Identity instance의 part/assembly와 하나의 step이 기본이다. Instance transform·INCLUDE·contact·MPC·비탄성·혼합 beam/shell 또는 shell/solid는 공개 subset이 아니다. Label·instance/source identity와 내부 index는 다르다. Keyword와 DOF의 정확한 허용 범위는 기능별 요약을 따른다. ## 결과와 reference 비교 HDF5 reader는 feature identity·version·procedure discriminator를 함께 읽는다. 기존 linear static schema0에는 `analysis_procedure`가 없으며 이를 새 필수값으로 만들지 않는다. Nonlinear static schema1, eigen의 `eigen_schema_version=1`, 현재 shell HHT schema2와 역사적 full-coordinate schema1, solid·thermal 결과는 별도 계약이다. `Step-1`은 storage slot이다. Frequency source step은 step attribute, HHT·thermal은 metadata에서 읽고 기존 static에 source step 이름이 별도 저장됐다고 가정하지 않는다. Float64/fixed-width integer는 little-endian, 문자열은 UTF-8이며 compound는 field name/type/extent로 읽어 C++ padding에 의존하지 않는다. Input identity는 실제 읽은 bytes에 결속된다. Source instance/node/element/point/endpoint·step/frame·component로 일대일 대응한다. Missing/extra/duplicate row, component/schema 누락, identity 불일치, NaN/Inf는 tolerance 전에 실패한다. 위치·단위·좌표·부호가 다른 값을 평균하거나 nearest-position matching하지 않는다. Reference INP/CSV/TXT 원본을 결과에 맞춰 수정·보정·zero-clamp하지 않는다. 공통 comparison family는 같은 case·step/frame·logical quantity·단위 차원·좌표계·판정 영향으로 묶는다. Reference만으로 `S=max|r_i|`, `B=0.01S`를 계산한다. `|r_i|<=B`이면 `|f_i-r_i|<=B`, 나머지 행은 상대오차 `<=0.05`다. 모든 행과 `sqrt(mean((f-r)^2))/S<=0.01`을 동시에 만족해야 한다. 독립 absolute tolerance·`max(1,S)` floor는 없고 `S=0`이면 FESA도 모두 exact zero여야 한다. 사전검사는 항상 blocking이며 warning-only family의 수치 실패는 경고다. 행·family·경고 순서와 원본 값·scale·metric·policy를 재현 가능하게 기록한다. Named exception **`mitc4-eigen-first-implementation`**(2026-09-10 승인)은 eigen2의 source modal frame별 blocking singleton `Value`에만 행 상대5%·RMS5%를 적용한다. Near-zero·zero-scale·identity/finite 검사는 유지하고 `policy_id`와 RMS0.05를 기록한다. 다른 기능·정적 U/UR/reaction은 공통 RMS1%이며 Hex8 eigen에 이 예외를 확대하지 않는다. 내부 eigen residual·정규화·직교성 `1e-10`도 유지한다. 예외는 S3 평형 충돌이나 외부 알고리즘 동등성의 해결이 아니다. ## 개발 교훈과 검증 경계 수식·적분·reduction 순서·cache 수명의 변경은 수치 변경이다. Analytical/invariant·독립 numerical review·실제 물리 평형·시간수렴·reference의 검증 범위는 다르다. 프로세스 exit0·link 성공·CTest·Python suite·외부 비교를 서로 대신하지 않는다. 미실행/중단/옛 revision evidence를 현재 PASS로 승계하지 않는다. 문서만 바뀌면 source·링크·원문 보존의 정적 검토를 하고 build/test를 실행하지 않는다. Configure/build/test의 compiler·구성·build tree를 함께 맞추고 discovery0건·다른 compiler를 실패로 다룬다. MSVC runtime DLL staging을 확인하며 개발자 PATH의 우연한 성공을 배포 검증으로 삼지 않는다. Dense allocation 크기 곱 overflow는 할당 전에 검사한다. Header는 self-contained `.h`/guard, 파일·namespace snake_case, 타입·함수 PascalCase, 변수 snake_case, class member trailing `_`, Google 기반2-space format을 따른다. Vendor target에 FESA warning 정책을 강제하지 않는다. 현재 공백은 shell HHT의 전체 물리·시간정확도, thermal 새 필수 reference, 기존 drilling 방식의 S3 partial-BC raw 정적 gate 한계, 성능 목표 미달이다. S3의 기본 직접 방식은 별도 인공 모멘트 차감으로 기존 gate를 통과하며 기존 방식의 raw 거부를 유지한다. 각 기능에 구체 한계를 보존한다. 기존1×1 PARDISO 반복의 heap failure는 SpMV 없는 baseline에서도 재현됐고 vendor 원인은 미확정이다. 기본 정책의 해당 크기에만 local1-thread/TLS 복원을 적용했으며 보조100회 성공을 라이브러리 전체 해결로 확대하지 않는다. ## 빌드와 직접 검사 직접 관리하는 [FESA.sln](../../FESA.sln)과 [msvc 지침](../../msvc/AGENTS.md)이 주 개발 빌드다. VS x64 개발 환경에서 저장소 root를 작업 디렉터리로 사용한다. v145 toolset과 로컬 GoogleTest source checkout, MKL ILP64/dynamic/Intel threading·TBB·HDF5·OpenMP가 필요하다. `FESA_GTEST_SOURCE_DIR`, `FESA_MKL_ROOT`, `FESA_TBB_ROOT`, `FESA_HDF5_ROOT`, `FESA_OMP_ROOT`에 설치 root를 지정하거나 ignored `msvc/Fesa.Local.props`로 주입한다. 개인 설치 경로를 추적 파일에 고정하거나 dependency를 자동 다운로드하지 않는다. ```bat MSBuild FESA.sln /m:2 /p:Configuration=Debug /p:Platform=x64 .build\native\Debug\tests\fesa_unit_tests.exe .build\native\Debug\tests\fesa_integration_tests.exe .build\native\Debug\tests\fesa_reference_tests.exe ``` Native CLI는 `.build/native/Debug/src/fesa/fesa.exe`이며 테스트 executable은 `.build/native/Debug/tests/`에 있다. Release는 같은 구조의 `Release` 디렉터리다. 직접 MSBuild에 CMake·Ninja·Doxygen을 선행 조건으로 추가하지 않는다. 보조 경로는 CMake3.25 이상·Ninja·동일 MSVC 환경을 사용한다. `MKL_DIR`·`TBB_DIR`· `HDF5_DIR`은 각각 config package 디렉터리다. 한국어 MSVC의 include 추적을 위해 configure/build 모두 code page 65001을 사용한다. ```bat chcp 65001 cmake --preset windows-msvc-ninja-debug cmake --build --preset windows-msvc-ninja-debug --parallel 2 ctest --preset windows-msvc-ninja-debug --show-only=json-v1 ctest --preset windows-msvc-ninja-debug --output-on-failure -j 1 python -m pytest tests -q ``` [Presets](../../CMakePresets.json)의 Debug tree는 `.build/cmake-debug`, Release는 `.build/cmake-release`다. CMake CLI도 각 tree의 `src/fesa/fesa.exe`에 있다. Debug/Release의 binary·DLL·검사 목록·실행 근거를 섞지 않는다. AVX2는 전용 Hex8 source에만 적용하며 CRT·정밀도·source·link·runtime DLL과 검사 identity를 두 경로에서 대조한다. `fesa_docs`는 Doxygen을 찾았을 때만 생기는 선택적 target이다. 위 명령은 일반 전체 실행 예시다. 실행 범위를 한정할 때는 선택/제외 identity와 이유를 기록한다. 2026-10-18 병합은 사용자 지시로 정적·공통 검사와 정식 정적5모델만 검증했다. 그 결과와 비정적 미검증 범위는 [병합 현황](../cpp-object-oriented-modular-refactoring/README.md#병합-결과와-검증-범위)에 있으며 이후 기능 검증의 기본 범위를 제한하지 않는다. Native/CTest는 짧고 고유한 TMP/TEMP와 raw 출력 경로를 분리해 직렬 실행한다. 긴 lease 경로는 output failure를 일으킬 수 있다. 상속된 필터·sharding을 제거하고 실제 identity·수·exit·XML/log를 discovery와 대조한다. Python 합성 비교와 실제 solver 실행을 구분한다. Skip·0건·자료 누락은 PASS가 아니다. 모델은 hash가 확인된 새 작업 복사본에서만 실행한다. `reference/temp`는 검증에 사용하지 않는다. ## 공통 procedure와 결과 경계 [PrepareAnalysisInput/CreateAnalysisProcedure](../../include/fesa/analysis/prepared_analysis.h)는 한 번 mapping한 Domain에서 한 family를 선택한다. `AnalysisProcedure::Execute`는 [typed outcome](../../include/fesa/analysis/prepared_analysis_outcome.h)을 반환하며 파일을 열지 않는다. Domain과 주입된 solver·parallel·log adapter는 실행 동안 살아 있고 이동하지 않아야 한다. App이 로그 close 성공을 확인한 뒤 `FinalizePreparedAnalysisResult`가 [WritePreparedAnalysisResult](../../include/fesa/results/prepared_results_writer.h)로 연결한다. 필요한 writer 부재는 output failure이며 다른 결과 family로 위장하지 않는다.