Skip to main contentIBM Quantum Documentation Mirror

Simulate a 127-qubit kicked-Ising model

In this guide we use the pauli-prop package to classically simulate the time dynamics of a 127-qubit kicked-Ising model on a heavy-hex lattice and approximate the single-site magnetization, Z62\langle Z_{62} \rangle. The Hamiltonian considered is:

H=Ji,jZiZj+hiXiH = -J\sum\limits_{\langle i,j \rangle} Z_iZ_j + h\sum\limits_iX_i

where J>0J>0 describes the coupling of nearest-neighbor spins, i<ji<j, and hh is the global transverse field. A first-order Trotter decomposition of the time-evolved operator will be implemented as a quantum circuit, UU, over 2020 Trotter steps. The coupling constant, JJ, will be fixed at J=π2J=-\frac{\pi}{2} such that UU is Clifford any time hmodπ2=0h\mod\frac{\pi}{2}=0.

Workflow:

  • Create quantum circuits implementing the Trotterized Ising model
    • Vary hh between Clifford points, 0.00.0 and π2\frac{\pi}{2}
  • Choose a site in the middle of the lattice (qubit 6262) and estimate the magnetization after 2020 Trotter steps for each circuit
  • Observe the magnetization roll off from 1.01.0 to 0.00.0 as hh moves from one Clifford point to another

Create circuits

First, we use FakeSherbrooke from qiskit-ibm-runtime to get the edge indices for the coupling map of the 127-qubit Eagle QPU. Once we have the edge indices, we create the Trotter circuits for the varying values of hh we want to simulate. The circuits will implement a first-order Trotterization of the Hamiltonian using 20 Trotter steps and 5420 gates.

import numpy as np
from qiskit import QuantumCircuit
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke

# Get the edges in the device coupling map
backend = FakeSherbrooke()
edges = backend.coupling_map.get_edges()

# Num Trotter steps
num_steps = 20

# Create Trotter circuits with varying global fields
hs = [i * np.pi / 32 for i in range(17)]
circuits = []
for h in hs:
    circuit = QuantumCircuit(backend.num_qubits)
    for _ in range(num_steps):
        circuit.rx(h, [i for i in range(backend.num_qubits)])
        for edge in edges:
            circuit.rzz(-np.pi / 2, edge[0], edge[1])
    circuits.append(circuit)

Simulate the time dynamics with Pauli propagation

Next, we specify the observable, OO, we want to measure. In this case, we estimate Z62\langle Z_{62} \rangle.

To study the magnetization between Clifford points, we approximately propagate OO to the beginning of the circuit, UU, resulting in a new observable, OUOUO^\prime \approx U^{\dagger}OU.

During propagation, the number of terms in the evolved observable grows exponentially in the number of gates, so it is usually necessary to limit how large it can grow. Here, we limit the number of terms in OO^\prime to 10510^5 and truncate terms with coefficients smaller than 10510^{-5}. Truncating terms results in some error. Once we have OO^\prime, we can easily estimate the expectation value, 0UOU0=0O0\langle0|U^{\dagger}OU|0\rangle = \langle0|O^\prime|0\rangle, by summing the coefficients of the diagonal terms in OO^\prime (terms consisting only of I or Z on all qubits).

We set frame='h' to specify we want to evolve the observable in the Heisenberg framework (UOUU^{\dagger}OU). Use frame=s for Schrödinger evolution (UOUUOU^{\dagger}).

import time

from pauli_prop import propagate_through_circuit
from qiskit.quantum_info import Pauli, SparsePauliOp

# Z_62 observable
id_pauli = Pauli("I" * backend.num_qubits)
observable = SparsePauliOp(id_pauli.dot(Pauli("Z"), [62]))

# Estimate the single-site magnetization for varying magnetic fields
times = []
approx_evs = []
for circuit in circuits:
    st = time.perf_counter()
    propagated_obs = propagate_through_circuit(
        observable, circuit, 100_000, 1e-5, frame="h"
    )[0]
    times.append(time.perf_counter() - st)
    approx_evs.append(
        float(
            propagated_obs.coeffs[~propagated_obs.paulis.x.any(axis=1)].sum()
        )
    )
print(f"Finished {len(circuits)} simulations in {sum(times):.0f}s")

Output:

Finished 17 simulations in 494s

Visualize

Finally, we plot the magnetization as a function of hh. When h=0h=0, the circuit does not affect the initial state in the computational basis, and we know 0O0=1.0\langle0|O|0\rangle = 1.0. As hh moves further from 00, the magnetization decays monotonically until hh reaches the next Clifford angle, π2\frac{\pi}{2}, and the magnetization converges to 00.

import matplotlib.pyplot as plt

plt.xlabel(r"$R_x$ angle $\theta_h$")
plt.ylabel(r"$\langle Z_{62} \rangle$")
plt.plot(hs, approx_evs, marker="o")

Output:

[<matplotlib.lines.Line2D at 0x123f68b00>]
Output of the previous code cell