Introducción a los primitivos
Versiones de paquetes
El código de esta página fue desarrollado con los siguientes requisitos. Se recomienda usar estas versiones o más recientes.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
La versión beta de un nuevo modelo de ejecución ya está disponible. El modelo de ejecución dirigida ofrece mayor flexibilidad para personalizar tu flujo de trabajo de mitigación de errores. Consulta la guía del Modelo de ejecución dirigida para más información.
¿Por qué Qiskit introdujo los primitivos?
De forma similar a los primeros días de las computadoras clásicas, cuando los desarrolladores tenían que manipular directamente los registros de la CPU, la interfaz inicial con las QPUs simplemente devolvía los datos crudos de la electrónica de control.
Esto no representaba un gran problema cuando las QPUs se encontraban en laboratorios y solo permitían acceso directo a investigadores.
Reconociendo que la mayoría de los desarrolladores no tenían por qué estar familiarizados con la transformación de esos datos crudos en 0s y 1s, Qiskit introdujo backend.run, una primera abstracción para acceder a las QPUs en la nube. Esto permitió a los desarrolladores
trabajar con un formato de datos familiar y centrarse en el panorama general.
A medida que el acceso a las QPUs se fue extendiendo y se desarrollaron más algoritmos cuánticos,
surgió de nuevo la necesidad de una abstracción de mayor nivel. En respuesta, Qiskit introdujo
la interfaz de primitivos, optimizada para dos tareas fundamentales en el desarrollo de algoritmos cuánticos:
la estimación de valores esperados (Estimator) y el muestreo de circuitos (Sampler). El objetivo es, una vez
más, ayudar a los desarrolladores a centrarse más en la innovación y menos en la conversión de datos. La interfaz de primitivos reemplaza a la interfaz backend.run, ya que Sampler ofrece el mismo acceso directo al hardware que proporcionaba backend.run.
¿Qué es un primitivo?
Los sistemas de cómputo se construyen sobre múltiples capas de abstracción. Las abstracciones te permiten centrarte en un nivel de detalle concreto, relevante para la tarea que tienes entre manos. Cuanto más cerca estés del hardware, menor será el nivel de abstracción que necesitas (por ejemplo, es posible que tengas que mover o manipular datos a nivel de instrucción de la CPU). Cuanto más compleja sea la tarea que quieres realizar, mayor será el nivel de abstracción necesario (por ejemplo, podrías estar usando una biblioteca de programación para realizar cálculos algebraicos).
En este contexto, un primitivo es la instrucción de procesamiento más pequeña, el bloque de construcción más simple a partir del cual se puede crear algo útil para un nivel de abstracción determinado.
El progreso reciente en computación cuántica ha aumentado la necesidad de trabajar en niveles de abstracción más altos. A medida que el campo avanza hacia unidades de procesamiento cuántico (QPUs) más grandes y flujos de trabajo más complejos, el foco se desplaza de la interacción con señales individuales de qubit hacia la concepción de los dispositivos cuánticos como sistemas que realizan tareas necesarias.
Las dos tareas más comunes para las computadoras cuánticas son el muestreo de estados cuánticos y el cálculo de valores esperados. Estas tareas motivaron el diseño de los primitivos de Qiskit: Estimator y Sampler.
- Estimator calcula los valores esperados de observables respecto a los estados preparados por circuitos cuánticos.
- Sampler muestrea el registro de salida de la ejecución de circuitos cuánticos.
En resumen, el modelo computacional introducido por los primitivos de Qiskit acerca la programación cuántica un paso más al estado actual de la programación clásica, donde el foco está menos en los detalles del hardware y más en los resultados que se quieren obtener.
Definición e implementaciones de primitivos
Existen dos tipos de primitivos de Qiskit: las clases base y sus implementaciones. Los primitivos de Qiskit están definidos por clases base de código abierto que residen en el SDK de Qiskit (en el módulo qiskit.primitives). Los proveedores (como Qiskit Runtime) pueden usar estas clases base para derivar sus propias implementaciones de Sampler y Estimator. La mayoría de los usuarios interactuará con las implementaciones de los proveedores, no con los primitivos base.
Clases base
BaseEstimatorV2 y BaseSamplerV2 - Clases base abstractas que definen una interfaz común para implementar primitivos. Todas las demás clases del módulo qiskit.primitives heredan de estas clases base. Los desarrolladores deben usarlas si están interesados en crear su propio modelo de ejecución basado en primitivos para un proveedor específico. Estas clases también pueden ser útiles para quienes desean realizar un procesamiento muy personalizado y encuentran que las implementaciones existentes de primitivos son demasiado simples para sus necesidades. Los usuarios en general no utilizarán directamente las clases base.
Implementaciones
Estas son las implementaciones de las clases base de primitivos:
-
Los primitivos de Qiskit Runtime (
EstimatorV2ySamplerV2) proporcionan una implementación más sofisticada (por ejemplo, incluyendo mitigación de errores) como servicio en la nube. Esta implementación de los primitivos base se utiliza para acceder al hardware de IBM Quantum®. Se accede a través de IBM Qiskit Runtime. -
StatevectorEstimatoryStatevectorSampler- Implementaciones de referencia de los primitivos que utilizan el simulador integrado en Qiskit. Están construidas con el móduloquantum_infode Qiskit y producen resultados basados en simulaciones de vector de estado ideales. Se accede a través de Qiskit. -
BackendEstimatorV2yBackendSamplerV2- Puedes usar estas clases para "envolver" cualquier recurso de computación cuántica en un primitivo. Esto te permite escribir código estilo primitivo para proveedores que aún no tienen una interfaz basada en primitivos. Estas clases se pueden usar igual que Sampler y Estimator normales, salvo que deben inicializarse con un argumento adicionalbackendpara seleccionar en qué computadora cuántica ejecutar. Se accede a través de Qiskit.
Ventajas de los primitivos de Qiskit
Con los primitivos, los usuarios de Qiskit pueden escribir código cuántico para una QPU específica sin tener que gestionar
explícitamente cada detalle. Además, gracias a la capa adicional de abstracción, es posible que puedas acceder
con mayor facilidad a las capacidades avanzadas del hardware de un proveedor determinado. Por ejemplo, con los primitivos de Qiskit Runtime,
puedes aprovechar los últimos avances en mitigación y supresión de errores activando opciones como el resilience_level del primitivo, en lugar de implementar estas técnicas por tu cuenta.
Para los proveedores de hardware, implementar primitivos de forma nativa significa que puedes ofrecer a tus usuarios una forma más "lista para usar" de acceder a las características de tu hardware, como técnicas avanzadas de posprocesamiento. De esta manera, resulta más sencillo para tus usuarios beneficiarse de las mejores capacidades de tu hardware.
Detalles de los primitivos
Como se describió anteriormente, todos los primitivos se crean a partir de las clases base; por lo tanto, tienen la misma estructura y uso general. Por ejemplo, el formato de la entrada para todos los primitivos Estimator es el mismo. Sin embargo, existen diferencias en las implementaciones que los hacen únicos.
Dado que la mayoría de los usuarios acceden a los primitivos de Qiskit Runtime, los ejemplos del resto de esta sección están basados en los primitivos de Qiskit Runtime.
Estimator
El primitivo Estimator calcula los valores esperados de uno o más observables respecto a los estados preparados por circuitos cuánticos. Los circuitos pueden estar parametrizados, siempre que los valores de los parámetros también se proporcionen como entrada al primitivo.
La entrada es un array de PUBs. Cada PUB tiene el formato:
(<circuito único>, <uno o más observables>, <valores de parámetros opcionales>, <precisión opcional>),
donde los valores de parámetros opcionales pueden ser una lista o un único parámetro. Las distintas implementaciones de Estimator admiten diversas opciones de configuración. Si la entrada contiene mediciones, estas se ignoran.
La salida es un PubResult que contiene los valores esperados calculados por par, y sus errores estándar, en forma de PubResult. Cada PubResult contiene tanto datos como metadatos.
El Estimator combina elementos de observables y valores de parámetros siguiendo las reglas de broadcasting de NumPy, tal como se describe en el tema Entradas y salidas de primitivos.
Ejemplo:
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime
# This cell is hidden from users, it creates the circuits and observables to run
from qiskit_ibm_runtime import EstimatorV2, SamplerV2, QiskitRuntimeService
from qiskit.circuit.random import random_circuit
from qiskit.circuit import Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
import numpy as np
service = QiskitRuntimeService()
backend = service.least_busy()
phi = Parameter("phi")
circuit1 = random_circuit(10, 5, seed=12345)
circuit1.rzz(phi, 1, 2)
observable1 = SparsePauliOp.from_sparse_list(
[("ZXYZ", [1, 2, 3, 4], 1)], num_qubits=10
)
param_values1 = np.random.uniform(size=5).T
circuit2 = random_circuit(10, 5, seed=12345)
circuit2.rzz(phi, 1, 2)
observable2 = SparsePauliOp.from_sparse_list(
[("XZYX", [1, 2, 3, 4], 1)], num_qubits=10
)
param_values2 = np.random.uniform(size=5).T
shots1 = 164
shots2 = 1024
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
circuit1 = pm.run(circuit1)
circuit2 = pm.run(circuit2)
observable1 = observable1.apply_layout(circuit1.layout)
observable2 = observable2.apply_layout(circuit2.layout)
estimator = EstimatorV2(mode=backend)
estimator_job = estimator.run(
[
(circuit1, observable1, param_values1),
(circuit2, observable2, param_values2),
]
)
Sampler
La tarea principal del Sampler es muestrear el registro de salida de la ejecución de uno o más circuitos cuánticos. Los circuitos de entrada pueden estar parametrizados, siempre que los valores de los parámetros también se proporcionen como entrada al primitivo.
La entrada es uno o más PUBs, en el formato:
(<circuito único>, <uno o más valores de parámetros opcionales>, <shots opcionales>),
donde puede haber múltiples elementos de valores de parámetros, y cada elemento puede ser un array o un único parámetro, según el circuito elegido. Además, la entrada debe contener mediciones.
La salida son conteos o mediciones por disparo, como objetos PubResult, sin pesos. Sin embargo, la clase de resultado tiene métodos para devolver muestras ponderadas, como conteos. Consulta Entradas y salidas de primitivos para más detalles.
Ejemplo:
# This cell is hidden from users, add measurement instructions to circuits
circuit1.measure_active()
circuit2.measure_active()
sampler = SamplerV2(mode=backend)
sampler_job = sampler.run(
[
(circuit1, param_values1, shots1),
(circuit2, param_values2, shots2),
]
)
Próximos pasos
- Lee Primeros pasos con los primitivos para implementar los primitivos en tu trabajo.
- Revisa los ejemplos detallados de primitivos.
- Practica con los primitivos completando la lección sobre funciones de costo en IBM Quantum Learning.
- Consulta la referencia de la API de EstimatorV2 y la referencia de la API de SamplerV2.
- Lee Migrar a los primitivos V2.