Utilisation de différentes normes Lp pour la troncature des termes de Pauli
Remarque : avant de lire ce guide, nous vous recommandons de consulter le guide consacré aux termes de Pauli tronqués, qui décrit la troncature des termes de Pauli de faible poids intégrée à la méthode de rétropropagation sur la base d’une « TruncationErrorBudget » spécifiée.
Dans ce guide, vous découvrirez l'argument de mot-clé « backpropagate » p_norm, qui permet de modifier la norme Lp utilisée pour estimer l'erreur induite par les termes de Pauli tronqués.
Construisez un circuit d'exemple
Ce guide utilise le même circuit d'exemple que celui présenté dans le guide consacré aux termes de Pauli tronqués :
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:
Définir une observable pour la magnétisation totale :
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
)À titre de référence, calculez la valeur exacte de l'espérance :
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
Utilisez la norme « L1 »
Par défaut, et comme vous l'avez déjà vu dans le guide consacré aux termes de Pauli tronqués, p_norm=1, ce qui signifie que l'erreur est estimée comme suit :
où est l'état quantique, est la différence réelle entre l'observable exacte et l'observable tronquée (qui est inconnue), est l'ensemble des termes de Pauli qui ont été tronqués, et est le coefficient des termes de Pauli. Cette inégalité constitue une borne supérieure rigoureuse mais très large pour la plupart des scénarios.
Ce guide effectue une rétropropagation sur six tranches du circuit d'exemple, en utilisant une erreur constante de par tranche 0.001. Ce montant doit être considéré comme le budget alloué dans le cadre de ce projet 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.
Nous pouvons désormais calculer la valeur attendue de l'observable rétropropagée, ainsi que l'erreur par rapport à la référence exacte :
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
Enfin, nous pouvons représenter graphiquement l'erreur survenue lors de la rétropropagation de chaque tranche, ainsi que l'erreur cumulée. L'erreur cumulée correspond à la somme des erreurs de tranche. On constate que l'erreur cumulée constitue une borne supérieure très large par rapport à l'erreur réelle.
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:
Utilisez la norme « L2 »
On peut affirmer que la norme « L2 » constitue une meilleure approximation de l'erreur commise que la norme « L1 ». En effet, on peut supposer que l'état quantique est tiré d'un ensemble aléatoire de Haar; dans ce cas, l'erreur commise suit une distribution dont la moyenne tend vers zéro et dont la variance peut être approximativement estimée par la norme d' L2 :
Bien que cette borne ne soit pas rigoureuse, elle ne sera dépassée que dans des cas pathologiques.
Nous effectuons à nouveau une rétropropagation sur six tranches du circuit d'exemple, en fixant une erreur maximale par tranche de 0.001 (interprétée cette fois-ci selon la norme d' L2 ).
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.
Une fois encore, nous calculons la valeur attendue de l'observable rétropropagée et de l'erreur par rapport à la référence exacte :
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
Le tracé des erreurs survenues par tranche et de l'erreur cumulée donne une image similaire à celle d'avant.
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:
Il convient de noter que l'erreur cumulée correspond à nouveau à la somme des erreurs de chaque tranche. Il s'agit là encore d'une borne approximative due à l'inégalité de Minkowski, car nous devons calculer cette borne de manière récursive :
où le nouvel indice indique l'itération actuelle de la tranche, ce qui fait que correspond à l'erreur réelle à l'itération de la rétropropagation, à l'erreur de troncature approximative issue de l'itération , et à l'ensemble des termes de Pauli tronqués à l'itération .