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

PEC مع TREX

إلغاء الخطأ الاحتمالي (PEC) هو تقنية لتخفيف الضوضاء الكمّية في حسابات القيم المتوقعة للمرصودات. تتيح لك حساب تقدير غير منحاز للقيم المتوقعة. تعمل هذه التقنية بتنفيذ مجموعة من الدوائر المضوضَّة التي حُقن فيها معكوس نموذج ضوضاء متعلَّم. ثم يُقرَّب نموذج الضوضاء إلى قناة Pauli باستخدام لفّ Pauli (المعروف أيضًا بـالتجميع العشوائي). يمكن تعلّم نماذج الضوضاء هذه بكفاءة ويمكن تخفيفها باستخدام PEC (انظر Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors لمزيد من التفاصيل).

PEC

Twirled Readout Error eXtinction (TREX) هي تقنية تخفّف أخطاء القياس وهي مفيدة لتقدير القيم المتوقعة لمرصودات Pauli. تعتمد على لفّ القياس (حقن بوابة X عشوائيًا قبل القياس وقلب النتيجة المقيسة)، مما يجعل مصفوفة انتقال خطأ القراءة قطرية ويسهّل عكسها. يُتعلَّم حدّ إعادة تحجيم من قناة الضوضاء القطرية بقياس دوائر معايرة ملتفّة جرى تهيئتها في الحالة صفر.

Measurement twirling

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

سير العمل كما يلي:

  1. حدّد دائرة ومرصودًا.
  2. احسب القيم المتوقعة الدقيقة باستخدام محاكي متجه الحالة في Qiskit.
  3. احسب القيم المتوقعة المضوضَّة غير المخفَّفة.
  4. تعلّم ضوضاء طبقات التشابك باستخدام qiskit-noise-learning.
  5. جهّز برنامج PEC + TREX وأضف إليه دائرة معايرة TREX.
  6. قارن القيم المتوقعة المخفَّفة.

حدّد دائرة ومرصودًا​

جهّز دائرة مرآة لتطور زمني بطريقة Trotter لنموذج Ising خطي ذي مجال مستعرض على ثمانية كيوبتات. اعكس الدائرة لإلغاء وحدويّة الدائرة وتكوين دائرة هوية. يتيح لك ذلك حساب القيمة المتوقعة المثالية لكل مرصود بسهولة. بالإضافة إلى ذلك، يضاعف الانعكاس عمق الدائرة، وهو ما يضاعف بدوره أثر الضوضاء.

استخدم الزاوية θ=π2\theta= \frac{\pi}{2} للإبقاء على جميع البوابات في الدائرة ضمن زمرة Clifford، مما يتيح محاكاة أسرع باستخدام محاكي stabilizer.

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

_needed = {"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"}
_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 QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

warnings.filterwarnings("ignore")

def construct_ising_circuit(
num_qubits: int,
num_trotter_steps: int,
rx_angle: float,
barrier: bool = True,
) -> QuantumCircuit:
circuit = QuantumCircuit(num_qubits)

for _step in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
if barrier:
circuit.barrier()
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)
if barrier:
circuit.barrier()

return circuit

def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
mirror_circ = circuit.copy_empty_like()
mirror_circ.compose(circuit, inplace=True)
mirror_circ.barrier()
mirror_circ.compose(circuit.inverse(), inplace=True)
mirror_circ.measure_active()
return mirror_circ

# define observables
num_qubits = 8
observables = [
SparsePauliOp("I" * ((num_qubits - 1) - q) + "Z" + "I" * q)
for q in range(num_qubits)
]

# Instantiate circuit
num_trotter_steps = 5
rx_angle = np.pi / 2

circuit = construct_ising_circuit(
num_qubits, num_trotter_steps, rx_angle, barrier=False
)
mirrored_circuit = mirror_circuit(circuit)
print("Visualization of 1 mirrored Trotter step")
mirror_circuit(
construct_ising_circuit(num_qubits, 1, rx_angle, barrier=False)
).draw("mpl", scale=0.7)
Visualization of 1 mirrored Trotter step

Output of the previous code cell

احسب القيم المتوقعة المضوضَّة غير المخفَّفة​

يحتوي QuantumProgram على دائرة كمّية وsamplex بصيغة مناسبة للمُنفِّذ الأولي Executor. يبني MitigationTask.prepare كائن QuantumProgram من الدائرة والمرصودات وقيم المعاملات، ويضع الدائرة في صناديق (boxes) وفق خيارات التصنيد المعطاة. اضبط خيار التصنيد measure_annotations على "change_basis" لتدوير القياسات إلى الأسس المطلوبة، وأضف add_tags: "unique_box" ليتمكن المحاكي من ربط نموذج ضوضاء بصناديق الدائرة. يُلفّ البرنامج بطريقة Pauli افتراضيًا، وهو ما يحوّل على العتاد الضوضاء المتماسكة إلى ضوضاء Pauli التي تفترضها طرق التخفيف. يحدد num_randomizations عدد النسخ الملتفّة التي تُؤخذ من كل دائرة، ويحدد shots_per_randomization عدد اللقطات التي تحصل عليها كل نسخة، وبالتالي فإن حاصل ضربهما هو حد اللقطات لكل دائرة.

إذا كان لديك وصول إلى QPU، يمكنك تنفيذ البرنامج باستخدام Executor مبني من backend تابع لـ QiskitRuntimeService. يستخدم هذا المثال AerSimulator لتشغيل البرنامج محليًا. يحقن خيار المحاكي layer_noise_model الخاص به نموذج ضوضاء Pauli-Lindblad عشوائيًا على كل طبقة من بوابات التشابك، يتم توليده باستخدام random_pauli_lindblad_noise من qiskit-mitigation مع خطأ لكل بوابة طبقية (EPLG) يساوي 0.003، وهو نموذجي لمعالجات Heron QPU الحالية.

import matplotlib.pyplot as plt
from qiskit.circuit import CircuitInstruction
from qiskit.quantum_info import PauliLindbladMap, pauli_basis
from qiskit.transpiler import PassManager
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import Executor
from qiskit_mitigation import MitigationTask, find_combined_unique_layers
from qiskit_noise_learning.aer_executor.insert_noise_pass import (
InsertNoisePass,
)
from samplomatic import Tag
from samplomatic.utils import get_annotation

def generate_random_pauli_lindblad_noise(
layers,
num_qubits: int,
eplg: float = 3e-3,
readout_noise_scale: float = 3e-2,
seed: int | None = None,
) -> tuple[
list[tuple[CircuitInstruction, PauliLindbladMap]], 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 not contain_measurement:
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))
# generate a readout noise model
rng = np.random.default_rng(seed=seed)
rates = rng.random(num_qubits) * readout_noise_scale
readout_noise_model = PauliLindbladMap.from_sparse_list(
[("X", [q], rate) for q, rate in enumerate(rates)],
num_qubits=num_qubits,
)
return layer_noise_model, readout_noise_model

def add_readout_noise(program, readout_noise_model):
"""Injects readout noise to the circuits in a quantum program."""
readout_noise_map = {}
for item in program.items:
readout_tag = item.circuit[-1].label[
-4:
] # extract the readout layer tag from the circuit
readout_noise_map[readout_tag] = readout_noise_model
item.circuit = PassManager(
[InsertNoisePass(noise_dict=readout_noise_map, noise_after=False)]
).run(item.circuit)

num_randomizations = 300
shots_per_randomization = 64

boxing_options = {"add_tags": "unique_box"}

# find the unique layers to generate the layered noise for
box_layers = find_combined_unique_layers(
[mirrored_circuit], custom_boxing_options=boxing_options
)
layer_noise_model, readout_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(method="stabilizer"),
options={"simulator": {"layer_noise_model": layer_noise_model}},
)

task = MitigationTask()
program = task.prepare(
circuit=mirrored_circuit,
observables=observables,
parameters=None,
num_randomizations=num_randomizations,
shots_per_randomization=shots_per_randomization,
custom_boxing_options=boxing_options,
)
# For the simulation - we inject readout noise to the circuits of the program
add_readout_noise(program, readout_noise_model)

program._semantic_role = "simulation" # flag the program for the simulator
unmitigated_res = task.postprocess(noisy_executor.run(program).result())

x_vals = np.array(range(num_qubits))
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(x_vals, [1] * len(x_vals), "k-", alpha=0.5, label="Exact")
ax.errorbar(
x_vals,
unmitigated_res.data.evs,
unmitigated_res.data.stds,
fmt="o",
label="Unmitigated",
)

x_tick_labels = [r"$Z_{ {i} }$" for i in range(num_qubits)]
ax.set_xlabel(r"$\theta/\pi$")
ax.set_ylabel("Expectation value")
ax.legend()
ax.set_xticks(x_vals, x_tick_labels)
plt.tight_layout()
plt.show()

Output of the previous code cell

تعلّم ضوضاء طبقات التشابك​

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

يُستخدم qiskit-noise-learning لتعلّم الضوضاء. يكرّر المتعلّم كل طبقة fragment_depths مرة في دوائر التعلّم الخاصة به، ويلفّ كل دائرة num_randomizations مرة، ثم يطابق تلاشي الدقّات المقاسة مع نموذج Pauli-Lindblad. يمكن لهذه المعاملات أن تؤثر في دقة النموذج المتعلَّم. وكلما كان نموذج الضوضاء المتعلَّم أدق، كان إلغاء الضوضاء أدق. يرسم الكود التالي نموذج الضوضاء المتعلَّم مع دمج الطبقتين في خريطة واحدة. كل كيوبت يظهر كدائرة مقسّمة تمثّل معدلات X وY وZ ذات الوزن 1 مجموعةً على الطبقتين، وتُظهر كل حافة معدلات الوزن 2 المتعلَّمة. على سبيل المثال، معدل 3e-4 يعني أن الخطأ يحدث تقريبًا في ثلاثة من كل 10,000 استخدام للطبقة.

يتم تخفيف ضوضاء القراءة في المعالجة اللاحقة باستخدام TREX، على عكس نموذج ضوضاء البوابات المطلوب لتجهيز البرنامج من أجل حقن الضوضاء المضادة التي تقابل نموذج الضوضاء المتعلَّم. لذلك يمكن تنفيذ ضوضاء القراءة وتعلّمها إما عند تعلّم ضوضاء البوابات، أو عند تنفيذ البرنامج المخفَّف. قد يكون تعلّم ضوضاء القراءة إلى جانب ضوضاء البوابات مفيدًا لتعلّم نموذج ضوضاء متسق ذاتيًا (انظر Disambiguating Pauli noise in quantum computers على سبيل المثال). وفي الحالات التي تكون فيها ضوضاء البوابات وضوضاء القراءة منفصلتين في نموذج الضوضاء، يُوصى بتعلّم ضوضاء القراءة عند تنفيذ البرنامج المخفَّف لتجنّب انجراف الضوضاء بين وقت تعلّمها ووقت تنفيذها.

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

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 PEC
from qiskit_noise_learning.aer_executor import AerExecutor
from qiskit_noise_learning.noise_learner import LearningOptions, NoiseLearner
from samplomatic import InjectNoise

line_coupling = [(i, i + 1) for i in range(num_qubits - 1)]
backend = GenericBackendV2(
num_qubits=num_qubits,
basis_gates=["id", "rz", "sx", "x", "cz"],
coupling_map=line_coupling,
)

task_pec = PEC()
# first learn only the layers PEC will inject noise into, i.e. not the measurement layer
pec_layers = [
layer
for layer in task_pec.find_unique_layers(mirrored_circuit, boxing_options)
if get_annotation(layer.operation, InjectNoise)
]
layers = task_pec.find_unique_layers(mirrored_circuit, boxing_options)

learning_options = LearningOptions(
fragment_depths=[2, 4, 8, 16],
num_randomizations=250,
shots_per_randomizations=16,
)

# 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(method="stabilizer"), noise_dict=noise_dict
)
learner = NoiseLearner(
backend, options=learning_options, executor=noise_learner_simulator
)
learned_noise = (
learner.run(pec_layers).result().to_dict()
) # keyed by InjectNoise reference

# Draw the learned model on the line, laid out as a line with the qubits in order.
# Composing the two layers' maps sums their rates, giving one map over all the edges.
embedding = Embedding(
[(0, 0), (0, 2), (0, 4), (2, 4), (4, 4), (4, 2), (4, 0), (2, 0)],
line_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

جهّز برنامج PEC وTREX مدمجًا​

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

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

from qiskit_mitigation import TREX

trex = TREX()

program_pec = task_pec.prepare(
circuit=mirrored_circuit,
observables=observables,
parameters=None,
num_randomizations=1200,
shots_per_randomization=16,
custom_boxing_options=boxing_options,
trex=trex,
noise_maps=learned_noise, # PEC option
noise_gain=0, # PEC option
max_sampling_overhead=100, # PEC option
)
program_pec = trex.prepare(
num_randomizations=1500,
quantum_program=program_pec,
custom_boxing_options=boxing_options,
)

# For the simulation - we inject readout noise to the circuits of the program
add_readout_noise(program_pec, readout_noise_model)

افحص حمل اللقطات، ونفّذ البرنامج، وعالج النتائج​

يعيد PEC تقديرًا غير منحاز للقيمة المتوقعة، لكن بكلفة حمل كبير من اللقطات. يمكن تقريب حمل اللقطات المطلوب بحساب عامل غاما للدائرة، الذي يعكس أثر نموذج الضوضاء المتعلَّم على الدائرة. افتراضيًا، يُضاعَف عدد العمليات العشوائية لمهمة PEC بمقدار حمل اللقطات التقريبي اللازم للحصول على تقدير غير منحاز (~γ2\gamma^2). لتجنّب هذا التضخيم التلقائي، يمكنك ضبط المعامل scale_randomizations_by_gamma على False. ويُحدَّد سقف لحمل اللقطات بالمعامل max_sampling_overhead.

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

print(f"The circuit calculated gamma is: {task_pec.gamma}")
print(f"The circuit shot overhead is ~{task_pec.gamma**2}")
The circuit calculated gamma is: 1.2774846896089953
The circuit shot overhead is ~1.631967132185391
program_pec._semantic_role = (
"simulation" # flag the program for the simulator
)
job_pec_results = noisy_executor.run(program_pec).result()

# post-processing
pec_res = task_pec.postprocess(job_pec_results)
# In order to post process the results without TREX, we call `postprocess` again, but instead of the default options we
# pass only the PEC item result and the `measure_noise_data` as `None`
pec_without_trex_res = task_pec.postprocess(
job_pec_results[task_pec._program_item_index], measure_noise_data=None
)

قارن القيم المتوقعة المخفَّفة​

أخيرًا، ارسم القيمة المتوقعة لكل مرصود. يستعيد PEC + TREX القيم المتوقعة الدقيقة ضمن أشرطة الخطأ الخاصة به، بينما تنحاز النتيجة غير المخفَّفة إلى ما دونها بكثير.

x = np.array(range(num_qubits))
x_tick_labels = [r"$Z_{ {i} }$" for i in range(num_qubits)]

fig, ax = plt.subplots(figsize=(8, 5))
ax.errorbar(
x,
unmitigated_res.data.evs,
unmitigated_res.data.stds,
fmt="o",
label="Twirl only",
)
ax.errorbar(
x,
pec_without_trex_res.data.evs,
pec_without_trex_res.data.stds,
fmt="o",
label="PEC only",
)
ax.errorbar(
x,
pec_res.data.evs,
pec_res.data.stds,
fmt="o",
label="PEC + TREX",
)

ax.plot(x, [1] * len(x), "--", color="black", alpha=0.5, label="Exact")

tick_labels = [f"$Z_{ {i} }$" for i in range(num_qubits)]
ax.set_xticks(x, tick_labels)

ax.set_ylabel("Expectation value")
ax.legend()
ax.set_title("PEC+TREX Mitigated Expectation Values")
plt.tight_layout()
plt.show()

Output of the previous code cell