Source code for quchip.analysis.dispersive_readout

"""Calculate steady-state dispersive pointer states and detected information.

The model is a linear resonator with vacuum energy loss and fixed qubit state.
It excludes ring-up transients, thermal noise, and measurement-induced qubit T1.
``kappa`` is the total energy linewidth. ``kappa_detected`` is the observed
part, before detector efficiency. Elimination's intrinsic downward-decay
summary is not a general linewidth or detected rate.

For drive detuning ``delta_j = 2pi*(delta_r + j*chi_eff)``,
``alpha_j = -i*eps/(kappa/2 + i*delta_j)``. The ground-state photon number is
``nbar_0 = |alpha_0|²``. The dephasing rate is
``Gamma_m = kappa*|alpha_1-alpha_0|²/2``. Heterodyne detection gives
``SNR = |alpha_1-alpha_0|*sqrt(2*efficiency*kappa_detected*tau)``.

``chi`` is the full pull ``f_r|1-f_r|0`` in GHz. With
``Z=|0><0|-|1><1|``, the angular coefficient of ``Z*n_r`` is ``-pi*chi``.
The small-pull limit is ``8*(pi*chi)²*nbar_0/kappa`` divided by
``1+(4pi*delta_r/kappa)²``. It reduces to the usual expression on resonance.

The optional ``chi/(1+nbar_0/n_crit)`` interpolation is phenomenological.
It does not extend dispersive theory into a controlled strong-drive regime.
For a two-level Jaynes-Cummings model, ``n_crit=Delta²/(4g²)`` uses the resolved
exchange matrix element. Multilevel dispersive limits depend on both qubit
transition detunings and the occupied levels.

References
----------
Koch et al., PRA 76, 042319 (2007), section III B:
https://arxiv.org/abs/cond-mat/0703002.
Gambetta et al., PRA 74, 042318 (2006):
https://arxiv.org/abs/cond-mat/0602322.
Krantz et al., Applied Physics Reviews 6, 021318 (2019), section V:
https://arxiv.org/abs/1904.06560.
Blais et al., RMP 93, 025005 (2021), section III.3:
https://arxiv.org/abs/2005.12667.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

import numpy as np
from scipy.special import erfc as _scipy_erfc

from quchip.utils.constants import TWO_PI
from quchip.utils.jax_utils import contains_tracer, is_jax_array, is_jax_namespace


def _erfc(x: Any, xp: Any) -> Any:
    """Complementary error function in the given array namespace."""
    if is_jax_namespace(xp):
        from jax.scipy.special import erfc

        return erfc(x)
    return _scipy_erfc(x)


# ---------------------------------------------------------------------------
# Result container
# ---------------------------------------------------------------------------


[docs] @dataclass(frozen=True) class DispersiveReadoutResult: """Store steady-state dispersive-readout figures of merit. All numeric fields stay in the inputs' array namespace (JAX in, JAX out), so the full result traces under ``jax.jit``/``grad``. If ``n_crit`` was given, ``validity`` holds ``{"n_over_ncrit", "below_ncrit"}``, and otherwise is empty. Under jit/grad, ``below_ncrit`` is a *traced* boolean, so read it outside the traced region or branch with ``jnp.where``. Here, ``chi`` is the full pull ``f_r|1 − f_r|0`` in GHz. Attributes ---------- pointer_states Complex ``α_j`` in the IQ plane, shape ``(levels,)``. photon_numbers Steady-state photons ``|α_j|²``, shape ``(levels,)``. sigma Integrated vacuum-noise width in the cavity amplitude plane. It is ``1/sqrt(2*efficiency*kappa_detected*tau)``, or infinity without detection. snr Pointer separation divided by ``sigma``. assignment_error ``½·erfc(SNR/(2√2))``, the optimal linear discriminant between two equal Gaussians. dephasing_rate Measurement-induced dephasing ``Γ_m = κ·|α₁ − α₀|²/2`` in 1/ns. chi_eff ``chi``, or ``chi/(1 + n̄₀/n_crit)`` when ``n_crit`` was given (GHz). validity See above. notes Physics that the analysis explicitly drops. """ pointer_states: Any photon_numbers: Any sigma: Any snr: Any assignment_error: Any dephasing_rate: Any chi_eff: Any validity: dict[str, Any] notes: list[str]
[docs] def summary(self) -> str: """Print and return a formatted summary. Concrete values only. Call it outside ``jax.jit``/``grad`` regions. """ alphas = np.asarray(self.pointer_states) photons = np.asarray(self.photon_numbers) lines = ["Dispersive readout (steady state):"] lines.append(" pointer states α_j = " + ", ".join(f"{a.real:+.4f}{a.imag:+.4f}j" for a in alphas)) lines.append(" photons n̄_j = " + ", ".join(f"{n:.4f}" for n in photons)) lines.append(f" SNR = {float(self.snr):.4f} (blob width σ = {float(self.sigma):.4f})") lines.append(f" assignment error = {float(self.assignment_error):.3e}") lines.append(f" dephasing Γ_m = {float(self.dephasing_rate):.3e} 1/ns") text = "\n".join(lines) print(text) return text
# --------------------------------------------------------------------------- # Public entry point # ---------------------------------------------------------------------------
[docs] def analyze_dispersive_readout( chi: Any, kappa: Any, tau: Any, *, n_photons: Any = None, eps: Any = None, delta_r: Any = 0.0, n_crit: Any = None, levels: int = 2, kappa_detected: Any = None, efficiency: Any = 1.0, ) -> DispersiveReadoutResult: """Calculate closed-form steady-state readout figures of merit from ``(chi, kappa)``. Parameters ---------- chi Full pull ``f_r|1-f_r|0`` in ordinary GHz. Its signed Pauli coefficient is ``-chi/2`` for ``Z=|0><0|-|1><1|``. Elimination reports this full pull. kappa Positive total resonator energy linewidth in 1/ns, including observed and unobserved vacuum loss. Exclude pure dephasing. Elimination's intrinsic downward-decay diagnostic is not a general replacement. tau Positive integration time in ns. n_photons Nonnegative ground-state steady photon number, or None. Give exactly one of ``n_photons`` and ``eps``. This sets ``eps=sqrt(n_photons*((kappa/2)**2+(2*pi*delta_r)**2))``. eps Complex drive rate in rad/ns, or None. Give exactly one of ``eps`` and ``n_photons``. It is the coefficient driving the cavity amplitude. delta_r Real ``f_r|0-f_drive`` in GHz. The default zero drives the ground-state cavity resonance. Positive values place the drive below resonance. n_crit Positive critical-occupation estimate, or None by default. When given, apply the phenomenological interpolation ``chi/(1+nbar_0/n_crit)``. Report ``n_over_ncrit`` and ``below_ncrit`` without certifying validity. levels Static Python integer of at least two, default two. Level ``j`` has the assumed pull ``j*chi``. Higher conditional frequencies are not derived from a multilevel Hamiltonian. kappa_detected Observed energy-loss rate in 1/ns, between zero and ``kappa``. None defaults to ``kappa``, representing complete collection. efficiency Detection efficiency between zero and one, default one. Zero gives zero SNR, infinite ``sigma``, and assignment error one half. Returns ------- DispersiveReadoutResult Raises ------ ValueError If drive arguments are ambiguous, ``levels`` is invalid, or concrete numerical values are nonfinite or outside their stated domains. Numerical domain checks are skipped for tracers. Callers must keep traced inputs within the same physical domains. Examples -------- >>> from quchip import analyze_dispersive_readout >>> ro = analyze_dispersive_readout(chi=0.002, kappa=0.005, tau=500.0, n_photons=2.0) >>> snr, p_err = ro.snr, ro.assignment_error Specify total and detected loss from the physical cavity and receiver. For example, ``kappa_detected=0.004`` with ``kappa=0.005`` leaves ``0.001/ns`` unobserved. """ if (n_photons is None) == (eps is None): raise ValueError("Provide exactly one of n_photons (target photons) or eps (drive rate, rad/ns).") if not isinstance(levels, int) or isinstance(levels, bool) or levels < 2: raise ValueError("levels must be a Python integer of at least two.") if kappa_detected is None: kappa_detected = kappa domains = ( ("kappa", kappa, True), ("tau", tau, True), ("n_crit", n_crit, True), ("n_photons", n_photons, False), ("kappa_detected", kappa_detected, False), ("efficiency", efficiency, False), ) for name, value, positive in domains: if value is None or contains_tracer(value): continue array = np.asarray(value) if not np.isrealobj(array) or np.any(~np.isfinite(array)) or np.any(array <= 0 if positive else array < 0): raise ValueError(f"{name} must be finite and {'positive' if positive else 'nonnegative'}.") if not contains_tracer(efficiency) and np.any(np.asarray(efficiency) > 1): raise ValueError("efficiency must not exceed one.") if not contains_tracer((kappa_detected, kappa)) and np.any(np.asarray(kappa_detected) > np.asarray(kappa)): raise ValueError("kappa_detected must not exceed kappa.") for name, value in (("chi", chi), ("delta_r", delta_r), ("eps", eps)): if value is not None and not contains_tracer(value) and np.any(~np.isfinite(np.asarray(value))): raise ValueError(f"{name} must be finite.") given = tuple(v for v in (chi, kappa, tau, n_photons, eps, delta_r, n_crit, kappa_detected, efficiency) if v is not None) xp: Any = np if any(is_jax_array(v) for v in given) or contains_tracer(given): import jax.numpy as jnp xp = jnp # The single GHz -> rad/ns boundary of this module (ordinary -> angular). delta_0 = TWO_PI * delta_r half_kappa = kappa / 2.0 if eps is None: eps = xp.sqrt(n_photons * (half_kappa**2 + delta_0**2)) alpha_0 = -1j * eps / (half_kappa + 1j * delta_0) n_0 = xp.abs(alpha_0) ** 2 notes = [ "Steady-state pointer states only: ring-up transients not modeled.", "Linear resonator, 2nd-order dispersive approximation.", "Measurement-induced qubit T1 not modeled.", "Vacuum energy-loss channels and coherent pointer states assumed.", ] # Argument presence is static. The interpolation remains traced. if n_crit is None: chi_eff = chi validity: dict[str, Any] = {} else: n_over_ncrit = n_0 / n_crit chi_eff = chi / (1.0 + n_over_ncrit) validity = {"n_over_ncrit": n_over_ncrit, "below_ncrit": n_over_ncrit < 1.0} notes.append("Phenomenological interpolation applied: chi_eff = chi/(1 + n̄₀/n_crit).") j = xp.arange(levels) delta_j = TWO_PI * (delta_r + chi_eff * j) alpha = -1j * eps / (half_kappa + 1j * delta_j) separation = xp.abs(alpha[1] - alpha[0]) collected = efficiency * kappa_detected snr = separation * xp.sqrt(2.0 * collected * tau) variance_rate = xp.where(collected > 0, 2.0 * collected * tau, 1.0) sigma = xp.where(collected > 0, 1.0 / xp.sqrt(variance_rate), xp.inf) return DispersiveReadoutResult( pointer_states=alpha, photon_numbers=xp.abs(alpha) ** 2, sigma=sigma, snr=snr, assignment_error=0.5 * _erfc(snr / (2.0 * np.sqrt(2.0)), xp), dephasing_rate=kappa * separation**2 / 2.0, chi_eff=chi_eff, validity=validity, notes=notes, )