from __future__ import annotations from dataclasses import dataclass from typing import Protocol from app.simulation.systems.network import Endpoint, SimulationNetwork class SignalOutputComponent(Protocol): name: str def signal_output_values(self, time: float) -> dict[str, float]: ... @dataclass(frozen=True) class SignalSolveDiagnostics: propagated: int def as_dict(self) -> dict[str, object]: return {"propagated": self.propagated} class SignalResolver: """Propagate scalar signal connections from output ports to input ports.""" def __init__(self, network: SimulationNetwork) -> None: self.network = network self._connections = [ connection for connection in network.connections if connection.kind == "signal" ] self.last_diagnostics: SignalSolveDiagnostics | None = None def solve(self, time: float) -> SignalSolveDiagnostics: for component in self.network.components.values(): signal_output_values = getattr(component, "signal_output_values", None) if signal_output_values is None: continue for port_name, value in signal_output_values(time).items(): component.get_port(port_name).signal = float(value) propagated = 0 for connection in self._connections: source, target = self._source_target(connection.endpoints) source_port = self.network.components[source.component].get_port(source.port) target_port = self.network.components[target.component].get_port(target.port) target_port.signal = source_port.signal propagated += 1 diagnostics = SignalSolveDiagnostics(propagated=propagated) self.last_diagnostics = diagnostics return diagnostics def _source_target(self, endpoints: tuple[Endpoint, Endpoint]) -> tuple[Endpoint, Endpoint]: first, second = endpoints first_port = self.network.components[first.component].get_port(first.port) second_port = self.network.components[second.component].get_port(second.port) if first_port.definition is not None and first_port.definition.nominal_role == "output": return first, second if second_port.definition is not None and second_port.definition.nominal_role == "output": return second, first raise ValueError("Signal connection must contain one output endpoint.")