PEC na may TREX
Ang Probabilistic error cancellation (PEC) ay isang teknik para bawasan ang quantum noise sa mga kalkulasyon ng observable expectation value. Pinapahintulutan ka nitong makakuha ng unbiased na tantiya ng mga expectation value. Gumagana ang teknik na ito sa pagpapatakbo ng isang ensemble ng mga noisy na circuit na may inject na inverse ng isang natutunang noise model. Ang noise model ay tinatantiya pagkatapos bilang isang Pauli channel gamit ang Pauli twirling (kilala rin bilang randomized compiling). Ang ganitong mga noise model ay mahusay na matututunan at maaaring ma-mitigate gamit ang PEC (tingnan ang Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors para sa higit pang detalye).
Ang Twirled Readout Error eXtinction (TREX) ay isang teknik na nagbabawas ng mga measurement error at kapaki-pakinabang sa pagtantiya ng mga Pauli observable expectation value. Umaasa ito sa measurement twirling (random na pag-i-inject ng X gate bago ang measurement at pag-flip sa nasukat na resulta), na dinadayagonal ang readout-error transfer matrix at ginagawang madaling i-invert. Natututunan ang isang rescaling term mula sa diagonal na noise channel sa pamamagitan ng pagsukat ng mga twirled na calibration circuit na na-initialize sa zero state.
Ipinapakita ng gabay na ito kung paano gamitin ang PEC para bawasan ang gate noise at pagsamahin ito sa TREX para bawasan din ang measurement noise kapag tinatantiya ang mga expectation value ng mga observable.
Ganito ang workflow:
- Tukuyin ang isang circuit at observable.
- Kalkulahin ang mga eksaktong expectation value gamit ang statevector simulator ng Qiskit.
- Kalkulahin ang mga noisy, unmitigated na expectation value.
- Alamin ang noise ng mga entangling layer gamit ang qiskit-noise-learning.
- Maghanda ng PEC + TREX program at magdagdag dito ng TREX calibration circuit.
- Ihambing ang mga mitigated na expectation value.
Tukuyin ang isang circuit at observable
Maghanda ng mirror circuit ng Trotterized na time evolution ng isang linear na transverse-field Ising model sa walong qubit. I-mirror ang circuit para kanselahin ang unitary ng circuit at bumuo ng identity circuit. Pinapadali nito ang pagkalkula ng ideal na expectation value ng bawat observable. Bukod dito, dinodoble ng mirroring ang circuit depth, na dinodoble rin ang epekto ng noise.
Gamitin ang anggulong para panatilihing mula sa Clifford group ang lahat ng gate sa circuit, na nagpapahintulot ng mas mabilis na simulation gamit ang stabilizer simulator.
# 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
Kalkulahin ang mga noisy, unmitigated na expectation value
Ang QuantumProgram ay naglalaman ng isang quantum circuit at isang samplex sa format na angkop para sa Executor primitive. Binubuo ng MitigationTask.prepare ang isang QuantumProgram mula sa circuit, mga observable at mga parameter value, at ibinabox ang circuit gamit ang ibinigay na mga boxing option. Itakda ang boxing option na measure_annotations sa "change_basis" para i-rotate ang mga measurement sa kinakailangang mga basis, at idagdag ang add_tags: "unique_box" para maimapa ng simulator ang isang noise model sa mga box ng circuit. Pauli twirled ang programa bilang default, na sa hardware ay ginagawang Pauli noise ang coherent noise na ipinapalagay ng mga paraan ng mitigation. Itinatakda ng num_randomizations kung ilang twirled na instance ng bawat circuit ang sina-sample at itinatakda ng shots_per_randomization kung ilang shot ang natatanggap ng bawat instance, kaya ang kanilang produkto ang shot limit bawat circuit.
Kung may access ka sa QPU, maaari mong patakbuhin ang programa gamit ang Executor na binuo mula sa isang backend ng QiskitRuntimeService. Gumagamit ang halimbawang ito ng AerSimulator para patakbuhin ang programa nang lokal. Ang simulator option nitong layer_noise_model ay nag-i-inject ng random na Pauli-Lindblad noise model sa bawat layer ng entangling gates, na nabuo gamit ang random_pauli_lindblad_noise mula sa qiskit-mitigation na may error per layered gate (EPLG) na 0.003, na tipikal sa mga kasalukuyang 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()
Alamin ang noise ng mga entangling layer
Nagsisimula ang PEC sa pag-aaral ng twirled noise model ng bawat layer ng entangling gates sa circuit. Kung ipinapalagay mo ang Markovian na noise model, ipinapalagay na hindi naaapektuhan ng depth ng circuit ang noise channel ng isang entangling layer. Kaya, ang noise model lang ng bawat natatanging layer ng entangling gates ang kailangang matutunan. Ang circuit na ginamit sa gabay na ito ay maaaring hatiin sa dalawang natatanging layer. Mahalagang idisenyo ang mga circuit na may pinakamaliit na set ng natatanging entangling layer kung gusto mong matutunan ang isang noise model, dahil kailangang matutunan nang hiwalay ang mga natatanging layer. Kung masyadong marami ang natatanging layer sa circuit mo, maaaring maging imposible ang noise learning dahil sa sampling cost ng pag-aaral ng maraming layer.
Ginagamit ang qiskit-noise-learning para matutunan ang noise. Inuulit ng learner ang bawat layer nang fragment_depths beses sa mga learning circuit nito, nagta-twirl ng bawat circuit nang num_randomizations beses, at ifi-fit ang pagbaba ng mga nasukat na fidelity sa isang Pauli-Lindblad model. Maaaring makaapekto ang mga parameter na ito sa katumpakan ng natutunang model. Kung mas tumpak ang natutunang noise model, mas tumpak din ang pagkansela ng noise.
Ipinapakita ng sumusunod na code ang natutunang noise model kung saan pinagsama sa isang mapa ang dalawang layer. Ang bawat qubit ay isang pie ng weight-1 na X, Y at Z rate nito na pinagsama sa dalawang layer, at ipinapakita ng bawat edge ang mga natutunang weight-2 rate. Halimbawa, ang rate na 3e-4 ay nangangahulugang nagaganap ang error na iyon sa humigit-kumulang tatlo sa bawat 10,000 gamit ng layer.
Ginagawa ang readout noise mitigation sa post-processing gamit ang TREX, kabaligtaran ng gate noise model, na kailangan sa paghahanda ng programa para ma-inject ang anti-noise na tumutugma sa natutunang noise model. Kaya, ang readout noise ay maaaring patakbuhin at matutunan habang inaalam ang noise ng mga gate, o habang pinapatakbo ang mitigated na programa. Maaaring makatulong ang pag-aaral ng readout noise kasabay ng gate noise para matutunan ang self-consistent na noise model (tingnan ang Disambiguating Pauli noise in quantum computers bilang halimbawa). Sa mga kasong magkahiwalay ang gate noise at readout noise sa noise model, inirerekomendang alamin ang readout noise habang pinapatakbo ang mitigated na programa para maiwasan ang drift ng noise sa pagitan ng pagkakatuto nito at ng pagpapatakbo.
Sa gabay na ito, natututunan ang readout noise habang pinapatakbo ang mitigated na programa gamit ang TREX calibration circuit.
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()
Maghanda ng pinagsamang PEC at TREX program
Dito, naghahanda tayo ng parehong TREX at PEC task, at idinaragdag ang TREX task bilang variable sa PEC task. Tinitiyak nito na naidaragdag sa PEC mitigated na item ang lahat ng kinakailangang property para sa TREX mitigation (tulad ng measurement twirling). Isang beses sa bawat randomization sina-sample ang ini-inject na noise, kaya ang statistical error ng PEC ay kinokontrol ng bilang ng mga randomization sa halip na ng mga shot bawat randomization. Kaya, maraming randomization ang ginagamit - bawat isa ay may kaunting shot - habang pinananatiling pareho ang kabuuang bilang ng shot sa unmitigated na programa.
Idinaragdag ang TREX calibration circuit sa programa para matutunan ang readout noise habang pinapatakbo ang programa. Napakababaw ng TREX calibration circuit, kaya medyo mura ang karagdagang mga randomization nito at nagpapahintulot na mas tumpak na matutunan ang readout noise.
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)
Suriin ang shot overhead, patakbuhin ang programa, at mag-post-process
Nagbabalik ang PEC ng unbiased na pagtantiya ng expectation value, kapalit ng malaking shot overhead. Matatantiya ang kinakailangang shot overhead sa pagkalkula ng circuit gamma factor, na sumasalamin sa epekto ng natutunang noise model sa circuit. Bilang default, ini-scale ang bilang ng randomization ng PEC task ayon sa tinatayang dami ng shot overhead na kailangan para sa unbiased na pagtantiya (~). Para maiwasan ang awtomatikong scale na ito, maaari mong itakda ang parameter na scale_randomizations_by_gamma sa False. Nililimitahan ng parameter na max_sampling_overhead ang shots overhead.
Dapat mong suriin ang bilang ng shot na kailangan ng PEC para makamit ang unbiased na pagtantiya bago ang pagpapatakbo para magpasya kung abot-kaya ang halagang ito, dahil exponential itong tumataas ayon sa mga noise rate at circuit depth.
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
)
Ihambing ang mga mitigated na expectation value
Panghuli, i-plot ang expectation value ng bawat observable. Nababawi ng PEC + TREX ang eksaktong mga expectation value sa loob ng mga error bar nito, habang ang unmitigated na resulta ay may bias na mas mababa nang malayo rito.
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()