"""Callback-free data contract for the whole-system numeric IR. System IR v2 describes a compiled simulation model. It is intentionally a pure, immutable data graph: Python functions, model objects, object addresses, and run-local diagnostic state are not part of this module's wire contract. """ from __future__ import annotations from dataclasses import dataclass, fields, is_dataclass from enum import StrEnum from hashlib import sha256 import json from math import isfinite from struct import pack from typing import ClassVar from unicodedata import normalize SYSTEM_NUMERIC_IR_SCHEMA_ID = "system-numeric-ir" SYSTEM_NUMERIC_IR_SCHEMA_MAJOR = 2 SYSTEM_NUMERIC_IR_SCHEMA_MINOR = 0 SYSTEM_NUMERIC_IR_COMPILER_ID = "generic-fluid-system" SYSTEM_NUMERIC_IR_COMPILER_VERSION = "2.0.0" NATIVE_NUMERIC_ABI_VERSION = 1 class IRDType(StrEnum): FLOAT64 = "float64" INT32 = "int32" class IRBufferKind(StrEnum): TIME = "time" STATE_INPUT = "state_input" DERIVATIVE_OUTPUT = "derivative_output" LOCAL_STATE = "local_state" LOCAL_DERIVATIVE = "local_derivative" ALGEBRAIC = "algebraic" SIGNAL = "signal" PARAMETER = "parameter" CONSTANT = "constant" MODE = "mode" WORK_FLOAT = "work_float" WORK_INT = "work_int" EVENT_OUTPUT = "event_output" JACOBIAN_VALUE = "jacobian_value" RESULT_OUTPUT = "result_output" RUNTIME_INPUT = "runtime_input" class IRKernelPhase(StrEnum): PRIMAL = "primal" RESIDUAL = "residual" DERIVATIVE = "derivative" PROPERTY = "property" EVENT = "event" RESET = "reset" JACOBIAN = "jacobian" OUTPUT = "output" class IRKernelAvailability(StrEnum): NATIVE = "native" REFERENCE_ONLY = "reference_only" class IRPortKind(StrEnum): PHYSICAL = "physical" SIGNAL = "signal" class IRPortNominalRole(StrEnum): INLET = "inlet" OUTLET = "outlet" BIDIRECTIONAL = "bidirectional" INPUT = "input" OUTPUT = "output" class IRPositiveFlowDirection(StrEnum): INTO_COMPONENT = "intoComponent" class IRVariableRole(StrEnum): EFFORT = "effort" FLOW = "flow" STREAM = "stream" SIGNAL = "signal" class IRConnectionRule(StrEnum): EQUAL = "equal" SUM_TO_ZERO = "sumToZero" STREAM_MIX = "streamMix" DIRECTED = "directed" class IREquationOwner(StrEnum): COMPONENT = "component" CONNECTION = "connection" class IREquationRelation(StrEnum): EQUAL = "equal" SUM_TO_ZERO = "sumToZero" CONSTITUTIVE = "constitutive" STATE = "state" class IRPressureFlowScopeKind(StrEnum): NETWORK = "network" PHYSICAL_ISLAND = "physical_island" EQUATION_BLOCK = "equation_block" class IRStageKind(StrEnum): STATE_REDUCE = "state_reduce" SIGNAL = "signal" MECHANICAL_EQUIVALENCE = "mechanical_equivalence" DYNAMIC_VOLUME = "dynamic_volume" PROPERTY = "property" PRESSURE_FLOW = "pressure_flow" STREAM = "stream" TEMPERATURE_REFERENCE = "temperature_reference" THERMOFLUID_FIXED_POINT = "thermofluid_fixed_point" MECHANICAL_ACCELERATION = "mechanical_acceleration" DERIVATIVE_REDUCE = "derivative_reduce" EVENT = "event" JACOBIAN = "jacobian" OUTPUT = "output" RESET = "reset" class IREntryPointKind(StrEnum): RHS = "rhs" EVENTS = "events" JACOBIAN = "jacobian" OUTPUTS = "outputs" class IRBlockKind(StrEnum): SEQUENCE = "sequence" FIXED_POINT = "fixed_point" STREAM_SCC = "stream_scc" class IRStepKind(StrEnum): STAGE = "stage" BLOCK = "block" class IRStateMapKind(StrEnum): SCATTER = "scatter" DERIVATIVE_GATHER = "derivative_gather" class IREventDirection(StrEnum): DECREASING = "decreasing" ANY = "any" INCREASING = "increasing" class IRFailurePolicy(StrEnum): FAIL = "fail" RETRY_SMALLER_STEP = "retry_smaller_step" class IRCacheKind(StrEnum): PROPERTY = "property" PRESSURE_FLOW = "pressure_flow" STREAM = "stream" JACOBIAN = "jacobian" OUTPUT = "output" class IRCapabilityLevel(StrEnum): NATIVE = "native" REFERENCE_ONLY = "reference_only" UNSUPPORTED = "unsupported" class IRDiagnosticSeverity(StrEnum): INFO = "info" WARNING = "warning" ERROR = "error" class IROpcode(StrEnum): FILL = "fill" COPY = "copy" SCATTER = "scatter" LINEAR_COMBINATION = "linear_combination" STATE_MAP = "state_map" KERNEL_CALL = "kernel_call" EFFORT_BROADCAST = "effort_broadcast" FLOW_ASSIGN = "flow_assign" CHECK_FINITE = "check_finite" @dataclass(frozen=True, slots=True) class IRSchemaVersion: schema_id: str = SYSTEM_NUMERIC_IR_SCHEMA_ID major: int = SYSTEM_NUMERIC_IR_SCHEMA_MAJOR minor: int = SYSTEM_NUMERIC_IR_SCHEMA_MINOR CURRENT_SYSTEM_IR_VERSION = IRSchemaVersion() @dataclass(frozen=True, slots=True) class IRSlotRef: buffer: IRBufferKind index: int @dataclass(frozen=True, slots=True) class IRBufferSpec: kind: IRBufferKind dtype: IRDType size: int initial_float_values: tuple[float, ...] = () initial_int_values: tuple[int, ...] = () @dataclass(frozen=True, slots=True) class IRValueSpec: value_id: str slot: IRSlotRef semantic: str role: str quantity: str unit: str scale: float lower_bound: float | None = None upper_bound: float | None = None owner_component_index: int | None = None @dataclass(frozen=True, slots=True) class IRKernelPhaseSpec: """Phase capability tag; each call's ordered slot lists define its C-01 ABI. Fixed component-kernel signatures deliberately belong to the C-02 contract. Recording placeholder arities here would make the current reference-only kernels look more strictly specified than they are. """ phase: IRKernelPhase @dataclass(frozen=True, slots=True) class IRKernelSpec: kernel_id: str model_type: str model_version: str implementation_version: str availability: IRKernelAvailability unavailable_reason: str | None phases: tuple[IRKernelPhaseSpec, ...] parameter_count: int state_count: int mode_count: int workspace_float_count: int workspace_int_count: int @dataclass(frozen=True, slots=True) class IRComponentInstance: instance_id: str kernel_index: int parameter_slots: tuple[IRSlotRef, ...] state_slots: tuple[IRSlotRef, ...] derivative_slots: tuple[IRSlotRef, ...] mode_slots: tuple[IRSlotRef, ...] port_indices: tuple[int, ...] port_slots: tuple[IRSlotRef, ...] output_indices: tuple[int, ...] workspace_float_slots: tuple[IRSlotRef, ...] workspace_int_slots: tuple[IRSlotRef, ...] @dataclass(frozen=True, slots=True) class IRPortVariable: variable_id: str name: str role: IRVariableRole connection_rule: IRConnectionRule quantity: str unit: str result_visible: bool slot: IRSlotRef @dataclass(frozen=True, slots=True) class IRPortSpec: port_id: str component_index: int name: str kind: IRPortKind domain: str nominal_role: IRPortNominalRole positive_flow_direction: IRPositiveFlowDirection | None variables: tuple[IRPortVariable, ...] @dataclass(frozen=True, slots=True) class IRConnectionVariable: name: str rule: IRConnectionRule endpoint_a_slot: IRSlotRef endpoint_b_slot: IRSlotRef @dataclass(frozen=True, slots=True) class IRConnectionSpec: connection_id: str kind: IRPortKind domain: str endpoint_a_port_index: int endpoint_b_port_index: int variables: tuple[IRConnectionVariable, ...] @dataclass(frozen=True, slots=True) class IRMediumSpec: medium_id: str name: str implementation_id: str implementation_version: str parameter_slots: tuple[IRSlotRef, ...] component_indices: tuple[int, ...] @dataclass(frozen=True, slots=True) class IRCSRPattern: row_count: int column_count: int row_pointers: tuple[int, ...] column_indices: tuple[int, ...] @property def nonzero_count(self) -> int: return len(self.column_indices) @dataclass(frozen=True, slots=True) class IRCSRMatrix: pattern: IRCSRPattern values: tuple[float, ...] @dataclass(frozen=True, slots=True) class IRStateReducer: solver_state_count: int local_state_slots: tuple[IRSlotRef, ...] raw_derivative_slots: tuple[IRSlotRef, ...] state_scatter: IRCSRMatrix derivative_gather: IRCSRMatrix initial_state: tuple[float, ...] absolute_tolerances: tuple[float, ...] @dataclass(frozen=True, slots=True) class IRFillOperation: opcode: ClassVar[IROpcode] = IROpcode.FILL target_slots: tuple[IRSlotRef, ...] value: float @dataclass(frozen=True, slots=True) class IRCopyOperation: opcode: ClassVar[IROpcode] = IROpcode.COPY source_slot: IRSlotRef target_slot: IRSlotRef @dataclass(frozen=True, slots=True) class IRScatterOperation: opcode: ClassVar[IROpcode] = IROpcode.SCATTER source_slot: IRSlotRef target_slots: tuple[IRSlotRef, ...] @dataclass(frozen=True, slots=True) class IRLinearCombinationOperation: opcode: ClassVar[IROpcode] = IROpcode.LINEAR_COMBINATION source_slots: tuple[IRSlotRef, ...] weights: tuple[float, ...] target_slot: IRSlotRef bias: float = 0.0 @dataclass(frozen=True, slots=True) class IRStateMapOperation: opcode: ClassVar[IROpcode] = IROpcode.STATE_MAP map_kind: IRStateMapKind source_slots: tuple[IRSlotRef, ...] target_slots: tuple[IRSlotRef, ...] @dataclass(frozen=True, slots=True) class IRKernelCallOperation: opcode: ClassVar[IROpcode] = IROpcode.KERNEL_CALL kernel_index: int component_index: int | None phase: IRKernelPhase read_slots: tuple[IRSlotRef, ...] write_slots: tuple[IRSlotRef, ...] equation_indices: tuple[int, ...] = () @dataclass(frozen=True, slots=True) class IREffortBroadcastOperation: opcode: ClassVar[IROpcode] = IROpcode.EFFORT_BROADCAST variable: str anchor_slot: IRSlotRef residual_slot: IRSlotRef result_slot: IRSlotRef scatter_slots: tuple[IRSlotRef, ...] equation_id: str lower_bound: float | None = None upper_bound: float | None = None @dataclass(frozen=True, slots=True) class IRFlowAssignmentOperation: opcode: ClassVar[IROpcode] = IROpcode.FLOW_ASSIGN value_slot: IRSlotRef result_slot: IRSlotRef scatter_slots: tuple[IRSlotRef, ...] equation_id: str @dataclass(frozen=True, slots=True) class IRCheckFiniteOperation: opcode: ClassVar[IROpcode] = IROpcode.CHECK_FINITE slots: tuple[IRSlotRef, ...] error_code: str IROperation = ( IRFillOperation | IRCopyOperation | IRScatterOperation | IRLinearCombinationOperation | IRStateMapOperation | IRKernelCallOperation | IREffortBroadcastOperation | IRFlowAssignmentOperation | IRCheckFiniteOperation ) def operation_read_slots(operation: IROperation) -> tuple[IRSlotRef, ...]: if isinstance(operation, IRFillOperation): return () if isinstance(operation, (IRCopyOperation, IRScatterOperation)): return (operation.source_slot,) if isinstance(operation, (IRLinearCombinationOperation, IRStateMapOperation)): return operation.source_slots if isinstance(operation, IRKernelCallOperation): return operation.read_slots if isinstance(operation, IREffortBroadcastOperation): return (operation.anchor_slot, operation.residual_slot) if isinstance(operation, IRFlowAssignmentOperation): return (operation.value_slot,) if isinstance(operation, IRCheckFiniteOperation): return operation.slots raise TypeError(f"Unsupported IR operation: {type(operation).__name__}.") def operation_write_slots(operation: IROperation) -> tuple[IRSlotRef, ...]: if isinstance(operation, IRFillOperation): return operation.target_slots if isinstance(operation, IRCopyOperation): return (operation.target_slot,) if isinstance(operation, IRScatterOperation): return operation.target_slots if isinstance(operation, IRLinearCombinationOperation): return (operation.target_slot,) if isinstance(operation, IRStateMapOperation): return operation.target_slots if isinstance(operation, IRKernelCallOperation): return operation.write_slots if isinstance(operation, IREffortBroadcastOperation): return (operation.result_slot, *operation.scatter_slots) if isinstance(operation, IRFlowAssignmentOperation): return (operation.result_slot, *operation.scatter_slots) if isinstance(operation, IRCheckFiniteOperation): return () raise TypeError(f"Unsupported IR operation: {type(operation).__name__}.") @dataclass(frozen=True, slots=True) class IRStage: stage_id: str kind: IRStageKind operations: tuple[IROperation, ...] declared_read_slots: tuple[IRSlotRef, ...] declared_write_slots: tuple[IRSlotRef, ...] @dataclass(frozen=True, slots=True) class IRStepRef: kind: IRStepKind index: int @dataclass(frozen=True, slots=True) class IRConvergenceSpec: monitor_slots: tuple[IRSlotRef, ...] absolute_tolerance: float relative_tolerance: float max_iterations: int relaxation: float rollback_slots: tuple[IRSlotRef, ...] failure_policy: IRFailurePolicy @dataclass(frozen=True, slots=True) class IRExecutionBlock: block_id: str kind: IRBlockKind steps: tuple[IRStepRef, ...] convergence: IRConvergenceSpec | None = None @dataclass(frozen=True, slots=True) class IREntryPoint: kind: IREntryPointKind steps: tuple[IRStepRef, ...] input_slots: tuple[IRSlotRef, ...] output_slots: tuple[IRSlotRef, ...] @dataclass(frozen=True, slots=True) class IRCausalEffortStageRef: variable: str stage_index: int @dataclass(frozen=True, slots=True) class IRCausalPlan: plan_id: str scope_component_indices: tuple[int, ...] source_schema_version: int source_structural_signature: str | None fallback_reason: str | None canonical_slots: tuple[IRSlotRef, ...] compatibility_slots: tuple[IRSlotRef, ...] reset_slots: tuple[IRSlotRef, ...] external_effort_slots: tuple[IRSlotRef, ...] effort_stages: tuple[IRCausalEffortStageRef, ...] flow_stage_indices: tuple[int, ...] @dataclass(frozen=True, slots=True) class IRAlgebraicUnknown: unknown_id: str component_index: int port_index: int variable: str role: IRVariableRole slot: IRSlotRef scale: float lower_bound: float | None = None upper_bound: float | None = None @dataclass(frozen=True, slots=True) class IRPressureFlowEquation: equation_id: str owner: IREquationOwner owner_index: int relation: IREquationRelation role: IRVariableRole | None variable_slots: tuple[IRSlotRef, ...] residual_slot: IRSlotRef scale: float @dataclass(frozen=True, slots=True) class IRPressureFlowBlock: block_id: str unknown_indices: tuple[int, ...] equation_indices: tuple[int, ...] jacobian_pattern: IRCSRPattern @dataclass(frozen=True, slots=True) class IRPressureFlowScope: scope_id: str kind: IRPressureFlowScopeKind component_indices: tuple[int, ...] unknown_indices: tuple[int, ...] equation_indices: tuple[int, ...] block_indices: tuple[int, ...] causal_plan_index: int | None residual_tolerance: float max_evaluations: int sparse_pattern_trusted: bool sparse_fallback_reason: str | None @dataclass(frozen=True, slots=True) class IRPressureFlowPlan: unknowns: tuple[IRAlgebraicUnknown, ...] equations: tuple[IRPressureFlowEquation, ...] blocks: tuple[IRPressureFlowBlock, ...] scopes: tuple[IRPressureFlowScope, ...] global_scope_index: int secondary_scope_indices: tuple[int, ...] pressure_lower_bound: float @dataclass(frozen=True, slots=True) class IRStreamSCC: scc_id: str node_slots: tuple[IRSlotRef, ...] block_index: int @dataclass(frozen=True, slots=True) class IRStreamEdge: source_scc_index: int target_scc_index: int @dataclass(frozen=True, slots=True) class IRStreamPlan: plan_id: str node_slots: tuple[IRSlotRef, ...] strongly_connected_components: tuple[IRStreamSCC, ...] condensed_edges: tuple[IRStreamEdge, ...] topological_scc_indices: tuple[int, ...] @dataclass(frozen=True, slots=True) class IRThermofluidPlan: physical_port_indices: tuple[int, ...] global_component_indices: tuple[int, ...] stream_plan_index: int secondary_pressure_scope_indices: tuple[int, ...] sensitive_component_indices: tuple[int, ...] maximum_iterations: int flow_relative_tolerance: float uses_conservative_global_solver: bool conservative_fallback_reason: str | None @dataclass(frozen=True, slots=True) class IRTransactionPlan: snapshot_slots: tuple[IRSlotRef, ...] flow_slots: tuple[IRSlotRef, ...] cache_component_indices: tuple[int, ...] cache_attribute_ids: tuple[str, ...] diagnostic_owner_ids: tuple[str, ...] restores_on_recoverable_failure: bool restores_on_fatal_failure: bool @dataclass(frozen=True, slots=True) class IRModeValueSpec: value: int name: str @dataclass(frozen=True, slots=True) class IRModeSpec: mode_id: str slot: IRSlotRef owner_component_indices: tuple[int, ...] values: tuple[IRModeValueSpec, ...] initial_value: int @dataclass(frozen=True, slots=True) class IRFiniteDifferenceColumn: column_index: int value_indices: tuple[int, ...] relative_step: float @dataclass(frozen=True, slots=True) class IRJacobianPlan: pattern: IRCSRPattern value_slots: tuple[IRSlotRef, ...] color_groups: tuple[tuple[int, ...], ...] fill_steps: tuple[IRStepRef, ...] analytic_value_indices: tuple[int, ...] local_finite_difference_columns: tuple[IRFiniteDifferenceColumn, ...] @dataclass(frozen=True, slots=True) class IRModeGuard: mode_slot: IRSlotRef allowed_values: tuple[int, ...] @dataclass(frozen=True, slots=True) class IREventSpec: event_id: str event_kind: str owner_component_indices: tuple[int, ...] root_slot: IRSlotRef direction: IREventDirection terminal: bool priority: int mode_guards: tuple[IRModeGuard, ...] reset_steps: tuple[IRStepRef, ...] invalidated_caches: tuple[IRCacheKind, ...] restarts_integrator: bool @dataclass(frozen=True, slots=True) class IROutputSpec: output_id: str component_index: int scope: str port_name: str | None name: str label: str category: str quantity: str unit: str order: int source_slot: IRSlotRef output_slot: IRSlotRef scale: float = 1.0 offset: float = 0.0 @dataclass(frozen=True, slots=True) class IRComponentCapability: component_index: int level: IRCapabilityLevel supported_phases: tuple[IRKernelPhase, ...] missing_features: tuple[str, ...] @dataclass(frozen=True, slots=True) class IRCapabilityIssue: code: str severity: IRDiagnosticSeverity scope_id: str message: str @dataclass(frozen=True, slots=True) class IRCapabilityReport: system_level: IRCapabilityLevel components: tuple[IRComponentCapability, ...] issues: tuple[IRCapabilityIssue, ...] @dataclass(frozen=True, slots=True) class SystemIR: version: IRSchemaVersion model_id: str model_version: str compiler_id: str compiler_version: str numeric_dtype: IRDType buffers: tuple[IRBufferSpec, ...] values: tuple[IRValueSpec, ...] kernels: tuple[IRKernelSpec, ...] components: tuple[IRComponentInstance, ...] mediums: tuple[IRMediumSpec, ...] ports: tuple[IRPortSpec, ...] connections: tuple[IRConnectionSpec, ...] state_reducer: IRStateReducer causal_plans: tuple[IRCausalPlan, ...] pressure_flow: IRPressureFlowPlan stream_plans: tuple[IRStreamPlan, ...] thermofluid: IRThermofluidPlan stages: tuple[IRStage, ...] blocks: tuple[IRExecutionBlock, ...] entry_points: tuple[IREntryPoint, ...] transaction: IRTransactionPlan modes: tuple[IRModeSpec, ...] jacobian: IRJacobianPlan events: tuple[IREventSpec, ...] outputs: tuple[IROutputSpec, ...] capabilities: IRCapabilityReport required_features: tuple[str, ...] = () def canonical_json_bytes(self) -> bytes: return canonical_json_bytes(self) def calculate_structural_signature(self) -> str: return sha256(self.canonical_json_bytes()).hexdigest() @property def structural_signature(self) -> str: return self.calculate_structural_signature() @dataclass(frozen=True, slots=True) class IRNativeBuildIdentity: abi_version: int target_triple: str compiler_id: str compiler_version: str compile_flags: tuple[str, ...] floating_point_policy: str kernel_library_signature: str @dataclass(frozen=True, slots=True) class _IRNativeArtifactKeyInput: program_signature: str build: IRNativeBuildIdentity _CANONICAL_TYPE_NAMES: tuple[tuple[type[object], str], ...] = ( (IRSchemaVersion, "schema_version"), (IRSlotRef, "slot_ref"), (IRBufferSpec, "buffer"), (IRValueSpec, "value"), (IRKernelPhaseSpec, "kernel_phase"), (IRKernelSpec, "kernel"), (IRComponentInstance, "component"), (IRPortVariable, "port_variable"), (IRPortSpec, "port"), (IRConnectionVariable, "connection_variable"), (IRConnectionSpec, "connection"), (IRMediumSpec, "medium"), (IRCSRPattern, "csr_pattern"), (IRCSRMatrix, "csr_matrix"), (IRStateReducer, "state_reducer"), (IRFillOperation, "operation"), (IRCopyOperation, "operation"), (IRScatterOperation, "operation"), (IRLinearCombinationOperation, "operation"), (IRStateMapOperation, "operation"), (IRKernelCallOperation, "operation"), (IREffortBroadcastOperation, "operation"), (IRFlowAssignmentOperation, "operation"), (IRCheckFiniteOperation, "operation"), (IRStage, "stage"), (IRStepRef, "step_ref"), (IRConvergenceSpec, "convergence"), (IRExecutionBlock, "block"), (IREntryPoint, "entry_point"), (IRCausalEffortStageRef, "causal_effort_stage"), (IRCausalPlan, "causal_plan"), (IRAlgebraicUnknown, "algebraic_unknown"), (IRPressureFlowEquation, "pressure_flow_equation"), (IRPressureFlowBlock, "pressure_flow_block"), (IRPressureFlowScope, "pressure_flow_scope"), (IRPressureFlowPlan, "pressure_flow_plan"), (IRStreamSCC, "stream_scc"), (IRStreamEdge, "stream_edge"), (IRStreamPlan, "stream_plan"), (IRThermofluidPlan, "thermofluid_plan"), (IRTransactionPlan, "transaction_plan"), (IRModeValueSpec, "mode_value"), (IRModeSpec, "mode"), (IRFiniteDifferenceColumn, "finite_difference_column"), (IRJacobianPlan, "jacobian_plan"), (IRModeGuard, "mode_guard"), (IREventSpec, "event"), (IROutputSpec, "output"), (IRComponentCapability, "component_capability"), (IRCapabilityIssue, "capability_issue"), (IRCapabilityReport, "capability_report"), (SystemIR, "system_ir"), (IRNativeBuildIdentity, "native_build"), (_IRNativeArtifactKeyInput, "native_artifact_key_input"), ) _OPERATION_TYPES = ( IRFillOperation, IRCopyOperation, IRScatterOperation, IRLinearCombinationOperation, IRStateMapOperation, IRKernelCallOperation, IREffortBroadcastOperation, IRFlowAssignmentOperation, IRCheckFiniteOperation, ) def _canonical_type_name(value: object) -> str: value_type = type(value) for candidate, name in _CANONICAL_TYPE_NAMES: if value_type is candidate: return name raise TypeError(f"Unsupported IR schema object: {value_type.__name__}.") def _canonical_float(value: float) -> object: numeric = float(value) if not isfinite(numeric): raise ValueError("Canonical IR JSON does not permit NaN or infinity.") if numeric == 0.0: numeric = 0.0 return {"$float64": pack(">d", numeric).hex()} def _canonical_value(value: object) -> object: if value is None or isinstance(value, bool): return value if isinstance(value, StrEnum): return value.value if isinstance(value, int): return value if isinstance(value, float): return _canonical_float(value) if isinstance(value, str): return normalize("NFC", value) if isinstance(value, tuple): return [_canonical_value(item) for item in value] if is_dataclass(value) and not isinstance(value, type): payload: dict[str, object] = {"$type": _canonical_type_name(value)} if isinstance(value, _OPERATION_TYPES): payload["opcode"] = value.opcode.value for item in fields(value): payload[item.name] = _canonical_value(getattr(value, item.name)) return payload raise TypeError( "Canonical IR JSON accepts only schema dataclasses, tuples, enums, and " f"scalar values; received {type(value).__name__}." ) def canonical_json_bytes(value: object) -> bytes: """Return the exact platform-independent canonical JSON byte sequence.""" return json.dumps( _canonical_value(value), ensure_ascii=True, allow_nan=False, sort_keys=True, separators=(",", ":"), ).encode("utf-8") def native_artifact_key( program: SystemIR, build: IRNativeBuildIdentity, ) -> str: """Build cache key; target details never contaminate the program hash.""" if build.abi_version != NATIVE_NUMERIC_ABI_VERSION: raise ValueError( "Native build ABI does not match NATIVE_NUMERIC_ABI_VERSION." ) for field_name, value in ( ("target_triple", build.target_triple), ("compiler_id", build.compiler_id), ("compiler_version", build.compiler_version), ("floating_point_policy", build.floating_point_policy), ): if not value: raise ValueError(f"Native build {field_name} must not be empty.") if any(not flag for flag in build.compile_flags): raise ValueError("Native build flags must not contain empty entries.") if ( len(build.kernel_library_signature) != 64 or any( character not in "0123456789abcdef" for character in build.kernel_library_signature ) ): raise ValueError( "Native kernel library signature must be lowercase SHA-256 hex." ) payload = _IRNativeArtifactKeyInput(program.structural_signature, build) return sha256(canonical_json_bytes(payload)).hexdigest()