Source code for quchip.chip.port_network

"""Composable, instantaneous SLH field boundaries."""

from __future__ import annotations

from collections.abc import Callable, Collection, Iterator, Mapping, Sequence
from dataclasses import dataclass, field, replace
from itertools import pairwise
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Literal

import numpy as np
from scipy import sparse

from quchip.chip.ports import Port
from quchip.engine.field_noise import amplifier_values, attenuation_value, noise_parameters, thermal_occupation_value
from quchip.engine.output_network import pad_mixing
from quchip.engine.slh import CompiledChannel, CompiledNetwork, ModeResponse, _same_carrier, cascade_hamiltonian
from quchip.engine.reference import (
    FieldChannel,
    ReferenceAmplifier,
    ReferenceDelay,
    ReferenceElement,
    ReferenceFilter,
    ReferencePlane,
    ReferenceLoss,
    noise_density,
    has_colored_noise,
    source_occupation,
)
from quchip.utils.jax_utils import contains_tracer, select_array_module
from quchip.utils.labeling import auto_label, resolve_label
from quchip.utils.values import copy_value
from quchip.utils.deprecation import warn_renamed

if TYPE_CHECKING:
    from quchip.control.field import CoherentInput
    from quchip.engine.ir import ResolvedSLH
    from quchip.observables import OutputField


TerminalDirection = Literal["input", "output"]
TerminalKey = tuple[str, str]


@dataclass(frozen=True)
class FieldTerminal:
    """One directional terminal owned by a :class:`PortNetwork`.

    Parameters
    ----------
    component : str
        Owning component label.
    name : str
        Component-local terminal name.
    direction : {"input", "output"}
        Field-propagation direction.
    """

    component: str
    name: str
    direction: TerminalDirection
    _network_token: object = field(repr=False, compare=False)

    @property
    def key(self) -> TerminalKey:
        """Return the stable component-local terminal key."""
        return (self.component, self.name)


@dataclass(frozen=True)
class SLHComponent:
    """Boundary component with scalar scattering and named terminals.

    Parameters
    ----------
    label : str
        Unique component label.
    input_names, output_names : tuple[str, ...]
        Terminal names in scattering column and row order.
    scattering : array-like
        Dimensionless scalar scattering matrix.
    sides : tuple[str, ...], default=()
        Names pairing physical input and output terminals.
    """

    label: str
    input_names: tuple[str, ...]
    output_names: tuple[str, ...]
    scattering: Any
    _network_token: object = field(repr=False, compare=False)
    sides: tuple[str, ...] = ()
    _local_ports: tuple[Port | None, ...] = field(default=(), repr=False, compare=False)
    _hidden_pairs: tuple[tuple[str, str, str], ...] = field(
        default=(), repr=False, compare=False
    )
    _row_inputs: tuple[tuple[int, ...], ...] | None = field(default=None, repr=False, compare=False)
    _kind: str = field(default="scattering", repr=False)
    _parameters: dict[str, Any] = field(default_factory=dict, repr=False, compare=False)
    _transfer: Callable[..., Any] | None = field(default=None, repr=False, compare=False)

    def port(self, name: str | int) -> "ComponentPort":
        """Return a component port with both field directions.

        Parameters
        ----------
        name : str or int
            Physical side name.
        """
        label = str(name)
        if not self.sides:
            raise ValueError(
                f"Component {self.label!r} is directional and has no component ports; use "
                "input_terminal()/output_terminal() or cascade()."
            )
        if label not in self.sides:
            raise KeyError(f"No port {label!r} on component {self.label!r}; available: {list(self.sides)}")
        return ComponentPort(self.input_terminal(label), self.output_terminal(label))

    def side(self, name: str | int) -> "ComponentPort":
        """Deprecated alias for :meth:`port`.

        Parameters
        ----------
        name : str or int
            Physical side name.
        """
        warn_renamed("component.side()", "component.port()")
        return self.port(name)

    @property
    def inputs(self) -> tuple[FieldTerminal, ...]:
        """Return input terminals in the component's scattering-column order."""
        return tuple(
            FieldTerminal(self.label, name, "input", self._network_token)
            for name in self.input_names
        )

    @property
    def outputs(self) -> tuple[FieldTerminal, ...]:
        """Return output terminals in the component's scattering-row order."""
        return tuple(
            FieldTerminal(self.label, name, "output", self._network_token)
            for name in self.output_names
        )

    def input_terminal(self, name: str) -> FieldTerminal:
        """Return one named input terminal.

        Parameters
        ----------
        name : str
            Input terminal name.
        """
        try:
            index = self.input_names.index(name)
        except ValueError as exc:
            raise KeyError(
                f"No input {name!r} on component {self.label!r}; available: {list(self.input_names)}"
            ) from exc
        return self.inputs[index]

    def output_terminal(self, name: str) -> FieldTerminal:
        """Return one named output terminal.

        Parameters
        ----------
        name : str
            Output terminal name.
        """
        try:
            index = self.output_names.index(name)
        except ValueError as exc:
            raise KeyError(
                f"No output {name!r} on component {self.label!r}; available: {list(self.output_names)}"
            ) from exc
        return self.outputs[index]

    @property
    def input(self) -> FieldTerminal:
        """Return the sole or signal input terminal."""
        if "signal" in self.input_names:
            return self.input_terminal("signal")
        if len(self.input_names) == 1:
            return self.inputs[0]
        raise AttributeError(
            f"Component {self.label!r} has multiple inputs; use input_terminal(name)."
        )

    @property
    def output(self) -> FieldTerminal:
        """Return the sole or signal output terminal."""
        if "signal" in self.output_names:
            return self.output_terminal("signal")
        if len(self.output_names) == 1:
            return self.outputs[0]
        raise AttributeError(
            f"Component {self.label!r} has multiple outputs; use output_terminal(name)."
        )


@dataclass(frozen=True)
class ComponentPort:
    """A component port pairing incoming and outgoing field connections.

    Parameters
    ----------
    input, output : FieldTerminal
        Incoming and outgoing terminals on the same physical side.
    """

    input: FieldTerminal
    output: FieldTerminal


@dataclass(frozen=True, eq=False)
class NetworkPort:
    """A named external network port with incoming and outgoing fields.

    The reference plane specifies where these fields are defined. A device's
    :class:`Port` instead specifies its coupling to the network.

    Parameters
    ----------
    label : str
        External reference-plane label.
    """

    label: str
    _input_key: TerminalKey = field(repr=False)
    _output_key: TerminalKey = field(repr=False)
    _hidden: bool = field(default=False, repr=False)
    _network_token: object = field(repr=False, compare=False, kw_only=True)

    def __eq__(self, other: object) -> bool:
        """Compare stable plane identity in one network."""
        return (
            isinstance(other, NetworkPort)
            and self._network_token is other._network_token
            and self.label == other.label
        )

    def __hash__(self) -> int:
        """Hash stable plane identity in one network."""
        return hash((id(self._network_token), self.label))

    @property
    def input(self) -> "CoherentInput":
        """Return the coherent-field endpoint scheduled at this plane."""
        if self._hidden:
            raise AttributeError("Hidden vacuum channels cannot be driven.")
        from quchip.control.field import CoherentInput

        return CoherentInput(self.label, label=f"{self.label}.input")

    @property
    def output(self) -> "OutputField":
        """Return the complete transient output-field request for this plane."""
        if self._hidden:
            raise AttributeError("Hidden vacuum channels are not observable.")
        from quchip.observables import OutputField

        return OutputField(self.label)


def _strongly_connected(nodes: Mapping[Any, Any], successors: Callable[[Any], list[Any]]) -> list[list[Any]]:
    """Return strongly connected components with every component after the ones it depends on."""
    index: dict[Any, int] = {}
    lowlink: dict[Any, int] = {}
    stack: list[Any] = []
    on_stack: set[Any] = set()
    components: list[list[Any]] = []

    def visit(node: Any) -> None:
        index[node] = lowlink[node] = len(index)
        stack.append(node)
        on_stack.add(node)
        for successor in successors(node):
            if successor not in index:
                visit(successor)
                lowlink[node] = min(lowlink[node], lowlink[successor])
            elif successor in on_stack:
                lowlink[node] = min(lowlink[node], index[successor])
        if lowlink[node] == index[node]:
            component = []
            while True:
                member = stack.pop()
                on_stack.discard(member)
                component.append(member)
                if member == node:
                    break
            components.append(component[::-1])

    for node in nodes:
        if node not in index:
            visit(node)
    return components


@dataclass(frozen=True)
class IncludedNetwork:
    """Access one prefixed copy of a template network inside a host.

    ``component(name)`` returns a copied component. Template exposures made with
    ``expose(..., at=...)`` are available through ``side(name)``. Asymmetric
    exposures use ``input(name)`` and ``output(name)``.

    Parameters
    ----------
    prefix : str
        Label prefix assigned to the included copy.
    """

    prefix: str
    _host: "PortNetwork" = field(repr=False, compare=False)
    _interfaces: Mapping[str, tuple[TerminalKey, TerminalKey]] = field(repr=False, compare=False)

    def component(self, name: str) -> SLHComponent:
        """Return a copied template component.

        Parameters
        ----------
        name : str
            Unprefixed template component name.
        """
        label = f"{self.prefix}/{name}"
        if label not in self._host._components:
            raise KeyError(f"No component {name!r} in block {self.prefix!r}.")
        return self._host._components[label]

    def input(self, name: str) -> FieldTerminal:
        """Return an exported input terminal.

        Parameters
        ----------
        name : str
            Template external-port name.
        """
        key = self._interface(name)[0]
        return self._host._components[key[0]].input_terminal(key[1])

    def output(self, name: str) -> FieldTerminal:
        """Return an exported output terminal.

        Parameters
        ----------
        name : str
            Template external-port name.
        """
        key = self._interface(name)[1]
        return self._host._components[key[0]].output_terminal(key[1])

    def port(self, name: str) -> ComponentPort:
        """Return an exported component port.

        Parameters
        ----------
        name : str
            Symmetric template external-port name.
        """
        input_key, output_key = self._interface(name)
        component = self._host._components[input_key[0]]
        if input_key != output_key or input_key[1] not in component.sides:
            raise ValueError(f"Interface {name!r} of block {self.prefix!r} is asymmetric; use input() and output().")
        return component.port(input_key[1])

    def side(self, name: str) -> ComponentPort:
        """Deprecated alias for :meth:`port`.

        Parameters
        ----------
        name : str
            Symmetric template external-port name.
        """
        warn_renamed("block.side()", "block.port()")
        return self.port(name)

    def _interface(self, name: str) -> tuple[TerminalKey, TerminalKey]:
        if name not in self._interfaces:
            raise KeyError(f"No interface {name!r} in block {self.prefix!r}; available: {list(self._interfaces)}")
        return self._interfaces[name]


_SERIALIZED_FACTORIES = frozenset(
    {
        "through",
        "hybrid90",
        "permutation",
        "circulator",
        "isolator",
        "phase_shift",
        "beam_splitter",
        "attenuator",
        "delay",
        "amplifier",
        "mode_reflection",
        "mode_transmission",
        "termination",
    }
)

#: Two-sided sections peeled from exposure legs instead of entering the Markovian core.
_REFERENCE_KINDS = frozenset({"delay", "filter", "amplifier", "mode_reflection", "mode_transmission"})


def _mode_transmission_transfer(
    frequency: Any, *, freq: Any, external_rate: Any, internal_rate: Any = 0.0,
) -> Any:
    """Return the one-pass amplitude transfer of a damped linear mode."""
    xp = select_array_module(contains_tracer((frequency, freq, external_rate, internal_rate)))
    detuning = 2.0 * xp.pi * (xp.asarray(frequency) - freq)
    denominator = (external_rate + internal_rate) / 2.0 - 1j * detuning
    # A disconnected, lossless mode is transparent even at its bare resonance.
    absent = (denominator == 0.0) & (external_rate == 0.0)
    return 1.0 - external_rate / xp.where(absent, 1.0, denominator)


def _mode_reflection_transfer(
    frequency: Any, *, freq: Any, external_rate: Any, internal_rate: Any = 0.0, reference_freq: Any = None,
) -> Any:
    """Return the per-pass amplitude transfer whose square is a linear mode's reflection.

    With ``Ω = 2π(f − freq)``, the reflection is
    ``S(f) = ((κ_i − κ_e)/2 − iΩ) / ((κ_e + κ_i)/2 − iΩ)``. The square root is
    continuous in frequency; ``reference_freq`` selects the branch with a
    nonnegative real part there.
    """
    values = (frequency, freq, external_rate, internal_rate, reference_freq)
    xp = select_array_module(contains_tracer(values))

    def root(at: Any) -> Any:
        detuning = 2.0 * xp.pi * (xp.asarray(at) - freq)
        numerator = (internal_rate - external_rate) / 2.0 - 1j * detuning
        denominator = (external_rate + internal_rate) / 2.0 - 1j * detuning
        # Each square root stays off its branch cut: an overcoupled mode
        # (κ_e ≥ κ_i) has Re(−numerator) ≥ 0, an undercoupled one Re(numerator) > 0.
        overcoupled = external_rate >= internal_rate
        upper = xp.sqrt(xp.where(overcoupled, -numerator, numerator) + 0j)
        return xp.where(overcoupled, 1j * upper, upper) / xp.sqrt(denominator + 0j)

    transfer = root(frequency)
    if reference_freq is None:
        return transfer
    return transfer * xp.where(xp.real(root(reference_freq)) < 0.0, -1.0, 1.0)


#: ``(coefficient, boundary_input, upstream_output)`` for one structurally fed column.
_Feeder = tuple[Any, "TerminalKey | None", "TerminalKey | None"]


@dataclass
class _AffineField:
    scattering: list[Any]
    coupling: dict[str, Any]
    support: list[bool]


[docs] class PortNetwork: """Compose Markovian ports with instantaneous scalar scattering. The network acts on fields at one reference frequency. Scattering matrices are dimensionless and must be unitary. Explicit hidden channels or reference components represent loss and noise. The network reduces feedback loops algebraically, so this class does not model propagation retardation. Parameters ---------- scattering : array-like or mapping, optional Authored instantaneous scattering matrix or labeled entries. label : str, optional Network label. """ _type_prefix = "port_network" def __init__( self, *, scattering: Any = None, label: str | None = None, ) -> None: """Create an empty network, optionally with an authored scattering map. Parameters ---------- scattering : array-like or mapping, optional Static scattering matrix, or ``{(output, input): amplitude}`` entries keyed by external labels. A callable is rejected because the network is instantaneous. label : str, optional Network label, generated if omitted. """ self.label = label if label is not None else auto_label(self._type_prefix) self._token = object() if callable(scattering): raise TypeError("PortNetwork scattering is instantaneous; callables are not supported.") if isinstance(scattering, Mapping): normalized: dict[tuple[str, str], Any] = {} for key, value in scattering.items(): if not isinstance(key, tuple) or len(key) != 2: raise TypeError("Scattering mappings use (output, input) keys.") output, input_ = (resolve_label(item) for item in key) if (output, input_) in normalized: raise ValueError(f"Duplicate scattering entry {(output, input_)}.") normalized[(output, input_)] = value self._authored_scattering = normalized else: self._authored_scattering = scattering self._components: dict[str, SLHComponent] = {} self._ports: list[Port] = [] self._connections: dict[TerminalKey, TerminalKey] = {} self._used_outputs: dict[TerminalKey, TerminalKey] = {} self._exposures: list[NetworkPort] = [] self._mode_responses: tuple[ModeResponse, ...] = ()
[docs] @classmethod def from_ports( cls, ports: Sequence[Port], *, scattering: Any = None, label: str | None = None, ) -> "PortNetwork": """Build a network containing ``ports`` in declaration order. Parameters ---------- ports : sequence of Port Quantum coupling channels to add. Each port can belong to only one network, so pass a copy to use it in another. scattering : array-like or mapping, optional Initial external scattering specification. label : str, optional Network label. Returns ------- PortNetwork Network with the supplied ports and no internal components. """ network = cls(scattering=scattering, label=label) for port in ports: network._add_port(port) return network
@property def ports(self) -> tuple[Port, ...]: """Return network-owned quantum coupling ports in declaration order.""" return tuple(self._ports) @property def components(self) -> tuple[SLHComponent, ...]: """Return all graph components in declaration order.""" return tuple(self._components.values()) @property def external_ports(self) -> tuple[NetworkPort, ...]: """Return the external network ports in channel order.""" return tuple(exposure for exposure in self._effective_exposures() if not exposure._hidden) @property def exposures(self) -> tuple[NetworkPort, ...]: """Deprecated alias for :attr:`external_ports`.""" warn_renamed("network.exposures", "network.external_ports") return self.external_ports
[docs] def exposure(self, exposure: str | NetworkPort) -> NetworkPort: """Deprecated alias for :meth:`external_port`. Parameters ---------- exposure : str or NetworkPort External reference plane. """ warn_renamed("network.exposure()", "network.external_port()") return self.external_port(exposure)
[docs] def external_port(self, exposure: str | NetworkPort) -> NetworkPort: """Return one external network port by object or label. Parameters ---------- exposure : str or NetworkPort External reference plane. """ if isinstance(exposure, NetworkPort) and exposure._network_token is not self._token: raise ValueError(f"Network port {exposure.label!r} belongs to another PortNetwork.") label = resolve_label(exposure) exposures = self.external_ports for candidate in exposures: if candidate.label == label: return candidate raise KeyError( f"No exposure labeled {label!r}. Available: " f"{[candidate.label for candidate in exposures]}" )
@property def S(self) -> Any: """Return the composed instantaneous scalar scattering matrix.""" return self._compile().scattering @property def parameters(self) -> Mapping[str, Any]: """Return bindable scalar parameters of the network and retained responses. Paths are ``scattering.<output>.<input>``, ``component.<label>.<name>`` and ``response.<mode>.<name>``. A retained mode response has ``freq`` in GHz, a dimensionless ``weight`` and ``internal_rate`` in 1/ns. The ``internal_rate`` already includes the weight. """ values: dict[str, Any] = {} if isinstance(self._authored_scattering, Mapping): for (output, input_), value in self._authored_scattering.items(): values[f"scattering.{resolve_label(output)}.{resolve_label(input_)}"] = value for component in self.components: for name, value in component._parameters.items(): values[f"component.{component.label}.{name}"] = value for mode in self._mode_responses: for name in ("freq", "weight", "internal_rate"): values[f"response.{mode.label}.{name}"] = getattr(mode, name) return MappingProxyType(values)
[docs] def set_parameter_value(self, name: str, value: Any) -> None: """Set one network-owned scalar on an isolated structural copy. Parameters ---------- name : str Path below ``scattering``, ``component`` or ``response``. value : Any Replacement scalar in the parameter's units. :attr:`parameters` gives the units of retained mode responses. """ parts = name.split(".") if ( len(parts) == 3 and parts[0] == "scattering" and isinstance(self._authored_scattering, dict) ): key = (parts[1], parts[2]) if key in self._authored_scattering: self._authored_scattering[key] = value return if len(parts) == 3 and parts[0] == "component": component = self._components.get(parts[1]) if component is not None and parts[2] in component._parameters: component._parameters[parts[2]] = value return if len(parts) == 3 and parts[0] == "response" and parts[2] in {"freq", "weight", "internal_rate"}: for index, mode in enumerate(self._mode_responses): if mode.label == parts[1]: modes = list(self._mode_responses) modes[index] = replace(mode, **{parts[2]: value}) self._mode_responses = tuple(modes) return raise KeyError(name)
[docs] def port( self, label: str, *, target: Any | Sequence[Any], rate: Any = None, external_quality_factor: Any = None, operator: Any = None, phase: Any = 0.0, ) -> Port: """Create and add one quantum coupling port. Parameters ---------- label : str Unique port label. target, rate, external_quality_factor, operator, phase See :class:`~quchip.chip.ports.Port`. Returns ------- Port The network-owned port, whose ``input``, ``output`` and ``side`` terminals can be connected immediately. """ port = Port( target, rate=rate, external_quality_factor=external_quality_factor, operator=operator, phase=phase, label=label, ) self._add_port(port) return port
[docs] def component( self, label: str, *, scattering: Any, terminals: Sequence[str] | None = None, ) -> SLHComponent: """Add a scalar scattering component with named terminals. Parameters ---------- label : str Unique component label. scattering : square array-like Dimensionless scattering matrix. Rows are outputs and columns are inputs, and concrete matrices must be unitary. terminals : sequence of str, optional Names for both input and output terminals. Defaults to ``("signal",)`` for one channel and numeric names otherwise. Returns ------- SLHComponent Component handle used with :meth:`connect`, :meth:`cascade`, or :meth:`link`. """ return self._scattering_component(label, scattering=scattering, terminals=terminals, kind="scattering")
def _scattering_component( self, label: str, *, scattering: Any, terminals: Sequence[str] | None, kind: str, parameters: dict[str, Any] | None = None, ) -> SLHComponent: shape = getattr(scattering, "shape", None) if shape is None: shape = np.shape(scattering) if len(shape) != 2 or shape[0] != shape[1]: raise ValueError(f"SLHComponent scattering must be square, got shape {shape}.") size = int(shape[0]) names = tuple(terminals) if terminals is not None else ( ("signal",) if size == 1 else tuple(str(index) for index in range(size)) ) if len(names) != size or len(set(names)) != len(names): raise ValueError("Component terminal names must be unique and match scattering size.") component = SLHComponent( label=label, input_names=names, output_names=names, scattering=scattering, _network_token=self._token, _local_ports=tuple(None for _ in names), _kind=kind, _parameters={} if parameters is None else parameters, ) return self._add_component(component)
[docs] def through(self, label: str) -> SLHComponent: """Add a one-channel identity through component. Parameters ---------- label : str Component label. """ return self._scattering_component(label, scattering=[[1.0]], terminals=("signal",), kind="through")
[docs] def phase_shift(self, label: str, *, phase: Any) -> SLHComponent: """Add a one-channel phase shift. Parameters ---------- label : str Component label. phase : float or array-like Phase in radians. """ xp = select_array_module(contains_tracer(phase)) return self._scattering_component( label, scattering=xp.asarray([[xp.exp(1j * xp.asarray(phase))]]), terminals=("signal",), kind="phase_shift", parameters={"phase": phase}, )
[docs] def beam_splitter(self, label: str, *, eta: Any = 0.5) -> SLHComponent: """Add a directional two-input/two-output splitter. ``eta`` is the power from input ``k`` to output ``k``. With ``t = sqrt(eta)`` and ``r = sqrt(1 - eta)``, the scattering matrix is ``[[t, r], [-r, t]]``. This component has no component ports. Use ``input_terminal()``, ``output_terminal()``, or ``cascade()``. Parameters ---------- label : str Component label. eta : float or array-like, default=0.5 Power transmission in [0, 1]. """ matrix = self._transmission_matrix(eta) return self._scattering_component( label, scattering=matrix, terminals=("left", "right"), kind="beam_splitter", parameters={"eta": eta} )
[docs] def hybrid90(self, label: str) -> SLHComponent: """Add a directional two-input/two-output ideal 90-degree hybrid. The scattering matrix is ``[[1, 1j], [1j, 1]] / sqrt(2)``. This component has no component ports. Use ``input_terminal()``, ``output_terminal()``, or ``cascade()``. Parameters ---------- label : str Unique component label. """ return self._scattering_component( label, scattering=np.asarray([[1.0, 1j], [1j, 1.0]], dtype=complex) / np.sqrt(2.0), terminals=("left", "right"), kind="hybrid90", )
[docs] def permutation(self, label: str, *, order: Sequence[int]) -> SLHComponent: """Add an output permutation. Parameters ---------- label : str Unique component label. order : sequence of int Input column selected by each output row. """ order = tuple(order) if sorted(order) != list(range(len(order))): raise ValueError("Permutation order must contain each input index exactly once.") names = tuple(str(index) for index in range(len(order))) return self._permutation_component(label, names, order, kind="permutation", sided=False)
[docs] def attenuator( self, label: str, *, eta: Any = None, loss_db: Any = None, thermal_occupation: Any = None, ) -> SLHComponent: """Add a reciprocal two-sided attenuator with power transmission ``eta``. Each direction has amplitude transmission ``sqrt(eta)`` and couples to one of two hidden loss channels with amplitude ``sqrt(1-eta)``. They default to vacuum. ``thermal_occupation`` gives both loads a mean thermal population in quanta, constant across the modeled band. Specify either power transmission ``eta`` or positive ``loss_db``. Parameters ---------- label : str Unique component label. eta : scalar or None, default=None Power transmission in the interval [0, 1]. loss_db : scalar or None, default=None Positive power loss in dB, mutually exclusive with ``eta``. thermal_occupation : scalar or None, default=None Mean occupation of each hidden load in quanta. ``None`` means vacuum. """ power = {key: value for key, value in (("eta", eta), ("loss_db", loss_db)) if value is not None} transmission = attenuation_value(power) component = SLHComponent( label=label, input_names=("1", "2", "vacuum_1", "vacuum_2"), output_names=("1", "2", "vacuum_1", "vacuum_2"), scattering=self._attenuator_matrix(transmission), _network_token=self._token, sides=("1", "2"), _local_ports=(None,) * 4, _hidden_pairs=tuple( (name, name, f"hidden.{label}.{name}") for name in ("vacuum_1", "vacuum_2") ), _row_inputs=((1, 3), (0, 2), (0, 2), (1, 3)), _kind="attenuator", _parameters={**power, **noise_parameters(thermal_occupation)}, ) return self._add_component(component)
[docs] def delay(self, label: str, *, duration: Any) -> SLHComponent: """Add a two-sided reference section with duration ``duration`` ns. The section shifts fields at exposure planes but does not introduce retardation or memory into the Markovian dynamics. Place it with :meth:`link` or :meth:`connect` like any other component. The compiler peels adjacent runs from each exposure leg, so the section never enters Markovian ``S``, ``L``, or ``H``. Every reference section must belong to an exposed run or an acyclic downstream output graph. Its duration is tracked at ``network.component.<label>.duration``. Parameters ---------- label : str Component label. duration : float or array-like Reference-plane delay in ns. """ ReferenceDelay(label, duration) return self._reference_component(label, kind="delay", parameters={"duration": duration})
[docs] def filter( self, label: str, *, transfer: Callable[..., Any], thermal_occupation: Any = None, **parameters: Any, ) -> SLHComponent: """Add a two-sided passive filter reference section. ``transfer(frequency, **parameters)`` must accept scalar or array frequencies in GHz and return a broadcast-compatible complex value without concretizing traced inputs. Each keyword parameter is tracked at ``network.component.<label>.<name>``. Place the section with :meth:`link` or :meth:`connect`. The compiler peels it from an adjacent exposure leg, so it never enters the Markovian ``S``, ``L``, or ``H``. The section reshapes the propagating field but leaves every collapse operator unchanged, including the radiative (Purcell) decay of coupled devices. Model a Purcell filter as a :class:`~quchip.Resonator` between the readout mode and the port instead (see the Purcell filtering guide). Continuous-wave APIs evaluate the transfer at each frequency. Transient APIs use its narrowband value at the applicable carrier. Concrete evaluations with ``|H| > 1`` raise. You cannot serialize networks that contain filters with :meth:`to_dict`. ``Chip.clone()`` and ``Chip.with_params()`` preserve the callable. Declare ``thermal_occupation`` in quanta, constant across the modeled band, for a matched absorptive realization that emits (1-|H|²)n. A scalar transfer alone does not distinguish absorption from reflection. Colored thermal emission cannot feed a quantum coupling through a reference section. Use a dynamical filter/bath model for that case. Parameters ---------- label : str Unique component label. transfer : callable Passive complex amplitude transfer versus frequency in GHz. thermal_occupation : scalar or None, default=None Mean matched-load occupation in quanta. ``None`` means vacuum. **parameters : Any Named transfer-function parameters tracked by the network. """ parameters.update(noise_parameters(thermal_occupation)) return self._reference_component(label, kind="filter", parameters=dict(parameters), transfer=transfer)
[docs] def mode_reflection( self, label: str, *, freq: Any, external_rate: Any, internal_rate: Any = 0.0, reference_freq: Any = None, ) -> SLHComponent: """Add a two-sided reference section that reflects like a damped linear mode. Each pass multiplies the field by ``H(f)``, with ``H(f)² = ((κ_i − κ_e)/2 − iΩ) / ((κ_e + κ_i)/2 − iΩ)`` and ``Ω = 2π(f − freq)``, the reflection of a mode coupled to the line at rate ``κ_e`` with internal loss ``κ_i``. On a reflection line the incident field passes once inbound and once outbound, so the plane acquires that reflection. ``H`` is the continuous square root whose real part is nonnegative at ``reference_freq``. When ``reference_freq`` is ``None``, ``H`` is the root that tends to one far below ``freq``. The branch fixes the phase of fields that cross only one leg, such as emission from the devices behind the section. Like :meth:`filter`, the section stays outside the Markovian ``S``, ``L``, and ``H`` and leaves every collapse operator unchanged. Continuous-wave APIs evaluate ``H`` at each frequency. Transient APIs use its value at the applicable carrier. The internal-loss bath is vacuum. :func:`~quchip.eliminate` inserts this section when it removes a mode whose port is alone on its plane. A shared line gets :meth:`mode_transmission`. Every parameter is tracked at ``network.component.<label>.<name>`` and serializes. Parameters ---------- label : str Unique component label. freq : float or array-like Mode frequency in GHz. external_rate : float or array-like Coupling rate ``κ_e`` to this line, in 1/ns. internal_rate : float or array-like, default=0.0 Internal loss rate ``κ_i`` in 1/ns. reference_freq : float or array-like or None, default=None Frequency in GHz that selects the square-root branch. """ parameters = {"freq": freq, "external_rate": external_rate, "internal_rate": internal_rate} if reference_freq is not None: parameters["reference_freq"] = reference_freq return self._reference_component( label, kind="mode_reflection", parameters=parameters, transfer=_mode_reflection_transfer, )
[docs] def mode_transmission( self, label: str, *, freq: Any, external_rate: Any, internal_rate: Any = 0.0, ) -> SLHComponent: """Add a one-pass reference section for a damped linear mode. Side 1 to side 2 multiplies the field by ``S_r(f) = ((κ_i - κ_e)/2 - iΩ) / ((κ_e + κ_i)/2 - iΩ)``, with ``Ω = 2π(f - freq)``. Reverse propagation is transparent. With zero external coupling, the section is transparent at every frequency. The section acts on one leg and stays outside the Markovian ``S``, ``L``, and ``H``. Its internal-loss bath is vacuum. Continuous-wave APIs evaluate the transfer at each frequency. Transient APIs use its value at the applicable carrier. quchip tracks every parameter at ``network.component.<label>.<name>``, and the section serializes. Parameters ---------- label : str Unique component label. freq : float or array-like Mode frequency in GHz. external_rate : float or array-like Coupling rate ``κ_e`` to this line, in 1/ns. internal_rate : float or array-like, default=0.0 Internal loss rate ``κ_i`` in 1/ns. Returns ------- SLHComponent Two-sided component with forward transfer and transparent reverse propagation. """ return self._reference_component( label, kind="mode_transmission", parameters={"freq": freq, "external_rate": external_rate, "internal_rate": internal_rate}, transfer=_mode_transmission_transfer, )
[docs] def amplifier( self, label: str, *, added_noise: Any, gain: Any = None, gain_db: Any = None, ) -> SLHComponent: """Add a phase-preserving amplifier reference section to an output line. ``gain`` is power gain ``G``. ``added_noise`` is input-referred symmetrized noise in quanta and must be at least ``(1 - 1/G) / 2``. Forward propagation from side 1 to side 2 multiplies field amplitudes by ``sqrt(G)``. Reverse propagation is transparent. Place side 1 toward the chip and side 2 toward the exposed output plane. The compiler rejects a section that would amplify an incident field into the chip or put the output plane on side 1. Both parameters are tracked at ``network.component.<label>.<name>`` and remain sweepable and differentiable. Acyclic downstream splitters keep its shared output noise. The section remains outside Markovian ``S``, ``L``, and ``H`` and serializes normally. ``gain_db`` can replace linear ``gain``. Added quanta are constant across the modeled band and exclude the input's own noise. Parameters ---------- label : str Component label. added_noise : float or array-like Input-referred symmetrized added noise in quanta. gain, gain_db : float or array-like, optional Power gain, specified linearly or in dB. Supply at most one. """ parameters = {key: value for key, value in ( ("gain", gain), ("gain_db", gain_db), ("added_noise", added_noise)) if value is not None} ReferenceAmplifier(label, *amplifier_values(parameters)) return self._reference_component( label, kind="amplifier", parameters=parameters, )
def _reference_component( self, label: str, *, kind: str, parameters: dict[str, Any], transfer: Callable[..., Any] | None = None, ) -> SLHComponent: component = SLHComponent( label=label, input_names=("1", "2"), output_names=("1", "2"), scattering=None, _network_token=self._token, sides=("1", "2"), _local_ports=(None, None), _kind=kind, _parameters=parameters, _transfer=transfer, ) return self._add_component(component)
[docs] def circulator(self, label: str, *, ports: int = 3) -> SLHComponent: """Add an ideal circulator routing side ``k`` to side ``k + 1``. The highest-numbered side routes back to side 1. Parameters ---------- label : str Unique component label. ports : int, default=3 Number of physical sides, which must be at least three. """ if ports < 3: raise ValueError(f"A circulator needs at least three sides, got {ports}.") names = tuple(str(index) for index in range(1, ports + 1)) sources = tuple((row - 1) % ports for row in range(ports)) return self._permutation_component(label, names, sources, kind="circulator")
[docs] def isolator( self, label: str, *, thermal_occupation: Any = None, ) -> SLHComponent: """Add an ideal isolator routing side 1 to side 2. The reverse field is dumped into ``hidden.<label>.load``, whose thermal population travels back toward side 1. ``thermal_occupation`` is in quanta, constant across the modeled band, and defaults to vacuum. Parameters ---------- label : str Unique component label. thermal_occupation : scalar or None, default=None Mean hidden-load occupation in quanta. ``None`` means vacuum. """ component = self._permutation_component( label, ("1", "2", "load"), (2, 0, 1), kind="isolator", hidden_pairs=(("load", "load", f"hidden.{label}.load"),), ) component._parameters.update(noise_parameters(thermal_occupation)) return component
[docs] def termination( self, label: str, *, thermal_occupation: Any = None, ) -> SLHComponent: """Add a matched one-sided load with an optional thermal input state. ``thermal_occupation`` is the mean thermal population in quanta, constant across the modeled band, and defaults to vacuum. Parameters ---------- label : str Unique component label. thermal_occupation : scalar or None, default=None Mean load occupation in quanta. ``None`` means vacuum. """ component = self._permutation_component( label, ("1", "load"), (1, 0), kind="termination", hidden_pairs=(("load", "load", f"hidden.{label}.load"),), ) component._parameters.update(noise_parameters(thermal_occupation)) return component
def _permutation_component( self, label: str, names: tuple[str, ...], sources: Sequence[int], *, kind: str, hidden_pairs: tuple[tuple[str, str, str], ...] = (), sided: bool = True, ) -> SLHComponent: """Add a selection component whose output row ``r`` copies input ``sources[r]``.""" size = len(names) matrix = np.zeros((size, size), dtype=complex) matrix[np.arange(size), sources] = 1.0 hidden = {pair[0] for pair in hidden_pairs} component = SLHComponent( label=label, input_names=names, output_names=names, scattering=matrix, _network_token=self._token, sides=tuple(name for name in names if name not in hidden) if sided else (), _local_ports=(None,) * size, _hidden_pairs=hidden_pairs, _row_inputs=tuple((source,) for source in sources), _kind=kind, ) return self._add_component(component)
[docs] def connect(self, output: FieldTerminal, input: FieldTerminal) -> None: """Connect one component output to one component input. Parameters ---------- output, input : FieldTerminal Unused directional terminals from this network. """ self._validate_terminal(output, "output") self._validate_terminal(input, "input") self._reject_hidden_terminal(output) self._reject_hidden_terminal(input) if input.key in self._connections: raise ValueError(f"Input terminal {input.key} is already connected.") if output.key in self._used_outputs: raise ValueError(f"Output terminal {output.key} is already connected.") if self._terminal_is_exposed(input) or self._terminal_is_exposed(output): raise ValueError("An exposed terminal cannot also be connected internally.") self._connections[input.key] = output.key self._used_outputs[output.key] = input.key
[docs] def cascade(self, *items: Port | SLHComponent | FieldTerminal) -> None: """Connect each item's output to the next item's input in sequence. Parameters ---------- *items : Port, SLHComponent, or FieldTerminal At least two compatible endpoints. """ for first, second in self._pairs("cascade", items): self.connect(self._endpoint_of(first, "output"), self._endpoint_of(second, "input"))
@staticmethod def _pairs(name: str, items: tuple[Any, ...]) -> Iterator[tuple[Any, Any]]: if len(items) < 2: raise ValueError(f"{name}() requires at least two items, got {len(items)}") return pairwise(items)
[docs] def expose( self, label: str, *, at: Port | SLHComponent | ComponentPort | None = None, input: FieldTerminal | Port | SLHComponent | None = None, output: FieldTerminal | Port | SLHComponent | None = None, ) -> NetworkPort: """Name and return an external network port. Pass ``at=`` to expose one component port. Use ``input=`` and ``output=`` for separate input and output connections. Ports and components then select their sole or ``signal`` terminal unless explicit terminals are passed. Parameters ---------- label : str Unique external reference-plane label. at : Port, SLHComponent, ComponentPort, or None, default=None Physical side to expose for both directions. input, output : FieldTerminal, Port, SLHComponent, or None, default=None Separate incoming and outgoing connection points. """ if at is not None: if input is not None or output is not None: raise TypeError("expose() takes either at= or input=/output=, not both.") side = self._side_of(at, leaving=None) input, output = side.input, side.output if input is None or output is None: raise TypeError("expose() requires at= or both input= and output=.") input = self._endpoint_of(input, "input") output = self._endpoint_of(output, "output") self._validate_terminal(input, "input") self._validate_terminal(output, "output") self._reject_hidden_terminal(input) self._reject_hidden_terminal(output) if label in {exposure.label for exposure in self._exposures}: raise ValueError(f"Duplicate external network port label {label!r}.") if input.key in self._connections or output.key in self._used_outputs: raise ValueError("Connected terminals cannot also be exposed.") if self._terminal_is_exposed(input) or self._terminal_is_exposed(output): raise ValueError("A terminal cannot belong to more than one external network port.") exposure = NetworkPort(label, input.key, output.key, _network_token=self._token) self._exposures.append(exposure) return exposure
[docs] def validate_for(self, chip: Any) -> None: """Validate every quantum port target against a chip. Parameters ---------- chip : Chip Chip whose device labels must contain every port target. """ for port in self._ports: port.resolve_targets(chip)
[docs] def fingerprint(self) -> tuple[Any, ...]: """Return a conservative structural cache signature.""" return ( self.label, tuple( ( component.label, component.input_names, component.output_names, component._kind, self._cache_value(component.scattering), tuple( (name, self._cache_value(value)) for name, value in component._parameters.items() ), id(component._transfer), ) for component in self.components ), tuple(self._connections.items()), tuple((item.label, item._input_key, item._output_key) for item in self._exposures), self._cache_value(self._authored_scattering), tuple((mode.label, mode.ports, *(self._cache_value(getattr(mode, name)) for name in ("freq", "weight", "internal_rate"))) for mode in self._mode_responses), )
[docs] def resolve( self, base: "ResolvedSLH", *, _compiled: CompiledNetwork | None = None, _generated: Any = None, ) -> "ResolvedSLH": """Compose this boundary onto an input-free SLH value. Parameters ---------- base : ResolvedSLH Resolved quantum-port channels to compose with the network. """ from quchip.engine.ir import ( CollapseTerm, HamiltonianProgram, ResolvedSLH, SLHChannel, StaticTerm, ) if not self._ports: raise ValueError("PortNetwork requires at least one quantum coupling port.") port_channels = {channel.collapse.label: channel for channel in base.external_channels} expected = [port.label for port in self._ports] if set(port_channels) != set(expected) or len(port_channels) != len(expected): raise RuntimeError( "PortNetwork channels do not match assembled chip ports; " f"network={expected}, assembled={list(port_channels)}." ) compiled = self._compile() if _compiled is None else _compiled operators = {label: port_channels[label].coupling for label in expected} channels: list[SLHChannel] = [] for entry, field_channel in zip(compiled.channels, self._field_channels(compiled), strict=True): exposure, mapping = entry.exposure, entry.coupling input_occupation = field_channel.input_occupation frame_frequency = self._common_frame_frequency( mapping, port_channels, boundary=f"exposure {exposure.label!r}" ) coupling = self._materialize_coupling(mapping, operators, key=exposure.label) if ( exposure.label in port_channels and set(mapping) == {exposure.label} and self._is_concrete_one(mapping[exposure.label]) ): channels.append( replace( port_channels[exposure.label], key=exposure.label, accessibility="hidden" if exposure._hidden else "exposed", reference=entry.reference, input_occupation=input_occupation, ) ) continue template = CollapseTerm( operator=coupling, rate=1.0, source=exposure.label, channel="network", frame_frequency=frame_frequency, ) channels.append( SLHChannel( key=exposure.label, accessibility="hidden" if exposure._hidden else "exposed", collapse=template, coupling_operator=coupling, reference=entry.reference, input_occupation=input_occupation, ) ) # A compact caller supplies the generated Hamiltonian, formed on each pair's joint support before # projection, because composing projected ports drops exchanges through labels outside its space. generated = self._materialize_generated_hamiltonian(compiled.generated_pairs, operators, _generated) static_terms = base.H.static_terms if generated is not None: static_terms = (*static_terms, StaticTerm(operator=generated, origin="network")) hidden = base.hidden_channels full_size = len(channels) + len(hidden) xp = select_array_module(contains_tracer(compiled.scattering)) full_support = np.eye(full_size, dtype=bool) full_support[: len(channels), : len(channels)] = compiled.support full_scattering = (pad_mixing(xp.asarray(compiled.scattering, dtype=complex), full_size, xp) if channels else xp.eye(full_size, dtype=complex)) return ResolvedSLH( scattering=full_scattering, hamiltonian=HamiltonianProgram( static_terms=static_terms, dynamic_terms=base.H.dynamic_terms, ), channels=tuple((*channels, *hidden)), support=full_support, output_network=compiled.output_network, )
def _field_channels(self, compiled: CompiledNetwork) -> tuple[FieldChannel, ...]: """Resolve source states once for both operator and compact mode solvers.""" xp = select_array_module(contains_tracer(compiled.scattering)) scattering = xp.asarray(compiled.scattering) fields = [] for index, entry in enumerate(compiled.channels): exposure = entry.exposure occupation = (thermal_occupation_value(self._components[exposure._input_key[0]]._parameters) if exposure._hidden else None) inbound = entry.reference.inbound if has_colored_noise(inbound): # Input j drives K_j = (S†L)_j; shared output support alone # cannot distinguish backaction from a downstream splitter. coupling = self._combine_maps(xp.conj(scattering[:, index]), [dict(c.coupling) for c in compiled.channels]) if self._active_mapping_sources(coupling): raise ValueError("A thermal reference filter feeds a quantum coupling; use an explicit " "dynamical filter/bath for colored thermal backaction.") elif any(source_occupation(e) is not None for e in inbound): # Filters before all occupied sources do not color their noise. flat = tuple(e for e in inbound if not isinstance(e, ReferenceFilter)) occupation = noise_density(flat, 0.0, xp) fields.append(FieldChannel(exposure.label, entry.reference, occupation)) return tuple(fields)
[docs] def to_dict(self) -> dict[str, Any]: """Serialize the network graph, quantum ports and retained mode responses.""" filters = [component.label for component in self.components if component._kind == "filter"] if filters: raise TypeError( f"PortNetwork filter components {sorted(filters)} use Python " "transfer callables and cannot be serialized by to_dict(). Chip.clone() and " "Chip.with_params() preserve them." ) return { "label": self.label, "ports": [port.to_dict() for port in self._ports], **({"mode_responses": [ {"label": mode.label, "ports": list(mode.ports), **{name: self._serialize_scalar(getattr(mode, name)) for name in ("freq", "weight", "internal_rate")}} for mode in self._mode_responses ]} if self._mode_responses else {}), "scattering": self._serialize_boundary(self._authored_scattering), "components": [ self._serialize_component(component) for component in self.components if not any(port is not None for port in component._local_ports) ], "connections": [ { "output": list(output_key), "input": list(input_key), } for input_key, output_key in self._connections.items() ], "exposures": [ { "label": exposure.label, "input": list(exposure._input_key), "output": list(exposure._output_key), } for exposure in self._exposures ], }
def _serialize_component(self, component: SLHComponent) -> dict[str, Any]: kind = component._kind if kind == "scattering": return { "label": component.label, "kind": kind, "terminals": list(component.input_names), "scattering": self._serialize_matrix(component.scattering), } if kind == "circulator": parameters: dict[str, Any] = {"ports": len(component.sides)} elif kind == "permutation": parameters = {"order": [inputs[0] for inputs in component._row_inputs or ()]} else: parameters = {} parameters.update( (name, self._serialize_scalar(value)) for name, value in component._parameters.items() ) return {"label": component.label, "kind": kind, "parameters": parameters}
[docs] @classmethod def from_dict(cls, data: Mapping[str, Any]) -> "PortNetwork": """Reconstruct a network produced by :meth:`to_dict`. Parameters ---------- data : mapping Serialized network payload. """ unknown = set(data) - { "label", "ports", "scattering", "components", "mode_responses", "connections", "exposures", } if unknown: raise TypeError(f"Unsupported serialized PortNetwork fields: {sorted(unknown)}") network = cls( label=data.get("label"), scattering=cls._deserialize_boundary(data.get("scattering")), ) for payload in data.get("ports", []): network._add_port(Port.from_dict(payload)) for payload in data.get("components", []): if "kind" not in payload: raise TypeError(f"Serialized PortNetwork component {payload.get('label')!r} has no kind.") kind = payload["kind"] if kind == "scattering": network.component( payload["label"], scattering=cls._deserialize_matrix(payload["scattering"]), terminals=tuple(payload["terminals"]), ) continue if kind not in _SERIALIZED_FACTORIES: raise TypeError(f"Unknown serialized PortNetwork component kind {kind!r}.") parameters = { name: cls._deserialize_scalar(value) for name, value in payload.get("parameters", {}).items() } getattr(network, kind)(payload["label"], **parameters) for payload in data.get("connections", []): input_component, input_name = payload["input"] output_component, output_name = payload["output"] network.connect( FieldTerminal(output_component, output_name, "output", network._token), FieldTerminal(input_component, input_name, "input", network._token), ) for payload in data.get("exposures", []): input_component, input_name = payload["input"] output_component, output_name = payload["output"] network.expose( payload["label"], input=FieldTerminal(input_component, input_name, "input", network._token), output=FieldTerminal(output_component, output_name, "output", network._token), ) known = {port.label for port in network._ports} for payload in data.get("mode_responses", ()): ports = tuple(payload["ports"]) if not ports or len(set(ports)) != len(ports) or not known.issuperset(ports): raise ValueError(f"Mode response {payload['label']!r} must name distinct ports of this network, " f"got {list(ports)}. Available ports: {sorted(known)}.") network._mode_responses = tuple( ModeResponse(payload["label"], tuple(payload["ports"]), *(cls._deserialize_scalar(payload[name]) for name in ("freq", "weight", "internal_rate"))) for payload in data.get("mode_responses", ()) ) return network
[docs] def copy(self) -> "PortNetwork": """Return an independent structural copy with label-based port targets.""" return self._copy_with_port_replacements({})
[docs] def restrict(self, ports: Sequence[str | Port]) -> "PortNetwork": """Return an independent sub-network of field-graph components reachable from ``ports``. Every reachable component is copied with its connections, exposures, tracked parameters, and filter callables. Authored boundary scattering is limited to the kept planes. A reachable subgraph that also touches an unselected port raises, because cutting it would change the SLH dynamics. Parameters ---------- ports : sequence of str or Port Quantum coupling ports whose field subgraphs are kept. Returns ------- PortNetwork An independent network with label-based port targets. Raises ------ KeyError A selected port is not part of this network. ValueError A reachable subgraph also touches an unselected port, boundary scattering couples kept and dropped planes, or the kept ports hold only part of a retained mode response. """ labels = {port if isinstance(port, str) else port.label for port in ports} known = {port.label for port in self._ports} missing = sorted(labels - known) if missing: raise KeyError(f"Unknown PortNetwork ports {missing}; available: {sorted(known)}.") neighbours: dict[str, set[str]] = {component.label: set() for component in self.components} edges = [*self._connections.items(), *((item._input_key, item._output_key) for item in self._exposures)] for first, second in edges: neighbours[first[0]].add(second[0]) neighbours[second[0]].add(first[0]) keep: set[str] = set() stack = list(labels) while stack: label = stack.pop() if label not in keep: keep.add(label) stack.extend(neighbours[label]) stray = sorted(keep & known - labels) if stray: raise ValueError( f"PortNetwork subgraph reached from ports {sorted(labels)} also touches ports {stray}; " "restrict them together." ) return self._copy_graph({}, keep)
[docs] def include(self, template: "PortNetwork", *, prefix: str) -> IncludedNetwork: """Copy a reusable network template into this network under ``prefix``. Components and connections are copied with labels ``prefix/label``. Tracked parameters therefore use paths such as ``network.component.prefix/label.name``. Filter callables are kept. The template remains unchanged, so you can include it again under another prefix. The template's exposures become interfaces rather than host planes. Exposures made with ``expose(..., at=...)`` are available through ``side(name)``. Asymmetric exposures use ``input(name)`` and ``output(name)``. Wire the returned interfaces with :meth:`link`, :meth:`cascade`, or :meth:`expose`. Parameters ---------- template : PortNetwork A network without quantum ports or authored boundary scattering. prefix : str A label prefix for the copied components, unique in this network. Returns ------- IncludedNetwork Accessors for the copied components and interfaces. Raises ------ ValueError The template owns ports or boundary scattering, or ``prefix`` is already in use. """ if template is self: raise ValueError("A PortNetwork cannot include itself.") if template._ports: raise ValueError("Included networks cannot own quantum ports; declare ports on the host network.") if template._authored_scattering is not None: raise ValueError("Included networks cannot author boundary scattering; declare it on the host network.") if any(label.startswith(f"{prefix}/") for label in self._components): raise ValueError(f"Prefix {prefix!r} is already used in this PortNetwork.") def rename(key: TerminalKey) -> TerminalKey: return (f"{prefix}/{key[0]}", key[1]) for component in template.components: self._add_component( template._clone_component(component, self._token, label=f"{prefix}/{component.label}") ) for input_key, output_key in template._connections.items(): new_input, new_output = rename(input_key), rename(output_key) self._connections[new_input] = new_output self._used_outputs[new_output] = new_input interfaces = { exposure.label: (rename(exposure._input_key), rename(exposure._output_key)) for exposure in template._exposures } return IncludedNetwork(prefix, self, MappingProxyType(interfaces))
@staticmethod def _clone_component(component: SLHComponent, token: object, *, label: str | None = None) -> SLHComponent: """Return a portless component copy bound to ``token``, optionally relabelled.""" new_label = component.label if label is None else label return replace( component, label=new_label, scattering=copy_value(component.scattering), _parameters=copy_value(component._parameters), _network_token=token, _hidden_pairs=tuple( (first, second, f"hidden.{new_label}.{first}") for first, second, _ in component._hidden_pairs ), ) def _line_exposure(self, port_label: str) -> tuple[str, tuple[str, ...], tuple[str, ...]]: """Return the plane and the ports upstream and downstream of a port. The port must couple to one visible channel without channel mixing. Every other port on that channel must pass it strictly in series. Generated pairs determine the order without reading their coefficients. """ compiled = self._compile() rows = [index for index, channel in enumerate(compiled.channels) if port_label in self._active_mapping_sources(channel.coupling)] support = np.asarray(compiled.support, dtype=bool) if len(rows) == 1: (row,) = rows channel = compiled.channels[row] others = np.arange(support.shape[0]) != row if (not channel.exposure._hidden and not np.any(support[row, others]) and not np.any(support[others, row])): ports = self._active_mapping_sources(channel.coupling) predecessors: dict[str, set[str]] = {label: set() for label in ports} for downstream, upstream, _coefficient in compiled.generated_pairs: if downstream in predecessors and upstream in predecessors: predecessors[downstream].add(upstream) for middle in ports: for downstream in ports: if middle in predecessors[downstream]: predecessors[downstream].update(predecessors[middle]) upstream_ports = predecessors[port_label] downstream_ports = {label for label in ports if port_label in predecessors[label]} if (not upstream_ports.intersection(downstream_ports) and upstream_ports | downstream_ports == set(ports) - {port_label}): ordered = sorted(ports, key=lambda label: len(predecessors[label])) return (channel.exposure.label, tuple(label for label in ordered if label in upstream_ports), tuple(label for label in ordered if label in downstream_ports)) raise NotImplementedError( f"Port {port_label!r} shares its external plane with other fields; eliminating its " "mode would need the mode's frequency-dependent scattering inside the network. " "Keep the port-coupled mode." ) def _insert_reference_section( self, exposure_label: str, section: SLHComponent, *, reverse: bool = False, ) -> None: """Insert a two-sided reference section where an exposure's reference run meets the core. Side 2 faces the external plane and side 1 the Markov boundary. Existing reference sections of the run stay outside the new one. Every external plane becomes explicit so that the channel order does not change. With ``reverse=True``, side 1 faces the plane instead. """ planes = [exposure for exposure in self._effective_exposures() if not exposure._hidden] index = next(i for i, exposure in enumerate(planes) if exposure.label == exposure_label) exposure = planes[index] boundary_input, boundary_output, _ = self._peel(exposure) inner, outer = (section.label, "1"), (section.label, "2") if reverse: inner, outer = outer, inner if exposure._input_key == boundary_input: exposure = replace(exposure, _input_key=outer) else: feeder = self._connections.pop(boundary_input) self._connections[outer] = feeder self._used_outputs[feeder] = outer self._connections[boundary_input] = inner self._used_outputs[inner] = boundary_input if exposure._output_key == boundary_output: exposure = replace(exposure, _output_key=outer) else: sink = self._used_outputs.pop(boundary_output) self._connections[sink] = outer self._used_outputs[outer] = sink self._connections[inner] = boundary_output self._used_outputs[boundary_output] = inner planes[index] = exposure self._exposures = planes def _copy_with_port_replacements( self, replacements: Mapping[str, Port], ) -> "PortNetwork": """Copy this graph while replacing selected quantum coupling ports.""" known = {port.label for port in self._ports} unknown = set(replacements) - known if unknown: raise KeyError(f"Cannot replace unknown PortNetwork ports: {sorted(unknown)}.") for label, replacement in replacements.items(): if replacement.label != label: raise ValueError( f"Replacement for port {label!r} must retain that label, got {replacement.label!r}." ) return self._copy_graph(replacements, None) def _restricted_boundary(self, keep: set[str]) -> Any: """Return the authored boundary scattering limited to planes inside ``keep``.""" authored = self._authored_scattering if authored is None: return None planes = [exposure for exposure in self._effective_exposures() if not exposure._hidden] kept = {exposure.label for exposure in planes if exposure._input_key[0] in keep} if isinstance(authored, Mapping): restricted = {} for (output, input_), value in authored.items(): has_output, has_input = resolve_label(output) in kept, resolve_label(input_) in kept if has_output != has_input: raise ValueError( f"Boundary scattering entry {(resolve_label(output), resolve_label(input_))} couples " "planes of different field subgraphs." ) if has_output: restricted[(output, input_)] = copy_value(value) return restricted or None if len(kept) == len(planes): return copy_value(authored) if not kept: return None raise ValueError("Matrix-form boundary scattering spans planes of different field subgraphs.") def _copy_graph(self, replacements: Mapping[str, Port], keep: set[str] | None) -> "PortNetwork": """Copy the components in ``keep`` (all when ``None``) with their wiring and parameters.""" kept = keep if keep is not None else {component.label for component in self.components} for mode in self._mode_responses: if set(mode.ports) & kept and not set(mode.ports) <= kept: raise ValueError(f"Retained mode response {mode.label!r} needs all of its ports " f"{sorted(mode.ports)}. Restrict them together.") scattering = copy_value(self._authored_scattering) if keep is None else self._restricted_boundary(kept) copied = PortNetwork(scattering=scattering, label=self.label) for component in self.components: if component.label not in kept: continue if any(port is not None for port in component._local_ports): assert len(component._local_ports) == 1 local_port = component._local_ports[0] assert local_port is not None copied._add_port(replacements.get(local_port.label, local_port).copy()) else: copied._add_component(self._clone_component(component, copied._token)) copied._connections = { input_key: output_key for input_key, output_key in self._connections.items() if input_key[0] in kept } copied._used_outputs = {output_key: input_key for input_key, output_key in copied._connections.items()} copied._exposures = [ replace(exposure, _network_token=copied._token) for exposure in self._exposures if exposure._input_key[0] in kept ] copied._mode_responses = tuple( replace(mode, **{name: copy_value(getattr(mode, name)) for name in ("freq", "weight", "internal_rate")}) for mode in self._mode_responses if set(mode.ports) <= kept ) return copied
[docs] def physics_notes(self) -> list[str]: """Return the boundary convention and approximation scope.""" notes = [ "This instantaneous Markovian boundary uses b_out = S b_in + L; scalar S is unitary after " "explicit loss dilation; declared thermal input states enter the quantum dynamics.", "Reference sections shift incident and reported fields at external planes only; they do not " "enter S, L, or H and cannot sit inside feedback loops. Acyclic output mixing preserves joint noise.", ] if self._mode_responses: notes.append( "Retained multiport harmonic poles enter vacuum VNA sweeps at their original port locations. " "Quantum dynamics use only the transformed Markov channels. " "The dissipative reduction remains dispersive." ) return notes
[docs] def dynamical_supports( self, chip: Any, *, _compiled: CompiledNetwork | None = None, ) -> tuple[tuple[str, ...], ...]: """Return device groups coupled by cascade-generated Hamiltonian terms. Static scattering alone is excluded, because a unitary mixer changes field coordinates but leaves the summed Lindbladian invariant. Each downstream/upstream coupling pair generated by SLH series composition contributes the union of its two quantum-port supports. Parameters ---------- chip : Chip Chip used to resolve port target supports. """ supports = { port.label: port.resolve_targets(chip) for port in self._ports } groups: list[tuple[str, ...]] = [] compiled = self._compile() if _compiled is None else _compiled for downstream, upstream, _coefficient in self._active_pairs(compiled.generated_pairs): touched = set(supports[downstream] + supports[upstream]) members = tuple(device.label for device in chip.devices if device.label in touched) if len(members) > 1 and members not in groups: groups.append(members) return tuple(groups)
[docs] def fed_ports( self, chip: Any, exposure: str, *, _compiled: CompiledNetwork | None = None, ) -> list[tuple[Any, Any]]: """Return ``(port, coefficient)`` pairs reached from ``exposure``. Each coefficient combines the scattering entry from the exposure to a channel with that port's weight in the channel. A coherent input ``β`` therefore reaches the port coupling as ``c = coefficient · β``. Parameters ---------- chip : Chip Chip used to resolve the network boundary. exposure : str External input reference-plane label. """ compiled = self._compile() if _compiled is None else _compiled labels = [item.exposure.label for item in compiled.channels] if exposure not in labels: raise ValueError(f"Unknown resolved port {exposure!r}.") column = labels.index(exposure) fed: list[tuple[Any, Any]] = [] for row, channel in enumerate(compiled.channels): if not compiled.support[row, column]: continue mapping = channel.coupling gain = compiled.scattering[row][column] for source in self._active_mapping_sources(mapping): fed.append((chip.port(source), gain * mapping[source])) return fed
def _responses_by_output( self, *, recorded: bool = False, _compiled: CompiledNetwork | None = None, ) -> dict[str, tuple[str, ...]]: """Return the retained mode responses whose ports emit into each output channel. The keys are the exposure labels of the affected channels. A channel whose coupling has no active weight on a response port is absent. With ``recorded``, a port also counts the removed modes that it records, whose kept response its Markov field lacks. """ owners: dict[str, set[str]] = {} for mode in self._mode_responses: for label in mode.ports: owners.setdefault(label, set()).add(mode.label) for port in self._ports if recorded else (): if port._omitted_responses: owners.setdefault(port.label, set()).update(port._omitted_responses) if not owners: return {} compiled = self._compile() if _compiled is None else _compiled reached: dict[str, tuple[str, ...]] = {} for channel in compiled.channels: labels = sorted({label for source in self._active_mapping_sources(channel.coupling) for label in owners.get(source, ())}) if labels: reached[channel.exposure.label] = tuple(labels) return reached @staticmethod def _active_pairs( pairs: Sequence[tuple[str, str, Any]], ) -> tuple[tuple[str, str, Any], ...]: """Drop only generated Hamiltonian pairs known to have zero weight.""" return tuple( (downstream, upstream, coefficient) for downstream, upstream, coefficient in pairs if not PortNetwork._is_concrete_zero(coefficient) ) def _active_generated_pairs(self) -> tuple[tuple[str, str, Any], ...]: """Return quantum-port pairs that generate a series Hamiltonian.""" return self._active_pairs(self._compile().generated_pairs) @staticmethod def _serialize_matrix(value: Any) -> dict[str, Any] | None: if value is None: return None if contains_tracer(value): raise TypeError("Traced PortNetwork scattering cannot be serialized.") matrix = np.asarray(value, dtype=complex) return {"real": matrix.real.tolist(), "imag": matrix.imag.tolist()} @staticmethod def _deserialize_matrix(value: Mapping[str, Any] | None) -> np.ndarray | None: if value is None: return None return np.asarray(value["real"]) + 1j * np.asarray(value["imag"]) @classmethod def _serialize_boundary(cls, value: Any) -> Any: if value is None: return None if callable(value): raise TypeError("Callable PortNetwork scattering is not serializable.") if isinstance(value, Mapping): return { "kind": "mapping", "entries": [ { "output": resolve_label(output), "input": resolve_label(input_), "value": cls._serialize_scalar(item), } for (output, input_), item in value.items() ], } return {"kind": "matrix", "value": cls._serialize_matrix(value)} @classmethod def _deserialize_boundary(cls, value: Any) -> Any: if value is None: return None if value["kind"] == "matrix": return cls._deserialize_matrix(value["value"]) if value["kind"] == "mapping": return { (entry["output"], entry["input"]): cls._deserialize_scalar(entry["value"]) for entry in value["entries"] } raise TypeError(f"Unknown serialized PortNetwork scattering kind {value['kind']!r}.") @staticmethod def _serialize_scalar(value: Any) -> dict[str, float]: if contains_tracer(value): raise TypeError("Traced PortNetwork values cannot be serialized.") scalar = complex(np.asarray(value).item()) return {"real": scalar.real, "imag": scalar.imag} @staticmethod def _deserialize_scalar(value: Any) -> Any: # Structural options such as a permutation order or a port count are # plain JSON values and pass through untouched. if not isinstance(value, Mapping): return value scalar = complex(value["real"], value["imag"]) return scalar.real if scalar.imag == 0.0 else scalar def _add_port(self, port: Port) -> None: if not isinstance(port, Port): raise TypeError(f"Expected a Port, got {type(port).__name__}: {port!r}") component = SLHComponent( label=port.label, input_names=("field",), output_names=("field",), scattering=np.asarray([[1.0]], dtype=complex), _network_token=self._token, sides=("field",), _local_ports=(port,), _kind="port", ) self._add_component(component) port._bind_network(self, component) self._ports.append(port) def _add_component(self, component: SLHComponent) -> SLHComponent: if component.label in self._components: raise ValueError(f"Duplicate PortNetwork component label {component.label!r}.") self._components[component.label] = component return component def _effective_exposures(self) -> tuple[NetworkPort, ...]: exposed = list(self._exposures) used = { key for exposure in self._exposures for key in (exposure._input_key, exposure._output_key) } for component in self.components: if not any(port is not None for port in component._local_ports): continue input_key = (component.label, component.input_names[0]) output_key = (component.label, component.output_names[0]) touched = ( input_key in self._connections or output_key in self._used_outputs or input_key in used or output_key in used ) if not touched: exposed.append( NetworkPort( component.label, input_key, output_key, _network_token=self._token, ) ) hidden: list[NetworkPort] = [] for component in self.components: if self._is_reference(component.label): continue for input_name, output_name, label in component._hidden_pairs: hidden.append( NetworkPort( label, (component.label, input_name), (component.label, output_name), _hidden=True, _network_token=self._token, ) ) return tuple((*exposed, *hidden)) def _is_reference(self, label: str) -> bool: kind = self._components[label]._kind if kind in _REFERENCE_KINDS: return True # A passive two-sided section downstream of a reference run belongs # to that same external run. Its directed vacuum/thermal dilation is # retained in ReferenceLoss rather than becoming an internal amplifier. visited: set[str] = set() while kind in {"attenuator", "isolator"} and label not in visited: visited.add(label) previous = self._connections.get((label, "1")) if previous is None or previous[1] != "2": return False label = previous[0] kind = self._components[label]._kind if kind in _REFERENCE_KINDS: return True return False def _reference_element(self, label: str, *, output_side: str | None = None) -> ReferenceElement: component = self._components[label] parameters = component._parameters kind = component._kind if kind == "delay": return ReferenceDelay(label, parameters["duration"]) if kind == "amplifier": return ReferenceAmplifier(label, *amplifier_values(parameters)) if kind in {"attenuator", "isolator"}: assert output_side is not None eta = attenuation_value(parameters) if kind == "attenuator" else float(output_side == "2") return ReferenceLoss(label, eta, thermal_occupation_value(parameters)) assert component._transfer is not None noise = thermal_occupation_value(parameters) transfer_parameters = {name: value for name, value in parameters.items() if name != "thermal_occupation"} return ReferenceFilter(label, component._transfer, MappingProxyType(transfer_parameters), noise) def _peel( self, exposure: NetworkPort, *, traversals: set[TerminalKey] | None = None, ) -> tuple[TerminalKey, TerminalKey, ReferencePlane]: """Peel adjacent reference runs from ``exposure`` to the Markov boundary. The returned plane orders inbound sections from exposure to boundary and outbound sections from boundary to exposure. """ def walk( key: TerminalKey, step: Mapping[TerminalKey, TerminalKey], *, outbound: bool ) -> tuple[TerminalKey, list[ReferenceElement]]: run: list[ReferenceElement] = [] while self._is_reference(key[0]): label, name = key if traversals is not None: traversals.add((label, name if outbound else ("2" if name == "1" else "1"))) if any(element.label == label for element in run): raise ValueError(f"Reference component {label!r} feeds back into itself.") if self._components[label]._kind == "amplifier": if name != "2": raise ValueError( f"Amplifier {label!r} must lie on an output line with side 1 toward the " "chip and side 2 toward the exposed plane; it cannot amplify an incident " "field into the chip." ) if outbound: run.append(self._reference_element(label)) elif self._components[label]._kind == "mode_transmission": forward = (name == "2") if outbound else (name == "1") if forward: run.append(self._reference_element(label)) elif self._components[label]._kind in {"attenuator", "isolator"}: forward = (name == "2") if outbound else (name == "1") run.append(self._reference_element(label, output_side="2" if forward else "1")) else: run.append(self._reference_element(label)) other = (label, "2" if name == "1" else "1") if other not in step: raise ValueError( f"Reference component {label!r} must connect toward the Markov boundary; " f"terminal {other} is free." ) key = step[other] return key, run boundary_input, inbound = walk(exposure._input_key, self._used_outputs, outbound=False) boundary_output, outbound = walk(exposure._output_key, self._connections, outbound=True) plane = ReferencePlane(inbound=tuple(inbound), outbound=tuple(reversed(outbound))) return boundary_input, boundary_output, plane def _compile( self, *, _embedded: Mapping[TerminalKey, ReferenceElement] | None = None, _retained: Mapping[str, ReferencePlane] | None = None, ) -> CompiledNetwork: exposures = self._effective_exposures() traversals: set[TerminalKey] = set() peeled = [self._peel(exposure, traversals=traversals) for exposure in exposures] planes = [(_retained or {}).get(exposure.label, plane) for exposure, (_, _, plane) in zip(exposures, peeled, strict=True)] def active_sides(label: str) -> tuple[str, ...]: return tuple(side for side in ("1", "2") if (label, side) in traversals or (label, side) in self._used_outputs or (label, "2" if side == "1" else "1") in self._connections) unused = [label for label in self._components if self._is_reference(label) and not active_sides(label)] if unused: raise ValueError(f"Reference components {unused} must connect to an exposed run " "or downstream output graph.") internal = [label for label in self._components if self._is_reference(label) and any((label, side) not in traversals for side in active_sides(label))] if internal: if _embedded is not None: raise ValueError("Reference graph cannot be reduced to exposed downstream fields.") # A component can have an exposed inbound leg and a branched outbound # leg. Preserve each traversal separately when forming the unitary core. from copy import copy bypassed = copy(self) bypassed._components = dict(self._components) embedded: dict[TerminalKey, ReferenceElement] = {} for label in internal: component = self._components[label] sides = active_sides(label) for side in sides: if ((label, side) in traversals or (component._kind in {"amplifier", "mode_transmission"} and side == "1")): continue if component._kind in {"isolator", "attenuator"}: element = self._reference_element(label, output_side=side) else: element = self._reference_element(label) embedded[(label, side)] = element bypassed._components[label] = replace( component, _kind="through", _parameters={}, _transfer=None, input_names=tuple("2" if side == "1" else "1" for side in sides), output_names=sides, scattering=np.eye(len(sides), dtype=complex), _row_inputs=tuple((i,) for i in range(len(sides))), _local_ports=(None,)*len(sides), _hidden_pairs=(), sides=()) retained = {exposure.label: plane for exposure, plane in zip(exposures, planes, strict=True)} return bypassed._compile(_embedded=embedded, _retained=retained) core = [component for component in self.components if not self._is_reference(component.label)] covered_inputs = {boundary_input for boundary_input, _, _ in peeled} covered_outputs = {boundary_output for _, boundary_output, _ in peeled} all_inputs = {(component.label, name) for component in core for name in component.input_names} all_outputs = {(component.label, name) for component in core for name in component.output_names} free_inputs = all_inputs - set(self._connections) - covered_inputs free_outputs = all_outputs - set(self._used_outputs) - covered_outputs if free_inputs or free_outputs: raise ValueError( (f"Reference components {list(_embedded)} must be outside a feedback loop with all terminals covered. " if _embedded else "") + "PortNetwork has free terminals; connect or expose them explicitly: " f"inputs={sorted(free_inputs)}, outputs={sorted(free_outputs)}." ) if len(covered_inputs) != len(exposures) or len(covered_outputs) != len(exposures): raise ValueError("PortNetwork exposures must use distinct input and output terminals.") size = len(exposures) input_fields: dict[TerminalKey, _AffineField] = {} output_fields: dict[TerminalKey, _AffineField] = {} for column, (boundary_input, _, _) in enumerate(peeled): basis = [0.0] * size basis[column] = 1.0 input_fields[boundary_input] = _AffineField(basis, {}, [index == column for index in range(size)]) nodes = { (component.label, name): (component, row) for component in core for row, name in enumerate(component.output_names) } matrices = {component.label: self._component_matrix(component) for component in core} def _feeders(terminal: TerminalKey) -> list[_Feeder]: """Return ``(coefficient, boundary_input, upstream_output)`` per structurally fed column.""" component, row = nodes[terminal] columns = ( range(len(component.input_names)) if component._row_inputs is None else component._row_inputs[row] ) result: list[_Feeder] = [] for column in columns: key = (component.label, component.input_names[column]) coefficient = matrices[component.label][row, column] if key in input_fields: result.append((coefficient, key, None)) else: result.append((coefficient, None, self._connections[key])) return result feeds = {terminal: _feeders(terminal) for terminal in nodes} generated_pairs: list[tuple[str, str, Any]] = [] groups = [] for members in _strongly_connected(nodes, lambda terminal: [up for _, _, up in feeds[terminal] if up]): cyclic = len(members) > 1 or any(up == members[0] for _, _, up in feeds[members[0]]) groups.append((members, cyclic)) if cyclic: solved = self._solve_loop(members, nodes, feeds, input_fields, output_fields, size) else: solved = {members[0]: self._propagate(members[0], nodes, feeds, input_fields, output_fields, size)} for terminal, solved_field in solved.items(): component, row = nodes[terminal] local_port = component._local_ports[row] if local_port is not None: upstream = dict(solved_field.coupling) upstream[local_port.label] = upstream.get(local_port.label, 0.0) - 1.0 generated_pairs.extend( (local_port.label, source, coefficient) for source, coefficient in upstream.items() if not self._is_concrete_zero(coefficient) ) output_fields[terminal] = solved_field rows = [output_fields[boundary_output] for _, boundary_output, _ in peeled] support_matrix = np.asarray([row.support for row in rows], dtype=bool) scattering_rows = [row.scattering for row in rows] xp = select_array_module(contains_tracer(scattering_rows)) scattering = xp.asarray(scattering_rows, dtype=complex) coupling_maps = [row.coupling for row in rows] external_count = sum(not exposure._hidden for exposure in exposures) boundary = self._boundary_scattering(tuple(exposure.label for exposure in exposures[:external_count])) if boundary is not None: boundary_xp = select_array_module(contains_tracer((scattering, boundary))) boundary = boundary_xp.asarray(boundary, dtype=complex) if boundary.shape != (external_count, external_count): raise ValueError( "PortNetwork scattering shape must match exposed channels; " f"got {boundary.shape} for {external_count}." ) full_boundary = pad_mixing(boundary, size, boundary_xp) scattering = full_boundary @ scattering coupling_maps = [ self._combine_maps(full_boundary[row], coupling_maps) for row in range(size) ] mixes = np.asarray( [[not self._is_concrete_zero(full_boundary[row, k]) for k in range(size)] for row in range(size)], dtype=bool, ) support_matrix = (mixes.astype(int) @ support_matrix.astype(int)) > 0 self._validate_unitary(scattering) return CompiledNetwork( channels=tuple( CompiledChannel(exposure, MappingProxyType(mapping), plane) for exposure, mapping, plane in zip(exposures, coupling_maps, planes, strict=True) ), scattering=scattering, generated_pairs=tuple(generated_pairs), support=support_matrix, output_network=(self._output_network(_embedded, groups, nodes, feeds, output_fields, input_fields, peeled, scattering, boundary) if _embedded else None), ) @staticmethod def _output_network(embedded, groups, nodes, feeds, fields, inputs, peeled, scattering, boundary): """Freeze acyclic downstream field maps, preserving a unitary solver boundary.""" from quchip.engine.output_network import OutputNetwork, OutputStep xp = select_array_module(contains_tracer(scattering)) inverse = xp.conj(xp.asarray(scattering).T) affected = set() steps = [] for members, cyclic in groups: local = {} for terminal in members: reference = embedded.get(terminal) changed = reference is not None or any(upstream in affected for _, _, upstream in feeds[terminal]) local[terminal] = (reference, changed) if cyclic and any(changed for _, changed in local.values()): raise ValueError(f"Reference components {sorted({key[0] for key in embedded})} must remain " "outside feedback loops; " "only acyclic downstream scattering is supported.") for terminal in members: reference, changed = local[terminal] component, row = nodes[terminal] if changed and component._local_ports[row] is not None: raise ValueError("An internal reference component feeds a quantum coupling; " "place amplifiers on output lines and use a dynamical model for internal filters.") base = xp.asarray(fields[terminal].scattering) @ inverse terms = () if changed: affected.add(terminal) terms = tuple((coefficient, upstream, xp.asarray(inputs[boundary_input].scattering) @ inverse if boundary_input is not None else None) for coefficient, boundary_input, upstream in feeds[terminal]) steps.append(OutputStep(terminal, base, terms, reference)) size = len(scattering) full_boundary = xp.eye(size, dtype=complex) if boundary is None else pad_mixing(boundary, size, xp) return OutputNetwork(tuple(steps), tuple(output for _, output, _ in peeled), full_boundary, size) @staticmethod def _incoming( terminal: TerminalKey, feeds: Mapping[TerminalKey, list[_Feeder]], input_fields: Mapping[TerminalKey, _AffineField], output_fields: Mapping[TerminalKey, _AffineField], pending: Collection[TerminalKey] = (), ) -> tuple[list[tuple[Any, _AffineField]], list[tuple[Any, TerminalKey]]]: """Split a terminal's feeders into known fields and unsolved loop members.""" known: list[tuple[Any, _AffineField]] = [] unsolved: list[tuple[Any, TerminalKey]] = [] for coefficient, boundary, upstream in feeds[terminal]: if boundary is not None: known.append((coefficient, input_fields[boundary])) continue assert upstream is not None if upstream in pending: unsolved.append((coefficient, upstream)) else: known.append((coefficient, output_fields[upstream])) return known, unsolved def _propagate( self, terminal: TerminalKey, nodes: Mapping[TerminalKey, tuple[SLHComponent, int]], feeds: Mapping[TerminalKey, list[_Feeder]], input_fields: Mapping[TerminalKey, _AffineField], output_fields: Mapping[TerminalKey, _AffineField], size: int, ) -> _AffineField: """Evaluate one output terminal whose feeders are all known.""" known, _ = self._incoming(terminal, feeds, input_fields, output_fields) scattering = [ sum(coefficient * known_field.scattering[index] for coefficient, known_field in known) for index in range(size) ] coupling: dict[str, Any] = {} for coefficient, known_field in known: for source, value in known_field.coupling.items(): coupling[source] = coupling.get(source, 0.0) + coefficient * value support = [any(known_field.support[index] for _, known_field in known) for index in range(size)] component, row = nodes[terminal] local_port = component._local_ports[row] if local_port is not None: coupling[local_port.label] = coupling.get(local_port.label, 0.0) + 1.0 return _AffineField(scattering, coupling, support) def _solve_loop( self, members: list[TerminalKey], nodes: Mapping[TerminalKey, tuple[SLHComponent, int]], feeds: Mapping[TerminalKey, list[_Feeder]], input_fields: Mapping[TerminalKey, _AffineField], output_fields: Mapping[TerminalKey, _AffineField], size: int, ) -> dict[TerminalKey, _AffineField]: """Solve the algebraic loop ``y = M y + b`` for one strongly connected set of terminals.""" pending = set(members) position = {terminal: index for index, terminal in enumerate(members)} count = len(members) loop_matrix: list[list[Any]] = [[0.0] * count for _ in members] structure = np.zeros((count, count), dtype=bool) knowns: list[list[tuple[Any, _AffineField]]] = [] for terminal in members: known, unsolved = self._incoming(terminal, feeds, input_fields, output_fields, pending) knowns.append(known) for coefficient, upstream in unsolved: loop_matrix[position[terminal]][position[upstream]] += coefficient structure[position[terminal], position[upstream]] = True sources: list[str] = [] for index, terminal in enumerate(members): component, row = nodes[terminal] local_port = component._local_ports[row] candidates = [source for _, known_field in knowns[index] for source in known_field.coupling] if local_port is not None: candidates.append(local_port.label) sources.extend(source for source in candidates if source not in sources) width = size + len(sources) drive: list[list[Any]] = [[0.0] * width for _ in members] drive_support = np.zeros((count, size), dtype=bool) for index, terminal in enumerate(members): for coefficient, known_field in knowns[index]: for column in range(size): drive[index][column] += coefficient * known_field.scattering[column] for source, value in known_field.coupling.items(): drive[index][size + sources.index(source)] += coefficient * value drive_support[index] |= np.asarray(known_field.support, dtype=bool) component, row = nodes[terminal] local_port = component._local_ports[row] if local_port is not None: drive[index][size + sources.index(local_port.label)] += 1.0 xp = select_array_module(contains_tracer((loop_matrix, drive))) system = xp.eye(count, dtype=complex) - xp.asarray(loop_matrix, dtype=complex) if not contains_tracer(system): singular_values = np.linalg.svd(np.asarray(system), compute_uv=False) if singular_values[-1] <= 1e-12 * max(1.0, float(singular_values[0])): labels = sorted({terminal[0] for terminal in members}) raise ValueError(f"Instantaneous feedback loop through {labels} is singular: I - M has no inverse.") solution = xp.linalg.solve(system, xp.asarray(drive, dtype=complex)) support = drive_support.copy() for _ in members: support = drive_support | ((structure.astype(int) @ support.astype(int)) > 0) fields: dict[TerminalKey, _AffineField] = {} for index, terminal in enumerate(members): coupling = { source: solution[index, size + offset] for offset, source in enumerate(sources) if not self._is_concrete_zero(solution[index, size + offset]) } fields[terminal] = _AffineField( [solution[index, column] for column in range(size)], coupling, [bool(flag) for flag in support[index]] ) return fields def _boundary_scattering(self, labels: tuple[str, ...]) -> Any | None: authored = self._authored_scattering if authored is None: return None if callable(authored): raise TypeError("PortNetwork scattering is instantaneous; callables are not supported.") if isinstance(authored, Mapping): known = set(labels) rows: list[list[Any]] = [] normalized: dict[tuple[str, str], Any] = {} for key, value in authored.items(): if not isinstance(key, tuple) or len(key) != 2: raise TypeError("Scattering mappings use (output, input) keys.") output, input_ = (resolve_label(item) for item in key) if output not in known or input_ not in known: raise ValueError( f"Scattering entry {(output, input_)} references outside exposures {list(labels)}." ) normalized[(output, input_)] = value for output in labels: rows.append([normalized.get((output, input_), 0.0) for input_ in labels]) return rows return authored @staticmethod def _combine_maps(coefficients: Any, maps: list[dict[str, Any]]) -> dict[str, Any]: combined: dict[str, Any] = {} for coefficient, mapping in zip(coefficients, maps, strict=True): for source, value in mapping.items(): combined[source] = combined.get(source, 0.0) + coefficient * value return combined @staticmethod def _active_mapping_sources(mapping: Mapping[str, Any]) -> tuple[str, ...]: """Return sources whose scalar coefficient is not concretely zero.""" return tuple( source for source, coefficient in mapping.items() if not PortNetwork._is_concrete_zero(coefficient) ) @classmethod def _common_frame_frequency( cls, mapping: Mapping[str, Any], channels: Mapping[str, Any], *, boundary: str, ) -> Any: """Return one carrier for a statically composed output channel; ``None`` without coupling.""" sources = cls._active_mapping_sources(mapping) if not sources: return None reference = channels[sources[0]].collapse.frame_frequency if any( not _same_carrier( reference, channels[source].collapse.frame_frequency, ) for source in sources[1:] ): raise ValueError( f"PortNetwork cannot statically compose {boundary} from ports {list(sources)}: their " "rotating-frame frequencies are not statically known to be equal (traced frequencies must " "share the same traced value). Use the lab/common frame; time-dependent collapse channels " "are not implemented." ) return reference @staticmethod def _materialize_coupling( mapping: Mapping[str, Any], operators: dict[str, Any], *, key: str, ) -> Any: from quchip.engine.ir import CanonicalOperator if not operators: raise RuntimeError("A PortNetwork cannot resolve without quantum coupling ports.") first = next(iter(operators.values())) prefer_jax = contains_tracer(( tuple(mapping.values()), tuple(operator.values for operator in operators.values()) )) xp = select_array_module(prefer_jax) values = xp.zeros(first.shape, dtype=complex) for source, coefficient in mapping.items(): values = values + xp.asarray(coefficient) * xp.asarray(operators[source].to_dense()) return CanonicalOperator.from_dense( values, dims=first.dims, basis=first.basis, subsystem_labels=first.subsystem_labels, tag=f"slh:{key}", ) def _materialize_generated_hamiltonian( self, pairs: Sequence[tuple[str, str, Any]], operators: dict[str, Any], values: Any = None, ) -> Any | None: from quchip.engine.ir import CanonicalOperator if not pairs: return None first = next(iter(operators.values())) if values is None: values = cascade_hamiltonian([ (operators[downstream].to_dense(), operators[upstream].to_dense(), coefficient) for downstream, upstream, coefficient in pairs ]) elif sparse.issparse(values) and not values.count_nonzero(): values = None elif not sparse.issparse(values) and not contains_tracer(values) and not np.any(np.asarray(values)): # The compact caller already dropped each local term below the round-off threshold. values = None if values is None: return None if sparse.issparse(values): return CanonicalOperator.from_csr( values.data, values.indices, values.indptr, shape=values.shape, dims=first.dims, basis=first.basis, subsystem_labels=first.subsystem_labels, tag=f"slh-network:{self.label}", ) return CanonicalOperator.from_dense( values, dims=first.dims, basis=first.basis, subsystem_labels=first.subsystem_labels, tag=f"slh-network:{self.label}", ) def _component_matrix(self, component: SLHComponent) -> Any: kind = component._kind parameters = component._parameters if kind == "phase_shift": phase = parameters["phase"] xp = select_array_module(contains_tracer(phase)) matrix = xp.asarray([[xp.exp(1j * xp.asarray(phase))]]) elif kind == "beam_splitter": matrix = self._transmission_matrix(parameters["eta"]) elif kind == "attenuator": matrix = self._attenuator_matrix(attenuation_value(parameters)) else: matrix = component.scattering xp = select_array_module(contains_tracer(matrix)) matrix = xp.asarray(matrix, dtype=complex) expected = (len(component.output_names), len(component.input_names)) if matrix.shape != expected: raise ValueError( f"Component {component.label!r} scattering shape {matrix.shape} must be {expected}." ) self._validate_unitary(matrix) return matrix @staticmethod def _validate_unitary(matrix: Any) -> None: if contains_tracer(matrix): return concrete = np.asarray(matrix, dtype=complex) identity = np.eye(concrete.shape[0], dtype=complex) if not np.allclose(concrete.conj().T @ concrete, identity, rtol=1e-10, atol=1e-12): raise ValueError("Concrete PortNetwork scattering must be unitary.") @staticmethod def _transmission_matrix(eta: Any) -> Any: """Return ``[[t, r], [-r, t]]`` for the directional splitter. Here ``t = sqrt(eta)`` and ``r = sqrt(1 - eta)``. Rows select outputs and columns select inputs, so ``eta`` is the power from input ``k`` to output ``k``. """ xp = select_array_module(contains_tracer(eta)) transmission = xp.sqrt(xp.asarray(eta)) loss = xp.sqrt(1.0 - xp.asarray(eta)) return xp.asarray([[transmission, loss], [-loss, transmission]], dtype=complex) @staticmethod def _attenuator_matrix(eta: Any) -> Any: """Return the reciprocal unitary dilation for a two-sided attenuator.""" xp = select_array_module(contains_tracer(eta)) t = xp.sqrt(xp.asarray(eta)) r = xp.sqrt(1.0 - xp.asarray(eta)) return xp.asarray( [[0.0, t, 0.0, r], [t, 0.0, r, 0.0], [-r, 0.0, t, 0.0], [0.0, -r, 0.0, t]], dtype=complex, ) def _validate_terminal(self, terminal: FieldTerminal, direction: TerminalDirection) -> None: if not isinstance(terminal, FieldTerminal): raise TypeError(f"Expected a FieldTerminal, got {type(terminal).__name__}.") if terminal._network_token is not self._token: raise ValueError("Cannot connect terminals from different PortNetwork objects.") if terminal.direction != direction: raise ValueError(f"Expected an {direction} terminal, got {terminal.direction}.") component = self._components.get(terminal.component) if component is None: raise ValueError(f"Unknown component {terminal.component!r}.") names = component.input_names if direction == "input" else component.output_names if terminal.name not in names: raise ValueError(f"Unknown terminal {terminal.key}.") def _reject_hidden_terminal(self, terminal: FieldTerminal) -> None: component = self._components[terminal.component] hidden_names = { input_name if terminal.direction == "input" else output_name for input_name, output_name, _ in component._hidden_pairs } if terminal.name in hidden_names: raise ValueError("Vacuum-dilation terminals are network-owned and cannot be exposed or rewired.") def _terminal_is_exposed(self, terminal: FieldTerminal) -> bool: return any( terminal.key == (exposure._input_key if terminal.direction == "input" else exposure._output_key) for exposure in self._exposures ) def _component_for_port(self, port: Port) -> SLHComponent: for component in self.components: if any(candidate is port for candidate in component._local_ports): return component raise ValueError(f"Port {port.label!r} is not owned by this PortNetwork.") def _side_of( self, value: Port | SLHComponent | ComponentPort, *, leaving: bool | None ) -> ComponentPort: """Resolve a component port for ``link`` or ``expose``. For a two-sided component, ``leaving=True`` selects side 2, ``leaving=False`` selects side 1, and ``leaving=None`` requires an explicit side. """ if isinstance(value, ComponentPort): self._validate_terminal(value.input, "input") self._validate_terminal(value.output, "output") return value component = self._component_of(value) if len(component.sides) == 1: return component.port(component.sides[0]) if len(component.sides) == 2 and leaving is not None: return component.port(component.sides[1 if leaving else 0]) raise ValueError( f"Cannot infer a component port for component {component.label!r} with " f"{len(component.sides)} ports; pass component.port(k) for a " "multi-sided component or use cascade() for a directional one." ) def _endpoint_of( self, value: Port | SLHComponent | FieldTerminal, direction: TerminalDirection ) -> FieldTerminal: if isinstance(value, FieldTerminal): return value component = self._component_of(value) return component.input if direction == "input" else component.output def _component_of(self, value: Port | SLHComponent) -> SLHComponent: return self._component_for_port(value) if isinstance(value, Port) else value @staticmethod def _cache_value(value: Any) -> Any: if value is None: return None if contains_tracer(value): raise ValueError("Traced network values are not cacheable.") if isinstance(value, Mapping): return tuple( sorted( (tuple(map(resolve_label, key)), PortNetwork._cache_value(item)) for key, item in value.items() ) ) try: array = np.asarray(value) except Exception: return repr(value) return (array.shape, array.dtype.str, array.tobytes()) @staticmethod def _is_concrete_one(value: Any) -> bool: if contains_tracer(value): return False try: return bool(np.allclose(np.asarray(value), 1.0)) except Exception: return False @staticmethod def _is_concrete_zero(value: Any) -> bool: if contains_tracer(value): return False try: return bool(np.allclose(np.asarray(value), 0.0)) except Exception: return False
__all__ = ["PortNetwork"] # Compatibility class names; remove in quchip 0.5. FieldExposure = NetworkPort FieldSide = ComponentPort