Saltar al contenido principal
Este proyecto está en fase beta. ¿Encontraste un error? Abre un issue en GitHub. ¿Tienes una pregunta o una idea? Pregunta en Discussions — ¡nos encantaría tu opinión!
¿Cómo es la calidad de la traducción en esta página?

Usa distintas normas Lp para el truncamiento de términos de Pauli

Nota: Antes de leer esta guía, deberías leer la guía Truncar términos de Pauli, que describe el truncamiento de términos de Pauli de bajo peso integrado en el método backpropagate a partir de un TruncationErrorBudget especificado. En esta guía aprenderás sobre el argumento de palabra clave p_norm de backpropagate, que se puede usar para cambiar la norma Lp utilizada para estimar el error incurrido por los términos de Pauli truncados.

Construir un circuito de ejemplo​

Esta guía usa el mismo circuito de ejemplo que la guía Truncar términos de Pauli:

# 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

Define un 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
)

Como referencia, calcula el valor esperado exacto:

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

Usar la norma L1​

Por defecto, y como ya has visto en la guía Truncar términos de Pauli, p_norm=1, lo que significa que el error se estima como:

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

donde ψ\psi es el estado cuántico, Δ\Delta es la diferencia real entre el observable exacto y el truncado (que se desconoce), T\mathcal{T} es el conjunto de términos de Pauli que se truncaron y cPc_P es el coeficiente del término de Pauli. Esta desigualdad es una cota superior rigurosa pero muy holgada en la mayoría de los escenarios. Esta guía retropropaga seis fragmentos del circuito de ejemplo, usando un error constante por fragmento de 0.001. Este valor debe entenderse como el presupuesto dentro del p_norm dado.

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.

Ahora podemos calcular el valor esperado del observable retropropagado, 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)
9.317869899338842 0.00032796052330397174

Por último, podemos graficar el error incurrido durante la retropropagación de cada fragmento, así como el error acumulado. El error acumulado es la suma de los errores de los fragmentos. Vemos que el error acumulado es una cota superior muy holgada 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 of the previous code cell

Usar la norma L2​

Se puede argumentar que la norma L2 es una mejor aproximación del error incurrido que la norma L1. Eso se debe a que podemos suponer que el estado cuántico ∣ψ⟩|\psi\rangle se extrae de un conjunto aleatorio de Haar, en cuyo caso el error incurrido sigue una distribución con media nula y una varianza que puede acotarse aproximadamente por la norma 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}

Aunque la cota no es rigurosa, solo se violará en casos patológicos. Volvemos a retropropagar seis fragmentos del circuito de ejemplo, usando un error máximo por fragmento de 0.001 (esta vez, interpretado en la norma 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.

De nuevo, calculamos el valor esperado del observable retropropagado 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)
9.317829770853422 0.00036808900872387085

Al graficar los errores incurridos por fragmento y el error acumulado se obtiene una imagen similar a la 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 of the previous code cell

Ten en cuenta que el error acumulado es de nuevo la suma de los errores de cada fragmento. Esta es otra cota holgada debido a la desigualdad de Minkowski, ya que tenemos que calcular esta cota de forma recursiva:

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

donde el nuevo subíndice ii indica la iteración actual de fragmento, haciendo de Δi\Delta_i el error real en la iteración de retropropagación ii, de Δ~i−1\tilde{\Delta}_{i-1} el error de truncamiento aproximado de la iteración i−1i-1, y de Ti\mathcal{T}_i el conjunto de términos de Pauli truncados en la iteración ii.