Lewati ke konten utama
Proyek ini masih dalam tahap beta. Menemukan bug? Buat isu GitHub. Punya pertanyaan atau ide? Tanya di Discussions — kami sangat menghargai masukan kamu!
Bagaimana kualitas terjemahan di halaman ini?

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

OpsiDefaultMengaturEfek pada hasil
Accuracy
evolution_max_terms1_000_000Berapa banyak suku operator yang bertahan selama evolusiLebih rendah: lebih murah, bound lebih longgar
atol_simplify1e-8Suku commutator mana yang dibuang karena dianggap sepeleLebih tinggi: lebih murah, bound lebih longgar
eigval_max_qubits14Selebar apa commutator boleh agar spectral norm-nya dihitungLebih rendah: lebih murah, bound lebih longgar
atol_eigenvalue1e-10Kapan eigensolver iteratif dianggap sudah konvergenLebih tinggi: lebih murah, bound kurang akurat
Scope
max_num_boxesNoneBerapa banyak box yang dihitung bound-nyaBox di luar batas tetap memakai bound trivial 2.0
Execution
timeoutNoneAnggaran waktu (wall-clock) untuk seluruh komputasiPekerjaan yang tersisa tetap memakai bound trivial 2.0
num_processes1Berapa banyak proses worker yang menghitung bound secara paralelTidak 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, 2∑i∣ci∣2 \sum_i |c_i| atas koefisien commutator cic_i, 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:

  1. num_processes: Percepatan gratis, tanpa biaya accuracy.

  2. max_num_boxes: Kalau kamu sudah tahu seberapa dalam di circuit yang kamu pedulikan.

  3. eigval_max_qubits: Melewati langkah paling mahal untuk commutator terlebar, yang biasanya hanya minoritas kecil dari suku.

  4. evolution_max_terms dan atol_simplify: Keduanya memengaruhi accuracy secara menyeluruh, jadi setel belakangan, sambil mengecek dengan sweep seperti di atas untuk melihat seberapa jauh bound bergeser.

  5. 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.