Extrapolare la zero zgomot cu pliere de porți și extrapolare personalizată
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. Acest ghid amplifică zgomotul prin „plierea digitală a porților", ceea ce înseamnă că porțile pe doi qubiți sunt înlocuite cu secvențe echivalente ale porții și inversei sale. De exemplu, înlocuirea unui operator unitar cu ar produce un factor de amplificare a zgomotului de 3. Poți implementa amplificarea și prin amplificare probabilistică a erorilor (PEA), care necesită un pas suplimentar de învățare a unui model de zgomot și este tratată în ghidul amplificare probabilistică a erorilor. Acest ghid extrapolează folosind câteva strategii de ajustare predefinite (liniară și exponențială) și demonstrează, de asemenea, cum să implementezi propria funcție de ajustare personalizată.
Ț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.
Fluxul de lucru este următorul:
- Specifică un circuit și o observabilă
- Calculează valorile medii exacte folosind simulatorul de vector de stare din Qiskit
- Calculează valorile medii zgomotoase, neatenuate
- Pregătește un program ZNE cu pliere digitală a porților și eșantionează circuite cu scale de zgomot variate
- Extrapolează la limita de zero zgomot cu funcții de ajustare predefinite și personalizate
- Compară valorile medii atenuate
Specifică un circuit și o observabilă
Calculează magnetizarea medie pe situsuri 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.
# 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", "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 numpy as np
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
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("Visualization of 1 Trotter step")
trotter_circuit(num_qubits, theta, layers, 1).draw("mpl", scale=0.7)
Visualization of 1 Trotter step
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 pe fiecare situs pentru fiecare valoare a lui .
import matplotlib.pyplot as plt
from qiskit.quantum_info import Statevector
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()
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. Setezi opțiunea de încadrare measure_annotations la "change_basis", care rotește măsurătorile în bazele necesare, și adaugi 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 trece 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 aleator de zgomot Pauli-Lindblad pe fiecare strat de porți de încâlcire, generat cu random_pauli_lindblad_noise din qiskit-mitigation cu o eroare pe 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()
Pregătește un program ZNE cu pliere digitală a porților și eșantionează circuite cu scale de zgomot variate
Pregătește un program ZNE care conține circuite cu scale de zgomot crescătoare. Fiecare factor de zgomot reprezintă multiplul de porți de încâlcire pe care îl conține circuitul (un factor de zgomot de 3.0 conține de trei ori mai multe porți de încâlcire decât circuitul original). Circuitul fiecărui factor de zgomot este twirled cu același număr de randomizări și shot-uri ca programul neatenuat.
from qiskit_mitigation import GateFolding
task_gf = GateFolding()
program_zne = task_gf.prepare(
circuit=circuit,
observables=observables,
parameters=parameter_values,
num_randomizations=num_randomizations,
shots_per_randomization=shots_per_randomization,
custom_boxing_options=boxing_options,
noise_factors=[1, 3, 5], # ZNE option
)
Desenează primul pas Trotter al șablonului twirled cu factorul de zgomot 1 și cu factorul de zgomot 3 pentru a vizualiza amplificarea. Pentru factorul de zgomot 3.0, fiecare strat al circuitului este înlocuit cu stratul, inversul său și din nou stratul.
import IPython.display
def first_trotter_step(template, noise_factor):
"""The template up to the closing barrier of its first Trotter step (two layers, each folded ``noise_factor`` times)."""
toy = template.copy_empty_like()
for instruction in template.data:
toy.append(instruction)
if (
instruction.operation.name == "barrier"
and instruction.operation.label.startswith(
f"R{2 * noise_factor - 1}@"
)
):
break
return toy
IPython.display.display(
first_trotter_step(program_zne.items[0].circuit, 1).draw(
"mpl", scale=0.7, plot_barriers=False
)
)
first_trotter_step(program_zne.items[1].circuit, 3).draw(
"mpl", fold=-1, plot_barriers=False
)

În final, rulează programul pe simulatorul zgomotos. Rezultatele sale alimentează extrapolarea din pasul următor.
program_zne._semantic_role = (
"simulation" # flag the program for the simulator
)
job_zne_results = noisy_executor.run(program_zne).result()
Extrapolează la limita de zero zgomot cu funcții de ajustare predefinite și personalizate
Acum că ai date pentru fiecare scală de zgomot, poți ajusta o funcție la date și extrapola înapoi la valoarea de zero zgomot pentru a estima o valoare medie a observabilei fără zgomot. Există multe moduri de a ajusta datele, iar alegerea potrivită a funcției depinde de natura datelor eșantionate. GateFolding.postprocess oferă mai multe funcții de extrapolare predefinite, inclusiv linear, exponential și double_exponential. Metoda postprocess primește o listă de metode de extrapolare, le evaluează în ordine și returnează prima valoare cu valori valide fizic și varianță finită. Aceasta înseamnă că ordinea în care specifici extrapolatoarele are un impact mare asupra extrapolatorului ales. Transmiterea fallback ca extrapolator înseamnă că, dacă nu s-au găsit răspunsuri valide, se va alege și returna valoarea medie a celui mai mic factor de zgomot. Dacă fallback nu este transmis și niciun extrapolator nu produce o valoare validă fizic, se returnează rezultatul cu cel mai mic std. Sub zgomot Pauli, o observabilă scade exponențial cu factorul de zgomot, deci cu 30 de straturi (15 pași Trotter, 2 straturi pe pas) de porți de încâlcire extrapolatorul exponențial este alegerea naturală; poți extrapola și liniar, ceea ce este o alegere frecventă când scăderea este mică.
Pentru a folosi o funcție de ajustare personalizată, transmite extrapolatorul "custom" împreună cu un tuplu custom_fit care conține funcția de ajustare, o listă cu o estimare inițială pentru fiecare dintre parametrii săi și limite. Acestea sunt transmise către scipy.optimize.curve_fit. Post-procesarea poate fi repetată cu extrapolatoare diferite fără a re-executa programul. Aici folosești o funcție de ajustare personalizată de tip rădăcină pătrată. Pentru a vizualiza extrapolarea, reprezintă grafic magnetizarea medie pe situsuri calculată la fiecare factor de zgomot pentru , cele trei ajustări și media pe situsuri a valorilor medii extrapolate în punctul de zero zgomot. Cele trei puncte de date par aproape liniare, totuși extrapolarea liniară ajunge vizibil sub valoarea exactă. Așa cum era de așteptat, ajustarea exponențială estimează cel mai eficient valoarea medie fără zgomot.
from scipy.optimize import curve_fit
zne_mitigated_exp = task_gf.postprocess(
job_zne_results, extrapolator=["exponential", "linear"]
)
zne_mitigated_lin = task_gf.postprocess(
job_zne_results, extrapolator=["linear"]
)
def sqrt_fit(x, a, b):
return a * np.sqrt(x) + b
custom_fit = (sqrt_fit, [1.0, 1.0], (-np.inf, np.inf))
zne_mitigated_custom = task_gf.postprocess(
job_zne_results,
extrapolator=["custom", "exponential", "linear"],
custom_fit=custom_fit,
)
# The result holds the expectation value at each noise factor and the extrapolated value, but
# not the fit parameters, so we refit the same models here to draw the curves.
noise_factors = np.array([1, 3, 5])
angle = 3 # theta = 0.15 pi, the angle with the largest unmitigated error
amplifications = zne_mitigated_exp.data.evs_noise_factors[:, angle, :].mean(
axis=0
)
x_fit = np.linspace(0, 5, 51)
fits = {
"exponential": (
lambda x, a, b: a * np.exp(b * x),
zne_mitigated_exp,
"black",
),
"linear": (lambda x, a, b: a * x + b, zne_mitigated_lin, "blue"),
"square-root": (sqrt_fit, zne_mitigated_custom, "green"),
}
plt.plot(
noise_factors, amplifications, "o", color="red", label="amplifications"
)
for name, (fit, res, color) in fits.items():
params, _ = curve_fit(fit, noise_factors, amplifications, p0=[1.0, -0.1])
plt.plot(
x_fit, fit(x_fit, *params), "--", color=color, label=f"{name} fit"
)
plt.plot(
[0],
res.data.evs[:, angle].mean(),
"*",
color=color,
markersize=15,
label=f"{name} 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()

Compară valorile medii atenuate
Reprezintă grafic magnetizarea medie pe situsuri a fiecărei metode pe întreaga parcurgere a unghiurilor de rotație Ising. Extrapolarea exponențială recuperează magnetizarea exactă în limitele barelor de eroare, în timp ce extrapolarea liniară elimină o mare parte din bias, dar subestimează constant, deoarece scăderea reală este exponențială. Ajustarea de tip rădăcină pătrată supraestimează puternic.
results = {
"Unmitigated": unmitigated_res,
"ZNE exponential": zne_mitigated_exp,
"ZNE linear": zne_mitigated_lin,
"ZNE square-root": zne_mitigated_custom,
}
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()
