Ensemble Evolution

This page demonstrates workflows for computing two-time correlations in a deterministic (noiseless, unitary) ensemble in YAQS. The focus is on compact, executable examples:

  • Single-state auto/two-time correlations.

  • Ensemble-averaged correlations (typicality view).

  • Small periodic spin-current transport example.

1. Unitary analog evolution primer

In unitary analog evolution, we have no noise or tensor jumps. To trigger the backend to perform a unitary dynamics, you can avoid passing noise_model='' to Simulator.run because noise_model is set to None by default.

 1import numpy as np
 2import matplotlib.pyplot as plt
 3
 4from mqt.yaqs import Simulator
 5from mqt.yaqs.core.data_structures.hamiltonian import Hamiltonian
 6from mqt.yaqs.core.data_structures.simulation_parameters import AnalogSimParams, Observable
 7from mqt.yaqs.core.data_structures.state import State
 8from mqt.yaqs.core.libraries.gate_library import BaseGate, Z, X, Y
 9
10sim = Simulator(show_progress=False)
 1L = 6
 2Jxx = 1.0
 3delta = 0.7
 4h_x = 0.4
 5
 6# Open XXZ + transverse field: H = Jxx ∑_r (S^x_r S^x_{r+1} + S^y_r S^y_{r+1}) + Δ ∑_r S^z_r S^z_{r+1} + h_x ∑_r S^x_r
 7# (Pauli convention in code: S^α = σ^α/2, matching two_body prefactors 0.25 * Jxx / Δ.)
 8H_open = Hamiltonian.pauli(
 9    length=L,
10    two_body=[(0.25 * Jxx, "X", "X"), (0.25 * Jxx, "Y", "Y"), (0.25 * delta, "Z", "Z")],
11    one_body=[(0.5 * h_x, "X")],
12    bc="open",
13)
14
15mid = L // 2
16psi0 = State(L, initial="haar-random", pad=2)
17
18primer_params = AnalogSimParams(
19    observables=[Observable(Z(), mid)],
20    elapsed_time=5.0,
21    dt=0.15,
22    max_bond_dim=64,
23    svd_threshold=1e-10,
24    sample_timesteps=True,
25)
26
27result_primer = sim.run(psi0, H_open, primer_params)
28times_primer = primer_params.times
29zexp_primer = result_primer.expectation_values[0]
1fig, ax = plt.subplots(1, 1, figsize=(5.4, 3.2))
2ax.plot(times_primer, zexp_primer, marker="o", ms=3)
3ax.set_xlabel("t")
4ax.set_ylabel(r"$\langle S^z_m(t)\rangle$")
5ax.set_title("Single-state unitary evolution")
6ax.grid(alpha=0.3)
7plt.show()
../_images/472a69bbfb9ac8ca2d8da09725366713bfe631ba1489051b97ad9d73697aa775.svg

2. Two-time Correlations

For an initial state \(|\psi(0)\rangle\) and unitary \(U(t)\):

  • Autocorrelation (for one observable \(O\)): [ C_{OO}(t) = \langle \psi(0)| U^\dagger(t), O, U(t), O |\psi(0)\rangle. ]

  • Generic two-time correlation (probe \(A\) and kick \(B\)): [ C_{AB}(t) = \langle \psi(0)| U^\dagger(t), A, U(t), B |\psi(0)\rangle. ]

These quantities probe dynamical memory and relaxation. They are standard observables in dynamical quantum typicality (DQT) and related finite-temperature dynamics studies, where one compares single-trajectory and ensemble-averaged behavior.

The unitary-ensemble backend computes multi_time_observables pairs for list[State] inputs (each with representation="mps", the default). Autocorrelation is the special case where both the observables are the same (O, O). For a single-state demonstration, we pass a list with one element.

 1sz_mid = Observable(Z(), mid)
 2sx_mid = Observable(X(), mid)
 3
 4single_state_params = AnalogSimParams(
 5    observables=[],
 6    elapsed_time=5.0,
 7    dt=0.15,
 8    max_bond_dim=64,
 9    svd_threshold=1e-10,
10    sample_timesteps=True,
11    multi_time_observables=[(sz_mid, sz_mid), (sz_mid, sx_mid)],  # row 0: C_zz(t), row 1: C_zx(t)
12)
13
14sim = Simulator(show_progress=False)
15result_single = sim.run(
16    [State(L, initial="haar-random", pad=2)], H_open, single_state_params
17)
18
19t_single = result_single.multi_time_times
20czz_single = result_single.multi_time_results[0]
21czx_single = result_single.multi_time_results[1]
1fig, ax = plt.subplots(1, 1, figsize=(5.8, 3.4))
2ax.plot(t_single, np.real(czz_single), "o-", label=r"$C_{zz}(t)$")
3ax.plot(t_single, np.real(czx_single), "s--", label=r"$C_{zx}(t)$")
4ax.set_xlabel("t")
5ax.set_ylabel(r"$C_{ab}(t)$")
6ax.set_title("Single-state two-time correlations")
7ax.legend()
8ax.grid(alpha=0.3)
9plt.show()
../_images/fe77288f1f69afb224a3a898ecdcde150205cd3cafe79746af548eee7ed06819.svg

3. Typicality view: from one state to an ensemble

In dynamical typicality studies, one often averages correlations over an ensemble of initial states. Under certain thermalisation guarantees, one can show that the typical relaxation behavior of any state can be represented by an ensemble average of the expectation over randomly initialised states. For sufficiently rich ensembles, this can approximate high-temperature traces and reveal robust transport trends.

YAQS supports this directly by passing list[State] into Simulator.run. Each member evolves independently, which, when parallelized by the unitary backend, offers computational advantage to calculate these variables.

 1num_states = 8
 2ensemble_states = [State(L, initial="haar-random", pad=2) for _ in range(num_states)]
 3
 4ensemble_params = AnalogSimParams(
 5    observables=[],
 6    elapsed_time=5.0,
 7    dt=0.15,
 8    max_bond_dim=64,
 9    svd_threshold=1e-10,
10    sample_timesteps=True,
11    multi_time_observables=[
12        (Observable(Z(), mid), Observable(Z(), mid)),  # C_zz(t) autocorrelation
13        (Observable(Z(), mid), Observable(X(), mid)),  # C_zx(t)
14    ],
15)
16
17result_ens = sim.run(ensemble_states, H_open, ensemble_params)
18t_ens = result_ens.multi_time_times
19czz_ens = result_ens.multi_time_results[0]
20czx_ens = result_ens.multi_time_results[1]
1fig, ax = plt.subplots(1, 1, figsize=(5.8, 3.4))
2ax.plot(t_ens, np.real(czz_ens), "o-", label=r"ensemble $C_{zz}(t)$")
3ax.plot(t_ens, np.real(czx_ens), "s--", label=r"ensemble $C_{zx}(t)$")
4ax.set_xlabel("t")
5ax.set_ylabel(r"$\overline{C}_{ab}(t)$")
6ax.set_title(f"Typicality-style ensemble average of $C_{{zz}}(t)$ and $C_{{zx}}(t)$ (N={num_states})")
7ax.legend()
8ax.grid(alpha=0.3)
9plt.show()
../_images/41a2540f3a4c19c3a4b2a57c9b0a1fb248d71194b81b2255cb7d3bdec7fd9592.svg

In this illustrative run, the ensemble-averaged \(C_{zz}(t)\) appears to decay toward zero while \(C_{zx}(t)\) stays comparatively close to zero over the sampled window; other runs may show different behavior.

4. Spin transport example: periodic spin-current autocorrelation

For periodic XXZ chains, define local bond current

[ j_r = J_{xx} \bigl(S_r^x S_{r+1}^y - S_r^y S_{r+1}^x\bigr) ]

and total current \(J = \sum_r j_r\). The normalized autocorrelator

[ C_{JJ}(t) = \frac{1}{L},\langle J(t),J(0)\rangle ]

can be assembled from all bond-pair two-time correlators. Such current autocorrelations are central to linear-response spin transport; dynamical typicality makes it practical to estimate high-temperature ensemble quantities from a few random pure-state trajectories (Steiningeweg et al., Phys. Rev. Lett. 112, 120601 (2014)). For finite-temperature Drude weights, diffusion, and integrable XXZ phenomenology—including the role of conservation laws—see the review (Bertini et al., Rev. Mod. Phys. 93, 025003 (2021)).

 1def spin_current_bond_matrix(j_coupling: float) -> np.ndarray:
 2    x = np.array([[0.0, 1.0], [1.0, 0.0]], dtype=np.complex128)
 3    y = np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=np.complex128)
 4    return 0.25 * j_coupling * (np.kron(x, y) - np.kron(y, x))
 5
 6
 7def periodic_bonds(length: int) -> list[tuple[int, int]]:
 8    return [(i, (i + 1) % length) for i in range(length)]
 9
10
11def current_observables(length: int, j_coupling: float) -> list[Observable]:
12    j_mat = spin_current_bond_matrix(j_coupling)
13    gate = BaseGate(j_mat)
14    return [Observable(gate, sites=[i, j]) for i, j in periodic_bonds(length)]
 1Ltr = 6
 2Jxx = 1.0
 3deltas = [0.1, 0.5, 1.5]
 4t_final = 5.0
 5dt = 0.15
 6n_transport_states = 4
 7
 8states_transport = [State(Ltr, initial="haar-random", pad=2) for _ in range(n_transport_states)]
 9bond_obs = current_observables(Ltr, Jxx)
10pairs_jj = [(a, b) for a in bond_obs for b in bond_obs]
11
12transport_curves: dict[float, np.ndarray] = {}
13t_transport = None
14for d in deltas:
15    h_periodic = Hamiltonian.pauli(
16        length=Ltr,
17        two_body=[(0.25 * Jxx, "X", "X"), (0.25 * Jxx, "Y", "Y"), (0.25 * d, "Z", "Z")],
18        one_body=[],
19        bc="periodic",
20    )
21    sp = AnalogSimParams(
22        observables=[],
23        elapsed_time=t_final,
24        dt=dt,
25        max_bond_dim=64,
26        svd_threshold=1e-10,
27        sample_timesteps=True,
28        multi_time_observables=pairs_jj,
29    )
30    result_transport = sim.run(states_transport, h_periodic, sp)
31    assert result_transport.multi_time_results is not None
32    t_transport = result_transport.multi_time_times
33    c_jj = np.real(np.sum(result_transport.multi_time_results, axis=0) / Ltr)
34    transport_curves[d] = c_jj
1fig, ax = plt.subplots(1, 1, figsize=(6.0, 3.5))
2for d in deltas:
3    ax.plot(t_transport, transport_curves[d], marker="o", ms=3, label=rf"$\Delta={d}$")
4ax.set_xlabel("t")
5ax.set_ylabel(r"$C_{JJ}(t)$")
6ax.set_title("Periodic XXZ spin-current autocorrelation (small illustrative setup)")
7ax.legend()
8ax.grid(alpha=0.3)
9plt.show()
../_images/cf259077baaa3c55dbaacba5ac83d6009c505b3f7cc80570bf0824da23a7ce04.svg

This finite-size, short-time run already shows different relaxation trends for different anisotropies. In the thermodynamic limit and Kubo picture, the long-time behavior of \(C_{JJ}(t)\) is tied to the spin Drude weight and to ballistic versus diffusive transport in the XXZ chain; Bertini et al., Rev. Mod. Phys. 93, 025003 (2021) summarizes the established finite-temperature picture (including subtleties at \(\Delta=1\) and in finite systems). The illustrative curves here use small \(L\) and a handful of Haar-random states; larger-scale or higher-accuracy studies follow typicality, as in Steiningeweg et al., Phys. Rev. Lett. 112, 120601 (2014).

Tip

Practical notes: scaling runs and MPS entanglement

  • Scale gradually: L, ensemble size, dt, elapsed_time, and max_bond_dim.

  • Enable ensemble parallelization (Simulator(parallel=True)) when you have many initial states.

  • MPS entanglement: under unitary evolution, entanglement entropy and required bond dimension typically grow with time (until truncation or saturation). For longer times or larger \(L\), increase max_bond_dim, tighten svd_threshold only with care, or shorten the window so the MPS remains an accurate ansatz for your observable.