Pagbutihin ang expectation values gamit ang propagated noise absorption (PNA)
Tinatayang gamit: 10 minuto sa isang Heron processor (TANDAAN: Isa lamang itong tantiya. Maaaring mag-iba ang iyong runtime.)
Mga learning outcome
-
Ano ang propagated noise absorption (PNA), at kung paano nito pinapagaan ang two-qubit gate noise sa pamamagitan ng pag-absorb ng natutunang inverse noise channel sa nasusukat na observable
-
Paano gamitin ang
samplomaticupang i-box at i-annotate ang circuit layers para sa twirling, basis changes, at noise injection -
Paano matuto ng layer noise gamit ang
[NoiseLearnerV3](/guides/directed-execution-model#noiselearnerv3) at i-propagate ito sa isang noise-mitigating observable gamit angqiskit-addon-pna -
Paano mag-sample ng randomized circuits gamit ang
QuantumProgramatExecutorclasses saqiskit-ibm-runtime, at pagsamahin ang PNA sa TREX at post-selection
Prerequisites
-
Ang Qiskit patterns workflow
-
Paggamit ng Estimator primitive upang kalkulahin ang expectation values ng isang observable
-
Mga error mitigation techniques tulad ng Pauli twirling at TREX, na tinatalakay sa Combine error mitigation options with the Estimator primitive
Background
Sa tutorial na ito, ipinapakita natin kung paano gamitin ang mga advanced na error-mitigation tools sa Qiskit upang mapabuti ang expectation value estimation sa noisy quantum experiments.
Ano ang propagated noise absorption (PNA)?
Ang propagated noise absorption ay isang teknik para pagaanin ang gate errors sa pamamagitan ng pag-propagate ng observable sa pamamagitan ng inverse noise channel na nakaaapekto sa two-qubit gates, na nagreresulta sa isang noise-mitigating observable.
Maaari nating gamitin ang quantum error mitigation upang kumuha ng kapaki-pakinabang na expectation values mula sa noisy quantum hardware nang hindi kinakailangan ng full fault tolerance. Ang PNA ay partikular na nakatuon sa pag-absorb ng mga epekto ng noise sa observable mismo sa halip na baguhin ang mga circuit operations.
Ang bawat noisy gate sa isang quantum circuit ay maaaring i-model bilang isang ideal gate na sinusundan ng isang noise channel. Natututo o kina-characterize ng PNA ang mga noise channel na ito at tinutukoy ang kanilang mga inverse. Sa halip na iturok ang inverse operations sa hardware execution (na karaniwang hindi feasible), pina-propagate ng PNA ang inverse noise channels pasulong sa buong circuit at inilalapat ang mga ito sa observable. Ang prosesong ito ay nagbabago sa observable patungo sa isang bagong operator , kung saan ang pagsukat sa sa noisy circuit ay nagbibigay ng parehong expectation value gaya ng pagsukat sa sa isang ideal, noise-free circuit.
Maaari itong ibuod bilang:
- I-model ang bawat noisy gate bilang isang ideal operation na sinusundan ng isang noise channel .
- Alamin o tantiyahin ang bawat gamit ang mga noise characterization tools.
- Tukuyin at i-propagate ang inverse noise maps pasulong sa buong circuit gamit ang Pauli transfer techniques.
- I-absorb ang mga inverse na ito sa observable, na nagreresulta sa isang noise-mitigated operator .
Kapag inilarawan ang noise bilang isang Pauli channel (o mas pangkalahatan bilang isang sparse Pauli-Lindblad channel), ang propagation na ito ay maisasagawa nang episyente gamit ang Pauli propagation. Nagbibigay ang Pauli propagation ng isang framework para sa pag-approximate kung paano nagbabago ang inverse noise channels habang dumaraan sila sa mga layer ng Clifford at non-Clifford operations, habang kinokontrol ang computational complexity.
Sa pamamagitan ng paglipat ng mitigation sa observable, iniiwasan ng PNA ang malaking sampling overhead na kung hindi man ay manggagaling sa pagsisingit ng physical correction operations sa circuit. Sa halip, ang orihinal na noisy circuit ang isasagawa, habang ang observable ay binabago patungo sa isang bagong operator na ang expectation value ay kumakansela sa mga epekto ng noise.
Ang PNA workflow
Ang proseso ay maaaring maintindihan sa pamamagitan ng sumusunod na mga konseptwal na yugto. Ang unang schematic ay naglalarawan ng isang karaniwang noisy na eksperimento.
Kung matututunan natin ang noise model, maaari nating i-apply ang kabaligtaran nito at kanselahin ang noise.
Sa halip na i-implement ang inverse noise channel sa pamamagitan ng pag-sample nito sa QPU tulad sa probabilistic error cancellation (PEC), inilalapat natin ito nang classical sa nasukat na observable gamit ang Pauli propagation. Ang resultang observable ay epektibong nagpapagaan sa natutunang gate noise kapag sinukat.

Modular na error mitigation gamit ang Samplomatic at Executor
Ang PNA approach ay binuo batay sa isang modular error-mitigation architecture sa Qiskit. Ginagamit ng architecture na ito ang samplomatic library kasama ng QuantumProgram at Executor classes (idinagdag sa Qiskit Runtime sa qiskit-ibm-runtime v0.47.0) upang gawing composable at reusable ang mga teknik tulad ng propagated noise absorption at Pauli twirling sa iba't ibang eksperimento.
Sa halip na i-embed ang mitigation logic sa loob mismo ng circuit definition, ang mitigation ay ipinapahayag nang declarative sa pamamagitan ng samplomatic annotations at pinamamahalaan nang programmatic sa pamamagitan ng Executor, na kumokontrol kung paano nabubuo, naisasagawa, at napo-post-process ang mga randomized circuit.
Sa tutorial na ito, ipinapatupad natin ang isang Qiskit pattern upang ipakita kung paano maaaring i-propagate ng PNA ang inverse Pauli noise channels at baguhin ang observable ayon dito, upang mapahusay ang estimation ng expectation values sa noisy na mga QPU.
Overview ng workflow
-
Hakbang 1: I-map sa quantum na problema
- Gumawa ng mirrored Trotterized kicked-Ising model at isang target na observable.
-
Hakbang 2: I-characterize at i-propagate ang noise
-
Gamitin ang
samplomaticupang tukuyin at i-annotate ang mga natatanging two-qubit layers at measurements sa circuit. -
Alamin ang noise na nakakaapekto sa bawat natatanging layer gamit ang
NoiseLearnerV3. -
I-map ang bawat
InjectNoiseannotation sa kaukulang natutunang noise model nito. -
Gamitin ang
qiskit-addon-pnaupang i-propagate ang inverse noise channels pasulong sa circuit at isama ang mga ito sa target na observable.
-
-
Hakbang 3: Isagawa ang mga quantum na eksperimento
- Tukuyin ang isang
QuantumProgramupang tumukoy ng randomized sampling sa pamamagitan ngsamplexat isagawa ang mga eksperimento sa backend gamit angExecutor.
- Tukuyin ang isang
-
Hakbang 4: Buuin muli at suriin ang mga resulta
- Ihambing ang mga mitigation strategy (PNA, PNA+TREX, PNA+PS, PNA+PS+TREX) at i-visualize ang pagpapahusay kumpara sa mga hindi na-mitigate na resulta.
Requirements
Bago simulan ang tutorial na ito, siguraduhing naka-install na ang mga sumusunod:
-
Qiskit SDK v2.2 o mas bago, na may suporta sa visualization
-
Qiskit Runtime v0.47 o mas bago (
pip install qiskit-ibm-runtime) -
Samplomatic v0.13 o mas bago (
pip install samplomatic) -
PNA Qiskit addon (
pip install qiskit-addon-pna) -
Qiskit addon utils (
pip install qiskit-addon-utils)
Setup
# 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
Halimbawa ng maliitang simulator
Pinapagaan ng PNA ang pisikal na two-qubit gate noise ng isang partikular na quantum processor. Ang workflow ay umaasa sa dalawang hardware service na walang makabuluhang katumbas sa isang ideal na simulator:
-
Eksperimental na kinakarakter ng
NoiseLearnerV3ang sparse Pauli-Lindblad noise channel na nakadikit sa bawat natatanging two-qubit layer ng transpiled circuit. Sa isang noiseless na simulator, walang noise na matututunan, at ang propagated inverse channel ay magiging identity. -
Sinasampol ng
Executorprimitive ang mga twirled, randomized na circuit na binuo ngsamplomaticsa isang backend.
Sa prinsipyo, maaari kang gumamit ng isang synthetic noise model bilang kapalit. Halimbawa, maaari kang mag-attach ng PauliLindbladError instructions gamit ang Qiskit Aer at ipasa ang resultang noisy na circuit nang direkta sa generate_noise_mitigating_observable. Gayunpaman, ito ay bumabalido lamang sa classical bookkeeping laban sa noise na ikaw mismo ang nag-inject, at ito ay nagtatakip sa layunin ng teknik. Dahil dito, hindi natin isasagawa ang simulator example at direktang ipapakita ang buong PNA workflow sa hardware, na may bawat hakbang ng Qiskit pattern na inilalarawan sa ibaba.
Halimbawa ng malakihang hardware
Ngayon ay isasagawa natin ang kumpletong PNA workflow sa isang 30-site na kicked Ising model na isinasagawa sa IBM Quantum® hardware, sinusunod ang apat na hakbang ng Qiskit pattern.
Hakbang 1: I-map sa isang quantum na problema
Bumuo ng mirrored Trotter circuit at observable
Para sa eksperimentong ito, pag-aaralan natin ang time dynamics ng isang 30-site na kicked Ising model sa isang 1D spin chain. Ang Hamiltonian na isinasaalang-alang ay:
,
kung saan inilalarawan ng ang coupling ng nearest-neighbor spins, , at ang global transverse field, , ay itinakda sa . Kung mas malayo ang mula sa isang Clifford angle (ibig sabihin, ), mas mahirap na i-propagate ang mga anti-noise generator sa circuit.
Para sa pagpili ng observable, isasaalang-alang natin ang average single-site magnetization, , kung saan ang ay ang bilang ng mga site.
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)

Hakbang 2: I-optimize ang problema para sa pagpapatupad sa hardware
Ang susunod na hakbang ay ihanda ang ating mirrored Trotter circuit para sa pagpapatakbo sa tunay na IBM Quantum hardware. Ang pagpapatakbo sa isang QPU ay nangangailangan ng higit pa sa pagbuo lamang ng abstract circuit, dahil kailangan nating i-optimize ito upang:
-
Sumusunod ito sa native gate set at connectivity ng backend. Ina-map ng transpilation ang logical circuit patungo sa isang ISA circuit na sinusuportahan ng target backend. Tinitiyak nito na ang bawat gate at qubit interaction ay pisikal na maisasakatuparan.
-
Makakarakter natin ang noise sa antas ng circuit layers. Umaasa ang PNA sa pagtuto at pag-propagate ng inverse noise channels. Upang gawin ito nang episyente, hinahati natin ang transpiled circuit sa mga natatanging two-qubit na "boxed" layer. Ang mga box na ito ay nagbibigay-daan sa atin na iugnay ang bawat layer ng circuit sa sarili nitong natutunang noise model.
-
Makakapagpakain tayo ng makatotohanang noise model sa PNA. Sa sandaling na-box na ang circuit, ginagamit natin ang serbisyong
NoiseLearnerV3upang eksperimental na matutunan ang mga Pauli noise channel na nakakaapekto sa bawat natatanging two-qubit layer. Ang mga natutunang modelong ito ay pagkatapos ay ikinokonekta pabalik sa circuit sa pamamagitan ng mga annotation ng Samplomatic.
Sa kabuuan, ang hakbang na ito ang tumutulay sa agwat sa pagitan ng isang idealized mirrored circuit at isang hardware-ready circuit na may natutunang mga noise model. Sa setup na ito, maaaring i-propagate ng PNA ang inverse noise channels sa buong circuit at i-adjust ang observable ayon dito.
Kumonekta sa backend at i-transpile patungo sa isang ISA circuit
Una nating i-initialize ang IBM Quantum Compute Service at pumili ng backend. Mag-authenticate gamit ang sarili mong account sa pamamagitan ng pagsunod sa instructions to save your credentials, pagkatapos nito awtomatikong makukuha ito ng 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
Susunod, pipili tayo ng isang connected chain ng mga qubit sa backend at i-transpile ang circuit dito.
Gamit ang find_qubit_chain helper na tinukoy sa Setup section, pinipili natin ang isang linya ng num_qubits na konektadong pisikal na qubit. Pagkatapos ay nagtra-transpile tayo gamit ang optimization_level=0 at ipinipin ang chain na ito bilang initial_layout, na eksaktong pinapanatili ang two-qubit-gate layer structure ng mirrored circuit. Ang structure na iyon ang inaasahan ng boxing at noise-learning steps, kaya dapat iwasan dito ang mas mataas na optimization level (na magkakansela sa mirrored gates).
# 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)
I-twirl ang mga two-qubit gate layer at sukat, at hanapin ang mga natatanging layer
Ginagamit natin ang samplomatic upang i-box ang circuit at tukuyin ang mga natatanging two-qubit layer. Ang box ay isang construct na pinagsasama-sama ang mga instruction upang ang mga tiyak na intent o annotation ay mailapat sa parehong paraan sa lahat ng gate sa loob ng parehong box.
Dito, tinatawag natin ang method na generate_boxing_pass_manager, na lampas pa sa simpleng pagtukoy ng two-qubit layers. Isinasagawa nito ang ilang mahalagang gawain:
-
Ginugrupo ang lahat ng two-qubit layers sa circuit,
-
Inilalapat ang mga
TwirlatChangeBasisannotation sa mga layer na iyon, -
Ginugrupo ang mga measurement operation sa sarili nilang boxed sections, at
-
Inilalapat ang
InjectNoiseannotation sa bawat two-qubit layer.
Tinutukoy ng mga annotation na ito kung paano hinahawakan ang noise, basis changes, at twirling sa buong circuit. Itinatatag din nila ang structure na gagamitin mamaya para sa pagtuto at pagpapagaan ng noise.
Ang mga pangunahing configuration option ay:
-
enable_gates/enable_measures: True: I-box ang lahat ng two-qubit gate layers at terminal measurements. Ang single-qubit gates ay left-dressed sa loob ng mga box. -
measure_annotations: all: Isama ang mgaTwirlatChangeBasisannotation sa measurement box. -
twirling_strategy: active: I-twirl ang lahat ng active na qubit sa bawat box na naglalaman ng entangling gates. -
inject_noise_targets: gates: Magdagdag ngInjectNoiseannotations sa lahat ngTwirl-annotated na box na naglalaman ng entangling gates. -
inject_noise_strategy: uniform_modification: I-scale ang lahat ng noise layers nang pantay-pantay sa buong circuit.
# 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)
Bumuo ng template circuit at samplex, na tumutukoy kung paano sasampolin ang circuit.
Dito, magdaragdag din tayo ng spectator at post-selection measurements, na kailangan upang magsagawa ng post-selection sa mga sample na output mula sa 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)

Alamin ang noise gamit ang NoiseLearnerV3
Bago natin ilapat ang PNA para sa error mitigation, kailangan muna nating ikarakter ang noise na kumikilos sa bawat natatanging two-qubit layer at ang measurement layer sa ating circuit. Upang gawin ito, ginagamit natin ang NoiseLearnerV3 program upang eksperimental na matutunan ang mga noise model para sa bawat layer na natukoy na kanina. Ang learner ay nagsasagawa ng mga eksperimentong tulad ng benchmarking na nagtataya sa noise channel na nakakaapekto sa bawat layer at nagbabalik ng result object na naglalaman ng natutunang modelo.
Magsisimula tayo sa pagtukoy ng mga natatanging layer sa ating circuit gamit ang find_unique_box_instructions mula sa samplomatic. Tinitiyak nito na iisang beses lamang natin matututunan ang noise para sa bawat natatanging uri ng layer, na binabawasan ang bilang ng mga eksperimento at kabuuang gastos sa shot. Ang resultang listahan ng mga layer ay ipinapasa sa noise learner.
Mayroong ilang mahalagang parameter na kumokontrol kung paano matututunan ang noise:
-
num_randomizations: Bilang ng random circuit na ginagamit sa bawat learning configuration. -
shots_per_randomization: Bilang ng shot na kinukuha sa bawat random learning circuit. -
layer_pair_depths: Ang circuit depths (sinusukat sa bilang ng pairs) na gagamitin sa mga learning experiment. -
post_selection: Nagbibigay-daan sa edge-based post-selection gamit ang mgarxgate upang ilapat ang post-measurement pulses.
# 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()
I-visualize ang mga natutunang noise rate
Matapos matutunan ang mga noise model, maaari nating suriin ang distribution ng mga natutunang error rate para sa parehong one-qubit at two-qubit operations. Ang code sa ibaba ay kumukuha ng mga Pauli-Lindblad representation mula sa natutunang noise results at kinokolekta ang kaukulang mga noise rate.
Para sa bawat natutunang layer:
- Kina-convert natin ang noise model sa isang sparse na listahan ng
(pstr, qubits, rate)tuples, kung saan angpstray ang Pauli string na kumikilos sa ibinigay na mga qubit at angrateay ang kaugnay na error rate. - Hinahati natin ang mga rate sa one-qubit (
len(pstr) == 1) at two-qubit (len(pstr) == 2) na mga termino. - Ang mga listahan ng rate ay pinagsu-sort, at kinakalkula ang kanilang median values.
Ipinapakita natin sa graph ang distribution ng one-qubit (pula) at two-qubit (asul) na noise rate sa logarithmic scale, na may kanilang median values na minarkahan ng vertical lines, upang maihambing natin ang relative magnitude ng natutunang Pauli-Lindblad generators. Ang pagkakasunud-sunod ng one- at two-qubit rates ay depende sa device at sa mga tiyak na layer na kinakarakter; sa run na ito, ang single-qubit (weight-1) na mga generator ang may mas malaking median rate.
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>
Iugnay ang mga circuit box sa natutunang noise
Kapag nakuha na natin ang mga noise model para sa bawat natatanging two-qubit layer, kailangan nating ikonekta ang mga ito sa kaukulang mga InjectNoise annotation sa loob ng boxed circuit.
Ang InjectNoise directive ay isang samplomatic annotation na gumagamit ng single-qubit dressers upang mag-inject ng noise sa circuit sa isang kontrolado at configurable na paraan. Nagbibigay-daan ito sa modular noise modeling sa iba't ibang layer.
Kasama sa bawat InjectNoise annotation:
InjectNoise.ref- isang natatanging identifier para sa annotation. Ginagamit ito ngsamplexobject upang tama itong maitalaga sa kaukulang noise model.InjectNoise.modifier_ref(opsyonal) - isang pangalawang reference na nagbibigay-daan sa pag-scale ng itinalagang noise model gamit ang isang multiplicative factor.
Sa hakbang na ito, bumubuo tayo ng isang mapping mula sa bawat InjectNoise.ref patungo sa kaukulang natutunang noise model (PauliLindbladMap). Tinitiyak ng mapping na ito na ang bawat entangling gate layer sa circuit ay ipinares sa naaangkop na noise model, upang tumpak na mailapat ang mga epekto ng noise sa panahon ng sampling at kasunod na mga hakbang sa noise mitigation.
# 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()
)
I-propagate ang observable sa pamamagitan ng natutunang anti-noise
Tulad ng tinalakay sa itaas, ginagawa ito sa dalawang hakbang. Una, ipina-propagate natin ang isang anti-noise generator patungo sa dulo ng circuit. Pagkatapos nito, ipina-propagate natin ang observable sa evolved generator na iyon. Ang prosesong ito ay inuulit para sa bawat anti-noise generator sa circuit. Sa implementasyong ito, ang bawat generator sa isang naibigay na layer ay ipina-propagate patungo sa dulo ng circuit nang parallel. Bukod dito, ginagamit ang Python multiprocessing upang isagawa nang parallel ang forward-propagation ng anti-noise pati na rin ang back-propagation ng observable. Pinipigilan nito ang pag-ipon ng evolved generators sa memory at pinapahusay din ang paggamit ng compute resources.
Kapag pinapatakbo ang PNA, kailangan mo palaging magbigay ng isang noisy circuit at observable. Kung ang iyong noisy circuit ay isang boxed circuit na may InjectNoise annotations, kailangan mong ibigay ang mapping na nabuo natin sa hakbang sa itaas. Maaari ring magpasa ng isang non-boxed circuit na naglalaman ng PauliLindbladError instructions mula sa qiskit-aer. Sa kasong iyon, hindi na kailangang ibigay ang refs_to_noise_models. Bukod sa mga pangunahing input, isaalang-alang din ang mga sumusunod:
-
max_err_terms: Ang bilang ng mga termino na papanatilihin sa bawat anti-noise generator habang ito ay forward-propagated. Ang pagpapahintulot dito na maging mas malaki ay karaniwang nagpapataas ng accuracy, ngunit hindi garantisadong monotonic ang ugaling ito. -
max_obs_terms: Ang bilang ng mga termino na papanatilihin sa noise-mitigating observable, , habang ito ay back-propagated sa evolved anti-noise. Ang mas malalaking value ay karaniwang nagpapataas ng accuracy, ngunit hindi garantisadong monotonic ito. -
num_processes: Ang bilang ng cores na ilalaan sa proseso. Tandaan, ang mga generator ay forward-propagated at inilalapat sa observable nang parallel. -
search_step: Ginagamit ng back-propagation step ang isang greedy method upang aproksimadong i-conjugate ang dalawang operator sa Pauli basis. Ang method na ito ay maaaring pabilisin sa pamamagitan ng pagtaas ngsearch_step. Tingnan angpauli-propdocs para sa karagdagang impormasyon. -
num_to_measure: Bagama't hindi ito isang input sagenerate_noise_mitigating_observable, ginagamit natin ito upang kontrolin kung ilang termino mula sa ang gusto talaga nating sukatin. Dito, sinusukat lamang natin ang top 30 terms, na siyang orihinal na mga termino sa ating observable. Ang mga termino ay na-re-scale na ngayon upang ang pagsukat sa kanila ay may epekto ng pagpapagaan sa natutunang gate noise. Bagama't 30 termino lamang mula sa ang ating sinusukat, madalas na kapaki-pakinabang pa rin na hayaang lumaki ito, dahil pinapataas nito ang precision ng scaling factors ng mga leading terms.
# 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>]
I-transform ang mga measurement basis patungo sa canonical form
Susunod, hinahanap natin ang isang minimal set ng mga basis na susukatin upang ganap nating masaklaw ang bawat Pauli term sa nasukat na observable (maaaring sukatin nang sabay-sabay ang maraming observable kung sila ay commute qubit-wise). Dahil sinusukat lamang natin ang mga termino sa ating orihinal na observable, na siyang kabuuan ng lahat ng single-Z Paulis, iisang basis lamang ang kailangan: ang all-Z basis.
Bukod sa paghahanap ng set ng Pauli measurement bases, kailangan nating i-map ang mga Pauli term na ito sa canonical form na inaasahan ng Executor. Para sa karagdagang impormasyon tungkol sa canonical qubit ordering, bisitahin ang Samplomatic docs.
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
]
Hakbang 3: Isagawa ang mga quantum na eksperimento
I-specify kung paano mag-sample sa QuantumProgram
Itatakda na natin ngayon ang QuantumProgram, na siyang sentral na container para sa lahat ng circuit at sampling configuration na isasagawa ng Executor. Tinutukoy ng object na ito kung paano nabubuo, nagagrupo, at naisasagawa ang mga randomized circuit instance upang makabuo ng mga resulta ng measurement na ginagamit sa PNA.
Ang isang QuantumProgram ay maaaring maglaman ng maraming item, na ang bawat isa ay binubuo ng isang template circuit at kaukulang samplex object na tumutukoy kung paano inilalapat ang mga randomization. Ang abstraction na ito ay nagbibigay-daan sa Executor na hawakan ang buong workflow bilang isang solo, modular na program — mula sa randomized circuit generation hanggang sa shot collection at aggregation.
Sa hakbang na ito, gumagawa tayo ng isang QuantumProgram na nagpapatakbo ng ating PNA experiment gamit ang template circuit at samplex na binuo natin kanina. Kasama sa configuration ang mga sumusunod na elemento:
-
template_circuit: Ang circuit na naglalaman ng lahat ng gates na kailangan upang ipatupad ang lahat ng ninanais na randomization (mula sa twirling randomizations, parameters, at iba pa). -
samplex: Isang object na tumutukoy ng probability distribution sa lahat ng posibleng circuit randomizations kung saan mag-sasample. -
samplex_arguments: Bindings na kailangan upang ganap na matukoy angsamplex-
basis_changes: Dito natin tinutukoy ang isang set ng mga basis na susukatin na sasaklaw sa lahat ng Pauli terms sa nasukat na observable. -
noise_scales.ref: Itinatakda natin ang scale ng bawat noise layer sa0.0upang maiwasan ang anumang karagdagang noise na ma-inject sa ating mga sample. -
pauli_lindblad_maps: Kinakailangan kung ipapasa angnoise_scales. Ito ay nag-mamap lamang ng noise layers sa kaugnay na noise model.
-
-
shape: Isang shape tuple upang palawigin ang implicit na shape na tinutukoy ngsamplex_arguments. Ang mga non-trivial axes na ipinapakilala ng extension na ito ay nag-eenumerate ng mga randomization.
# 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,),
)
I-sample ang circuit gamit ang Executor
Ngayong natukoy na natin ang ating QuantumProgram, simple na lamang ang pagsasagawa ng eksperimento. Iina-instantiate lamang natin ang Executor object, ibinibigay dito ang backend, at pinapatakbo ang program.
# Execute (sample) the circuit
executor = Executor(backend)
job_exec = executor.run(program)
exec_results = job_exec.result()
Hakbang 4: Buuin muli at suriin ang mga resulta
Upang makalkula ang isang error-mitigated expectation value, ginagawa natin ang mga sumusunod:
-
Kalkulahin ang TREX scaling factors batay sa natutunang noise na nakakaapekto sa mga measurement,
-
Bumuo ng mask para sa pagpapanatili lamang ng mga post-selected sample, at
-
Gamitin ang function na
executor_expectation_valuesmula saqiskit-addon-utilsupang pagsamahin ang lahat ng data patungo sa isang error-mitigated expectation value.
# 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,
)
Ihambing ang mga estratehiya sa pagpapagaan: PNA, PNA+TREX, PNA+PS, PNA+PS+TREX
Kinakalkula at ina-visualize natin ang mga expectation value para sa ilang mitigation variant gamit ang mga resulta ng 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()
Ipinapakita ng mga resulta ang cumulative na benepisyo ng pagsasama-sama ng iba't ibang error-mitigation technique. Ang plain PNA approach ay naibabalik na ang expectation value nang malapit sa ideal benchmark, na nagpapahiwatig na ang pag-propagate ng inverse noise channels sa observable ay epektibong bumabawi sa two-qubit gate errors.
-
Ang pagdaragdag ng TREX reweighting (PNA+TREX) ay bahagyang nagpapahusay sa estimate sa pamamagitan ng pagwawasto sa sampling imbalance sa mga randomized circuit.
-
Ang post-selection (PNA+PS) ay nagbibigay ng mas kapansin-pansing pagbuti sa pamamagitan ng pag-filter sa mga hindi pare-parehong measurement outcome na malamang na dulot ng mga natitirang error.
-
Sa huli, ang pagsasama ng dalawa (PNA+PS+TREX) ay nagbibigay ng pinaka-tumpak na resulta, halos katumbas ng ideal value, na nagpapakita kung paano pinagtitibay ng mga mitigation strategy na ito ang isa't isa.
Sa kabuuan, itinatampok ng paghahambing na ito na ang PNA ay nagsisilbing matibay na pundasyon para sa noise-aware na expectation value estimation, habang ang TREX at post-selection ay nag-aalok ng mga karagdagang pagpapahusay na nagpupuno sa isa't isa para sa dagdag na katumpakan.
Mga susunod na hakbang
Kung naging interesante sa iyo ang gawaing ito, maaaring interesado ka rin sa mga sumusunod na materyal:
