Przejdź do głównej treści
Ten projekt jest w wersji beta. Znalazłeś błąd? Otwórz zgłoszenie na GitHub. Masz pytanie lub pomysł? Zapytaj w Discussions — chętnie poznamy Twoją opinię!
Jak oceniasz jakość tłumaczenia tej strony?

Szybki start


title: Szybki start description: Przewodnik szybkiego startu dla pakietu dodatków Qiskit pauli-prop​

W tym przewodniku używamy pakietu pauli-prop, aby klasycznie zasymulować dynamikę czasową 10-kubitowego modelu Isinga z kopnięciami (kicked Ising) na jednowymiarowym łańcuchu spinów.

Przygotuj dane wejściowe do propagacji Pauliego​

Rozważany hamiltonian ma postać:

H=−J∑⟨i,j⟩ZiZj+h∑iXiH = -J\sum\limits_{\langle i,j \rangle} Z_iZ_j + h\sum\limits_iX_i

gdzie J>0J>0 opisuje sprzężenie spinów najbliższych sąsiadów, i<ji<j, a hh jest globalnym polem poprzecznym. Rozkład Trottera pierwszego rzędu operatora ewolucji w czasie zostanie zaimplementowany jako obwód kwantowy, UU, o 2020 krokach Trottera. Stała sprzężenia, JJ, zostanie ustalona na J=−π2J=-\frac{\pi}{2}, a hh na π6\frac{\pi}{6}. Oddziaływania ZZZZ zostaną zaimplementowane za pomocą bramek Clifforda (CXCX, SdgSdg, Y\sqrt{Y}).

Implementujemy ewolucję w czasie metodą Trottera jako obwód kwantowy i używamy π6\frac{\pi}{6} dla obrotów niecliffordowskich wokół osi x. Im dalej te kąty są od kątów Clifforda (na przykład θ=nπ2,n∈Z\theta=n\frac{\pi}{2}, n \in \mathbb{Z}), tym trudniej symulować układ metodami propagacji Pauliego.

Jako obserwablę wybieramy średnią magnetyzację pojedynczego węzła, 1N∑i=1N⟨zi⟩\frac{1}{N} \sum_{i=1}^{N} \langle z_i \rangle, gdzie NN jest liczbą spinów.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "pauli_prop": "pauli-prop", "qiskit": "qiskit", "qiskit_aer": "qiskit-aer"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import CouplingMap

num_qubits = 10
coupling_map = CouplingMap.from_line(num_qubits, bidirectional=False)

# Num Trotter steps
num_steps = 20
theta_rx = np.pi / 6

# Average single-site magnetization
observable = (
SparsePauliOp(
[
"I" * iq + "Z" + "I" * (num_qubits - iq - 1)
for iq in range(num_qubits)
]
)
/ num_qubits
)

# Create the Trotter circuit
num_qubits = 10
num_steps = 20
theta_rx = np.pi / 6
circuit = QuantumCircuit(num_qubits)
edges = CouplingMap.from_line(num_qubits, bidirectional=False).get_edges()
for _ in range(num_steps):
circuit.rx(theta_rx, [i for i in range(num_qubits)])
for edge in edges:
circuit.sdg(edge)
circuit.ry(np.pi / 2, edge[1])
circuit.cx(edge[0], edge[1])
circuit.ry(-np.pi / 2, edge[1])
circuit.draw("mpl", fold=-1)

Output of the previous code cell

Zasymuluj ewolucję czasową układu za pomocą propagacji Pauliego​

Gdy mamy już obwód, UU, i obserwablę, OO, możemy łatwo zasymulować układ w kilku krokach:

  • Rozdziel UU na część cliffordowską, CC, i niecliffordowską, PP, tak aby U=PCU=PC, używając evolve_through_cliffords

  • Propaguj OO przez PP, otrzymując nowy operator, O′O^\prime, za pomocą pauli_prop.propagate_through_circuit

  • Przeprowadź ewolucję O′O^\prime przez cliffordowską część obwodu, używając obsługi ewolucji Clifforda wbudowanej w Qiskit

  • Przybliż wartość oczekiwaną jako ⟨0∣O′∣0⟩≈⟨0∣U†OU∣0⟩\langle0|O^\prime|0\rangle \approx \langle0|U^\dagger OU|0\rangle, sumując współczynniki w O′O^\prime związane z w pełni diagonalnymi wyrazami Pauliego (wyrazami Pauliego zawierającymi I lub Z na wszystkich kubitach). Pamiętaj, że jest to przybliżenie, ponieważ obcinaliśmy wyrazy z O′O^\prime podczas jego propagacji przez niecliffordowską część obwodu.

import time

from pauli_prop import evolve_through_cliffords, propagate_through_circuit

cliff, non_cliff = evolve_through_cliffords(circuit)

max_terms_list = [10**i for i in range(8)]
approx_evs = []
durations = []
for max_terms in max_terms_list:
st = time.perf_counter()
evolved_obs = propagate_through_circuit(
observable, non_cliff, max_terms=max_terms, atol=1e-12, frame="h"
)[0]
evolved_obs.paulis = evolved_obs.paulis.evolve(cliff, frame="h")
durations.append(time.perf_counter() - st)
approx_evs.append(
float(evolved_obs.coeffs[~evolved_obs.paulis.x.any(axis=1)].sum())
)

Gdy wykonujemy większe obliczenia, przybliżenia wartości oczekiwanej stają się dokładniejsze. W tym przykładzie wysycamy całą przestrzeń Pauliego około 410≈1064^{10}\approx10^6, co widać po wypłaszczeniu się krzywej między dwoma ostatnimi punktami.

Choć poniższy wykres pokazuje monotoniczną zbieżność, symulacje propagacji Pauliego na ogół nie zbiegają monotonicznie. Nie jest niczym niezwykłym, że na takich wykresach widać „poszarpane” zachowanie.

import matplotlib.pyplot as plt
from qiskit_aer import AerSimulator

sim_circ = circuit.copy()
sim_circ.save_statevector()
backend = AerSimulator(method="statevector")
psi = backend.run(sim_circ).result().data()["statevector"]
exact_ev = psi.expectation_value(observable)

ax1 = plt.gca()
ax1.plot(max_terms_list, approx_evs, marker="o", label="Approximate")
ax1.axhline(exact_ev, linestyle="--", color="green", label="Exact")
ax1.set_xscale("log")
ax1.set_xlabel("# terms kept")
ax1.set_ylabel(r"$\frac{1}{N} \sum_{i=1}^{N} \langle z_i \rangle$")

ax2 = ax1.twinx()
ax2.plot(
max_terms_list, durations, marker=".", label="Runtime", color="orange"
)
ax2.set_ylabel("Runtime (s)", color="orange")
ax2.set_yscale("log")

handles1, labels1 = ax1.get_legend_handles_labels()
handles2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(handles1 + handles2, labels1 + labels2, loc="lower right")

plt.title(f"Simulating {num_steps}-step 1D Ising Model")
Text(0.5, 1.0, 'Simulating 20-step 1D Ising Model')

Output of the previous code cell