Skip to main content
IBM Quantum Platform

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:

Output of the previous code cell

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:

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

wobei ψ\psi der Quantenzustand ist, Δ\Delta die tatsächliche Differenz zwischen der exakten und der abgeschnittenen Observablen (die unbekannt ist), T\mathcal{T} die Menge der abgeschnittenen Pauli-Terme und cPc_P 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:

Output of the previous code cell

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

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

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:

Output of the previous code cell

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:

∣⟨ψ∣Δ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|

wobei der neue Index ii die aktuelle Slice-Iteration angibt, sodass Δi\Delta_i den tatsächlichen Fehler bei der Backpropagation-Iteration ii darstellt, Δ~i−1\tilde{\Delta}_{i-1} den approximierten Abschneidefehler aus der Iteration i−1i-1 und Ti\mathcal{T}_i die Menge der Pauli-Terme, die bei der Iteration ii abgeschnitten wurden.

War diese Seite hilfreich?
Melden Sie auf GitHub einen Fehler, einen Tippfehler oder fordern Sie Inhalte an.