Lumaktaw sa pangunahing nilalaman
Ang proyektong ito ay nasa beta. Nakakita ng bug? Mag-bukas ng GitHub issue. May tanong o ideya? Magtanong sa Discussions — gusto naming marinig ang iyong feedback!
Gaano kaganda ang kalidad ng pagsasalin sa pahinang ito?

Paano kalkulahin ang mga custom na bound

Ang compute_forward_bounds at compute_backward_bounds ay mga convenience wrapper. Pareho silang binuo mula sa parehong tatlong piyesa, na inilalantad ng public API:

  • Isang norm function, na nagbo-bound sa isang na-evolve na Pauli error term

  • Isang light cone, na nagtatakda kung saan ine-evolve ang mga error term

  • compute_bounds, na nag-iterate sa circuit at nagpapamahagi ng trabaho kada term

Ang pag-assemble sa mga ito nang mag-isa ay nangangahulugang makakapag-bound ka ng bagay na hindi sakop ng mga wrapper. Tinatalakay ng gabay na ito ang isang kasong sulit gawin ito: isang mirror circuit, kung saan ang karaniwang backward bound ay walang saysay na maluwag.

Ang aktwal na ginagawa ng mga wrapper​

Magkaiba ang dalawang wrapper sa tatlong lugar na nakalista sa itaas:

compute_forward_boundscompute_backward_bounds
circuitgaya ng ibinigayremove_measure(circuit).inverse()
norm functiontime_evolved_norm_forward, laban sa observabletime_evolved_norm_backward, laban sa initial state
light coneLightCone.initialize_from_pauli, mula sa observableLightCone.initialize_from_measurements, mula sa bawat aktibong qubit
backwardsFalseTrue

Tinatanong ng forward bound ang "Gaano mababago ng error na ito ang observable na susukatin ko?" at sinasagot ito gamit ang spectral norm ng isang commutator. Tinatanong ng backward bound ang "Gaano mababago ng error na ito ang state na pinagmulan ko?" at sinasagot gamit ang nuclear norm, na ang state ay ang all-zero state sa bawat aktibong qubit. Magkaibang tanong sila, at bawat isa ang tama para sa mga error sa sarili nitong kalahati ng circuit.

Ang kaso ng mirror circuit​

Ang mirror circuit ay naglalapat ng isang unitary at pagkatapos ay binabawi ito: U†UU^\dagger U. Lumilitaw ang mga ito tuwing kailangang tiyak na alam ang ideal na resulta (karaniwan sa mga Loschmidt echo, randomized benchmarking, at mga eksperimento sa verification).

May katangian ang mga mirror circuit na hindi magagamit ng generic na backward bound. Dahil ang ikalawang kalahati ay ang inverse ng una, ang pag-evolve ng error pabalik sa initial state ay dumadaan sa parehong mga gate gaya ng pag-evolve nito pasulong sa observable. Nagtutugma ang dalawang tanong, kaya ang mas matalas na forward-style na bound ay valid din para sa backward na direksyon.

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

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_slc": "qiskit-addon-slc", "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 numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli
from qiskit_addon_slc.utils import generate_noise_model_paulis
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions

def trotter_ising_circuit(num_qubits, num_steps, rx_angle, rzz_angle):
"""Trotterized transverse-field Ising evolution on a 1D chain."""
circuit = QuantumCircuit(num_qubits)
for _ in range(num_steps):
circuit.rx(rx_angle, range(num_qubits))
circuit.barrier()
for start in (0, 1): # even then odd bonds
for i in range(start, num_qubits - 1, 2):
circuit.rzz(rzz_angle, i, i + 1)
circuit.barrier()
return circuit

num_qubits = 10
# The circuit of the quickstart guide...
forward_half = trotter_ising_circuit(
num_qubits, num_steps=4, rx_angle=np.pi / 16, rzz_angle=-np.pi / 3
)
# ...followed by its inverse. The ideal result is the initial state.
circuit = forward_half.compose(forward_half.inverse())

observable = Pauli("I" * num_qubits).compose("Z", [num_qubits // 2])

boxing_pass = generate_boxing_pass_manager(
inject_noise_targets="all",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
twirling_strategy="active",
remove_barriers="never",
)
boxed_circuit = boxing_pass.run(circuit)
noise_model_paulis = generate_noise_model_paulis(
find_unique_box_instructions(boxed_circuit)
)

print(f"mirror circuit: {num_qubits} qubits, depth {circuit.depth()}")
print(f"noisy layers: {len(noise_model_paulis)}")
mirror circuit: 10 qubits, depth 24
noisy layers: 4

I-assemble ang custom na bound​

Pinananatili ng recipe ang ginagawa ng compute_backward_bounds sa circuit (alisin ang mga measurement, i-invert ito, at i-iterate ito gamit ang backwards=True) at pinapalitan ang dalawa pang piyesa: ang forward norm function, at isang light cone na mula sa observable gamit ang LightCone.initialize_from_pauli, eksakto kung paano binubuo ng compute_forward_bounds ang sarili nito. Isang linya ang bawat pagpapalit.

from functools import partial

from qiskit_addon_slc.bounds import (
LightCone,
compute_bounds,
time_evolved_norm_forward,
)
from qiskit_addon_slc.utils import remove_measure

def compute_mirror_backward_bounds(
circuit,
noise_model_paulis,
/,
observable,
*,
evolution_max_terms=1_000_000,
**kwargs,
):
"""Bounds the backward direction of a mirror circuit with a forward-style bound.

Valid only for circuits whose second half inverts the first: the backward evolution then
traverses the same gates as the forward one, and reproduces the forward bounds.
"""
# As `compute_backward_bounds` does: strip measurements and invert, so that walking the circuit
# backwards from its end becomes walking the inverted circuit forwards.
circuit = remove_measure(circuit).inverse()

# The substitution: the forward norm function, bounding against the observable rather than
# against the initial state.
norm_fn = partial(
time_evolved_norm_forward,
observable=observable,
evolution_max_terms=evolution_max_terms,
)

# Seed the light cone from the observable, not from every active qubit.
light_cone = LightCone.initialize_from_pauli(circuit, observable)

return compute_bounds(
circuit,
noise_model_paulis,
light_cone,
norm_fn,
backwards=True,
**kwargs,
)

Gaano ito kalaki ang naidaragdag​

Paghahambing sa stock na backward bound sa parehong circuit, na binuod sa parehong paraan tulad sa configuration guide: ang mean na bound at ang standard deviation nito, kasunod ang bahagi ng mga term na may bound na 0, may nakalkulang bound sa pagitan, at nanatili sa trivial na 2.0.

from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
)

forward_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable
)
stock_backward = compute_backward_bounds(boxed_circuit, noise_model_paulis)
custom_backward = compute_mirror_backward_bounds(
boxed_circuit, noise_model_paulis, observable
)

def summarize(label, bounds):
"""Prints a one-line summary of a set of bounds."""
rates = np.concatenate([bound.rates for bound in bounds.values()])
# The bounds are floating-point results, so a term counts as bounded by 0, or as left at the
# trivial 2.0, when it is within rounding error of that value.
exact = np.isclose(rates, 0.0, rtol=0.0, atol=1e-12)
trivial = np.isclose(rates, 2.0, rtol=0.0, atol=1e-12)
computed = ~exact & ~trivial
print(
f"{label:18} mean {rates.mean():.4f} ± {rates.std():.3f} │ "
f"at 0: {exact.mean():4.0%} computed: {computed.mean():4.0%} "
f"at 2.0: {trivial.mean():4.0%}"
)

summarize("forward", forward_bounds)
summarize("backward (stock)", stock_backward)
summarize("backward (custom)", custom_backward)
forward mean 0.4323 ± 0.679 │ at 0: 38% computed: 61% at 2.0: 1%
backward (stock) mean 1.7218 ± 0.592 │ at 0: 0% computed: 95% at 2.0: 5%
backward (custom) mean 0.4323 ± 0.679 │ at 0: 38% computed: 61% at 2.0: 1%

Ang stock na backward bound ay malapit sa trivial na halaga na 2.0, na halos walang dalang impormasyon. Sa isang mirror circuit, bawat error ay "malapit sa initial state" sa kahulugang sinusukat ng bound na iyon, kaya hindi nito mapag-iiba ang mga term. Ang custom na bound ay ilang beses na mas mahigpit at nagbo-bound sa maraming term sa 0.

Para suriin ang implementasyon, maaaring ihambing ang dalawa kada box. Para sa bawat box, ang custom na backward bound ay lumalabas na magkapareho sa forward bound ng parehong box, hanggang sa rounding error. Parehong nagbo-bound ang dalawa sa parehong error sa parehong punto sa circuit, at sa mirror circuit, ang mga gate pagkatapos ng puntong iyon ay nagka-cancel bilang inverse ng mga gate bago nito, kaya ang dalawang pagkalkula ay sumusuri sa parehong dami mula sa magkabilang dulo:

matches = sum(
np.allclose(forward_bounds[box_id].rates, custom_backward[box_id].rates)
for box_id in forward_bounds
)
print(f"{matches} of {len(forward_bounds)} boxes match the forward bounds")

worst = max(
np.abs(forward_bounds[box_id].rates - custom_backward[box_id].rates).max()
for box_id in forward_bounds
)
print(f"largest disagreement across all terms: {worst:.2e}")
16 of 16 boxes match the forward bounds
largest disagreement across all terms: 6.00e-15

Tandaan: Ang pagkakapantay na ito ay katangian ng istruktura ng mirror, hindi ng teknik. Sa circuit na hindi self-inverting, tumatakbo pa rin ang parehong pagpapalit pero hindi na nito kinakalkula ang valid na bound para sa backward na direksyon: sinasagot nito ang forward na tanong tungkol sa kalahati ng circuit kung saan hindi nalalapat ang tanong. Gamitin lang ito kapag ang ikalawang kalahati ng circuit mo ay nag-i-invert sa una.

Ipasok ito sa natitirang bahagi ng workflow​

Ang resulta ay karaniwang Bounds dictionary, kaya walang nagbabago sa lahat ng susunod.

Sa mirror circuit, wala ring dahilan para kalkulahin ang alinmang bound para sa buong circuit. Ang forward na mga bound ay kailangan lang para sa mga box ng U†U^\dagger, na binibilang mula sa dulo, at ang backward na mga bound ay para lang sa mga box ng UU, na binibilang mula sa simula. Kaya nililimitahan ng max_num_boxes ang bawat isa sa kalahati ng mga maingay na box, at pinagsasama ng merge_bounds ang dalawang kalahati.

Ganito ginagamit ang mga bound sa praktika, at nangangahulugan itong hindi na magagamit ang paghahambing na box-por-box sa itaas: kailangan nito ang parehong bound para sa bawat box. Isa itong pagsusuri ng implementasyon para sa gabay na ito, hindi hakbang ng workflow.

from qiskit.quantum_info import PauliLindbladMap, QubitSparsePauliList
from qiskit_addon_slc.bounds import compute_local_scales, merge_bounds

# Stand-in for rates that a noise-learning experiment would measure on hardware.
rng = np.random.default_rng(42)
noise_rates = {
layer: PauliLindbladMap.from_components(
rng.random(len(terms)) * 5e-3,
QubitSparsePauliList.from_sparse_list(
terms.to_sparse_list(), terms.num_qubits
),
)
for layer, terms in noise_model_paulis.items()
}

# Compute each bound for its own half of the mirror circuit only. `forward_bounds` has an entry
# for every noisy box, which are the boxes `max_num_boxes` counts.
half = len(forward_bounds) // 2
half_forward = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable, max_num_boxes=half
)
half_stock = compute_backward_bounds(
boxed_circuit, noise_model_paulis, max_num_boxes=half
)
half_custom = compute_mirror_backward_bounds(
boxed_circuit, noise_model_paulis, observable, max_num_boxes=half
)

print(
f"each bound computed for {half} of {len(forward_bounds)} noisy boxes\n"
)
print(f"{'bounds':34} {'overhead':>12} {'residual bias':>14}")
for label, bounds in (
(
"forward + stock backward",
merge_bounds(boxed_circuit, half_forward, half_stock, noise_rates),
),
(
"forward + custom backward",
merge_bounds(boxed_circuit, half_forward, half_custom, noise_rates),
),
("forward only, whole circuit", forward_bounds),
):
_, cost, bias = compute_local_scales(
boxed_circuit, bounds, noise_rates, bias_tolerance=0.05
)
print(f"{label:34} {cost:12.4f} {bias:14.4f}")
Optimal spacetime partitioning not implemented! Just partitioning list of noisy boxes.
Optimal spacetime partitioning not implemented! Just partitioning list of noisy boxes.
each bound computed for 8 of 16 noisy boxes

bounds overhead residual bias
forward + stock backward 29848.0583 0.0498
forward + custom backward 465.4028 0.0498
forward only, whole circuit 465.4028 0.0498

Sa mga stock na backward bound, mas malaki nang malayo ang sampling overhead. Ginagamit ng merge_bounds ang backward na mga bound hanggang sa isang switch point sa circuit at ang forward na mga bound mula roon, pinipili ang puntong nagpapaliit sa kabuuang bias. Kapag ang bawat bound ay kinalkula para sa isang kalahati, ang puntong iyon ay babagsak sa gitna ng circuit, kaya ang mga box ng UU ay sakop ng backward na mga bound lang. Ang stock na backward bound ay malapit sa trivial na 2.0 doon, na nagpapataas ng overhead. Ang custom ay kasinghigpit ng magiging forward bound, kaya ang overhead ay tumutugma sa kumpletong forward na mga bound.

Dahil dito, lumilitaw ang malinaw na tanong kung bakit pa mag-aabala, kung ang kumpletong forward na mga bound lang ay nagbibigay ng parehong resulta. Ang sagot ay gastos. Ang forward bound para sa box sa unang bahagi ng circuit ay ine-evolve ang error nito sa natitirang bahagi ng UU at pagkatapos ay sa buong U†U^\dagger, na siyang pinakamahaba at pinakamalapad na evolution sa circuit. Maaaring lumaki nang exponential ang na-evolve na operator ayon sa bilang ng mga non-Clifford gate na dinadaanan nito, at ang pagkalkula ng spectral norm ng commutator ay nagiging exponentially na mas magastos habang lumalapad ang light cone. Sa half-and-half na paraan, walang error na ine-evolve sa higit sa kalahati ng circuit.

Kaya lumalaki ang natitipid kasabay ng lalim ng circuit. Mas mahalaga, ang kumpletong forward na mga bound ay hindi na makakalkula nang mas maaga kaysa sa mga halfway. Kapag ang mga evolution ng mga maagang box ay lumampas sa evolution_max_terms o eigval_max_qubits, ang mga bound nila ay pinuputol o bumabalik sa triangle inequality, at sa huli ay masyado nang magastos kalkulahin. Saanman ang puntong iyon, ang half-and-half na paraan ay nangangailangan lang na mahulog ito sa isang lugar sa unang kalahati ng circuit. Ang mga forward na bound ay hindi kailangang lumampas sa gitna, at sa mirror circuit, ang backward na mga bound para sa mga box ng UU ay ine-evolve ang mga error nila pabalik sa simula sa kasing-daming bahagi ng circuit gaya ng ginagawa ng mga forward na bound para sa katugmang mga box ng U†U^\dagger patungo sa dulo, kaya sila ay nauubusan sa parehong lalim.

Tandaan: Bakit kakalkulahin ang backward na mga bound para sa mga box ng UU, kung ang forward na mga bound ng mga box ng U†U^\dagger ay mukhang dapat may parehong mga numero? Dahil ang mga box ay hindi mirror image ng isa't isa. Bawat box ay nag-i-inject ng noise nito sa isang panig ng mga gate nito, at ang mga inverse layer ng U†U^\dagger ay hiwalay na mga box na may sarili nilang noise model, kaya ang box na kamukha ng isang box ng UU ay nakikita ang mga error nito sa ibang punto sa circuit. Samakatuwid, bahagya lang ang pagtutugma ng mga bound nila, at ang mga bound para sa mga box ng UU ay kailangang kalkulahin para sa mga box na iyon.

Pagsulat ng iba pang custom na bound​

Sakop ng parehong tatlong pagpapalit ang iba pang kaso:

  • Ibang norm. Tumatanggap ang time_evolved_norm_forward ng comm_norm_order; ang pagpasa ng anumang bukod sa 2 ay nilalaktawan ang eigensolver at direktang kinakalkula ang norm na iyon.

  • Ibang light cone. Ang LightCone.initialize_from_pauli ay nagtatakda ng light cone mula sa anumang Pauli, at ang constructor ng LightCone ay tumatanggap ng mga qubit na susubaybayan at ang mga operasyong nagtatakda rito, kaya maaaring kalkulahin ang bound laban sa anumang operator na maipapahayag mo sa ganitong paraan.

  • Isang ganap na ibang norm function. Ang compute_bounds ay nangangailangan lang ng callable: na tumatanggap ng Pauli at RotationGates at nagbabalik ng CommutatorBounds. Anumang tumutupad sa signature na iyon ay gumagana, kasama ang closed-form na bound na partikular sa pamilya ng circuit mo.

Anuman ang ipalit mo, kailangan mong patunayan na ang resulta ay tunay na upper bound. Ang pagmamaliit sa epekto ng isang error term ay hindi nagdudulot ng nakikitang pagkabigo. Nagbubunga ito ng expectation value na tahimik na mali, dahil ang term ay maaalis sa mitigated na noise model.