استقراء الضوضاء الصفرية مع طي البوابات واستقراء مخصص
استقراء الضوضاء الصفرية (ZNE) تقنية للتخفيف من الضوضاء الكمومية في حسابات القيم المتوقعة للمُراقَبات. وهي تعمل بتنفيذ دائرة كمومية بمقاييس ضوضاء متزايدة، وأخذ القيم المتوقعة بالنسبة لتلك المقاييس، ثم الاستقراء لتقدير القيمة المتوقعة عند ضوضاء صفرية. ويمكن تنفيذ مرحلتَي تضخيم الضوضاء والاستقراء بطرق مختلفة. يضخّم هذا الدليل الضوضاء بـ "الطي الرقمي للبوابات"، أي استبدال بوابات الكيوبتين بتسلسلات مكافئة من البوابة ومعكوسها. فمثلًا، استبدال المؤثر الواحدي بـ يعطي معامل تضخيم للضوضاء يساوي 3. ويمكنك أيضًا تنفيذ التضخيم بتضخيم الخطأ الاحتمالي (PEA)، الذي يحتاج إلى خطوة إضافية لتعلّم نموذج الضوضاء ومشروح في دليل تضخيم الخطأ الاحتمالي. يجري هذا الدليل الاستقراء باستخدام بضع استراتيجيات ملاءمة مدمجة (خطية وأسّية)، كما يوضح كيفية تنفيذ دالة ملاءمة مخصصة خاصة بك.
تذكّر أنه رغم أن ZNE قد يكون فعّالًا في التخفيف من الضوضاء الكمومية بتكلفة أخذ عينات أقل من الطرق الأخرى، فإنه لا يأتي مع أي حدود على الانحياز.
سير العمل كما يلي:
- حدد دائرة ومُراقَبة
- احسب القيم المتوقعة الدقيقة باستخدام محاكي متجه الحالة في Qiskit
- احسب القيم المتوقعة الضوضائية غير المخففة
- جهّز برنامج 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", "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
احسب القيم المتوقعة الدقيقة باستخدام محاكي متجه الحالة في Qiskit
استخدم الصنف Statevector في Qiskit لحساب القيمة المتوقعة الدقيقة لـ على كل موقع لكل قيمة من قيم .
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()
احسب القيم المتوقعة الضوضائية غير المخففة
يحتوي 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()
جهّز برنامج ZNE مع الطي الرقمي للبوابات وعيّن دوائر بمقاييس ضوضاء متفاوتة
جهّز برنامج ZNE يحتوي على دوائر بمقاييس ضوضاء متزايدة. يمثل كل معامل ضوضاء مضاعف بوابات التشابك التي تحتويها الدائرة (معامل الضوضاء 3.0 يعني ثلاثة أضعاف بوابات التشابك في الدائرة الأصلية). تُعشّى (twirl) دائرة كل معامل ضوضاء بالعدد نفسه من العشوائيات واللقطات كما في البرنامج غير المخفف.
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
)
ارسم أول خطوة Trotter من القالب المعشّى بمعامل ضوضاء 1 وبمعامل ضوضاء 3 لتصوّر التضخيم. وعند معامل الضوضاء 3.0، تُستبدل كل طبقة في الدائرة بالطبقة ثم معكوسها ثم الطبقة مرة أخرى.
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
)

أخيرًا، شغّل البرنامج على المحاكي الضوضائي. تغذّي نتائجه الاستقراء في الخطوة التالية.
program_zne._semantic_role = (
"simulation" # flag the program for the simulator
)
job_zne_results = noisy_executor.run(program_zne).result()
استقرئ إلى حد الضوضاء الصفرية بدوال ملاءمة مدمجة ومخصصة
الآن وقد صارت لديك بيانات لكل مقياس ضوضاء، يمكنك ملاءمة دالة مع البيانات ثم الاستقراء عائدًا إلى قيمة الضوضاء الصفرية لتقدير القيمة المتوقعة للمُراقَبة الخالية من الضوضاء. هناك طرق كثيرة لملاءمة البيانات، ويعتمد الاختيار الصحيح للدالة على طبيعة البيانات المأخوذة. توفر GateFolding.postprocess عدة دوال استقراء مدمجة، منها linear وexponential وdouble_exponential. تأخذ الدالة postprocess قائمة بطرق الاستقراء، وتقيّمها بالترتيب، وتُرجع أول قيمة صالحة فيزيائيًا وذات تباين منتهٍ. وهذا يعني أن الترتيب الذي تحدد به المستقرِئات له أثر كبير في اختيار المستقرِئ. وتمرير fallback كمستقرِئ يعني أنه إن لم تُوجد إجابات صالحة، فستُختار وتُرجع القيمة المتوقعة لأصغر معامل ضوضاء. وإن لم يُمرَّر fallback ولم يُنتج أي مستقرِئ قيمة صالحة فيزيائيًا، تُرجع النتيجة ذات أصغر std. وتحت ضوضاء Pauli تتناقص المُراقَبة أسّيًا مع معامل الضوضاء، لذا ومع 30 طبقة (15 خطوة Trotter، بطبقتين لكل خطوة) من بوابات التشابك يكون المستقرِئ الأسّي هو الخيار الطبيعي؛ ويمكنك أيضًا الاستقراء خطيًا، وهو خيار شائع عندما يكون التناقص صغيرًا.
لاستخدام دالة ملاءمة مخصصة، مرّر المستقرِئ "custom" مع المجموعة custom_fit التي تحتوي على دالة الملاءمة، وقائمة بتخمين أولي لكل من معاملاتها، والحدود. تُمرَّر هذه إلى scipy.optimize.curve_fit. ويمكن تكرار المعالجة اللاحقة بمستقرِئات مختلفة دون إعادة تنفيذ البرنامج. هنا تستخدم دالة ملاءمة مخصصة من نوع الجذر التربيعي. ولتصوّر الاستقراء، ارسم المغنطة المتوسطة على المواقع المحسوبة عند كل معامل ضوضاء لـ ، والملاءمات الثلاث، ومتوسط المواقع للقيم المتوقعة المستقرأة عند نقطة الضوضاء الصفرية. تبدو نقاط البيانات الثلاث شبه خطية، ومع ذلك يهبط الاستقراء الخطي بوضوح تحت القيمة الدقيقة. وكما كان متوقعًا، تقدّر الملاءمة الأسّية القيمة المتوقعة الخالية من الضوضاء بأفضل فعالية.
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()

قارن القيم المتوقعة المخففة
ارسم المغنطة المتوسطة على المواقع لكل طريقة عبر مسح زوايا دوران Ising. يستعيد الاستقراء الأسّي المغنطة الدقيقة ضمن أشرطة خطئه، بينما يزيل الاستقراء الخطي جزءًا كبيرًا من الانحياز لكنه يقلّل التقدير باستمرار، لأن التناقص الحقيقي أسّي. أما ملاءمة الجذر التربيعي فتبالغ في التقدير بشكل كبير.
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()
