Użyj różnych norm Lp do obcinania wyrazów Pauliego
Uwaga: Przed przeczytaniem tego przewodnika warto zapoznać się z przewodnikiem Obcinanie wyrazów Pauliego, który opisuje obcinanie wyrazów Pauliego o niskiej wadze, wbudowane w metodę backpropagate na podstawie zadanego TruncationErrorBudget.
W tym przewodniku dowiesz się o argumencie nazwanym p_norm metody backpropagate, za pomocą którego możesz zmienić normę Lp używaną do oszacowania błędu wprowadzanego przez obcięte wyrazy Pauliego.
Zbuduj przykładowy obwód
Ten przewodnik używa tego samego przykładowego obwodu co przewodnik Obcinanie wyrazów Pauliego:
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"matplotlib": "matplotlib", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils", "rustworkx": "rustworkx"}
_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 rustworkx.generators
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import (
PauliOrderStrategy,
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types
# Generate a linear chain of 10 qubits
num_qubits = 10
linear_chain = rustworkx.generators.path_graph(num_qubits)
# Use an arbitrary XY model
hamiltonian = generate_xyz_hamiltonian(
linear_chain,
coupling_constants=(0.05, 0.02, 0.0),
ext_magnetic_field=(0.02, 0.08, 0.0),
pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,
)
# Evolve for some time
circuit = generate_time_evolution_circuit(
hamiltonian, synthesis=LieTrotter(reps=3), time=2.0
)
# slice the circuit by gate type
slices = slice_by_gate_types(circuit)
# For visualization purposes only, recombine the slices with barriers between them and draw the resulting circuit
combine_slices(slices, include_barriers=True).draw("mpl", fold=50, scale=0.6)

Zdefiniuj obserwablę dla całkowitej magnetyzacji:
from qiskit.quantum_info import SparsePauliOp
obs = SparsePauliOp.from_sparse_list(
[("Z", [i], 1.0) for i in range(num_qubits)], num_qubits=num_qubits
)
Dla porównania oblicz dokładną wartość oczekiwaną:
from qiskit.primitives import StatevectorEstimator
estimator = StatevectorEstimator()
job = estimator.run([(circuit, obs)])
res = job.result()
exact_exp = res[0].data.evs
print(exact_exp)
9.318197859862146
Użyj normy L1
Domyślnie, jak już widziałeś w przewodniku Obcinanie wyrazów Pauliego, p_norm=1, co oznacza, że błąd jest szacowany jako:
gdzie jest stanem kwantowym, jest rzeczywistą różnicą między dokładną a obciętą obserwablą (która jest nieznana), jest zbiorem obciętych wyrazów Pauliego, a jest współczynnikiem wyrazu Pauliego.
Ta nierówność jest rygorystycznym, ale w większości scenariuszy bardzo luźnym ograniczeniem górnym.
W tym przewodniku wykonujemy propagację wsteczną sześciu wycinków przykładowego obwodu, przyjmując stały błąd na wycinek równy 0.001. Tę wartość należy rozumieć jako budżet w ramach zadanej normy p_norm.
from qiskit_addon_obp.utils.truncating import setup_budget
l1_truncation_error_budget = setup_budget(max_error_per_slice=0.001, p_norm=1)
print(l1_truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
from qiskit_addon_obp import backpropagate
max_slices = 6
l1_bp_obs, l1_remaining_slices, l1_metadata = backpropagate(
obs,
slices[-max_slices:],
truncation_error_budget=l1_truncation_error_budget,
)
l1_reduced_circuit = combine_slices(
slices[:-max_slices] + l1_remaining_slices
)
print(
f"Backpropagated {max_slices - len(l1_remaining_slices)} circuit slices."
)
print(
f"New observable contains {len(l1_bp_obs)} terms and {len(l1_bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 6 circuit slices.
New observable contains 116 terms and 10 commuting groups.
Możemy teraz obliczyć wartość oczekiwaną obserwabli po propagacji wstecznej, a także błąd względem dokładnego odniesienia:
estimator = StatevectorEstimator()
job = estimator.run([(l1_reduced_circuit, l1_bp_obs)])
res = job.result()
l1_exp = res[0].data.evs
l1_error = exact_exp - l1_exp
print(l1_exp, l1_error)
9.317869899338842 0.00032796052330397174
Na koniec możemy wykreślić błąd wprowadzony podczas propagacji wstecznej każdego wycinka oraz błąd skumulowany. Błąd skumulowany jest sumą błędów poszczególnych wycinków. Widzimy, że błąd skumulowany jest bardzo luźnym ograniczeniem górnym rzeczywistego błędu.
from matplotlib import pyplot as plt
from qiskit_addon_obp.utils.visualization import (
plot_accumulated_error,
plot_slice_errors,
)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[1].plot([6], [l1_error], "x", color="red", label="actual error")
plot_slice_errors(l1_metadata, axes[0])
plot_accumulated_error(l1_metadata, axes[1])

Użyj normy L2
Można argumentować, że norma L2 jest lepszym przybliżeniem wprowadzanego błędu niż norma L1. Wynika to z tego, że możemy założyć, iż stan kwantowy jest wylosowany z zespołu losowego Haara, a wówczas wprowadzany błąd ma rozkład o zerowej średniej i wariancji, którą można w przybliżeniu ograniczyć normą L2:
Choć to ograniczenie nie jest rygorystyczne, zostanie naruszone jedynie w przypadkach patologicznych.
Ponownie wykonujemy propagację wsteczną sześciu wycinków przykładowego obwodu, przyjmując maksymalny błąd na wycinek równy 0.001 (tym razem interpretowany w normie L2).
l2_truncation_error_budget = setup_budget(max_error_per_slice=0.001, p_norm=2)
print(l2_truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=2)
max_slices = 6
l2_bp_obs, l2_remaining_slices, l2_metadata = backpropagate(
obs,
slices[-max_slices:],
truncation_error_budget=l2_truncation_error_budget,
)
l2_reduced_circuit = combine_slices(
slices[:-max_slices] + l2_remaining_slices
)
print(
f"Backpropagated {max_slices - len(l2_remaining_slices)} circuit slices."
)
print(
f"New observable contains {len(l2_bp_obs)} terms and {len(l2_bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 6 circuit slices.
New observable contains 84 terms and 6 commuting groups.
Ponownie obliczamy wartość oczekiwaną obserwabli po propagacji wstecznej oraz błąd względem dokładnego odniesienia:
estimator = StatevectorEstimator()
job = estimator.run([(l2_reduced_circuit, l2_bp_obs)])
res = job.result()
l2_exp = res[0].data.evs
l2_error = exact_exp - l2_exp
print(l2_exp, l2_error)
9.317829770853422 0.00036808900872387085
Wykres błędów wprowadzonych w poszczególnych wycinkach oraz błędu skumulowanego daje obraz podobny do poprzedniego.
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[1].plot([6], [l2_error], "x", color="red", label="actual error")
plot_slice_errors(l2_metadata, axes[0])
plot_accumulated_error(l2_metadata, axes[1])

Zauważ, że błąd skumulowany jest znów sumą błędów pojedynczych wycinków. Jest to kolejne luźne ograniczenie wynikające z nierówności Minkowskiego, ponieważ musimy obliczać to ograniczenie rekurencyjnie:
gdzie nowy indeks dolny oznacza bieżącą iterację po wycinkach, czyli jest rzeczywistym błędem w iteracji propagacji wstecznej , jest przybliżonym błędem obcięcia z iteracji , a jest zbiorem wyrazów Pauliego obciętych w iteracji .