Skip to main content
IBM Quantum Platform

Cancelación probabilística de errores con conos de luz sombreados

Tiempo de ejecución estimado: 10 minutos en un procesador Heron (NOTA: Se trata únicamente de una estimación. (El tiempo de ejecución puede variar.)


Resultados del aprendizaje

Una vez completado este tutorial, los usuarios deberían comprender:

  • Qué es la cancelación probabilística de errores (PEC) y por qué su sobrecarga de muestreo γ2\gamma^2 crece exponencialmente con el ruido total que actúa sobre el circuito
  • Cómo los conos de luz sombreados (SLC) limitan la contribución de cada término de ruido a la magnitud observable objetivo, de modo que puedas destinar el presupuesto de mitigación a donde realmente importa; y cómo, más allá de la reducción de la sobrecarga sin sesgo que ya proporciona un cono de luz puramente geométrico, el sombreado te permite cambiar un sesgo residual limitado por una sobrecarga de muestreo aún menor
  • Cómo aprender el ruido de capa con NoiseLearnerV3 e inyectar antirruido a través de samplomatic y la Executor primitiva
  • Cómo combinar PEC y PEC+SLC con TREX y la postselección para estimar un valor esperado en hardware

Requisitos previos

Recomendamos a los usuarios que se familiaricen con los siguientes temas antes de seguir este tutorial:


En segundo plano

Este tutorial muestra cómo mitigar los errores utilizando el complemento «Shaded Light Cone» (SLC). Este complemento es una evolución de la técnica de cancelación probabilística de errores (PEC), en la que el usuario determina el ruido de capas concretas de un circuito y, a continuación, lo cancela mediante la aplicación de puertas de un solo qubit y técnicas de posprocesamiento. En comparación con otros métodos, el PEC ofrece límites más sólidos sobre el sesgo del resultado mitigado, pero tiende a suponer una mayor carga en cuanto al tiempo de la QPU. Durante el PEC, para compensar la atenuación del valor esperado debida al ruido, el resultado medio se reescala por un factor de γ=exp(l,σ2λl,σ)\gamma = \exp(\sum_{l,\sigma} 2\lambda_{l,\sigma}), donde λl,σ\lambda_{l,\sigma} es la tasa de error de Pauli σ\sigma aprendida en la capa ll del circuito. Este reescalado aumenta la varianza en un factor de γ2\gamma^2 y, por lo tanto, también multiplica por γ2\gamma^2 el número de ejecuciones de circuitos necesarias en la QPU, lo que denominamos «coste de muestreo» o «sobrecarga de muestreo». Dado que « γ\gamma » crece de forma exponencial, el PEC suele limitarse a circuitos poco profundos o con pocos qubits. Para obtener más información sobre la PEC, consulta el artículo «Cancelación probabilística de errores con modelos de Pauli-Lindblad dispersos en procesadores cuánticos con ruido ».

Si logramos identificar los errores que no es necesario mitigar, podremos reducir este coste de muestreo de forma exponencial. Un primer paso en esta dirección es la implementación de una mitigación de errores con conciencia local, que utiliza un «cono de luz» convencional de cálculo rápido para reducir la sobrecarga de la PEC al limitar la sensibilidad de una observable a los errores en todo el circuito, lo que amplía la viabilidad de la PEC a escalas mayores para algunos problemas. Los errores que se produzcan fuera de este cono de luz no pueden afectar al resultado medido y, por lo tanto, pueden excluirse del procedimiento de cancelación de errores. Esta exclusión reduce la sobrecarga del muestreo, en algunos casos de forma considerable, sin introducir sesgos adicionales. En concreto, para medir una observable local OO de un circuito de profundidad fija, la sobrecarga de muestreo necesaria acaba estabilizándose al aumentar el número de qubits del circuito (véase la fig. 2b en «Locality and error mitigation of quantum circuits »).

Los conos de luz sombreados (SLC) van más allá, ya que utilizan simulaciones clásicas para limitar con mayor precisión la sensibilidad a los errores en todo el circuito. Esto canjea algo de tiempo de la QPU por tiempo de la CPU y reduce la sobrecarga de muestreo necesaria para renormalizar el sesgo. En lugar de un límite absoluto, a cada error potencial del circuito se le asigna un «grado» que establece un límite superior a la susceptibilidad del valor esperado de la magnitud observable frente a ese error. (En el caso de los límites de propagación hacia atrás que se presentan a continuación, esta susceptibilidad también depende del estado inicial del circuito —el estado en el que todos los valores son cero en los flujos de trabajo habituales y en este tutorial—.) Esta caracterización más precisa permite aplicaciones más eficientes y específicas de la PEC con una varianza reducida, al tiempo que ofrece al usuario la posibilidad de ajustar de forma controlada el sesgo en la estimación de la observable. Para más detalles, véase «Sombreado de cono de luz para la mitigación de errores cuánticos con aceleración clásica ».

Nuestro flujo de trabajo para el complemento SLC aprovecha la samplomatic biblioteca junto con las QuantumProgram clases y Executor añadidas a Qiskit Runtime en qiskit-ibm-runtimev0.47.0, lo que permite a los usuarios disponer de un control más modular de los ajustes de ejecución para la supresión y mitigación de errores, sin renunciar a la facilidad de uso. Para más información, consulta la guía sobre el modelo de ejecución dirigida.

Resumen del flujo de trabajo de mitigación de errores de SLC

Para modelar el ruido de la QPU, utilizamos un modelo de ruido de Pauli-Lindblad disperso con tasas de error de Pauli de uno y dos qubits, generadas localmente en cada qubit y en cada borde del dispositivo. Con esta opción, el flujo de trabajo de mitigación de errores de SLC que se presenta en este tutorial es el siguiente:

a. CPU — Impacto limitado por error de los errores de Pauli de uno y dos qubits

  1. Propagación hacia adelante (efecto ligado en la observable). Propaga cada error hasta el final del circuito y calcula su conmutador con el observable.
    • Truncar los términos del operador durante la evolución para que el cálculo resulte manejable.
    • Restringe aún más estos límites mediante una retropropagación aproximada de la observable basada en los límites de velocidad cuánticos.
  2. Propagación hacia atrás (efecto limitado sobre el estado inicial). Propaga cada error hasta el inicio del circuito y calcula su conmutador con el estado inicial.

b. QPU — Conoce los índices de ruido. Utiliza NoiseLearnerV3 para estimar los parámetros del modelo de ruido de Pauli-Lindblad.

c. CPU — Dar prioridad a las medidas de mitigación

  1. Actualizar los límites fusionados con las tasas de ruido aprendidas. Combinar los límites hacia adelante y hacia atrás calculados previamente y actualizarlos con las tasas de ruido aprendidas.
  2. Clasifica los componentes de ruido que deben mitigarse utilizando los límites calculados y las tasas aprendidas. Prioriza cada posible error de ruido en función de su impacto estimado en el sesgo y del coste asociado a su corrección.

d. QPU: introduce el código antirruido y ejecútalo. Ejecuta el circuito en cuestión con el antirruido (ruido inverso) especificado mediante Box anotaciones.

decir, CPU — Estimación observable. Calcular el valor esperado, aplicando una postselección basada en la medición para reducir el impacto del ruido no markoviano.

Resumen sobre el aprendizaje con ruido

El aprendizaje del ruido es un paso habitual en varios métodos de mitigación de errores, llevado a cabo por el «aprendiz de ruido»; también aparece en el tutorial sobre amplificación probabilística de errores. En NoiseLearnerV3, puedes identificar específicamente las capas de ruido que se van a aprender como CircuitInstruction objetos, de modo que puedas calcular los límites de ruido SLC deseados para cada capa tal y como se ha descrito anteriormente. El modelo de Pauli-Lindblad proporciona los coeficientes que se deben utilizar en la priorización PEC+SLC. generate_noise_model_paulisLa forma en que se agrupan las puertas en capas puede determinarse utilizando las generate_boxing_pass_manager funciones de conveniencia y find_unique_box_instructions , para luego introducir los resultados en la función de utilidad SLC, tal y como se describe en el paso 2 a continuación.

Parte 1.
Parte 2.
Parte 3.
Capas de puertas de dos qubits con giro de PauliRepite los pares de capas idénticas y aprende el ruidoCalcular la fidelidad (error para cada canal de ruido)
Giros de PauliAprender sobre las capasAjuste de la curva

Resumen del posprocesamiento

Tras ejecutar el código en el hardware cuántico utilizando los samplomatic marcos y Executor , convertimos nuestras mediciones de cadenas de bits en el valor observable deseado. En este tutorial, mostramos el flujo de trabajo en un circuito de Ising simétrico (construido en el paso 1 más abajo), en el que el valor esperado ideal de la observable medida es 1, ya que la segunda mitad del circuito anula la primera. Al calcular el valor observable con la executor_expectation_values función, aplicamos algunas técnicas de posprocesamiento que reducen el impacto del ruido. Entre ellas se incluyen la eliminación de las tomas afectadas por ruido no markoviano, la mitigación de los errores de lectura y la consideración de los detalles de nuestra implementación de PEC. Los detalles se explican en el paso 4, más abajo.


Requisitos

Antes de empezar este tutorial, asegúrate de tener instalado lo siguiente:

  • Qiskit SDK v2.2 o posterior, con soporte para visualización
  • Qiskit Runtime v0.47 o posterior (pip install qiskit-ibm-runtime)
  • Complemento «Shaded lightcones» de Qiskit v0.1 o posterior (pip install qiskit-addon-slc)
  • Complemento Qiskit utils v0.3 o posterior (pip install qiskit-addon-utils)
  • Samplomatic v0.13 o o posterior (pip install samplomatic)

Configuración

En primer lugar, importa los paquetes y las funciones necesarios para ejecutar este cuaderno.

from multiprocessing import set_start_method

# Setting this value prevents itertools.starmap deadlock on UNIX systems
set_start_method("spawn")

# Needed to prevent PySCF from parallelizing internally (SLC only)
%set_env OMP_NUM_THREADS=1

Output:

env: OMP_NUM_THREADS=1
import numpy as np
from matplotlib import pyplot as plt

from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager

from qiskit_ibm_runtime import (
    QiskitRuntimeService,
    QuantumProgram,
    Executor,
    NoiseLearnerV3,
)

import samplomatic
from samplomatic.utils import find_unique_box_instructions
from samplomatic.transpiler import generate_boxing_pass_manager

from qiskit_addon_utils.exp_vals.measurement_bases import (
    get_measurement_bases,
)
from qiskit_addon_utils.exp_vals.expectation_values import (
    executor_expectation_values,
)
from qiskit_addon_utils.noise_management import (
    gamma_from_noisy_boxes,
    trex_factors,
)
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
    AddPostSelectionMeasures,
    AddSpectatorMeasures,
)

from qiskit_addon_slc.bounds import (
    compute_backward_bounds,
    compute_forward_bounds,
    compute_local_scales,
    merge_bounds,
    tighten_with_speed_limit,
)
from qiskit_addon_slc.utils import (
    generate_noise_model_paulis,
    map_modifier_ref_to_ref,
)
from qiskit_addon_slc.visualization import draw_shaded_lightcone

Ejemplo de simulador a pequeña escala

Al igual que otros métodos de mitigación de errores basados en el aprendizaje, el PEC con conos de luz sombreados mitiga el ruido físico de un procesador cuántico específico, por lo que depende de servicios de hardware que no tienen un equivalente significativo en un simulador ideal:

  • NoiseLearnerV3 caracteriza experimentalmente el canal de ruido disperso de Pauli-Lindblad en cada una de las capas únicas de dos qubits. En un simulador silencioso no hay ruido que cancelar.
  • El Executor «primitive» muestrea los circuitos retorcidos y con inyección de antirruido generados por samplomatic en un backend.

El cálculo del límite del cono de luz sombreado es clásico, pero solo tiene sentido en relación con las tasas de ruido del hardware aprendidas, que determinan el presupuesto de mitigación y la sobrecarga de muestreo. Por estas razones, omitimos el ejemplo del simulador a pequeña escala y mostramos el flujo de trabajo completo de PEC+SLC directamente en el hardware, desglosando a continuación cada paso del patrón de Qiskit.


Ejemplo de hardware a gran escala

Ejecutamos el flujo de trabajo completo de PEC+SLC en un circuito de Ising duplicado de 20 qubits, ejecutado en un hardware de l IBM Quantum®, siguiendo los cuatro pasos de un patrón de Qiskit.

Paso 1: Analizar el problema

Nuestra demostración se ha organizado de la siguiente manera:

  1. Hemos optado por realizar la evolución temporal bajo un hamiltoniano de cadena de Ising de tipo « 1D ». La cadena de Ising « 1D » ofrece una estructura de circuitos muy densa, lo que resulta muy útil para mostrar implementaciones de PEC.
  2. Hemos decidido medir la magnitud observable « X6Z13\langle X_6 Z_{13}\rangle », que contiene un componente « XX » y otro « ZZ ». Esto resulta útil para poner de relieve cómo los límites del SLC tratan de forma diferente los distintos tipos de errores.
  3. Queremos que el valor esperado ideal de esta magnitud observable sea exactamente 1, de modo que resulte fácil evaluar la calidad de las estimaciones mitigadas. Al invertir el circuito, ya tenemos casi todo resuelto: por cada puerta de la segunda mitad del circuito hay una puerta inversa en la primera mitad, de modo que el circuito ideal actúa como la identidad.
  4. Por último, para obtener el valor esperado ideal de 1, es necesario que el circuito comience —y, por lo tanto, gracias al efecto de simetría, idealmente también termine— en un estado propio +1 del observable. Dado que el observable contiene un componente no « ZZ », y que la Executor «primitiva» establece la base de medición deseada al final del circuito, anteponemos las puertas correspondientes de preparación de la base al inicio del circuito espejo mediante una prepare_basis función. Utilizamos la get_measurement_bases función para identificar qué puertas son necesarias y dónde colocarlas, al tiempo que tenemos en cuenta las sutilezas relacionadas con el índice de los qubits que se derivan de box las convenciones de anotación, tal y como se explica en la sección sobre la preparación de mediciones en base canónica.
# Width of the Ising chain (a free parameter of this demonstration)
num_qubits = 20

# The observable to measure: one X component (on qubit 6) and one Z component
# (on qubit 13), as motivated in "Step 1: Map the problem" above
target_obs_sparse = [("XZ", [6, 13], 1.0)]
observable = SparsePauliOp.from_sparse_list(
    target_obs_sparse, num_qubits=num_qubits
)
# Determine the measurement basis each qubit requires for this observable.
# The reverser is used during post-processing in Step 4.
bases_virt, reverser_virt = get_measurement_bases(observable)
# Free parameters of the demonstration circuit: the number of Trotter steps
# sets the circuit depth, and rx_angle is the transverse-field rotation angle
# applied in each step (pi/4 makes the dynamics non-Clifford)
num_trotter_steps = 10
rx_angle = np.pi / 4
def construct_ising_circuit(
    num_qubits: int,
    num_trotter_steps: int,
    rx_angle: float,
) -> QuantumCircuit:
    circuit = QuantumCircuit(num_qubits)

    for _ in range(num_trotter_steps):
        circuit.rx(rx_angle, range(num_qubits))
        for first_qubit in (1, 2):
            for idx in range(first_qubit, num_qubits, 2):
                # equivalent to Rzz(-pi/2):
                circuit.sdg([idx - 1, idx])
                circuit.cz(idx - 1, idx)

    return circuit


def prepare_basis(
    circuit: QuantumCircuit, basis: list[int]
) -> QuantumCircuit:
    # basis is a list of integer values from 0 to 3. These map to the basis measurement as:
    # 0 = I; 1 = Z; 2 = X; 3 = Y
    assert len(basis) == circuit.num_qubits

    out_circ = circuit.copy_empty_like()
    for qb, bas in enumerate(basis):
        if bas == 2:
            out_circ.h(qb)
        elif bas == 3:
            out_circ.rx(-np.pi / 2, qb)

    out_circ.barrier()
    out_circ.compose(circuit, inplace=True)
    return out_circ


def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
    mirror_circ = circuit.copy_empty_like()
    mirror_circ.compose(circuit.inverse(), inplace=True)
    mirror_circ.barrier()
    mirror_circ.compose(circuit, inplace=True)
    mirror_circ.measure_active()
    return mirror_circ
# Instantiate the mirrored circuit and prepend the basis-preparation gates
circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle)
mirrored_circuit = mirror_circuit(circuit)
mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])
mirrored_circuit.draw(
    "mpl", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False
)

Output:

Output of the previous code cell

Paso 2: Optimizar

Optimizamos los detalles relacionados con el circuito que se va a ejecutar, la magnitud observable que se va a medir y los parámetros de aprendizaje del ruido. Como punto de partida, seleccionamos el procesador Heron menos ocupado y lo instanciamos con las puertas fraccionarias activadas. Estas puertas fraccionarias permiten una mayor sensibilidad en algunos de nuestros filtros posteriores a la selección.

# Initialize the Qiskit Runtime service using your saved credentials
service = QiskitRuntimeService()

# Select the least busy Heron device. This tutorial targets Heron processors:
# the usage estimate and the non-Markovian-noise post-selection discussed in
# Step 4 are specific to this processor family.
backend = service.least_busy(
    operational=True,
    simulator=False,
    filters=lambda b: b.processor_type["family"] == "Heron",
)
# Re-fetch with fractional gates enabled (least_busy does not forward this)
# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.
backend = service.backend(backend.name, use_fractional_gates=True)
print(f"Selected backend: {backend.name}")

Output:

Selected backend: ibm_kingston

En primer lugar, transpilaremos nuestro circuito a instrucciones ISA, tal y como se requiere para su ejecución en nuestras QPU. La elección de los qubits físicos en los que se ejecuta el proceso tiene un gran impacto en la calidad de los resultados, ya que los tiempos de coherencia y la fidelidad de las puertas varían dentro de un mismo dispositivo y se desvían con el tiempo. Para realizar un experimento minucioso, puede resultar útil seleccionar manualmente una cadena de qubits basándose en experimentos de evaluación comparativa rápidos (véase, por ejemplo, el tutorial «Evaluación comparativa en tiempo real para la selección de qubits »). A efectos de este tutorial, utilizamos el transpilador de Qiskit para seleccionar una disposición basada en el mapa de acoplamiento del backend y sus datos de calibración más recientes.

isa_pm = generate_preset_pass_manager(backend=backend, optimization_level=1)

isa_circuit = isa_pm.run(mirrored_circuit)

# The chain of physical qubits selected by the transpiler
layout = isa_circuit.layout.final_index_layout()
print(f"Selected layout: {layout}")

isa_observable = observable.apply_layout(
    layout, num_qubits=isa_circuit.num_qubits
)

Output:

Selected layout: [71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 36, 21, 22, 23, 16, 3, 2]
wire_order = layout + [
    q for q in range(isa_circuit.num_qubits) if q not in layout
]
isa_circuit.draw(
    "mpl",
    fold=-1,
    scale=0.3,
    idle_wires=False,
    wire_order=wire_order,
    measure_arrows=False,
)

Output:

Output of the previous code cell

Enmarca el circuito

Para facilitar la implementación, utilizamos la generate_boxing_pass_manager fase de transpilación, que coloca las instrucciones del circuito en recuadros anotados. Estas casillas indican claramente dónde, en el caso del PEC, debe inyectarse la señal antirruido en el circuito. Para obtener más información sobre la configuración, consulta la documentación de Samplomatic.

Ten en cuenta que el flujo de trabajo de SLC requiere el uso de inject_noise_strategy="individual_modification" más adelante en el proceso, ya que esto nos permite identificar de forma unívoca las InjectNoise anotaciones de las BoxOp instrucciones del circuito. (Las casillas que no tengan ninguna InjectNoise anotación no se pueden identificar de esta manera.)

Caution

Los cálculos relacionados con SLC parten actualmente de la base de que las InjectNoise anotaciones se colocan con site="after" (véase qiskit-addon-slc#33 ). Dado que el valor por defecto de esta configuración ha cambiado en las últimas versiones de samplomatic (véase samplomatic#372 ), lo configuramos inject_noise_site="after" explícitamente a continuación.

La find_unique_box_instructions función recorre el circuito encapsulado proporcionado e identifica aquellas capas o mediciones de dos qubits ( 2Q ) que son únicas, con el fin de aprender sobre el ruido e inyectarlo.

# Box circuit with Twirl and InjectNoise annotations
boxes_pm = generate_boxing_pass_manager(
    twirling_strategy="active",
    inject_noise_strategy="individual_modification",
    inject_noise_site="after",
    inject_noise_targets="gates",
    measure_annotations="all",
)


boxed_circuit = boxes_pm.run(isa_circuit)

# Find the unique instructions (layers) from boxed circuit
unique_2q_instructions = find_unique_box_instructions(
    boxed_circuit, normalize_annotations=None, undress_boxes=True
)
boxed_circuit.draw(
    "mpl",
    fold=-1,
    scale=0.3,
    idle_wires=False,
    wire_order=wire_order,
    measure_arrows=False,
)

Output:

Output of the previous code cell

Preparar las mediciones de las bases canónicas

Ten en cuenta que debemos prestar especial atención a mantener el orden de los qubits. A continuación, presentamos el concepto de canonical_qubits como medio para actualizar adecuadamente el orden de los qubits al pasárselo a Executor, en función de cómo se captura dicho orden al empaquetar circuitos y determinar las instrucciones únicas. Consulta la documentación sobre la convención de ordenación de qubits para obtener más detalles.

# Determine the canonical qubits order
meas_box = boxed_circuit.data[-1]
canonical_qubits = [
    idx
    for idx, qubit in enumerate(boxed_circuit.qubits)
    if qubit in meas_box.qubits
]

# map canonical qubit to physical (isa) qubit
c_2_p = {c: p for c, p in enumerate(canonical_qubits)}
# map physical (isa) qubit to virtual qubit (index in original circuit)
p_2_v = {p: v for v, p in enumerate(layout)}
# compute map between virtual and canonical qubit indices.
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}

assert len(c_2_v) == num_qubits

bases_canon = [
    np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
    for base_i in bases_virt
]

Flujo de trabajo para el sombreado de cono de luz, el aprendizaje del ruido y la inyección antirruido

Note

En este tutorial, ejecutamos los cálculos de límites SLC antes de que finalice el aprendizaje del ruido, de modo que el circuito que se va a mitigar se ejecuta lo más cerca posible en el tiempo del modelo de ruido aprendido. En principio, este flujo de trabajo puede paralelizarse aún más: se puede ejecutar una tarea de aprendizaje del ruido mientras, en paralelo, se estiman los límites del ruido. En el caso de un circuito cuántico arbitrario, el cálculo limitado por el ruido puede escalar con una dependencia exponencial débil; por lo tanto, la paralelización de dicho cálculo (por ejemplo, entre muchos núcleos de CPU) permite obtener límites más ajustados para un tiempo de cálculo determinado, y tanto las ejecuciones de la QPU como los cálculos limitados pueden a su vez paralelizarse para lograr un flujo de trabajo más eficiente.

Predicción de los Paulis del modelo de ruido que se va a aprender

Antes de ejecutar cualquier proceso de aprendizaje con ruido en el hardware, enumeramos primero qué términos de error de Pauli incluirá nuestro modelo de ruido. Recordemos que modelamos el ruido de cada capa individual como un modelo de Pauli-Lindblad disperso, con una tasa de error por cada Pauli de peso uno ( XX, YY o ZZ ) en cada qubit activo, y otra por cada Pauli de peso dos en cada par conectado de qubits activos. La generate_noise_model_paulis función recorre cada capa encaajada única del circuito proporcionado y genera exactamente esta lista de términos de Pauli, teniendo en cuenta la conectividad de los qubits del circuito.

En otras palabras, este paso predice la estructura del modelo de ruido que NoiseLearnerV3 posteriormente se completará con las tasas aprendidas. Conocer esta estructura de antemano es lo que nos permite calcular un límite superior y un límite inferior para cada error potencial antes incluso de que se haya ejecutado el aprendizaje con ruido.

noise_model_paulis = generate_noise_model_paulis(
    unique_2q_instructions, backend.coupling_map, boxed_circuit
)
noise_model_rates = {ref: None for ref in noise_model_paulis}
a. Calcular los límites del avance

La compute_forward_bounds función evalúa las relaciones de conmutación entre las puertas de cada capa y los términos de Pauli generados anteriormente, en función de cómo afectan los errores de propagación hacia adelante al observable deseado AA. En el caso de las puertas que conmutan con los términos de Pauli, no se realiza ninguna acción. En el caso de las puertas de Clifford, se colocan al principio del circuito. En el caso de las puertas que no son de Clifford, calculamos de forma aproximada su influencia sobre las observables objetivo para, posteriormente, priorizarlas a la hora de la cancelación de ruido (una vez que se hayan fusionado todos los límites). Este límite se obtiene aplicando en primer lugar la norma de L2 (es decir, la raíz cuadrada de la suma de los cuadrados de los coeficientes de los términos de Pauli pertinentes). Cuando hay demasiados términos de qubits implicados, recurrimos a un límite menos estricto que utiliza la desigualdad triangular.

Configurar los parámetros de cálculo de límites

Los siguientes parámetros controlan los cálculos de límites clásicos:

  • slc_atol: la tolerancia absoluta de los cálculos. Los términos del operador cuyos coeficientes sean inferiores a este umbral se consideran insignificantes y se descartan.
  • slc_eigval_max_qubits: el número máximo de qubits para los que se evalúa el límite más estricto, basado en los valores propios ( L2-norm ). Los operadores que admiten un número de qubits superior a este recurren al límite más holgado de la desigualdad triangular descrito anteriormente.
  • slc_evolution_max_terms: el número máximo de términos de Pauli que se conservan al propagar los operadores a lo largo del circuito. Al truncar los términos más pequeños, el cálculo sigue siendo manejable, aunque a costa de obtener límites ligeramente menos precisos.
  • slc_num_processes: el número de procesos de la CPU entre los que se paraleliza el cálculo vinculado.
  • slc_timeout: el tiempo máximo (en segundos) para el cálculo del límite. A las capas a las que no se haya llegado dentro de este presupuesto se les asigna el límite trivial de 2.
Note

En este tutorial, se alcanza el tiempo de espera tal y como está previsto : lo mantenemos slc_timeout deliberadamente bajo para que el cuaderno se ejecute rápidamente y para demostrar que alcanzar el tiempo de espera es seguro. Los tonos calculados son límites superiores en cada etapa, por lo que alcanzar el tiempo de espera solo hace que los límites sean más amplios (triviales) para las capas a las que no se ha llegado; nunca subestima el efecto de un error. Esto se aprecia en las visualizaciones que aparecen a continuación, en las que las capas situadas más a la izquierda se rellenan por completo con el límite trivial. Aumentar el tiempo de espera (o el número de procesos) reduce los límites y disminuye aún más la sobrecarga de muestreo.

slc_atol = 1e-8
slc_eigval_max_qubits = 18
slc_evolution_max_terms = 1000
slc_num_processes = 8
slc_timeout = 60
forward_bounds = compute_forward_bounds(
    boxed_circuit,
    noise_model_paulis,
    isa_observable,
    evolution_max_terms=slc_evolution_max_terms,
    eigval_max_qubits=slc_eigval_max_qubits,
    atol=slc_atol,
    num_processes=slc_num_processes,
    timeout=slc_timeout,
)

Output:

Bounds computation timed out.

Visualiza el SLC para realizar una inspección manual

Se puede interpretar el comportamiento de los límites sombreados analizando cómo interactúan las mediciones y los términos de Pauli con los errores locales. Estos patrones son característicos de este problema de evolución temporal del hamiltoniano de Ising con «kicked» y también aparecen en el artículo «Lightcone Shading for Classically Accelerated Quantum Error Mitigation », con varias características reveladoras:

  • Podemos distinguir claramente los dos conos que se derivan de los dos Paulis distintos de la identidad en la observable.
  • Podemos observar que la medición X en el qubit 6 conmuta con el error X de la capa situada más a la derecha.
  • Podemos observar que el operador Z de Pauli del qubit 13 conmuta con el error Z de la capa más a la derecha.
  • Cuando se alcanza el tiempo de espera especificado anteriormente, las capas restantes de la izquierda se rellenan por completo con límites triviales de dos.
for p in "XYZ":
    display(
        draw_shaded_lightcone(
            boxed_circuit,
            forward_bounds,
            noise_model_paulis,
            pauli_filter=p,
            scale=0.15,
            fold=-1,
            idle_wires=False,
            wire_order=wire_order,
            measure_arrows=False,
        )
    )

Output:

Output of the previous code cell Output of the previous code cell Output of the previous code cell

b. Reducir los límites superiores con los límites de velocidad cuánticos

A continuación, reducimos los límites utilizando la tighten_with_speed_limit función. La extensión geométrica del cono de luz de la magnitud ya se ha tenido plenamente en cuenta en los límites hacia adelante calculados anteriormente. Lo que aporta esta función es una pasada de autoconsistencia que aprovecha la velocidad limitada a la que la información puede fluir a través del circuito —el cono de luz crece como máximo un qubit por capa de puertas de dos qubits— para ajustar los límites calculados previamente entre sí. La información circula en ambas direcciones:

  1. Desde los límites del conmutador hasta los límites de la observable retropropagada: si la observable conmuta con un error de tipo « ZZ » en un qubit, entonces no puede tener allí un componente significativo de « XX » o « YY ».
  2. De los límites del observable retropropagado a los límites del conmutador: si el observable tiene componentes de XX y YY muy pequeñas en un qubit, entonces debe conmutar casi por completo con ZZ en ese punto.
forward_bounds_tighter = tighten_with_speed_limit(
    forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)

Visualiza el SLC para realizar una inspección manual

En principio, este ajuste permite una transición más suave entre los límites calculados y los límites triviales asignados a las capas una vez superado el tiempo de espera. En este ejemplo, el efecto apenas se aprecia, ya que los conos de luz ya se han extendido hasta los bordes del circuito cuando se alcanza el tiempo de espera.

for p in "XYZ":
    display(
        draw_shaded_lightcone(
            boxed_circuit,
            forward_bounds_tighter,
            noise_model_paulis,
            pauli_filter=p,
            scale=0.15,
            fold=-1,
            idle_wires=False,
            wire_order=wire_order,
            measure_arrows=False,
        )
    )

Output:

Output of the previous code cell Output of the previous code cell Output of the previous code cell

c. Calcular los límites posteriores

Esta parte de la predicción del ruido evalúa cómo un error en una capa concreta puede afectar al estado de entrada ρ\rho. La compute_backward_bounds función invierte primero el circuito y elimina las puertas de medición, para luego realizar un análisis similar al que se llevó a cabo para los cálculos en sentido directo.

backward_bounds = compute_backward_bounds(
    boxed_circuit,
    noise_model_paulis,
    evolution_max_terms=slc_evolution_max_terms,
    num_processes=slc_num_processes,
    timeout=slc_timeout,
)

Visualiza el SLC para realizar una inspección manual

Al calcular los límites hacia atrás, podemos observar cómo la estructura del estado inicial determina el comportamiento inicial de la propagación del error:

  • Podemos ver claramente cómo los errores Z se combinan inicialmente con el estado inicial |0⟩.
  • Solo en el qubit 6, donde inicializamos el estado propio +1 de la base X, un error Z no conmuta, mientras que un error X sí lo hace.
for p in "XYZ":
    display(
        draw_shaded_lightcone(
            boxed_circuit,
            backward_bounds,
            noise_model_paulis,
            pauli_filter=p,
            scale=0.15,
            fold=-1,
            idle_wires=False,
            wire_order=wire_order,
            measure_arrows=False,
        )
    )

Output:

Output of the previous code cell Output of the previous code cell Output of the previous code cell

Vista previa de los límites fusionados sin tasas de ruido aprendidas

La merged_bounds función determina el punto del circuito en el que el cambio de los límites posteriores a los límites anteriores minimiza el sesgo total estimado sobre la observable deseada. Este sesgo se calcula como la suma de las contribuciones en sentido inverso de todas las ubicaciones de ruido anteriores a ese punto, más las contribuciones en sentido directo de todas las ubicaciones de ruido posteriores a él. Actualmente, esto se hace de forma uniforme para todos los qubits.

Nota importante

El punto óptimo en el que pasar de los límites hacia atrás a los límites hacia adelante depende de las tasas de ruido aprendidas. En esta fase aún no hemos establecido ninguna tasa: noise_model_rates se ha inicializado en None para cada capa, lo que merge_bounds interpreta como tasas de ruido uniformes. Por lo tanto, los límites fusionados que se muestran aquí son solo una vista previa; los volvemos a calcular con las tasas reales aprendidas en el paso 3.

merged_bounds = merge_bounds(
    boxed_circuit,
    forward_bounds_tighter,
    backward_bounds,
    noise_model_rates,
)

Output:

Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.

Visualiza el SLC para realizar una inspección manual

Tras fusionar los límites traseros y los límites delanteros más ajustados, queda claro el comportamiento de los SLC combinados:

  • La función anterior nos indica que se elige una partición en la que se produce el cambio de límites inferiores a límites superiores más estrictos.
  • Como podemos ver a continuación, los SLC contienen ahora límites superiores parciales hacia atrás y límites inferiores parciales hacia adelante.
for p in "XYZ":
    display(
        draw_shaded_lightcone(
            boxed_circuit,
            merged_bounds,
            noise_model_paulis,
            pauli_filter=p,
            scale=0.15,
            fold=-1,
            idle_wires=False,
            wire_order=wire_order,
            measure_arrows=False,
        )
    )

Output:

Output of the previous code cell Output of the previous code cell Output of the previous code cell

Paso 3: Ejecutar

En esta sección comenzamos la parte del flujo de trabajo en la que se utiliza un dispositivo cuántico real. Este método de mitigación de errores basado en el aprendizaje consta de dos pasos:

  1. Aprende el sonido utilizando NoiseLearnerV3.
  2. Ejecuta un circuito de mitigación de errores con los samplomatic marcos y Executor .

A partir de los errores limitados de nuestro circuito cuántico, determinamos las tasas de ruido asociadas para establecer prioridades en nuestro presupuesto de errores, calcular la sobrecarga de muestreo y ejecutar el código en una QPU.

a. Infórmate sobre los índices de ruido

El «noise learner» caracteriza los procesos de ruido que afectan a las puertas de uno o varios circuitos de interés, basándose en el modelo de ruido disperso de Pauli-Lindblad. El run() método inicia un trabajo de aprendizaje con ruido para las capas únicas de dos qubits proporcionadas, utilizando las opciones especificadas en la configuración del «noise-learner». Estas opciones controlan la estrategia de «Pauli-twirling», el número de aleatorizaciones y de intentos, las profundidades de aprendizaje y la selección posterior.

Además, elegimos deliberadamente el nivel de profundidad del aprendizaje. Una conclusión práctica para la mitigación basada en samplomatic el aprendizaje es que resulta muy beneficioso que la profundidad máxima del modelo de aprendizaje coincida con la profundidad del circuito que se desea mitigar. Dado que los « NLv3layer_pair_depths » se miden en pares de capas (una capa más su inversa), fijamos el valor más profundo en la mitad de la profundidad de la capa de dos qubits del circuito.

post_selection_enabled = True
# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer
# plus its inverse), so the deepest value is half the circuit's two-qubit-layer
# depth. Learning to this depth markedly improves the quality of the mitigation.
#
# We measure the two-qubit-layer depth on the pre-boxed ISA circuit: after
# boxing, every two-qubit gate is hidden inside a full-width ``BoxOp``, so a
# ``num_qubits == 2`` filter on ``boxed_circuit`` matches nothing (and
# ``QuantumCircuit.depth`` does not recurse into boxes).
depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2  # dividing by 2 since we want pairs

# Use a fixed schedule of learning depths, but drop any that exceed the circuit's
# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never
# learn deeper than the circuit being mitigated.
candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]
layer_pair_depths = sorted(
    {d for d in candidate_depths if d < max_layer_pair_depth}
    | {max_layer_pair_depth}
)

noise_learner_options = {
    "num_randomizations": 64,
    "shots_per_randomization": 128,
    "layer_pair_depths": layer_pair_depths,
    "post_selection": {
        "enable": post_selection_enabled,
        "strategy": "edge",
        "x_pulse_type": "rx",
    },
    "environment": {"job_tags": ["TUT_SLC"]},
}

noise_learner = NoiseLearnerV3(backend, noise_learner_options)
noise_learner_job = noise_learner.run(unique_2q_instructions)
noise_learner_result = noise_learner_job.result()
if post_selection_enabled:
    print(
        "Minimum fraction of shots kept for noise learning experiments: ",
        end="",
    )
    print(
        f"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}"
    )

Output:

Minimum fraction of shots kept for noise learning experiments: 0.71
# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap
refs_2_plm = noise_learner_result.to_dict(
    unique_2q_instructions, require_refs=False
)

b.i. Actualizar los límites fusionados con las tasas de ruido reales aprendidas

Ahora que se ha aprendido el modelo de ruido específico, podemos aplicar las tasas de ruido aprendidas a los límites de ruido predichos y obtener una determinación definitiva de qué límites tienen mayor impacto a la hora de minimizar el sesgo.

merged_bounds = merge_bounds(
    boxed_circuit,
    forward_bounds_tighter,
    backward_bounds,
    refs_2_plm,
)

Output:

Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.

b.ii. Calcular el local_scales para la ejecución en hardware

compute_local_scales analiza cada posible error de ruido en el circuito y calcula en qué medida ese error podría sesgar la medición final, así como cuánto costaría corregirlo. A continuación, clasifica los errores según lo conveniente que resulte mitigarlos y selecciona el subconjunto que reduzca el sesgo al máximo, sin sobrepasar el presupuesto de coste de muestreo permitido (o alcanzando la precisión deseada). El resultado es un conjunto de factores de escalado que indican qué errores se mitigarán de forma activa y cuáles no se mitigarán (local_scales), junto con la sobrecarga total prevista del coste de muestreo (sampling_costs) y el sesgo restante (residual_bias_bound).

La capacidad de controlar el sesgo residual deseado es una característica fundamental de la implementación SLC del PEC. Mientras que en la implementación original la sobrecarga de muestreo siempre tenía como objetivo un sesgo nulo, podemos ajustar la sobrecarga de muestreo necesaria a cambio de un sesgo residual esperado. Esto permite al usuario mantenerse dentro de un presupuesto de muestreo fijo, lo que puede resultar especialmente útil a la hora de crear un prototipo inicial de un flujo de trabajo.

id_map = map_modifier_ref_to_ref(boxed_circuit)
summed_rates = 0.0
for box_id, noise_id in id_map.items():
    learned_plm = refs_2_plm[noise_id]
    summed_rates += np.sum(learned_plm.rates)
    # print(f"{box_id}:\tgamma = {np.exp(2 * summed_rates):1.6e}\tsampling cost = {np.exp(4 * summed_rates):1.6e}")
total_gamma = np.exp(2 * summed_rates)
print(
    f"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}"
)

Output:

Full PEC gamma=12.750876317157042, sampling cost (gamma^2) = 162.58484685543633
biases = []
costs = []
for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():
    _, cost_, bias_ = compute_local_scales(
        boxed_circuit,
        merged_bounds,
        refs_2_plm,
        sampling_cost_budget=np.inf,
        bias_tolerance=bias,
    )
    biases.append(bias_)
    costs.append(cost_)

Equilibrar la sobrecarga de muestreo con el sesgo residual

xticks = np.arange(0, 11)

fig, ax = plt.subplots()
ax.scatter(
    [0], [total_gamma**2], marker="D", c="tab:orange", label="full PEC"
)
ax.plot(
    100 * np.array(biases),
    np.array(costs),
    "o-",
    c="tab:blue",
    label="PEC+SLC",
)
ax.set_yscale("log")
ax.set_xticks(xticks, [f"{x:.1f}" for x in xticks])

ax.set_xlabel("Remaining bias [%]")
ax.set_ylabel(r"Sampling overhead, $\gamma^2$")
ax.grid()
ax.legend()
fig.suptitle("PEC sampling overhead reduction due to SLC")

Output:

Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')
Output of the previous code cell
chosen_bias_thres = 0.1
local_scales, sampling_cost, residual_bias_bound = compute_local_scales(
    boxed_circuit,
    merged_bounds,
    refs_2_plm,
    sampling_cost_budget=np.inf,
    bias_tolerance=chosen_bias_thres,
)
print(
    f"PEC+SLC sampling cost (gamma^2) = {sampling_cost} "
    f"w/ remaining bias = {100 * residual_bias_bound:.1f}%"
)

Output:

PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%

c. Ejecuta el circuito que te interese con antinoise

c.i. Prepara la plantilla del circuito utilizando samplex

El samplex es el resultado del build método de Samplomatic, que codifica toda la información necesaria para generar parámetros aleatorios para template_circuit. A continuación, estos se utilizan para configurar los QuantumProgram objetos, que a su vez se ejecutan en una QPU con la Executor primitiva. Cada uno QuantumProgram puede contener varios elementos, que puedes considerar como un par formado por template y samplex.

Consulta el tutorial «Hello» de Samplomatic para obtener más detalles.

# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)
# Set up postselection if it's been enabled
if post_selection_enabled:
    # Set up post selection PM (to add PS instructions)
    post_selection_pm = PassManager(
        [
            AddSpectatorMeasures(backend.coupling_map),
            AddPostSelectionMeasures(x_pulse_type="rx"),
        ]
    )
    final_template_circuit = post_selection_pm.run(template_circuit)
else:
    final_template_circuit = template_circuit

c.ii. Configura el QuantumProgram

num_randomizations = 4096
shots_per_randomization = 64
chunk_size = 256
# Set up QuantumProgram
program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)

# no EM

# Collect up a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
    f"noise_scales.{ref}": float(0) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
    samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)

program.append_samplex_item(
    circuit=final_template_circuit,
    samplex=samplex,
    samplex_arguments=samplex_arguments,
    shape=(num_randomizations,),
    chunk_size=chunk_size,
)

# plain PEC

# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
    f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
    samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)

program.append_samplex_item(
    circuit=final_template_circuit,
    samplex=samplex,
    samplex_arguments=samplex_arguments,
    shape=(num_randomizations,),
    chunk_size=chunk_size,
)

# PEC+SLC

# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
    f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
samplex_inputs |= {"local_scales": local_scales}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
    samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)

program.append_samplex_item(
    circuit=final_template_circuit,
    samplex=samplex,
    samplex_arguments=samplex_arguments,
    shape=(num_randomizations,),
    chunk_size=chunk_size,
)

c.iii. Ejecutar el programa con la Executor primitiva

executor = Executor(backend)
job_exec = executor.run(program)
results_exec = job_exec.result()

Paso 4: Posprocesado

Al calcular el valor esperado final que nos interesa mediante executor_expectation_values, aplicamos algunas técnicas de posprocesamiento para asegurarnos de obtener resultados de la mayor calidad posible. En primer lugar, aplicamos nuestro método de extinción de errores de lectura con giro (TREX), que tiene en cuenta cualquier error que se produzca durante el proceso de lectura. A continuación, corregimos los errores debidos al ruido no markoviano en nuestros backends de Heron mediante un método de postselección. Este método mide los qubits activos y los espectadores, a continuación aplica una rotación lenta a cada qubit y, por último, vuelve a realizar la medición. En los casos en que las dos mediciones no confirmen que el qubit haya cambiado de estado como se esperaba, estas mediciones se descartan aplicando un mask del PostSelector. En el cálculo de la máscara, se puede establecer una estrategia específica para filtrar en función de nodos de un solo qubit o de aristas de «espectador» adyacentes, lo que puede influir tanto en el número de tramos filtrados como en la calidad de los resultados.

measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)
post_selection_strategy = "node"
def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):
    meas = datum["meas"]
    flips = datum["measurement_flips.meas"]
    signs = datum.get("pauli_signs", None)

    meas_basis_axis = None
    avg_axis = 0

    mask = None
    if ps and post_selection_enabled:
        # Post-select the results
        post_selector = PostSelector.from_circuit(
            circuit=final_template_circuit, coupling_map=backend.coupling_map
        )

        # Compute the ps mask for filtering results
        mask = post_selector.compute_mask(
            datum, strategy=post_selection_strategy
        )

        # Compute fraction of shots kept from post selection
        total_num_shots = num_randomizations * shots_per_randomization
        ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)
        print(
            f"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):"
        )

    results = []
    for i in range(steps, num_randomizations + 1, steps):
        # Compute mitigated expvals w/out post-selection
        res = executor_expectation_values(
            meas[:i],
            reverser_virt,
            meas_basis_axis,
            avg_axis=avg_axis,
            measurement_flips=flips[:i],
            pauli_signs=signs[:i] if signs is not None else None,
            postselect_mask=mask[:i] if mask is not None else None,
            rescale_factors=trex_scale_factors if trex else None,
            gamma_factor=gamma,
        )
        results.append(res[0])
    return results
gamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)
gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)
steps = 16
results = {}

for label, result_idx, gamma, use_ps, use_trex in [
    ("PEC", 1, gamma_pec, True, True),
    ("PEC+SLC", 2, gamma_slc, True, True),
    ("Unmitigated", 0, None, False, False),
]:
    res = post_process_conv(
        results_exec[result_idx],
        steps=steps,
        gamma=gamma,
        ps=use_ps,
        trex=use_trex,
    )
    results[label] = res

Output:

With node-based post selection (24.1% of shots kept):
With node-based post selection (24.2% of shots kept):

A partir del análisis de los resultados experimentales, podemos comparar directamente el comportamiento de los distintos enfoques: PEC, PEC combinado con SLC y la referencia sin medidas de mitigación. Algunos detalles concretos que cabe destacar:

  • El resultado sin ajustar se sitúa muy por fuera de la banda de sesgo del 10 % (véase 0.35 ) y no se ve afectado por el número de aleatorizaciones.
  • En este dispositivo, el PEC completo conlleva una sobrecarga de muestreo de γ21.6×102\gamma^2 \approx 1.6\times10^2. Tras grandes oscilaciones iniciales, la estimación del PEC simple recupera la señal y se estabiliza en torno a 1.1, sobrepasando ligeramente el valor exacto. Cabe señalar que la banda de sesgo del 10 % solo se aplica al estimador PEC+SLC: el PEC simple es, en principio, insesgado, por lo que esta desviación residual no está cubierta por una garantía de sesgo, sino que refleja fluctuaciones estadísticas e imperfecciones en el modelo de ruido aprendido, lo cual es razonable dado el tamaño de este problema.
  • El SLC reduce la sobrecarga en un ~3.4-fold (hasta γ248\gamma^2 \approx 48, lo que supone un límite de sesgo residual de aproximadamente el 10 %). El PEC+SLC también presenta fuertes fluctuaciones con pocas aleatorizaciones, llegando incluso inicialmente a la región no física, pero su estimación converge de forma constante hacia el valor exacto, estabilizándose cerca de 1.0, bien dentro de la banda. Su precisión es igual o superior a la del PEC simple, con un coste de muestreo de aproximadamente un tercio, lo que demuestra las ventajas del sombreado de cono de luz.
  • Las barras de error de ambas estimaciones mitigadas se reducen a medida que se acumulan las aleatorizaciones; para un número fijo de aleatorizaciones, el estimador PEC+SLC presenta la menor varianza estadística, lo que concuerda con su menor « γ2\gamma^2 ».

Ten en cuenta que las tasas de ruido aprendidas (y, por lo tanto, las sobrecargas y las estimaciones mitigadas) dependen del backend y de su calibración en el momento de la ejecución, por lo que volver a ejecutar este cuaderno puede dar lugar a valores cuantitativamente diferentes.

fig, ax = plt.subplots(1, 1, figsize=(12, 6))

ax.axhline(1.0, color="black", label="Exact")
ax.fill_between(
    [-50, 4100], -10, 0, color="grey", alpha=0.25, label="Unphysical"
)
ax.fill_between([-50, 4100], 1, 10, color="grey", alpha=0.25)
ax.fill_between(
    [-50, 4100], 0.9, 1.1, color="red", alpha=0.25, label="10% bias"
)

for label, res in results.items():
    ax.errorbar(
        list(range(steps, num_randomizations + 1, steps)),
        [r[0] for r in res],
        yerr=[r[1] for r in res],
        alpha=0.75,
        marker="o",
        linestyle="",
        markerfacecolor="none",
        label=label,
    )

ax.set_ylabel(r"$\langle X_{6}Z_{13}\rangle$")
ax.set_xlabel("# randomizations")
ax.grid()

ax.legend(ncols=2)
ax.set_ylim([-0.1, 2.0])
ax.set_xlim([-50, 4100])

Output:

(-50.0, 4100.0)
Output of the previous code cell

Próximos pasos

Si este trabajo te ha parecido interesante, quizá te interese el siguiente material:

¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.