from __future__ import annotations from app.simulation.examples.test_mql.closure import TestMqlPneumaticChamberSegmentSpec from app.simulation.examples.test_mql.topology import TestMqlCirTopology def discover_fixed_chamber_segments( topology: TestMqlCirTopology, component_specs: list[dict[str, object]], connection_specs: list[dict[str, object]], ) -> tuple[TestMqlPneumaticChamberSegmentSpec, ...]: submodel_by_alias = { str(component["alias"]): str(component["submodel"]) for component in component_specs } segments = [] for component in component_specs: volume_alias = str(component["alias"]) if component["submodel"] != "PNCH023": continue orifice_contacts = [] for contact in topology.contacts_for(volume_alias): other_alias, other_port = contact.other_endpoint(volume_alias) if submodel_by_alias.get(other_alias) == "PNOR001": orifice_contacts.append( ( other_alias, other_port, contact.port_for(volume_alias), ) ) if len(orifice_contacts) != 2: raise ValueError( f"{volume_alias} must contact exactly two PNOR001 orifices; " f"found {len(orifice_contacts)}" ) sides = [ _resolve_orifice_boundary( orifice_alias=orifice_alias, orifice_volume_port=orifice_volume_port, volume_port=volume_port, connection_specs=connection_specs, submodel_by_alias=submodel_by_alias, ) for orifice_alias, orifice_volume_port, volume_port in orifice_contacts ] inlet_sides = [side for side in sides if side["role"] == "inlet"] outlet_sides = [side for side in sides if side["role"] == "outlet"] if len(inlet_sides) != 1 or len(outlet_sides) != 1: raise ValueError( f"{volume_alias} requires one inlet and one outlet topology side" ) inlet = inlet_sides[0] outlet = outlet_sides[0] segments.append( TestMqlPneumaticChamberSegmentSpec( name=f"{volume_alias}_segment", inlet_node_alias=inlet["node_alias"], inlet_line_alias=inlet["line_alias"], inlet_orifice_alias=inlet["orifice_alias"], inlet_orifice_boundary_port=inlet["orifice_boundary_port"], inlet_orifice_volume_port=inlet["orifice_volume_port"], volume_alias=volume_alias, volume_inlet_port=inlet["volume_port"], volume_outlet_port=outlet["volume_port"], outlet_orifice_alias=outlet["orifice_alias"], outlet_orifice_volume_port=outlet["orifice_volume_port"], outlet_orifice_boundary_port=outlet["orifice_boundary_port"], outlet_line_alias=outlet["line_alias"], outlet_node_alias=outlet["node_alias"], ) ) return tuple(segments) def _resolve_orifice_boundary( *, orifice_alias: str, orifice_volume_port: str, volume_port: str, connection_specs: list[dict[str, object]], submodel_by_alias: dict[str, str], ) -> dict[str, str]: boundary_connections = [] for connection in connection_specs: if ( connection["source_component"] == orifice_alias and connection["source_port"] != orifice_volume_port ) or ( connection["target_component"] == orifice_alias and connection["target_port"] != orifice_volume_port ): boundary_connections.append(connection) if len(boundary_connections) != 1: raise ValueError( f"{orifice_alias} must have exactly one non-volume boundary connection; " f"found {len(boundary_connections)}" ) connection = boundary_connections[0] if connection["submodel"] != "PNL0001": raise ValueError( f"{orifice_alias} boundary must use PNL0001, got {connection['submodel']}" ) if connection["target_component"] == orifice_alias: role = "inlet" node_alias = str(connection["source_component"]) orifice_boundary_port = str(connection["target_port"]) else: role = "outlet" node_alias = str(connection["target_component"]) orifice_boundary_port = str(connection["source_port"]) if submodel_by_alias.get(node_alias) != "PN3NODE2": raise ValueError( f"{orifice_alias} PNL0001 boundary must terminate at PN3NODE2, " f"got {node_alias}" ) return { "role": role, "node_alias": node_alias, "line_alias": str(connection["alias"]), "orifice_alias": orifice_alias, "orifice_boundary_port": orifice_boundary_port, "orifice_volume_port": orifice_volume_port, "volume_port": volume_port, }