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?

PEC con TREX

La cancelación probabilística de errores (PEC) es una técnica para mitigar el ruido cuántico en el cálculo de valores esperados de observables. Te permite calcular una estimación insesgada de los valores esperados. Esta técnica funciona ejecutando un conjunto de circuitos ruidosos en los que se inyecta la inversa de un modelo de ruido aprendido. Luego, el modelo de ruido se aproxima a un canal de Pauli mediante Pauli twirling (también conocido como compilación aleatorizada). Estos modelos de ruido pueden aprenderse de forma eficiente y mitigarse mediante PEC (consulta Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors para más detalles).

PEC

Twirled Readout Error eXtinction (TREX) es una técnica que mitiga los errores de medición y es útil para estimar valores esperados de observables de Pauli. Se basa en el twirling de mediciones (inyectar aleatoriamente una puerta X antes de la medición e invertir el resultado medido), lo que diagonaliza la matriz de transferencia del error de lectura y facilita su inversión. Un término de reescalado se aprende del canal de ruido diagonal midiendo circuitos de calibración con twirling inicializados en el estado cero.

Measurement twirling

Esta guía muestra cómo usar PEC para mitigar el ruido de las puertas y combinarlo con TREX para mitigar también el ruido de medición al estimar valores esperados de observables.

El flujo de trabajo es el siguiente:

  1. Especifica un circuito y un observable.
  2. Calcula valores esperados exactos usando el simulador de vector de estado de Qiskit.
  3. Calcula valores esperados con ruido y sin mitigar.
  4. Aprende el ruido de las capas de entrelazamiento usando qiskit-noise-learning.
  5. Prepara un programa PEC + TREX y añádele un circuito de calibración TREX.
  6. Compara los valores esperados mitigados.

Especifica un circuito y un observable​

Prepara un circuito espejo de una evolución temporal con Trotter de un modelo de Ising lineal de campo transversal en ocho qubits. Refleja el circuito para cancelar la unitaria del circuito y formar un circuito identidad. Esto te permite calcular fácilmente el valor esperado ideal de cada observable. Además, el espejado duplica la profundidad del circuito, lo que a su vez duplica el efecto del ruido.

Usa el ángulo θ=π2\theta= \frac{\pi}{2} para que todas las puertas del circuito pertenezcan al grupo de Clifford, lo que permite una simulación más rápida con el simulador stabilizer.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "qiskit_mitigation": "qiskit-mitigation", "qiskit_noise_learning": "qiskit-noise-learning", "samplomatic": "samplomatic"}
_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 warnings

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

warnings.filterwarnings("ignore")

def construct_ising_circuit(
num_qubits: int,
num_trotter_steps: int,
rx_angle: float,
barrier: bool = True,
) -> QuantumCircuit:
circuit = QuantumCircuit(num_qubits)

for _step in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
if barrier:
circuit.barrier()
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)
if barrier:
circuit.barrier()

return circuit

def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
mirror_circ = circuit.copy_empty_like()
mirror_circ.compose(circuit, inplace=True)
mirror_circ.barrier()
mirror_circ.compose(circuit.inverse(), inplace=True)
mirror_circ.measure_active()
return mirror_circ

# define observables
num_qubits = 8
observables = [
SparsePauliOp("I" * ((num_qubits - 1) - q) + "Z" + "I" * q)
for q in range(num_qubits)
]

# Instantiate circuit
num_trotter_steps = 5
rx_angle = np.pi / 2

circuit = construct_ising_circuit(
num_qubits, num_trotter_steps, rx_angle, barrier=False
)
mirrored_circuit = mirror_circuit(circuit)
print("Visualization of 1 mirrored Trotter step")
mirror_circuit(
construct_ising_circuit(num_qubits, 1, rx_angle, barrier=False)
).draw("mpl", scale=0.7)
Visualization of 1 mirrored Trotter step

Output of the previous code cell

Calcula valores esperados con ruido y sin mitigar​

Un QuantumProgram contiene un circuito cuántico y un samplex en un formato adecuado para la primitiva Executor. MitigationTask.prepare construye un QuantumProgram a partir del circuito, los observables y los valores de los parámetros, y agrupa el circuito en cajas con las opciones de boxing indicadas. Establece la opción de boxing measure_annotations en "change_basis" para rotar las mediciones a las bases requeridas, y añade add_tags: "unique_box" para que el simulador pueda asignar un modelo de ruido a las cajas del circuito. El programa se somete a Pauli twirling por defecto, lo que en hardware convierte el ruido coherente en el ruido de Pauli que asumen los métodos de mitigación. num_randomizations establece cuántas instancias con twirling de cada circuito se muestrean y shots_per_randomization establece cuántos shots recibe cada instancia, de modo que su producto es el límite de shots por circuito.

Si tienes acceso a QPU, puedes ejecutar el programa con un Executor creado a partir de un backend de QiskitRuntimeService. Este ejemplo usa un AerSimulator para ejecutar el programa localmente. Su opción de simulador layer_noise_model inyecta un modelo de ruido de Pauli-Lindblad aleatorio en cada capa de puertas de entrelazamiento, generado con random_pauli_lindblad_noise de qiskit-mitigation con un error por puerta en capa (EPLG) de 0.003, típico de los QPU Heron actuales.

import matplotlib.pyplot as plt
from qiskit.circuit import CircuitInstruction
from qiskit.quantum_info import PauliLindbladMap, pauli_basis
from qiskit.transpiler import PassManager
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import Executor
from qiskit_mitigation import MitigationTask, find_combined_unique_layers
from qiskit_noise_learning.aer_executor.insert_noise_pass import (
InsertNoisePass,
)
from samplomatic import Tag
from samplomatic.utils import get_annotation

def generate_random_pauli_lindblad_noise(
layers,
num_qubits: int,
eplg: float = 3e-3,
readout_noise_scale: float = 3e-2,
seed: int | None = None,
) -> tuple[
list[tuple[CircuitInstruction, PauliLindbladMap]], PauliLindbladMap
]:
"""Generate random Pauli-Lindblad noise over the full Pauli basis."""
layer_noise_model = []
for layer in layers:
layer_instructions = layer.operation.params[0]
contain_measurement = any(
inst.name.startswith("measure") for inst in layer_instructions
)
if not contain_measurement:
edges = [
(inst.qubits[0]._index, inst.qubits[1]._index)
for inst in layer_instructions
] # extract the edges of the layer

basis_paulis = [p for p in pauli_basis(2) if np.sum(p.x + p.z)]
rng = np.random.default_rng(seed=seed)
num_generators = len(basis_paulis)
rates = rng.random(len(basis_paulis)) * eplg / num_generators

layer_noise = PauliLindbladMap.from_sparse_list(
[
(pauli.to_label(), edge, rate)
for pauli, rate in zip(basis_paulis, rates, strict=True)
for edge in edges
],
num_qubits=num_qubits,
)
layer_noise_model.append((layer, layer_noise))
# generate a readout noise model
rng = np.random.default_rng(seed=seed)
rates = rng.random(num_qubits) * readout_noise_scale
readout_noise_model = PauliLindbladMap.from_sparse_list(
[("X", [q], rate) for q, rate in enumerate(rates)],
num_qubits=num_qubits,
)
return layer_noise_model, readout_noise_model

def add_readout_noise(program, readout_noise_model):
"""Injects readout noise to the circuits in a quantum program."""
readout_noise_map = {}
for item in program.items:
readout_tag = item.circuit[-1].label[
-4:
] # extract the readout layer tag from the circuit
readout_noise_map[readout_tag] = readout_noise_model
item.circuit = PassManager(
[InsertNoisePass(noise_dict=readout_noise_map, noise_after=False)]
).run(item.circuit)

num_randomizations = 300
shots_per_randomization = 64

boxing_options = {"add_tags": "unique_box"}

# find the unique layers to generate the layered noise for
box_layers = find_combined_unique_layers(
[mirrored_circuit], custom_boxing_options=boxing_options
)
layer_noise_model, readout_noise_model = generate_random_pauli_lindblad_noise(
box_layers, num_qubits, seed=42
)

# a local noisy simulator in place of a QPU backend
noisy_executor = Executor(
AerSimulator(method="stabilizer"),
options={"simulator": {"layer_noise_model": layer_noise_model}},
)

task = MitigationTask()
program = task.prepare(
circuit=mirrored_circuit,
observables=observables,
parameters=None,
num_randomizations=num_randomizations,
shots_per_randomization=shots_per_randomization,
custom_boxing_options=boxing_options,
)
# For the simulation - we inject readout noise to the circuits of the program
add_readout_noise(program, readout_noise_model)

program._semantic_role = "simulation" # flag the program for the simulator
unmitigated_res = task.postprocess(noisy_executor.run(program).result())

x_vals = np.array(range(num_qubits))
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(x_vals, [1] * len(x_vals), "k-", alpha=0.5, label="Exact")
ax.errorbar(
x_vals,
unmitigated_res.data.evs,
unmitigated_res.data.stds,
fmt="o",
label="Unmitigated",
)

x_tick_labels = [r"$Z_{ {i} }$" for i in range(num_qubits)]
ax.set_xlabel(r"$\theta/\pi$")
ax.set_ylabel("Expectation value")
ax.legend()
ax.set_xticks(x_vals, x_tick_labels)
plt.tight_layout()
plt.show()

Output of the previous code cell

Aprende el ruido de las capas de entrelazamiento​

PEC comienza aprendiendo el modelo de ruido con twirling de cada capa de puertas de entrelazamiento del circuito. Si asumes un modelo de ruido markoviano, se supone que la profundidad del circuito no afecta al canal de ruido de una capa de entrelazamiento dada. Por lo tanto, solo debe aprenderse el modelo de ruido de cada capa única de puertas de entrelazamiento. El circuito usado en esta guía puede dividirse en dos capas únicas. Diseñar circuitos con un conjunto mínimo de capas de entrelazamiento únicas es importante si quieres aprender un modelo de ruido, ya que las capas únicas deben aprenderse de forma independiente. Si tu circuito tiene demasiadas capas únicas, el aprendizaje del ruido puede resultar inviable debido al coste de muestreo de aprender muchas capas.

Se usa qiskit-noise-learning para aprender el ruido. El aprendiz repite cada capa fragment_depths veces en sus circuitos de aprendizaje, aplica twirling a cada circuito num_randomizations veces y ajusta el decaimiento de las fidelidades medidas a un modelo de Pauli-Lindblad. Estos parámetros pueden afectar a la precisión del modelo aprendido. Cuanto más preciso sea el modelo de ruido aprendido, más precisa será la cancelación del ruido. El siguiente código dibuja el modelo de ruido aprendido con las dos capas fusionadas en un solo mapa. Cada qubit es un gráfico circular con sus tasas X, Y y Z de peso 1 sumadas sobre las dos capas, y cada arista muestra las tasas de peso 2 aprendidas. Por ejemplo, una tasa de 3e-4 significa que ese error ocurre aproximadamente en tres de cada 10 000 usos de la capa.

La mitigación del ruido de lectura se realiza en el posprocesamiento mediante TREX, a diferencia del modelo de ruido de las puertas, que es necesario para la preparación del programa con el fin de inyectar el antirruido correspondiente al modelo de ruido aprendido. Por lo tanto, el ruido de lectura puede ejecutarse y aprenderse al aprender el ruido de las puertas, o al ejecutar el programa mitigado. Aprender el ruido de lectura junto con el ruido de las puertas puede ser beneficioso para aprender un modelo de ruido autoconsistente (consulta, por ejemplo, Disambiguating Pauli noise in quantum computers). En los casos en que el ruido de las puertas y el ruido de lectura están separados en el modelo de ruido, se recomienda aprender el ruido de lectura al ejecutar el programa mitigado para evitar la deriva del ruido entre el momento en que se aprendió y el momento en que se ejecutó.

En esta guía, el ruido de lectura se aprende durante la ejecución del programa mitigado mediante el circuito de calibración TREX.

from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.quantum_info import PauliList
from qiskit_ibm_runtime.results import LayerError, PauliLindbladError
from qiskit_ibm_runtime.visualization.embeddings import Embedding
from qiskit_mitigation import PEC
from qiskit_noise_learning.aer_executor import AerExecutor
from qiskit_noise_learning.noise_learner import LearningOptions, NoiseLearner
from samplomatic import InjectNoise

line_coupling = [(i, i + 1) for i in range(num_qubits - 1)]
backend = GenericBackendV2(
num_qubits=num_qubits,
basis_gates=["id", "rz", "sx", "x", "cz"],
coupling_map=line_coupling,
)

task_pec = PEC()
# first learn only the layers PEC will inject noise into, i.e. not the measurement layer
pec_layers = [
layer
for layer in task_pec.find_unique_layers(mirrored_circuit, boxing_options)
if get_annotation(layer.operation, InjectNoise)
]
layers = task_pec.find_unique_layers(mirrored_circuit, boxing_options)

learning_options = LearningOptions(
fragment_depths=[2, 4, 8, 16],
num_randomizations=250,
shots_per_randomizations=16,
)

# qiskit-noise-learning is not compatible with the Executor in local mode,
# and requires the simulator present in the package for running simulation
noise_dict = {
get_annotation(layer.operation, Tag).ref: noise_layer
for layer, noise_layer in layer_noise_model
}
noise_learner_simulator = AerExecutor(
qasm_simulator=AerSimulator(method="stabilizer"), noise_dict=noise_dict
)
learner = NoiseLearner(
backend, options=learning_options, executor=noise_learner_simulator
)
learned_noise = (
learner.run(pec_layers).result().to_dict()
) # keyed by InjectNoise reference

# Draw the learned model on the line, laid out as a line with the qubits in order.
# Composing the two layers' maps sums their rates, giving one map over all the edges.
embedding = Embedding(
[(0, 0), (0, 2), (0, 4), (2, 4), (4, 4), (4, 2), (4, 0), (2, 0)],
line_coupling,
)
first, second = learned_noise.values()
learned_model = first.compose(second).simplify()
error = PauliLindbladError(
PauliList(
learned_model.get_qubit_sparse_pauli_list_copy().to_pauli_list()
),
learned_model.rates,
)
fig = LayerError(circuit, list(range(num_qubits)), error).draw_map(
embedding, radius=0.4, height=460, width=620
)
fig.update_traces(
marker={
"cmin": 0,
"cmax": max(learned_model.rates),
"colorbar": {"tickformat": ".1e", "title": "rate"},
},
selector={"mode": "markers"},
)
fig.show()

Output of the previous code cell

Prepara un programa combinado de PEC y TREX​

Aquí preparamos tanto una tarea TREX como una PEC, y añadimos la tarea TREX como variable a la tarea PEC. Esto garantiza que todas las propiedades necesarias para la mitigación TREX (como el twirling de mediciones) se añadan al elemento mitigado con PEC. El ruido inyectado se muestrea una vez por aleatorización, de modo que el error estadístico de PEC se controla mediante el número de aleatorizaciones y no mediante los shots por aleatorización. Por ello se usan muchas aleatorizaciones, cada una con pocos shots, manteniendo el número total de shots igual que en el programa sin mitigar.

El circuito de calibración TREX se añade al programa para aprender el ruido de lectura mientras se ejecuta el programa. El circuito de calibración TREX es muy poco profundo, lo que hace que las aleatorizaciones adicionales sean relativamente baratas y permite aprender el ruido de lectura con mayor precisión.

from qiskit_mitigation import TREX

trex = TREX()

program_pec = task_pec.prepare(
circuit=mirrored_circuit,
observables=observables,
parameters=None,
num_randomizations=1200,
shots_per_randomization=16,
custom_boxing_options=boxing_options,
trex=trex,
noise_maps=learned_noise, # PEC option
noise_gain=0, # PEC option
max_sampling_overhead=100, # PEC option
)
program_pec = trex.prepare(
num_randomizations=1500,
quantum_program=program_pec,
custom_boxing_options=boxing_options,
)

# For the simulation - we inject readout noise to the circuits of the program
add_readout_noise(program_pec, readout_noise_model)

Examina la sobrecarga de shots, ejecuta el programa y haz el posprocesamiento​

PEC devuelve una estimación insesgada del valor esperado, a costa de una gran sobrecarga de shots. La sobrecarga de shots necesaria puede aproximarse calculando el factor gamma del circuito, que refleja el efecto del modelo de ruido aprendido sobre el circuito. Por defecto, el número de aleatorizaciones de la tarea PEC se escala según la cantidad aproximada de sobrecarga de shots necesaria para obtener una estimación insesgada (~γ2\gamma^2). Para evitar este escalado automático, puedes establecer el parámetro scale_randomizations_by_gamma en False. La sobrecarga de shots está limitada por el parámetro max_sampling_overhead.

Debes revisar el número de shots que requiere PEC para alcanzar una estimación insesgada antes de la ejecución, para decidir si esa cantidad es asumible, ya que escala exponencialmente con las tasas de ruido y la profundidad del circuito.

print(f"The circuit calculated gamma is: {task_pec.gamma}")
print(f"The circuit shot overhead is ~{task_pec.gamma**2}")
The circuit calculated gamma is: 1.2774846896089953
The circuit shot overhead is ~1.631967132185391
program_pec._semantic_role = (
"simulation" # flag the program for the simulator
)
job_pec_results = noisy_executor.run(program_pec).result()

# post-processing
pec_res = task_pec.postprocess(job_pec_results)
# In order to post process the results without TREX, we call `postprocess` again, but instead of the default options we
# pass only the PEC item result and the `measure_noise_data` as `None`
pec_without_trex_res = task_pec.postprocess(
job_pec_results[task_pec._program_item_index], measure_noise_data=None
)

Compara los valores esperados mitigados​

Por último, representa el valor esperado de cada observable. PEC + TREX recupera los valores esperados exactos dentro de sus barras de error, mientras que el resultado sin mitigar queda sesgado muy por debajo.

x = np.array(range(num_qubits))
x_tick_labels = [r"$Z_{ {i} }$" for i in range(num_qubits)]

fig, ax = plt.subplots(figsize=(8, 5))
ax.errorbar(
x,
unmitigated_res.data.evs,
unmitigated_res.data.stds,
fmt="o",
label="Twirl only",
)
ax.errorbar(
x,
pec_without_trex_res.data.evs,
pec_without_trex_res.data.stds,
fmt="o",
label="PEC only",
)
ax.errorbar(
x,
pec_res.data.evs,
pec_res.data.stds,
fmt="o",
label="PEC + TREX",
)

ax.plot(x, [1] * len(x), "--", color="black", alpha=0.5, label="Exact")

tick_labels = [f"$Z_{ {i} }$" for i in range(num_qubits)]
ax.set_xticks(x, tick_labels)

ax.set_ylabel("Expectation value")
ax.legend()
ax.set_title("PEC+TREX Mitigated Expectation Values")
plt.tight_layout()
plt.show()

Output of the previous code cell