Quickstart¶
This page runs minimal workflows end-to-end: analog and digital simulation, equivalence checking, environmental memory (characterize from the response matrix, then train a surrogate to predict probe density matrices under a control sequence), and Markovian noise digital-twin fitting. Install the package first (installation), then copy the cells below.
Every example in this guide uses show_progress=False on Simulator, MemoryCharacterizer, and NoiseCharacterizer so tqdm progress bars do not clutter the documentation; figures below each cell show the main results.
1. Analog simulation¶
Néel-initialized transverse-field Ising chain with on-site damping. Staggered \(\langle Z_i \rangle\) spreads and decays in a site-dependent way under open-system evolution:
1import matplotlib.pyplot as plt
2import numpy as np
3
4from mqt.yaqs import AnalogSimParams, Hamiltonian, NoiseModel, Observable, Simulator, State
5
6L = 5
7state = State(L, initial="Neel")
8hamiltonian = Hamiltonian.ising(L, J=1.0, g=0.8)
9noise_model = NoiseModel([
10 {"name": "lowering", "sites": [i], "strength": 0.06} for i in range(L)
11])
12
13params = AnalogSimParams(
14 observables=[Observable("z", site) for site in range(L)],
15 elapsed_time=4.0,
16 dt=0.1,
17 num_traj=16,
18 max_bond_dim=16,
19 order=2,
20 sample_timesteps=True,
21)
22
23sim = Simulator(show_progress=False)
24result = sim.run(state, hamiltonian, params, noise_model)
25
26heatmap = np.vstack([np.real(v) for v in result.expectation_values])
27fig, ax = plt.subplots(figsize=(6, 3.5), layout="constrained")
28im = ax.imshow(heatmap, aspect="auto", extent=(0, 4.0, L, 0), vmin=-1, vmax=1, cmap="RdBu_r")
29ax.set_xlabel("time")
30ax.set_yticks([x - 0.5 for x in range(1, L + 1)], [str(x) for x in range(L)])
31ax.set_ylabel("site")
32fig.colorbar(im, ax=ax, shrink=0.9, label=r"$\langle Z \rangle$")
33ax.set_title("Staggered magnetization under damping")
Text(0.5, 1.0, 'Staggered magnetization under damping')
2. Strong simulation¶
Evolve a short Trotterized Ising circuit and compare final \(\langle Z_i\rangle\) without noise and with an optional NoiseModel. See Strong Simulation for noise sweeps, mid-circuit sampling, and gate modes.
1from mqt.yaqs import NoiseModel, Observable, StrongSimParams
2from mqt.yaqs.core.libraries.circuit_library import create_ising_circuit
3
4num_qubits = 3
5qc = create_ising_circuit(L=num_qubits, J=1.0, g=0.8, dt=0.1, timesteps=6)
6circuit_state = State(num_qubits, initial="zeros")
7circuit_params = StrongSimParams(
8 observables=[Observable("z", site) for site in range(num_qubits)],
9 preset="fast",
10 num_traj=32,
11)
12noise_model = NoiseModel([
13 {"name": "lowering", "sites": [site], "strength": 0.05} for site in range(num_qubits)
14])
15
16clean_result = sim.run(circuit_state, qc, circuit_params)
17noisy_result = sim.run(State(num_qubits, initial="zeros"), qc, circuit_params, noise_model)
18clean_z = np.array([float(np.real(v[0])) for v in clean_result.expectation_values])
19noisy_z = np.array([float(np.real(v[0])) for v in noisy_result.expectation_values])
20
21fig, ax = plt.subplots(figsize=(5, 3), layout="constrained")
22x = np.arange(num_qubits)
23bar_width = 0.35
24ax.bar(x - bar_width / 2, clean_z, bar_width, label="unitary", color="0.55")
25ax.bar(x + bar_width / 2, noisy_z, bar_width, label="with damping", color="C0")
26ax.set_xticks(x, [rf"$\langle Z_{i}\rangle$" for i in range(num_qubits)])
27ax.set_ylim(-1.05, 1.05)
28ax.set_ylabel("expectation value")
29ax.set_title("Digital Ising circuit: optional open-system noise")
30ax.legend(frameon=False)
<matplotlib.legend.Legend at 0x7dbc98575fd0>
3. Equivalence checking¶
Verify that a native GHZ circuit matches its transpiled decomposition (different gate basis, same unitary) with EquivalenceChecker:
1from qiskit import transpile
2from qiskit.circuit import QuantumCircuit
3
4from mqt.yaqs import EquivalenceChecker
5
6ghz_native = QuantumCircuit(3)
7ghz_native.h(0)
8ghz_native.cx(0, 1)
9ghz_native.cx(1, 2)
10
11ghz_transpiled = transpile(
12 ghz_native,
13 basis_gates=["rz", "sx", "x", "cx"],
14 optimization_level=1,
15)
16
17checker = EquivalenceChecker(representation="mpo", threshold=1e-6)
18equiv = checker.check(ghz_native, ghz_transpiled)
19print(f"equivalent: {equiv['equivalent']}")
20print(f"fidelity: {equiv['fidelity']:.4e}")
21print(f"center-cut operator entropy: {equiv['center_cut_entanglement_entropy']:.4f}")
22print(f"global operator entropy: {equiv['global_entanglement_entropy']:.4f}")
23
24fig, ax = plt.subplots(figsize=(4.5, 3))
25ax.semilogy(equiv["schmidt_values"], "o-")
26ax.set_xlabel("Schmidt index")
27ax.set_ylabel("singular value")
28ax.set_title("Composed operator $W = U_2^\\dagger U_1$")
29fig.tight_layout()
equivalent: True
fidelity: 1.0000e+00
center-cut operator entropy: 0.0000
global operator entropy: 0.0000
For larger circuits, compiler passes, and OpenQASM inputs, see Equivalence Checking.
4. Characterize environmental memory¶
Probe a probe qubit coupled to a short chain at an interior temporal cut. The memory spectrum and response matrix show how many independent past branches remain visible at the cut:
1import numpy as np
2
3from mqt.yaqs import AnalogSimParams, Hamiltonian, MemoryCharacterizer
4from mqt.yaqs.characterization.memory.shared.utils import make_zero_psi
5
6length = 4
7ham = Hamiltonian.ising(length=length, J=1.0, g=1.0)
8params = AnalogSimParams(dt=0.1, max_bond_dim=16, order=1)
9mc = MemoryCharacterizer(show_progress=False)
10
11cut, num_interventions = 4, 6
12result = mc.characterize(
13 ham,
14 params,
15 num_interventions=num_interventions,
16 cut=cut,
17 n_pasts=6,
18 n_futures=6,
19 initial_psi=make_zero_psi(length),
20 rng=np.random.default_rng(0),
21)
22sv = result.singular_values(cut)
23v = result.response_matrix(cut)
24
25fig, axes = plt.subplots(1, 2, figsize=(8, 3))
26axes[0].semilogy(sv, "o-")
27axes[0].set_xlabel("mode index")
28axes[0].set_ylabel("singular value")
29axes[0].set_title(rf"Memory spectrum: $S_V(c={cut})={result.entropy(cut):.2f}$")
30
31im = axes[1].imshow(np.abs(v), aspect="auto", cmap="viridis")
32axes[1].set_title(rf"$|\widetilde{{V}}(c)|$, $R(c)={result.modes(cut):.1f}$")
33axes[1].set_xlabel("future probe")
34axes[1].set_ylabel("past probe")
35fig.colorbar(im, ax=axes[1], fraction=0.046, pad=0.04)
36fig.tight_layout()
5. Fit a Markovian noise digital twin (analytical optimization)¶
Learn Lindblad jump rates from observable trajectories with NoiseCharacterizer using analytical optimization (simulator forward model + CMA-ES trajectory matching).
1import warnings
2
3import numpy as np
4
5warnings.filterwarnings("ignore", message=".*special injected samples.*")
6
7from mqt.yaqs import AnalogSimParams, Hamiltonian, NoiseCharacterizer, NoiseModel, Observable, State
8
9n_sites = 3
10sites = list(range(n_sites))
11hamiltonian = Hamiltonian.ising(n_sites, J=1.0, g=2.0)
12init_state = State(n_sites, initial="zeros")
13fitting_observables = [Observable("y", 0), Observable("z", 0), Observable("y", 1)]
14sim_params = AnalogSimParams(
15 observables=fitting_observables,
16 elapsed_time=0.8,
17 dt=0.1,
18 order=1,
19 sample_timesteps=True,
20)
21reference_model = NoiseModel(
22 [{"name": "pauli_x", "sites": [s], "strength": 0.08} for s in sites]
23 + [{"name": "pauli_y", "sites": [s], "strength": 0.08} for s in sites]
24 + [{"name": "pauli_z", "sites": [s], "strength": 0.08} for s in sites]
25)
26init_guess = NoiseModel(
27 [{"name": "pauli_x", "sites": [s], "strength": 0.35} for s in sites]
28 + [{"name": "pauli_y", "sites": [s], "strength": 0.35} for s in sites]
29 + [{"name": "pauli_z", "sites": [s], "strength": 0.35} for s in sites]
30)
31
32result = NoiseCharacterizer(show_progress=False).characterize(
33 hamiltonian,
34 sim_params,
35 init_state=init_state,
36 init_guess=init_guess,
37 observables=fitting_observables,
38 reference_model=reference_model,
39 x_low=np.zeros(len(init_guess.processes)),
40 x_up=np.full(len(init_guess.processes), 0.5),
41 sigma0=0.05,
42 popsize=8,
43 max_iter=20,
44 seed=42,
45)
46
47times = result.times
48obs_labels = [r"$\langle Y_0\rangle$", r"$\langle Z_0\rangle$", r"$\langle Y_1\rangle$"]
49fig, axes = plt.subplots(1, 2, figsize=(8, 2.8), layout="constrained", sharey=True)
50for ax, traj, title in zip(axes, [result.fit_traj, result.ref_traj], ["learned twin", "reference"], strict=True):
51 im = ax.imshow(
52 traj,
53 aspect="auto",
54 extent=(times[0], times[-1], len(obs_labels), 0),
55 vmin=-1,
56 vmax=1,
57 cmap="RdBu_r",
58 )
59 ax.set_yticks([i + 0.5 for i in range(len(obs_labels))], obs_labels)
60 ax.set_xlabel("time")
61 ax.set_title(title)
62fig.colorbar(im, ax=axes, shrink=0.9, label="expectation")
63fig.suptitle(rf"Twin fit: RMSE={result.trajectory_rmse():.2e}", y=1.02)
Text(0.5, 1.02, 'Twin fit: RMSE=1.58e-02')
See Analytical Optimization Digital Twin from Experimental Trajectories for the full analytical-optimization workflow, experimental-data fitting, held-out prediction, and MCWF fitting.
6. Train a surrogate and predict under controls¶
Train a causal surrogate with MemoryCharacterizer, then predict the probe-qubit state after one or more control legs.
Pass an explicit per-leg list to compare different sequences on the same trained model.
Surrogate training requires PyTorch (pip install mqt.yaqs[torch]).
1rho0 = np.eye(2, dtype=np.complex128) / 2.0
2ham_sure = Hamiltonian.ising(length=2, J=1.0, g=1.0)
3
4model = mc.train(
5 ham_sure,
6 params,
7 num_interventions=1,
8 n=32,
9 train_kwargs={"epochs": 30, "batch_size": 8},
10 model_kwargs={"d_model": 32, "nhead": 4, "num_layers": 1, "dim_ff": 64},
11)
12
13hadamard = np.array([[1, 1], [1, -1]], dtype=np.complex128) / np.sqrt(2)
14pauli_x = np.array([[0, 1], [1, 0]], dtype=np.complex128)
15control_sequences = {
16 r"$\mathrm{H}$": [{"unitary": hadamard}],
17 r"$\mathrm{X}$": [{"unitary": pauli_x}],
18}
19
20pauli_ops = {
21 "X": np.array([[0, 1], [1, 0]], dtype=np.complex128),
22 "Y": np.array([[0, -1j], [1j, 0]], dtype=np.complex128),
23 "Z": np.array([[1, 0], [0, -1]], dtype=np.complex128),
24}
25expectations = {
26 label: [
27 float(np.trace(op @ mc.predict(model, rho0, controls, num_interventions=1)).real)
28 for op in pauli_ops.values()
29 ]
30 for label, controls in control_sequences.items()
31}
32
33pauli_names = list(pauli_ops)
34x = np.arange(len(pauli_names))
35width = 0.35
36
37fig, ax = plt.subplots(figsize=(5, 3.5))
38for offset, (label, values) in zip((-width / 2, width / 2), expectations.items()):
39 ax.bar(x + offset, values, width, label=f"control {label}")
40ax.set_xticks(x, pauli_names)
41ax.set_ylabel("expectation value")
42ax.set_title("Probe Pauli expectations for two control sequences")
43ax.legend(frameon=False)
44fig.tight_layout()
predict also accepts a style string (for example "haar") or a per-leg list mixing unitaries and measure–prepare slots. See Probing Environmental Memory for environmental memory probing and Memory Surrogate Training and Prediction for held-out accuracy checks and exact-reference validation.
7. Where to go next¶
Goal |
Start here |
|---|---|
Environmental memory probing |
|
Markovian noise digital-twin fitting |
Analytical Optimization Digital Twin from Experimental Trajectories |
Surrogate training, prediction, and exact validation |
|
Open-system dynamics, noise, time grids |
|
Bell-curve (log-normal) noise strengths |
|
Strong simulation, mid-circuit sampling, OpenQASM |
|
Accuracy presets and truncation knobs |
|
Check two circuits for equivalence |