Muestreo de circuito en un algoritmo¶
La primitiva Sampler se usa para diseñar un algoritmo que muestrea circuitos y extrae distribuciones de probabilidad.
Antecedentes¶
El rol de la primitiva Sampler
es doble: actúa como un punto de entrada a dispositivos cuánticos o simuladores, reemplazando backend.run()
. Además, es una abstracción algorítmica para extraer distribuciones de probabilidad a partir de conteos de mediciones.
Tanto Sampler
como backend.run()
toman circuitos como entradas. La principal diferencia es el formato de la salida: backend.run()
genera conteos, mientras que Sampler
procesa esos conteos y genera la distribución de cuasi-probabilidad asociada con ellos.
Nota
Modelo del backend.run(): En este modelo, usaste el módulo qiskit-ibmq-provider
(ahora migrado a qiskit-ibm-provider
) para acceder a backends reales y simuladores remotos. Para ejecutar simulaciones locales, puedes importar un backend específico de qiskit-aer
. Todos ellos siguieron la interfaz backend.run()
.
Code example with
qiskit-ibmq-provider
&backend.run()
from qiskit import IBMQ # Select provider provider = IBMQ.load_account() # Get backend backend = provider.get_backend("ibmq_qasm_simulator") # Use the cloud simulator # Run result = backend.run(circuits)Code example for
qiskit-aer
&backend.run()
from qiskit_aer import AerSimulator # former import: from qiskit import Aer # Get local simulator backend backend = AerSimulator() # Run result = backend.run(circuits)
Modelo de primitivas: Accede a backends reales y simuladores remotos a través de las primitivas de qiskit-ibm-runtime (Sampler y Estimator). Para ejecutar simulaciones locales, importa primitivas local específicas de qiskit_aer.primitives
y qiskit.primitives
. Todas siguen las interfaces BaseSampler
y BaseEstimator
, pero solo las primitivas Runtime ofrecen acceso al servicio Runtime, las sesiones y la mitigación de errores integrada.
Code example for Runtime Sampler
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler # Define service service = QiskitRuntimeService() # Get backend backend = service.backend("ibmq_qasm_simulator") # Use a cloud simulator # Define Sampler sampler = Sampler(backend=backend) # Run Quasi-Probability calculation result = sampler.run(circuits).result()Code example for Aer Sampler
from qiskit_aer import Sampler # Get local simulator Sampler sampler = Sampler() # Run Quasi-Probability calculation result = sampler.run(circuits).result()
A continuación, mostraremos un ejemplo completo de muestreo de un circuito: primero, con backend.run()
, luego usando Sampler
.
Ejemplo de principio a fin¶
1. Definición del problema¶
Queremos encontrar la distribución de probabilidad (o cuasi-probabilidad) asociada con un estado cuántico:
Atención
Importante: Si deseas utilizar la primitiva Sampler
, el circuito debe contener mediciones.
from qiskit import QuantumCircuit
circuit = QuantumCircuit(4)
circuit.h(range(2))
circuit.cx(0,1)
circuit.measure_all() # measurement!
2. Calcular la distribución de probabilidad en un dispositivo real o simulador en la nube¶
2.a. [Anterior] Usar backend.run()
¶
Los pasos necesarios para alcanzar nuestro objetivo con backend.run()
son:
Ejecuta los circuitos
Obtén los conteos del objeto de resultado
Usa los conteos y las iteraciones (shots) para calcular la distribución de probabilidad
Primero, ejecutamos el circuito en un simulador de la nube y generamos el objeto de resultado:
Nota
Reemplaza ibmq_qasm_simulator
con el nombre de tu dispositivo para ver el flujo de trabajo completo para un dispositivo real.
from qiskit import IBMQ
# Define provider and backend
provider = IBMQ.load_account()
backend = provider.get_backend("ibmq_qasm_simulator")
# Run
result = backend.run(circuit, shots=1024).result()
>>> print("result: ", result)
result: Result(backend_name='ibmq_qasm_simulator', backend_version='0.11.0',
qobj_id='65bb8a73-cced-40c1-995a-8961cc2badc4', job_id='63fc95612751d57b6639f777',
success=True, results=[ExperimentResult(shots=1024, success=True, meas_level=2,
data=ExperimentResultData(counts={'0x0': 255, '0x1': 258, '0x2': 243, '0x3': 268}),
header=QobjExperimentHeader(clbit_labels=[['meas', 0], ['meas', 1], ['meas', 2], ['meas', 3]],
creg_sizes=[['meas', 4]], global_phase=0.0, memory_slots=4, metadata={}, n_qubits=4,
name='circuit-930', qreg_sizes=[['q', 4]], qubit_labels=[['q', 0], ['q', 1], ['q', 2], ['q', 3]]),
status=DONE, metadata={'active_input_qubits': [0, 1, 2, 3], 'batched_shots_optimization': False,
'device': 'CPU', 'fusion': {'enabled': False}, 'input_qubit_map': [[3, 3], [2, 2], [1, 1], [0, 0]],
'measure_sampling': True, 'method': 'stabilizer', 'noise': 'ideal', 'num_clbits': 4, 'num_qubits': 4,
'parallel_shots': 1, 'parallel_state_update': 16, 'remapped_qubits': False,
'sample_measure_time': 0.001001096}, seed_simulator=2191402198, time_taken=0.002996865)],
date=2023-02-27 12:35:00.203255+01:00, status=COMPLETED, header=QobjHeader(backend_name='ibmq_qasm_simulator',
backend_version='0.1.547'), metadata={'max_gpu_memory_mb': 0, 'max_memory_mb': 386782, 'mpi_rank': 0,
'num_mpi_processes': 1, 'num_processes_per_experiments': 1, 'omp_enabled': True, 'parallel_experiments': 1,
'time_taken': 0.003215252, 'time_taken_execute': 0.00303248, 'time_taken_load_qobj': 0.000169435},
time_taken=0.003215252, client_version={'qiskit': '0.39.5'})
Ahora obtenemos la distribución de probabilidad de la salida:
counts = result.get_counts(circuit)
quasi_dists = {}
for key,count in counts.items():
quasi_dists[key] = count/1024
>>> print("counts: ", counts)
>>> print("quasi_dists: ", quasi_dists)
counts: {'0000': 255, '0001': 258, '0010': 243, '0011': 268}
quasi_dists: {'0000': 0.2490234375, '0001': 0.251953125, '0010': 0.2373046875, '0011': 0.26171875}
2.b. [Nuevo] Usar la primitiva Sampler
de Runtime¶
Si bien la sintaxis del lado del usuario de Sampler
es muy similar a backend.run()
, observa que el flujo de trabajo ahora está simplificado, ya que la distribución de cuasi-probabilidad se devuelve directamente (no necesita realizar un procesamiento posterior), junto con algunos metadatos clave.
Nota
Reemplaza ibmq_qasm_simulator
con el nombre de tu dispositivo para ver el flujo de trabajo completo para un dispositivo real.
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService(channel="ibm_quantum")
backend = service.backend("ibmq_qasm_simulator")
sampler = Sampler(backend=backend)
result = sampler.run(circuit, shots=1024).result()
quasi_dists = result.quasi_dists
>>> print("result: ", result)
>>> print("quasi_dists: ", quasi_dists)
result: SamplerResult(quasi_dists=[{0: 0.2802734375, 1: 0.2509765625, 2: 0.232421875, 3: 0.236328125}],
metadata=[{'header_metadata': {}, 'shots': 1024, 'readout_mitigation_overhead': 1.0,
'readout_mitigation_time': 0.03801989182829857}])
quasi_dists: [{0: 0.2802734375, 1: 0.2509765625, 2: 0.232421875, 3: 0.236328125}]
Atención
Ten cuidado con el formato de salida. Con Sampler
, los estados ya no se representan por cadenas de bits, por ejemplo, "11"
, sino por números enteros, por ejemplo, 3
. Para convertir la salida de Sampler
a cadenas de bits, puedes usar el método QuasiDistribution.binary_probabilities()
, como se muestra a continuación.
>>> # convert the output to bit strings
>>> binary_quasi_dist = quasi_dists[0].binary_probabilities()
>>> print("binary_quasi_dist: ", binary_quasi_dist)
binary_quasi_dist: {'0000': 0.2802734375, '0001': 0.2509765625, '0010': 0.232421875, '0011': 0.236328125}
La primitiva Sampler
de Runtime ofrece varias características y opciones de ajuste que no tienen una alternativa en el modo anterior a ser migrada, pero pueden ayudar a mejorar tu rendimiento y resultados. Para obtener más información, consulta lo siguiente:
3. Otras alternativas de ejecución (no Runtime)¶
Las siguientes rutas de migración usan primitivas que no son de Runtime para usar la simulación local para probar un algoritmo. Supongamos que queremos usar una simulación local de vector de estado para resolver el problema definido anteriormente.
3.a. [Anterior] Usar el simulador Qiskit Aer¶
from qiskit_aer import AerSimulator
# Define the statevector simulator
simulator = AerSimulator(method="statevector")
# Run and get counts
result = simulator.run(circuit, shots=1024).result()
>>> print("result: ", result)
result: Result(backend_name='aer_simulator_statevector', backend_version='0.11.2',
qobj_id='e51e51bc-96d8-4e10-aa4e-15ee6264f4a0', job_id='c603daa7-2c03-488c-8c75-8c6ea0381bbc',
success=True, results=[ExperimentResult(shots=1024, success=True, meas_level=2,
data=ExperimentResultData(counts={'0x2': 236, '0x0': 276, '0x3': 262, '0x1': 250}),
header=QobjExperimentHeader(clbit_labels=[['meas', 0], ['meas', 1], ['meas', 2], ['meas', 3]],
creg_sizes=[['meas', 4]], global_phase=0.0, memory_slots=4, metadata={}, n_qubits=4, name='circuit-930',
qreg_sizes=[['q', 4]], qubit_labels=[['q', 0], ['q', 1], ['q', 2], ['q', 3]]), status=DONE,
seed_simulator=3531074553, metadata={'parallel_state_update': 16, 'parallel_shots': 1,
'sample_measure_time': 0.000405246, 'noise': 'ideal', 'batched_shots_optimization': False,
'remapped_qubits': False, 'device': 'CPU', 'active_input_qubits': [0, 1, 2, 3], 'measure_sampling': True,
'num_clbits': 4, 'input_qubit_map': [[3, 3], [2, 2], [1, 1], [0, 0]], 'num_qubits': 4, 'method': 'statevector',
'fusion': {'applied': False, 'max_fused_qubits': 5, 'threshold': 14, 'enabled': True}}, time_taken=0.001981756)],
date=2023-02-27T12:38:18.580995, status=COMPLETED, header=QobjHeader(backend_name='aer_simulator_statevector',
backend_version='0.11.2'), metadata={'mpi_rank': 0, 'num_mpi_processes': 1, 'num_processes_per_experiments': 1,
'time_taken': 0.002216379, 'max_gpu_memory_mb': 0, 'time_taken_execute': 0.002005713, 'max_memory_mb': 65536,
'time_taken_load_qobj': 0.000200642, 'parallel_experiments': 1, 'omp_enabled': True},
time_taken=0.0025920867919921875)
Ahora obtengamos la distribución de probabilidad de la salida:
counts = result.get_counts(circuit)
quasi_dists = {}
for key,count in counts.items():
quasi_dists[key] = count/1024
>>> print("counts: ", counts)
>>> print("quasi_dists: ", quasi_dists)
counts: {'0010': 236, '0000': 276, '0011': 262, '0001': 250}
quasi_dists: {'0010': 0.23046875, '0000': 0.26953125, '0011': 0.255859375, '0001': 0.244140625}
3.b. [Nuevo] Usar la primitiva Sampler
de referencia o el Sampler
de Aer¶
El Sampler
de referencia te permite realizar una simulación exacta o ruidosa basada en iteraciones de la clase Statevector
en el módulo qiskit.quantum_info
.
from qiskit.primitives import Sampler
sampler = Sampler()
result = sampler.run(circuit).result()
quasi_dists = result.quasi_dists
>>> print("result: ", result)
>>> print("quasi_dists: ", quasi_dists)
result: SamplerResult(quasi_dists=[{0: 0.249999999999, 1: 0.249999999999,
2: 0.249999999999, 3: 0.249999999999}], metadata=[{}])
quasi_dists: [{0: 0.249999999999, 1: 0.249999999999, 2: 0.249999999999,
3: 0.249999999999}]
Si se especifican iteraciones (shots), esta primitiva genera una simulación basada en iteraciones (ya no es exacta):
from qiskit.primitives import Sampler
sampler = Sampler()
result = sampler.run(circuit, shots=1024).result()
quasi_dists = result.quasi_dists
>>> print("result: ", result)
>>> print("quasi_dists: ", quasi_dists)
result: SamplerResult(quasi_dists=[{0: 0.2490234375, 1: 0.2578125,
2: 0.2431640625, 3: 0.25}], metadata=[{'shots': 1024}])
quasi_dists: [{0: 0.2490234375, 1: 0.2578125, 2: 0.2431640625, 3: 0.25}]
Todavía puedes acceder al simulador Aer a través de su Sampler
dedicado. Esto puede ser útil para realizar simulaciones con modelos de ruido. En este ejemplo, el método de simulación se ha actualizado para que coincida con el resultado de 3.a.
from qiskit_aer.primitives import Sampler as AerSampler # import change!
sampler = AerSampler(run_options= {"method": "statevector"})
result = sampler.run(circuit, shots=1024).result()
quasi_dists = result.quasi_dists
>>> print("result: ", result)
>>> print("quasi_dists: ", quasi_dists)
result: SamplerResult(quasi_dists=[{1: 0.2802734375, 2: 0.2412109375, 0: 0.2392578125,
3: 0.2392578125}], metadata=[{'shots': 1024, 'simulator_metadata':
{'parallel_state_update': 16, 'parallel_shots': 1, 'sample_measure_time': 0.000409608,
'noise': 'ideal', 'batched_shots_optimization': False, 'remapped_qubits': False,
'device': 'CPU', 'active_input_qubits': [0, 1, 2, 3], 'measure_sampling': True,
'num_clbits': 4, 'input_qubit_map': [[3, 3], [2, 2], [1, 1], [0, 0]], 'num_qubits': 4,
'method': 'statevector', 'fusion': {'applied': False, 'max_fused_qubits': 5,
'threshold': 14, 'enabled': True}}}])
quasi_dists: [{1: 0.2802734375, 2: 0.2412109375, 0: 0.2392578125, 3: 0.2392578125}]
>>> # Convert the output to bit strings
>>> binary_quasi_dist = quasi_dists[0].binary_probabilities()
>>> print("binary_quasi_dist: ", binary_quasi_dist)
binary_quasi_dist: {'0001': 0.2802734375, '0010': 0.2412109375, '0000': 0.2392578125, '0011': 0.2392578125}
Para obtener información, consulta Simuladores ruidosos en Qiskit Runtime.