Verwendung verschiedener Lp-Normen für die Trunkierung des Pauli-Terms
Hinweis: Bevor Sie diese Anleitung lesen, sollten Sie die Anleitung zu „Truncate Pauli Terms“ lesen, in der die Abschneidung von Pauli-Termen mit geringem Gewicht beschrieben wird, die in die Backpropagation-Methode auf der Grundlage eines festgelegten „ TruncationErrorBudget “ integriert ist.
In dieser Anleitung erfahren Sie mehr über das Schlüsselwortargument „backpropagate“, mit dem Sie die Lp-Norm ändern können p_norm, die zur Schätzung des durch die abgeschnittenen Pauli-Terme verursachten Fehlers verwendet wird.
Bauen Sie eine Beispielschaltung auf
In dieser Anleitung wird dieselbe Beispielschaltung verwendet wie in der Anleitung zum Kürzen von Pauli-Termen :
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:
Definieren Sie eine Observable für die Gesamtmagnetisierung:
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
)Berechnen Sie zur Veranschaulichung den exakten Erwartungswert:
from qiskit.primitives import StatevectorEstimator
estimator = StatevectorEstimator()
job = estimator.run([(circuit, obs)])
res = job.result()
exact_exp = res[0].data.evs
print(exact_exp)Output:
9.318197859862146
Verwenden Sie die „ L1 “-Norm
Standardmäßig – und wie Sie bereits im Leitfaden zu den „Truncate-Pauli“-Termen gesehen haben – p_norm=1, was bedeutet, dass der Fehler wie folgt geschätzt wird:
wobei der Quantenzustand ist, die tatsächliche Differenz zwischen der exakten und der abgeschnittenen Observablen (die unbekannt ist), die Menge der abgeschnittenen Pauli-Terme und der Koeffizient der Pauli-Terme. Diese Ungleichung stellt für die meisten Szenarien eine strenge, aber sehr lockere Obergrenze dar.
In diesem Leitfaden werden sechs Abschnitte der Beispielschaltung rückwärtsverbreitet, wobei pro Abschnitt ein konstanter Fehler von verwendet wird 0.001. Dieser Wert ist als Budget innerhalb des vorgegebenen Rahmens zu verstehen 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)Output:
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."
)Output:
Backpropagated 6 circuit slices.
New observable contains 116 terms and 10 commuting groups.
Nun können wir den Erwartungswert der rückpropagierten Observablen sowie den Fehler gegenüber der exakten Referenz berechnen:
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)Output:
9.317869899338842 0.00032796052330397174
Schließlich können wir den Fehler, der bei der Rückpropagierung jeder Schicht entstanden ist, sowie den kumulierten Fehler grafisch darstellen. Der kumulierte Fehler ist die Summe der Slice-Fehler. Wir sehen, dass der kumulierte Fehler eine sehr grobe Obergrenze für den tatsächlichen Fehler darstellt.
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:
Verwenden Sie die „ L2 “-Norm
Man könnte argumentieren, dass die Norm „ L2 “ eine bessere Annäherung an den aufgetretenen Fehler darstellt als die Norm „ L1 “. Das liegt daran, dass wir davon ausgehen können, dass der Quantenzustand aus einem Haar-Zufallsensemble stammt; in diesem Fall folgt der auftretende Fehler einer Verteilung mit einem gegen Null gehenden Mittelwert und einer Varianz, die näherungsweise durch die L2 -Norm begrenzt werden kann:
Auch wenn die Schranke nicht streng ist, wird sie nur in pathologischen Fällen überschritten.
Wir führen erneut eine Rückpropagierung für sechs Schnitten der Beispielschaltung durch, wobei wir pro Schnitt einen maximalen Fehler von verwenden ( 0.001 diesmal interpretiert gemäß der L2 -Norm).
l2_truncation_error_budget = setup_budget(max_error_per_slice=0.001, p_norm=2)
print(l2_truncation_error_budget)Output:
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."
)Output:
Backpropagated 6 circuit slices.
New observable contains 84 terms and 6 commuting groups.
Wir berechnen erneut den Erwartungswert der rückpropagierten Observablen und den Fehler im Vergleich zur exakten Referenz:
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)Output:
9.317829770853422 0.00036808900872387085
Die grafische Darstellung der pro Slice aufgetretenen Fehler und des kumulierten Fehlers ergibt ein ähnliches Bild wie zuvor.
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:
Beachten Sie, dass der kumulierte Fehler wiederum die Summe der Fehler der einzelnen Schichten ist. Dies ist eine weitere grobe Schranke, die sich aus der Minkowski-Ungleichung ergibt, da wir diese Schranke rekursiv berechnen müssen:
wobei der neue Index die aktuelle Slice-Iteration angibt, sodass den tatsächlichen Fehler bei der Backpropagation-Iteration darstellt, den approximierten Abschneidefehler aus der Iteration und die Menge der Pauli-Terme, die bei der Iteration abgeschnitten wurden.