# HHT I/O 계약 - 상태: `io-contract-defined; implementation-and-readback-pending`, 2026-09-22. - Owner: `io-definition-agent`. 메인 인계로 새 procedure의 아래 공개 subset/schema를 확정한다. 물리·수치 기준의 원본은 [formulation](formulation.md), [inertia](formulation-inertia.md), [numerical-controls](numerical-controls.md)다. - 기준: [AGENTS](../../AGENTS.md), [프로젝트 PRD](../PRD.md), [공통 ADR](../ADR.md), [ARCHITECTURE](../ARCHITECTURE.md), [Feature PRD](PRD.md), [Feature ADR](ADR.md). 기존 문서의 `I/O pending`은 본 계약 이전 상태다. ## 1. 입력과 호환성 기존 [비선형 I/O](../nonlinear-static-newton-raphson/io.md)의 lexical parsing, source target resolution, part/identity instance, node/set, S4/S4R → FESA-MITC4, centered homogeneous shell, isotropic elastic material, geometry와 chart 검증을 상속한다. 아래 절차 차이만 dynamic에 적용한다. 기존 static/frequency 입력·결과의 의미나 schema는 바꾸지 않는다. Source S4/S4R 표기는 보존하며 Abaqus 내부 요소·관성·시간 알고리즘 동등성을 주장하지 않는다. 공개 Abaqus 관례의 근거는 이미 조사한 [research](research.md#research)의 관측 가능한 입력 의미에 한정한다. | 구문 | 이번 지원 의미 / 검증 | | --- | --- | | `*STEP, NAME=, NLGEOM=YES, INC=` | 한 step만 허용. NAME은 기존 규칙, 생략 시 `Step-1`; NLGEOM=YES 필수. INC 생략값 100, 명시값 양의 uint32. INC는 시간 increment 상한이며 Newton 반복 상한이 아님 | | `*DYNAMIC, DIRECT` | step의 첫 procedure. DIRECT는 값 없는 flag, 다른 parameter는 거부. ALPHA/BETA/GAMMA, APPLICATION, INITIAL, EXPLICIT, 자동 증분은 미지원 | | data `h,T` 또는 `h,T,` | 정확히 한 row, 두 finite positive 값. h≤T. `N=round(T/h)`가 uint32 범위의 양의 정수이고 `double(N)*h == T`, N≤INC일 때만 허용. 중간 누적 덧셈·끝 증분 단축·time tolerance 없음. 그 밖의 nonempty field/row 거부 | | `*DENSITY` 아래 `rho` 또는 `rho,` | 활성 material마다 정확히 한 finite positive density; temperature/dependency/추가 field 미지원. 질량 단위는 `force*time^2/length`, density는 `force*time^2/length^4`. Frequency의 기존 허용 유지, static에 새 허용하지 않음 | | `*BOUNDARY` | 모델 앞 또는 procedure 뒤/하중 앞. 기존 target, DOF 1–6 범위; magnitude 생략 또는 exact 0. 전체 시간에 q/v/a/A constrained 성분 exact 0. 초기 q/v와 충돌하는 값, AMPLITUDE/OP/TYPE/변경 경계 거부 | | partial zero BC | component selector 의미를 그대로 보존. 전 DOF clamp/free 필수 지원, 부분구속은 N4 analytic null과 free 공간 교집합 사용. Rank-changing branch/regularity 실패는 오류이며 부분구속을 full clamp로 바꾸지 않음 | | `*CLOAD` | step 내 global dead force DOF 1–3만 허용. finite magnitude; 동일 resolved node/DOF의 여러 row는 source order로 합산. DOF 4–6은 값이 0이어도 미지원. AMPLITUDE/FOLLOWER/OP 및 모델-level 하중 미지원 | | `*PREPRINT, ECHO=NO, MODEL=NO, HISTORY=NO, CONTACT=NO` | 정확한 flags, data 없음: `ignored-output-request` warning. 출력 제어 효과 없음 | | `*RESTART, WRITE, FREQUENCY=0` | data 없는 이 조합만 warning no-op. Restart 생성·읽기 지원 아님 | | `*OUTPUT, FIELD, VARIABLE=PRESELECT, FREQUENCY=1` | data 없음. HISTORY도 같은 조합 허용, FIELD/HISTORY 둘 중 하나. FREQUENCY 생략도 1 의미로 수용. 요청마다 warning no-op; FESA 필수 전 frame 출력을 바꾸지 않음 | `*NODE OUTPUT`, `*ELEMENT OUTPUT` 등 child selectors와 다른 OUTPUT parameter/frequency는 미지원이다. 원본 [INP](../../reference/shellNL-dyn-meshfine/shellNL-dyn-meshfine.inp)는 위 root 요청만 사용한다. 초기조건 keyword, amplitude, damping, distributed/follower load, contact, 비탄성, 다른 dynamic 요소, 다중 step/restart는 fail closed다. 기존 shell section의 source integration-point field `5`는 기존 allowlisted warning 의미를 유지하며 FESA 관성 적분 선택이 아니다. 공개 계수는 기본 `alpha=-0.05,beta=0.275625,gamma=0.55`만 제공한다. Internal 검증용 alpha=0은 formulation의 결합계수로 실행할 수 있으나 INP 지원 확대가 아니다. 질량은 `consistent-kinetic`; 사용자 선택을 lumped fallback으로 바꾸지 않는다. 초기 frame은 `0+`, q0=v0=0, F0=F*, A0=a0이며 physical a0와 null 일관성은 N3에 따라 구한다. 하중은 이후 모든 endpoint에도 전값이다. ## 2. Identity, 시간, state 경계 Canonical storage slot은 `/steps/Step-1`이다. 실제 source name을 별도 `source_step_name`에 저장한다. Reference의 실제 name은 `Step-1`이다. Instance는 기존 ASCII case-insensitive canonical identity와 raw label text를 보존하고, logical label은 positive int64다. Internal node/element ID와 equation ID를 source label 대신 사용하지 않는다. N increments의 완성 결과는 frame 0..N, increment 0..N을 각각 한 번 포함한다. Frame n의 `time=double(n)*h`, frame0=+0, `is_initial=(n==0)`이며 n≥1에서 시간이 strictly increasing해야 한다. 종료시각은 T와 exact 일치한다. Frame0 iteration_count=0; 이후 0..100 corrections, 초기 추정 수렴/empty free solve는 0 허용. INC에 frame0는 포함하지 않는다. Original h=.1,T=10,INC=1000은 N=100/101 frames다. Domain이 dynamic semantic 정의를 소유한다. AnalysisModel은 non-owning view, DofManager는 full/free/constraint mapping을 소유한다. State vector의 node-major 6성분은 `[u1,u2,u3,phi1,phi2,phi3]`이며 node inventory는 Domain 선언 순서다. Rotation은 fixed-initial global total rotation-vector chart다. Trial은 committed state/history와 alias되지 않는다. Candidate의 time, q/v/a/A, orientation, force, recovery와 evidence가 모두 검증된 뒤 한 번에 commit한다. Failure는 마지막 committed frame과 history를 보존하며 failed/trial frame을 노출하지 않는다. ### 구현 인계용 C++ 경계 이는 backend-neutral 경계이며 vendor type을 포함하지 않는다. Default-constructed record는 구성 중 candidate일 뿐 유효한 완료 state가 아니다. ```cpp struct DynamicStepDefinition { std::string source_step_name_raw, source_step_name_canonical; SourceLocation step_location, dynamic_location, data_location; double initial_increment = 0.0, time_period = 0.0; std::uint32_t increment_count = 0, maximum_increments = 100; double alpha = -0.05, beta = 0.275625, gamma = 0.55; std::vector boundaries; std::vector loads; }; struct DynamicAcceptedFrameState { std::uint32_t frame_index = 0, increment_index = 0, iteration_count = 0; double time = 0.0; bool is_initial = false; std::vector nodal_internal_order; std::vector nodal_source_ids; Vector generalized_coordinates, velocity, acceleration; Vector algorithmic_acceleration; // Each 6*N; physical a != auxiliary A. std::vector, 3>> nodal_triad; std::vector> nodal_director; Vector angular_velocity, angular_acceleration; // Each 3*N, spatial. Vector inertia_force, internal_force, external_force, residual, reaction; Vector residual_scale; // Each 6*N, N2 uncancelled per-row scale. std::vector shell_results; double kinetic_energy = 0.0, physical_strain_energy = 0.0; double drilling_energy = 0.0, external_work = 0.0, energy_defect = 0.0; std::array global_equilibrium{}; std::array linear_momentum{}, angular_momentum{}; std::array linear_momentum_rate{}, angular_momentum_rate{}; std::array free_residual_metrics{}; // u, phi channels. Vector hidden_constraint, hidden_velocity_defect, hidden_acceleration_defect; Vector hidden_constraint_scale, hidden_velocity_defect_scale; Vector hidden_acceleration_defect_scale; // Each analytic null count nz. }; ``` `ModelDefinition.dynamic_step:optional`, `StepDefinition::IsDynamic()`, `StepDefinition::Dynamic()` 및 `Domain::Dynamic()` accessor를 추가한다. Dynamic과 static/frequency 정의는 상호배타적이다. `DynamicAnalysisState::Create(const Domain&,const DofManager&):Result`는 immutable inventory/schedule을 검증한다. 기본 empty 구성이 가능하되 Create 전 commit/실행/Write를 거부한다. `BeginTrialFromCommitted():Result`는 deep copy, `CommitAcceptedFrame(const DynamicAcceptedFrameState&):Status`는 강한 실패 보존, `DiscardTrial():void`는 caller-owned trial 폐기를 위한 no-op이다. `AcceptedFrames():const vector&`, `CommittedState():const DynamicAcceptedFrameState&`를 제공한다. Frame0는 Create 후 완성하여 같은 commit 검사를 통과해야 한다. 첫 commit 전 CommittedState 읽기는 금지다. Full q6N이 authority이며 displacement/rotation state를 중복 소유하지 않는다. `DynamicExecutionResult { DynamicAnalysisState state; vector diagnostics; }`를 PreparedAnalysisOutcome에 추가하고 별도 dynamic executor를 dispatch한다. `DynamicResultsWriter::Write(const filesystem::path&, const Domain&, const DynamicAnalysisState&, const vector&):Status` 및 `DynamicHdf5ResultsWriter`를 추가한다. Existing static writer에 time 또는 static load_factor로 dynamic을 위장하지 않는다. Momentum/rates는 전체 두께 volume kinetic kinematics로 구한 physical quantities, 원점 O=(0,0,0)에 대한 global angular momentum이다. Chart generalized momentum을 spatial angular momentum으로 표시하지 않는다. `global_equilibrium`은 N6의 applied+support minus momentum-rate이며 current arms와 spatial moments를 사용한다. Hidden vectors는 N3의 raw ψ/1차/2차 defects, `_scale`는 그 항별 uncancelled norm scale다. n_z=0은 empty vector, empty free channel metric 저장값 0은 해당 constraint inventory와 함께 N/A로 해석한다. NaN sentinel을 사용하지 않는다. Hidden 1·2차 defect는 진단/시간수렴 대상이며 Newton 수렴 기준으로 임의 승격하지 않는다. ## 3. 공식 HDF5 schema v1 공식 경로는 input lexical sibling `_results.h5`, CLI/lock/log/atomic file lifecycle은 기존 I/O §7.1을 상속한다. Dynamic log는 `nsnr-log-v1` 또는 target_factor를 사용하지 않는다. 기존 log stream에 dynamic diagnostic을 기록할 경우 `record=dynamic-newton-iteration`, `schema=hht-log-v1`, increment/time/iteration/stage와 N2 metric의 raw numerator/scale/branch/pass를 명시한다. 정적 전용 tolerance를 재사용하지 않는다. Schema identity는 `(feature_id,analysis_procedure,schema_version)`다. `/metadata` attribute: `feature_id=hht-implicit-dynamics`, `analysis_procedure=hht-implicit-dynamics`, `schema_version:uint64=1`, `mass_formulation=consistent-kinetic`, `internal_formulation=FESA-MITC4-total-lagrangian`, `initial_state=zero-q-v-step-load-0+`, `acceleration_convention=physical-endpoint-and-auxiliary-separated`, `source_step_name`, `step_name=Step-1`, `alpha/beta/gamma:float64`, `initial_increment/time_period:float64`, `increment_count/maximum_increments:uint64`. 기존 solver_version/source_input_identity/unit_system_label도 필수, 단위 label은 `consistent-input-units`이며 SI로 추정하지 않는다. 모든 numeric data는 finite-first, float64=IEEE binary64 little-endian, integer는 fixed width little-endian, string은 UTF-8 variable length다. Mandatory datasets는 contiguous/uncompressed. 아래 N=nodes, E=elements, F=Nincrements+1이다. Paths의 ``은 leading-zero 없는 decimal ordinal이다. Model schema는 기존 I/O §7.3의 `/model/nodes`, `/model/elements`, `/model/shell/materials`, `/model/shell/sections`, `/model/shell/nodal_director`, `/model/shell/nodal_frame`, `/model/nodal_constraint_mask`, `/model/shell/midsurface_locations`, `/model/shell/section_positions`의 정확한 dtype/shape/identity를 재사용한다. `/model/prescribed_displacement_start/target`은 dynamic에서 쓰지 않는다. 추가 `/model/prescribed_displacement:[N,6] float64`는 전값 0, `/model/material_density:[material_count]` compound는 `internal_material_id:uint64,rho:float64`로 materials 행 순서다. 활성 material의 rho는 finite positive다. `/steps/Step-1/accepted_frames:[F]` compound fields는 `frame_index:uint64,increment_index:uint64,time:float64,iteration_count:uint64,is_initial:uint8`. Table의 시간과 모든 frame attrs가 일치해야 한다. Load factor는 없다. 각 `/steps/Step-1/frames/` 아래 필수 dataset은 다음과 같다. | 상대 path | shape, dtype | component / 의미 / 차원 | | --- | --- | --- | | `nodal/displacement` | [N,6] float64 | UX,UY,UZ,URX,URY,URZ; q; length×3,1×3 (UR는 rad) | | `nodal/velocity` | [N,6] float64 | V1,V2,V3,PHIDOT1,PHIDOT2,PHIDOT3; chart v; length/time×3,1/time×3 | | `nodal/acceleration` | [N,6] float64 | A1,A2,A3,PHIDDOT1,PHIDDOT2,PHIDDOT3; physical chart a; length/time²×3,1/time²×3 | | `nodal/algorithmic_acceleration` | [N,6] float64 | AUX1,AUX2,AUX3,AUX4,AUX5,AUX6; auxiliary A; a와 같은 차원 | | `nodal/angular_velocity` | [N,3] float64 | OMEGA1,OMEGA2,OMEGA3; J_l(phi) phidot; 1/time | | `nodal/angular_acceleration` | [N,3] float64 | OMEGADOT1,OMEGADOT2,OMEGADOT3; J_l phiddot+DJ_l[phidot]phidot; 1/time² | | `nodal/triad`, `nodal/director` | [N,3,3], [N,3] float64 | 기존 current triad/director 의미, axis row A,B,D / global XYZ | | `nodal/generalized_inertia_force` | [N,6] float64 | I=M(q)a+c(q,v), physical endpoint | | `nodal/generalized_internal_force` | [N,6] float64 | physical+objective drilling internal force | | `nodal/generalized_external_force` | [N,6] float64 | F*, frame0에도 full value | | `nodal/generalized_residual` | [N,6] float64 | E=I+f_int-F, free와 constrained 전체 | | `nodal/generalized_reaction` | [N,6] float64 | constrained selector를 E에 적용, free 성분 exact zero | | `nodal/reaction` | [N,6] float64 | RF1,RF2,RF3,RM1,RM2,RM3; support-on-structure; moment=J_l^-T generalized reaction | | `element/shell/local_frame` | [E,4,3,3] float64 | 기존 GP1..4, local axes E1,E2,E3 | | `element/shell/generalized_strain` | [E,4,8] float64 | 기존 E11,E22,G12,K11,K22,K12,G13,G23 | | `element/shell/section_resultant` | [E,4,8] float64 | 기존 N11,N22,N12,M11,M22,M12,Q13,Q23 | | `element/shell/stress` | [E,4,3,3] float64 | 기존 BOTTOM,MIDDLE,TOP × S11,S22,S12 | | `global/energy` | [5] float64 | KINETIC_ENERGY,PHYSICAL_STRAIN_ENERGY,DRILLING_ENERGY,EXTERNAL_WORK,ENERGY_DEFECT; 모두 force*length | | `global/equilibrium` | [6] float64 | FORCE_1..3,MOMENT_1..3; N6 관성 포함 balance error | Generalized force 계열 component_names=`F1,F2,F3,MPHI1,MPHI2,MPHI3`, units=`force,force,force,force*length,force*length,force*length`, coordinate_system=`fixed-initial-global-chart`. Spatial U/omega/reaction은 `global-cartesian`; q/v/a/A는 translation global 및 rotation chart임을 `rotation_representation=total-rotation-vector-chart`와 `quantity_semantics`로 명시한다. Shell units, local_frame/source_element_type/midsurface/section_position 참조 attrs는 기존 I/O §7.5 그대로다. Numerical drill force를 physical shell stress/resultant에 섞지 않는다. 모든 result dataset에 `schema_version`, `component_names`, `component_unit_dimensions`, `coordinate_system`, `location`, `step_name`, `source_step_name`, `frame_index`, `increment_index`, `time`, `is_initial` attr를 쓴다. Scalar numeric attrs는 위 dtype, flags uint8이다. Location은 `nodal`, shell 기존 location, `global`/`global-balance-error`다. 모든 nodal row는 `/model/nodes`, element row는 `/model/elements` 행을 참조한다. Energy의 W=F*ᵀ(qn-q0), defect=T+Uphys+Udrill-E0-W다. Initial physical energy E0는 frame0의 첫 세 성분 합이며 W0/defect0=0. A를 kinetic energy나 physical inertia에 쓰지 않는다. Algorithmic dissipation을 physical damping energy로 이름 붙이지 않는다. Raw momentum/hidden diagnostics는 위 internal frame의 필수 evidence이며 이번 persistent schema에는 별도 필수 dataset을 추가하지 않는다. HDF5에서 계산할 수 없는 상세 검증은 실행 보고서가 internal evidence를 기록한다. `/diagnostics`는 기존 diagnostic의 의미·dtype·정렬을 재사용하되 dynamic compound field 이름을 severity/code/source_file/source_line/keyword/entity_identity/message로 명시한다. 기존 static writer의 file/line 이름은 이 dynamic schema에 적용하지 않는다. Writer는 전체 frames/node/element inventory, dtype/rank/component/attrs/time/finite/constraint, complete final frame를 검사하고 write/flush/close/reopen/self-check 후 원자 replace한다. 기존 final file은 실패 시 보존한다. Live log의 이미 flush된 prefix는 rollback하지 않는다. No-op warning이 있어도 필수 dataset을 생략할 수 없다. ## 4. 진단과 비교 binding 기존 `Diagnostic`의 severity/code/location/keyword/entity_identity/message를 사용하며 `SortDiagnostics` 순서 유지. 위치는 가능한 source keyword/data row, runtime에서는 step+increment와 node/element source identity를 message/entity_identity에 포함한다. Parser/model/output/backend의 기존 FailureCategory/CLI exit mapping을 유지하고 새 exit code를 만들지 않는다. | code | 발생 조건 | | --- | --- | | `unsupported-dynamic-parameter` | DIRECT 외 dynamic parameter, 잘못된 flag, 계수 입력 | | `invalid-dynamic-data` | h/T/nonfinite/arity/범위 오류 | | `invalid-dynamic-schedule` | N*h≠T, N>INC, uint32 초과, 시간 비증가 | | `unsupported-dynamic-boundary` | 비영/시간변화/BC option | | `unsupported-dynamic-load` | CLOAD4–6, follower/amplitude/distributed load | | `invalid-density-data` | 누락/중복/nonpositive/비상수 density; 기존 code 재사용 | | `unsupported-analysis-procedure`, `unsupported-multiple-step`, `unsupported-keyword` | 잘못된 procedure/NLGEOM, 다중 step, allowlist 밖 의미 | | `ignored-output-request` | 위 정확한 allowlisted no-op만 warning | | `dynamic-initial-consistency`, `dynamic-mass-regularity` | N3 초기화/숨은 조건 또는 N4 rank/positive block/regularity 실패 | | `dynamic-nonconvergence`, `dynamic-linear-solve` | N2 correction 한도 또는 solve/backward-error 실패 | | `dynamic-invalid-state`, `dynamic-output-schema` | chart/geometry/nonfinite/inventory/update 또는 저장/재독 계약 위반 | 기존 geometry/backend code가 더 구체적이면 보존하며 위 code로 감싸 원래 source context를 버리지 않는다. 질량 singularity 자체는 오류가 아니다. Formulation에 정의된 허용 null을 제거하거나 epsilon mass를 주지 않는다. [Reference 계약](reference-model.md)의 source step/instance/node/increment/time/component authority를 그대로 적용한다. Binding: `/model/nodes`의 `(instance_name,source_label)`와 frame table을 조인하고 `nodal/displacement[:,0:3]→U-U1/U2/U3`, `[:,3:6]→UR-UR1/UR2/UR3`. 먼저 전체 729-node 101-frame identity/finite 검사를 수행하고 고정 9-node subset으로 projection한다. Decimal CSV time=n/10 검증과 binary HDF5 time=double(n)*double(.1) 검증을 각각 수행한다. Nearest-time/보간/평균/row-index matching은 금지다. Wrong procedure/mass/coefficient/초기 convention, missing/extra/duplicate/nonfinite는 tolerance 전에 실패다. Frame별 U blocking/UR warning-only 202 families와 공통 [TOLERANCE](../TOLERANCE.md)는 변경하지 않는다. ## 5. 인계 및 확인 상태 입력 원본의 procedure/density/BC/CLOAD/output 요청과 기존 semantic/prepared_analysis/state/writer 경계를 대조했다. 이번 변경은 이 문서뿐이며 source/test/reference artifact는 수정하지 않았다. 문서 링크와 diff를 정적 검사한다. Build/test·dynamic HDF5 readback·reference 비교는 문서 owner의 작업이 아니므로 미실행이다. 상위 물리 선택 충돌은 없다. 구현에 아직 필요한 upstream quantity는 별도 Udrill energy, exact inertia/kinetic energy, spatial rates, physical momentum/rates, N2 uncancelled per-row scale 및 N3 hidden defects다. 기존 nonlinear recovery가 Uphys만 반환한다는 이유로 Udrill=0 또는 누락으로 바꾸면 안 된다. 메인은 이 문서를 main report에서 연결하고 공개 subset/schema 결정 근거를 Feature ADR에 기록한다. 이 계약 정의만으로 solver/physics gate가 통과한 것은 아니다.