r"""Perturbation theory helpers for superconducting quantum circuits.
This module provides helper functions for computing effective Hamiltonians
and energy corrections using quasi-degenerate perturbation theory via
`pymablock <https://pymablock.readthedocs.io/>`_
:cite:`arayaDayPymablockAlgorithmPackage2025`.
The primary application is computing the **dispersive shift** :math:`\chi`
of a resonator coupled to a transmon qubit, which is the key quantity for
dispersive readout of superconducting qubits
:cite:`blaisCircuitQuantumElectrodynamics2021,kochChargeinsensitiveQubitDesign2007a`.
The transmon-resonator Hamiltonian (without the rotating wave approximation)
is:
.. math::
\mathcal{H} = -\omega_t\, a_t^\dagger a_t
+ \frac{\alpha}{2}\, a_t^{\dagger 2} a_t^2
+ \omega_r\, a_r^\dagger a_r
- g\,(a_t^\dagger - a_t)(a_r^\dagger - a_r)
where :math:`\omega_t` is the transmon frequency, :math:`\omega_r` the resonator
frequency, :math:`\alpha` the anharmonicity, and :math:`g` the coupling strength.
References:
- :cite:`arayaDayPymablockAlgorithmPackage2025`
- :cite:`blaisCircuitQuantumElectrodynamics2021`
- :cite:`kochChargeinsensitiveQubitDesign2007a`
"""
from __future__ import annotations
from functools import partial
import jax
import jax.numpy as jnp
import sympy
from jax.typing import ArrayLike
from sympy.physics.quantum import Dagger
from sympy.physics.quantum.boson import BosonOp
def transmon_resonator_hamiltonian() -> tuple[
sympy.Expr, sympy.Expr, tuple[sympy.Symbol, ...]
]:
r"""Build the symbolic transmon-resonator Hamiltonian.
Constructs the unperturbed (:math:`H_0`) and perturbation (:math:`H_p`)
parts of the transmon-resonator Hamiltonian using bosonic operators.
The Hamiltonian is split as :math:`H = H_0 + H_p` where:
.. math::
\begin{aligned}
H_0 &= -\omega_t\, a_t^\dagger a_t
+ \frac{\alpha}{2}\, a_t^{\dagger 2} a_t^2
+ \omega_r\, a_r^\dagger a_r \\
H_p &= -g\,(a_t^\dagger - a_t)(a_r^\dagger - a_r)
\end{aligned}
Returns:
A tuple ``(H_0, H_p, symbols)`` where ``symbols`` is
``(omega_t, omega_r, alpha, g)``.
Example:
>>> H_0, H_p, (omega_t, omega_r, alpha, g) = transmon_resonator_hamiltonian()
"""
omega_t, omega_r, alpha, g = sympy.symbols(
r"\omega_{t} \omega_{r} \alpha g", real=True, positive=True
)
a_t, a_r = BosonOp("a_t"), BosonOp("a_r")
H_0 = (
-omega_t * Dagger(a_t) * a_t
+ omega_r * Dagger(a_r) * a_r
+ alpha * Dagger(a_t) ** 2 * a_t**2 / 2
)
H_p = -g * (Dagger(a_t) - a_t) * (Dagger(a_r) - a_r)
return H_0, H_p, (omega_t, omega_r, alpha, g)
[docs]
@partial(jax.jit, inline=True)
def dispersive_shift(
ω_t_ghz: float | ArrayLike,
ω_r_ghz: float | ArrayLike,
α_ghz: float | ArrayLike,
g_ghz: float | ArrayLike,
) -> float | jax.Array:
r"""Compute the dispersive shift numerically.
Evaluates the second-order dispersive shift for a transmon coupled
to a resonator. Uses the analytical formula derived from perturbation
theory (without the rotating wave approximation)
:cite:`kochChargeinsensitiveQubitDesign2007a,blaisCircuitQuantumElectrodynamics2021`:
.. math::
\chi = \frac{2g^2}{\Delta - \alpha}
- \frac{2g^2}{\Delta}
- \frac{2g^2}{\omega_t + \omega_r + \alpha}
+ \frac{2g^2}{\omega_t + \omega_r}
where :math:`\Delta = \omega_t - \omega_r`. The first two terms give
the rotating-wave-approximation (RWA) contribution
.. math::
\chi_\text{RWA} = \frac{2 \alpha g^2}{\Delta(\Delta - \alpha)}
and the last two are corrections from the counter-rotating terms.
All parameters are in GHz, and the returned value is also in GHz.
Args:
ω_t_ghz: Transmon frequency in GHz.
ω_r_ghz: Resonator frequency in GHz.
α_ghz: Transmon anharmonicity in GHz (positive value).
g_ghz: Coupling strength in GHz.
Returns:
Dispersive shift :math:`\chi` in GHz.
Example:
>>> χ = dispersive_shift(5.0, 7.0, 0.2, 0.1)
>>> print(f"χ = {χ * 1e3:.2f} MHz")
"""
Δ = ω_t_ghz - ω_r_ghz
# Full expression including counter-rotating terms
return (
2 * g_ghz**2 / (Δ - α_ghz)
- 2 * g_ghz**2 / Δ
- 2 * g_ghz**2 / (ω_t_ghz + ω_r_ghz + α_ghz)
+ 2 * g_ghz**2 / (ω_t_ghz + ω_r_ghz)
)
[docs]
@partial(jax.jit, inline=True)
def dispersive_shift_to_coupling(
χ_ghz: float | ArrayLike,
ω_t_ghz: float | ArrayLike,
ω_r_ghz: float | ArrayLike,
α_ghz: float | ArrayLike,
) -> float | jax.Array:
r"""Compute the coupling strength from a target dispersive shift.
Inverts the dispersive shift relation to find the coupling strength
:math:`g` required to achieve a desired :math:`\chi`. Uses only the
dominant rotating-wave term
:cite:`kochChargeinsensitiveQubitDesign2007a`:
.. math::
g \approx \sqrt{\frac{-\chi\,\Delta\,(\Delta - \alpha)}{2\alpha}}
where :math:`\Delta = \omega_t - \omega_r`.
Note:
The expression under the square root may be negative when the
sign of the target :math:`\chi` is inconsistent with the detuning
and anharmonicity (e.g., positive :math:`\chi` with
:math:`\Delta < 0`). In that case the absolute value is taken
so that the returned coupling strength is always real and
non-negative, but the caller should verify self-consistency
via :func:`dispersive_shift`.
Args:
χ_ghz: Target dispersive shift in GHz (typically negative).
ω_t_ghz: Transmon frequency in GHz.
ω_r_ghz: Resonator frequency in GHz.
α_ghz: Transmon anharmonicity in GHz (positive value;
the physical anharmonicity of a transmon is negative, but
following the Hamiltonian convention used throughout this
module, :math:`\alpha` is taken as positive).
Returns:
Coupling strength :math:`g` in GHz.
Example:
>>> g = dispersive_shift_to_coupling(-0.001, 5.0, 7.0, 0.2)
>>> print(f"g = {g * 1e3:.1f} MHz")
"""
Δ = ω_t_ghz - ω_r_ghz
g_squared = -χ_ghz * Δ * (Δ - α_ghz) / (2 * α_ghz)
return jnp.sqrt(jnp.abs(g_squared))
[docs]
@partial(jax.jit, inline=True)
def ej_ec_to_frequency_and_anharmonicity(
ej_ghz: float | ArrayLike, ec_ghz: float | ArrayLike
) -> tuple[float | jax.Array, float | jax.Array]:
r"""Convert :math:`E_J` and :math:`E_C` to qubit frequency and anharmonicity.
Uses the standard transmon approximations
:cite:`kochChargeinsensitiveQubitDesign2007a`:
.. math::
\begin{aligned}
\omega_q &\approx \sqrt{8 E_J E_C} - E_C \\
\alpha &\approx E_C
\end{aligned}
Note:
The physical anharmonicity of a transmon is *negative*
(:math:`\alpha = -E_C`), but the Hamiltonian convention used
in this module and in pymablock takes :math:`\alpha` as positive.
Args:
ej_ghz: Josephson energy in GHz.
ec_ghz: Charging energy in GHz.
Returns:
Tuple of ``(ω_q_ghz, α_ghz)``.
Example:
>>> ω_q, α = ej_ec_to_frequency_and_anharmonicity(20.0, 0.2)
>>> print(f"ω_q = {ω_q:.2f} GHz, α = {α:.1f} GHz")
"""
return jnp.sqrt(8 * ej_ghz * ec_ghz) - ec_ghz, ec_ghz
[docs]
@partial(jax.jit, inline=True)
def purcell_decay_rate(
g_ghz: float | ArrayLike,
ω_t_ghz: float | ArrayLike,
ω_r_ghz: float | ArrayLike,
κ_ghz: float | ArrayLike,
) -> float | jax.Array:
r"""Estimate the Purcell decay rate of a transmon through a resonator.
The Purcell effect limits qubit lifetime when coupled to a lossy
resonator. In the dispersive regime
:cite:`houckControllingSpontaneousEmission2008,blaisCircuitQuantumElectrodynamics2021`:
.. math::
\gamma_\text{Purcell} = \kappa \left(\frac{g}{\Delta}\right)^2
where :math:`\kappa` is the resonator decay rate and
:math:`\Delta = \omega_t - \omega_r`.
Args:
g_ghz: Coupling strength in GHz.
ω_t_ghz: Transmon frequency in GHz.
ω_r_ghz: Resonator frequency in GHz.
κ_ghz: Resonator linewidth (decay rate) in GHz.
Returns:
Purcell decay rate in GHz.
Example:
>>> γ = purcell_decay_rate(0.1, 5.0, 7.0, 0.001)
>>> T_purcell = 1 / (γ * 1e9)
>>> print(f"T_Purcell = {T_purcell * 1e6:.0f} µs")
"""
Δ = ω_t_ghz - ω_r_ghz
return κ_ghz * (g_ghz / Δ) ** 2
[docs]
@partial(jax.jit, inline=True)
def resonator_linewidth_from_q(
ω_r_ghz: float | ArrayLike, q_ext: float | ArrayLike
) -> float | jax.Array:
r"""Compute resonator linewidth from external quality factor.
Converts external quality factor to linewidth (half-linewidth at half-maximum)
:cite:`gopplCoplanarWaveguideResonators2008a,m.pozarMicrowaveEngineering2012`:
.. math::
\kappa = \frac{\omega_r}{Q_\text{ext}}
Args:
ω_r_ghz: Resonator frequency in GHz.
q_ext: External quality factor.
Returns:
Resonator linewidth :math:`\kappa` in GHz.
Example:
>>> κ = resonator_linewidth_from_q(7.0, 10_000)
>>> print(f"κ = {κ * 1e6:.1f} kHz")
"""
return ω_r_ghz / q_ext
[docs]
@partial(jax.jit, inline=True)
def measurement_induced_dephasing(
χ_ghz: float | ArrayLike, κ_ghz: float | ArrayLike, n_bar: float | ArrayLike
) -> float | jax.Array:
r"""Estimate measurement-induced dephasing rate.
During dispersive readout, photons in the resonator cause
additional dephasing of the qubit
:cite:`gambettaQubitphotonInteractionsCavity2006,blaisCircuitQuantumElectrodynamics2021`:
.. math::
\Gamma_\phi = \frac{8 \chi^2 \bar{n}}{\kappa}
where :math:`\bar{n}` is the mean photon number in the resonator.
Args:
χ_ghz: Dispersive shift in GHz.
κ_ghz: Resonator linewidth in GHz.
n_bar: Mean photon number in the resonator during measurement.
Returns:
Measurement-induced dephasing rate in GHz.
Example:
>>> Γ_φ = measurement_induced_dephasing(-0.001, 0.001, 5.0)
>>> print(f"Γ_φ = {Γ_φ * 1e6:.1f} kHz")
"""
return 8 * χ_ghz**2 * n_bar / κ_ghz