Utilizar diferentes normas Lp para el truncamiento del término de Pauli
Nota: Antes de leer esta guía, debes consultar la guía sobre los términos de Pauli truncados, en la que se describe el truncamiento de los términos de Pauli de bajo peso que se integra en el método de retropropagación basándose en un « TruncationErrorBudget » especificado.
En esta guía, aprenderás sobre el argumento de palabra clave «backpropagate» p_norm, que se puede utilizar para modificar la norma Lp empleada para estimar el error derivado de los términos de Pauli truncados.
Construye un circuito de ejemplo
Esta guía utiliza el mismo circuito de ejemplo que la guía sobre los términos truncados de Pauli :
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:
Define una observable para la magnetización total:
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
)A modo de referencia, calcula el valor exacto de la esperanza:
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
Utiliza la norma « L1 »
Por defecto, y tal y como ya has visto en la guía sobre los términos truncados de Pauli, p_norm=1, lo que significa que el error se estima como:
donde es el estado cuántico, es la diferencia real entre la observable exacta y la truncada (que se desconoce), es el conjunto de términos de Pauli que se han truncado, y es el coeficiente de los términos de Pauli. Esta desigualdad constituye un límite superior riguroso, aunque muy holgado, para la mayoría de los casos.
Esta guía realiza la retropropagación de seis secciones del circuito de ejemplo, utilizando un error constante por sección de 0.001. Este valor debe entenderse como el presupuesto dentro del marco establecido 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.
Ahora podemos calcular el valor esperado de la observable retropropagada, así como el error con respecto a la referencia exacta:
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
Por último, podemos representar gráficamente el error producido durante la retropropagación de cada corte, así como el error acumulado. El error acumulado es la suma de los errores de cada segmento. Podemos observar que el error acumulado es un límite superior muy aproximado del error real.
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:
Utiliza la norma « L2 »
Se podría argumentar que la norma « L2 » es una mejor aproximación al error incurrido que la norma « L1 ». Esto se debe a que podemos suponer que el estado cuántico procede de un conjunto aleatorio de Haar, en cuyo caso el error incurrido sigue una distribución con una media nula y una varianza que puede limitarse aproximadamente mediante la norma de L2 :
Aunque el límite no es riguroso, solo se incumplirá en casos patológicos.
Volvemos a realizar la retropropagación en seis secciones del circuito de ejemplo, utilizando un error máximo por sección de 0.001 (esta vez, interpretado según la norma 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.
De nuevo, calculamos el valor esperado de la observable retropropagada y el error con respecto a la referencia exacta:
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
Al representar gráficamente los errores incurridos por segmento y el error acumulado, se obtiene un panorama similar al anterior.
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:
Ten en cuenta que el error acumulado es, una vez más, la suma de los errores de cada corte. Este es otro límite aproximado debido a la desigualdad de Minkowski, ya que tenemos que calcular este límite de forma recursiva:
donde el nuevo subíndice indica la iteración actual del corte, de modo que es el error real en la iteración de retropropagación , es el error de truncamiento aproximado de la iteración , y es el conjunto de términos de Pauli truncados en la iteración .