انتقل إلى المحتوى الرئيسي
هذا المشروع في مرحلة تجريبية. وجدت خطأ؟ افتح مشكلة على GitHub. لديك سؤال أو فكرة؟ اسأل في Discussions — نحب سماع ملاحظاتك!
كيف جودة الترجمة في هذه الصفحة؟

تحسين قيم التوقع باستخدام امتصاص الضجيج المنتشر (PNA)

تقدير الاستخدام: 10 دقائق على معالج Heron (ملاحظة: هذا مجرد تقدير. قد يختلف وقت التشغيل لديك.)

مخرجات التعلم​

  • ما هو امتصاص الضجيج المُنتشر (PNA)، وكيف يُخفف ضجيج البوابات الثنائية الكيوبت عن طريق امتصاص قنوات الضجيج العكسية المُتعلَّمة في المُلاحَظ المقيس

  • كيفية استخدام samplomatic لوضع طبقات الدارة في صناديق وإضافة تعليقات إليها للفّ، وتغييرات الأساس، وحقن الضجيج

  • كيفية تعلم ضجيج الطبقة باستخدام [NoiseLearnerV3 ونشره في مُلاحَظ مخفف للضجيج باستخدام qiskit-addon-pna

  • كيفية أخذ عينات من دارات عشوائية باستخدام صنفي QuantumProgram وExecutor في qiskit-ibm-runtime، ودمج PNA مع TREX والانتقاء البعدي

المتطلبات الأساسية​

الخلفية​

في هذا الدرس التطبيقي، نوضح كيفية الاستفادة من أدوات تخفيف الأخطاء المتقدمة في Qiskit لتحسين تقدير قيمة التوقع في التجارب الكمومية الصاخبة.

ما هو امتصاص الضجيج المُنتشر (PNA)؟​

امتصاص الضجيج المُنتشر هو تقنية لتخفيف أخطاء البوابات عن طريق نشر المُلاحَظ عبر قناة الضجيج المعكوسة المؤثرة على بوابات الكيوبتين، مما ينتج عنه مُلاحَظ مخفِّف للضجيج.

يمكننا استخدام تخفيف أخطاء الكم لاستخراج قيم توقع مفيدة من عتاد الكم الصاخب دون الحاجة إلى تحمل كامل للأخطاء. يركز PNA تحديدًا على امتصاص تأثيرات الضجيج داخل المُلاحَظ نفسه بدلاً من تعديل عمليات الدارة.

يمكن نمذجة كل بوابة صاخبة في دارة كمية على أنها بوابة مثالية تتبعها قناة ضجيج. يقوم PNA بتعلّم أو توصيف قنوات الضجيج هذه ويحدد معكوساتها. بدلاً من إدخال العمليات المعكوسة في تنفيذ العتاد (وهو أمر غير عملي عادةً)، ينشر PNA قنوات الضجيج المعكوسة إلى الأمام عبر الدارة ويطبقها على المُلاحَظ. تحوّل هذه العملية المُلاحَظ OO إلى مؤثر جديد O~\tilde{O}، بحيث يعطي قياس O~\tilde{O} على الدارة الصاخبة نفس قيمة التوقع الناتجة عن قياس OO على دارة مثالية خالية من الضجيج.

يمكن تلخيص ذلك كما يلي:

  1. نمذجة كل بوابة صاخبة uiu_i كعملية مثالية تتبعها قناة ضجيج Λi\Lambda_i.
  2. تعلّم أو تقدير كل Λi\Lambda_i باستخدام أدوات توصيف الضجيج.
  3. تحديد ونشر خرائط الضجيج المعكوسة Λi−1\Lambda_i^{-1} إلى الأمام عبر الدارة باستخدام تقنيات نقل باولي.
  4. امتصاص هذه المعكوسات في المُلاحَظ، مما ينتج عنه مؤثر مخفَّف الضجيج O~\tilde{O}.

عندما يوصَف الضجيج كقناة باولي (أو بشكل أعم كقناة باولي-ليندبلاد متناثرة)، يمكن تنفيذ هذا النشر بكفاءة باستخدام نشر باولي. يوفر نشر باولي إطارًا لتقريب كيفية تحوّل قنوات الضجيج المعكوسة أثناء مرورها عبر طبقات من العمليات الكليفوردية وغير الكليفوردية، مع التحكم في التعقيد الحسابي.

من خلال نقل التخفيف إلى المُلاحَظ، يتجنب PNA النفقات الكبيرة لأخذ العينات التي كانت ستنتج لولا ذلك عن إدخال عمليات تصحيح فيزيائية في الدارة. بدلاً من ذلك، تُنفَّذ الدارة الصاخبة الأصلية، بينما يُحوَّل المُلاحَظ إلى مؤثر جديد O~\tilde{O} تلغي قيمة توقعه تأثيرات الضجيج.

سير عمل PNA​

يمكن فهم العملية من خلال المراحل المفاهيمية التالية. يوضح المخطط الأول تجربة صاخبة قياسية.

Noisy experiment

إذا تعلمنا نموذج الضجيج، يمكننا تطبيق معكوسه وإلغاء الضجيج.

Noise-mitigated experiment

بدلاً من تنفيذ قناة الضجيج المعكوسة عن طريق أخذ عينات منها على QPU كما في إلغاء الأخطاء الاحتمالي (PEC)، نطبقها كلاسيكيًا على المُلاحَظ المقيس باستخدام نشر باولي. يخفف المُلاحَظ الناتج فعليًا ضجيج البوابات المتعلَّم عند قياسه.

PNA overview

تخفيف الأخطاء المعياري باستخدام Samplomatic و Executor​

يعتمد نهج PNA على بنية معيارية لتخفيف الأخطاء في Qiskit. تستخدم هذه البنية مكتبة samplomatic مع صنفي QuantumProgram وExecutor (المضافين إلى Qiskit Runtime في qiskit-ibm-runtime الإصدار v0.47.0) لجعل تقنيات مثل امتصاص الضجيج المُنتشر ولفّ باولي قابلة للتركيب وإعادة الاستخدام عبر تجارب مختلفة.

بدلاً من تضمين منطق التخفيف داخل تعريف الدارة نفسه، يُعبَّر عن التخفيف بشكل تصريحي من خلال تعليقات samplomatic ويُدار برمجيًا عبر Executor، الذي يتحكم في كيفية توليد الدارات العشوائية وتنفيذها ومعالجتها لاحقًا.

في هذا الدليل التعليمي، ننفذ نمط Qiskit لتوضيح كيف يمكن لـ PNA نشر قنوات ضجيج باولي المعكوسة وتعديل المُلاحَظ وفقًا لذلك، لتحسين تقدير قيم التوقع على QPUs الصاخبة.

نظرة عامة على سير العمل​

  • الخطوة 1: الربط بالمسألة الكمية

    • بناء نموذج إيزينغ المُركَّل الترتري المعكوس ومُلاحَظ مستهدف.
  • الخطوة 2: توصيف ونشر الضجيج

    • استخدام samplomatic لتحديد وتعليق طبقات فريدة من بوابات الكيوبتين والقياسات في الدارة.

    • تعلّم الضجيج المؤثر على كل طبقة فريدة باستخدام NoiseLearnerV3.

    • ربط كل تعليق InjectNoise بنموذج الضجيج المتعلَّم المقابل له.

    • استخدام qiskit-addon-pna لنشر قنوات الضجيج المعكوسة إلى الأمام عبر الدارة وامتصاصها في المُلاحَظ المستهدف.

  • الخطوة 3: تنفيذ التجارب الكمية

    • تعريف QuantumProgram لتحديد أخذ العينات العشوائي عبر samplex وتنفيذ التجارب على الخلفية باستخدام Executor.
  • الخطوة 4: إعادة البناء وتحليل النتائج

    • مقارنة استراتيجيات التخفيف (PNA، PNA+TREX، PNA+PS، PNA+PS+TREX) وتصور التحسن مقارنةً بالنتائج غير المخففة.

المتطلبات​

قبل بدء هذا الدليل التعليمي، تأكد من تثبيت ما يلي:

  • حزمة تطوير برمجيات Qiskit v2.2 أو أحدث، مع دعم التصور

  • Qiskit Runtime v0.47 أو أحدث (pip install qiskit-ibm-runtime)

  • Samplomatic v0.13 أو أحدث (pip install samplomatic)

  • إضافة PNA لـ Qiskit (pip install qiskit-addon-pna)

  • أدوات إضافة Qiskit (pip install qiskit-addon-utils)

الإعداد​

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

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_pna": "qiskit-addon-pna", "qiskit_addon_utils": "qiskit-addon-utils", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "samplomatic": "samplomatic"}
_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")
from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
QuantumProgram,
Executor,
NoiseLearnerV3,
)
from qiskit_addon_utils.exp_vals.measurement_bases import (
get_measurement_bases,
)
from qiskit_addon_utils.exp_vals.expectation_values import (
executor_expectation_values,
)
from qiskit_addon_utils.noise_management import trex_factors
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)
from qiskit_addon_pna import generate_noise_mitigating_observable
import samplomatic
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.annotations import InjectNoise
from samplomatic.utils import get_annotation, find_unique_box_instructions
import numpy as np
import matplotlib.pyplot as plt

# Selects a connected chain of low-error qubits on the target backend.
# The mirrored kicked-Ising circuit is a 1D chain, so we only need a line
# of connected physical qubits; the helper walks the backend's coupling map
# and grows a chain along the lowest-error two-qubit edges, so it works for
# any backend rather than relying on a hardcoded layout.
def find_qubit_chain(backend, length):
"""Find a connected chain of ``length`` physical qubits on ``backend``.

The chain is grown greedily along the lowest-error two-qubit edges, so it
favors better-performing qubits. Because the mirrored kicked-Ising circuit
is a 1D chain, a connected line is all we need.
"""
target = backend.target

# Identify the native two-qubit gate and build a per-edge error lookup.
two_qubit_gate = next(
name
for name in target.operation_names
if target[name]
and all(q is not None and len(q) == 2 for q in target[name])
)
edge_error = {
frozenset(qargs): (
1.0 if props is None or props.error is None else props.error
)
for qargs, props in target[two_qubit_gate].items()
}

graph = backend.coupling_map.graph.to_undirected(multigraph=False)
neighbors = {n: list(graph.neighbors(n)) for n in graph.node_indices()}

def first_chain_from(start):
path, visited = [start], {start}

def grow():
if len(path) == length:
return True
node = path[-1]
order = sorted(
neighbors[node],
key=lambda m: edge_error.get(frozenset((node, m)), 1.0),
)
for nxt in order:
if nxt not in visited:
visited.add(nxt)
path.append(nxt)
if grow():
return True
path.pop()
visited.remove(nxt)
return False

return path if grow() else None

def chain_cost(path):
return sum(
edge_error.get(frozenset((path[i], path[i + 1])), 1.0)
for i in range(len(path) - 1)
)

# Try low-degree qubits first (the natural ends of long chains) and keep
# the lowest-error chain found.
best_path, best_cost = None, float("inf")
for start in sorted(neighbors, key=lambda n: len(neighbors[n])):
chain = first_chain_from(start)
if chain is not None and (cost := chain_cost(chain)) < best_cost:
best_path, best_cost = chain, cost

if best_path is None:
raise ValueError(
f"Could not find a connected chain of {length} qubits "
f"on '{backend.name}'."
)
return best_path

مثال محاكاة صغير النطاق​

يخفف PNA ضجيج بوابات الكيوبتين الفيزيائية لمعالج كمي محدد. يعتمد سير العمل على خدمتي عتاد ليس لهما نظير معنوي على محاكي مثالي:

  • يقوم NoiseLearnerV3 تجريبيًا بتوصيف قناة ضجيج باولي-ليندبلاد المتناثرة المرتبطة بكل طبقة فريدة من بوابات الكيوبتين في الدارة المحوَّلة. على محاكي خالٍ من الضجيج، لا يوجد ضجيج ليُتعلَّم، وستكون القناة المعكوسة المنشورة هي قناة الهوية.

  • يأخذ الأساسي Executor عينات من الدارات الملفوفة والعشوائية التي يولّدها samplomatic، وذلك بتنفيذها على خلفية حوسبة.

من حيث المبدأ، يمكنك الاستعاضة بنموذج ضجيج اصطناعي. على سبيل المثال، يمكنك إرفاق تعليمات PauliLindbladError باستخدام Qiskit Aer وتمرير الدارة الصاخبة الناتجة مباشرةً إلى generate_noise_mitigating_observable. لكن هذا يتحقق فقط من الحسابات الكلاسيكية مقابل الضجيج الذي أدخلته بنفسك، ويحجب مغزى التقنية. لهذا السبب نتخطى مثال المحاكاة ونعرض سير عمل PNA الكامل مباشرةً على العتاد، مع تفصيل كل خطوة من نمط Qiskit أدناه.

مثال عتاد واسع النطاق​

سنشغّل الآن سير عمل PNA الكامل على نموذج إيزينغ مُركَّل بـ30 موقعًا مُنفَّذ على عتاد ®IBM Quantum، متبعين الخطوات الأربع لنمط Qiskit.

الخطوة 1: الربط بمسألة كمية​

توليد دارة تروتر المعكوسة والمُلاحَظ

لهذه التجربة، سندرس ديناميكية الزمن لنموذج إيزينغ مُركَّل بـ30 موقعًا على سلسلة دوران أحادية البعد. الهاملتوني المعتبر هو:

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

حيث J>0J>0 تصف اقتران الدورانات المتجاورة، i<ji<j، والحقل المستعرض الشامل، hh، يُضبط على π8\frac{\pi}{8}. كلما ابتعد hh عن زاوية كليفوردية (أي θ=nπ2,n∈Z\theta=n\frac{\pi}{2}, n \in \mathbb{Z})، أصبح من الأصعب نشر مولدات مضادة الضجيج عبر الدارة.

لاختيار المُلاحَظ، سنعتبر متوسط المغناطيسية أحادية الموقع، 1N∑i=1N⟨zi⟩\frac{1}{N} \sum_{i=1}^{N} \langle z_i \rangle، حيث NN هو عدد المواقع.

num_qubits = 30
num_trotter_steps = 10
rx_angle = np.pi / 8

# Avg single-site magnetization
id_pauli = Pauli("I" * num_qubits)
observable = (
SparsePauliOp([id_pauli.dot(Pauli("Z"), [i]) for i in range(num_qubits)])
/ num_qubits
)

# Implement Trotterized kicked-Ising model
circuit = QuantumCircuit(num_qubits)
for _step in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
for first_qubit in (1, 2):
for idx in range(first_qubit, num_qubits, 2):
# equivalent to Rzz(-pi/2):
circuit.sdg([idx - 1, idx])
circuit.cz(idx - 1, idx)
# Append the inverse circuit to complete the mirroring
circuit.compose(circuit.inverse(), inplace=True)
circuit.measure_active()
circuit.draw("mpl", fold=-1)

Output of the previous code cell

الخطوة 2: تحسين المسألة للتنفيذ على العتاد​

الخطوة التالية هي تحضير دارة تروتر المعكوسة لدينا للتنفيذ على عتاد IBM Quantum الحقيقي. يتطلب التشغيل على QPU أكثر من مجرد بناء الدارة المجردة، حيث نحتاج إلى تحسينها بحيث:

  1. تحترم مجموعة البوابات الأصلية والاتصال الخاص بالخلفية. يقوم التحويل بربط الدارة المنطقية بدارة ISA مدعومة من الخلفية المستهدفة. هذا يضمن أن كل تفاعل بوابة وكيوبت قابل للتحقيق فيزيائيًا.

  2. يمكننا توصيف الضجيج على مستوى طبقات الدارة. يعتمد PNA على تعلّم ونشر قنوات الضجيج المعكوسة. للقيام بذلك بكفاءة، نقسّم الدارة المحوَّلة إلى طبقات "صناديق" فريدة من بوابات الكيوبتين. تسمح لنا هذه الصناديق بربط كل طبقة من الدارة بنموذج الضجيج المتعلَّم الخاص بها.

  3. يمكننا تغذية نماذج ضجيج واقعية في PNA. بمجرد وضع الدارة في صناديق، نستخدم خدمة NoiseLearnerV3 لتعلّم قنوات ضجيج باولي المؤثرة على كل طبقة فريدة من بوابات الكيوبتين تجريبيًا. تُربَط هذه النماذج المتعلَّمة بعد ذلك بالدارة من خلال تعليقات Samplomatic.

في مجملها، تسد هذه الخطوة الفجوة بين دارة معكوسة مثالية ودارة جاهزة للعتاد مع نماذج ضجيج متعلَّمة. مع هذا الإعداد، يمكن لـ PNA نشر قنوات الضجيج المعكوسة عبر الدارة وتعديل المُلاحَظ وفقًا لذلك.

الاتصال بالخلفية والتحويل إلى دارة ISA​

نبدأ أولاً بتهيئة خدمة الحوسبة الكمية IBM Quantum واختيار خلفية. صادق باستخدام حسابك الخاص باتباع تعليمات حفظ بيانات الاعتماد، وبعدها سيلتقطها QiskitRuntimeService() تلقائيًا.

# Initialize IBM Quantum Compute Service using your saved credentials
service = QiskitRuntimeService()

backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=num_qubits
)
# Re-fetch with fractional gates enabled (least_busy does not forward this)
# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.
backend = service.backend(backend.name, use_fractional_gates=True)
print(f"Selected backend: {backend.name}")
Selected backend: ibm_fez

بعد ذلك، نختار سلسلة متصلة من الكيوبتات على الخلفية ونحوّل الدارة إليها.

باستخدام الأداة المساعدة find_qubit_chain المعرَّفة في قسم الإعداد، نختار خطًا من num_qubits كيوبتًا فيزيائيًا متصلًا. ثم نحوّل باستخدام optimization_level=0 ونثبّت هذه السلسلة كـ initial_layout، مما يحافظ تمامًا على بنية طبقة بوابة الكيوبتين للدارة المعكوسة. هذه البنية هي ما تعتمد عليه خطوات وضع الصناديق وتعلّم الضجيج، لذا يجب تجنب مستوى تحسين أعلى (سيلغي البوابات المعكوسة) هنا.

# Find a connected, low-error chain of qubits on the chosen backend
layout = find_qubit_chain(backend, num_qubits)

# Transpile the circuit for the target backend, pinning the chain as the layout.
# optimization_level=0 preserves the mirrored two-qubit-gate layers that the
# boxing and noise-learning steps rely on.
pm = generate_preset_pass_manager(
backend=backend, optimization_level=0, initial_layout=layout
)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
isa_circuit.draw("mpl", fold=-1)

Output of the previous code cell

لفّ طبقات بوابات الكيوبتين والقياسات، وإيجاد طبقات فريدة​

نستخدم samplomatic لوضع الدارة في صناديق وتحديد طبقات فريدة من بوابات الكيوبتين. الصندوق هو بنية تجمّع التعليمات معًا بحيث يمكن لاحقًا تطبيق نوايا أو تعليقات محددة بشكل موحد على جميع البوابات ضمن الصندوق نفسه.

هنا، نستدعي الطريقة generate_boxing_pass_manager، والتي تتجاوز مجرد تحديد طبقات بوابات الكيوبتين. تؤدي عدة مهام رئيسية:

  • تجميع جميع طبقات بوابات الكيوبتين في الدارة،

  • تطبيق تعليقي Twirl وChangeBasis على تلك الطبقات،

  • تجميع عمليات القياس في أقسامها الخاصة الموضوعة في صناديق، و

  • تطبيق تعليق InjectNoise على كل طبقة من بوابات الكيوبتين.

تحدد هذه التعليقات كيفية التعامل مع الضجيج وتغييرات الأساس واللف عبر الدارة. كما أنها تؤسس البنية المستخدمة لاحقًا لتعلّم الضجيج وتخفيفه.

خيارات التهيئة الرئيسية هي:

  • enable_gates/enable_measures: True: وضع جميع طبقات بوابات الكيوبتين والقياسات النهائية في صناديق. تُترك بوابات الكيوبت الواحد ملبَّسة يسارًا داخل الصناديق.

  • measure_annotations: all: تضمين تعليقي Twirl وChangeBasis على صندوق القياس.

  • twirling_strategy: active: لفّ جميع الكيوبتات النشطة في كل صندوق يحتوي على بوابات تشابك.

  • inject_noise_targets: gates: إضافة تعليقات InjectNoise إلى جميع الصناديق المعلَّمة بـTwirl والتي تحتوي على بوابات تشابك.

  • inject_noise_strategy: uniform_modification: تحجيم جميع طبقات الضجيج بشكل متكافئ عبر الدارة.

# Box up circuit with Twirl and InjectNoise annotations
pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
twirling_strategy="active",
inject_noise_targets="gates",
inject_noise_strategy="uniform_modification",
)
boxed_circuit = pm.run(isa_circuit)
draw_circ = QuantumCircuit(boxed_circuit.num_qubits)
draw_circ.append(boxed_circuit.data[0], qargs=boxed_circuit.data[0].qubits)
draw_circ.append(boxed_circuit.data[1], qargs=boxed_circuit.data[1].qubits)
draw_circ.draw("mpl", fold=-1, scale=0.3, idle_wires=False)

Output of the previous code cell

توليد دارة القالب وsamplex، اللذين يحددان كيفية أخذ عينات من الدارة.

نضيف هنا أيضًا قياسات مراقبة وانتقاء بعدي، وهي ضرورية لإجراء الانتقاء البعدي على العينات الناتجة من Executor.

# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)

# Add post-selection instructions to the template circuit
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
template_circuit = post_selection_pm.run(template_circuit)
draw_circ = template_circuit.copy_empty_like()
draw_circ.data = template_circuit.data[:324]
draw_circ.draw("mpl", fold=-1, scale=0.3, idle_wires=False)

Output of the previous code cell

تعلّم الضجيج باستخدام NoiseLearnerV3​

قبل أن نتمكن من تطبيق PNA لتخفيف الأخطاء، نحتاج أولاً إلى توصيف الضجيج المؤثر على كل طبقة فريدة من بوابات الكيوبتين وطبقة القياس في دارتنا. للقيام بذلك، نستخدم برنامج NoiseLearnerV3 لتعلّم نماذج ضجيج تجريبيًا لكل طبقة تم تحديدها سابقًا. تؤدي أداة التعلّم تجارب على غرار المعايرة تقدّر قناة الضجيج المؤثرة على كل طبقة وتُرجع كائن نتيجة يحتوي على النموذج المتعلَّم.

نبدأ بتحديد الطبقات الفريدة في دارتنا باستخدام find_unique_box_instructions من samplomatic. هذا يضمن أننا نتعلّم الضجيج مرة واحدة فقط لكل نوع طبقة مميز، مما يقلل من عدد التجارب وتكلفة الإطلاقات الإجمالية. تُمرَّر قائمة الطبقات الناتجة إلى أداة تعلّم الضجيج.

هناك عدة معاملات رئيسية تتحكم في كيفية تعلّم الضجيج:

  • num_randomizations: عدد الدارات العشوائية المستخدمة لكل تهيئة تعلّم.

  • shots_per_randomization: عدد الإطلاقات المأخوذة لكل دارة تعلّم عشوائية.

  • layer_pair_depths: أعماق الدارة (مقاسة بعدد الأزواج) المستخدمة في تجارب التعلّم.

  • post_selection: يمكّن الانتقاء البعدي القائم على الحواف باستخدام بوابات rx لتطبيق نبضات ما بعد القياس.

# Noise learning parameters
num_randomizations_nl = 64
shots_per_randomization_nl = 128

# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. ``layer_pair_depths`` are measured in layer pairs (a layer + its
# inverse), so the deepest value is half the circuits's two-qubit-layer depth.
# Learning to this depth improves the quality of the mitigation.

depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2

# Use a fixed schedule of learning depths, but drop any that exceed the circuit's
# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never
# learn deeper than the circuit being mitigated
candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]
layer_pair_depths = sorted(
{d for d in candidate_depths if d < max_layer_pair_depth}
| {max_layer_pair_depth}
)

# Find the unique instructions (layers) from the boxed-up circuit
unique_2q_layers_and_meas = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)

# Configure and run the noise learner on the unique layers.
# Options can be passed directly as a dictionary.
noise_learner_options = {
"num_randomizations": num_randomizations_nl,
"shots_per_randomization": shots_per_randomization_nl,
"layer_pair_depths": layer_pair_depths,
"post_selection": {
"enable": True,
"strategy": "edge",
"x_pulse_type": "rx",
},
"environment": {"job_tags": ["TUT_PNA"]},
}

noise_learner = NoiseLearnerV3(backend, noise_learner_options)
noise_learner_job = noise_learner.run(unique_2q_layers_and_meas)
noise_learner_result = noise_learner_job.result()

تصور معدلات الضجيج المتعلَّمة​

بعد تعلّم نماذج الضجيج، يمكننا فحص توزيع معدلات الأخطاء المُستنتَجة لكل من العمليات أحادية الكيوبت وثنائية الكيوبت. يستخرج الكود أدناه تمثيلات باولي-ليندبلاد من نتائج الضجيج المتعلَّمة ويجمع معدلات الضجيج المقابلة.

لكل طبقة متعلَّمة:

  • نحوّل نموذج الضجيج إلى قائمة متناثرة من مجموعات (pstr, qubits, rate)، حيث pstr هي سلسلة باولي المؤثرة على الكيوبتات المعطاة وrate هو معدل الخطأ المرتبط بها.
  • نفصل المعدلات إلى حدود أحادية الكيوبت (len(pstr) == 1) وثنائية الكيوبت (len(pstr) == 2).
  • تُرتَّب بعد ذلك قوائم المعدلات، وتُحسب قيمها الوسيطة.

نرسم توزيعات معدلات الضجيج أحادية الكيوبت (أحمر) وثنائية الكيوبت (أزرق) على مقياس لوغاريتمي، مع تمييز قيمها الوسيطة بخطوط عمودية، حتى نتمكن من مقارنة الأحجام النسبية لمولدات باولي-ليندبلاد المتعلَّمة. يعتمد ترتيب المعدلات أحادية وثنائية الكيوبت على الجهاز وعلى الطبقات المحددة التي يجري توصيفها؛ في هذا التشغيل، تحمل المولدات أحادية الكيوبت (الوزن-1) المعدل الوسيط الأكبر.

hw_rates_1q = []
hw_rates_2q = []
for nlr in noise_learner_result[:2]:
plm_list = nlr.to_pauli_lindblad_map().to_sparse_list()
hw_rates_1q += [
rate for (pstr, qubits, rate) in plm_list if len(pstr) == 1
]
hw_rates_2q += [
rate for (pstr, qubits, rate) in plm_list if len(pstr) == 2
]
hw_rates_1q = sorted(hw_rates_1q)
hw_rates_2q = sorted(hw_rates_2q)
median_1q = hw_rates_1q[len(hw_rates_1q) // 2]
median_2q = hw_rates_2q[len(hw_rates_2q) // 2]
fig, ax = plt.subplots(1, 1, figsize=(14, 5))
ax.scatter(
(hw_rates_1q),
[(i) / (len(hw_rates_1q) - 1) for i in range(len(hw_rates_1q))],
color="red",
label="1q rates",
)
ax.set_xscale("log")
ax.set_ylim(0, 1.1)
ax.vlines(median_1q, 0, 1, color="red")
ax.text(median_1q * 1.1, 0.1, f"{median_1q:.2e}")
ax.scatter(
(hw_rates_2q),
[(i) / (len(hw_rates_2q) - 1) for i in range(len(hw_rates_2q))],
color="blue",
label="2q rates",
)
ax.set_xscale("log")
ax.set_ylim(0, 1.1)
ax.vlines(median_2q, 0, 1, color="blue")
ax.text(median_2q * 1.1, 0.2, f"{median_2q:.2e}")
ax.set_title("Learned noise rates")
ax.set_xlabel("Noise rate")
ax.set_yticks([])
plt.legend()
<matplotlib.legend.Legend at 0x125336120>

Output of the previous code cell

ربط صناديق الدارة بالضجيج المتعلَّم​

بمجرد حصولنا على نماذج ضجيج لكل طبقة فريدة من بوابات الكيوبتين، نحتاج إلى ربطها بتعليقات InjectNoise المقابلة داخل الدارة الموضوعة في صناديق.

توجيه InjectNoise هو تعليق samplomatic يستخدم ملبِّسات الكيوبت الواحد لحقن الضجيج في الدارة بطريقة متحكم بها وقابلة للتهيئة. يمكّن من نمذجة ضجيج معيارية عبر طبقات مختلفة.

يتضمن كل تعليق InjectNoise:

  • InjectNoise.ref - معرّف فريد للتعليق. يُستخدم هذا بواسطة كائن samplex لتعيين نموذج الضجيج المقابل بشكل صحيح.
  • InjectNoise.modifier_ref (اختياري) - مرجع ثانوي يسمح بتحجيم نموذج الضجيج المعيَّن بعامل ضربي.

في هذه الخطوة، نبني تخطيطًا من كل InjectNoise.ref إلى نموذج الضجيج المتعلَّم المقابل له (PauliLindbladMap). يضمن هذا التخطيط أن كل طبقة بوابة تشابك في الدارة مقترنة بنموذج الضجيج المناسب، بحيث تُطبَّق تأثيرات الضجيج بدقة أثناء أخذ العينات وخطوات تخفيف الضجيج اللاحقة.

# map inject noise refs to pauli lindblad maps
refs_to_noise_models = {}
for instruction, result in zip(
unique_2q_layers_and_meas, noise_learner_result, strict=False
):
if inject_noise_annot := get_annotation(
instruction.operation, InjectNoise
):
refs_to_noise_models[inject_noise_annot.ref] = (
result.to_pauli_lindblad_map()
)

نشر المُلاحَظ عبر مضاد الضجيج المتعلَّم​

كما نوقش أعلاه، يتم ذلك في خطوتين. أولاً، ننشر مولد مضاد الضجيج إلى نهاية الدارة. بعد ذلك، ننشر المُلاحَظ عبر ذلك المولد بعد تطوره. تتكرر هذه العملية لكل مولد مضاد ضجيج في الدارة. في هذا التنفيذ، يُنشَر كل مولد في طبقة معينة إلى نهاية الدارة بالتوازي. بالإضافة إلى ذلك، تُستخدَم المعالجة المتعددة في بايثون لأداء كل من النشر الأمامي لمضاد الضجيج والنشر الخلفي للمُلاحَظ بالتوازي. هذا يمنع تراكم المولدات بعد تطورها في الذاكرة ويعظّم أيضًا موارد الحوسبة.

عند تشغيل PNA، يجب دائمًا توفير دارة صاخبة ومُلاحَظ. إذا كانت دارتك الصاخبة دارة موضوعة في صناديق مع تعليقات InjectNoise، فأنت بحاجة إلى توفير التخطيط الذي أنشأناه في الخطوة أعلاه. يمكن أيضًا تمرير دارة غير موضوعة في صناديق تحتوي على تعليمات PauliLindbladError من qiskit-aer. في تلك الحالة، لا يلزم توفير refs_to_noise_models. بالإضافة إلى المدخلات الأساسية، ضع في اعتبارك أيضًا ما يلي:

  • max_err_terms: عدد الحدود التي يجب الاحتفاظ بها في كل مولد مضاد ضجيج أثناء نشره للأمام. يؤدي السماح بأن يكون هذا أكبر عمومًا إلى زيادة الدقة، لكن هذا السلوك غير مضمون أن يكون رتيبًا.

  • max_obs_terms: عدد الحدود التي يجب الاحتفاظ بها في المُلاحَظ المخفِّف للضجيج، O~\tilde{O}، أثناء نشره للخلف عبر مضاد الضجيج بعد تطوره. تزيد القيم الأكبر عمومًا من الدقة، لكن ليس مضمونًا أن تفعل ذلك بشكل رتيب.

  • num_processes: عدد الأنوية المخصصة للعملية. تذكر، تُنشَر المولدات للأمام وتُطبَّق على المُلاحَظ بالتوازي.

  • search_step: تستخدم خطوة النشر الخلفي طريقة جشعة لإجراء الترافق (conjugation) بين مؤثرين تقريبيًا في أساس باولي. يمكن تسريع هذه الطريقة عن طريق زيادة search_step. راجع توثيق pauli-prop لمزيد من المعلومات.

  • num_to_measure: على الرغم من أن هذا المتغير ليس مدخلاً لـ generate_noise_mitigating_observable، إلا أننا نستخدمه للتحكم في عدد الحدود من O~\tilde{O} التي نريد قياسها فعليًا. هنا نقيس فقط أفضل 30 حدًا، وهي الحدود الأصلية في مُلاحَظنا. أُعيد تحجيم الحدود الآن بحيث يكون لقياسها تأثير تخفيف ضجيج البوابات المتعلَّم. على الرغم من أننا نقيس 30 حدًا فقط من O~\tilde{O}، غالبًا ما يكون من المفيد السماح له بأن يكبر، لأن ذلك يزيد من دقة عوامل تحجيم الحدود الرائدة.

# PNA parameters
num_processes = 8
max_err_terms = 10_000
max_obs_terms = 10_000
num_to_measure = num_qubits

obs_tilde_isa = generate_noise_mitigating_observable(
boxed_circuit,
isa_observable,
refs_to_noise_models,
max_err_terms=max_err_terms,
max_obs_terms=max_obs_terms,
num_processes=num_processes,
print_progress=True,
search_step=8,
)
p_2_v = {p: v for v, p in enumerate(layout)}
obs_tilde_virtual = SparsePauliOp.from_sparse_list(
[
(pstr, [p_2_v[p] for p in p_qubits], coeff)
for (pstr, p_qubits, coeff) in obs_tilde_isa.to_sparse_list()
],
num_qubits=num_qubits,
)
obs_tilde_virtual = obs_tilde_virtual[
np.argsort(np.abs(obs_tilde_virtual.coeffs))[::-1]
][:num_to_measure]
Finished! 13740 / 13740 generators propagated.
obs_tilde_isa = obs_tilde_isa[np.argsort(np.abs(obs_tilde_isa.coeffs))][::-1]
plt.xscale("log")
plt.yscale("log")
plt.title(r"$\tilde{O}$ coeff magnitudes")
plt.ylabel("Magnitude")
plt.xlabel("Pauli term index")
plt.plot(np.abs(obs_tilde_isa.coeffs), ".")
[<matplotlib.lines.Line2D at 0x124b0a120>]

Output of the previous code cell

تحويل أسس القياس إلى الشكل القانوني​

بعد ذلك، نجد مجموعة صغرى من الأسس للقياس بحيث يمكننا تغطية كل حد باولي في المُلاحَظ المقيس بالكامل (يمكن قياس عدة مُلاحَظات في وقت واحد إذا تبادلت من حيث الكيوبتات). نظرًا لأننا نقيس فقط الحدود في مُلاحَظنا الأصلي، وهو مجموع كل باوليات-Z الأحادية، هناك حاجة إلى أساس واحد فقط: الأساس-Z بالكامل.

بالإضافة إلى إيجاد مجموعة من أسس قياس باولي، نحتاج إلى ربط حدود باولي هذه بالشكل القانوني المتوقع من Executor. لمزيد من المعلومات حول ترتيب الكيوبت القانوني، تفضل بزيارة توثيق Samplomatic.

meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx
for idx, qubit in enumerate(boxed_circuit.qubits)
if qubit in meas_box.qubits
]
c_2_p = {
c: p for c, p in enumerate(canonical_qubits)
} # canonical -> physical
p_2_v = {p: v for v, p in enumerate(layout)} # physical -> virtual
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()} # canonical -> virtual
meas_bases, bases_reverser = get_measurement_bases(obs_tilde_virtual)
meas_bases_canonical = [
np.array([base[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
for base in meas_bases
]

الخطوة 3: تنفيذ التجارب الكمية​

تحديد كيفية أخذ العينات في QuantumProgram​

نقوم الآن بإعداد QuantumProgram، الذي يعمل كحاوية مركزية لجميع الدارات وتهيئات أخذ العينات التي سينفذها Executor. يحدد هذا الكائن كيفية توليد نسخ دارات عشوائية، وتجميعها في دفعات، وتنفيذها لإنتاج نتائج القياس المستخدمة في PNA.

يمكن أن يحتوي QuantumProgram على عدة عناصر، يتكون كل منها من دارة قالب وكائن samplex مقابل يحدد كيفية تطبيق العشوائيات. يسمح هذا التجريد لـ Executor بالتعامل مع سير العمل بأكمله كبرنامج واحد معياري — من توليد الدارات العشوائية إلى جمع الإطلاقات وتجميعها.

في هذه الخطوة، ننشئ QuantumProgram يشغّل تجربة PNA لدينا باستخدام دارة القالب وsamplex اللذين بنيناهما سابقًا. تشمل التهيئة العناصر التالية:

  • template_circuit: الدارة التي تحتوي على جميع البوابات اللازمة لتنفيذ جميع العشوائيات المرغوبة (من عشوائيات اللف، والمعاملات، وما إلى ذلك).

  • samplex: كائن يحدد توزيع احتمالات على جميع عشوائيات الدارة الممكنة التي يُؤخذ منها عينات.

  • samplex_arguments: الربطات اللازمة لتحديد samplex بالكامل

    • basis_changes: هنا نحدد مجموعة من الأسس للقياس التي ستغطي جميع حدود باولي في المُلاحَظ المقيس.

    • noise_scales.ref: نضبط مقياس كل طبقة ضجيج على 0.0 لمنع حقن أي ضجيج إضافي في عيناتنا.

    • pauli_lindblad_maps: مطلوب إذا تم تمرير noise_scales. هذا يربط فقط طبقات الضجيج بنموذج الضجيج المرتبط بها.

  • shape: مجموعة شكل لتمديد الشكل الضمني المحدد بواسطة samplex_arguments. المحاور غير التافهة الناتجة عن هذا التمديد تُعدِّد العشوائيات.

# Control the # of shots during execution
shots_per_randomization_exec = 64
num_randomizations_exec = 6144

# Zero out the noise to prevent noise from being injected during execution.
# We only added InjectNoise annotations so PNA could associate the noise
# to layers in the circuit
samplex_inputs = {f"noise_scales.{ref}": 0.0 for ref in refs_to_noise_models}
samplex_inputs |= {"pauli_lindblad_maps": refs_to_noise_models}

# Specify the bases to measure. The samplex exposes one basis-change input per
# ChangeBasis-annotated box; here a single all-Z basis covers every term. We
# look up the basis-change interface name rather than hardcoding an index, since
# the name depends on the circuit's box structure.
bases_broadcastable = np.expand_dims(np.array(meas_bases_canonical), axis=1)
samplex_inputs |= {
spec.name: bases_broadcastable
for spec in samplex.inputs().get_specs(r"^basis_changes\.")
}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().make_broadcastable().bind(**samplex_inputs)
)

# Instantiate the QuantumProgram with the specified parameters
program = QuantumProgram(shots=shots_per_randomization_exec)
program.append_samplex_item(
circuit=template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations_exec,),
)

أخذ عينات من الدارة باستخدام Executor​

الآن بعد أن حددنا QuantumProgram الخاص بنا، يصبح تنفيذ التجربة مباشرًا. نقوم ببساطة بإنشاء كائن Executor، وتزويده بالخلفية، وتشغيل البرنامج.

# Execute (sample) the circuit
executor = Executor(backend)
job_exec = executor.run(program)
exec_results = job_exec.result()

الخطوة 4: إعادة البناء وتحليل النتائج​

لحساب قيمة توقع مخففة الأخطاء، نقوم بما يلي:

  • حساب عوامل تحجيم TREX بناءً على الضجيج المتعلَّم المؤثر على القياسات،

  • توليد قناع للاحتفاظ فقط بالعينات المنتقاة بعديًا، و

  • استخدام دالة executor_expectation_values من qiskit-addon-utils لدمج جميع البيانات في قيمة توقع مخففة الأخطاء.

# Computing the TREX factors
measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_rescale_factors = trex_factors(measurement_noise_map, bases_reverser)

# Post-select the results
post_selector = PostSelector.from_circuit(
circuit=template_circuit, coupling_map=backend.coupling_map
)

# Compute the ps mask for filtering results
mask = post_selector.compute_mask(exec_results[0], strategy="edge")

# Compute expvals using post selected results
results = executor_expectation_values(
exec_results[0]["meas"],
bases_reverser,
meas_basis_axis=0,
avg_axis=1,
measurement_flips=exec_results[0]["measurement_flips.meas"],
pauli_signs=exec_results[0].get("pauli_signs", None),
postselect_mask=mask,
rescale_factors=trex_rescale_factors,
)

مقارنة استراتيجيات التخفيف: PNA، PNA+TREX، PNA+PS، PNA+PS+TREX​

نحسب ونصور قيم التوقع لعدة أشكال من التخفيف باستخدام نتائج Executor.

bases_reverser_unmit = {Pauli("Z" * num_qubits): [observable]}
args = [
(bases_reverser_unmit, None, None),
(bases_reverser, None, None),
(bases_reverser, None, trex_rescale_factors),
(bases_reverser, mask, None),
(bases_reverser, mask, trex_rescale_factors),
]

evs = []
for reverser, postsel_mask, factors in args:
# Compute expvals using post selected results
res_ps = executor_expectation_values(
exec_results[0]["meas"],
reverser,
meas_basis_axis=0,
avg_axis=1,
measurement_flips=exec_results[0]["measurement_flips.meas"],
pauli_signs=exec_results[0].get("pauli_signs", None),
postselect_mask=postsel_mask,
rescale_factors=factors,
)
res_ps = np.array(res_ps)
evs.append(res_ps[:, 0][0])

experiments = ["PNA", "PNA+TREX", "PNA+PS", "PNA+PS+TREX"]
colors = ["#d9d9d9", "#b0b0b0", "#7f7f7f", "#4c4c4c"]
plt.bar(experiments, evs[1:], color=colors)
plt.axhline(y=1, color="green", linestyle="--", linewidth=2, label="Ideal")
plt.axhline(
y=evs[0], color="red", linestyle="--", linewidth=2, label="Unmitigated"
)
plt.ylabel("Expectation value", fontsize=14)

plt.title(
r"30q Mirrored Ising, 10 Trotter steps, $\theta_{rx}=\frac{\pi}{8}$",
fontsize=14,
)
plt.legend(loc="upper left", bbox_to_anchor=(1.05, 1), borderaxespad=0.0)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Output of the previous code cell

تُظهر النتائج الفوائد التراكمية لدمج تقنيات مختلفة لتخفيف الأخطاء. نهج PNA البسيط يستعيد بالفعل قيمة التوقع لتقترب من المعيار المثالي، مما يشير إلى أن نشر قنوات الضجيج المعكوسة في المُلاحَظ يعوض بفعالية أخطاء البوابات الثنائية الكيوبت.

  • إضافة إعادة الترجيح TREX (PNA+TREX) تحسّن التقدير قليلاً من خلال تصحيح عدم التوازن في أخذ العينات في الدارات العشوائية.

  • الانتقاء البعدي (PNA+PS) يوفر تحسناً أكثر وضوحاً من خلال تصفية نتائج القياس غير المتسقة التي تنتج على الأرجح عن أخطاء متبقية.

  • أخيراً، الجمع بين الاثنين (PNA+PS+TREX) يعطي النتيجة الأكثر دقة، وتكاد تطابق القيمة المثالية، مما يُظهر كيف تعزز استراتيجيات التخفيف هذه بعضها البعض.

بشكل عام، تسلط المقارنة الضوء على أن PNA يُشكّل أساساً متيناً لتقدير قيم التوقع الواعي بالضجيج، بينما يقدم TREX والانتقاء البعدي تحسينات مكمّلة لتحقيق مزيد من الدقة.

الخطوات التالية​

إذا وجدت هذا العمل مثيراً للاهتمام، فقد تهتم بالمواد التالية: