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?

Construir un experimento de aprendizaje

Este tutorial muestra cómo construir un experimento de aprendizaje estándar desde cero y usarlo para aprender un modelo a partir de datos ruidosos simulados.

  1. Define un conjunto de puertas en un anillo de qubits

  2. Elige un modelo de Pauli-Lindblad 2-local

  3. Construye el experimento de aprendizaje

  4. Genera los circuitos

  5. Configura la simulación local

  6. Ejecuta el programa

  7. Analiza los datos

Ejecución en hardware real

Los circuitos siguientes se simulan localmente, por lo que este recorrido no necesita credenciales de IBM Quantum®. Para ejecutarlo, en cambio, en un dispositivo real:

  • Paso 1: reemplaza FakeMarrakesh por un Backend real.

  • Paso 5: pasa ese Backend como el modo del ejecutor y elimina las opciones del simulador.

1. Define un conjunto de puertas en un anillo de qubits​

Construye un QiskitGateSet a partir del Target de un Backend y un subconjunto de sus qubits (aquí, un anillo de doce qubits de FakeMarrakesh) y luego añade una puerta: una capa de seis puertas CZ que cubre ese anillo. De forma predeterminada, el conjunto de puertas se inicializa con una puerta de preparación P y una puerta de medición M.

from qiskit_ibm_runtime.fake_provider import FakeMarrakesh

from qiskit_noise_learning.gate_sets import QiskitGateSet

backend = FakeMarrakesh()

qubit_subset = [*range(25, 30), *range(37, 39), *range(45, 50)]
cz_pairs = [(25, 26), (27, 28), (29, 38), (37, 45), (46, 47), (48, 49)]

gate_set = QiskitGateSet(backend.num_qubits, target=backend.target, qubit_subset=qubit_subset)

with gate_set.build_new_gate("cz_gate", latex_str=r"\mathrm{CZ}") as builder:
for pair in cz_pairs:
builder.circuit.cz(*pair)

list(gate_set)
['M', 'P', 'cz_gate']
Ejecución en hardware real
from qiskit_ibm_runtime import QiskitRuntimeService

backend = QiskitRuntimeService().backend("ibm_marrakesh")

Llama a draw() para ver la puerta en la topología del dispositivo: el anillo de qubits del conjunto de puertas y qué pares de ellos entrelaza la puerta.

gate_set.draw()

2. Elige un modelo de Pauli-Lindblad 2-local​

Decide qué generadores de Pauli-Lindblad puede tener el ruido con un PauliLindbladModel. Construye ese conjunto de generadores a partir de la conectividad del conjunto de puertas con k_local(): aquí, todos los Pauli 2-locales en pares de qubits conectados de la puerta unitaria y Pauli de un solo qubit para la preparación y la medición.

from qiskit_noise_learning.models import PauliLindbladModel

model = PauliLindbladModel.k_local(gate_set, gate_k={"cz_gate": 2, "M": 1, "P": 1})

Muestra el número de parámetros desconocidos del modelo de ruido de cada puerta.

{name: len(generators) for name, generators in model.generators.items()}
{'M': 12, 'P': 12, 'cz_gate': 144}

3. Construye el experimento de aprendizaje​

Ensambla un Experiment componiendo etapas del constructor. Cada etapa lee lo que escribieron las etapas anteriores, así que el orden importa:

from qiskit_noise_learning.experiment_builder import (
BindFragmentDepths,
CompleteSequences,
EvenDepthVanillaPaths,
Experiment,
GenerateInstructionSequences,
IdentifyRelations,
MergeInstructionSequences,
SPAMPaths,
VanillaInstructionSequences,
)

experiment_builder = (
# Add standard vanilla learning paths and instruction sequences, and identify relations
EvenDepthVanillaPaths()
+ VanillaInstructionSequences()
+ IdentifyRelations()
# Add paths for learning SPAM, then generate and merge instruction sequences for measuring them
+ SPAMPaths()
+ GenerateInstructionSequences()
+ MergeInstructionSequences()
# Finalize by completing the instruction sequences and setting experiment depths
+ CompleteSequences()
+ BindFragmentDepths([2, 16, 32, 64])
)

experiment = experiment_builder.run(
Experiment(fidelity_model=model, shots=20, randomizations=50)
)

print(f"Number of paths: {len(experiment.paths)}")
print(f"Number of instruction sequences: {len(experiment.instruction_sequences)}")
Number of paths: 156
Number of instruction sequences: 37

Observa el rango de la matriz de diseño:

print(f"Design matrix rank: {experiment.design_matrix.rank}")
Design matrix rank: 120

4. Genera los circuitos​

Compila el experimento con un ExecutorCircuitGenerator, generando un QuantumProgram: un circuito plantilla parametrizado por cada profundidad de fragmento.

from qiskit_noise_learning.circuit_generator import ExecutorCircuitGenerator

circuit_generator = ExecutorCircuitGenerator(gate_set)
quantum_program = circuit_generator.generate(experiment)

print(f"Number of template circuits: {len(quantum_program.items)}")
Number of template circuits: 5

Dibuja un circuito plantilla para inspeccionarlo.

quantum_program.items[0].circuit.draw("mpl", idle_wires=False, fold=False)

../_images/4088c7e98baeb5b56925c6c1f276159582c5f7040fbba76de438b919210bcc5f.png

5. Configura la simulación local​

Para definir el modelo de ruido de la simulación, usamos los generadores que ya están en el modelo con tasas aleatorias para construir un PauliLindbladMap.

import numpy as np
from qiskit.quantum_info import PauliLindbladMap

rng = np.random.default_rng(1234)

true_maps = {
name: PauliLindbladMap.from_terms(
[
PauliLindbladMap.GeneratorTerm(rate, generator)
for generator, rate in zip(generators, rng.uniform(2e-4, 1.5e-3, len(generators)))
]
)
for name, generators in model.generators.items()
if name != "P"
}
{name: noise_map.num_qubits for name, noise_map in true_maps.items()}
{'M': 156, 'cz_gate': 156}

Esos mapas tienen el ancho del dispositivo, ya que el modelo se expresa con la indexación de qubits de backend, pero el simulador necesita que cada mapa tenga el ancho de la puerta a la que se aplica, con los índices de Pauli recorriendo los qubits de la puerta en orden físico ascendente. Redúcelos con keep_qubits(), que traza fuera todo lo que queda fuera del anillo:

noise_dict = {
name: noise_map.keep_qubits(qubit_subset) for name, noise_map in true_maps.items()
}
{name: noise_map.num_qubits for name, noise_map in noise_dict.items()}
{'M': 12, 'cz_gate': 12}

El simulador nombra el ruido de una capa según la instrucción de caja a partir de la cual se construyó la capa, que to_instruction() devuelve para cualquier puerta del conjunto de puertas. Empareja cada caja con su mapa, usa el método de simulación de estabilizadores y establece seed_simulator para que los datos simulados sean reproducibles:

from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import Executor

executor = Executor(
mode=AerSimulator(method="stabilizer"),
options={
"simulator": {
"layer_noise_model": [
(gate_set[name].to_instruction(), noise_map)
for name, noise_map in noise_dict.items()
],
"seed_simulator": 42,
}
},
)
Ejecución en hardware real
executor = Executor(mode=backend)

6. Ejecuta el programa​

Ejecuta el programa y luego da formato a los resultados con collect() para producir un Fit, el contenedor del que leen y en el que escriben las etapas de análisis.

Este paso es el mismo tanto si el ejecutor apunta a un simulador como a un dispositivo real.

job = executor.run(quantum_program)
fit = circuit_generator.collect(job.result())

7. Analiza los datos​

Construye un pipeline de análisis basado en el ajuste del modelo por mínimos cuadrados no negativos.

from qiskit_noise_learning.analysis import (
ComputeObservables, # computes observables from raw data
CurveFitObservables, # performs exponential fitting
LeastSquaresSolve, # solves model with (nonnegative) least squares
)

analyzer = ComputeObservables() + CurveFitObservables() + LeastSquaresSolve()

fit = analyzer.run(fit)
/opt/hostedtoolcache/Python/3.15.1/x64/lib/python3.15/site-packages/qiskit_noise_learning/analysis/model_solve.py:250: UserWarning: 7 of 156 row(s) of the linear system have a non-positive or non-finite uncertainty, so they carry no usable statistical weight. Positions in LinearSystemData.row_labels: 144, 145, 148, 149, 151, 152, 154.
system = LinearSystemData.from_fit(fit)

Grafica las medias medidas del observable frente a sus exponenciales ajustadas, un subgráfico por par CZ, para ver si los decaimientos que se ajustan son realmente exponenciales.

fit.plot_qubit_pair_decays(
pairs=cz_pairs,
observable_type="means",
exponential_fit=True,
)

Cambia exponential_fit por model_prediction para comparar los mismos datos con los decaimientos implícitos en las tasas obtenidas de la resolución por mínimos cuadrados no negativos.

fit.plot_qubit_pair_decays(
pairs=cz_pairs,
observable_type="means",
model_prediction=True,
)