Przejdź do głównej treści
Ten projekt jest w wersji beta. Znalazłeś błąd? Otwórz zgłoszenie na GitHub. Masz pytanie lub pomysł? Zapytaj w Discussions — chętnie poznamy Twoją opinię!
Jak oceniasz jakość tłumaczenia tej strony?

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)

Output of the previous code cell

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:

∣⟨ψ∣Δ∣ψ⟩∣≤∑P∈T∣cP∣|\langle\psi|\Delta|\psi\rangle| \leq \sum_{P\in\mathcal{T}} |c_P|

gdzie ψ\psi jest stanem kwantowym, Δ\Delta jest rzeczywistą różnicą między dokładną a obciętą obserwablą (która jest nieznana), T\mathcal{T} jest zbiorem obciętych wyrazów Pauliego, a cPc_P 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])

Output of the previous code cell

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 ∣ψ⟩|\psi\rangle 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:

∣⟨ψ∣Δ∣ψ⟩∣≲(∑P∈T∣cP∣2)1/2|\langle\psi|\Delta|\psi\rangle| \lesssim \left( \sum_{P\in\mathcal{T}} |c_P|^2 \right)^{1/2}

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

Output of the previous code cell

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:

∣⟨ψ∣Δi∣ψ⟩∣≤∣⟨ψ∣Δ~i−1∣ψ⟩∣+(∑P∈Ti∣cP∣2)1/2=∣⟨ψ∣Δ~i∣ψ⟩∣|\langle\psi|\Delta_{i}|\psi\rangle| \leq |\langle\psi|\tilde{\Delta}_{i-1}|\psi\rangle| + \left( \sum_{P\in\mathcal{T_i}} |c_P|^2 \right)^{1/2} = |\langle\psi|\tilde{\Delta}_{i}|\psi\rangle|

gdzie nowy indeks dolny ii oznacza bieżącą iterację po wycinkach, czyli Δi\Delta_i jest rzeczywistym błędem w iteracji propagacji wstecznej ii, Δ~i−1\tilde{\Delta}_{i-1} jest przybliżonym błędem obcięcia z iteracji i−1i-1, a Ti\mathcal{T}_i jest zbiorem wyrazów Pauliego obciętych w iteracji ii.