Source code for quchip.chip.couplings

"""Coupling models for two-body interactions between devices.

Couplers own their local Hamiltonians. A coupling defines the interaction
Hamiltonian ``H_int ∈ L(H_a ⊗ H_b)`` on the two-device subspace, and the engine
embeds it into the full chip space at assembly time. Coupling strengths are in
GHz.

References
----------
Blais, A., Huang, R.-S., Wallraff, A., Girvin, S. M., & Schoelkopf, R. J.
    Cavity quantum electrodynamics for superconducting electrical circuits:
    An architecture for quantum computation. PRA 69, 062320 (2004), Eq. 11.
Krantz, P., Kjaergaard, M., Yan, F., Orlando, T. P., Gustavsson, S., &
    Oliver, W. D. A quantum engineer's guide to superconducting qubits.
    Applied Physics Reviews 6, 021318 (2019), §V.
Blais, A., Grimsmo, A. L., Girvin, S. M., & Wallraff, A. Circuit quantum
    electrodynamics. Rev. Mod. Phys. 93, 025005 (2021).
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast

from quchip.backend.protocol import Operator
from quchip.chip.coupling_base import BaseCoupling as _BaseCoupling
from quchip.declarative.expr import PhysicsExpr
from quchip.declarative.models import CouplingModel
from quchip.declarative.ops import EndpointOps
from quchip.declarative.parameters import UNBOUND, Scalar, parameter
from quchip.devices.base import BaseDevice

if TYPE_CHECKING:
    from quchip.backend.protocol import Backend


__all__ = ["Capacitive", "Coupling", "CrossKerr", "TunableCapacitive"]


def _get_backend() -> "Backend":
    """Resolve the active default backend at call time (lazy import)."""
    from quchip.backend import get_default_backend

    return get_default_backend()


def _full(s: Any, a: EndpointOps, b: EndpointOps) -> PhysicsExpr:
    """Full charge-charge capacitive interaction."""
    return s * a.charge * b.charge


[docs] class Capacitive(CouplingModel): """Capacitive (charge-charge) coupling between two devices. The interaction is ``H_int = g · Q_a Q_b``, where ``Q`` is each endpoint's charge operator. A device declares ``Q`` with ``charge_coupling_operator()``, the operator that :class:`~quchip.control.drive.ChargeDrive` also uses. Between Fock devices, ``Q = i(a − a†)``: - Full form: ``H_int = g · i(a − a†) · i(b − b†) = g · (a†b + a b†) − g · (a b + a†b†)`` - Band-RWA form: ``H_int = g · (a†b + a b†)`` (derived, not authored) The coupling authors only the full form. :class:`~quchip.RWA` keeps its ``Δa + Δb == 0`` bands, which are exactly ``g · (a†b + a b†)``, and drops the counter-rotating terms ``a b`` and ``a† b†``. This is valid when ``ω_a + ω_b ≫ g``, which makes those terms fast-rotating and negligible. It differs from the dispersive condition ``|ω_a − ω_b| ≫ g``, which determines whether the *retained* exchange term ``g · (a†b + a b†)`` can be treated perturbatively (see :class:`TunableCapacitive` / :func:`~quchip.chip.transformations.eliminate` for the dispersive reduction). You select the approximation on the chip or for one solve, never on the coupling. Parameters ---------- device_a, device_b : BaseDevice or str The two coupled devices, given as objects or label strings. :class:`Chip` late-binds label-string references. g : float Coupling strength in GHz. Can be a traced JAX scalar for sweeps / autodiff. The exchange matrix element is ``g`` times the endpoints' ``0 -> 1`` charge elements. Their magnitude is 1 on a :class:`~quchip.devices.transmon.duffing.DuffingTransmon` or :class:`~quchip.devices.resonator.Resonator`, ``s(Φ)`` on a :class:`~quchip.devices.transmon.flux_tunable.FluxTunableTransmon`, and ``|<0|n|1>|`` on a charge-basis or phase-grid device. label : str, optional Human-readable label; defaults to ``"cap_{n}"``. References ---------- Blais et al., PRA 69, 062320 (2004), Eq. 11. Krantz et al., Appl. Phys. Rev. 6, 021318 (2019), §V.B. Blais et al., Rev. Mod. Phys. 93, 025005 (2021), §II.B. Examples -------- >>> from quchip import DuffingTransmon, Resonator, Capacitive >>> q = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="q") >>> r = Resonator(freq=7.0, levels=5, label="r") >>> coupling = Capacitive(q, r, g=0.05) """ _type_prefix: ClassVar[str] = "cap" reduces_to_crosskerr: ClassVar[bool] = True default_fit_observable: ClassVar[str] = "cross_kerr" g: Scalar = parameter(default=UNBOUND, unit="GHz", symbol="g")
[docs] def default_dressed_target(self) -> tuple[str, Any]: """Use exchange rate for an edge between non-computational modes.""" if ( isinstance(self.device_a, BaseDevice) and isinstance(self.device_b, BaseDevice) and not self.device_a.computational and not self.device_b.computational ): return "exchange_rate", self.coupling_strength return super().default_dressed_target()
[docs] def interaction(self, a: EndpointOps, b: EndpointOps, p: Any) -> PhysicsExpr: """Return the full capacitive interaction ``g * Q_a Q_b``. Parameters ---------- a, b : EndpointOps Resolved operators for the two endpoints. p : ParameterNamespace Bound parameters, including ``g`` in GHz. """ return _full(p.g, a, b)
[docs] def physics_notes(self) -> list[str]: """Return the declared capacitive interaction.""" notes = super().physics_notes() notes.append("Interaction form: g · Q_a Q_b with each device's charge operator Q") return notes
def __repr__(self) -> str: """Return a compact endpoint/coupling summary.""" return f"Capacitive('{self.device_a_label}' <-> '{self.device_b_label}', g={self.g})"
[docs] @classmethod def from_dict( cls, d: dict[str, Any], device_a: BaseDevice, device_b: BaseDevice, ) -> "Capacitive": """Reconstruct a capacitive coupling from serialized state. Parameters ---------- d : dict[str, Any] Serialized coupling fields. device_a, device_b : BaseDevice Resolved endpoint devices. """ return cls( device_a=device_a, device_b=device_b, g=d["g"], label=cast(str, d.get("label")), )
[docs] class TunableCapacitive(CouplingModel): r"""Capacitive coupling with a scheduled parametric pump. Effective two-body interaction with a static mean coupling strength: .. math:: H_{\text{int}} \;=\; g_0\,\hat Q_a\hat Q_b where :math:`\hat Q` is each endpoint's charge operator, as in :class:`Capacitive` (:math:`i(\hat a - \hat a^\dagger)` for a Fock model). The engine applies any requested RWA after local-basis materialization. :math:`g_0` is the static coupling strength in GHz and can be a JAX tracer that flows through :func:`jax.grad` without concretization. Time dependence is not a construction-time parameter. A :class:`~quchip.control.drive.ParametricDrive` wired onto this coupling schedules a pump δ(t) via :meth:`~quchip.control.sequence.QuantumSequence.pump`. The pump multiplies the same operator structure as the static term (:meth:`parametric_interaction`). Parameters ---------- device_a, device_b : BaseDevice The two coupled devices. g_0 : float Static (mean) coupling strength in GHz. Can be a JAX tracer. label : str, optional Human-readable label; defaults to ``"tunable_cap_{n}"``. Notes ----- The pump multiplies :meth:`parametric_interaction`, and frame and RWA logic stay in the engine. A pump tone at the qubits' difference frequency ``|ω_a − ω_b|`` activates the parametric beam-splitter / iSWAP exchange. A tone at the sum frequency ``ω_a + ω_b`` activates two-mode-squeezing (``a†b†``) terms. Express either tone with the drive's ``freq`` argument, not with a coupling-side carrier. References ---------- McKay, Filipp, Mezzacapo, Magesan, Chow & Gambetta, *Universal Gate for Fixed-Frequency Qubits via a Tunable Bus*, Phys. Rev. Applied **6**, 064007 (2016) — parametric two-qubit gates via coupler flux modulation. Krantz et al., *A quantum engineer's guide to superconducting qubits*, Appl. Phys. Rev. **6**, 021318 (2019), §V.D — tunable couplers. Examples -------- >>> from quchip import Chip, ControlEquipment, DuffingTransmon, ParametricDrive, TunableCapacitive >>> q0 = DuffingTransmon(freq=5.0, anharmonicity=-0.25, levels=3, label="q0") >>> q1 = DuffingTransmon(freq=5.2, anharmonicity=-0.22, levels=3, label="q1") >>> tc = TunableCapacitive(q0, q1, g_0=0.0, label="tc") >>> pump = ParametricDrive(tc, label="pump") >>> chip = Chip([q0, q1], couplings=[tc], control_equipment=ControlEquipment([pump])) >>> # seq.pump(tc, envelope=..., freq=...) schedules δg(t); see QuantumSequence.pump. """ _type_prefix: ClassVar[str] = "tunable_cap" is_effective: ClassVar[bool] = True reduces_to_crosskerr: ClassVar[bool] = True default_fit_observable: ClassVar[str] = "cross_kerr" g_0: Scalar = parameter(default=UNBOUND, unit="GHz", symbol="g_0")
[docs] def interaction(self, a: EndpointOps, b: EndpointOps, p: Any) -> PhysicsExpr: """Return the static ``g_0 · Q_a Q_b`` contribution. Parameters ---------- a, b : EndpointOps Resolved endpoint operators. p : ParameterNamespace Bound parameters, including ``g_0`` in GHz. """ return _full(p.g_0, a, b)
[docs] def parametric_interaction(self, a: EndpointOps, b: EndpointOps, p: Any) -> PhysicsExpr: """Return the charge-charge structure multiplied by a pump. Parameters ---------- a, b : EndpointOps Resolved endpoint operators. p : ParameterNamespace Bound parameters; unused because the pump supplies the coefficient. """ _ = p return a.charge * b.charge
[docs] def physics_notes(self) -> list[str]: """Return the tunable-coupling and effective-model assumptions.""" notes = super().physics_notes() notes.append("Interaction form: g_0 · Q_a Q_b with each device's charge operator Q") notes.append( "Effective parametric model: the physical coupler mode is eliminated; coupler " "leakage and mediated shifts beyond exchange are not represented — use a physical " "bus device plus eliminate() when they matter" ) return notes
def __repr__(self) -> str: """Return a compact endpoint/coupling summary.""" return ( f"TunableCapacitive('{self.device_a_label}' <-> '{self.device_b_label}', " f"g_0={self.g_0})" )
[docs] class CrossKerr(CouplingModel): """Cross-Kerr (dispersive) coupling ``H_int = χ · n̂_a n̂_b``. This effective diagonal interaction remains when an exchange coupling is reduced in the dispersive regime. It suits effective readout chips (qubit + resonator + ``CrossKerr`` probed by an ordinary charge line) and static-ZZ modelling. The term is diagonal in both endpoints, so its RWA and full forms coincide and it is frame-trivial. Declared approximation: this is a *uniform-pull* model with one χ per edge and the same shift per endpoint excitation. It does not represent per-level χ differences, dispersive breakdown at the critical photon number, or Purcell decay. If Purcell decay matters, fold it into the endpoint ``T1`` with ``eliminate()``. Parameters ---------- device_a, device_b : BaseDevice or str The two coupled devices, as objects or label strings. chi : float Cross-Kerr shift in GHz per excitation pair, sign included (convention: full pull ``E₁₁ − E₁₀ − E₀₁ + E₀₀``). Can be a JAX tracer. label : str, optional Defaults to ``"crosskerr_{n}"``. """ _type_prefix: ClassVar[str] = "crosskerr" is_effective: ClassVar[bool] = True default_fit_observable: ClassVar[str] = "cross_kerr" chi: Scalar = parameter(default=UNBOUND, unit="GHz", symbol=r"\chi")
[docs] def interaction(self, a: EndpointOps, b: EndpointOps, p: Any) -> PhysicsExpr: """Return ``χ · n̂_a n̂_b``, identical under RWA. Parameters ---------- a, b : EndpointOps Resolved endpoint operators. p : ParameterNamespace Bound parameters, including ``chi`` in GHz. """ return p.chi * (a.level * b.level)
[docs] def parametric_interaction(self, a: EndpointOps, b: EndpointOps, p: Any) -> PhysicsExpr: """Return the number-number structure multiplied by a pump. Parameters ---------- a, b : EndpointOps Resolved endpoint operators. p : ParameterNamespace Bound parameters; unused because the pump supplies the coefficient. """ _ = p return a.level * b.level
[docs] def physics_notes(self) -> list[str]: """Return the declared dispersive-approximation provenance.""" notes = super().physics_notes() notes.append( "Effective dispersive model: uniform pull χ per excitation pair; per-level χ " "differences, n_crit breakdown, and Purcell are not represented" ) return notes
def __repr__(self) -> str: """Return a compact endpoint/χ summary.""" return f"CrossKerr('{self.device_a_label}' <-> '{self.device_b_label}', chi={self.chi})"
[docs] class Coupling(_BaseCoupling): """Generic two-body coupling with a user-supplied interaction. Use this escape hatch when no concrete coupling class models the required physics (inductive, longitudinal, cross-Kerr test forms, synthetic spin-spin couplings, photonics-style beam-splitters, …). The user supplies the operator structure, and this class supplies only the ``g`` scaling, RWA pass-through, and bookkeeping. Two mutually exclusive modes: **Product form** — ``H_int = g · op_a(device_a) ⊗ op_b(device_b)``:: Coupling(q, r, g=0.02, op_a=lambda d: d.number_operator(), op_b=lambda d: d.number_operator()) **Callable form** — ``H_int = g · interaction(device_a, device_b, backend)``:: Coupling(q, r, g=0.02, interaction=lambda a, b, bk: ( bk.tensor(bk.dag(a.lowering_operator()), b.lowering_operator()) + bk.tensor(a.lowering_operator(), bk.dag(b.lowering_operator())) )) The selected chip approximation is applied to the complete user-supplied operator after authored physics is assembled. Parameters ---------- device_a, device_b : BaseDevice or str Coupled devices or late-bound labels. g : float Scalar interaction strength in GHz. op_a, op_b : callable or None, default=None Local operator factories for product form. interaction : callable or None, default=None Callable receiving both devices and the active backend. label : str or None, default=None Coupling label; generated when omitted. """ _type_prefix: ClassVar[str] = "coupling" structural_setting_names: ClassVar[tuple[str, ...]] = ("_op_a", "_op_b", "_interaction") def __init__( self, device_a: BaseDevice | str, device_b: BaseDevice | str, g: float, *, op_a: Callable[[BaseDevice], Operator] | None = None, op_b: Callable[[BaseDevice], Operator] | None = None, interaction: Callable[[BaseDevice, BaseDevice, "Backend"], Operator] | None = None, label: str | None = None, ) -> None: """Initialize a user-defined product-form or callable interaction.""" super().__init__(device_a, device_b, label=label) has_product = op_a is not None or op_b is not None has_interaction = interaction is not None if has_product and has_interaction: raise ValueError("Provide either (op_a, op_b) or interaction, not both.") if not (has_product or has_interaction): raise ValueError("Provide either (op_a, op_b) or interaction.") if has_product and (op_a is None or op_b is None): raise ValueError("Both op_a and op_b are required for product-form coupling.") self.g = g self._op_a = op_a self._op_b = op_b self._interaction = interaction @property def coupling_strength(self) -> float: """Scalar prefactor ``g`` supplied by the user.""" return self.g
[docs] def interaction_hamiltonian(self) -> Operator: """User-defined interaction on ``H_a ⊗ H_b``, scaled by ``g``.""" backend = _get_backend() # device_a/device_b are typed BaseDevice | str for late label binding, but # Chip._resolve_devices() resolves both to concrete BaseDevice instances # before interaction_hamiltonian() is ever invoked (see is_resolved). if self._interaction is not None: return self.g * self._interaction(cast(BaseDevice, self.device_a), cast(BaseDevice, self.device_b), backend) # op_a / op_b both guaranteed non-None by __init__ validation. return self.g * backend.tensor( self._op_a(cast(BaseDevice, self.device_a)), # type: ignore[misc] self._op_b(cast(BaseDevice, self.device_b)), # type: ignore[misc] )
def __repr__(self) -> str: """Return a compact endpoint/mode summary.""" mode = "interaction" if self._interaction is not None else "product" return f"Coupling('{self.device_a_label}' <-> '{self.device_b_label}', g={self.g}, mode={mode})"
[docs] def physics_notes(self) -> list[str]: """Return notes describing the user-supplied interaction mode.""" notes = super().physics_notes() mode = "user-supplied interaction" if self._interaction is not None else "product form op_a ⊗ op_b" notes.append(f"Interaction form: {mode}") return notes
[docs] def to_dict(self) -> dict[str, Any]: """Reject serialization because callables cannot be persisted.""" raise NotImplementedError( "Generic Coupling carries user-defined callables and cannot be serialized. " "Use a concrete coupling subclass for persistent storage." )
[docs] @classmethod def from_dict( cls, d: dict[str, Any], device_a: "BaseDevice", device_b: "BaseDevice", ) -> "Coupling": """Reject deserialization because callables cannot be reconstructed. Parameters ---------- d : dict[str, Any] Serialized fields; unsupported for callable interactions. device_a, device_b : BaseDevice Endpoint devices; unused because deserialization is unsupported. """ raise NotImplementedError( "Generic Coupling carries user-defined callables and cannot be deserialized. " "Use a concrete coupling subclass for persistent storage." )