Перейти до основного вмісту
Цей проєкт у бета-версії. Знайшов(-ла) помилку? Відкрити issue на GitHub. Є запитання чи ідея? Запитати в Discussions — будемо раді твоєму відгуку!
Яка якість перекладу на цій сторінці?

Як налаштувати параметри конфігурації

Обчислення меж у принципі точне, але на практиці наближене: еволюція члена похибки Паулі через схему дає оператор з експоненційно великою кількістю членів, а знаходження спектральної норми отриманого комутатора саме по собі дороге. Наведені нижче параметри керують цією вартістю. Вони поділяються на три групи:

  • Параметри точності змінюють числове значення меж, обмінюючи щільність меж на вартість.

  • Область визначає, які межі обчислюються.

  • Параметри виконання змінюють, як виконується обчислення (а не що воно обчислює).

ПараметрЗначення за замовчуваннямЩо контролюєВплив на результат
Точність
evolution_max_terms1_000_000Скільки членів оператора переживає еволюціюМенше: дешевше, вільніші межі
atol_simplify1e-8Які члени комутатора відкидаються як незначніБільше: дешевше, вільніші межі
eigval_max_qubits14Наскільки широким може бути комутатор, щоб обчислити його спектральну нормуМенше: дешевше, вільніші межі
atol_eigenvalue1e-10Коли ітеративний розв'язувач власних значень вважається збіжнимБільше: дешевше, менш точні межі
Область
max_num_boxesNoneДля скількох боксів обчислюються межіБокси понад ліміт зберігають тривіальну межу 2.0
Виконання
timeoutNoneБюджет реального часу на все обчисленняБудь-яка решта роботи зберігає тривіальну межу 2.0
num_processes1Скільки робочих процесів обчислюють межі паралельноСам по собі жодного

Межа корисна, лише якщо вона коректна: вона ніколи не повинна занижувати вплив члена похибки на спостережувану. Послаблення будь-якого з цих параметрів, окрім atol_eigenvalue, може лише зробити межі більшими (консервативнішими), але ніколи не меншими. Отже, ціна слабкого налаштування — більші накладні витрати на вибірку, а не хибне очікуване значення; винятком є atol_eigenvalue.

Визначення схеми​

У наведених нижче експериментах використовується та сама тротеризована схема Ізинга та спостережувана, що й у посібнику швидкого старту. За такого розміру кожен параметр має помітний ефект, а кожне обчислення все ще триває менше секунди.

# 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

Невеликий допоміжний засіб підсумовує кожен набір меж в одному рядку, тож вплив параметра видно як кілька чисел, а не стіну масивів.

Кожна межа лежить між 0 і тривіальною межею 2.0. Підсумок повідомляє середню межу та її стандартне відхилення, а потім те, як члени розподіляються за такими групами:

  • Члени, обмежені точно 0, бо вони комутують з еволюціонованою спостережуваною
  • Члени з обчисленою межею між 0 і 2.0
  • Члени, що залишилися на тривіальній межі 2.0

Зауваж, що менше середнє означає щільнішу межу, а щільніша межа означає менші накладні витрати на вибірку пізніше.

Прогін за одним параметром позначає значення цього параметра за замовчуванням символом *:

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

Точність​

evolution_max_terms​

Еволюція члена похибки Паулі через не-кліффордові вентилі перетворює його на суму членів Паулі, і ця сума зростає з глибиною схеми. evolution_max_terms обмежує, скільки цих членів зберігається. 1-норма того, що відкинуто, додається до межі, тож нічого не втрачається непомітно.

Прогін іде від щільнішого, ніж за замовчуванням, до екстремального обрізання. Зниження обмеження робить обчислення дешевшим, а межі — вільнішими.

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

Підвищення обмеження понад значення за замовчуванням тут нічого не змінює. Для схеми такого розміру значення за замовчуванням вже зберігає кожен член, тож межі настільки щільні, наскільки цей параметр може їх зробити. Це типовa ситуація. Посилення параметра точності окупається лише тоді, коли його значення за замовчуванням справді обмежує, і дізнатися про це можна за допомогою наведеного вище експерименту. Запусти його знову з щільнішим значенням і подивись, чи рухаються межі.

Попередження: Базова процедура поширення трактує evolution_max_terms як ємність для попереднього виділення, а не як верхню межу, з якою порівнюють. Передавання номінально необмеженого значення, такого як numpy.iinfo(numpy.uint).max, змушує розподілювач запитати терабайти пам'яті й аварійно завершує процес. Задавай реалістичне число. Значення за замовчуванням в один мільйон уже щедре.

atol_simplify​

Після утворення комутатора члени з коефіцієнтом нижче atol_simplify відкидаються. Як і з обмеженням еволюції, 1-норма, втрачена таким чином, додається до межі.

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​

Найщільніша доступна межа — це спектральна норма комутатора, для якої потрібне обчислення власних значень. Воно стає дорогим, коли комутатор розширюється, тому eigval_max_qubits задає ширину, понад якою спробу облишають на користь нерівності трикутника. Ця межа, 2∑i∣ci∣2 \sum_i |c_i| за коефіцієнтами cic_i комутатора, коректна, але вільніша.

Установлення нуля змушує повсюди використовувати резервний варіант, що показує перевагу розв'язувача власних значень в одному числі.

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

Навіть із вимкненим розв'язувачем власних значень середнє залишається значно нижчим за тривіальне 2.0. Частка членів, обмежених 0, не змінюється: член, що точно комутує з еволюціонованою спостережуваною, має нульовий комутатор, що розпізнається ще до розгляду будь-якого обчислення власних значень. Для інших межа за нерівністю трикутника для деяких сягає 2.0 і там обрізається, але для багатьох вона все ще менша, тож вони зберігають обчислену межу нижче 2.0.

atol_eigenvalue​

Коли спектральна норма обчислюється, atol_eigenvalue є допуском збіжності ітеративного розв'язувача власних значень. Проте більшість комутаторів до цього розв'язувача ніколи не доходять:

  • Комутатор, що діє щонайбільше на чотири кубіти, діагоналізується точно, напряму.

  • Ширший спочатку зводиться до найменшої кількості кубітів, яку потребують його члени Паулі, і якщо ця зведена форма діє щонайбільше на вісім кубітів, він діагоналізується точно.

До ітеративного розв'язувача потрапляє лише комутатор, якому після цього зведення все ще потрібно понад вісім кубітів; саме там єдине місце, де застосовується цей допуск. Схеми розміру, використаного в цьому посібнику, до цього ніколи не доходять, тож прогін за цим параметром показав би однакові межі для кожного значення. Він важливий для широких світлових конусів на великих пристроях.

На відміну від інших параметрів точності, послаблення atol_eigenvalue не безпечне в одному напрямку. Ітеративний розв'язувач власних значень наближається до екстремального власного значення зсередини спектра, тож оцінка, що не зійшлася повністю, занижує спектральну норму, а отже й межу. Значення за замовчуванням навмисно щільне саме з цієї причини. Якщо розв'язувач не збігається, перебирає на себе резервний варіант за нерівністю трикутника, який знову коректний.

Область​

max_num_boxes​

Іноді корисне обмеження стосується не точності, а глибини. max_num_boxes припиняє запуск обчислень меж понад заданою кількістю боксів, і кожному шару за цією межею натомість присвоюється тривіальна межа 2.0.

Обмеження враховує шумові інструкції BoxOp (ті, що мають анотацію InjectNoise), а не, наприклад, глибину у вентилях. Це прив'язує його до видимої користувачеві структури схеми та уникає зчеплення з автогенерованими посиланнями на анотації, які можуть змінюватися між запусками транспіляції.

Це природний вибір для схеми, дві половини якої обробляються прямими та зворотними межами відповідно: обчислюй кожну лише на ту глибину, на яку її буде використано.

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

Виконання​

timeout​

timeout — це бюджет реального часу в секундах. Коли він спливає, обчислені межі зберігаються, а все незавершене зберігає тривіальну межу 2.0, тож результат завжди придатний до використання, лише вільніший, чим раніше його обірвати.

Це не жорсткий дедлайн: уже запущеним завданням дозволяється завершитися, тож виклик може перевищити бюджет на тривалість одного завдання.

num_processes​

Межі для різних членів похибки незалежні, тому num_processes розподіляє їх між робочими процесами. Сам по собі він впливає лише на тривалість обчислення, але ніколи на його результат.

Примітка: Поєднання timeout з num_processes робить результат залежним від часу: які завдання завершаться до спливу бюджету, не детерміновано. Межі залишаються коректними, але вони більше не відтворюються від запуску до запуску.

З чого почати​

Значення за замовчуванням обрані так, щоб давати щільні межі для схем такого розміру, де це доступно за вартістю. Коли обчислення надто повільне, спробуй застосовувати параметри приблизно в такому порядку:

  1. num_processes: безкоштовне прискорення без втрати точності.

  2. max_num_boxes: якщо ти заздалегідь знаєш, наскільки глибоко в схему тебе цікавить.

  3. eigval_max_qubits: пропускає найдорожчий крок для найширших комутаторів, які зазвичай становлять невелику меншість членів.

  4. evolution_max_terms та atol_simplify: вони впливають на точність повсюдно, тож налаштовуй їх останніми, перевіряючи прогоном, як-от наведені вище, наскільки зсуваються межі.

  5. timeout: жорстка зупинка, а не засіб налаштування, на випадок, коли обчислення має завершитися до дедлайну, незалежно від якості.

Чим вільніші межі, тим більші накладні витрати на вибірку, про які повідомить compute_local_scales для заданого допуску зсуву. Саме ця величина вартa уваги під час налаштування: вона показує, чого насправді коштують наближення.