Source code for quchip.chip.ports

"""Physical Markovian input-output channels attached to a chip."""

from __future__ import annotations

from collections.abc import Sequence
from typing import TYPE_CHECKING, Any

import jax
import numpy as np

from quchip.declarative.dissipation import CollapseChannel, normalize_dissipation
from quchip.utils.jax_utils import maybe_concrete_scalar
from quchip.utils.labeling import auto_label, resolve_label

if TYPE_CHECKING:
    from quchip.chip.chip import Chip
    from quchip.chip.port_network import ComponentPort, FieldTerminal, PortNetwork, SLHComponent


[docs] class Port: """One accessible Markovian channel with a dimensionless coupling operator. ``rate``, ``external_quality_factor``, and ``phase`` can be traced or swept between solves but remain constant within a solve. Model shaped emission with an explicit buffer or coupler device holding a static ``Port`` and a modulated Hamiltonian coupling. Parameters ---------- target : object or sequence of object Device target(s), or labels resolved when attached to a chip. Multiple targets require an explicit joint ``operator``. rate : float or array-like or None, default=None External coupling rate in 1/ns. Mutually exclusive with ``external_quality_factor``. external_quality_factor : float or array-like or None, default=None Dimensionless external quality factor for one target, giving the rate ``2*pi*freq/Q`` in 1/ns. operator : object or str or None, default=None Dimensionless coupling operator. ``None`` uses the target's lowering operator. phase : float or array-like, default=0.0 Reference-plane phase in radians. label : str or None, default=None Stable channel label, generated if omitted. """ _type_prefix = "port" _parameter_names = ("rate", "external_quality_factor", "phase") def __init__( self, target: Any | Sequence[Any], *, rate: Any = None, external_quality_factor: Any = None, operator: Any = None, phase: Any = 0.0, label: str | None = None, ) -> None: if isinstance(target, Sequence) and not isinstance(target, (str, bytes)): targets = tuple(target) else: targets = (target,) if not targets: raise ValueError("Port requires at least one target device.") if (rate is None) == (external_quality_factor is None): raise ValueError("Port requires exactly one of rate or external_quality_factor.") if external_quality_factor is not None and len(targets) != 1: raise ValueError("external_quality_factor is defined only for a single target device.") if operator is None and len(targets) != 1: raise ValueError("A multi-device Port requires an explicit coupling operator.") self._targets = targets self.rate = rate self.external_quality_factor = external_quality_factor self.operator = operator self.phase = phase self.label = label if label is not None else auto_label(self._type_prefix) self._network: PortNetwork | None = None self._network_component: SLHComponent | None = None # Removed modes whose kept response the Markov field of a transformed port lacks. self._omitted_responses: tuple[str, ...] = () def _bind_network(self, network: "PortNetwork", component: "SLHComponent") -> None: """Bind terminal access to the one network that owns this port.""" if self._network is not None and self._network is not network: raise ValueError( f"Port {self.label!r} already belongs to another PortNetwork; copy it first." ) self._network = network self._network_component = component @property def input(self) -> "FieldTerminal": """Return this port's field input terminal.""" if self._network_component is None: raise AttributeError("A Port has terminals only after it belongs to a PortNetwork.") return self._network_component.input @property def output(self) -> "FieldTerminal": """Return this port's field output terminal.""" if self._network_component is None: raise AttributeError("A Port has terminals only after it belongs to a PortNetwork.") return self._network_component.output @property def side(self) -> "ComponentPort": """Return this port's physical side, pairing its input and output terminals.""" if self._network_component is None: raise AttributeError("A Port has terminals only after it belongs to a PortNetwork.") return self._network_component.port("field") @staticmethod def _validate_positive(name: str, value: Any) -> None: concrete = maybe_concrete_scalar(value) if concrete is not None and concrete <= 0: raise ValueError(f"{name} must be positive, got {value}") def __setattr__(self, name: str, value: Any) -> None: if name in ("rate", "external_quality_factor"): self._validate_positive(name, value) other_name = "external_quality_factor" if name == "rate" else "rate" if hasattr(self, other_name): other = getattr(self, other_name) if (value is None) == (other is None): raise ValueError("Port requires exactly one of rate or external_quality_factor.") super().__setattr__(name, value)
[docs] def resolve_targets(self, chip: "Chip") -> tuple[str, ...]: """Return target labels after checking that they belong to a chip. Parameters ---------- chip : Chip Chip against which labels are resolved. """ labels = tuple(resolve_label(target) for target in self._targets) unknown = [label for label in labels if label not in chip.device_map] if unknown: raise ValueError(f"Port {self.label!r} targets unknown device(s) {unknown}.") if len(set(labels)) != len(labels): raise ValueError(f"Port {self.label!r} repeats a target device.") return labels
[docs] def rate_value(self, chip: "Chip") -> Any: """Return the external coupling rate in 1/ns. Parameters ---------- chip : Chip Chip that supplies the target frequency for a quality-factor rate. """ if self.rate is not None: return self.rate label = self.resolve_targets(chip)[0] target = chip[label] if not hasattr(target, "freq"): raise TypeError("external_quality_factor requires a target with a freq parameter.") return 2.0 * np.pi * target.freq / self.external_quality_factor
def _authored_operator(self, chip: "Chip") -> Any: labels = self.resolve_targets(chip) if self.operator is not None and not isinstance(self.operator, str): return self.operator if isinstance(self.operator, str) and len(labels) != 1: raise ValueError("A named Port operator requires exactly one target.") from quchip.backend import get_default_backend # The target's own operator is a constant of its authored space; keep # it concrete inside jax.jit so its excitation change stays known. with jax.ensure_compile_time_eval(), get_default_backend().eager_operators(): if self.operator is None: return chip[labels[0]].lowering_operator() return chip[labels[0]].local_operator(self.operator) def _collapse_channels_with_paths( self, chip: "Chip", ) -> tuple[tuple[CollapseChannel, tuple[str, ...]], ...]: labels = self.resolve_targets(chip) normalized = normalize_dissipation( (CollapseChannel(self._authored_operator(chip), self.rate_value(chip), "external_coupling"),), labels=labels, dims=tuple(chip[label].local_space().dimension for label in labels), owner=self, scope=f"port.{self.label}", ) rate_paths: tuple[str, ...] if self.rate is not None: rate_paths = (f"port.{self.label}.rate",) else: rate_paths = ( f"{labels[0]}.freq", f"port.{self.label}.external_quality_factor", ) return tuple((channel, tuple(dict.fromkeys((*paths, *rate_paths)))) for channel, paths in normalized)
[docs] def parameter_values(self) -> dict[str, Any]: """Return active sweepable port values.""" return { name: value for name in self._parameter_names if (value := getattr(self, name)) is not None }
[docs] def set_parameter_value(self, name: str, value: Any) -> None: """Set one port parameter on an isolated chip copy. Parameters ---------- name : {"rate", "external_quality_factor", "phase"} Local parameter name. value : Any Replacement value in 1/ns, dimensionless units, or radians. """ if name not in self._parameter_names: raise KeyError(name) setattr(self, name, value)
[docs] def copy(self) -> "Port": """Return an independent port that keeps label-based targets.""" operator = self.operator.copy() if isinstance(self.operator, np.ndarray) else self.operator result = Port( tuple(resolve_label(target) for target in self._targets), rate=self.rate, external_quality_factor=self.external_quality_factor, operator=operator, phase=self.phase, label=self.label, ) result._omitted_responses = self._omitted_responses return result
[docs] def physics_notes(self) -> list[str]: """Return the input-output convention owned by this port.""" return [ "This accessible Markovian channel uses " "L = exp(i phase) sqrt(rate) A and b_out = b_in + L; " "the same L sets damping, coherent input coupling, and reported output." ]
[docs] def to_dict(self) -> dict[str, Any]: """Serialize port targets and scalar coupling data.""" operator: Any = self.operator if operator is not None and not isinstance(operator, str): if hasattr(operator, "matrix"): operator = operator.matrix() elif hasattr(operator, "to_jax"): operator = operator.to_jax() elif hasattr(operator, "full"): operator = operator.full() array = np.asarray(operator, dtype=complex) operator = { "kind": "dense", "real": array.real.tolist(), "imag": array.imag.tolist(), } elif isinstance(operator, str): operator = {"kind": "named", "name": operator} omitted = {"omitted_responses": list(self._omitted_responses)} if self._omitted_responses else {} return { "targets": [resolve_label(target) for target in self._targets], "rate": self.rate, "external_quality_factor": self.external_quality_factor, "operator": operator, "phase": self.phase, "label": self.label, **omitted, }
[docs] @classmethod def from_dict(cls, data: dict[str, Any]) -> "Port": """Reconstruct a port from label-based serialized data. Parameters ---------- data : dict[str, Any] Payload produced by :meth:`to_dict`. """ targets = data["targets"] target: Any = targets[0] if len(targets) == 1 else targets operator = data.get("operator") if isinstance(operator, dict): if operator.get("kind") == "named": operator = operator["name"] elif operator.get("kind") == "dense": operator = np.asarray(operator["real"]) + 1j * np.asarray(operator["imag"]) else: raise TypeError(f"Unknown serialized Port operator kind: {operator.get('kind')!r}") port = cls( target, rate=data.get("rate"), external_quality_factor=data.get("external_quality_factor"), operator=operator, phase=data.get("phase", 0.0), label=data.get("label"), ) omitted = data.get("omitted_responses", ()) if any(not isinstance(label, str) or not label for label in omitted): raise ValueError("Omitted port responses must be nonempty labels.") port._omitted_responses = tuple(sorted(set(omitted))) return port