Sari la conținutul principal
Acest proiect este în versiune beta. Ai găsit o eroare? Deschide un issue pe GitHub. Ai o întrebare sau o idee? Întreabă în Discussions — ne-ar plăcea să aflăm părerea ta!
Cum este calitatea traducerii pe această pagină?

Extrapolare la zero zgomot cu amplificare probabilistică a erorilor

Extrapolarea la zero zgomot (ZNE) este o tehnică de atenuare a zgomotului cuantic în calculele valorilor medii ale observabilelor. Funcționează executând un circuit cuantic la scale de zgomot crescătoare, calculând valorile medii pentru aceste scale de zgomot și extrapolând pentru a estima o valoare medie la zero zgomot. Atât etapa de amplificare a zgomotului, cât și cea de extrapolare pot fi implementate în moduri diferite. În acest ghid vei amplifica zgomotul prin amplificare probabilistică a erorilor (PEA), așa cum se sugerează în "Evidence for the utility of quantum computing before fault tolerance". Pentru a amplifica cu PEA, trebuie mai întâi să modelezi zgomotul care afectează porțile de încâlcire ale circuitului. Poți implementa amplificarea și prin pliere digitală a porților, care nu necesită un model de zgomot și este tratată în ghidul de pliere a porților. Vei învăța zgomotul cu qiskit-noise-learning și vei extrapola cu strategia de ajustare exponențială predefinită.

Ține cont că, deși ZNE poate fi eficientă în atenuarea zgomotului cuantic la un cost de eșantionare mai mic decât alte metode, nu oferă nicio limită pentru bias.

ZNE extrapolation

Fluxul de lucru este următorul:

  1. Specifică un circuit și o observabilă
  2. Calculează valorile medii exacte folosind simulatorul de vector de stare din Qiskit
  3. Calculează valorile medii zgomotoase, neatenuate
  4. Învață zgomotul straturilor de încâlcire cu qiskit-noise-learning
  5. Pregătește un program ZNE cu amplificare probabilistică a erorilor și eșantionează circuite cu scale de zgomot variate
  6. Extrapolează la limita de zero zgomot
  7. Compară valorile medii atenuate

Specifică un circuit și o observabilă​

În acest ghid vei calcula magnetizarea medie pe situsuri ⟨Z⟩avg=1N∑i=1N⟨Zi⟩\langle Z \rangle_{\text{avg}} = \frac{1}{N} \sum_{i=1}^{N} \langle Z_i \rangle a unui model Ising cu câmp transversal (TFIM) trotterizat pe opt qubiți într-o topologie inelară. Vei parcurge mai multe valori ale intensității câmpului transversal și vei reprezenta grafic magnetizarea în funcție de intensitatea câmpului. Într-o topologie inelară cu un număr par de qubiți, straturile de porți pe doi qubiți pot fi împărțite în două straturi unice, așa cum arată culorile roșu și verde din harta de cuplare de mai jos. Proiectarea circuitelor cu un set minim de straturi de încâlcire unice este importantă dacă vrei să înveți un model de zgomot, deoarece straturile unice trebuie învățate independent. Dacă ai prea multe straturi unice în circuit, învățarea zgomotului poate deveni nefezabilă din cauza costului de eșantionare al învățării multor straturi.

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

_needed = {"IPython": "IPython", "matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "qiskit_mitigation": "qiskit-mitigation", "qiskit_noise_learning": "qiskit-noise-learning", "samplomatic": "samplomatic", "scipy": "scipy"}
_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 warnings

import IPython.display
import numpy as np
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.visualization import plot_coupling_map

warnings.filterwarnings("ignore")

def trotter_circuit(num_qubits, theta, layers, num_steps):
"""Trotterized transverse-field Ising evolution: RX on every site, then a CZ layer per coupling layer."""
circuit = QuantumCircuit(num_qubits)
for _ in range(num_steps):
circuit.rx(theta, range(num_qubits))
for layer in layers:
circuit.barrier()
for edge in layer:
circuit.cz(*edge)
circuit.sdg(edge)
circuit.barrier()
return circuit

num_qubits = 8
trotter_steps = 15
ring_coupling = [(i, (i + 1) % num_qubits) for i in range(num_qubits)]
layers = [
ring_coupling[0::2],
ring_coupling[1::2],
] # two disjoint layers of CZ gates
theta = Parameter("theta")
circuit = trotter_circuit(num_qubits, theta, layers, trotter_steps)

# Parameter values: theta in [0, 0.25 pi] - values for the transverse-field sweep
data_points = 6
parameter_values = np.linspace(0, 0.25 * np.pi, data_points).reshape(
(data_points, 1)
)

# Define observables - Z on each site
observables = [
SparsePauliOp("I" * (num_qubits - q - 1) + "Z" + "I" * q)
for q in range(num_qubits)
]

print("Edges of the same color may execute their gates simultaneously.")
IPython.display.display(
plot_coupling_map(
num_qubits=num_qubits,
qubit_coordinates=None,
coupling_map=ring_coupling,
figsize=(4, 4),
qubit_size=150,
line_width=5,
line_color=[
"red" if edge in layers[0] else "green" for edge in ring_coupling
],
font_size=90,
)
)
print("Visualization of 1 Trotter step")
trotter_circuit(num_qubits, theta, layers, 1).draw("mpl", scale=0.7)
Edges of the same color may execute their gates simultaneously.

Output of the previous code cell

Visualization of 1 Trotter step

Output of the previous code cell

Calculează valorile medii exacte folosind simulatorul de vector de stare din Qiskit​

Folosește clasa Statevector din Qiskit pentru a calcula valoarea medie exactă a lui ZiZ_i pe fiecare situs pentru fiecare valoare a lui θ\theta.

from qiskit.quantum_info import Statevector
import matplotlib.pyplot as plt

exact_evs = np.array(
[
[
Statevector(circuit.assign_parameters([value]))
.expectation_value(obs)
.real
for value in parameter_values.ravel()
]
for obs in observables
]
) # shape (observables, parameter values)

x_vals = parameter_values.ravel() / np.pi
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(x_vals, exact_evs.mean(axis=0), "ko-", label="Exact")
ax.set_xlabel(r"$\theta/\pi$")
ax.set_ylabel(r"$\langle Z \rangle_{\text{avg}}$")
ax.legend()
plt.tight_layout()
plt.show()

Output of the previous code cell

Calculează valorile medii zgomotoase, neatenuate​

Un QuantumProgram conține un circuit cuantic și un samplex într-un format potrivit pentru primitiva Executor. MitigationTask.prepare construiește un QuantumProgram din circuit, observabile și valorile parametrilor, și încadrează circuitul în casete cu opțiunile de încadrare date. Setează opțiunea de încadrare measure_annotations la "change_basis", care rotește măsurătorile în bazele necesare, și adaugă add_tags: "unique_box" astfel încât simulatorul să poată mapa un model de zgomot pe casetele circuitului. Programul este implicit Pauli-twirled, ceea ce pe hardware transformă zgomotul coerent în zgomotul Pauli pe care îl presupun metodele de atenuare. num_randomizations stabilește câte instanțe twirled ale fiecărui circuit sunt eșantionate, iar shots_per_randomization stabilește câte shot-uri primește fiecare instanță, deci produsul lor este bugetul de shot-uri pe circuit.

Un utilizator cu acces la QPU ar executa programul cu un Executor construit dintr-un backend al QiskitRuntimeService. Aici vei transmite un AerSimulator către Executor, care rulează programul local, și vei folosi opțiunea sa de simulator layer_noise_model pentru a injecta un model de zgomot Pauli-Lindblad aleatoriu pe fiecare strat de porți de încurcare, generat cu random_pauli_lindblad_noise din qiskit-mitigation, cu o eroare per poartă în strat (EPLG) de 0.003, tipică pentru QPU-urile Heron actuale.

from qiskit.circuit import CircuitInstruction
from qiskit.quantum_info import PauliLindbladMap, pauli_basis
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import Executor
from qiskit_mitigation import MitigationTask, find_combined_unique_layers

def generate_random_pauli_lindblad_noise(
layers,
num_qubits: int,
eplg: float = 3e-3,
seed: int | None = None,
) -> list[tuple[CircuitInstruction, PauliLindbladMap]]:
"""Generate random Pauli-Lindblad noise over the full Pauli basis."""
layer_noise_model = []
for layer in layers:
layer_instructions = layer.operation.params[0]
contain_measurement = any(
inst.name.startswith("measure") for inst in layer_instructions
)
if contain_measurement: # do not add measurement noise
layer_noise = PauliLindbladMap.identity(num_qubits)
else:
edges = [
(inst.qubits[0]._index, inst.qubits[1]._index)
for inst in layer_instructions
] # extract the edges of the layer

basis_paulis = [p for p in pauli_basis(2) if np.sum(p.x + p.z)]
rng = np.random.default_rng(seed=seed)
num_generators = len(basis_paulis)
rates = rng.random(len(basis_paulis)) * eplg / num_generators

layer_noise = PauliLindbladMap.from_sparse_list(
[
(pauli.to_label(), edge, rate)
for pauli, rate in zip(basis_paulis, rates, strict=True)
for edge in edges
],
num_qubits=num_qubits,
)
layer_noise_model.append((layer, layer_noise))
return layer_noise_model

num_randomizations = 50
shots_per_randomization = 64

boxing_options = {
"measure_annotations": "change_basis",
"add_tags": "unique_box",
}

# find the unique layers to generate the layered noise for
box_layers = find_combined_unique_layers(
[circuit], custom_boxing_options=boxing_options
)
layer_noise_model = generate_random_pauli_lindblad_noise(
box_layers, num_qubits, seed=42
)

# a local noisy simulator in place of a QPU backend
noisy_executor = Executor(
AerSimulator(),
options={"simulator": {"layer_noise_model": layer_noise_model}},
)

task = MitigationTask()
program = task.prepare(
circuit=circuit,
observables=observables,
parameters=parameter_values,
num_randomizations=num_randomizations,
shots_per_randomization=shots_per_randomization,
custom_boxing_options=boxing_options,
)
program._semantic_role = "simulation" # flag the program for the simulator
unmitigated_res = task.postprocess(noisy_executor.run(program).result())

fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(x_vals, exact_evs.mean(axis=0), "k-", label="Exact")
ax.errorbar(
x_vals,
np.mean(unmitigated_res.data.evs, axis=0),
np.mean(unmitigated_res.data.stds, axis=0),
fmt="o-",
label="Unmitigated",
)
ax.set_xlabel(r"$\theta/\pi$")
ax.set_ylabel(r"$\langle Z \rangle_{\text{avg}}$")
ax.legend()
plt.tight_layout()
plt.show()

Output of the previous code cell

Învață zgomotul straturilor de încurcare cu qiskit-noise-learning​

PEA începe prin a învăța modelul de zgomot twirled al fiecărui strat de porți de încurcare din circuit. Presupunem un model de zgomot markovian și, prin urmare, trebuie învățat doar modelul de zgomot al fiecărui strat unic de porți de încurcare, deoarece se presupune că adâncimea circuitului nu afectează canalul de zgomot al unui strat de încurcare dat. Cu cât modelul de zgomot învățat este mai precis, cu atât amplificarea va fi mai precisă.

Folosim qiskit-noise-learning pentru a învăța zgomotul. Learner-ul repetă fiecare strat de fragment_depths ori în circuitele sale de învățare, aplică twirling fiecărui circuit de num_randomizations ori și potrivește descreșterea fidelităților măsurate la un model Pauli-Lindblad. Codul următor desenează modelul de zgomot învățat, cu cele două straturi îmbinate într-o singură hartă. Fiecare qubit este o diagramă circulară a ratelor sale X, Y și Z de pondere 1, însumate peste cele două straturi, iar fiecare muchie arată ratele de pondere 2 învățate; o rată de 3e-4 înseamnă că eroarea apare aproximativ la trei din fiecare 10.000 de utilizări ale stratului.

from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.quantum_info import PauliList
from qiskit_ibm_runtime.results import LayerError, PauliLindbladError
from qiskit_ibm_runtime.visualization.embeddings import Embedding
from qiskit_mitigation import PEA
from qiskit_noise_learning.aer_executor import AerExecutor
from qiskit_noise_learning.noise_learner import LearningOptions, NoiseLearner
from samplomatic import InjectNoise, Tag
from samplomatic.utils import get_annotation

backend = GenericBackendV2(
num_qubits=num_qubits,
basis_gates=["id", "rz", "sx", "x", "cz"],
coupling_map=ring_coupling,
)

task_pea = PEA()
# learn only the layers PEA will inject noise into, i.e. not the measurement layer
pea_layers = [
layer
for layer in task_pea.find_unique_layers(circuit, boxing_options)
if get_annotation(layer.operation, InjectNoise)
]

learning_options = LearningOptions(
fragment_depths=[2, 4, 8, 16],
num_randomizations=50,
shots_per_randomizations=64,
)
# qiskit-noise-learning is not compatible with the Executor in local mode,
# and requires the simulator present in the package for running simulation
noise_dict = {
get_annotation(layer.operation, Tag).ref: noise_layer
for layer, noise_layer in layer_noise_model
}
noise_learner_simulator = AerExecutor(
qasm_simulator=AerSimulator(), noise_dict=noise_dict
)
learner = NoiseLearner(
backend, options=learning_options, executor=noise_learner_simulator
)
learned_noise = (
learner.run(pea_layers).result().to_dict()
) # keyed by InjectNoise reference

# Draw the learned model on the ring, laid out as a square with the qubits in ring order.
# Composing the two layers' maps sums their rates, giving one map over all the ring's edges.
embedding = Embedding(
[(0, 0), (0, 2), (0, 4), (2, 4), (4, 4), (4, 2), (4, 0), (2, 0)],
ring_coupling,
)
first, second = learned_noise.values()
learned_model = first.compose(second).simplify()
error = PauliLindbladError(
PauliList(
learned_model.get_qubit_sparse_pauli_list_copy().to_pauli_list()
),
learned_model.rates,
)
fig = LayerError(circuit, list(range(num_qubits)), error).draw_map(
embedding, radius=0.4, height=460, width=620
)
fig.update_traces(
marker={
"cmin": 0,
"cmax": max(learned_model.rates),
"colorbar": {"tickformat": ".1e", "title": "rate"},
},
selector={"mode": "markers"},
)
fig.show()

Output of the previous code cell

Pregătește un program ZNE cu amplificare probabilistică a erorilor și eșantionează circuite cu scale de zgomot variate​

Aici vei pregăti un program ZNE care conține circuite cu scale de zgomot crescătoare. Cu PEA, fiecare factor de zgomot reprezintă multiplul zgomotului învățat pe care îl experimentează circuitul: un factor de zgomot de 2.0 injectează o copie suplimentară a canalului de zgomot învățat al fiecărui strat după acel strat, peste zgomotul pe care stratul îl are deja. Deoarece PEA scalează direct ratele de zgomot învățate în loc să repete porți, este posibil orice factor de zgomot, inclusiv cei neîntregi, iar circuitul în sine nu este modificat: elementele programului împart un singur circuit șablon și diferă doar prin zgomotul injectat de samplex. Zgomotul injectat este eșantionat o dată per randomizare, deci eroarea statistică a PEA este controlată de numărul de randomizări, nu de numărul de shot-uri per randomizare; de aceea folosim multe randomizări cu puține shot-uri fiecare, păstrând numărul total de shot-uri identic cu cel al programului nemitigat. Este important să nu folosești prea puține randomizări, altfel ai putea întâlni o părtinire de eșantionare în injectarea zgomotului.

Overhead-ul total de eșantionare crește liniar cu numărul de factori de zgomot.

noise_factors = [1, 2, 3]
program_pea = task_pea.prepare(
circuit=circuit,
observables=observables,
parameters=parameter_values,
num_randomizations=200,
shots_per_randomization=16,
custom_boxing_options=boxing_options,
noise_maps=learned_noise,
noise_factors=noise_factors,
)

În final, rulează programul pe simulatorul cu zgomot. Rezultatele sale alimentează extrapolarea din pasul următor.

program_pea._semantic_role = (
"simulation" # flag the program for the simulator
)
job_pea_results = noisy_executor.run(program_pea).result()

Extrapolează la limita de zgomot zero​

Acum că ai date pentru fiecare scală de zgomot, poți potrivi o funcție pe date și extrapola înapoi la valoarea de zgomot zero pentru a estima o valoare de așteptare a observabilei fără zgomot. Sub zgomot Pauli, o observabilă descrește exponențial cu factorul de zgomot, așa că vei alege ca prim extrapolator pe cel exponențial și vei reveni la cel liniar dacă potrivirea exponențială eșuează. Pentru a vizualiza extrapolarea, reprezintă grafic magnetizarea mediată pe situri calculată la fiecare factor de zgomot, potrivirea exponențială și media pe situri a valorilor de așteptare extrapolate în punctul de zgomot zero. Observăm că potrivirea subestimează ușor pentru această observabilă particulară.

from scipy.optimize import curve_fit

pea_mitigated_res = task_pea.postprocess(
job_pea_results, extrapolator=["exponential", "linear"]
)

# The result holds the expectation value at each noise factor and the extrapolated value for
# every observable; we average them over the sites and fit the same model to draw the curve.
angle = 3 # theta = 0.15 pi, the angle with the largest unmitigated error
amplifications = pea_mitigated_res.data.evs_noise_factors[:, angle, :].mean(
axis=0
)
x_fit = np.linspace(0, max(noise_factors), 51)
params, _ = curve_fit(
lambda x, a, b: a * np.exp(b * x),
noise_factors,
amplifications,
p0=[1.0, -0.1],
)

plt.plot(
noise_factors, amplifications, "o", color="red", label="amplifications"
)
plt.plot(
x_fit,
params[0] * np.exp(params[1] * x_fit),
"--",
color="black",
label="exponential fit",
)
plt.plot(
[0],
pea_mitigated_res.data.evs[:, angle].mean(),
"*",
color="black",
markersize=15,
label="extrapolation",
)
plt.plot(
[0],
exact_evs[:, angle].mean(),
"_",
color="red",
markersize=15,
markeredgewidth=3,
label="exact",
)
plt.xlabel("noise factor")
plt.ylabel(r"$\langle Z \rangle_{\text{avg}}$")
plt.legend()
plt.show()

Output of the previous code cell

Compară valorile de așteptare mitigate​

Reprezintă grafic magnetizarea mediată pe situri a fiecărei metode, pe întreg intervalul de unghiuri de rotație Ising. PEA recuperează magnetizarea exactă în limitele barelor de eroare, în timp ce rezultatul nemitigat este părtinit mult sub ea.

results = {
"Unmitigated": unmitigated_res,
"PEA": pea_mitigated_res,
}

fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(x_vals, exact_evs.mean(axis=0), "k-", label="Exact")
for label, res in results.items():
ax.errorbar(
x_vals,
np.mean(res.data.evs, axis=0),
np.mean(res.data.stds, axis=0),
fmt="o-",
label=label,
)
ax.set_xlabel(r"$\theta/\pi$")
ax.set_ylabel(r"$\langle Z \rangle_{\text{avg}}$")
ax.legend()
ax.set_title(
f"Error-Mitigated Magnetization [{num_qubits}-qubit, {trotter_steps} Trotter steps]"
)
plt.tight_layout()
plt.show()

Output of the previous code cell