"""Coupling models for two-body interactions between devices.
Couplers own their local Hamiltonians. A coupling
defines the interaction Hamiltonian on the two-device subspace
``H_int ∈ L(H_a ⊗ H_b)``; 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, 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 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 dipole-dipole capacitive form ``s · (a + a†)(b + b†)``."""
return s * a.x * b.x
[docs]
class Capacitive(CouplingModel):
"""Capacitive (charge-charge) coupling between two devices.
The capacitive interaction between two electromagnetic modes takes the
canonical dipole-dipole form in the raising/lowering basis:
- Full form: ``H_int = g · (a + a†)(b + b†)``
- RWA form: ``H_int = g · (a†b + a b†)`` — *derived*, not authored
The coupling authors only the full form; the RWA form is the engine
retaining the ``Δa + Δb == 0`` bands of it, which is exactly
``g · (a†b + a b†)``. The RWA drops the counter-rotating terms ``a b``
and ``a† b†``, valid when ``ω_a + ω_b ≫ g`` — the sum-frequency
condition that makes those terms fast-rotating and hence negligible.
This is distinct from the dispersive condition ``|ω_a − ω_b| ≫ g``,
which instead governs 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). Whether to take the RWA is a per-coupling
policy; resolution against the chip default happens in
:meth:`Chip.resolve_rwa`.
Parameters
----------
device_a, device_b : BaseDevice or str
The two coupled devices, given as objects or label strings.
Label-string references are late-bound via :class:`Chip`.
g : float
Coupling strength in GHz. May be a traced JAX scalar for
sweeps / autodiff.
rwa : bool or None
Per-coupling RWA override. ``None`` inherits the chip default.
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) # RWA inherited from chip
>>> _strong = Capacitive(q, r, g=0.05, rwa=False) # force full quantum-Rabi form
"""
_type_prefix: str = "cap"
folds_exchange: bool = True
reduces_to_crosskerr: bool = True
g: Scalar = parameter(unit="GHz")
def __init__(
self,
device_a: BaseDevice | str,
device_b: BaseDevice | str,
*,
g: Scalar,
rwa: bool | None = None,
label: str | None = None,
) -> None:
"""Initialize a capacitive coupling between two devices or labels.
Explicit signature so mypy checks call sites against the real
``CouplingModel.__init__`` contract instead of a ``dataclass_transform``
synthesis derived from this class's own field order (which omits
``device_a``/``device_b``/``rwa``/``label`` entirely).
"""
super().__init__(device_a, device_b, label=label, rwa=rwa, g=g)
[docs]
def interaction(self, a: EndpointOps, b: EndpointOps) -> PhysicsExpr:
"""Return the full capacitive interaction ``g * (a + a†)(b + b†)``."""
return _full(self.g, a, b)
[docs]
def physics_notes(self) -> list[str]:
"""Return declared capacitive-coupling and RWA assumptions."""
notes = super().physics_notes()
notes.append("Interaction form: g · (a + a†)(b + b†); RWA drops counter-rotating a b, a† b†")
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}, rwa={self._rwa})"
[docs]
@classmethod
def from_dict(
cls,
d: dict[str, Any],
device_a: BaseDevice,
device_b: BaseDevice,
) -> "Capacitive":
"""Reconstruct a capacitive coupling from serialized state."""
return cls(
device_a=device_a,
device_b=device_b,
g=d["g"],
rwa=d.get("rwa"),
label=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 \cdot
\left\{\begin{array}{ll}
(a + a^\dagger)(b + b^\dagger) & \text{if } \texttt{rwa} = \texttt{False} \\
(a^\dagger b + a b^\dagger) & \text{if } \texttt{rwa} = \texttt{True}
\end{array}\right.
where :math:`g_0` is the static coupling strength in GHz; it may be
a JAX tracer and 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`, multiplying
the same operator structure the static term uses
(:meth:`parametric_interaction` / :meth:`rwa_parametric_interaction`).
Parameters
----------
device_a, device_b : BaseDevice
The two coupled devices.
g_0 : float
Static (mean) coupling strength in GHz. May be a JAX tracer.
rwa : bool or None
Per-coupling RWA override; ``None`` inherits the chip default.
label : str, optional
Human-readable label; defaults to ``"tunable_cap_{n}"``.
Notes
-----
The pump multiplies :meth:`parametric_interaction` /
:meth:`rwa_parametric_interaction` in the chip's frame natively —
no separate frame or carrier logic lives on the coupling; a pump
tone at the qubits' difference frequency ``|ω_a − ω_b|`` activates the
parametric beam-splitter / iSWAP exchange, while a tone at the sum
frequency ``ω_a + ω_b`` instead activates two-mode-squeezing
(``a†b†``) terms. Either is expressed via the drive's ``freq``
argument, not 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: str = "tunable_cap"
is_effective: bool = True
folds_exchange: bool = True
reduces_to_crosskerr: bool = True
g_0: Scalar = parameter(unit="GHz")
def __init__(
self,
device_a: BaseDevice | str,
device_b: BaseDevice | str,
*,
g_0: Scalar,
rwa: bool | None = None,
label: str | None = None,
) -> None:
"""Initialize a tunable capacitive coupling between two devices or labels.
Explicit signature so mypy checks call sites against the real
``CouplingModel.__init__`` contract instead of a ``dataclass_transform``
synthesis derived from this class's own field order (which omits
``device_a``/``device_b``/``rwa``/``label`` entirely).
"""
super().__init__(device_a, device_b, label=label, rwa=rwa, g_0=g_0)
[docs]
def interaction(self, a: EndpointOps, b: EndpointOps) -> PhysicsExpr:
"""Static contribution ``g_0 · (a + a†)(b + b†)``."""
return _full(self.g_0, a, b)
[docs]
def parametric_interaction(self, a: EndpointOps, b: EndpointOps) -> PhysicsExpr:
"""Modulable structure ``(a + a†)(b + b†)`` a scheduled pump multiplies."""
return a.x * b.x
[docs]
def rwa_parametric_interaction(self, a: EndpointOps, b: EndpointOps) -> PhysicsExpr:
"""Beam-splitter structure ``a†b + a b†`` retained under RWA."""
return a.adag * b.a + a.a * b.adag
[docs]
def physics_notes(self) -> list[str]:
"""Return the tunable-coupling, RWA, and effective-model assumptions."""
notes = super().physics_notes()
notes.append("Interaction form: g_0 · (a + a†)(b + b†); RWA keeps a†b + a b†")
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}, rwa={self._rwa})"
)
[docs]
class CrossKerr(CouplingModel):
"""Cross-Kerr (dispersive) coupling ``H_int = χ · n̂_a n̂_b``.
The effective diagonal interaction left when an exchange coupling is
reduced in the dispersive regime — the natural coupling for effective
readout chips (qubit + resonator + ``CrossKerr`` probed by an ordinary
charge line) and static-ZZ modeling. Diagonal in both endpoints, so the
RWA and full forms coincide and the term is frame-trivial.
Declared approximation: this is a *uniform-pull* model —
one χ per edge, the same shift per endpoint excitation; per-level χ
differences, dispersive breakdown at the critical photon number, and
Purcell decay are not represented (fold Purcell into endpoint ``T1``
via ``eliminate()`` when it matters).
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₀₀``). May be a JAX
tracer.
rwa : bool or None
Per-coupling RWA override; irrelevant to the produced operator
(both forms coincide) but stored for policy uniformity.
label : str, optional
Defaults to ``"crosskerr_{n}"``.
"""
_type_prefix: str = "crosskerr"
is_effective: bool = True
chi: Scalar = parameter(unit="GHz")
def __init__(
self,
device_a: BaseDevice | str,
device_b: BaseDevice | str,
*,
chi: Scalar,
rwa: bool | None = None,
label: str | None = None,
) -> None:
"""Initialize a cross-Kerr coupling between two devices or labels."""
super().__init__(device_a, device_b, label=label, rwa=rwa, chi=chi)
[docs]
def interaction(self, a: EndpointOps, b: EndpointOps) -> PhysicsExpr:
"""Full form ``χ · n̂_a n̂_b`` (diagonal; identical under RWA)."""
return self.chi * (a.n * b.n)
[docs]
def parametric_interaction(self, a: EndpointOps, b: EndpointOps) -> PhysicsExpr:
"""Modulable structure ``n̂_a n̂_b`` — δχ(t) pumps ride this."""
return a.n * b.n
[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
desired physics (inductive, longitudinal, cross-Kerr test forms,
synthetic spin-spin couplings, photonics-style beam-splitters, …).
The user supplies the operator structure; this class only provides
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 resolved RWA applies the structural band filter to the
user-supplied operator too, keeping only the bands
:meth:`~quchip.chip.coupling_base.BaseCoupling.rwa_keeps_band`
accepts. Supply ``rwa=False`` (or override ``rwa_keeps_band``) to
keep a hand-built form untouched.
"""
_type_prefix: str = "coupling"
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,
rwa: bool | 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
self._rwa = rwa
@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} (resolved RWA applies the structural band filter)")
return notes
[docs]
def to_dict(self) -> dict[str, Any]:
"""Reject serialization because callables cannot be made persistent."""
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."""
raise NotImplementedError(
"Generic Coupling carries user-defined callables and cannot be deserialized. "
"Use a concrete coupling subclass for persistent storage."
)