r"""Unimon qubit models.
This module provides both a SAX microwave S-parameter model and a quantum
Hamiltonian model for the unimon qubit.
**Microwave model** — The unimon is modeled as a capacitively coupled closed
system consisting of two quarter-wave CPW transmission-line sections shunted by
a Josephson junction (SQUID).
**Hamiltonian model** — The effective single-mode Hamiltonian of the unimon is
.. math::
\hat{H} = 4 E_\text{C} \hat{n}^2
+ \tfrac{1}{2} E_\text{L} (\hat{\varphi} - \varphi_{\text{ext}})^2
- E_\text{J} \cos\hat{\varphi}
where :math:`E_\text{C}` is the charging energy, :math:`E_\text{L}` the inductive energy
from the geometric inductance of the resonator arms, :math:`E_\text{J}` the
Josephson energy, and :math:`\varphi_{\text{ext}}` the external flux bias
(in units of the reduced flux quantum).
The unimon operates in the regime :math:`E_\text{L} \sim E_\text{J}`, which is distinct
from both the transmon (:math:`E_\text{J} \gg E_\text{C}`, no geometric inductance) and
the fluxonium (:math:`E_\text{L} \ll E_\text{J}`).
References:
- :cite:`hyyppaUnimonQubit2022`
- :cite:`tuohinoMultimodePhysicsUnimon2024`
- :cite:`dudaParameterOptimizationUnimon2025`
"""
import math
import jax
import jax.numpy as jnp
import sax
from jax.typing import ArrayLike
from sax.models.rf import capacitor, tee
from qpdk.models.constants import DEFAULT_FREQUENCY, Φ_0, h
from qpdk.models.generic import lc_resonator
from qpdk.models.jit import jit
from qpdk.models.waveguides import straight_shorted
# ---------------------------------------------------------------------------
# Helper: energy-scale conversions
# ---------------------------------------------------------------------------
[docs]
@jit(inline=True)
def el_to_arm_inductance(el_ghz: ArrayLike) -> jax.Array:
r"""Convert inductive energy :math:`E_\text{L}` to geometric inductance of one arm :math:`L`.
The total inductive energy :math:`E_\text{L}` of the unimon is related to the
geometric inductance of its two arms (in series) by:
.. math::
E_\text{L} = \frac{\Phi_0^2}{4 \pi^2 \cdot 2 L}
= \frac{(\hbar / 2e)^2}{2 L}
Solving for the inductance :math:`L` of a single arm:
.. math::
L = \frac{\Phi_0^2}{8 \pi^2 E_\text{L}}
Args:
el_ghz: Inductive energy in GHz.
Returns:
Geometric inductance of one arm in Henries.
Example:
>>> L = el_to_arm_inductance(5.0) # 5 GHz inductive energy
>>> print(f"{L * 1e9:.2f} nH")
"""
el_joules = jnp.asarray(el_ghz) * 1e9 * h
return Φ_0**2 / (8 * math.pi**2 * el_joules)
# ---------------------------------------------------------------------------
# SAX microwave model
# ---------------------------------------------------------------------------
def _unimon_internal(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
arm_length: float = 3000.0,
cross_section: str = "cpw",
junction_capacitance: float = 5e-15,
junction_inductance: float = -7e-9,
) -> sax.SDict:
r"""Internal SAX S-parameter model for a closed unimon qubit.
The unimon is modeled as two shorted quarter-wave CPW transmission lines
connected by a parallel LC element representing the Josephson junction
(linearised RCSJ model).
.. svgbob::
o1 ── λ/4 TL ──┬──L_J──┬── λ/4 TL ── o2
│ │
└──C_J──┘
(junction)
The two ports ``o1`` and ``o2`` correspond to the nodes on either side of
the Josephson junction. The other ends of the transmission lines are
shorted to ground.
Args:
f: Array of frequency points in Hz.
arm_length: Length of each quarter-wave resonator arm in µm.
cross_section: Cross-section specification for the CPW arms.
junction_capacitance: Junction capacitance :math:`C_\text{J}` in Farads.
junction_inductance: Junction inductance :math:`L_\text{J}` in Henries.
Returns:
sax.SDict: S-parameters dictionary with internal ports ``o1`` and ``o2``.
"""
f = jnp.asarray(f)
# Two quarter-wave shorted transmission-line arms
arm_left = straight_shorted(f=f, length=arm_length, cross_section=cross_section)
arm_right = straight_shorted(f=f, length=arm_length, cross_section=cross_section)
# Josephson junction as parallel LC
junction = lc_resonator(
f=f,
capacitance=junction_capacitance,
inductance=junction_inductance,
grounded=False,
)
# Tee junctions to connect junction in between arms
tee_l = tee(f=f)
tee_r = tee(f=f)
instances: dict[str, sax.SType] = {
"arm_left": arm_left,
"arm_right": arm_right,
"junction": junction,
"tee_l": tee_l,
"tee_r": tee_r,
}
connections = {
# Left arm connects to left tee
"arm_left,o1": "tee_l,o1",
# Right arm connects to right tee
"arm_right,o1": "tee_r,o1",
# Junction between tees
"tee_l,o2": "junction,o1",
"junction,o2": "tee_r,o2",
}
ports = {
"o1": "tee_l,o3",
"o2": "tee_r,o3",
}
return sax.evaluate_circuit_fg((connections, ports), instances)
[docs]
def unimon_coupled(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
arm_length: float = 3000.0,
cross_section: str = "cpw",
junction_capacitance: float = 5e-15,
junction_inductance: float = -7e-9,
coupling_capacitance: float = 10e-15,
) -> sax.SDict:
r"""SAX model for a capacitively coupled unimon qubit.
The unimon is modeled as a closed system of two shorted quarter-wave CPW
resonators connected by a Josephson junction. Interaction with the qubit
is provided via a single coupling capacitor connected to one of the arms.
.. svgbob::
o1 ── Cc ──┬── λ/4 TL ── (ground)
│
Junction
│
└── λ/4 TL ── (ground)
Args:
f: Array of frequency points in Hz.
arm_length: Length of each quarter-wave resonator arm in µm.
cross_section: Cross-section specification for the CPW arms.
junction_capacitance: Junction capacitance :math:`C_\text{J}` in Farads.
junction_inductance: Junction inductance :math:`L_\text{J}` in Henries.
coupling_capacitance: Coupling capacitance :math:`C_\text{c}` in Farads.
Returns:
sax.SDict: S-parameters dictionary with a single port ``o1``.
"""
f = jnp.asarray(f)
instances: dict[str, sax.SType] = {
"unimon": _unimon_internal(
f=f,
arm_length=arm_length,
cross_section=cross_section,
junction_capacitance=junction_capacitance,
junction_inductance=junction_inductance,
),
"coupling_cap": capacitor(f=f, capacitance=coupling_capacitance),
}
connections = {
"coupling_cap,o2": "unimon,o1",
}
ports = {
"o1": "coupling_cap,o1",
}
return sax.evaluate_circuit_fg((connections, ports), instances)
# ---------------------------------------------------------------------------
# Quantum Hamiltonian model
# ---------------------------------------------------------------------------
[docs]
def unimon_hamiltonian(
ec_ghz: float = 1.0,
el_ghz: float = 5.0,
ej_ghz: float = 10.0,
phi_ext: float = jnp.pi,
n_max: int = 30,
) -> jax.Array:
r"""Build the unimon Hamiltonian matrix in the harmonic-oscillator basis.
The effective single-mode unimon Hamiltonian is
.. math::
\hat{H} = 4 E_\text{C} \hat{n}^2
+ \tfrac{1}{2} E_\text{L} (\hat{\varphi} - \varphi_{\text{ext}})^2
- E_\text{J} \cos\hat{\varphi}
**Energy Scales and Physics**:
- :math:`E_\text{J}`: The Josephson energy of the single non-linear junction.
- :math:`E_\text{C}`: The effective charging energy. The large geometric capacitance
of the CPW resonator means this is lower than typical transmons.
- :math:`E_\text{L}`: The inductive energy provided by the CPW resonator shunting
the junction. The unimon operates in the regime :math:`E_\text{L} \sim E_\text{J}`.
**Similarities to Fluxonium**:
The effective single-mode circuit model of the unimon is mathematically identical
to a fluxonium qubit. The inductive shunt (provided by the CPW resonators)
guarantees there are no isolated superconducting islands, making the unimon
intrinsically immune to dephasing caused by low-frequency :math:`1/f` charge noise.
**Phase and Charge Operators**:
Because the unimon is inductively shunted, its potential energy features a
quadratic term :math:`\tfrac{1}{2} E_\text{L} \hat{\varphi}^2`, meaning the potential
is **not** :math:`2\pi`-periodic. Thus, the phase operator :math:`\hat{\varphi}`
must be treated as an extended, non-compact variable. Consequently, the
conjugate charge variable :math:`n` is continuous, rather than taking discrete
integer values as it does for a transmon.
*Note on this implementation*: The Hamiltonian is represented in the
eigenbasis of the harmonic part :math:`4 E_\text{C} \hat{n}^2
+ \tfrac{1}{2} E_\text{L} \hat{\varphi}^2`, truncated to
:math:`2 n_{\max} + 1` states, with
:math:`\hat{\varphi} = \varphi_\text{zpf} (\hat{a} + \hat{a}^\dagger)`,
:math:`\hat{n} = \tfrac{i}{2 \varphi_\text{zpf}} (\hat{a}^\dagger - \hat{a})` and
:math:`\varphi_\text{zpf} = (2 E_\text{C} / E_\text{L})^{1/4}`.
:math:`\cos\hat{\varphi}` is evaluated from the eigendecomposition of the
truncated :math:`\hat{\varphi}`.
See :cite:`hyyppaUnimonQubit2022` and :cite:`tuohinoMultimodePhysicsUnimon2024`.
Args:
ec_ghz: Charging energy :math:`E_\text{C}` in GHz.
el_ghz: Inductive energy :math:`E_\text{L}` in GHz.
ej_ghz: Josephson energy :math:`E_\text{J}` in GHz.
phi_ext: External flux bias :math:`\varphi_{\text{ext}}`
in radians (reduced flux-quantum units).
The optimal operating point is :math:`\pi`.
n_max: Basis truncation; Hilbert-space dimension is
:math:`2 n_{\max} + 1`.
Returns:
A ``(2*n_max+1, 2*n_max+1)`` Hermitian matrix (in GHz).
"""
n_states = 2 * n_max + 1
a = jnp.diag(jnp.sqrt(jnp.arange(1, n_states, dtype=float)), k=1)
phi_zpf = (2 * ec_ghz / el_ghz) ** 0.25
phi_hat = phi_zpf * (a + a.T)
# Build cos(phi) from the truncated phi operator rather than truncating cos(phi)
phi_eigvals, phi_eigvecs = jnp.linalg.eigh(phi_hat)
cos_phi = (phi_eigvecs * jnp.cos(phi_eigvals)) @ phi_eigvecs.T
harmonic = jnp.diag(jnp.sqrt(8 * ec_ghz * el_ghz) * (jnp.arange(n_states) + 0.5))
# H = 4 E_C n^2 + 0.5 E_L (phi - phi_ext)^2 - E_J cos(phi)
return (
harmonic
- el_ghz * phi_ext * phi_hat
+ 0.5 * el_ghz * phi_ext**2 * jnp.eye(n_states)
- ej_ghz * cos_phi
)
[docs]
def unimon_energies(
ec_ghz: float = 1.0,
el_ghz: float = 5.0,
ej_ghz: float = 10.0,
phi_ext: float = jnp.pi,
n_max: int = 30,
n_levels: int = 5,
) -> jax.Array:
r"""Compute the lowest energy eigenvalues of the unimon Hamiltonian.
Diagonalises the Hamiltonian returned by :func:`unimon_hamiltonian` and
returns the ``n_levels`` lowest eigenvalues, shifted so that the ground
state energy is zero.
Args:
ec_ghz: Charging energy :math:`E_\text{C}` in GHz.
el_ghz: Inductive energy :math:`E_\text{L}` in GHz.
ej_ghz: Josephson energy :math:`E_\text{J}` in GHz.
phi_ext: External flux :math:`\varphi_{\text{ext}}` in radians.
n_max: Basis truncation, see :func:`unimon_hamiltonian`.
n_levels: Number of lowest energy levels to return.
Returns:
Array of shape ``(n_levels,)`` with energies in GHz,
referenced to the ground state.
Example:
>>> energies = unimon_energies(ec_ghz=1.0, el_ghz=5.0, ej_ghz=10.0)
>>> f_01 = float(energies[1]) # qubit frequency in GHz
>>> alpha = float(energies[2] - 2 * energies[1]) # anharmonicity
"""
H = unimon_hamiltonian(
ec_ghz=ec_ghz,
el_ghz=el_ghz,
ej_ghz=ej_ghz,
phi_ext=phi_ext,
n_max=n_max,
)
eigenvalues = jnp.linalg.eigvalsh(H)
# Shift to ground state = 0
eigenvalues = eigenvalues - eigenvalues[0]
return eigenvalues[:n_levels]
[docs]
def unimon_frequency_and_anharmonicity(
ec_ghz: float = 1.0,
el_ghz: float = 5.0,
ej_ghz: float = 10.0,
phi_ext: float = jnp.pi,
n_max: int = 30,
) -> tuple[float, float]:
r"""Compute the qubit transition frequency and anharmonicity of the unimon.
The qubit frequency is
.. math::
f_{01} = E_1 - E_0
and the anharmonicity is
.. math::
\alpha = (E_2 - E_1) - (E_1 - E_0) = E_2 - 2 E_1
(since :math:`E_0 = 0` after shifting).
Args:
ec_ghz: Charging energy :math:`E_\text{C}` in GHz.
el_ghz: Inductive energy :math:`E_\text{L}` in GHz.
ej_ghz: Josephson energy :math:`E_\text{J}` in GHz.
phi_ext: External flux :math:`\varphi_{\text{ext}}` in radians.
n_max: Basis truncation, see :func:`unimon_hamiltonian`.
Returns:
Tuple of (frequency_ghz, anharmonicity_ghz).
Example:
>>> f01, alpha = unimon_frequency_and_anharmonicity(
... ec_ghz=1.0, el_ghz=5.0, ej_ghz=10.0
... )
>>> print(f"f_01 = {f01:.3f} GHz, alpha = {alpha:.3f} GHz")
"""
energies = unimon_energies(
ec_ghz=ec_ghz,
el_ghz=el_ghz,
ej_ghz=ej_ghz,
phi_ext=phi_ext,
n_max=n_max,
n_levels=3,
)
f_01 = float(energies[1])
alpha = float(energies[2] - 2 * energies[1])
return f_01, alpha