Spanish
Idiomas
English
Japanese
Spanish

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:

  1. Ejecuta los circuitos

  2. Obtén los conteos del objeto de resultado

  3. 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.