Langkau ke kandungan utama
Projek ini dalam fasa beta. Jumpa pepijat? Buat isu GitHub. Ada soalan atau idea? Tanya dalam Discussions — kami suka dengar maklum balas anda!
Bagaimana kualiti terjemahan pada halaman ini?

Cara mengira sempadan tersuai

compute_forward_bounds dan compute_backward_bounds ialah pembalut kemudahan. Kedua-duanya dibina daripada tiga bahagian yang sama, yang didedahkan oleh API awam:

  • Fungsi norma, yang membatasi satu sebutan ralat Pauli yang telah berevolusi

  • Kon cahaya, yang menyemai ke arah mana sebutan ralat dikembangkan

  • compute_bounds, yang mengulangi litar dan mengagihkan kerja bagi setiap sebutan

Memasangnya sendiri bermaksud anda boleh membatasi sesuatu yang tidak dilindungi oleh pembalut. Panduan ini menunjukkan satu kes yang berbaloi dilakukan: litar cermin, di mana sempadan belakang yang biasa terlalu longgar tanpa keperluan.

Apa yang sebenarnya dilakukan oleh pembalut​

Kedua-dua pembalut berbeza pada tiga tempat yang disenaraikan di atas:

compute_forward_boundscompute_backward_bounds
litarseperti yang diberikanremove_measure(circuit).inverse()
fungsi normatime_evolved_norm_forward, terhadap observabeltime_evolved_norm_backward, terhadap keadaan awal
kon cahayaLightCone.initialize_from_pauli, disemai daripada observabelLightCone.initialize_from_measurements, disemai daripada setiap qubit aktif
backwardsFalseTrue

Sempadan hadapan bertanya "Sejauh mana ralat ini boleh mengubah observabel yang akan saya ukur?" dan menjawabnya dengan norma spektrum bagi komutator. Sempadan belakang bertanya "Sejauh mana ralat ini boleh mengubah keadaan yang saya mulakan?" dan menjawab dengan norma nuklear, dengan mengambil keadaan itu sebagai keadaan semua-sifar pada setiap qubit aktif. Ini soalan yang berbeza, dan setiap satu ialah yang tepat bagi ralat dalam separuhnya sendiri daripada litar.

Kes litar cermin​

Litar cermin menggunakan suatu uniter dan kemudian membatalkannya: U†UU^\dagger U. Ia muncul apabila hasil ideal mesti diketahui dengan tepat (umumnya gema Loschmidt, penanda aras rawak, dan eksperimen pengesahan).

Litar cermin mempunyai sifat yang tidak dapat dieksploitasi oleh sempadan belakang generik. Oleh kerana separuh kedua ialah songsangan separuh pertama, mengembangkan ralat ke belakang menuju keadaan awal melalui get yang sama seperti mengembangkannya ke hadapan menuju observabel. Kedua-dua soalan itu bertepatan, jadi sempadan gaya hadapan yang lebih tajam adalah sah untuk arah belakang juga.

# 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

Pasang sempadan tersuai​

Resipi ini mengekalkan apa yang dilakukan oleh compute_backward_bounds terhadap litar (membuang pengukuran, menyongsangkannya, dan mengulanginya dengan backwards=True) dan menggantikan dua bahagian lain: fungsi norma hadapan, dan kon cahaya yang disemai daripada observabel dengan LightCone.initialize_from_pauli, tepat seperti compute_forward_bounds menyemai kon cahayanya sendiri. Setiap penggantian ialah satu baris.

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,
)

Berapa banyak yang diperoleh​

Membandingkan dengan sempadan belakang stok pada litar yang sama, diringkaskan dengan cara yang sama seperti dalam panduan konfigurasi: purata sempadan dan sisihan piawainya, diikuti dengan bahagian sebutan yang dibatasi oleh 0, dengan sempadan yang dikira di antaranya, dan yang kekal pada 2.0 remeh.

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%

Sempadan belakang stok berada hampir nilai remeh 2.0, yang hampir tidak membawa maklumat. Pada litar cermin, setiap ralat "dekat dengan keadaan awal" dalam erti kata yang diukur oleh sempadan itu, jadi ia tidak dapat membezakan antara sebutan. Sempadan tersuai beberapa kali lebih ketat dan membatasi banyak sebutan dengan 0.

Untuk menyemak pelaksanaan, kedua-duanya boleh dibandingkan kotak demi kotak. Bagi setiap kotak, sempadan belakang tersuai keluar sama dengan sempadan hadapan kotak yang sama itu, sehingga ralat pembundaran. Kedua-duanya membatasi ralat yang sama pada titik yang sama dalam litar, dan pada litar cermin get selepas titik itu membatal menjadi songsangan get sebelumnya, jadi kedua-dua pengiraan menilai kuantiti yang sama dari hujung yang bertentangan:

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

Nota: Kesamaan ini ialah sifat struktur cermin, bukan teknik tersebut. Pada litar yang bukan songsang-diri, penggantian yang sama masih berjalan tetapi tidak lagi mengira sempadan sah untuk arah belakang: ia menjawab soalan hadapan tentang separuh litar di mana soalan itu tidak terpakai. Gunakan ini hanya apabila separuh kedua litar anda menyongsangkan yang pertama.

Salurkan ke dalam aliran kerja selebihnya​

Hasilnya ialah kamus Bounds biasa, jadi semua yang mengikutinya tidak berubah.

Pada litar cermin juga tiada sebab untuk mengira kedua-dua sempadan bagi keseluruhan litar. Sempadan hadapan hanya diperlukan untuk kotak U†U^\dagger, dikira dari hujung, dan sempadan belakang hanya untuk kotak UU, dikira dari permulaan. Oleh itu max_num_boxes menghadkan setiap satunya kepada separuh daripada kotak bising, dan merge_bounds menggabungkan kedua-dua separuh itu.

Beginilah sempadan digunakan dalam amalan, dan ini bermakna perbandingan kotak demi kotak di atas tidak lagi tersedia: ia memerlukan kedua-dua sempadan bagi setiap kotak. Ia semakan pelaksanaan untuk panduan ini, bukan langkah aliran kerja.

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

Dengan sempadan belakang stok, overhed persampelan jauh lebih besar. merge_bounds menggunakan sempadan belakang sehingga satu titik pensuisan dalam litar dan sempadan hadapan dari situ seterusnya, memilih titik yang meminimumkan jumlah pincang. Dengan setiap sempadan dikira untuk satu separuh, titik itu jatuh di tengah litar, jadi kotak UU dilindungi oleh sempadan belakang sahaja. Sempadan belakang stok hampir 2.0 remeh di situ, yang menaikkan overhed. Yang tersuai sama ketat seperti sempadan hadapan, jadi overhed sepadan dengan overhed sempadan hadapan yang lengkap.

Itu menimbulkan soalan yang jelas mengapa perlu bersusah payah, apabila sempadan hadapan lengkap sahaja memberikan hasil yang sama. Jawapannya ialah kos. Sempadan hadapan bagi kotak awal dalam litar mengembangkan ralatnya melalui baki UU dan kemudian keseluruhan U†U^\dagger, iaitu evolusi terpanjang dan terlebar dalam litar. Operator yang telah berevolusi boleh bertambah secara eksponen dalam bilangan get bukan Clifford yang dilaluinya, dan mengira norma spektrum komutator menjadi semakin mahal secara eksponen apabila kon cahaya melebar. Dalam pendekatan separuh-separuh, tiada ralat dikembangkan melalui lebih daripada separuh litar.

Penjimatan itu oleh itu bertambah dengan kedalaman litar. Lebih penting lagi, sempadan hadapan lengkap tidak lagi boleh dikira jauh lebih awal daripada yang separuh. Apabila evolusi kotak awal melebihi evolution_max_terms atau eigval_max_qubits, sempadannya dipangkas atau beralih kepada ketaksamaan segi tiga, dan akhirnya ia terlalu mahal untuk dikira. Di mana pun titik itu terletak, pendekatan separuh-separuh hanya memerlukannya jatuh di suatu tempat dalam separuh pertama litar. Sempadan hadapan tidak perlu sampai lebih jauh daripada tengah, dan pada litar cermin, sempadan belakang bagi kotak UU mengembangkan ralatnya kembali ke permulaan melalui litar sebanyak sempadan hadapan bagi kotak U†U^\dagger yang sepadan hingga ke hujung, jadi ia kehabisan pada kedalaman yang sama.

Nota: Mengapa mengira sempadan belakang bagi kotak UU, apabila sempadan hadapan bagi kotak U†U^\dagger nampaknya patut mempunyai nombor yang sama? Kerana kotak-kotak itu bukan imej cermin bagi satu sama lain. Setiap kotak menyuntik hingarnya pada satu sisi get-getnya, dan lapisan songsang U†U^\dagger ialah kotak berasingan dengan model hingar tersendiri, jadi kotak yang mencerminkan kotak UU melihat ralatnya pada titik yang berbeza dalam litar. Oleh itu, sempadannya hanya sebahagian sepadan, dan sempadan bagi kotak UU mesti dikira untuk kotak-kotak tersebut.

Menulis sempadan tersuai yang lain​

Tiga penggantian yang sama merangkumi kes lain:

  • Norma yang berbeza. time_evolved_norm_forward menerima comm_norm_order; memasukkan apa-apa selain 2 melangkau penyelesai eigen dan mengira norma itu secara terus.

  • Kon cahaya yang berbeza. LightCone.initialize_from_pauli menyemai kon cahaya daripada sebarang Pauli, dan pembina LightCone menerima qubit yang hendak dijejak dan operasi yang menyemainya, jadi sempadan boleh dikira terhadap sebarang operator yang anda boleh ungkapkan dengan cara itu.

  • Fungsi norma yang sama sekali berbeza. compute_bounds hanya memerlukan satu callable: yang menerima Pauli dan RotationGates serta mengembalikan CommutatorBounds. Apa sahaja yang memenuhi tandatangan itu berfungsi, termasuk sempadan bentuk-tertutup khusus untuk keluarga litar anda.

Apa sahaja yang anda gantikan, anda mesti membuktikan bahawa hasilnya ialah sempadan atas yang tulen. Memperkecilkan kesan sesuatu sebutan ralat tidak menghasilkan kegagalan yang kelihatan. Ia menghasilkan nilai jangkaan yang salah secara senyap, kerana sebutan itu akan telah digugurkan daripada model hingar yang dimitigasi.