完善建模交互、组件图标与系统协议

This commit is contained in:
ljz committed 2026-08-15 17:40:18 +08:00
1 parent 456c29b3b6
commit 6572defaa4
66 files changed
+10067 -4163

No files matched your search

+108 -16
View File
@@ -47,6 +47,14 @@ class ThermofluidClosureError(RuntimeError):
"""Raised when stream enthalpy and pressure-flow do not reach one fixed point."""
class SimulationSampleTimeError(ValueError):
"""Stable failure contract for an unsafe or unrepresentable sample grid."""
def __init__(self, code: str, message: str) -> None:
super().__init__(message)
self.code = code
@dataclass(frozen=True)
class GenericSimulationResult:
success: bool
@@ -106,10 +114,9 @@ def simulation_preparation_issues(
) -> tuple[SimulationPreparationIssue, ...]:
issues: list[SimulationPreparationIssue] = []
physical_endpoints = {
Endpoint(component.name, definition.name)
Endpoint(component.name, port_name)
for component in network.components.values()
for definition in component.port_definitions
if definition.kind == "physical"
for port_name in component.required_connection_ports
}
connected_endpoints = {
endpoint
@@ -148,8 +155,18 @@ def simulation_preparation_issues(
)
)
adjacency = {name: set() for name in network.components}
physical_component_names = {
component.name
for component in network.components.values()
if any(
definition.kind == "physical"
for definition in component.port_definitions
)
}
adjacency = {name: set() for name in physical_component_names}
for connection in network.connections:
if connection.kind != "physical":
continue
first, second = connection.endpoints
adjacency[first.component].add(second.component)
adjacency[second.component].add(first.component)
@@ -224,20 +241,95 @@ def simulation_sample_times(
*,
max_points: int = 10001,
) -> list[float]:
if max_points < 2:
raise SimulationSampleTimeError(
"SIMULATION_SAMPLE_LIMIT_INVALID",
"Simulation sample limit must allow at least two points.",
)
t_start = float(config.t_start)
t_stop = float(config.t_stop)
if not isfinite(t_start) or not isfinite(t_stop):
raise SimulationSampleTimeError(
"SIMULATION_VALUE_NOT_FINITE",
"Simulation start and stop times must be finite.",
)
if step <= 0.0 or not isfinite(step):
raise ValueError("Simulation sample step must be finite and greater than zero.")
duration = config.t_stop - config.t_start
raise SimulationSampleTimeError(
"SIMULATION_SAMPLE_STEP_INVALID",
"Simulation sample step must be finite and greater than zero.",
)
duration = t_stop - t_start
if not isfinite(duration):
raise SimulationSampleTimeError(
"SIMULATION_TIME_SPAN_NOT_FINITE",
"Simulation time span must be finite.",
)
if duration <= 0.0:
raise ValueError("Simulation stop time must be greater than start time.")
interval_count = int(floor(duration / step + 1e-12))
times = [config.t_start + index * step for index in range(interval_count + 1)]
if times[-1] < config.t_stop - 1e-12:
times.append(config.t_stop)
else:
times[-1] = config.t_stop
if len(times) > max_points:
raise ValueError(
f"Simulation requests {len(times)} samples; the limit is {max_points}."
raise SimulationSampleTimeError(
"SIMULATION_TIME_RANGE_INVALID",
"Simulation stop time must be greater than start time.",
)
# Bound the grid before dividing by a potentially tiny step or allocating
# the result list. This avoids both float-to-int overflow and an OOM-sized
# ``range``/list when input comes from an external System XML document.
maximum_interval_count = max_points - 1
if step < duration / maximum_interval_count:
raise SimulationSampleTimeError(
"SIMULATION_SAMPLE_COUNT_EXCEEDED",
f"Simulation sample count exceeds the limit of {max_points}; "
"increase sampleStep.",
)
ratio = duration / step
if not isfinite(ratio):
raise SimulationSampleTimeError(
"SIMULATION_SAMPLE_COUNT_EXCEEDED",
f"Simulation sample count exceeds the limit of {max_points}; "
"increase sampleStep.",
)
interval_count = int(floor(ratio))
last_regular_time = t_start + interval_count * step
append_stop = last_regular_time < t_stop
requested_point_count = interval_count + 1 + int(append_stop)
if requested_point_count > max_points:
raise SimulationSampleTimeError(
"SIMULATION_SAMPLE_COUNT_EXCEEDED",
f"Simulation requests {requested_point_count} samples; "
f"the limit is {max_points}.",
)
times = [t_start]
for index in range(1, interval_count + 1):
candidate = t_start + index * step
if not isfinite(candidate):
raise SimulationSampleTimeError(
"SIMULATION_SAMPLE_TIME_UNREPRESENTABLE",
"Simulation sampleStep cannot be represented over the requested "
"absolute time range.",
)
if candidate >= t_stop:
candidate = t_stop
if candidate <= times[-1]:
raise SimulationSampleTimeError(
"SIMULATION_SAMPLE_TIME_UNREPRESENTABLE",
"Simulation sampleStep is too small to advance floating-point "
"time over the requested absolute time range.",
)
times.append(candidate)
if candidate == t_stop:
break
if times[-1] < t_stop:
times.append(t_stop)
if len(times) < 2 or any(
current >= following
for current, following in zip(times, times[1:])
):
raise SimulationSampleTimeError(
"SIMULATION_SAMPLE_TIME_UNREPRESENTABLE",
"Simulation sample times must contain at least two strictly "
"increasing values.",
)
return times
+14 -2
View File
@@ -91,8 +91,10 @@ class SimulationNetwork:
) -> Connection:
endpoint_a = Endpoint(endpoint_a_component, endpoint_a_port)
endpoint_b = Endpoint(endpoint_b_component, endpoint_b_port)
if endpoint_a == endpoint_b:
raise ValueError(f"Cannot connect endpoint {endpoint_a} to itself.")
if endpoint_a.component == endpoint_b.component:
raise ValueError(
f"Cannot connect component {endpoint_a.component} to itself."
)
first_port = self._port_for(endpoint_a)
second_port = self._port_for(endpoint_b)
@@ -131,6 +133,16 @@ class SimulationNetwork:
+ ", ".join(occupied)
+ ". Use a junction component for branching."
)
else:
signal_input = (
endpoint_a
if first_definition.nominal_role == "input"
else endpoint_b
)
if signal_input in occupied_endpoints:
raise ValueError(
f"Signal input {signal_input} already has a driver."
)
if first_definition.kind == "physical" and endpoint_b.key < endpoint_a.key:
endpoint_a, endpoint_b = endpoint_b, endpoint_a