استقراء الضوضاء الصفرية مع تضخيم الخطأ الاحتمالي
استقراء الضوضاء الصفرية (ZNE) تقنية للتخفيف من الضوضاء الكمومية في حسابات القيم المتوقعة للمُراقَبات. وهي تعمل بتنفيذ دائرة كمومية بمقاييس ضوضاء متزايدة، وأخذ القيم المتوقعة بالنسبة لتلك المقاييس، ثم الاستقراء لتقدير القيمة المتوقعة عند ضوضاء صفرية. ويمكن تنفيذ مرحلتَي تضخيم الضوضاء والاستقراء بطرق مختلفة. في هذا الدليل ستضخّم الضوضاء بتضخيم الخطأ الاحتمالي (PEA)، كما اقترحت الورقة "Evidence for the utility of quantum computing before fault tolerance". وللتضخيم بـ PEA، يجب أولًا نمذجة الضوضاء المؤثرة على بوابات التشابك في الدائرة. ويمكنك أيضًا تنفيذ التضخيم بالطي الرقمي للبوابات، الذي لا يحتاج إلى نموذج ضوضاء ومشروح في دليل طي البوابات. ستتعلّم الضوضاء باستخدام qiskit-noise-learning وتستقرئ باستراتيجية الملاءمة الأسّية المدمجة.
تذكّر أنه رغم أن ZNE قد يكون فعّالًا في التخفيف من الضوضاء الكمومية بتكلفة أخذ عينات أقل من الطرق الأخرى، فإنه لا يأتي مع أي حدود على الانحياز.
سير العمل كما يلي:
- حدد دائرة ومُراقَبة
- احسب القيم المتوقعة الدقيقة باستخدام محاكي متجه الحالة في Qiskit
- احسب القيم المتوقعة الضوضائية غير المخففة
- تعلّم ضوضاء طبقات التشابك باستخدام
qiskit-noise-learning - جهّز برنامج ZNE مع تضخيم الخطأ الاحتمالي وعيّن دوائر بمقاييس ضوضاء متفاوتة
- استقرئ إلى حد الضوضاء الصفرية
- قارن القيم المتوقعة المخففة
حدد دائرة ومُراقَبة
في هذا الدليل ستحسب المغنطة المتوسطة على المواقع لنموذج Ising ذي المجال المستعرض (TFIM) بعد تطبيق Trotter على ثمانية كيوبتات في طوبولوجيا حلقية. ستمسح عدة قيم لشدة المجال المستعرض وترسم المغنطة كدالة في شدة المجال. في طوبولوجيا حلقية على عدد زوجي من الكيوبتات، يمكن تقسيم طبقات بوابات الكيوبتين إلى طبقتين فريدتين، كما يوضح اللونان الأحمر والأخضر في خريطة الاقتران أدناه. ومن المهم تصميم الدوائر بحيث تحتوي على أقل مجموعة ممكنة من طبقات التشابك الفريدة إذا أردت تعلّم نموذج ضوضاء، لأن الطبقات الفريدة يجب تعلّمها بشكل مستقل. وإذا كان في دائرتك عدد كبير جدًا من الطبقات الفريدة، فقد يصبح تعلّم الضوضاء غير ممكن بسبب تكلفة أخذ العينات لتعلّم طبقات كثيرة.
# 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.
Visualization of 1 Trotter step
احسب القيم المتوقعة الدقيقة باستخدام محاكي متجه الحالة في Qiskit
استخدم الصنف Statevector في Qiskit لحساب القيمة المتوقعة الدقيقة لـ على كل موقع لكل قيمة من قيم .
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()
احسب القيم المتوقعة الضوضائية غير المخففة
يحتوي QuantumProgram على دائرة كمومية وsamplex بصيغة مناسبة لـ بدائية Executor. تبني MitigationTask.prepare كائن QuantumProgram من الدائرة والمُراقَبات وقيم المعاملات، وتضع الدائرة في صناديق وفق خيارات الصناديق المعطاة. اضبط خيار الصناديق measure_annotations على "change_basis"، الذي يدوّر القياسات إلى الأسس المطلوبة، وأضف add_tags: "unique_box" كي يتمكن المحاكي من إسقاط نموذج ضوضاء على صناديق الدائرة. يخضع البرنامج لـ Pauli twirling افتراضيًا، وهو ما يحوّل على العتاد الضوضاء المتماسكة إلى ضوضاء Pauli التي تفترضها طرق التخفيف. تحدد num_randomizations عدد النسخ المعشّاة (twirled) من كل دائرة التي تؤخذ عيّنةً منها، وتحدد shots_per_randomization عدد اللقطات التي تتلقاها كل نسخة، لذا فإن حاصل ضربهما هو ميزانية اللقطات لكل دائرة.
سيقوم المستخدم الذي لديه وصول إلى QPU بتنفيذ البرنامج باستخدام Executor مبني من backend تابع لـ QiskitRuntimeService. هنا ستمرّر AerSimulator إلى Executor، الذي يشغّل البرنامج محليًا، وستستخدم خيار المحاكي layer_noise_model لحقن نموذج ضوضاء Pauli-Lindblad عشوائي على كل طبقة من بوابات التشابك، يتم توليده باستخدام random_pauli_lindblad_noise من qiskit-mitigation مع خطأ لكل بوابة طبقية (EPLG) يساوي 0.003، وهو نموذجي لمعالجات Heron QPU الحالية.
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()
تعلّم ضوضاء طبقات التشابك باستخدام qiskit-noise-learning
يبدأ PEA بتعلّم نموذج الضوضاء الملتفّ (twirled) لكل طبقة من بوابات التشابك في الدائرة. نفترض نموذج ضوضاء ماركوفي، ولذلك يكفي تعلّم نموذج الضوضاء لكل طبقة فريدة من بوابات التشابك، لأننا نفترض أن عمق الدائرة لا يؤثر على قناة الضوضاء لطبقة تشابك معيّنة. كلما كان نموذج الضوضاء المتعلَّم أدق، كان التضخيم أدق.
نستخدم qiskit-noise-learning لتعلّم الضوضاء. يكرّر المتعلّم كل طبقة fragment_depths مرة في دوائر التعلّم الخاصة به، ويلفّ (twirl) كل دائرة num_randomizations مرة، ثم يطابق تلاشي الدقّات المقاسة مع نموذج Pauli-Lindblad. يرسم الكود التالي نموذج الضوضاء المتعلَّم، مع دمج الطبقتين في خريطة واحدة. كل كيوبت يظهر كدائرة مقسّمة (pie) تمثّل معدلات X وY وZ ذات الوزن 1 مجموعةً على الطبقتين، وتُظهر كل حافة معدلات الوزن 2 المتعلَّمة؛ ومعدل 3e-4 يعني أن الخطأ يحدث تقريبًا في ثلاثة من كل 10,000 استخدام للطبقة.
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()
جهّز برنامج ZNE مع تضخيم الخطأ الاحتمالي وعيّن دوائر بمقاييس ضوضاء متفاوتة
هنا ستجهّز برنامج ZNE يحتوي على دوائر بمقاييس ضوضاء متزايدة. مع PEA، يمثّل كل عامل ضوضاء مضاعف الضوضاء المتعلَّمة التي تتعرض لها الدائرة: عامل ضوضاء 2.0 يحقن نسخة إضافية واحدة من قناة الضوضاء المتعلَّمة لكل طبقة بعد تلك الطبقة، فوق الضوضاء الموجودة أصلًا في الطبقة. ولأن PEA يضبط معدلات الضوضاء المتعلَّمة مباشرة بدل تكرار البوابات، فأي عامل ضوضاء ممكن، بما في ذلك الأعداد غير الصحيحة، ولا تُعدَّل الدائرة نفسها: تتشارك عناصر البرنامج دائرة قالب واحدة وتختلف فقط في الضوضاء التي يحقنها samplex. تُعيَّن الضوضاء المحقونة مرة واحدة لكل عملية عشوائية، لذلك يتحكم عدد العمليات العشوائية، لا عدد اللقطات لكل عملية، في الخطأ الإحصائي لـ PEA؛ ولهذا نستخدم عددًا كبيرًا من العمليات العشوائية مع لقطات قليلة لكل منها، مع إبقاء العدد الكلي للقطات مساويًا لعدده في البرنامج غير المخفَّف. من المهم ألا تستخدم عددًا قليلًا جدًا من العمليات العشوائية، وإلا فقد تواجه انحيازًا في أخذ العينات عند حقن الضوضاء.
يزداد الحمل الكلي لأخذ العينات خطيًا مع عدد عوامل الضوضاء.
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,
)
أخيرًا، شغّل البرنامج على المحاكي المضوضَّ. نتائجه تغذّي الاستقراء في الخطوة التالية.
program_pea._semantic_role = (
"simulation" # flag the program for the simulator
)
job_pea_results = noisy_executor.run(program_pea).result()
استقرئ إلى حد الضوضاء الصفرية
الآن وقد أصبحت لديك بيانات لكل مقياس ضوضاء، يمكنك مطابقة دالة مع البيانات ثم الاستقراء عودةً إلى قيمة الضوضاء الصفرية لتقدير القيمة المتوقعة للمرصود الخالي من الضوضاء. في ظل ضوضاء Pauli يتلاشى المرصود أُسّيًا مع عامل الضوضاء، لذلك ستختار المستقرئ الأسّي كخيار أول، وتعود إلى الخطي إذا فشلت المطابقة الأسّية. لتصوير الاستقراء، ارسم المغنطة المتوسَّطة على المواقع المحسوبة عند كل عامل ضوضاء، والمطابقة الأسّية، ومتوسط المواقع للقيم المتوقعة المستقرأة عند نقطة الضوضاء الصفرية. نلاحظ أن المطابقة تقلّل التقدير قليلًا لهذا المرصود تحديدًا.
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()
قارن القيم المتوقعة المخفَّفة
ارسم المغنطة المتوسَّطة على المواقع لكل طريقة عبر مجموعة زوايا دوران Ising. يستعيد PEA المغنطة الدقيقة ضمن أشرطة الخطأ الخاصة به، بينما تنحاز النتيجة غير المخفَّفة إلى ما دونها بكثير.
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()
