Transmon-Resonator Chain Emulation

This example simulates a qubit–resonator–qubit chain with coupled_transmon() (dipole coupling per coupled_transmon()).

We prepare \(|100\rangle\) (left transmon excited) and evolve for one resonant swap period \(T_{\mathrm{swap}} = \pi/(\sqrt{2}\,g)\). The same evolution is run twice:

  1. Noiseless — unitary analog simulation (TDVP on the MPO).

  2. Noisy — open-system simulation with relaxation and dephasing on the qubit sites (TJM trajectories).

PVM observables track probabilities for bitstrings using only local indices \(0\) and \(1\) per site. With qubit_dim = resonator_dim = 3, population in the \(|2\rangle\) level appears as leakage (not counted in those bitstrings).

1. Hamiltonian and initial state

 1import numpy as np
 2from mqt.yaqs import Hamiltonian, State
 3
 4length = 3  # qubit – resonator – qubit
 5qubit_dim = 3
 6resonator_dim = 3
 7w_q = 4 / (2 * np.pi)
 8w_r = 4 / (2 * np.pi)
 9alpha = -0.3 / (2 * np.pi)
10g = 0.2 / (2 * np.pi)
11
12H_0 = Hamiltonian.coupled_transmon(
13    length=length,
14    qubit_dim=qubit_dim,
15    resonator_dim=resonator_dim,
16    qubit_freq=w_q,
17    resonator_freq=w_r,
18    anharmonicity=alpha,
19    coupling=g,
20)
21
22T_swap = np.pi / (np.sqrt(2) * g)
23dt = T_swap / 100
24
25# |100⟩: left qubit (site 0) in |1⟩
26state = State(
27    length,
28    initial="basis",
29    basis_string="100",
30    physical_dimensions=[qubit_dim, resonator_dim, qubit_dim],
31)

2. Observables and shared parameters

 1from mqt.yaqs import AnalogSimParams, Observable
 2
 3all_bitstrings = ["000", "001", "010", "011", "100", "101", "110", "111"]
 4
 5sim_params = AnalogSimParams(
 6    observables=[Observable(bstr) for bstr in all_bitstrings],
 7    elapsed_time=T_swap,
 8    dt=dt,
 9    sample_timesteps=True,
10)
11
12
13def pvm_curve(result, bitstring: str) -> np.ndarray:
14    for obs, vals in zip(result.observables, result.expectation_values, strict=True):
15        if obs.gate.bitstring == bitstring:
16            return np.asarray(vals, dtype=float)
17    msg = f"bitstring {bitstring!r} not in observables"
18    raise ValueError(msg)
19
20
21def leakage_at_t(result, t_idx: int) -> float:
22    leak = 1.0
23    for obs, vals in zip(result.observables, result.expectation_values, strict=True):
24        if obs.gate.bitstring in all_bitstrings:
25            leak -= float(vals[t_idx])
26    return leak

3. Noiseless SWAP

1import copy
2
3from mqt.yaqs import Simulator
4
5sim = Simulator(show_progress=False)
6result_clean = sim.run(copy.deepcopy(state), H_0, copy.deepcopy(sim_params))
 1p100_clean = pvm_curve(result_clean, "100")
 2p001_clean = pvm_curve(result_clean, "001")
 3times = sim_params.times
 4
 5print(f"noiseless  P(|001⟩) at T_swap = {p001_clean[-1]:.4f}")
 6print(f"noiseless  P(|100⟩) at T_swap = {p100_clean[-1]:.4f}")
 7print(f"noiseless  leakage at T_swap = {leakage_at_t(result_clean, -1):.4f}")
 8
 9assert p100_clean[-1] < 0.05 + 1e-9, "left qubit should be depopulated"
10assert p001_clean[-1] > 0.9 - 1e-9, "right qubit should receive excitation"
11assert leakage_at_t(result_clean, -1) < 0.05 + 1e-9
noiseless  P(|001⟩) at T_swap = 0.9937
noiseless  P(|100⟩) at T_swap = 0.0030
noiseless  leakage at T_swap = 0.0023

4. Noisy SWAP

Relaxation and dephasing on transmon sites (even indices). Built-in lowering and pauli_z processes are 2×2; for qubit_dim = 3 we pass explicit jump matrices (Destroy and a computational-subspace dephasing operator). For Gaussian and other distributed noise strengths, see Realistic Noise Models.

 1from mqt.yaqs import NoiseModel
 2from mqt.yaqs.core.libraries.gate_library import Destroy
 3
 4relax = Destroy(qubit_dim).matrix
 5dephase = np.diag([1.0, -1.0, 1.0]).astype(complex)  # |2⟩ unaffected
 6
 7noise_model = NoiseModel(
 8    [{"name": "t1", "sites": [i], "strength": 0.03, "matrix": relax} for i in (0, 2)]
 9    + [{"name": "dephase", "sites": [i], "strength": 0.02, "matrix": dephase} for i in (0, 2)]
10)
11
12noisy_params = AnalogSimParams(
13    observables=[Observable(bstr) for bstr in all_bitstrings],
14    elapsed_time=T_swap,
15    dt=dt,
16    sample_timesteps=True,
17    num_traj=32,
18    random_seed=7,
19)
20
21result_noisy = sim.run(copy.deepcopy(state), H_0, noisy_params, noise_model)
1p100_noisy = pvm_curve(result_noisy, "100")
2p001_noisy = pvm_curve(result_noisy, "001")
3
4print(f"noisy      P(|001⟩) at T_swap = {p001_noisy[-1]:.4f}")
5print(f"noisy      P(|100⟩) at T_swap = {p100_noisy[-1]:.4f}")
6print(f"noisy      leakage at T_swap = {leakage_at_t(result_noisy, -1):.4f}")
7
8assert p001_noisy[-1] < p001_clean[-1] - 0.02, "noise should reduce swap fidelity"
noisy      P(|001⟩) at T_swap = 0.0999
noisy      P(|100⟩) at T_swap = 0.0572
noisy      leakage at T_swap = 0.0136

5. Comparison plot

 1import matplotlib.pyplot as plt
 2
 3fig, (ax_pop, ax_leak) = plt.subplots(1, 2, figsize=(9, 3.5))
 4
 5ax_pop.plot(times, p001_clean, "-", color="tab:blue", label=r"noiseless $P(|001\rangle)$")
 6ax_pop.plot(times, p100_clean, "-", color="tab:orange", label=r"noiseless $P(|100\rangle)$")
 7ax_pop.plot(times, p001_noisy, "--", color="tab:blue", label=r"noisy $P(|001\rangle)$")
 8ax_pop.plot(times, p100_noisy, "--", color="tab:orange", label=r"noisy $P(|100\rangle)$")
 9ax_pop.axvline(T_swap, color="gray", linestyle=":", alpha=0.6, label=r"$T_{\mathrm{swap}}$")
10ax_pop.set_xlabel("time")
11ax_pop.set_ylabel("probability")
12ax_pop.set_title("SWAP populations: noiseless vs noisy")
13ax_pop.legend(fontsize=8)
14ax_pop.grid(alpha=0.3)
15
16leak_clean = [leakage_at_t(result_clean, i) for i in range(len(times))]
17leak_noisy = [leakage_at_t(result_noisy, i) for i in range(len(times))]
18ax_leak.plot(times, leak_clean, "-", color="tab:green", label="noiseless leakage")
19ax_leak.plot(times, leak_noisy, "--", color="tab:red", label="noisy leakage")
20ax_leak.set_xlabel("time")
21ax_leak.set_ylabel("leakage")
22ax_leak.set_title("Population outside 0/1 subspace per site")
23ax_leak.legend(fontsize=8)
24ax_leak.grid(alpha=0.3)
25
26plt.tight_layout()
27plt.show()