完成仿真系统IR-schema规定
This commit is contained in:
1 parent
03b86f52ba
commit
ce353d2dd2
8 files changed
+5949
-4
No files matched your search
@@ -0,0 +1,40 @@
|
|||||||
|
"""Whole-system numeric intermediate representation."""
|
||||||
|
|
||||||
|
from .compiler import compile_system_ir
|
||||||
|
from .schema import * # noqa: F403 - this package is the public schema facade.
|
||||||
|
from .schema import __dict__ as _schema_namespace
|
||||||
|
from .validation import (
|
||||||
|
IRValidationIssue,
|
||||||
|
IRValidationReport,
|
||||||
|
SystemIRValidationError,
|
||||||
|
require_valid_system_ir,
|
||||||
|
validate_system_ir,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"compile_system_ir",
|
||||||
|
"IRValidationIssue",
|
||||||
|
"IRValidationReport",
|
||||||
|
"SystemIRValidationError",
|
||||||
|
"require_valid_system_ir",
|
||||||
|
"validate_system_ir",
|
||||||
|
*sorted(
|
||||||
|
name
|
||||||
|
for name in _schema_namespace
|
||||||
|
if name.startswith("IR")
|
||||||
|
or name.startswith("SystemIR")
|
||||||
|
or name.startswith("SYSTEM_NUMERIC_IR")
|
||||||
|
or name
|
||||||
|
in {
|
||||||
|
"CURRENT_SYSTEM_IR_VERSION",
|
||||||
|
"NATIVE_NUMERIC_ABI_VERSION",
|
||||||
|
"canonical_json_bytes",
|
||||||
|
"native_artifact_key",
|
||||||
|
"operation_read_slots",
|
||||||
|
"operation_write_slots",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
del _schema_namespace
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,973 @@
|
|||||||
|
"""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()
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -139,6 +139,8 @@ SIMULATION_NUMERIC_ENGINE=python|native|shadow|auto
|
|||||||
|
|
||||||
#### C-01 完整数值 IR schema v2
|
#### C-01 完整数值 IR schema v2
|
||||||
|
|
||||||
|
状态:**已完成 v2.0 schema、规范、系统编译器、fail-closed 语义校验和复杂模型结构回归。** 当前结果明确为 `reference_only`,C-02/C-03 尚未完成,默认 Python 求解路径没有切换。
|
||||||
|
|
||||||
完整 IR 必须描述共享数值阶段,以及 RHS、Event、Jacobian 和输出四类独立的按需入口。它们可以复用同一套槽位和依赖信息,但不能被误实现成“每次 RHS 都顺序计算 Event、Jacobian 和输出”。共享 primal 计划为:
|
完整 IR 必须描述共享数值阶段,以及 RHS、Event、Jacobian 和输出四类独立的按需入口。它们可以复用同一套槽位和依赖信息,但不能被误实现成“每次 RHS 都顺序计算 Event、Jacobian 和输出”。共享 primal 计划为:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -169,11 +171,12 @@ IR 至少描述:
|
|||||||
- stream 图、SCC、物性状态包、外层固定点和事务恢复集合;
|
- stream 图、SCC、物性状态包、外层固定点和事务恢复集合;
|
||||||
- event ID、左/右状态、reset、Jacobian 失效和模式计划;
|
- event ID、左/右状态、reset、Jacobian 失效和模式计划;
|
||||||
- 固定的 CSR Jacobian 结构和 output projection;
|
- 固定的 CSR Jacobian 结构和 output projection;
|
||||||
- IR schema、native ABI、组件模型/实现、介质、编译器、dtype、平台和构建选项组成的结构签名。
|
- 完整 IR 内容(含组件模型/实现、介质、dtype、参数和执行计划)组成跨平台内容签名;
|
||||||
|
- native artifact key 再把内容签名与 native ABI、目标平台、编译器、构建选项、浮点策略和 kernel 库签名组合。
|
||||||
|
|
||||||
原生兼容 program 中不得存在 Python callback。schema v1 保留为参考适配器,不直接扩展成生产 ABI。
|
原生兼容 program 中不得存在 Python callback。schema v1 保留为参考适配器,不直接扩展成生产 ABI。
|
||||||
|
|
||||||
完成标准:同一模型重复编译得到字节级稳定的结构和签名;所有索引及读写集合可静态校验;缺少能力时明确拒绝编译。
|
完成标准:同一模型重复编译得到字节级稳定的结构和签名;所有索引及读写集合可静态校验;结构可完整描述但缺少原生能力时明确标为 `reference_only`,原生加载器必须拒绝接管。
|
||||||
|
|
||||||
#### C-02 纯数值组件合同
|
#### C-02 纯数值组件合同
|
||||||
|
|
||||||
@@ -397,8 +400,8 @@ Numba 可以在完整数组 IR 后用于 1–2 周的架构验证,但不作为
|
|||||||
第一批只做 P0,不直接开始大规模 C 编码:
|
第一批只做 P0,不直接开始大规模 C 编码:
|
||||||
|
|
||||||
1. **已完成:** 固定当前权威输入和回归证据的 `LF` 检出规则,重新规范化 Windows 工作树,并增加 Windows/Linux 字节合同测试;未重建语义未变的 golden。
|
1. **已完成:** 固定当前权威输入和回归证据的 `LF` 检出规则,重新规范化 Windows 工作树,并增加 Windows/Linux 字节合同测试;未重建语义未变的 golden。
|
||||||
2. 生成当前模型的组件类型、槽位、阶段、副作用和事件能力矩阵。
|
2. **已完成(C-01 结构层):** 生成当前模型的组件类型、槽位、阶段、副作用和事件能力描述;纯数值 kernel 能力矩阵的实现细节继续归入 C-02。
|
||||||
3. 将 schema v2 写成独立规范,先冻结 RHS 阶段、错误、事务、事件和结构签名。
|
3. **已完成:** 将 schema v2 写成独立规范,并冻结 RHS/Event/Jacobian/Outputs 入口、错误能力、事务、事件、Jacobian 和内容签名。
|
||||||
4. 建立对象引擎与扁平 IR 的逐阶段 Shadow runner。
|
4. 建立对象引擎与扁平 IR 的逐阶段 Shadow runner。
|
||||||
5. 用一条完整机械—气动—管路—接触支路完成 Python 参考闭环。
|
5. 用一条完整机械—气动—管路—接触支路完成 Python 参考闭环。
|
||||||
6. 评审通过后,再建立最小 C ABI 和纵向切片。
|
6. 评审通过后,再建立最小 C ABI 和纵向切片。
|
||||||
|
|||||||
@@ -0,0 +1,262 @@
|
|||||||
|
# 全系统数值中间表示(System Numeric IR)规范 v2.0
|
||||||
|
|
||||||
|
状态:C-01 已实现并冻结 v2.0 数据合同;当前编译结果为 `reference_only`,原生 kernel 与执行器属于 C-02/C-03 及后续工作。
|
||||||
|
|
||||||
|
适用范围:后端 `GenericFluidSystem` 编译后的整个仿真系统。
|
||||||
|
|
||||||
|
机器可读定义:`schemas/system-numeric-ir-v2.schema.json`。
|
||||||
|
|
||||||
|
## 1. 定位与边界
|
||||||
|
|
||||||
|
System Numeric IR(以下简称 IR)描述“一个已经解析并编译好的系统,数值求解时需要哪些数据、按什么关系执行”。它是系统级合同,不是单个部件的文件。
|
||||||
|
|
||||||
|
```text
|
||||||
|
XML / 建模 JSON
|
||||||
|
→ 模型解析和连接检查
|
||||||
|
→ GenericFluidSystem 对象图
|
||||||
|
→ System IR v2.0
|
||||||
|
→ 参考执行器 / 未来原生执行器
|
||||||
|
```
|
||||||
|
|
||||||
|
XML 保存用户建立了哪些元件、参数和连线;IR 在此基础上补充求解器真正需要的槽位编号、状态降维、方程块、执行阶段、闭合范围、事务回滚、事件、Jacobian 和输出投影。因此二者看起来相似,但用途和层级不同。
|
||||||
|
|
||||||
|
本版本只完成“完整、确定、可校验的数据合同”和从现有系统生成该合同的编译器。它没有替换当前默认 Python 求解路径,不改变现有仿真结果。IR 中禁止保存 Python 函数、闭包、模型对象、对象地址和运行期临时状态。
|
||||||
|
|
||||||
|
## 2. 三个权威来源
|
||||||
|
|
||||||
|
三份实现共同定义 v2.0:
|
||||||
|
|
||||||
|
- `app/simulation/ir/schema.py`:Python 不可变数据类型、枚举、规范序列化和签名算法;
|
||||||
|
- `schemas/system-numeric-ir-v2.schema.json`:跨语言 JSON 结构合同;
|
||||||
|
- `app/simulation/ir/validation.py`:仅靠 JSON Schema 无法表达的引用、覆盖、拓扑和数值语义校验。
|
||||||
|
|
||||||
|
生产者必须同时满足机器 Schema 和语义校验。字段有增删时必须同步修改三处以及合同测试,不能只更新文档。
|
||||||
|
|
||||||
|
## 3. 版本、兼容性和严格读取
|
||||||
|
|
||||||
|
版本不是顶层整数,而是 `version` 对象:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$type": "schema_version",
|
||||||
|
"schema_id": "system-numeric-ir",
|
||||||
|
"major": 2,
|
||||||
|
"minor": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `major` 改变表示不兼容的字段或执行语义变化;读取方必须拒绝未知主版本。
|
||||||
|
- `minor` 用于同一主版本内向前演进;当前读取方拒绝负数和高于自身能力的次版本。
|
||||||
|
- JSON Schema 对所有合同对象使用 `additionalProperties: false`,v2.0 读取方不会静默忽略未知字段或枚举值。
|
||||||
|
- 原生二进制 ABI 不写入 `SystemIR`,而由独立的 `IRNativeBuildIdentity.abi_version` 管理,当前值为 `1`。
|
||||||
|
- 压力流量中的 `causal_plans.source_schema_version == 1` 只表示其来源是既有 causal IR v1;它不是完整系统 IR 的版本,也不能携带 v1 的 Python 回调。
|
||||||
|
|
||||||
|
## 4. 线格式和顶层结构
|
||||||
|
|
||||||
|
每个 dataclass 序列化后都带有稳定的 `$type`;操作对象还带有 `opcode`。顶层 `$type` 为 `system_ir`,其字段完整集合如下:
|
||||||
|
|
||||||
|
| 字段 | 含义 |
|
||||||
|
| --- | --- |
|
||||||
|
| `version` | IR schema 身份与版本 |
|
||||||
|
| `model_id` / `model_version` | 输入系统的稳定身份和调用方提供的模型版本 |
|
||||||
|
| `compiler_id` / `compiler_version` | 产生 IR 的编译器身份,当前为 `generic-fluid-system` / `2.0.0` |
|
||||||
|
| `numeric_dtype` | 主数值类型,v2.0 只接受 `float64` |
|
||||||
|
| `buffers` / `values` | 连续缓冲区及每一个数值槽位的元数据 |
|
||||||
|
| `kernels` / `components` | kernel 声明与元件实例绑定 |
|
||||||
|
| `mediums` | 介质实现、介质常量和使用该介质的元件 |
|
||||||
|
| `ports` / `connections` | 端口变量和系统拓扑 |
|
||||||
|
| `state_reducer` | 求解器状态与元件局部状态/导数的线性映射 |
|
||||||
|
| `causal_plans` / `pressure_flow` | 因果子计划和完整压力流量方程计划 |
|
||||||
|
| `stream_plans` / `thermofluid` | stream SCC/DAG 与热流体外层闭合计划 |
|
||||||
|
| `stages` / `blocks` | 无回调操作阶段与复合/迭代执行块 |
|
||||||
|
| `entry_points` | `rhs`、`events`、`jacobian`、`outputs` 四个入口 |
|
||||||
|
| `transaction` | 试算快照、流量恢复和参考缓存诊断 |
|
||||||
|
| `modes` / `events` | 离散模式、根函数、reset 与缓存失效 |
|
||||||
|
| `jacobian` | 固定 CSR 结构、着色和局部有限差分计划 |
|
||||||
|
| `outputs` | 结果元数据和投影 |
|
||||||
|
| `capabilities` | 系统和元件的原生可执行能力及缺口 |
|
||||||
|
| `required_features` | 读取/执行该程序必须理解的功能 ID |
|
||||||
|
|
||||||
|
所有列表的顺序都是合同的一部分。引用统一采用数组索引或稳定 ID,不能依赖哈希表遍历顺序。
|
||||||
|
|
||||||
|
## 5. 规范序列化与内容签名
|
||||||
|
|
||||||
|
`canonical_json_bytes()` 是跨平台唯一线表示:
|
||||||
|
|
||||||
|
- UTF-8,ASCII 转义开启,JSON 键排序,无无意义空白;
|
||||||
|
- 字符串先做 Unicode NFC 规范化;
|
||||||
|
- tuple 写成 JSON array,不接受 list、dict、set 或任意对象;
|
||||||
|
- 浮点数写成 IEEE-754 binary64 大端十六进制对象,例如 `{"$float64":"3ff0000000000000"}`;
|
||||||
|
- `-0.0` 统一为 `+0.0`,NaN 和正负无穷直接拒绝;
|
||||||
|
- 枚举写成规范字符串,整数和布尔值保持其 JSON 类型。
|
||||||
|
|
||||||
|
`SystemIR.structural_signature` 是上述完整 `SystemIR` 内容字节的 SHA-256 小写十六进制值。它准确回答“这份 IR 内容是否完全相同”,包含参数值和所有计划,因此不声称不同表达形式的数学系统会得到同一签名。操作系统、机器路径、构建时间、编译器和 native flags 不进入这个签名。
|
||||||
|
|
||||||
|
## 6. 缓冲区、槽位和值
|
||||||
|
|
||||||
|
槽位引用的格式为 `{"$type":"slot_ref","buffer":"...","index":N}`。v2.0 恰好声明以下 16 类缓冲区,每类一次:
|
||||||
|
|
||||||
|
| dtype | 缓冲区 |
|
||||||
|
| --- | --- |
|
||||||
|
| `float64` | `time`、`state_input`、`derivative_output`、`local_state`、`local_derivative`、`algebraic`、`signal`、`parameter`、`constant`、`work_float`、`event_output`、`jacobian_value`、`result_output`、`runtime_input` |
|
||||||
|
| `int32` | `mode`、`work_int` |
|
||||||
|
|
||||||
|
`time` 的长度必须为 1;`int32` 初值必须在有符号 32 位范围内。每个缓冲区内索引为 `[0, size)`,而且每一个实际槽位必须恰好有一个 `IRValueSpec`。值描述包含稳定 ID、语义、角色、物理量、单位、缩放、可选上下界和可选所属元件。缩放必须为正有限数,边界必须有序且有限。
|
||||||
|
|
||||||
|
缓冲区是执行器唯一的数值寻址合同。名称用于诊断,不允许执行器重新用名称查找取代槽位访问。
|
||||||
|
|
||||||
|
## 7. Kernel 与元件绑定
|
||||||
|
|
||||||
|
`IRKernelSpec` 声明模型类型、模型版本、实现版本、能力、支持的 phase 以及参数/状态/mode/workspace 数量。phase 枚举为:
|
||||||
|
|
||||||
|
`primal`、`residual`、`derivative`、`property`、`event`、`reset`、`jacobian`、`output`。
|
||||||
|
|
||||||
|
phase 在 C-01 中只是稳定的功能标签。不同模型在同一 phase 下可能有不同输入输出数量,所以不能在 phase 上填写虚假的统一 arity。当前每个 `IRKernelCallOperation` 自身的有序 `read_slots`、`write_slots` 和 `equation_indices` 才是该次调用的权威依赖合同。C-02 将在此基础上冻结每个 `kernel_id + phase` 的纯数值调用签名并验证所有调用实例一致。
|
||||||
|
|
||||||
|
`IRComponentInstance` 把一个元件实例绑定到 kernel,并明确列出参数、局部状态、局部导数、mode、端口、输出和两类 workspace。绑定数量必须与 kernel 声明一致;端口和输出必须与其反向所属关系精确一致。
|
||||||
|
|
||||||
|
当前编译器把所有既有 Python kernel 标记为 `reference_only`。为便于结构审计,reference kernel 调用声明了保守读集合:可能多读,但不能漏掉模型对象当前可见的数值输入。Python 内部隐藏缓存仍不是原生槽位,因而任何含这类依赖的程序都不得宣称 `native`。
|
||||||
|
|
||||||
|
## 8. 介质、端口和连接
|
||||||
|
|
||||||
|
介质记录稳定 `medium_id`、名称、实现及其版本、常量槽位和使用它的元件索引。介质参数必须位于 `constant` 缓冲区。
|
||||||
|
|
||||||
|
端口分为 `physical` 与 `signal`:
|
||||||
|
|
||||||
|
- 物理端口必须声明正流方向,当前统一为 `intoComponent`;
|
||||||
|
- signal 端口不得声明物理流向;
|
||||||
|
- 变量角色与连接规则固定对应:`effort → equal`、`flow → sumToZero`、`stream → streamMix`、`signal → directed`。
|
||||||
|
|
||||||
|
连接必须引用两个已声明、不同、同 kind/同 domain 的端口,并精确覆盖两个端口的全部同名变量合同。物理端口和 signal 输入最多被一条连接占用;signal 输出允许扇出到多个输入。禁止重复端点对和悬空索引。
|
||||||
|
|
||||||
|
## 9. 状态降维与导数汇总
|
||||||
|
|
||||||
|
`IRStateReducer` 使求解器的 `state_input` 与各元件 `local_state` 分离。`state_reducer.initial_state` 是状态初值的语义描述,必须与 `state_input` 缓冲区的初值逐项完全相同,避免消费者面对两个不同初值:
|
||||||
|
|
||||||
|
- `state_scatter` 用 CSR 矩阵把求解器状态散射到有序局部状态槽位;
|
||||||
|
- `derivative_gather` 把有序局部导数汇总为 `derivative_output`;
|
||||||
|
- `initial_state` 与 `absolute_tolerances` 按求解器状态顺序定义。
|
||||||
|
|
||||||
|
这能显式表达共享机械坐标和气动储能状态的降维关系。例如同一气动储能状态可以按体积权重散射到多个局部状态,而不是由执行器临时按对象身份猜测。两个矩阵必须满足 CSR 不变量、维度和值数量合同,并覆盖全部组件状态/导数绑定。
|
||||||
|
|
||||||
|
## 10. 压力—流量计划与因果元数据
|
||||||
|
|
||||||
|
`IRPressureFlowPlan` 包含:
|
||||||
|
|
||||||
|
- `unknowns`:未知量的元件、端口、变量角色、槽位、缩放和边界;
|
||||||
|
- `equations`:元件或连接拥有的方程、关系、涉及槽位、残差槽位和缩放;
|
||||||
|
- `blocks`:未知量/方程的方块分解及每块 Jacobian CSR 结构;
|
||||||
|
- `scopes`:全网、敏感物理岛或方程块作用域及求解限制;
|
||||||
|
- `global_scope_index` 和 `secondary_scope_indices`:第一次全网求解与后续局部重算范围;
|
||||||
|
- `pressure_lower_bound`:全局压力下界。
|
||||||
|
|
||||||
|
全局 scope 必须覆盖完整网络;每个 scope 的未知量和方程必须等于它包含的 blocks 之并集;secondary scope 唯一且不能包含 global scope。每条方程必须拥有唯一的 residual 槽位,防止两个残差互相覆盖。`sparse_pattern_trusted=false` 时必须给出回退原因,可信结构则不得携带回退原因。
|
||||||
|
|
||||||
|
`IRCausalPlan` 保存当前 causal IR v1 编译得到的无回调元数据,包括作用域、规范/兼容/重置槽位、外部 effort、effort 阶段和 flow 阶段。它只作为 v2 压力流量计划的一部分,不代替完整系统计划。
|
||||||
|
|
||||||
|
## 11. 操作、阶段、执行块和四个入口
|
||||||
|
|
||||||
|
v2.0 的无回调 opcode 为:
|
||||||
|
|
||||||
|
`fill`、`copy`、`scatter`、`linear_combination`、`state_map`、`kernel_call`、`effort_broadcast`、`flow_assign`、`check_finite`。
|
||||||
|
|
||||||
|
`IRStage` 给出 stage kind、操作序列以及声明的读/写集合;声明集合必须与操作读写并集完全一致。`IRExecutionBlock` 可以按顺序引用 stage 或其他 block;引用图必须无环。`fixed_point` 和 `stream_scc` block 必须声明监控槽位、绝对/相对容差、最大迭代、松弛、回滚槽位和失败策略,其他 block 禁止携带收敛合同。
|
||||||
|
|
||||||
|
四个入口必须恰好各一个,且入口输入统一按 `time`、完整 `state_input`、完整 `runtime_input` 排列:
|
||||||
|
|
||||||
|
| 入口 | 必须到达的结果阶段 | 精确输出缓冲区 | 不允许夹带 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `rhs` | `derivative_reduce` | 全部 `derivative_output` | event、Jacobian、output、reset |
|
||||||
|
| `events` | `event` | 按事件顺序的全部 `event_output` 根槽位 | derivative reduce、Jacobian、output、reset |
|
||||||
|
| `jacobian` | `jacobian` | 按 CSR 顺序的全部 `jacobian_value` | event、output、reset |
|
||||||
|
| `outputs` | `output` | 按结果顺序的全部 `result_output` | event、Jacobian、reset |
|
||||||
|
|
||||||
|
入口可以复用前置 primal 阶段,但不能把四个入口合并成“每次 RHS 都把事件、Jacobian 和全部输出计算一遍”。入口执行不得写入 `state_input`、`parameter`、`constant` 或 `mode` 等持久输入。
|
||||||
|
|
||||||
|
## 12. Stream、热流体闭合和事务
|
||||||
|
|
||||||
|
`IRStreamPlan` 显式列出 stream 节点、强连通分量(SCC)、SCC 间缩点 DAG 和拓扑顺序。每个 SCC 对应一个 `stream_scc` block,循环只在 SCC 内迭代;监控槽位必须覆盖该 SCC 节点。
|
||||||
|
|
||||||
|
`IRThermofluidPlan` 覆盖全部物理端口,关联 stream plan、全局元件集合、敏感元件、secondary 压力 scope、最大迭代和流量相对容差。是否使用保守全网求解及原因必须成对出现,避免执行器静默扩大作用域。
|
||||||
|
|
||||||
|
`IRTransactionPlan` 冻结一次试探计算需要快照和恢复的端口变量,并单列实际活动气动端口上的 `m_flow`。当前目标系统中的这部分数量为 232;机械模型对象里没有作为活动端口变量出现的隐藏 `m_flow` 字段不会被误算进该集合。所有失败试算必须恢复快照;正常成功返回即为隐式提交,不另设可被误排序的 commit opcode。
|
||||||
|
|
||||||
|
`cache_component_indices`、`cache_attribute_ids` 和 `diagnostic_owner_ids` 只记录当前 Python 参考路径中仍需关注的隐藏副作用,供 C-02/C-03 清除和 Shadow 诊断;它们不是原生内存布局。存在 opaque Python cache 属性的系统不能标记为 `native`。
|
||||||
|
|
||||||
|
## 13. 模式、事件和 Reset
|
||||||
|
|
||||||
|
每个 `IRModeSpec` 记录 int32 mode 槽位、所属元件、合法值及初值。模式槽位必须全部且只被一个 mode 说明,组件 mode 绑定与 owner 关系必须双向覆盖。
|
||||||
|
|
||||||
|
每个 `IREventSpec` 记录稳定事件 ID、事件类型、owner、根槽位、触发方向、终止性、优先级、mode guard、reset steps、失效缓存种类以及是否重启积分器。reset 只能引用 `reset` stage,事件根槽位必须精确覆盖 `event_output` 缓冲区。
|
||||||
|
|
||||||
|
当前 IR 已能表达现有元件暴露的事件和模式结构;仍隐藏在 Python 信号求解或机械密集输出逻辑中的行为属于 `reference_only` 能力缺口,必须在 C-02/C-08 显式化后才能原生执行。
|
||||||
|
|
||||||
|
## 14. Jacobian 合同
|
||||||
|
|
||||||
|
`IRJacobianPlan` 包含固定 CSR pattern、与非零项一一对应的 `value_slots`、颜色组、填充值步骤、解析 value 索引和局部有限差分列。
|
||||||
|
|
||||||
|
CSR 必须满足:`row_pointers` 长度为行数加一、首项为 0、单调不减、末项等于非零项数量;每行列索引递增、唯一且在范围内。颜色组中的列不能共享同一潜在非零行,列不能重复着色。解析项和有限差分项不得重复或越界;每个有限差分列只能填写该列在 CSR 中确实存在的 value 索引,步长必须为正有限数。Jacobian 入口的执行步骤必须与 `fill_steps` 完全一致。
|
||||||
|
|
||||||
|
结构可以保守地多报潜在非零项,但不能漏报可能依赖。结构、模式布局或 kernel 实现改变会自然改变整份 IR 内容签名。
|
||||||
|
|
||||||
|
## 15. 输出合同
|
||||||
|
|
||||||
|
每个 `IROutputSpec` 包含稳定 output ID、所属元件、scope、可选端口名、内部名、显示标签、类别、物理量、单位、局部顺序、来源槽位、结果槽位以及线性 scale/offset。
|
||||||
|
|
||||||
|
`output_id` 和 `result_output` 槽位在全系统唯一。`order` 只在 `(component_index, scope, port_name)` 内排序,因此不同元件出现相同 `order` 是合法的;全局最终列顺序由 `outputs` 数组顺序确定。组件的 `output_indices` 必须精确反向覆盖其所有输出。
|
||||||
|
|
||||||
|
## 16. 能力报告与拒绝规则
|
||||||
|
|
||||||
|
能力级别只有:
|
||||||
|
|
||||||
|
- `native`:所有 kernel phase、状态、事件、事务和缓存都满足原生合同;
|
||||||
|
- `reference_only`:数学/结构已描述,但至少一个阶段仍依赖 Python 参考实现;
|
||||||
|
- `unsupported`:当前 IR 无法安全表达或执行,必须带 error 级能力问题。
|
||||||
|
|
||||||
|
每个元件必须恰好有一条 capability,列出支持 phase 与缺失 feature。系统为 `native` 时所有元件和 kernels 都必须是 native,且不能依赖 opaque Python cache;系统含任意 reference-only 元件时不能伪装为 native。能力问题具有 code、severity、scope ID 和消息,相同 code/scope 不得重复。
|
||||||
|
|
||||||
|
当前 `compile_system_ir()` 的输出明确为 `reference_only`,原因是 C-02 的纯数值 kernel 签名与 C-03 的扁平参考执行器尚未完成。这不是 IR 编译失败,也不允许 native loader 越过能力报告运行。标为 native 的系统还必须覆盖所有实际调用 phase,并且不得要求 `reference_kernel_dispatch`。
|
||||||
|
|
||||||
|
## 17. 原生构建产物键
|
||||||
|
|
||||||
|
二进制缓存身份与 IR 内容签名严格分离。`native_artifact_key(program, build)` 对以下信息再次做规范序列化和 SHA-256:
|
||||||
|
|
||||||
|
- `program.structural_signature`;
|
||||||
|
- native ABI 版本;
|
||||||
|
- target triple;
|
||||||
|
- 编译器 ID 与版本;
|
||||||
|
- 有序编译 flags;
|
||||||
|
- 浮点策略;
|
||||||
|
- kernel 库 SHA-256 签名。
|
||||||
|
|
||||||
|
ABI 必须等于当前支持值,字符串不能为空,flags 不能含空项,kernel 库签名必须为 64 位小写十六进制。这样相同 IR 在 Windows/Linux 上具有相同内容签名,但得到不同且安全的 native artifact key。
|
||||||
|
|
||||||
|
## 18. 编译、校验和消费流程
|
||||||
|
|
||||||
|
当前公开入口为:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app.simulation.ir import compile_system_ir, require_valid_system_ir
|
||||||
|
|
||||||
|
program = compile_system_ir(system, model_version="...")
|
||||||
|
require_valid_system_ir(program)
|
||||||
|
payload = program.canonical_json_bytes()
|
||||||
|
signature = program.structural_signature
|
||||||
|
```
|
||||||
|
|
||||||
|
`compile_system_ir()` 接收已完成解析和系统构建的 `GenericFluidSystem`,不直接解析 XML。消费者必须先验证,再根据 `capabilities.system_level` 选择参考路径或未来原生路径;不得把“JSON Schema 能读取”误当成“具备 native 执行能力”。
|
||||||
|
|
||||||
|
静态校验采用 fail-closed 策略,覆盖:版本与 required feature、全部槽位、数值范围、组件/kernel arity、端口/连接、介质、状态映射、压力流量方程与 scope、阶段读写、block 无环、四入口切片、stream/热流体、事务、mode/event/reset、Jacobian、输出及能力一致性。`require_valid_system_ir()` 聚合错误后拒绝程序。
|
||||||
|
|
||||||
|
## 19. C-01 验收边界与后续工作
|
||||||
|
|
||||||
|
C-01 的完成标准是:
|
||||||
|
|
||||||
|
- 能从当前目标复杂模型和历史 0.81 s 模型生成完整系统级结构;
|
||||||
|
- 同一系统重复编译得到字节完全相同的 canonical JSON 和签名;
|
||||||
|
- 换行方式、Python 哈希种子和目标平台不会污染 IR 内容身份;
|
||||||
|
- 故意破坏引用、覆盖、CSR、事务、入口或能力合同会被拒绝;
|
||||||
|
- IR 中没有 callback、对象地址或任意 Python 对象;
|
||||||
|
- 默认 Python 仿真路径保持不变。
|
||||||
|
|
||||||
|
C-01 不等于已经拥有可运行的 C 后端。下一步 C-02 要冻结每个 kernel 的纯数值签名、隐藏缓存和错误码;C-03 要用扁平 Python 执行器逐槽 Shadow 对照;完成这两项后,才可以建立 C ABI、原生执行器并逐步把 capability 从 `reference_only` 提升为 `native`。
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://systemsimulationapp.local/schema/system-numeric-ir-v2.schema.json",
|
||||||
|
"title": "System Numeric IR v2",
|
||||||
|
"description": "Schema for the callback-free canonical JSON emitted by app.simulation.ir.schema.SystemIR.canonical_json_bytes().",
|
||||||
|
"$ref": "#/$defs/system_ir",
|
||||||
|
"$defs": {
|
||||||
|
"float64": {"type":"object","additionalProperties":false,"required":["$float64"],"properties":{"$float64":{"type":"string","pattern":"^(?!8000000000000000$)(?!(?:7ff|fff)[0-9a-f]{13}$)[0-9a-f]{16}$"}}},
|
||||||
|
"str": {"type":"string"},
|
||||||
|
"int": {"type":"integer"},
|
||||||
|
"int32": {"type":"integer","minimum":-2147483648,"maximum":2147483647},
|
||||||
|
"nonnegative_int": {"type":"integer","minimum":0},
|
||||||
|
"positive_int": {"type":"integer","minimum":1},
|
||||||
|
"bool": {"type":"boolean"},
|
||||||
|
"float_or_null": {"anyOf":[{"$ref":"#/$defs/float64"},{"type":"null"}]},
|
||||||
|
"str_or_null": {"anyOf":[{"type":"string"},{"type":"null"}]},
|
||||||
|
"slot_ref": {"type":"object","additionalProperties":false,"required":["$type","buffer","index"],"properties":{"$type":{"const":"slot_ref"},"buffer":{"type":"string","enum":["time","state_input","derivative_output","local_state","local_derivative","algebraic","signal","parameter","constant","mode","work_float","work_int","event_output","jacobian_value","result_output","runtime_input"]},"index":{"$ref":"#/$defs/nonnegative_int"}}},
|
||||||
|
"schema_version": {"type":"object","additionalProperties":false,"required":["$type","schema_id","major","minor"],"properties":{"$type":{"const":"schema_version"},"schema_id":{"const":"system-numeric-ir"},"major":{"const":2},"minor":{"const":0}}},
|
||||||
|
"buffer": {"type":"object","additionalProperties":false,"required":["$type","kind","dtype","size","initial_float_values","initial_int_values"],"properties":{"$type":{"const":"buffer"},"kind":{"type":"string","enum":["time","state_input","derivative_output","local_state","local_derivative","algebraic","signal","parameter","constant","mode","work_float","work_int","event_output","jacobian_value","result_output","runtime_input"]},"dtype":{"type":"string","enum":["float64","int32"]},"size":{"$ref":"#/$defs/nonnegative_int"},"initial_float_values":{"type":"array","items":{"$ref":"#/$defs/float64"}},"initial_int_values":{"type":"array","items":{"$ref":"#/$defs/int32"}}}},
|
||||||
|
"value": {"type":"object","additionalProperties":false,"required":["$type","value_id","slot","semantic","role","quantity","unit","scale","lower_bound","upper_bound","owner_component_index"],"properties":{"$type":{"const":"value"},"value_id":{"type":"string"},"slot":{"$ref":"#/$defs/slot_ref"},"semantic":{"type":"string"},"role":{"type":"string"},"quantity":{"type":"string"},"unit":{"type":"string"},"scale":{"$ref":"#/$defs/float64"},"lower_bound":{"$ref":"#/$defs/float_or_null"},"upper_bound":{"$ref":"#/$defs/float_or_null"},"owner_component_index":{"anyOf":[{"$ref":"#/$defs/nonnegative_int"},{"type":"null"}]} }},
|
||||||
|
"kernel_phase": {"type":"object","additionalProperties":false,"required":["$type","phase"],"properties":{"$type":{"const":"kernel_phase"},"phase":{"type":"string","enum":["primal","residual","derivative","property","event","reset","jacobian","output"]}}},
|
||||||
|
"kernel": {"type":"object","additionalProperties":false,"required":["$type","kernel_id","model_type","model_version","implementation_version","availability","unavailable_reason","phases","parameter_count","state_count","mode_count","workspace_float_count","workspace_int_count"],"properties":{"$type":{"const":"kernel"},"kernel_id":{"type":"string"},"model_type":{"type":"string"},"model_version":{"type":"string"},"implementation_version":{"type":"string"},"availability":{"type":"string","enum":["native","reference_only"]},"unavailable_reason":{"$ref":"#/$defs/str_or_null"},"phases":{"type":"array","items":{"$ref":"#/$defs/kernel_phase"}},"parameter_count":{"$ref":"#/$defs/nonnegative_int"},"state_count":{"$ref":"#/$defs/nonnegative_int"},"mode_count":{"$ref":"#/$defs/nonnegative_int"},"workspace_float_count":{"$ref":"#/$defs/nonnegative_int"},"workspace_int_count":{"$ref":"#/$defs/nonnegative_int"}}},
|
||||||
|
"component": {"type":"object","additionalProperties":false,"required":["$type","instance_id","kernel_index","parameter_slots","state_slots","derivative_slots","mode_slots","port_indices","port_slots","output_indices","workspace_float_slots","workspace_int_slots"],"properties":{"$type":{"const":"component"},"instance_id":{"type":"string"},"kernel_index":{"$ref":"#/$defs/nonnegative_int"},"parameter_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"state_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"derivative_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"mode_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"port_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"port_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"output_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"workspace_float_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"workspace_int_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}}}},
|
||||||
|
"port_variable": {"type":"object","additionalProperties":false,"required":["$type","variable_id","name","role","connection_rule","quantity","unit","result_visible","slot"],"properties":{"$type":{"const":"port_variable"},"variable_id":{"type":"string"},"name":{"type":"string"},"role":{"type":"string","enum":["effort","flow","stream","signal"]},"connection_rule":{"type":"string","enum":["equal","sumToZero","streamMix","directed"]},"quantity":{"type":"string"},"unit":{"type":"string"},"result_visible":{"type":"boolean"},"slot":{"$ref":"#/$defs/slot_ref"}}},
|
||||||
|
"port": {"type":"object","additionalProperties":false,"required":["$type","port_id","component_index","name","kind","domain","nominal_role","positive_flow_direction","variables"],"properties":{"$type":{"const":"port"},"port_id":{"type":"string"},"component_index":{"$ref":"#/$defs/nonnegative_int"},"name":{"type":"string"},"kind":{"type":"string","enum":["physical","signal"]},"domain":{"type":"string"},"nominal_role":{"type":"string","enum":["inlet","outlet","bidirectional","input","output"]},"positive_flow_direction":{"anyOf":[{"const":"intoComponent"},{"type":"null"}]},"variables":{"type":"array","items":{"$ref":"#/$defs/port_variable"}}}},
|
||||||
|
"connection_variable": {"type":"object","additionalProperties":false,"required":["$type","name","rule","endpoint_a_slot","endpoint_b_slot"],"properties":{"$type":{"const":"connection_variable"},"name":{"type":"string"},"rule":{"type":"string","enum":["equal","sumToZero","streamMix","directed"]},"endpoint_a_slot":{"$ref":"#/$defs/slot_ref"},"endpoint_b_slot":{"$ref":"#/$defs/slot_ref"}}},
|
||||||
|
"connection": {"type":"object","additionalProperties":false,"required":["$type","connection_id","kind","domain","endpoint_a_port_index","endpoint_b_port_index","variables"],"properties":{"$type":{"const":"connection"},"connection_id":{"type":"string"},"kind":{"type":"string","enum":["physical","signal"]},"domain":{"type":"string"},"endpoint_a_port_index":{"$ref":"#/$defs/nonnegative_int"},"endpoint_b_port_index":{"$ref":"#/$defs/nonnegative_int"},"variables":{"type":"array","items":{"$ref":"#/$defs/connection_variable"}}}},
|
||||||
|
"medium": {"type":"object","additionalProperties":false,"required":["$type","medium_id","name","implementation_id","implementation_version","parameter_slots","component_indices"],"properties":{"$type":{"const":"medium"},"medium_id":{"type":"string"},"name":{"type":"string"},"implementation_id":{"type":"string"},"implementation_version":{"type":"string"},"parameter_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"component_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}}}},
|
||||||
|
"csr_pattern": {"type":"object","additionalProperties":false,"required":["$type","row_count","column_count","row_pointers","column_indices"],"properties":{"$type":{"const":"csr_pattern"},"row_count":{"$ref":"#/$defs/nonnegative_int"},"column_count":{"$ref":"#/$defs/nonnegative_int"},"row_pointers":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"column_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}}}},
|
||||||
|
"csr_matrix": {"type":"object","additionalProperties":false,"required":["$type","pattern","values"],"properties":{"$type":{"const":"csr_matrix"},"pattern":{"$ref":"#/$defs/csr_pattern"},"values":{"type":"array","items":{"$ref":"#/$defs/float64"}}}},
|
||||||
|
"state_reducer": {"type":"object","additionalProperties":false,"required":["$type","solver_state_count","local_state_slots","raw_derivative_slots","state_scatter","derivative_gather","initial_state","absolute_tolerances"],"properties":{"$type":{"const":"state_reducer"},"solver_state_count":{"$ref":"#/$defs/nonnegative_int"},"local_state_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"raw_derivative_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"state_scatter":{"$ref":"#/$defs/csr_matrix"},"derivative_gather":{"$ref":"#/$defs/csr_matrix"},"initial_state":{"type":"array","items":{"$ref":"#/$defs/float64"}},"absolute_tolerances":{"type":"array","items":{"$ref":"#/$defs/float64"}}}},
|
||||||
|
"operation": {"oneOf":[{"$ref":"#/$defs/fill"},{"$ref":"#/$defs/copy"},{"$ref":"#/$defs/scatter"},{"$ref":"#/$defs/linear_combination"},{"$ref":"#/$defs/state_map"},{"$ref":"#/$defs/kernel_call"},{"$ref":"#/$defs/effort_broadcast"},{"$ref":"#/$defs/flow_assign"},{"$ref":"#/$defs/check_finite"}]},
|
||||||
|
"fill": {"type":"object","additionalProperties":false,"required":["$type","opcode","target_slots","value"],"properties":{"$type":{"const":"operation"},"opcode":{"const":"fill"},"target_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"value":{"$ref":"#/$defs/float64"}}},
|
||||||
|
"copy": {"type":"object","additionalProperties":false,"required":["$type","opcode","source_slot","target_slot"],"properties":{"$type":{"const":"operation"},"opcode":{"const":"copy"},"source_slot":{"$ref":"#/$defs/slot_ref"},"target_slot":{"$ref":"#/$defs/slot_ref"}}},
|
||||||
|
"scatter": {"type":"object","additionalProperties":false,"required":["$type","opcode","source_slot","target_slots"],"properties":{"$type":{"const":"operation"},"opcode":{"const":"scatter"},"source_slot":{"$ref":"#/$defs/slot_ref"},"target_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}}}},
|
||||||
|
"linear_combination": {"type":"object","additionalProperties":false,"required":["$type","opcode","source_slots","weights","target_slot","bias"],"properties":{"$type":{"const":"operation"},"opcode":{"const":"linear_combination"},"source_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"weights":{"type":"array","items":{"$ref":"#/$defs/float64"}},"target_slot":{"$ref":"#/$defs/slot_ref"},"bias":{"$ref":"#/$defs/float64"}}},
|
||||||
|
"state_map": {"type":"object","additionalProperties":false,"required":["$type","opcode","map_kind","source_slots","target_slots"],"properties":{"$type":{"const":"operation"},"opcode":{"const":"state_map"},"map_kind":{"type":"string","enum":["scatter","derivative_gather"]},"source_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"target_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}}}},
|
||||||
|
"kernel_call": {"type":"object","additionalProperties":false,"required":["$type","opcode","kernel_index","component_index","phase","read_slots","write_slots","equation_indices"],"properties":{"$type":{"const":"operation"},"opcode":{"const":"kernel_call"},"kernel_index":{"$ref":"#/$defs/nonnegative_int"},"component_index":{"anyOf":[{"$ref":"#/$defs/nonnegative_int"},{"type":"null"}]},"phase":{"type":"string","enum":["primal","residual","derivative","property","event","reset","jacobian","output"]},"read_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"write_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"equation_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}}}},
|
||||||
|
"effort_broadcast": {"type":"object","additionalProperties":false,"required":["$type","opcode","variable","anchor_slot","residual_slot","result_slot","scatter_slots","equation_id","lower_bound","upper_bound"],"properties":{"$type":{"const":"operation"},"opcode":{"const":"effort_broadcast"},"variable":{"type":"string"},"anchor_slot":{"$ref":"#/$defs/slot_ref"},"residual_slot":{"$ref":"#/$defs/slot_ref"},"result_slot":{"$ref":"#/$defs/slot_ref"},"scatter_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"equation_id":{"type":"string"},"lower_bound":{"$ref":"#/$defs/float_or_null"},"upper_bound":{"$ref":"#/$defs/float_or_null"}}},
|
||||||
|
"flow_assign": {"type":"object","additionalProperties":false,"required":["$type","opcode","value_slot","result_slot","scatter_slots","equation_id"],"properties":{"$type":{"const":"operation"},"opcode":{"const":"flow_assign"},"value_slot":{"$ref":"#/$defs/slot_ref"},"result_slot":{"$ref":"#/$defs/slot_ref"},"scatter_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"equation_id":{"type":"string"}}},
|
||||||
|
"check_finite": {"type":"object","additionalProperties":false,"required":["$type","opcode","slots","error_code"],"properties":{"$type":{"const":"operation"},"opcode":{"const":"check_finite"},"slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"error_code":{"type":"string"}}},
|
||||||
|
"stage": {"type":"object","additionalProperties":false,"required":["$type","stage_id","kind","operations","declared_read_slots","declared_write_slots"],"properties":{"$type":{"const":"stage"},"stage_id":{"type":"string"},"kind":{"type":"string","enum":["state_reduce","signal","mechanical_equivalence","dynamic_volume","property","pressure_flow","stream","temperature_reference","thermofluid_fixed_point","mechanical_acceleration","derivative_reduce","event","jacobian","output","reset"]},"operations":{"type":"array","items":{"$ref":"#/$defs/operation"}},"declared_read_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"declared_write_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}}}},
|
||||||
|
"step_ref": {"type":"object","additionalProperties":false,"required":["$type","kind","index"],"properties":{"$type":{"const":"step_ref"},"kind":{"type":"string","enum":["stage","block"]},"index":{"$ref":"#/$defs/nonnegative_int"}}},
|
||||||
|
"convergence": {"type":"object","additionalProperties":false,"required":["$type","monitor_slots","absolute_tolerance","relative_tolerance","max_iterations","relaxation","rollback_slots","failure_policy"],"properties":{"$type":{"const":"convergence"},"monitor_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"absolute_tolerance":{"$ref":"#/$defs/float64"},"relative_tolerance":{"$ref":"#/$defs/float64"},"max_iterations":{"$ref":"#/$defs/positive_int"},"relaxation":{"$ref":"#/$defs/float64"},"rollback_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"failure_policy":{"type":"string","enum":["fail","retry_smaller_step"]}}},
|
||||||
|
"block": {"type":"object","additionalProperties":false,"required":["$type","block_id","kind","steps","convergence"],"properties":{"$type":{"const":"block"},"block_id":{"type":"string"},"kind":{"type":"string","enum":["sequence","fixed_point","stream_scc"]},"steps":{"type":"array","items":{"$ref":"#/$defs/step_ref"}},"convergence":{"anyOf":[{"$ref":"#/$defs/convergence"},{"type":"null"}]} }},
|
||||||
|
"entry_point": {"type":"object","additionalProperties":false,"required":["$type","kind","steps","input_slots","output_slots"],"properties":{"$type":{"const":"entry_point"},"kind":{"type":"string","enum":["rhs","events","jacobian","outputs"]},"steps":{"type":"array","items":{"$ref":"#/$defs/step_ref"}},"input_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"output_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}}}},
|
||||||
|
"causal_effort_stage": {"type":"object","additionalProperties":false,"required":["$type","variable","stage_index"],"properties":{"$type":{"const":"causal_effort_stage"},"variable":{"type":"string"},"stage_index":{"$ref":"#/$defs/nonnegative_int"}}},
|
||||||
|
"causal_plan": {"type":"object","additionalProperties":false,"required":["$type","plan_id","scope_component_indices","source_schema_version","source_structural_signature","fallback_reason","canonical_slots","compatibility_slots","reset_slots","external_effort_slots","effort_stages","flow_stage_indices"],"properties":{"$type":{"const":"causal_plan"},"plan_id":{"type":"string"},"scope_component_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"source_schema_version":{"$ref":"#/$defs/nonnegative_int"},"source_structural_signature":{"$ref":"#/$defs/str_or_null"},"fallback_reason":{"$ref":"#/$defs/str_or_null"},"canonical_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"compatibility_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"reset_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"external_effort_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"effort_stages":{"type":"array","items":{"$ref":"#/$defs/causal_effort_stage"}},"flow_stage_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}}}},
|
||||||
|
"algebraic_unknown": {"type":"object","additionalProperties":false,"required":["$type","unknown_id","component_index","port_index","variable","role","slot","scale","lower_bound","upper_bound"],"properties":{"$type":{"const":"algebraic_unknown"},"unknown_id":{"type":"string"},"component_index":{"$ref":"#/$defs/nonnegative_int"},"port_index":{"$ref":"#/$defs/nonnegative_int"},"variable":{"type":"string"},"role":{"type":"string","enum":["effort","flow","stream","signal"]},"slot":{"$ref":"#/$defs/slot_ref"},"scale":{"$ref":"#/$defs/float64"},"lower_bound":{"$ref":"#/$defs/float_or_null"},"upper_bound":{"$ref":"#/$defs/float_or_null"}}},
|
||||||
|
"pressure_flow_equation": {"type":"object","additionalProperties":false,"required":["$type","equation_id","owner","owner_index","relation","role","variable_slots","residual_slot","scale"],"properties":{"$type":{"const":"pressure_flow_equation"},"equation_id":{"type":"string"},"owner":{"type":"string","enum":["component","connection"]},"owner_index":{"$ref":"#/$defs/nonnegative_int"},"relation":{"type":"string","enum":["equal","sumToZero","constitutive","state"]},"role":{"anyOf":[{"type":"string","enum":["effort","flow","stream","signal"]},{"type":"null"}]},"variable_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"residual_slot":{"$ref":"#/$defs/slot_ref"},"scale":{"$ref":"#/$defs/float64"}}},
|
||||||
|
"pressure_flow_block": {"type":"object","additionalProperties":false,"required":["$type","block_id","unknown_indices","equation_indices","jacobian_pattern"],"properties":{"$type":{"const":"pressure_flow_block"},"block_id":{"type":"string"},"unknown_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"equation_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"jacobian_pattern":{"$ref":"#/$defs/csr_pattern"}}},
|
||||||
|
"pressure_flow_scope": {"type":"object","additionalProperties":false,"required":["$type","scope_id","kind","component_indices","unknown_indices","equation_indices","block_indices","causal_plan_index","residual_tolerance","max_evaluations","sparse_pattern_trusted","sparse_fallback_reason"],"properties":{"$type":{"const":"pressure_flow_scope"},"scope_id":{"type":"string"},"kind":{"type":"string","enum":["network","physical_island","equation_block"]},"component_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"unknown_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"equation_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"block_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"causal_plan_index":{"anyOf":[{"$ref":"#/$defs/nonnegative_int"},{"type":"null"}]},"residual_tolerance":{"$ref":"#/$defs/float64"},"max_evaluations":{"$ref":"#/$defs/positive_int"},"sparse_pattern_trusted":{"type":"boolean"},"sparse_fallback_reason":{"$ref":"#/$defs/str_or_null"}}},
|
||||||
|
"pressure_flow_plan": {"type":"object","additionalProperties":false,"required":["$type","unknowns","equations","blocks","scopes","global_scope_index","secondary_scope_indices","pressure_lower_bound"],"properties":{"$type":{"const":"pressure_flow_plan"},"unknowns":{"type":"array","items":{"$ref":"#/$defs/algebraic_unknown"}},"equations":{"type":"array","items":{"$ref":"#/$defs/pressure_flow_equation"}},"blocks":{"type":"array","items":{"$ref":"#/$defs/pressure_flow_block"}},"scopes":{"type":"array","items":{"$ref":"#/$defs/pressure_flow_scope"}},"global_scope_index":{"$ref":"#/$defs/nonnegative_int"},"secondary_scope_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"pressure_lower_bound":{"$ref":"#/$defs/float64"}}},
|
||||||
|
"stream_scc": {"type":"object","additionalProperties":false,"required":["$type","scc_id","node_slots","block_index"],"properties":{"$type":{"const":"stream_scc"},"scc_id":{"type":"string"},"node_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"block_index":{"$ref":"#/$defs/nonnegative_int"}}},
|
||||||
|
"stream_edge": {"type":"object","additionalProperties":false,"required":["$type","source_scc_index","target_scc_index"],"properties":{"$type":{"const":"stream_edge"},"source_scc_index":{"$ref":"#/$defs/nonnegative_int"},"target_scc_index":{"$ref":"#/$defs/nonnegative_int"}}},
|
||||||
|
"stream_plan": {"type":"object","additionalProperties":false,"required":["$type","plan_id","node_slots","strongly_connected_components","condensed_edges","topological_scc_indices"],"properties":{"$type":{"const":"stream_plan"},"plan_id":{"type":"string"},"node_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"strongly_connected_components":{"type":"array","items":{"$ref":"#/$defs/stream_scc"}},"condensed_edges":{"type":"array","items":{"$ref":"#/$defs/stream_edge"}},"topological_scc_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}}}},
|
||||||
|
"thermofluid_plan": {"type":"object","additionalProperties":false,"required":["$type","physical_port_indices","global_component_indices","stream_plan_index","secondary_pressure_scope_indices","sensitive_component_indices","maximum_iterations","flow_relative_tolerance","uses_conservative_global_solver","conservative_fallback_reason"],"properties":{"$type":{"const":"thermofluid_plan"},"physical_port_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"global_component_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"stream_plan_index":{"$ref":"#/$defs/nonnegative_int"},"secondary_pressure_scope_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"sensitive_component_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"maximum_iterations":{"$ref":"#/$defs/positive_int"},"flow_relative_tolerance":{"$ref":"#/$defs/float64"},"uses_conservative_global_solver":{"type":"boolean"},"conservative_fallback_reason":{"$ref":"#/$defs/str_or_null"}}},
|
||||||
|
"transaction_plan": {"type":"object","additionalProperties":false,"required":["$type","snapshot_slots","flow_slots","cache_component_indices","cache_attribute_ids","diagnostic_owner_ids","restores_on_recoverable_failure","restores_on_fatal_failure"],"properties":{"$type":{"const":"transaction_plan"},"snapshot_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"flow_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"cache_component_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"cache_attribute_ids":{"type":"array","items":{"type":"string"}},"diagnostic_owner_ids":{"type":"array","items":{"type":"string"}},"restores_on_recoverable_failure":{"type":"boolean"},"restores_on_fatal_failure":{"type":"boolean"}}},
|
||||||
|
"mode_value": {"type":"object","additionalProperties":false,"required":["$type","value","name"],"properties":{"$type":{"const":"mode_value"},"value":{"$ref":"#/$defs/int32"},"name":{"type":"string"}}},
|
||||||
|
"mode": {"type":"object","additionalProperties":false,"required":["$type","mode_id","slot","owner_component_indices","values","initial_value"],"properties":{"$type":{"const":"mode"},"mode_id":{"type":"string"},"slot":{"$ref":"#/$defs/slot_ref"},"owner_component_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"values":{"type":"array","items":{"$ref":"#/$defs/mode_value"}},"initial_value":{"$ref":"#/$defs/int32"}}},
|
||||||
|
"finite_difference_column": {"type":"object","additionalProperties":false,"required":["$type","column_index","value_indices","relative_step"],"properties":{"$type":{"const":"finite_difference_column"},"column_index":{"$ref":"#/$defs/nonnegative_int"},"value_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"relative_step":{"$ref":"#/$defs/float64"}}},
|
||||||
|
"jacobian_plan": {"type":"object","additionalProperties":false,"required":["$type","pattern","value_slots","color_groups","fill_steps","analytic_value_indices","local_finite_difference_columns"],"properties":{"$type":{"const":"jacobian_plan"},"pattern":{"$ref":"#/$defs/csr_pattern"},"value_slots":{"type":"array","items":{"$ref":"#/$defs/slot_ref"}},"color_groups":{"type":"array","items":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}}},"fill_steps":{"type":"array","items":{"$ref":"#/$defs/step_ref"}},"analytic_value_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"local_finite_difference_columns":{"type":"array","items":{"$ref":"#/$defs/finite_difference_column"}}}},
|
||||||
|
"mode_guard": {"type":"object","additionalProperties":false,"required":["$type","mode_slot","allowed_values"],"properties":{"$type":{"const":"mode_guard"},"mode_slot":{"$ref":"#/$defs/slot_ref"},"allowed_values":{"type":"array","items":{"$ref":"#/$defs/int32"}}}},
|
||||||
|
"event": {"type":"object","additionalProperties":false,"required":["$type","event_id","event_kind","owner_component_indices","root_slot","direction","terminal","priority","mode_guards","reset_steps","invalidated_caches","restarts_integrator"],"properties":{"$type":{"const":"event"},"event_id":{"type":"string"},"event_kind":{"type":"string"},"owner_component_indices":{"type":"array","items":{"$ref":"#/$defs/nonnegative_int"}},"root_slot":{"$ref":"#/$defs/slot_ref"},"direction":{"type":"string","enum":["decreasing","any","increasing"]},"terminal":{"type":"boolean"},"priority":{"type":"integer"},"mode_guards":{"type":"array","items":{"$ref":"#/$defs/mode_guard"}},"reset_steps":{"type":"array","items":{"$ref":"#/$defs/step_ref"}},"invalidated_caches":{"type":"array","items":{"type":"string","enum":["property","pressure_flow","stream","jacobian","output"]}},"restarts_integrator":{"type":"boolean"}}},
|
||||||
|
"output": {"type":"object","additionalProperties":false,"required":["$type","output_id","component_index","scope","port_name","name","label","category","quantity","unit","order","source_slot","output_slot","scale","offset"],"properties":{"$type":{"const":"output"},"output_id":{"type":"string"},"component_index":{"$ref":"#/$defs/nonnegative_int"},"scope":{"type":"string"},"port_name":{"$ref":"#/$defs/str_or_null"},"name":{"type":"string"},"label":{"type":"string"},"category":{"type":"string"},"quantity":{"type":"string"},"unit":{"type":"string"},"order":{"$ref":"#/$defs/nonnegative_int"},"source_slot":{"$ref":"#/$defs/slot_ref"},"output_slot":{"$ref":"#/$defs/slot_ref"},"scale":{"$ref":"#/$defs/float64"},"offset":{"$ref":"#/$defs/float64"}}},
|
||||||
|
"component_capability": {"type":"object","additionalProperties":false,"required":["$type","component_index","level","supported_phases","missing_features"],"properties":{"$type":{"const":"component_capability"},"component_index":{"$ref":"#/$defs/nonnegative_int"},"level":{"type":"string","enum":["native","reference_only","unsupported"]},"supported_phases":{"type":"array","items":{"type":"string","enum":["primal","residual","derivative","property","event","reset","jacobian","output"]}},"missing_features":{"type":"array","items":{"type":"string"}}}},
|
||||||
|
"capability_issue": {"type":"object","additionalProperties":false,"required":["$type","code","severity","scope_id","message"],"properties":{"$type":{"const":"capability_issue"},"code":{"type":"string"},"severity":{"type":"string","enum":["info","warning","error"]},"scope_id":{"type":"string"},"message":{"type":"string"}}},
|
||||||
|
"capability_report": {"type":"object","additionalProperties":false,"required":["$type","system_level","components","issues"],"properties":{"$type":{"const":"capability_report"},"system_level":{"type":"string","enum":["native","reference_only","unsupported"]},"components":{"type":"array","items":{"$ref":"#/$defs/component_capability"}},"issues":{"type":"array","items":{"$ref":"#/$defs/capability_issue"}}}},
|
||||||
|
"system_ir": {"type":"object","additionalProperties":false,"required":["$type","version","model_id","model_version","compiler_id","compiler_version","numeric_dtype","buffers","values","kernels","components","mediums","ports","connections","state_reducer","causal_plans","pressure_flow","stream_plans","thermofluid","stages","blocks","entry_points","transaction","modes","jacobian","events","outputs","capabilities","required_features"],"properties":{"$type":{"const":"system_ir"},"version":{"$ref":"#/$defs/schema_version"},"model_id":{"type":"string"},"model_version":{"type":"string"},"compiler_id":{"type":"string"},"compiler_version":{"type":"string"},"numeric_dtype":{"const":"float64"},"buffers":{"type":"array","items":{"$ref":"#/$defs/buffer"}},"values":{"type":"array","items":{"$ref":"#/$defs/value"}},"kernels":{"type":"array","items":{"$ref":"#/$defs/kernel"}},"components":{"type":"array","items":{"$ref":"#/$defs/component"}},"mediums":{"type":"array","items":{"$ref":"#/$defs/medium"}},"ports":{"type":"array","items":{"$ref":"#/$defs/port"}},"connections":{"type":"array","items":{"$ref":"#/$defs/connection"}},"state_reducer":{"$ref":"#/$defs/state_reducer"},"causal_plans":{"type":"array","items":{"$ref":"#/$defs/causal_plan"}},"pressure_flow":{"$ref":"#/$defs/pressure_flow_plan"},"stream_plans":{"type":"array","items":{"$ref":"#/$defs/stream_plan"}},"thermofluid":{"$ref":"#/$defs/thermofluid_plan"},"stages":{"type":"array","items":{"$ref":"#/$defs/stage"}},"blocks":{"type":"array","items":{"$ref":"#/$defs/block"}},"entry_points":{"type":"array","items":{"$ref":"#/$defs/entry_point"}},"transaction":{"$ref":"#/$defs/transaction_plan"},"modes":{"type":"array","items":{"$ref":"#/$defs/mode"}},"jacobian":{"$ref":"#/$defs/jacobian_plan"},"events":{"type":"array","items":{"$ref":"#/$defs/event"}},"outputs":{"type":"array","items":{"$ref":"#/$defs/output"}},"capabilities":{"$ref":"#/$defs/capability_report"},"required_features":{"type":"array","items":{"type":"string"}}}}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,767 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import fields, is_dataclass, replace
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from app.main import compile_reactflow_network, compile_system_xml_network
|
||||||
|
from app.simulation.ir import schema as ir_schema
|
||||||
|
from app.simulation.ir import (
|
||||||
|
IRBufferKind,
|
||||||
|
IRCapabilityLevel,
|
||||||
|
IRDType,
|
||||||
|
IREntryPointKind,
|
||||||
|
IRKernelAvailability,
|
||||||
|
IRKernelCallOperation,
|
||||||
|
IRNativeBuildIdentity,
|
||||||
|
IRStageKind,
|
||||||
|
IRStepKind,
|
||||||
|
canonical_json_bytes,
|
||||||
|
compile_system_ir,
|
||||||
|
native_artifact_key,
|
||||||
|
)
|
||||||
|
from app.simulation.ir.validation import (
|
||||||
|
SystemIRValidationError,
|
||||||
|
require_valid_system_ir,
|
||||||
|
validate_system_ir,
|
||||||
|
)
|
||||||
|
from app.simulation.systems.generic import GenericFluidSystem
|
||||||
|
from app.system_xml import validate_system_xml_document
|
||||||
|
from tests.test_amesim_mechanical_xml import zero_force_mass_project
|
||||||
|
|
||||||
|
|
||||||
|
TARGET_XML = Path("tests/data/test-mql-8.xml")
|
||||||
|
HISTORICAL_XML = Path("tests/data/test_mql-full-branches-01-04.xml")
|
||||||
|
MACHINE_SCHEMA = Path("schemas/system-numeric-ir-v2.schema.json")
|
||||||
|
|
||||||
|
|
||||||
|
def _system_from_xml(path: Path) -> GenericFluidSystem:
|
||||||
|
report = validate_system_xml_document(path.read_bytes())
|
||||||
|
if not report.valid or report.document is None:
|
||||||
|
raise AssertionError(report.as_dict())
|
||||||
|
return GenericFluidSystem(compile_system_xml_network(report.document))
|
||||||
|
|
||||||
|
|
||||||
|
def _entry_stage_kinds(program, entry_kind: IREntryPointKind) -> set[IRStageKind]:
|
||||||
|
entry = next(item for item in program.entry_points if item.kind is entry_kind)
|
||||||
|
result: set[IRStageKind] = set()
|
||||||
|
visited_blocks: set[int] = set()
|
||||||
|
|
||||||
|
def visit(step) -> None:
|
||||||
|
if step.kind is IRStepKind.STAGE:
|
||||||
|
result.add(program.stages[step.index].kind)
|
||||||
|
return
|
||||||
|
if step.index in visited_blocks:
|
||||||
|
return
|
||||||
|
visited_blocks.add(step.index)
|
||||||
|
for nested in program.blocks[step.index].steps:
|
||||||
|
visit(nested)
|
||||||
|
|
||||||
|
for step in entry.steps:
|
||||||
|
visit(step)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_callback_free(test: unittest.TestCase, value: object) -> None:
|
||||||
|
if is_dataclass(value) and not isinstance(value, type):
|
||||||
|
for item in fields(value):
|
||||||
|
_assert_callback_free(test, getattr(value, item.name))
|
||||||
|
return
|
||||||
|
if isinstance(value, tuple):
|
||||||
|
for item in value:
|
||||||
|
_assert_callback_free(test, item)
|
||||||
|
return
|
||||||
|
test.assertFalse(callable(value), type(value).__name__)
|
||||||
|
test.assertNotIsInstance(value, (dict, list, set))
|
||||||
|
|
||||||
|
|
||||||
|
class SystemNumericIRV2ContractTests(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls) -> None:
|
||||||
|
cls.system = GenericFluidSystem(
|
||||||
|
compile_reactflow_network(zero_force_mass_project())
|
||||||
|
)
|
||||||
|
cls.program = compile_system_ir(cls.system)
|
||||||
|
|
||||||
|
def test_compiler_returns_a_statically_valid_callback_free_program(self) -> None:
|
||||||
|
report = validate_system_ir(self.program)
|
||||||
|
|
||||||
|
self.assertTrue(report.valid, report.issues)
|
||||||
|
self.assertIs(require_valid_system_ir(self.program), self.program)
|
||||||
|
_assert_callback_free(self, self.program)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
canonical_json_bytes(lambda: None)
|
||||||
|
|
||||||
|
def test_canonical_bytes_and_signature_are_deterministic(self) -> None:
|
||||||
|
second = compile_system_ir(
|
||||||
|
GenericFluidSystem(
|
||||||
|
compile_reactflow_network(zero_force_mass_project())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.program.canonical_json_bytes(),
|
||||||
|
second.canonical_json_bytes(),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.program.structural_signature,
|
||||||
|
second.structural_signature,
|
||||||
|
)
|
||||||
|
self.assertEqual(len(self.program.structural_signature), 64)
|
||||||
|
self.assertEqual(
|
||||||
|
self.program.structural_signature,
|
||||||
|
self.program.calculate_structural_signature(),
|
||||||
|
)
|
||||||
|
|
||||||
|
decomposed = replace(self.program, model_id="e\u0301")
|
||||||
|
composed = replace(self.program, model_id="é")
|
||||||
|
self.assertEqual(
|
||||||
|
decomposed.canonical_json_bytes(),
|
||||||
|
composed.canonical_json_bytes(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parameter_change_invalidates_the_program_signature(self) -> None:
|
||||||
|
changed_project = zero_force_mass_project()
|
||||||
|
changed_project.nodes[1].data.parameters["mass"] = 3.0
|
||||||
|
changed = compile_system_ir(
|
||||||
|
GenericFluidSystem(compile_reactflow_network(changed_project))
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotEqual(
|
||||||
|
self.program.structural_signature,
|
||||||
|
changed.structural_signature,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_native_artifact_key_is_separate_from_structural_signature(self) -> None:
|
||||||
|
windows = IRNativeBuildIdentity(
|
||||||
|
abi_version=1,
|
||||||
|
target_triple="x86_64-pc-windows-msvc",
|
||||||
|
compiler_id="msvc",
|
||||||
|
compiler_version="19.40",
|
||||||
|
compile_flags=("/O2", "/fp:precise"),
|
||||||
|
floating_point_policy="strict",
|
||||||
|
kernel_library_signature="a" * 64,
|
||||||
|
)
|
||||||
|
linux = replace(
|
||||||
|
windows,
|
||||||
|
target_triple="x86_64-unknown-linux-gnu",
|
||||||
|
compiler_id="gcc",
|
||||||
|
compiler_version="14.2",
|
||||||
|
compile_flags=("-O2", "-fno-fast-math"),
|
||||||
|
)
|
||||||
|
|
||||||
|
signature = self.program.structural_signature
|
||||||
|
self.assertNotEqual(
|
||||||
|
native_artifact_key(self.program, windows),
|
||||||
|
native_artifact_key(self.program, linux),
|
||||||
|
)
|
||||||
|
self.assertEqual(self.program.structural_signature, signature)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
native_artifact_key(
|
||||||
|
self.program,
|
||||||
|
replace(windows, abi_version=windows.abi_version + 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_four_entry_points_are_independent(self) -> None:
|
||||||
|
self.assertEqual(
|
||||||
|
{item.kind for item in self.program.entry_points},
|
||||||
|
set(IREntryPointKind),
|
||||||
|
)
|
||||||
|
rhs_kinds = _entry_stage_kinds(self.program, IREntryPointKind.RHS)
|
||||||
|
event_kinds = _entry_stage_kinds(
|
||||||
|
self.program, IREntryPointKind.EVENTS
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn(IRStageKind.DERIVATIVE_REDUCE, rhs_kinds)
|
||||||
|
self.assertNotIn(IRStageKind.EVENT, rhs_kinds)
|
||||||
|
self.assertNotIn(IRStageKind.JACOBIAN, rhs_kinds)
|
||||||
|
self.assertNotIn(IRStageKind.OUTPUT, rhs_kinds)
|
||||||
|
self.assertIn(IRStageKind.EVENT, event_kinds)
|
||||||
|
self.assertNotIn(IRStageKind.JACOBIAN, event_kinds)
|
||||||
|
self.assertNotIn(IRStageKind.OUTPUT, event_kinds)
|
||||||
|
|
||||||
|
def test_validator_rejects_a_missing_entry_point(self) -> None:
|
||||||
|
broken = replace(
|
||||||
|
self.program,
|
||||||
|
entry_points=self.program.entry_points[:-1],
|
||||||
|
)
|
||||||
|
|
||||||
|
report = validate_system_ir(broken)
|
||||||
|
self.assertFalse(report.valid)
|
||||||
|
self.assertIn(
|
||||||
|
"ENTRY_POINT_SET_INVALID",
|
||||||
|
{item.code for item in report.issues},
|
||||||
|
)
|
||||||
|
with self.assertRaises(SystemIRValidationError):
|
||||||
|
require_valid_system_ir(broken)
|
||||||
|
|
||||||
|
def test_validator_rejects_entry_contract_and_buffer_dtype_corruption(self) -> None:
|
||||||
|
rhs_index = next(
|
||||||
|
index
|
||||||
|
for index, entry in enumerate(self.program.entry_points)
|
||||||
|
if entry.kind is IREntryPointKind.RHS
|
||||||
|
)
|
||||||
|
events_entry = next(
|
||||||
|
entry
|
||||||
|
for entry in self.program.entry_points
|
||||||
|
if entry.kind is IREntryPointKind.EVENTS
|
||||||
|
)
|
||||||
|
corrupted_entries = list(self.program.entry_points)
|
||||||
|
corrupted_entries[rhs_index] = replace(
|
||||||
|
corrupted_entries[rhs_index],
|
||||||
|
steps=events_entry.steps,
|
||||||
|
output_slots=(),
|
||||||
|
)
|
||||||
|
entry_report = validate_system_ir(
|
||||||
|
replace(self.program, entry_points=tuple(corrupted_entries))
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
{
|
||||||
|
"ENTRY_POINT_OUTPUT_COVERAGE",
|
||||||
|
"ENTRY_POINT_FINAL_STAGE_MISSING",
|
||||||
|
"ENTRY_POINT_STAGE_FORBIDDEN",
|
||||||
|
}.issubset({item.code for item in entry_report.issues})
|
||||||
|
)
|
||||||
|
|
||||||
|
state_buffer_index = next(
|
||||||
|
index
|
||||||
|
for index, buffer in enumerate(self.program.buffers)
|
||||||
|
if buffer.kind is IRBufferKind.STATE_INPUT
|
||||||
|
)
|
||||||
|
corrupted_buffers = list(self.program.buffers)
|
||||||
|
corrupted_buffers[state_buffer_index] = replace(
|
||||||
|
corrupted_buffers[state_buffer_index],
|
||||||
|
dtype=IRDType.INT32,
|
||||||
|
initial_float_values=(),
|
||||||
|
initial_int_values=tuple(
|
||||||
|
0 for _ in range(corrupted_buffers[state_buffer_index].size)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
dtype_report = validate_system_ir(
|
||||||
|
replace(self.program, buffers=tuple(corrupted_buffers))
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"BUFFER_DTYPE_INVALID",
|
||||||
|
{item.code for item in dtype_report.issues},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_validator_rejects_a_component_call_bound_to_another_kernel(self) -> None:
|
||||||
|
stage_index, operation_index, operation = next(
|
||||||
|
(stage_index, operation_index, operation)
|
||||||
|
for stage_index, stage in enumerate(self.program.stages)
|
||||||
|
for operation_index, operation in enumerate(stage.operations)
|
||||||
|
if isinstance(operation, IRKernelCallOperation)
|
||||||
|
and operation.component_index is not None
|
||||||
|
)
|
||||||
|
wrong_kernel_index = next(
|
||||||
|
index
|
||||||
|
for index in range(len(self.program.kernels))
|
||||||
|
if index != operation.kernel_index
|
||||||
|
)
|
||||||
|
broken_operations = list(self.program.stages[stage_index].operations)
|
||||||
|
broken_operations[operation_index] = replace(
|
||||||
|
operation,
|
||||||
|
kernel_index=wrong_kernel_index,
|
||||||
|
)
|
||||||
|
broken_stages = list(self.program.stages)
|
||||||
|
broken_stages[stage_index] = replace(
|
||||||
|
broken_stages[stage_index],
|
||||||
|
operations=tuple(broken_operations),
|
||||||
|
)
|
||||||
|
|
||||||
|
report = validate_system_ir(
|
||||||
|
replace(self.program, stages=tuple(broken_stages))
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertFalse(report.valid)
|
||||||
|
self.assertIn(
|
||||||
|
"KERNEL_COMPONENT_MISMATCH",
|
||||||
|
{item.code for item in report.issues},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_validator_rejects_native_component_with_missing_called_phases(self) -> None:
|
||||||
|
native_kernels = tuple(
|
||||||
|
replace(
|
||||||
|
kernel,
|
||||||
|
availability=IRKernelAvailability.NATIVE,
|
||||||
|
unavailable_reason=None,
|
||||||
|
)
|
||||||
|
for kernel in self.program.kernels
|
||||||
|
)
|
||||||
|
native_capabilities = tuple(
|
||||||
|
replace(
|
||||||
|
capability,
|
||||||
|
level=IRCapabilityLevel.NATIVE,
|
||||||
|
supported_phases=(),
|
||||||
|
missing_features=(),
|
||||||
|
)
|
||||||
|
for capability in self.program.capabilities.components
|
||||||
|
)
|
||||||
|
broken = replace(
|
||||||
|
self.program,
|
||||||
|
kernels=native_kernels,
|
||||||
|
required_features=tuple(
|
||||||
|
feature
|
||||||
|
for feature in self.program.required_features
|
||||||
|
if feature != "reference_kernel_dispatch"
|
||||||
|
),
|
||||||
|
transaction=replace(
|
||||||
|
self.program.transaction,
|
||||||
|
cache_attribute_ids=(),
|
||||||
|
),
|
||||||
|
capabilities=replace(
|
||||||
|
self.program.capabilities,
|
||||||
|
system_level=IRCapabilityLevel.NATIVE,
|
||||||
|
components=native_capabilities,
|
||||||
|
issues=(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
report = validate_system_ir(broken)
|
||||||
|
|
||||||
|
self.assertFalse(report.valid)
|
||||||
|
self.assertIn(
|
||||||
|
"CAPABILITY_NATIVE_PHASE_MISSING",
|
||||||
|
{item.code for item in report.issues},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_validator_rejects_runtime_types_that_break_the_wire_schema(self) -> None:
|
||||||
|
outputs = list(self.program.outputs)
|
||||||
|
outputs[0] = replace(outputs[0], scale=1)
|
||||||
|
|
||||||
|
report = validate_system_ir(
|
||||||
|
replace(self.program, outputs=tuple(outputs))
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertFalse(report.valid)
|
||||||
|
self.assertIn(
|
||||||
|
"RUNTIME_TYPE_MISMATCH",
|
||||||
|
{item.code for item in report.issues},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_validator_propagates_unsupported_component_to_system_level(self) -> None:
|
||||||
|
capabilities = list(self.program.capabilities.components)
|
||||||
|
capabilities[0] = replace(
|
||||||
|
capabilities[0],
|
||||||
|
level=IRCapabilityLevel.UNSUPPORTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
report = validate_system_ir(
|
||||||
|
replace(
|
||||||
|
self.program,
|
||||||
|
capabilities=replace(
|
||||||
|
self.program.capabilities,
|
||||||
|
components=tuple(capabilities),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertFalse(report.valid)
|
||||||
|
self.assertIn(
|
||||||
|
"CAPABILITY_LEVEL_CONFLICT",
|
||||||
|
{item.code for item in report.issues},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_machine_schema_has_no_dangling_local_references(self) -> None:
|
||||||
|
schema = json.loads(MACHINE_SCHEMA.read_text(encoding="utf-8"))
|
||||||
|
definitions = schema["$defs"]
|
||||||
|
references: list[str] = []
|
||||||
|
pending: list[object] = [schema]
|
||||||
|
while pending:
|
||||||
|
current = pending.pop()
|
||||||
|
if isinstance(current, dict):
|
||||||
|
references.extend(
|
||||||
|
value
|
||||||
|
for key, value in current.items()
|
||||||
|
if key == "$ref" and isinstance(value, str)
|
||||||
|
)
|
||||||
|
pending.extend(current.values())
|
||||||
|
elif isinstance(current, list):
|
||||||
|
pending.extend(current)
|
||||||
|
|
||||||
|
self.assertFalse(
|
||||||
|
{
|
||||||
|
reference
|
||||||
|
for reference in references
|
||||||
|
if reference.startswith("#/$defs/")
|
||||||
|
and reference.removeprefix("#/$defs/") not in definitions
|
||||||
|
}
|
||||||
|
)
|
||||||
|
payload = json.loads(self.program.canonical_json_bytes())
|
||||||
|
self.assertEqual(payload["$type"], "system_ir")
|
||||||
|
self.assertEqual(
|
||||||
|
set(payload),
|
||||||
|
set(definitions["system_ir"]["required"]),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
definitions["kernel_phase"]["required"],
|
||||||
|
["$type", "phase"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_machine_schema_fields_match_every_serialized_dataclass(self) -> None:
|
||||||
|
definitions = json.loads(
|
||||||
|
MACHINE_SCHEMA.read_text(encoding="utf-8")
|
||||||
|
)["$defs"]
|
||||||
|
skipped_types = {"native_build", "native_artifact_key_input"}
|
||||||
|
|
||||||
|
for value_type, canonical_type in ir_schema._CANONICAL_TYPE_NAMES:
|
||||||
|
if canonical_type in skipped_types:
|
||||||
|
continue
|
||||||
|
definition_name = (
|
||||||
|
value_type.opcode.value
|
||||||
|
if canonical_type == "operation"
|
||||||
|
else canonical_type
|
||||||
|
)
|
||||||
|
definition = definitions[definition_name]
|
||||||
|
expected_fields = {"$type", *(item.name for item in fields(value_type))}
|
||||||
|
if canonical_type == "operation":
|
||||||
|
expected_fields.add("opcode")
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
set(definition["required"]),
|
||||||
|
expected_fields,
|
||||||
|
definition_name,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
set(definition["properties"]),
|
||||||
|
expected_fields,
|
||||||
|
definition_name,
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
definition["additionalProperties"],
|
||||||
|
definition_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TargetSystemNumericIRV2Tests(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls) -> None:
|
||||||
|
cls.system = _system_from_xml(TARGET_XML)
|
||||||
|
cls.program = compile_system_ir(cls.system)
|
||||||
|
|
||||||
|
def test_target_model_is_fully_described(self) -> None:
|
||||||
|
program = self.program
|
||||||
|
|
||||||
|
self.assertEqual(len(program.components), 156)
|
||||||
|
self.assertEqual(len(program.ports), 356)
|
||||||
|
self.assertEqual(len(program.connections), 178)
|
||||||
|
self.assertEqual(program.state_reducer.solver_state_count, 132)
|
||||||
|
self.assertEqual(len(program.pressure_flow.unknowns), 776)
|
||||||
|
self.assertEqual(len(program.pressure_flow.equations), 776)
|
||||||
|
self.assertEqual(len(program.outputs), 1784)
|
||||||
|
self.assertEqual(program.jacobian.pattern.row_count, 132)
|
||||||
|
self.assertEqual(program.jacobian.pattern.column_count, 132)
|
||||||
|
self.assertGreater(program.jacobian.pattern.nonzero_count, 132)
|
||||||
|
self.assertLessEqual(len(program.jacobian.color_groups), 132)
|
||||||
|
|
||||||
|
self.assertEqual(len(program.causal_plans), 1)
|
||||||
|
causal = program.causal_plans[0]
|
||||||
|
self.assertEqual(len(causal.canonical_slots), 452)
|
||||||
|
self.assertEqual(len(causal.compatibility_slots), 776)
|
||||||
|
self.assertIsNone(causal.fallback_reason)
|
||||||
|
|
||||||
|
self.assertEqual(len(program.modes), 2)
|
||||||
|
self.assertEqual(len(program.events), 14)
|
||||||
|
self.assertTrue(program.thermofluid.sensitive_component_indices)
|
||||||
|
self.assertEqual(
|
||||||
|
program.thermofluid.secondary_pressure_scope_indices,
|
||||||
|
program.pressure_flow.secondary_scope_indices,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_state_reducer_and_all_buffers_have_complete_index_contracts(self) -> None:
|
||||||
|
reducer = self.program.state_reducer
|
||||||
|
self.assertEqual(
|
||||||
|
(reducer.state_scatter.pattern.row_count,
|
||||||
|
reducer.state_scatter.pattern.column_count),
|
||||||
|
(len(reducer.local_state_slots), reducer.solver_state_count),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
(reducer.derivative_gather.pattern.row_count,
|
||||||
|
reducer.derivative_gather.pattern.column_count),
|
||||||
|
(reducer.solver_state_count, len(reducer.raw_derivative_slots)),
|
||||||
|
)
|
||||||
|
|
||||||
|
by_buffer: dict[IRBufferKind, list[int]] = {
|
||||||
|
buffer.kind: [] for buffer in self.program.buffers
|
||||||
|
}
|
||||||
|
for value in self.program.values:
|
||||||
|
by_buffer[value.slot.buffer].append(value.slot.index)
|
||||||
|
for buffer in self.program.buffers:
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(by_buffer[buffer.kind]),
|
||||||
|
list(range(buffer.size)),
|
||||||
|
buffer.kind,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transaction_tracks_exactly_the_active_pneumatic_flows(self) -> None:
|
||||||
|
pneumatic_flow_slots = {
|
||||||
|
variable.slot
|
||||||
|
for port in self.program.ports
|
||||||
|
if port.kind.value == "physical" and port.domain == "pneumatic"
|
||||||
|
for variable in port.variables
|
||||||
|
if variable.name == "m_flow"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
set(self.program.transaction.flow_slots),
|
||||||
|
pneumatic_flow_slots,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_native_support_is_not_claimed_before_c02(self) -> None:
|
||||||
|
self.assertIs(
|
||||||
|
self.program.capabilities.system_level,
|
||||||
|
IRCapabilityLevel.REFERENCE_ONLY,
|
||||||
|
)
|
||||||
|
self.assertTrue(self.program.capabilities.components)
|
||||||
|
self.assertTrue(
|
||||||
|
all(
|
||||||
|
item.level is IRCapabilityLevel.REFERENCE_ONLY
|
||||||
|
for item in self.program.capabilities.components
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"IR_NATIVE_KERNELS_NOT_DECLARED",
|
||||||
|
{item.code for item in self.program.capabilities.issues},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_validator_rejects_cross_plan_and_sparse_contract_corruption(self) -> None:
|
||||||
|
program = self.program
|
||||||
|
variants: list[tuple[str, object, str]] = []
|
||||||
|
|
||||||
|
components = list(program.components)
|
||||||
|
foreign_port = next(
|
||||||
|
index
|
||||||
|
for index, port in enumerate(program.ports)
|
||||||
|
if port.component_index != 0
|
||||||
|
)
|
||||||
|
components[0] = replace(
|
||||||
|
components[0],
|
||||||
|
port_indices=(*components[0].port_indices, foreign_port),
|
||||||
|
)
|
||||||
|
variants.append(
|
||||||
|
(
|
||||||
|
"component port back-reference",
|
||||||
|
replace(program, components=tuple(components)),
|
||||||
|
"COMPONENT_PORT_COVERAGE",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
variants.append(
|
||||||
|
(
|
||||||
|
"thermofluid scope mismatch",
|
||||||
|
replace(
|
||||||
|
program,
|
||||||
|
thermofluid=replace(
|
||||||
|
program.thermofluid,
|
||||||
|
secondary_pressure_scope_indices=(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"THERMOFLUID_SCOPE_MISMATCH",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
equations = list(program.pressure_flow.equations)
|
||||||
|
equations[1] = replace(
|
||||||
|
equations[1],
|
||||||
|
residual_slot=equations[0].residual_slot,
|
||||||
|
)
|
||||||
|
variants.append(
|
||||||
|
(
|
||||||
|
"pressure-flow residual slot alias",
|
||||||
|
replace(
|
||||||
|
program,
|
||||||
|
pressure_flow=replace(
|
||||||
|
program.pressure_flow,
|
||||||
|
equations=tuple(equations),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"PRESSURE_FLOW_RESIDUAL_SLOT_DUPLICATE",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
buffers = list(program.buffers)
|
||||||
|
state_buffer_index = next(
|
||||||
|
index
|
||||||
|
for index, buffer in enumerate(buffers)
|
||||||
|
if buffer.kind is IRBufferKind.STATE_INPUT
|
||||||
|
)
|
||||||
|
state_values = list(buffers[state_buffer_index].initial_float_values)
|
||||||
|
state_values[0] += 1.0
|
||||||
|
buffers[state_buffer_index] = replace(
|
||||||
|
buffers[state_buffer_index],
|
||||||
|
initial_float_values=tuple(state_values),
|
||||||
|
)
|
||||||
|
variants.append(
|
||||||
|
(
|
||||||
|
"state initial value disagreement",
|
||||||
|
replace(program, buffers=tuple(buffers)),
|
||||||
|
"STATE_INITIAL_VALUE_MISMATCH",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
aliased_values = (
|
||||||
|
program.jacobian.value_slots[0],
|
||||||
|
program.jacobian.value_slots[0],
|
||||||
|
*program.jacobian.value_slots[2:],
|
||||||
|
)
|
||||||
|
variants.append(
|
||||||
|
(
|
||||||
|
"Jacobian value slot alias",
|
||||||
|
replace(
|
||||||
|
program,
|
||||||
|
jacobian=replace(
|
||||||
|
program.jacobian,
|
||||||
|
value_slots=aliased_values,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"JACOBIAN_VALUE_SLOT_COVERAGE",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
variants.append(
|
||||||
|
(
|
||||||
|
"Jacobian color conflict",
|
||||||
|
replace(
|
||||||
|
program,
|
||||||
|
jacobian=replace(
|
||||||
|
program.jacobian,
|
||||||
|
color_groups=(
|
||||||
|
tuple(range(program.state_reducer.solver_state_count)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"JACOBIAN_COLOR_CONFLICT",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fd_columns = list(program.jacobian.local_finite_difference_columns)
|
||||||
|
fd_column = fd_columns[0]
|
||||||
|
wrong_value_index = next(
|
||||||
|
index
|
||||||
|
for index, column in enumerate(
|
||||||
|
program.jacobian.pattern.column_indices
|
||||||
|
)
|
||||||
|
if column != fd_column.column_index
|
||||||
|
)
|
||||||
|
fd_columns[0] = replace(
|
||||||
|
fd_column,
|
||||||
|
value_indices=(wrong_value_index, *fd_column.value_indices[1:]),
|
||||||
|
)
|
||||||
|
variants.append(
|
||||||
|
(
|
||||||
|
"Jacobian finite-difference column mismatch",
|
||||||
|
replace(
|
||||||
|
program,
|
||||||
|
jacobian=replace(
|
||||||
|
program.jacobian,
|
||||||
|
local_finite_difference_columns=tuple(fd_columns),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"JACOBIAN_FD_COLUMN_MISMATCH",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
capabilities = list(program.capabilities.components)
|
||||||
|
capabilities[0] = replace(
|
||||||
|
capabilities[0],
|
||||||
|
level=IRCapabilityLevel.NATIVE,
|
||||||
|
missing_features=(),
|
||||||
|
)
|
||||||
|
variants.append(
|
||||||
|
(
|
||||||
|
"native capability overclaim",
|
||||||
|
replace(
|
||||||
|
program,
|
||||||
|
capabilities=replace(
|
||||||
|
program.capabilities,
|
||||||
|
components=tuple(capabilities),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"CAPABILITY_KERNEL_MISMATCH",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
variants.append(
|
||||||
|
(
|
||||||
|
"duplicate transaction flow",
|
||||||
|
replace(
|
||||||
|
program,
|
||||||
|
transaction=replace(
|
||||||
|
program.transaction,
|
||||||
|
flow_slots=(
|
||||||
|
*program.transaction.flow_slots,
|
||||||
|
program.transaction.flow_slots[0],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"TRANSACTION_DUPLICATE_SLOT",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
modes = list(program.modes)
|
||||||
|
modes[1] = replace(modes[1], slot=modes[0].slot)
|
||||||
|
variants.append(
|
||||||
|
(
|
||||||
|
"duplicate mode slot",
|
||||||
|
replace(program, modes=tuple(modes)),
|
||||||
|
"MODE_SLOT_DUPLICATE",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
gather_pattern = program.state_reducer.derivative_gather.pattern
|
||||||
|
gather_pointers = list(gather_pattern.row_pointers)
|
||||||
|
gather_pointers[1] = gather_pointers[0]
|
||||||
|
variants.append(
|
||||||
|
(
|
||||||
|
"empty derivative row",
|
||||||
|
replace(
|
||||||
|
program,
|
||||||
|
state_reducer=replace(
|
||||||
|
program.state_reducer,
|
||||||
|
derivative_gather=replace(
|
||||||
|
program.state_reducer.derivative_gather,
|
||||||
|
pattern=replace(
|
||||||
|
gather_pattern,
|
||||||
|
row_pointers=tuple(gather_pointers),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"DERIVATIVE_GATHER_EMPTY_ROW",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for label, corrupted, expected_code in variants:
|
||||||
|
with self.subTest(label=label):
|
||||||
|
report = validate_system_ir(corrupted)
|
||||||
|
self.assertFalse(report.valid)
|
||||||
|
self.assertIn(
|
||||||
|
expected_code,
|
||||||
|
{item.code for item in report.issues},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_target_recompilation_is_byte_stable(self) -> None:
|
||||||
|
second = compile_system_ir(_system_from_xml(TARGET_XML))
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.program.canonical_json_bytes(),
|
||||||
|
second.canonical_json_bytes(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HistoricalSystemNumericIRV2Tests(unittest.TestCase):
|
||||||
|
def test_historical_complex_model_also_compiles(self) -> None:
|
||||||
|
program = compile_system_ir(_system_from_xml(HISTORICAL_XML))
|
||||||
|
|
||||||
|
self.assertTrue(validate_system_ir(program).valid)
|
||||||
|
self.assertEqual(len(program.components), 98)
|
||||||
|
self.assertEqual(len(program.connections), 106)
|
||||||
|
self.assertEqual(program.state_reducer.solver_state_count, 74)
|
||||||
|
self.assertEqual(len(program.pressure_flow.unknowns), 472)
|
||||||
|
self.assertEqual(len(program.pressure_flow.equations), 472)
|
||||||
|
self.assertEqual(len(program.outputs), 1021)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in new issue
Block a user