feat: integrate AMESim media models and editor UI
This commit is contained in:
1 parent
3e4f466114
commit
e7177ab03e
181 files changed
+7042
-25270
No files matched your search
+111
-7
@@ -28,6 +28,7 @@ from app.system_xml import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.simulation.components.amesim.gases import AmesimGasRegistry
|
||||
from app.simulation.core.ports import PortDefinition
|
||||
from app.simulation.systems.network import SimulationNetwork
|
||||
|
||||
@@ -135,6 +136,7 @@ class ReactFlowSimulationConfig(BaseModel):
|
||||
|
||||
class ReactFlowProjectPayload(BaseModel):
|
||||
name: str = "untitled"
|
||||
mediumReferenceVersion: Literal[1] | None = None
|
||||
nodes: list[ReactFlowNodePayload] = Field(default_factory=list)
|
||||
edges: list[ReactFlowEdgePayload] = Field(default_factory=list)
|
||||
simulation: ReactFlowSimulationConfig = Field(default_factory=ReactFlowSimulationConfig)
|
||||
@@ -891,12 +893,15 @@ def _compile_xml_document_or_422(
|
||||
|
||||
|
||||
def build_reactflow_system_xml(project: ReactFlowProjectPayload) -> bytes:
|
||||
from app.simulation.registry import get_component_model_spec
|
||||
|
||||
system = ET.Element(
|
||||
"System",
|
||||
{
|
||||
"name": project.name,
|
||||
"schemaVersion": SYSTEM_XML_SCHEMA_VERSION,
|
||||
"unitSystem": SYSTEM_XML_UNIT_SYSTEM,
|
||||
"mediumReferenceVersion": "1",
|
||||
},
|
||||
)
|
||||
ET.SubElement(
|
||||
@@ -953,7 +958,20 @@ def build_reactflow_system_xml(project: ReactFlowProjectPayload) -> bytes:
|
||||
port_definition.positiveFlowDirection or "intoComponent"
|
||||
)
|
||||
ET.SubElement(component_node, "Port", port_attributes)
|
||||
for name, value in node.data.parameters.items():
|
||||
parameter_values = dict(node.data.parameters)
|
||||
try:
|
||||
component_spec = get_component_model_spec(node.data.modelType)
|
||||
except ValueError:
|
||||
component_spec = None
|
||||
if (
|
||||
component_spec is not None
|
||||
and component_spec.display.role == "amesimGasMediumDefinition"
|
||||
):
|
||||
for parameter in component_spec.parameters:
|
||||
if parameter.editor == "amesimGasPropertyModel":
|
||||
parameter_values.setdefault(parameter.name, parameter.default)
|
||||
|
||||
for name, value in parameter_values.items():
|
||||
ET.SubElement(
|
||||
component_node,
|
||||
"Parameter",
|
||||
@@ -1086,14 +1104,34 @@ def validate_compatible_ports(
|
||||
)
|
||||
|
||||
|
||||
def compile_reactflow_network(project: ReactFlowProjectPayload) -> "SimulationNetwork":
|
||||
from app.simulation.core.medium import IdealGasMedium
|
||||
def compile_reactflow_network(
|
||||
project: ReactFlowProjectPayload,
|
||||
*,
|
||||
amesim_gas_registry: "AmesimGasRegistry | None" = None,
|
||||
) -> "SimulationNetwork":
|
||||
from app.simulation.components.amesim.gases import (
|
||||
AMESIM_BUILTIN_AIR_GAS_INDEX,
|
||||
default_amesim_gas_registry,
|
||||
)
|
||||
from app.simulation.components.amesim.media.properties import (
|
||||
AmesimGasMediumDefinitionComponent,
|
||||
)
|
||||
from app.simulation.registry import get_component_model_spec
|
||||
from app.simulation.systems.network import SimulationNetwork
|
||||
|
||||
medium = IdealGasMedium()
|
||||
network = SimulationNetwork(name=project.name)
|
||||
if amesim_gas_registry is None:
|
||||
amesim_gas_registry = default_amesim_gas_registry()
|
||||
else:
|
||||
amesim_gas_registry = amesim_gas_registry.copy()
|
||||
|
||||
network = SimulationNetwork(name=project.name)
|
||||
resolved_nodes = []
|
||||
specs_by_component_id = {}
|
||||
gas_indices_by_component_id = {}
|
||||
|
||||
# Phase 1: normalize every node and consume compile-time medium definitions.
|
||||
# A definition node represents one complete property-method instance and is
|
||||
# intentionally not added to the equation network.
|
||||
for node in project.nodes:
|
||||
spec = get_component_model_spec(node.data.modelType)
|
||||
parameter_values = {
|
||||
@@ -1107,7 +1145,73 @@ def compile_reactflow_network(project: ReactFlowProjectPayload) -> "SimulationNe
|
||||
+ ", ".join(sorted(unknown_parameters))
|
||||
+ "."
|
||||
)
|
||||
component = spec.create(node.id, medium, parameter_values)
|
||||
if issubclass(
|
||||
spec.component_class,
|
||||
AmesimGasMediumDefinitionComponent,
|
||||
):
|
||||
definition_component = spec.create(
|
||||
node.id,
|
||||
amesim_gas_registry.resolve(AMESIM_BUILTIN_AIR_GAS_INDEX),
|
||||
parameter_values,
|
||||
)
|
||||
assert isinstance(
|
||||
definition_component,
|
||||
AmesimGasMediumDefinitionComponent,
|
||||
)
|
||||
validate_component_port_interface(
|
||||
node,
|
||||
definition_component.port_definitions,
|
||||
)
|
||||
try:
|
||||
amesim_gas_registry.register(
|
||||
definition_component.gas_definition()
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise ValueError(
|
||||
f"AMESim medium definition component '{node.id}' is invalid: "
|
||||
f"{exc}"
|
||||
) from exc
|
||||
continue
|
||||
|
||||
resolved_nodes.append((node, spec, parameter_values))
|
||||
specs_by_component_id[node.id] = spec
|
||||
gas_indices_by_component_id[node.id] = (
|
||||
parameter_values["gi"]
|
||||
if spec.library.id == "amesim" and "gi" in spec.parameter_by_name
|
||||
else None
|
||||
)
|
||||
|
||||
# Phase 2: resolve one gas property model for each pneumatic circuit before
|
||||
# any dynamic component initializes its thermodynamic state.
|
||||
pneumatic_connections = []
|
||||
for edge in project.edges:
|
||||
source_spec = specs_by_component_id.get(edge.source)
|
||||
target_spec = specs_by_component_id.get(edge.target)
|
||||
if source_spec is None or target_spec is None:
|
||||
continue
|
||||
source_ports = {port.name: port for port in source_spec.ports}
|
||||
target_ports = {port.name: port for port in target_spec.ports}
|
||||
source_port = source_ports.get(edge.sourceHandle or "")
|
||||
target_port = target_ports.get(edge.targetHandle or "")
|
||||
if (
|
||||
source_port is not None
|
||||
and target_port is not None
|
||||
and source_port.domain == "pneumatic"
|
||||
and target_port.domain == "pneumatic"
|
||||
):
|
||||
pneumatic_connections.append((edge.source, edge.target))
|
||||
|
||||
media_by_component_id = amesim_gas_registry.resolve_network_media(
|
||||
gas_indices_by_component_id,
|
||||
pneumatic_connections,
|
||||
)
|
||||
|
||||
for node, spec, parameter_values in resolved_nodes:
|
||||
component = spec.create(
|
||||
node.id,
|
||||
media_by_component_id[node.id],
|
||||
parameter_values,
|
||||
)
|
||||
validate_component_port_interface(node, component.port_definitions)
|
||||
network.add_component(component)
|
||||
|
||||
@@ -1260,7 +1364,7 @@ def run_reactflow_testmodel(project: ReactFlowProjectPayload) -> dict[str, objec
|
||||
|
||||
def run_reactflow_test_mql(project: ReactFlowProjectPayload) -> dict[str, object]:
|
||||
from app.simulation.examples.test_mql.run import run_test_mql
|
||||
from PythonModels.systems.test_mql import TestMqlRunConfig
|
||||
from app.simulation.examples.test_mql.system import TestMqlRunConfig
|
||||
|
||||
run_config = TestMqlRunConfig(
|
||||
t_start=project.simulation.t_start,
|
||||
|
||||
Reference in new issue
Block a user