Pulse-Level Simulation of Superconducting Qubits with QuTiP-QIP#

This notebook demonstrates how to use qutip-qip [LCM22] to perform pulse-level simulations of superconducting transmon qubits. It bridges the gap between the Hamiltonian-level design covered in the companion notebooks (scQubits parameter calculation and pymablock dispersive shift) and the actual control pulses that drive quantum gates on hardware.

We use qutip-jax as the JAX backend for QuTiP, leveraging the existing JAX integration in qpdk.

Why Pulse-Level Simulation?#

Ideal quantum gate models assume instantaneous, perfect operations. In practice, gates on superconducting qubits are realised by shaped microwave pulses applied over tens to hundreds of nanoseconds. Pulse-level simulation captures the actual time dynamics of the system, including:

  • Leakage to non-computational states (the third level of the transmon)

  • Gate errors from finite pulse duration and bandwidth

  • Decoherence from \(T_1\) relaxation and \(T_2\) dephasing

These effects are critical for designing high-fidelity quantum processors [KKY+19, KSBraumuller+20]. For some recent examples of qutip pulse-level simulation in the context of superconducting qubits, see [And24, Sal23].

Hide code cell source

import warnings

import jax.numpy as jnp
import matplotlib.pyplot as plt
import qutip
import qutip_jax  # noqa: F401 — registers JAX backend for QuTiP
from IPython.display import Math, display
from qutip_qip.circuit import QubitCircuit
from qutip_qip.device import SCQubits

from qpdk import PDK
from qpdk.cells.transmon import xmon_transmon
from qpdk.models.perturbation import ej_ec_to_frequency_and_anharmonicity

qutip.settings.core["numpy_backend"] = jnp
qutip.settings.core["default_dtype"] = "jax"

PDK.activate()

# Suppress FutureWarnings from qutip internals
warnings.filterwarnings("ignore", category=FutureWarning, module="qutip")

From Layout to Simulation: Design Mapping#

In a real quantum PDK (qpdk), the physical layout parameters directly determine the Hamiltonian parameters used in the pulse-level simulation.

Step 1: Layout Component#

We start with an Xmon transmon design. The sizes of arms and the gap between them determine the total shunt capacitance \(C_\Sigma\), which sets the charging energy \(E_C = e^2 / (2 C_\Sigma)\).

c_transmon = xmon_transmon(arm_width=[10.0] * 4, arm_lengths=[100.0] * 4)
c_transmon.plot()
plt.title("Xmon Transmon Layout (qpdk)")
plt.show()
../_images/b6fb0f11df60b6a00dc8105a30a5373129c863db9d16be6b3cb612a94a35183b.svg

Step 2: Parameter Extraction#

Changing the arm_width and arm_length in the layout modifies \(E_C\). For a transmon, the Josephson energy \(E_J\) is determined by the critical current of the junction.

Below we see how a change in \(E_C\) (e.g., from making the pads larger) affects the qubit frequency \(\omega_q\) and anharmonicity \(\alpha\).

# Fixed EJ = 15 GHz
EJ_val = 15.0

# EC = 0.3 GHz (standard pads) vs EC = 0.2 GHz (larger pads)
ec_values = [0.3, 0.2]

for ec in ec_values:
    wq_calc, alpha_calc = ej_ec_to_frequency_and_anharmonicity(EJ_val, ec)
    display(
        Math(rf"""
\text{{For }} E_C = {ec:.1f}\,\mathrm{{GHz}} \implies
\omega_q \approx {wq_calc:.3f}\,\mathrm{{GHz}}, \quad
\alpha \approx {alpha_calc:.3f}\,\mathrm{{GHz}}
""")
    )
\[\displaystyle \text{For } E_C = 0.3\,\mathrm{GHz} \implies \omega_q \approx 5.700\,\mathrm{GHz}, \quad \alpha \approx 0.300\,\mathrm{GHz} \]
\[\displaystyle \text{For } E_C = 0.2\,\mathrm{GHz} \implies \omega_q \approx 4.699\,\mathrm{GHz}, \quad \alpha \approx 0.200\,\mathrm{GHz} \]

Design Trade-off:

  • Larger pads (Lower \(E_C\)): Reduces \(\alpha\), which requires longer pulses to avoid leakage to \(|2\rangle\) (slower gates).

  • Smaller pads (Higher \(E_C\)): Increases \(\alpha\), allowing faster gates, but makes the qubit more sensitive to charge noise.

Pulse-Level Simulation: Single-Qubit X Gate#

We now simulate an \(X\) gate on a qubit with the extracted parameters. The SCQubits processor captures the time-domain pulse envelopes and the population dynamics.

# Using parameters extracted from layout (EC = 0.3 GHz, EJ = 15 GHz)
# QuTiP-QIP SCQubits expects negative anharmonicity for transmons,
# while the qpdk perturbation model returns positive values by convention.
wq0, a0 = ej_ec_to_frequency_and_anharmonicity(EJ_val, 0.3)
wq = [float(wq0), float(wq0) - 0.1]
alpha = [-float(a0), -float(a0)]

g_coupling = 0.1
wr = 6.5

qc_x = QubitCircuit(2)
qc_x.add_gate("X", targets=0)

processor_x = SCQubits(2, wq=wq, alpha=alpha, g=g_coupling, wr=wr)
processor_x.load_circuit(qc_x)

init_state = qutip.basis([3, 3], [0, 0])
result_x = processor_x.run_state(init_state)

Population Dynamics and Leakage#

We track the population in each basis state. The 3-level transmon model reveals any leakage to the \(|2\rangle\) state, which is heavily influenced by the anharmonicity \(\alpha\) set in the layout.

# Define projection operators for population tracking
proj_ops = {
    r"$|0,0\rangle$": qutip.tensor(qutip.fock_dm(3, 0), qutip.fock_dm(3, 0)),
    r"$|1,0\rangle$": qutip.tensor(qutip.fock_dm(3, 1), qutip.fock_dm(3, 0)),
    r"$|2,0\rangle$": qutip.tensor(qutip.fock_dm(3, 2), qutip.fock_dm(3, 0)),
}

tlist_x = jnp.asarray(result_x.times)

fig, ax = plt.subplots(figsize=(8, 4))
for label, proj in proj_ops.items():
    pops = [qutip.expect(proj, s) for s in result_x.states]
    ax.plot(tlist_x, pops, linewidth=2, label=label)

ax.set_xlabel("Time (ns)")
ax.set_ylabel("Population")
ax.set_title("X gate: population dynamics with leakage tracking")
ax.legend(loc="center right")
ax.grid(True, alpha=0.3)
plt.show()
../_images/8df7bd1ea596291ee6819e8c8f87867c81a64e5839e04af9ab47ce583c87ad61.svg

Bell State Preparation#

Entanglement between two qubits is realized via a CNOT gate. In this processor, the CNOT is implemented using Cross-Resonance (CR) pulses. The efficiency of the CR interaction depends on the frequency detuning \(\Delta = \omega_{q,1} - \omega_{q,2}\), which is set by the junction sizing in the layout.

qc_bell = QubitCircuit(2)
qc_bell.add_gate("SNOT", targets=0)  # Hadamard
qc_bell.add_gate("CNOT", controls=0, targets=1)

processor_bell = SCQubits(2, wq=wq, alpha=alpha, g=g_coupling, wr=wr)
processor_bell.load_circuit(qc_bell)

result_bell = processor_bell.run_state(init_state)
fig, axes = processor_bell.plot_pulses(figsize=(10, 8))
fig.suptitle(
    r"Control pulses for Bell state preparation ($H + \mathrm{CNOT}$)",
    fontsize=14,
    y=1.02,
)
fig.tight_layout()
plt.show()
../_images/a2ecf59236d204f415af23219be454218e676b42c0ad01b2cfa844692f54292a.svg

Fidelity Analysis#

We compute the state fidelity with the ideal Bell state.

# Ideal Bell state |Φ+⟩ = (|00⟩ + |11⟩)/√2
bell_ideal = (
    qutip.tensor(qutip.basis(3, 0), qutip.basis(3, 0))
    + qutip.tensor(qutip.basis(3, 1), qutip.basis(3, 1))
).unit()

final_state = result_bell.states[-1]
fidelity_bell = qutip.fidelity(final_state, bell_ideal)

display(
    Math(rf"""
\textbf{{Bell State Fidelity:}} \\
F(|\Phi^+\rangle) = {fidelity_bell:.6f}
""")
)
\[\displaystyle \textbf{Bell State Fidelity:} \\ F(|\Phi^+\rangle) = 1.000000 \]

Decoherence Effects: \(T_2\) Sweep#

Real qubits are limited by decoherence. If our layout has higher loss (e.g., from narrow gaps), the \(T_1\) and \(T_2\) times decrease, directly reducing the gate fidelity.

# Sweep T2 values (in ns) with fixed T1 = 80 μs
t1_fixed = 80_000.0  # 80 μs in ns
t2_values_us = jnp.array([5, 10, 20, 40, 80, 150])
t2_values_ns = t2_values_us * 1e3

fidelities_t2 = []
for t2 in t2_values_ns:
    proc = SCQubits(
        2, wq=wq, alpha=alpha, g=g_coupling, wr=wr, t1=t1_fixed, t2=float(t2)
    )
    proc.load_circuit(qc_bell)
    res = proc.run_state(init_state)
    f = qutip.fidelity(res.states[-1], bell_ideal)
    fidelities_t2.append(f)

fig, ax = plt.subplots(figsize=(8, 4))
ax.semilogx(t2_values_us, fidelities_t2, "o-", linewidth=2, markersize=8)
ax.set_xlabel(r"$T_2$ ($\mu$s)")
ax.set_ylabel(r"Bell state fidelity $F$")
ax.set_title(rf"Bell state fidelity vs. $T_2$ ($T_1 = {t1_fixed / 1e3:.0f}\,\mu$s)")
ax.grid(True, alpha=0.3, which="both")
plt.show()
../_images/137876401819995a5022d174baf0552919ea660b282705be52704fee7c430cff.svg

Summary: The Design Cycle#

This notebook demonstrated the connection between physical layout and pulse-level performance:

  1. Layout (qpdk): Geometric parameters (pad size, gap) determine \(C_\Sigma\) and \(L_J\).

  2. Hamiltonian (qpdk.models): Parameters \(E_C, E_J\) set \(\omega_q\) and \(\alpha\).

  3. Simulation (QuTiP): Pulse-level dynamics reveal gate fidelity and leakage.

  4. Iterate: If leakage is too high (\(|2\rangle\) population), we go back to step 1 and increase \(E_C\) in the layout.

For more details on parameter extraction, see the companion notebooks on scQubits parameter calculation.

References#

[And24]

Joona Andersson. Pulse-level simulations of the fermionic-simulation gate on a superconducting quantum processor. Master's thesis, Aalto University, March 2024. URL: https://urn.fi/URN:NBN:fi:aalto-202403172710.

[KSBraumuller+20]

Morten Kjaergaard, Mollie E. Schwartz, Jochen Braumüller, Philip Krantz, Joel I.-J. Wang, Simon Gustavsson, and William D. Oliver. Superconducting Qubits: Current State of Play. Annual Review of Condensed Matter Physics, 11(1):369–395, March 2020. doi:10.1146/annurev-conmatphys-031119-050605.

[KKY+19]

P. Krantz, M. Kjaergaard, F. Yan, T. P. Orlando, S. Gustavsson, and W. D. Oliver. A quantum engineer's guide to superconducting qubits. Applied Physics Reviews, 6(2):021318, June 2019. doi:10.1063/1.5089550.

[LCM22]

Boxi Li, Tommaso Calarco, and Felix Motzoi. Pulse-level noisy quantum circuits with QuTiP. Quantum, 6:630, January 2022. doi:10.22331/q-2022-01-24-630.

[Sal23]

Otto Salmenkivi. Mitigation of coherent errors in the control of superconducting qubits with composite pulse sequences. Master's thesis, University of Jyväskylä, 2023. URL: https://urn.fi/URN:NBN:fi:jyu-202304132446.