Simulación exacta con las primitivas del SDK de Qiskit
Versiones de paquetes
El código de esta página fue desarrollado usando los siguientes requisitos. Recomendamos usar estas versiones o más recientes.
qiskit[all]~=2.3.0
Las primitivas de referencia del SDK de Qiskit realizan simulaciones locales de vector de estado. Estas simulaciones no admiten modelar el ruido del dispositivo, pero son útiles para prototipar algoritmos rápidamente antes de explorar técnicas de simulación más avanzadas (usando Qiskit Aer) o ejecutar en dispositivos reales (primitivas de Qiskit Runtime).
La primitiva Estimator puede calcular valores de expectación de circuitos, y la primitiva Sampler puede muestrear desde las distribuciones de salida de los circuitos.
Las siguientes secciones muestran cómo usar las primitivas de referencia para ejecutar tu flujo de trabajo localmente.
Usa el Estimator de referencia
La implementación de referencia de EstimatorV2 en qiskit.primitives que se ejecuta sobre simuladores locales de vector de estado
es la clase StatevectorEstimator. Puede tomar circuitos, observables y parámetros como entradas y devuelve los valores de expectación calculados localmente.
El siguiente código prepara las entradas que se usarán en los ejemplos a continuación. El tipo de entrada esperado para los
observables es qiskit.quantum_info.SparsePauliOp. Ten en cuenta que
el circuito del ejemplo está parametrizado, pero también puedes ejecutar el Estimator en circuitos no parametrizados.
Cualquier circuito que se pase a un Estimator no debe incluir ninguna medición.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
# circuit for which you want to obtain the expected value
circuit = QuantumCircuit(2)
circuit.ry(Parameter("theta"), 0)
circuit.h(0)
circuit.cx(0, 1)
circuit.draw("mpl", style="iqp")
from qiskit.quantum_info import SparsePauliOp
import numpy as np
# observable(s) whose expected values you want to compute
observable = SparsePauliOp(["II", "XX", "YY", "ZZ"], coeffs=[1, 1, -1, 1])
# value(s) for the circuit parameter(s)
parameter_values = [[0], [np.pi / 6], [np.pi / 2]]
El flujo de trabajo de las primitivas de Qiskit Runtime requiere que los circuitos y observables sean transformados para usar únicamente instrucciones compatibles con la QPU (denominados circuitos y observables de arquitectura de conjunto de instrucciones (ISA)). Las primitivas de referencia aún aceptan instrucciones abstractas, ya que dependen de simulaciones locales de vector de estado, pero transpilar el circuito puede resultar beneficioso en términos de optimización del circuito.
# Generate a pass manager without providing a backend
from qiskit.transpiler import generate_preset_pass_manager
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
Inicializa el Estimator
Instancia un qiskit.primitives.StatevectorEstimator.
from qiskit.primitives import StatevectorEstimator
estimator = StatevectorEstimator()
Ejecuta y obtén resultados
Este ejemplo solo usa un circuito (de tipo QuantumCircuit) y un
observable.
Ejecuta la estimación llamando al método StatevectorEstimator.run, que devuelve una instancia de un objeto PrimitiveJob. Puedes obtener los resultados del trabajo (como un objeto qiskit.primitives.PrimitiveResult)
con el método qiskit.primitives.PrimitiveJob.result.
job = estimator.run([(circuit, observable, parameter_values)])
result = job.result()
print(f" > Result class: {type(result)}")
> Result class: <class 'qiskit.primitives.containers.primitive_result.PrimitiveResult'>
Obtén el valor esperado del resultado
La salida del resultado de las primitivas es un array de objetos PubResult, donde cada elemento del array es un objeto PubResult que contiene en sus datos el array de evaluaciones correspondiente a cada combinación circuito-observable del PUB.
Para recuperar los valores de expectación y los metadatos de la primera (y en este caso única) evaluación del circuito, debemos acceder a los data de evaluación para el PUB 0:
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
> Expectation value: [4. 3.73205081 2. ]
> Metadata: {'target_precision': 0.0, 'circuit_metadata': {}}
Configura las opciones de ejecución del Estimator
Por defecto, el Estimator de referencia realiza un cálculo exacto del vector de estado basado en la clase
quantum_info.Statevector.
Sin embargo, esto puede modificarse para introducir el efecto del overhead de muestreo (también conocido como "ruido de shot").
El Estimator acepta un argumento precision que expresa las barras de error que la
implementación primitiva debe alcanzar para las estimaciones de valores de expectación. Este es el overhead de muestreo y se define exclusivamente en el método .run(). Esto te permite ajustar la opción hasta el nivel de PUB.
# Estimate expectation values for two PUBs, both with 0.05 precision.
precise_job = estimator.run(
[(circuit, observable, parameter_values)], precision=0.05
)
Para ver un ejemplo completo, consulta la página de ejemplos de Primitivas.
Usa el Sampler de referencia
La implementación de referencia de SamplerV2 en qiskit.primitives es la clase StatevectorSampler. Toma circuitos y parámetros como entradas y devuelve los resultados del muestreo de las distribuciones de probabilidad de salida como una distribución de cuasi-probabilidad de los estados de salida.
El siguiente código prepara las entradas utilizadas en los ejemplos a continuación. Ten en cuenta que estos ejemplos ejecutan un único circuito parametrizado, pero también puedes ejecutar el Sampler en circuitos no parametrizados.
from qiskit import QuantumCircuit
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw("mpl", style="iqp")
Cualquier circuito cuántico que se pase a un Sampler debe incluir mediciones.
El flujo de trabajo de las primitivas de Qiskit Runtime requiere que los circuitos sean transformados para usar únicamente instrucciones compatibles con la QPU (denominados circuitos ISA). Las primitivas de referencia aún aceptan instrucciones abstractas, ya que dependen de simulaciones locales de vector de estado, pero transpilar el circuito puede resultar beneficioso en términos de optimización del circuito.
# Generate a pass manager without providing a backend
from qiskit.transpiler import generate_preset_pass_manager
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(qc)
Inicializa SamplerV2
Instancia qiskit.primitives.StatevectorSampler:
from qiskit.primitives import StatevectorSampler
sampler = StatevectorSampler()
Ejecuta y obtén resultados
# execute 1 circuit with Sampler
job = sampler.run([circuit])
pub_result = job.result()[0]
print(f" > Result class: {type(pub_result)}")
> Result class: <class 'qiskit.primitives.containers.sampler_pub_result.SamplerPubResult'>
Las primitivas aceptan múltiples PUBs como entradas, y cada PUB obtiene su propio resultado. Por lo tanto, puedes ejecutar diferentes circuitos con distintas combinaciones de parámetros/observables y recuperar los resultados de los PUBs:
from qiskit.transpiler import generate_preset_pass_manager
# create two circuits
circuit1 = circuit.copy()
circuit2 = circuit.copy()
# transpile circuits
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit1 = pm.run(circuit1)
isa_circuit2 = pm.run(circuit2)
# execute 2 circuits using Sampler
job = sampler.run([(isa_circuit1), (isa_circuit2)])
pub_result_1 = job.result()[0]
pub_result_2 = job.result()[1]
print(f" > Result class: {type(pub_result)}")
> Result class: <class 'qiskit.primitives.containers.sampler_pub_result.SamplerPubResult'>
Obtén la distribución de probabilidad o el resultado de la medición
Las muestras de resultados de medición se devuelven como bitstrings o counts. Los bitstrings muestran los resultados de las mediciones, preservando el orden de shots en que fueron medidos. Los objetos de resultado del Sampler organizan los datos en función de los nombres del registro clásico de los circuitos de entrada, para compatibilidad con los circuitos dinámicos.
El nombre del registro clásico tiene como valor predeterminado "meas". Este nombre se usará más adelante para acceder a los bitstrings de medición.
# Define quantum circuit with 2 qubits
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw()
┌───┐ ░ ┌─┐
q_0: ┤ H ├──■───░─┤M├───
└───┘┌─┴─┐ ░ └╥┘┌─┐
q_1: ─────┤ X ├─░──╫─┤M├
└───┘ ░ ║ └╥┘
meas: 2/══════════════╩══╩═
0 1
# Transpile circuit
pm = generate_preset_pass_manager(optimization_level=1)
isa_circuit = pm.run(circuit)
# Run using sampler
result = sampler.run([circuit]).result()
# Access result data for PUB 0
data_pub = result[0].data
# Access bitstring for the classical register "meas"
bitstrings = data_pub.meas.get_bitstrings()
print(f"The number of bitstrings is: {len(bitstrings)}")
# Get counts for the classical register "meas"
counts = data_pub.meas.get_counts()
print(f"The counts are: {counts}")
The number of bitstrings is: 1024
The counts are: {'11': 515, '00': 509}
Cambia las opciones de ejecución
Por defecto, el Sampler de referencia realiza un cálculo exacto del vector de estado basado en la clase
quantum_info.Statevector.
Sin embargo, esto puede modificarse para introducir el efecto del overhead de muestreo (también conocido como "ruido de shot"). Para gestionar este overhead, la interfaz del Sampler acepta un argumento shots que puede definirse a nivel de PUB.
Este ejemplo asume que has definido dos circuitos.
# Sample two circuits at 128 shots each.
sampler.run([isa_circuit1, isa_circuit2], shots=128)
# Sample two circuits at different amounts of shots. The "None"s are necessary
# as placeholders
# for the lack of parameter values in this example.
sampler.run([(isa_circuit1, None, 123), (isa_circuit2, None, 456)])
<qiskit.primitives.primitive_job.PrimitiveJob at 0x7fa430e39dd0>
Para ver un ejemplo completo, consulta la página de ejemplos de Primitivas.
Próximos pasos
- Para una simulación de mayor rendimiento que pueda manejar circuitos más grandes, o para incorporar modelos de ruido en tu simulación, consulta Simulación exacta y con ruido con las primitivas de Qiskit Aer.
- Para aprender a usar Quantum Composer para la simulación, consulta la guía de IBM Quantum Composer.
- Lee la referencia de la API del Estimator de Qiskit.
- Lee la referencia de la API del Sampler de Qiskit.
- Lee Migrar a las primitivas V2.