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?

Truncar términos de Pauli durante la retropropagación

Esta guía explica cómo configurar el mecanismo de truncamiento de términos de Pauli proporcionado por el módulo qiskit_addon_obp.utils.truncating.

La retropropagación de operadores (OBP) puede usarse para reducir la profundidad de los circuitos cuánticos a costa de un observable más complejo. Para obtener resultados significativos con OBP, normalmente es necesario truncar términos de su observable para evitar que crezca demasiado. Una forma de permitir una retropropagación más profunda en el circuito, evitando al mismo tiempo que el operador crezca demasiado, es truncar los términos con coeficientes pequeños en lugar de añadirlos al operador. Truncar términos puede dar como resultado menos circuitos cuánticos que ejecutar, pero hacerlo produce cierto error en el cálculo final del valor esperado, proporcional a la magnitud de los coeficientes de los términos truncados.

El método backpropagate acepta un TruncationErrorBudget opcional, que configura el truncamiento de términos de Pauli de bajo peso para cada observable tras la retropropagación exitosa de cada fragmento. La cantidad de términos que se truncan depende de varios parámetros de configuración especificados por el usuario. Por ahora, solo hay una estrategia de truncamiento disponible: el método truncate_binary_search. Dado un observable y un presupuesto, realizará una búsqueda binaria sobre los términos de Pauli y los coeficientes dentro de ese observable para encontrar el umbral óptimo, de modo que la suma de los coeficientes truncados sea máxima pero inferior al presupuesto.

Nota: Por defecto, se usa la norma L1 para evaluar y acotar el error de truncamiento; sin embargo, la opción p_norm permite especificar la norma Lp que se usará. Para obtener más información sobre cómo usar esa opción, consulta la guía Usar diferentes normas Lp para el truncamiento de términos de Pauli. Los siguientes ejemplos ilustran varias formas de construir un TruncationErrorBudget mediante la función complementaria setup_budget.

Construir un circuito de ejemplo​

Esta guía usa los siguientes fragmentos de circuito:

# 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
linear_chain = rustworkx.generators.path_graph(10)

# 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, 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

Veremos un único observable simple:

from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp("IIIIIZIIII")

El caso más simple: un presupuesto de truncamiento fijo para cada fragmento​

El presupuesto disponible para el truncamiento de términos de Pauli puede variar en cada paso de la retropropagación. Para entender cómo funciona, primero examinamos el caso más simple de un presupuesto de truncamiento fijo especificado por el usuario.

La forma más directa de especificar el presupuesto de truncamiento es usar el argumento max_error_per_slice. De hecho, esto es lo que se hace en el tutorial Reducción de la profundidad de circuitos con retropropagación de operadores. Al establecer max_error_per_slice en un float, a cada fragmento se le asigna un presupuesto igual a ese valor. En el ejemplo siguiente, fijamos este valor en 0.001, lo que garantiza un error de truncamiento implícito de como máximo 0.018, si se retropropagaran los 18 fragmentos.

Ten en cuenta que cualquier presupuesto de error que quede tras retropropagar un fragmento y truncar términos con coeficientes pequeños siempre se añadirá al presupuesto de error del fragmento siguiente.

from qiskit_addon_obp.utils.truncating import setup_budget

truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.simplify import OperatorBudget

op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
New observable contains 29 terms and 10 commuting groups.

A continuación usamos la instancia de OBPMetadata y las herramientas proporcionadas por el módulo de visualización para visualizar el proceso de retropropagación.

  • El gráfico superior izquierdo muestra que tenemos presupuesto suficiente para empezar a truncar términos del observable tras retropropagar el tercer fragmento. A partir del tercer fragmento, sabemos que truncamos al menos un término de cada fragmento que retropropagamos porque incurrimos en cierto error de truncamiento después de cada fragmento.

  • El gráfico superior derecho muestra que el presupuesto de error crece hasta .003 para el tercer fragmento. Vemos una caída brusca en el presupuesto restante, lo que significa que se truncaron términos del observable. Esto es coherente con lo que dedujimos del gráfico superior izquierdo.

  • El gráfico inferior izquierdo muestra que, a medida que truncamos términos de nuestro observable, nuestro error acumulado total crece de forma monótona. Este gráfico también confirma que no se truncó ningún término hasta después de haber retropropagado el tercer fragmento.

  • El gráfico inferior derecho muestra que el número de grupos de Pauli conmutantes en nuestro observable ha crecido hasta el límite especificado de 10. Este gráfico también muestra cómo retropropagar una capa más haría que nuestro observable superara la cota especificada, como se aprecia en el cruce de las líneas negra y roja.

Ten en cuenta que en todos estos gráficos el eje x enumera los fragmentos retropropagados, pero como OBP trabaja sobre el final del circuito, slice 1 es el último fragmento, slice 2 es el anterior, y así sucesivamente.

from matplotlib import pyplot as plt
from qiskit_addon_obp.utils.visualization import (
plot_accumulated_error,
plot_left_over_error_budget,
plot_num_qwc_groups,
plot_slice_errors,
)

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Especificar el presupuesto de cada fragmento explícitamente​

Si sabes cómo asignar un presupuesto a cada fragmento de modo que se optimice el rendimiento de la retropropagación, quizá quieras asignar explícitamente un presupuesto a cada fragmento. Con fines demostrativos, asignaremos presupuesto cero a los tres primeros fragmentos y usaremos un presupuesto de .001 por fragmento para los restantes.

# Zero out the first 3 slices' budgets
max_error_per_slice = [0.0] * 3 + [0.001] * (len(slices) - 3)

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
New observable contains 32 terms and 10 commuting groups.

Al quitar el presupuesto de las tres primeras capas, no se truncó ningún término hasta después de la cuarta capa, como puede confirmarse en tres de estos gráficos. Lo que quizá resulte algo sorprendente es que, aunque al cuarto fragmento no se le pasó presupuesto sobrante, aun así se truncó un término usando el presupuesto asignado de .001. Esto es evidente en el gráfico superior izquierdo, pero también puede observarse en el gráfico superior derecho, ya que la curva del presupuesto sobrante se aplana entre los fragmentos tres y cuatro. Otro detalle destacable es que, a partir de este punto, se truncó al menos un término, igual que en el ejemplo anterior.

La conclusión clave es que, aunque en el segundo ejemplo incurrimos en menos error de truncamiento debido a que pusimos a cero el presupuesto de algunos fragmentos, pudimos retropropagar el mismo número de fragmentos y nuestro observable contiene el mismo número de grupos de Pauli conmutantes. Podemos confirmar que la cota de nuestro error es menor en el segundo ejemplo inspeccionando el gráfico inferior izquierdo.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Especificar el presupuesto de forma cíclica​

Si tienes un circuito con algún patrón repetible, como un circuito de Trotter, quizá quieras especificar un presupuesto para ese subconjunto repetido de fragmentos y que ese presupuesto se use en todas las repeticiones siguientes de esos fragmentos.

Más concretamente, el circuito de ejemplo que estamos usando tiene seis fragmentos que se repiten tres veces, para un total de 18 fragmentos. Asignaremos arbitrariamente presupuesto cero a las capas de un solo qubit y a las capas RYY, y un presupuesto de .003 a cada una de las capas RXX. Observaremos cómo especificar el presupuesto como una secuencia de longitud 6 hace que el presupuesto se aplique cíclicamente a los 18 fragmentos.

De nuevo, ten en cuenta que los fragmentos se retropropagan en orden inverso (es decir, empezando por el final). Por lo tanto, la primera entrada de nuestro presupuesto cíclico se usa en realidad para el último fragmento, la segunda entrada para el fragmento anterior, y así sucesivamente.

# Specify a length-6 per-slice budget.
# This will be cycled over three times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=inf, p_norm=1)
op_budget = OperatorBudget(max_qwc_groups=20)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 13 circuit slices.
New observable contains 49 terms and 14 commuting groups.

Como se ve en las imágenes superior izquierda e inferior izquierda, no se realizó ningún truncamiento en los primeros cuatro fragmentos, ya que no se les asignó presupuesto de error. Los fragmentos cinco y seis tuvieron algunos de sus términos truncados a medida que el presupuesto estuvo disponible.

Como se ve en la imagen superior izquierda, se incurrió en una cantidad relativamente grande de error tras retropropagar el fragmento siete, aunque a ese fragmento no se le asignó presupuesto. Esto se debe a que solo se usaron unos .002 de los .006 de presupuesto total asignado a los fragmentos cinco y seis, por lo que el resto se pasó al fragmento siete y se gastó en su mayor parte, como se ve en la imagen superior derecha.

Las imágenes superior izquierda y superior derecha muestran que la pequeña cantidad de presupuesto sobrante se agota entre los fragmentos 8 a 10, y que un nuevo presupuesto queda disponible en el fragmento 11, como era de esperar. Las imágenes superior izquierda, superior derecha e inferior izquierda demuestran el comportamiento cíclico del argumento max_error_per_slice cuando su longitud es menor que el número de fragmentos. Este comportamiento cíclico habría continuado a lo largo de todos los fragmentos, pero el criterio de parada max_qwc_groups se alcanzó tras retropropagar 13 fragmentos, como se ve en la imagen inferior derecha.

También es interesante que el número de grupos de Pauli en realidad disminuye después de que la segunda ronda de presupuesto esté disponible en el fragmento 11. Esto se debe a que había grupos con coeficientes pequeños que no podían truncarse hasta que hubo más presupuesto tras retropropagar el fragmento 11, por lo que se acumularon en el observable durante varias iteraciones. Este caso también destaca que max_qwc_groups debe superarse para que el algoritmo termine.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Limitar el error total​

Además de especificar el presupuesto de error por fragmento, se puede especificar la cantidad máxima de error que se incurrirá por truncamiento. Una vez alcanzado ese límite, no se realizará más truncamiento; sin embargo, la retropropagación continuará hasta que el observable se vuelva demasiado grande y se cumpla uno de los criterios de parada.

A continuación volvemos a ejecutar el experimento anterior, estableciendo un límite en max_error_total de modo que el presupuesto de error se agote tras retropropagar el fragmento siete.

# Specify a length-6 per-slice budget.
# This will be cycled over 3 times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice, max_error_total=0.006
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=0.006, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 10 circuit slices.
New observable contains 67 terms and 20 commuting groups.

Como era de esperar, nuestro error de truncamiento alcanza su tope en .006 (gráfico inferior izquierdo). Es notable que en esta ejecución no pudimos retropropagar el fragmento 11. Esto se debe a que no teníamos presupuesto suficiente para truncar términos y el número de grupos de Pauli conmutantes superó el límite de 20, como se ve en la imagen inferior derecha.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Puede ser deseable simplemente limitar el presupuesto global con max_error_total sin especificar max_error_per_slice.

truncation_error_budget = setup_budget(max_error_total=0.018)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.018], max_error_total=0.018, p_norm=1)

La salida de la celda anterior puede resultar algo sorprendente porque el per_slice_budget se establece en max_error_total. Esto indica que todo el presupuesto disponible se consumirá de forma voraz. Puedes pensarlo así: todo el presupuesto está disponible para cada fragmento (porque iteramos sobre el per_slice_budget). Pero cualquier presupuesto que ya se haya consumido se deducirá del presupuesto disponible en ese punto del algoritmo.

op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 9 circuit slices.
New observable contains 25 terms and 9 commuting groups.

La imagen superior derecha muestra cómo todo el presupuesto de error de .018 se pone a disposición del primer fragmento. No se realiza ningún truncamiento hasta el tercer fragmento, por lo que el presupuesto sobrante permanece constante. El presupuesto disminuye de forma monótona, ya que el presupuesto completo se pone a disposición de cada fragmento retropropagado hasta que se agota.

Es notable que este experimento produjo dos fragmentos retropropagados menos en comparación con el primer experimento de este cuaderno, que es casi idéntico. Esto demuestra que, para algunos problemas, distribuir el presupuesto de manera uniforme puede ser óptimo. Para otros problemas, permitir que los fragmentos gasten vorazmente el presupuesto completo podría dar un mejor rendimiento.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Limitar conjuntamente el número de fragmentos retropropagados y el error total​

Si no quieres distribuir tu presupuesto de error a lo largo de todo el circuito pero tampoco quieres gastarlo de forma voraz, puedes especificar el número de fragmentos que piensas retropropagar (num_slices), junto con un presupuesto de error total (max_error_total). Esto distribuirá el presupuesto de error de manera uniforme (según p_norm) entre los fragmentos de entrada.

Aquí limitaremos a 12 el número de fragmentos que podríamos retropropagar, y mantendremos el mismo presupuesto de error total.

num_slices = 12

truncation_error_budget = setup_budget(
max_error_total=0.018, num_slices=num_slices, p_norm=1
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0014999999999999998], max_error_total=0.018, p_norm=1)

Ahora intentaremos retropropagar los 12 fragmentos para los que asignamos algo de presupuesto en el paso anterior. Lo hacemos simplemente pasando los últimos 12 fragmentos de nuestro circuito. Con p_norm=1, cada uno de los 12 fragmentos tiene un presupuesto disponible de 0.018 / num_slices = 0.0015.

bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices[-num_slices:],
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)

Como pasamos un subconjunto de nuestros fragmentos (en el código slices[-num_slices:]) a backpropagate, debemos combinar los fragmentos restantes tras la retropropagación con los fragmentos que nunca se enviaron a retropropagar (en el código slices[:-num_slices]).

Una vez que hemos combinado todos los fragmentos restantes, podemos usar combine_slices para generar el QuantumCircuit de profundidad reducida. A continuación, inspeccionamos cuántos fragmentos se retropropagaron frente a lo grande que se volvió nuestro observable.

# Recombine the slices remaining after backprop with the rest of the original circuit
reduced_circuit = combine_slices(slices[:-num_slices] + remaining_slices)

print(f"Backpropagated {num_slices - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 12 circuit slices.
New observable contains 29 terms and 9 commuting groups.

Los gráficos muestran que retropropagamos con éxito las 12 porciones manteniendo nuestro observable por debajo de 10 grupos de Pauli conmutantes. También vemos que usar num_slices junto con max_error_total produce una distribución de presupuestos entre las porciones, y el presupuesto no utilizado se transfiere de nuevo a la siguiente porción. Esto se aprecia mejor en el gráfico de la parte superior derecha, ya que el presupuesto se gasta y se repone a lo largo de la retropropagación.

Cabe destacar que este método de distribuir el error produjo los mejores resultados (más porciones retropropagadas), en comparación con el primer experimento de este notebook y el ejemplo inmediatamente anterior. Todos estos ejemplos asignaron un presupuesto de error de .018, pero la retropropagación se comportó de forma distinta según cómo se distribuyó ese presupuesto.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

Trabajar con varios observables​

El método qiskit_addon_obp.backpropagate permite pasar una secuencia de observables. Esto simplifica el flujo de trabajo cuando se manejan varios observables objetivo.

Lo mencionamos explícitamente aquí para enseñarte cómo la estrategia de truncamiento maneja un caso así. Para este ejemplo, añadimos un observable adicional al que hemos usado hasta ahora:

obs = [SparsePauliOp("IIIIIZIIII"), SparsePauliOp("IIIIIXIIII")]

Por último, repetimos el primer experimento de este tutorial, pero con dos observables hacia los que retropropagar el circuito.

En este ejemplo, esto no afecta al número de porciones que se pudieron retropropagar. Sin embargo, vemos que los dos observables dieron lugar a diferentes números de términos de Pauli y de grupos conmutantes.

truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"The new first observable contains {len(bp_obs[0])} terms and {len(bp_obs[0].group_commuting(qubit_wise=True))} commuting groups."
)
print(
f"The new second observable contains {len(bp_obs[1])} terms and {len(bp_obs[1].group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
The new first observable contains 29 terms and 10 commuting groups.
The new second observable contains 23 terms and 8 commuting groups.

Los gráficos siguientes ayudan a ilustrar cómo el algoritmo de retropropagación maneja varios observables.

Primero, los gráficos de la parte superior izquierda, la parte superior derecha y la parte inferior izquierda muestran que el presupuesto para truncar términos se establece individualmente para cada observable. En otras palabras, ambos observables pueden truncar términos asumiendo un error de 0.001 por porción retropropagada. Debido a la naturaleza distinta de los observables, esto resulta en un consumo diferente del presupuesto. En este ejemplo, presentan mucho solapamiento, algo que no siempre ocurre.

El gráfico de la parte inferior derecha ilustra que max_qwc_groups tiene en cuenta todos los observables. Esto significa que los términos de todos los observables se agrupan para llegar a un número final de grupos conmutantes por qubits (qubit-wise), que se compara con max_qwc_paulis. Lo mismo se hace con el umbral max_paulis (que no se trata en este notebook), el cual permite establecer un límite en el número de términos de Pauli entre todos los observables.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell