Cara menyetel opsi konfigurasi
Perhitungan bound pada prinsipnya eksak, tapi dalam praktiknya hanya perkiraan: mengevolusikan suku error Pauli melalui sebuah circuit menghasilkan operator dengan suku yang jumlahnya eksponensial, dan mencari spectral norm dari commutator hasilnya juga mahal. Opsi-opsi di bawah ini mengatur biaya tersebut. Opsinya terbagi menjadi tiga kelompok:
-
Opsi Accuracy mengubah nilai numerik bound, menukar ketatnya bound dengan biaya komputasi.
-
Scope menentukan bound mana saja yang dihitung.
-
Opsi Execution mengubah cara komputasi dijalankan (bukan apa yang dihitung).
| Opsi | Default | Mengatur | Efek pada hasil |
|---|---|---|---|
| Accuracy | |||
evolution_max_terms | 1_000_000 | Berapa banyak suku operator yang bertahan selama evolusi | Lebih rendah: lebih murah, bound lebih longgar |
atol_simplify | 1e-8 | Suku commutator mana yang dibuang karena dianggap sepele | Lebih tinggi: lebih murah, bound lebih longgar |
eigval_max_qubits | 14 | Selebar apa commutator boleh agar spectral norm-nya dihitung | Lebih rendah: lebih murah, bound lebih longgar |
atol_eigenvalue | 1e-10 | Kapan eigensolver iteratif dianggap sudah konvergen | Lebih tinggi: lebih murah, bound kurang akurat |
| Scope | |||
max_num_boxes | None | Berapa banyak box yang dihitung bound-nya | Box di luar batas tetap memakai bound trivial 2.0 |
| Execution | |||
timeout | None | Anggaran waktu (wall-clock) untuk seluruh komputasi | Pekerjaan yang tersisa tetap memakai bound trivial 2.0 |
num_processes | 1 | Berapa banyak proses worker yang menghitung bound secara paralel | Tidak ada efek langsung |
Sebuah bound hanya berguna kalau valid: bound tidak boleh meremehkan pengaruh suku error terhadap
observable. Melonggarkan opsi mana pun selain atol_eigenvalue hanya bisa membuat bound
lebih besar (lebih konservatif), tidak pernah lebih kecil. Jadi, harga dari pengaturan yang longgar adalah sampling
overhead yang lebih besar, bukan expectation value yang salah - atol_eigenvalue adalah pengecualiannya.
Definisi circuit
Eksperimen berikut memakai circuit Ising hasil Trotterisasi dan observable yang sama dengan panduan quickstart. Pada ukuran ini, setiap opsi punya efek yang terlihat, sementara setiap komputasi masih selesai dalam kurang dari satu detik.
# 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
circuit = trotter_ising_circuit(
num_qubits, num_steps=4, rx_angle=np.pi / 16, rzz_angle=-np.pi / 3
)
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"noisy layers: {len(noise_model_paulis)}")
print(
f"Pauli error terms: {sum(len(p) for p in noise_model_paulis.values())}"
)
noisy layers: 2
Pauli error terms: 198
Sebuah helper kecil merangkum setiap set bound dalam satu baris, jadi efek sebuah opsi terlihat sebagai beberapa angka, bukan tumpukan array.
Setiap bound berada di antara 0 dan bound trivial 2.0. Ringkasannya melaporkan rata-rata bound dan standar deviasinya, diikuti pembagian suku ke dalam kelompok-kelompok ini:
- Suku yang di-bound tepat 0 karena commute dengan observable yang sudah dievolusikan
- Suku dengan bound hasil hitung di antara 0 dan 2.0
- Suku yang dibiarkan di nilai trivial 2.0
Perhatikan bahwa rata-rata yang lebih rendah berarti lebih ketat, dan lebih ketat berarti sampling overhead yang lebih kecil nantinya.
Sweep atas satu opsi menandai default opsi tersebut dengan *:
import inspect
import time
from qiskit_addon_slc.bounds import compute_forward_bounds
def summarize(rates, label, elapsed):
"""Prints a one-line summary of a set of bounds."""
# 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:28} 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%} │ "
f"{elapsed:4.2f}s"
)
def forward(label, **kwargs):
"""Computes the forward bounds, summarizes them and returns all their rates."""
start = time.perf_counter()
bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable, **kwargs
)
elapsed = time.perf_counter() - start
rates = np.concatenate([bound.rates for bound in bounds.values()])
summarize(rates, label, elapsed)
return rates
def sweep(option, values):
"""Computes the forward bounds for each value of one option, marking the default with `*`."""
default = (
inspect.signature(compute_forward_bounds).parameters[option].default
)
for value in values:
marker = "*" if value == default else " "
forward(f"{marker} {option}={value:g}", **{option: value})
baseline = forward(" defaults")
defaults mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.29s
Akurasi
evolution_max_terms
Mengevolusikan suku error Pauli melalui gate non-Clifford mengubahnya menjadi jumlah dari beberapa Pauli, dan jumlah itu
bertambah seiring kedalaman circuit. evolution_max_terms membatasi berapa banyak suku yang dipertahankan. 1-norm dari
semua yang dibuang ditambahkan ke bound, jadi tidak ada yang hilang diam-diam.
Sweep berjalan dari yang lebih ketat daripada default sampai pemotongan ekstrem. Menurunkan batas ini membuat komputasi lebih murah dan bound lebih longgar.
sweep("evolution_max_terms", (10_000_000, 1_000_000, 1000, 100, 10, 2))
evolution_max_terms=1e+07 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.38s
* evolution_max_terms=1e+06 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.31s
evolution_max_terms=1000 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.30s
evolution_max_terms=100 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.29s
evolution_max_terms=10 mean 0.3927 ± 0.711 │ at 0: 59% computed: 31% at 2.0: 9% │ 0.28s
evolution_max_terms=2 mean 0.5245 ± 0.792 │ at 0: 59% computed: 23% at 2.0: 18% │ 0.21s
Menaikkan batas melewati default tidak mengubah apa-apa di sini. Untuk circuit seukuran ini, default sudah mempertahankan semua suku, jadi bound sudah seketat yang bisa dicapai opsi ini. Itu situasi yang umum. Memperketat opsi accuracy baru ada gunanya kalau default-nya memang membatasi, dan cara mengetahuinya adalah lewat eksperimen di atas. Jalankan ulang dengan nilai yang lebih ketat dan lihat apakah bound-nya bergeser.
Peringatan: Rutin propagasi di baliknya memperlakukan evolution_max_terms sebagai kapasitas untuk
dialokasikan di awal, bukan batas atas yang dicek. Memberi nilai yang nominalnya tak terbatas, seperti
numpy.iinfo(numpy.uint).max, membuat allocator meminta memori berterabyte dan proses langsung berhenti.
Pakai angka yang realistis. Default satu juta sudah cukup longgar.
atol_simplify
Setelah commutator terbentuk, suku dengan koefisien di bawah atol_simplify dibuang. Seperti pada
batas evolusi, 1-norm yang hilang dengan cara ini ditambahkan ke bound.
sweep("atol_simplify", (0.0, 1e-8, 1e-4, 1e-2, 1e-1))
atol_simplify=0 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.32s
* atol_simplify=1e-08 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.31s
atol_simplify=0.0001 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.33s
atol_simplify=0.01 mean 0.3656 ± 0.685 │ at 0: 59% computed: 35% at 2.0: 6% │ 0.31s
atol_simplify=0.1 mean 0.3837 ± 0.697 │ at 0: 59% computed: 31% at 2.0: 10% │ 0.31s
eigval_max_qubits
Bound paling ketat yang tersedia adalah spectral norm dari commutator, yang membutuhkan komputasi
eigenvalue. Itu jadi mahal saat commutator makin lebar, jadi eigval_max_qubits menetapkan lebar
yang jika dilewati, upayanya ditinggalkan dan diganti dengan pertidaksamaan segitiga. Bound itu,
atas koefisien commutator , valid tetapi lebih longgar.
Menyetelnya ke nol memaksa fallback di mana-mana, yang menunjukkan manfaat eigensolver dalam satu angka.
sweep("eigval_max_qubits", (20, 14, 8, 4, 2, 0))
eigval_max_qubits=20 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.31s
* eigval_max_qubits=14 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.30s
eigval_max_qubits=8 mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.32s
eigval_max_qubits=4 mean 0.3742 ± 0.694 │ at 0: 59% computed: 35% at 2.0: 6% │ 0.30s
eigval_max_qubits=2 mean 0.4983 ± 0.809 │ at 0: 59% computed: 23% at 2.0: 18% │ 0.27s
eigval_max_qubits=0 mean 0.5081 ± 0.810 │ at 0: 59% computed: 21% at 2.0: 19% │ 0.27s
Bahkan dengan eigensolver dimatikan, rata-ratanya tetap jauh di bawah nilai trivial 2.0. Porsi suku yang di-bound 0 tidak berubah: suku yang commute persis dengan observable yang sudah dievolusikan memiliki commutator nol, yang dikenali sebelum komputasi eigenvalue apa pun dipertimbangkan. Dari sisanya, bound pertidaksamaan segitiga mencapai 2.0 untuk sebagian dan dibatasi di situ, tetapi untuk banyak lainnya nilainya masih lebih kecil, jadi mereka tetap punya bound hasil hitung di bawah 2.0.
atol_eigenvalue
Ketika spectral norm memang dihitung, atol_eigenvalue adalah toleransi konvergensi dari
eigensolver iteratif. Meski begitu, sebagian besar commutator tidak pernah sampai ke solver itu:
-
Commutator yang bekerja pada paling banyak empat qubit didiagonalisasi secara eksak, langsung.
-
Commutator yang lebih lebar pertama-tama direduksi ke jumlah qubit terkecil yang dibutuhkan suku Pauli-nya, dan kalau bentuk tereduksi itu bekerja pada paling banyak delapan qubit, ia didiagonalisasi secara eksak.
Hanya commutator yang setelah reduksi itu masih butuh lebih dari delapan qubit yang masuk ke solver iteratif, yang merupakan satu-satunya tempat toleransi ini berlaku. Circuit seukuran yang dipakai di panduan ini tidak pernah sampai sejauh itu, jadi sweep atas opsi ini akan menunjukkan bound yang identik untuk setiap nilai. Opsi ini penting untuk light cone yang lebar pada device besar.
Berbeda dari opsi accuracy lainnya, melonggarkan atol_eigenvalue tidak aman dalam satu arah. Eigensolver
iteratif mendekati eigenvalue ekstrem dari dalam spektrum, jadi estimasi
yang belum konvergen sepenuhnya meremehkan spectral norm, dan juga bound-nya. Default-nya sengaja
dibuat ketat karena alasan itu. Kalau solver gagal konvergen, fallback pertidaksamaan segitiga
yang mengambil alih, yang kembali valid.
Cakupan
max_num_boxes
Kadang batas yang berguna bukan soal accuracy, melainkan kedalaman. max_num_boxes berhenti memunculkan komputasi
bound setelah sejumlah box tertentu, dan setiap layer di luarnya diberi bound trivial
2.0.
Batas ini menghitung instruksi BoxOp yang noisy (yang membawa anotasi InjectNoise) alih-alih,
misalnya, kedalaman dalam gate. Dengan begitu batas ini terikat pada struktur circuit yang terlihat oleh pengguna dan
menghindari ketergantungan pada referensi anotasi hasil generate otomatis, yang bisa berubah antar
proses transpilasi.
Ini pilihan yang wajar untuk circuit yang dua setengahnya ditangani oleh bound forward dan backward masing-masing: hitung tiap bound hanya sedalam yang akan dipakai.
from qiskit.circuit import BoxOp
from samplomatic import InjectNoise
# The limit counts the noisy boxes encountered while iterating the circuit, not the unique ones:
# a repeated layer is one noise model but several boxes. Boxes without an `InjectNoise`
# annotation carry no error terms and do not count.
num_boxes = sum(
1
for inst in boxed_circuit.data
if isinstance(inst.operation, BoxOp)
and any(
isinstance(annotation, InjectNoise)
for annotation in inst.operation.annotations
)
)
print(
f"the circuit has {num_boxes} noisy boxes drawn from "
f"{len(noise_model_paulis)} distinct noise models\n"
)
for limit in (None, 6, 4, 2, 1):
marker = "*" if limit is None else " "
forward(f"{marker} max_num_boxes={limit}", max_num_boxes=limit)
the circuit has 9 noisy boxes drawn from 2 distinct noise models
* max_num_boxes=None mean 0.3639 ± 0.684 │ at 0: 59% computed: 36% at 2.0: 5% │ 0.33s
max_num_boxes=6 mean 0.7486 ± 0.935 │ at 0: 53% computed: 18% at 2.0: 30% │ 0.18s
max_num_boxes=4 mean 1.1506 ± 0.980 │ at 0: 40% computed: 5% at 2.0: 55% │ 0.11s
max_num_boxes=2 mean 1.5707 ± 0.821 │ at 0: 21% computed: 0% at 2.0: 79% │ 0.05s
max_num_boxes=1 mean 1.8157 ± 0.579 │ at 0: 9% computed: 0% at 2.0: 91% │ 0.03s
Eksekusi
timeout
timeout adalah anggaran waktu wall-clock dalam detik. Saat habis, bound yang sudah terhitung dipertahankan
dan semua yang masih tertunda tetap memakai bound trivial 2.0, jadi hasilnya selalu bisa dipakai, hanya
saja makin longgar kalau dipotong makin awal.
Ini bukan tenggat yang keras: task yang sedang berjalan dibiarkan selesai, jadi pemanggilan bisa melewati anggaran sebesar durasi satu task.
num_processes
Bound untuk suku error yang berbeda saling independen, jadi num_processes membagikannya ke proses
worker. Sendirian, ini hanya memengaruhi lama komputasi, tidak pernah hasilnya.
Catatan: timeout yang digabung dengan num_processes membuat hasil bergantung pada waktu: task mana yang
selesai sebelum anggaran habis tidak deterministik. Bound tetap valid, tetapi tidak lagi
bisa direproduksi dari satu run ke run berikutnya.
Mulai dari mana
Default dipilih untuk memberi bound yang ketat pada circuit dengan ukuran yang masih terjangkau. Kalau komputasi terlalu lambat, coba terapkan opsi-opsinya kurang lebih dalam urutan ini:
-
num_processes: Percepatan gratis, tanpa biaya accuracy. -
max_num_boxes: Kalau kamu sudah tahu seberapa dalam di circuit yang kamu pedulikan. -
eigval_max_qubits: Melewati langkah paling mahal untuk commutator terlebar, yang biasanya hanya minoritas kecil dari suku. -
evolution_max_termsdanatol_simplify: Keduanya memengaruhi accuracy secara menyeluruh, jadi setel belakangan, sambil mengecek dengan sweep seperti di atas untuk melihat seberapa jauh bound bergeser. -
timeout: Penghenti keras, bukan alat penyetelan, untuk saat komputasi harus selesai sebelum tenggat, apa pun kualitasnya.
Makin longgar bound-nya, makin besar sampling overhead yang akan dilaporkan compute_local_scales untuk
toleransi bias tertentu. Overhead itu adalah besaran yang perlu dipantau saat menyetel: ia memberi tahu
berapa sebenarnya biaya dari aproksimasi tersebut.