Envío de circuitos con formato específicos a Azure Quantum
Aprenda a usar el azure-quantum
Python paquete para enviar circuitos en formatos específicos al servicio Azure Quantum. En este artículo se muestra cómo enviar circuitos en los siguientes formatos:
Para más información, consulte Circuitos cuánticos.
Requisitos previos
Para ejecutar los circuitos en un cuaderno en Azure Portal, necesita lo siguiente:
- Una cuenta de Azure con una suscripción activa. Si no tiene una cuenta de Azure, regístrese gratuitamente y regístrese para obtener una suscripción de pago por uso.
- Un área de trabajo de Azure Quantum. Para más información, consulte Creación de un área de trabajo de Azure Quantum.
Para desarrollar y ejecutar los circuitos en Visual Studio Code, también necesita lo siguiente:
Un Python entorno con Python y Pip instalado.
VS Code con las extensiones de Azure Quantum Development Kit, Pythony Jupyter instaladas.
Los paquetes de Azure Quantum
qsharp
,azure-quantum
yipykernel
.python -m pip install --upgrade qsharp azure-quantum ipykernel
Crear un nuevo cuaderno de Jupyter Notebook
Puede crear un cuaderno en VS Code o directamente en el portal de Azure Quantum.
- Inicie sesión en Azure Portal y seleccione el área de trabajo del paso anterior.
- En la hoja izquierda, seleccione Cuadernos.
- Haga clic en Mis cuadernos y, luego, en Agregar nuevo.
- En Tipo de kernel, seleccione IPython.
- Escriba un nombre para el archivo y haga clic en Crear archivo.
Cuando se abre el nuevo cuaderno, se crea automáticamente el código de la primera celda, en función de la información de la suscripción y del área de trabajo.
from azure.quantum import Workspace
workspace = Workspace (
resource_id = "", # Your resource_id
location = "" # Your workspace location (for example, "westus")
)
Enviar circuitos con formato QIR
La representación intermedia cuántica (QIR) es una representación intermedia que actúa como una interfaz común entre lenguajes de programación cuántica o marcos y plataformas de cálculo cuántico de destino. Para obtener más información, consulte Quantum Intermediate Representation.
Cree el circuito QIR. Por ejemplo, el código siguiente crea un circuito de entrelazamiento simple.
QIR_routine = """%Result = type opaque %Qubit = type opaque define void @ENTRYPOINT__main() #0 { call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) call void @__quantum__qis__cz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 3 to %Qubit*)) call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) #1 call void @__quantum__rt__tuple_record_output(i64 2, i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) ret void } declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*) declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*) declare void @__quantum__qis__cy__body(%Qubit*, %Qubit*) declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*) declare void @__quantum__qis__rx__body(double, %Qubit*) declare void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__ry__body(double, %Qubit*) declare void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__rz__body(double, %Qubit*) declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*) declare void @__quantum__qis__h__body(%Qubit*) declare void @__quantum__qis__s__body(%Qubit*) declare void @__quantum__qis__s__adj(%Qubit*) declare void @__quantum__qis__t__body(%Qubit*) declare void @__quantum__qis__t__adj(%Qubit*) declare void @__quantum__qis__x__body(%Qubit*) declare void @__quantum__qis__y__body(%Qubit*) declare void @__quantum__qis__z__body(%Qubit*) declare void @__quantum__qis__swap__body(%Qubit*, %Qubit*) declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 declare void @__quantum__rt__result_record_output(%Result*, i8*) declare void @__quantum__rt__array_record_output(i64, i8*) declare void @__quantum__rt__tuple_record_output(i64, i8*) attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="4" "required_num_results"="2" } attributes #1 = { "irreversible" } ; module flags !llvm.module.flags = !{!0, !1, !2, !3} !0 = !{i32 1, !"qir_major_version", i32 1} !1 = !{i32 7, !"qir_minor_version", i32 0} !2 = !{i32 1, !"dynamic_qubit_management", i1 false} !3 = !{i32 1, !"dynamic_result_management", i1 false} """
Cree una
submit_qir_job
función auxiliar para enviar el circuito QIR a .target Tenga en cuenta que los formatos de datos de entrada y salida se especifican comoqir.v1
ymicrosoft.quantum-results.v1
, respectivamente.# Submit the job with proper input and output data formats def submit_qir_job(target, input, name, count=100): job = target.submit( input_data=input, input_data_format="qir.v1", output_data_format="microsoft.quantum-results.v1", name=name, input_params = { "entryPoint": "ENTRYPOINT__main", "arguments": [], "count": count } ) print(f"Queued job: {job.id}") job.wait_until_completed() print(f"Job completed with state: {job.details.status}") #if job.details.status == "Succeeded": result = job.get_results() return result
Seleccione y target envíe el circuito QIR a Azure Quantum. Por ejemplo, para enviar el circuito QIR al simulador targetde IonQ:
target = workspace.get_targets(name="ionq.simulator") result = submit_qir_job(target, QIR_routine, "QIR routine") result
{'Histogram': ['(0, 0)', 0.5, '(1, 1)', 0.5]}
Envío de un circuito con un formato específico del proveedor a Azure Quantum
Además de los lenguajes QIR, como Q# o Qiskit, puede enviar circuitos cuánticos en formatos específicos del proveedor a Azure Quantum. Cada proveedor tiene su propio formato para representar circuitos cuánticos.
Envío de un circuito a IonQ con formato JSON
Cree un circuito cuántico mediante el formato JSON independiente del lenguaje compatible con IonQtargets, tal como se describe en la documentación de IonQ API. El ejemplo siguiente crea una superposición entre tres cúbits:
circuit = { "qubits": 3, "circuit": [ { "gate": "h", "target": 0 }, { "gate": "cnot", "control": 0, "target": 1 }, { "gate": "cnot", "control": 0, "target": 2 }, ] }
Envíe el circuito a IonQ target. En el ejemplo siguiente se usa el simulador de IonQ, que devuelve un objeto
Job
.target = workspace.get_targets(name="ionq.simulator") job = target.submit(circuit)
Espere hasta que se complete el trabajo y obtenga los resultados.
results = job.get_results() print(results)
..... {'duration': 8240356, 'histogram': {'0': 0.5, '7': 0.5}}
A continuación, podemos visualizar los resultados mediante Matplotlib.
import pylab as pl pl.rcParams["font.size"] = 16 hist = {format(n, "03b"): 0 for n in range(8)} hist.update({format(int(k), "03b"): v for k, v in results["histogram"].items()}) pl.bar(hist.keys(), hist.values()) pl.ylabel("Probabilities")
Antes de ejecutar un trabajo en la QPU, debe calcular cuánto costará ejecutarse.
Nota:
Para obtener los precios más actuales, consulte los precios de IonQ o vaya a su área de trabajo y consulte las opciones de precios en la hoja "Proveedores" en: aka.ms/aq/myworkspaces.
Envío de un circuito a PASQAL mediante el SDK de Pulser
Para enviar un circuito a PASQAL, puede usar el SDK de Pulser para crear secuencias de pulso y enviarlos al PASQAL target.
Instalación del SDK de Pulser
Pulser es un marco para crear, simular y ejecutar secuencias de pulso para dispositivos cuánticos neutros atom. Está diseñado por PASQAL como un paso a través para enviar experimentos cuánticos a sus procesadores cuánticos. Para obtener más información, consulte la documentación de Pulser.
Para enviar las secuencias de pulso, instale primero los paquetes del SDK de Pulser:
try:
import pulser
except ImportError:
!pip -q install pulser
Creación de un registro cuántico
Debe definir un registro y un diseño antes de continuar. El registro especifica dónde se organizarán los átomos, mientras que el diseño especifica el posicionamiento de capturas necesarios para capturar y estructurar estos átomos dentro del registro.
Para obtener más información sobre los diseños, consulte la documentación de Pulser.
En primer lugar, se crea un objeto "devices" para importar el equipo targetcuántico PASQAL , Fresnel.
from pulser_pasqal import PasqalCloud devices = PasqalCloud().fetch_available_devices() QPU = devices["FRESNEL"]
Diseños calibrados previamente
El dispositivo define una lista de diseños pre calibrados. Puede crear el registro fuera de uno de estos diseños.
Esta es la opción recomendada porque mejorará el rendimiento de la QPU.
Opción 1: Definir el registro mediante diseños calibrados previamente
Inspeccione los diseños disponibles en Fresnel y defina el registro de este diseño. Consulte la documentación de pulser para obtener más información sobre cómo hacerlo.
Ejemplo:
# let's say we are interested in the first layout available on the device layout = QPU.pre_calibrated_layouts[0] # Select traps 1, 3 and 5 of the layout to define the register traps = [1,3,5] reg = layout.define_register(*traps) # You can draw the resulting register to verify it matches your expectations reg.draw()
Diseños arbitrarios
Si los diseños pre calibrados no satisfacen los requisitos del experimento, puede crear un diseño personalizado.
Para cualquier registro arbitrario dado, una QPU de átomo neutro colocará trampas según el diseño, que después debe someterse a calibración. Dado que cada calibración requiere tiempo, generalmente es aconsejable reutilizar un diseño calibrado existente siempre que sea posible.
Opción 2: Derivar automáticamente un diseño del registro definido
Esta opción permite la generación automática de un diseño basado en un registro especificado. Sin embargo, en el caso de los registros grandes, este proceso puede producir soluciones sub-óptimas debido a limitaciones en el algoritmo usado para crear el diseño.
qubits = { "q0": (0, 0), "q1": (0, 10), "q2": (8, 2), "q3": (1, 15), "q4": (-10, -3), "q5": (-8, 5), } reg = Register(qubits).with_automatic_layout(device)
Opción 3: Definir el registro mediante un diseño definido manualmente
- Crear un diseño arbitrario con 20 capturas colocadas aleatoriamente en un plano 2D
import numpy as np # Generating random coordinates np.random.seed(301122) # Keeps results consistent between runs traps = np.random.randint(0, 30, size=(20, 2)) traps = traps - np.mean(traps, axis=0) # Creating the layout layout = RegisterLayout(traps, slug="random_20")
- Definición del registro con identificadores de captura específicos
trap_ids = [4, 8, 19, 0] reg = layout.define_register(*trap_ids, qubit_ids=["a", "b", "c", "d"]) reg.draw()
Escribir una secuencia de pulsos
Los átomos neutros se controlan con pulsos láser. El SDK de Pulser permite crear secuencias de pulso para aplicar al registro cuántico.
En primer lugar, se definen los atributos de secuencia de pulsos declarando los canales que se usarán para controlar los átomos. Para crear un
Sequence
, debe proporcionar unaRegister
instancia junto con el dispositivo donde se ejecutará la secuencia. Por ejemplo, el código siguiente declara un canal:ch0
.Nota:
Puede usar el
QPU = devices["FRESNEL"]
dispositivo o importar un dispositivo virtual desde Pulser para obtener más flexibilidad. El uso de unVirtualDevice
permite la creación de secuencias que está menos restringida por las especificaciones del dispositivo, lo que hace que sea adecuado para la ejecución en un emulador. Para obtener más información, consulte la documentación de Pulser.from pulser import Sequence seq = Sequence(reg, QPU) # print the available channels for your sequence print(seq.available_channels) # Declare a channel. In this example we will be using `rydberg_global` seq.declare_channel("ch0", "rydberg_global")
Agregue pulsos a la secuencia. Para ello, cree y agregue pulsos a los canales que declaró. Por ejemplo, el código siguiente crea un pulso y lo agrega al canal
ch0
.from pulser import Pulse from pulser.waveforms import RampWaveform, BlackmanWaveform import numpy as np amp_wf = BlackmanWaveform(1000, np.pi) det_wf = RampWaveform(1000, -5, 5) pulse = Pulse(amp_wf, det_wf, 0) seq.add(pulse, "ch0") seq.draw()
En la imagen siguiente se muestra la secuencia de pulsos.
Conversión de la secuencia en una cadena JSON
Para enviar las secuencias de pulso, debe convertir los objetos Pulser en una cadena JSON que se puede usar como datos de entrada.
import json
# Convert the sequence to a JSON string
def prepare_input_data(seq):
input_data = {}
input_data["sequence_builder"] = json.loads(seq.to_abstract_repr())
to_send = json.dumps(input_data)
return to_send
Enviar la secuencia de pulso a PASQAL target
En primer lugar, debe establecer los formatos de datos de entrada y salida adecuados. Por ejemplo, el código siguiente establece el formato
pasqal.pulser.v1
de datos de entrada en y el formato de datos de salida enpasqal.pulser-results.v1
.# Submit the job with proper input and output data formats def submit_job(target, seq, shots): job = target.submit( input_data=prepare_input_data(seq), # Take the JSON string previously defined as input data input_data_format="pasqal.pulser.v1", output_data_format="pasqal.pulser-results.v1", name="PASQAL sequence", shots=shots # Number of shots ) print(f"Queued job: {job.id}") return job
Nota:
El tiempo necesario para ejecutar un trabajo en la QPU depende de los tiempos de cola actuales. Para ver el tiempo medio de cola de un target , seleccione la hoja Proveedores del área de trabajo.
Envíe el programa a PASQAL. Antes de enviar el código al hardware cuántico real, puede probar el código mediante el emulador
pasqal.sim.emu-tn
como .targettarget = workspace.get_targets(name="pasqal.sim.emu-tn") # Change to "pasqal.qpu.fresnel" to use Fresnel QPU job = submit_job(target, seq, 10) job.wait_until_completed() print(f"Job completed with state: {job.details.status}") result = job.get_results() print(result)
{ "1000000": 3, "0010000": 1, "0010101": 1 }
Envío de un circuito a Quantinuum mediante OpenQASM
Cree un circuito cuántico en la representación de OpenQASM. En el ejemplo siguiente, se crea un circuito de teletransporte:
circuit = """OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c0[3]; h q[0]; cx q[0], q[1]; cx q[1], q[2]; measure q[0] -> c0[0]; measure q[1] -> c0[1]; measure q[2] -> c0[2]; """
También puede cargar el circuito desde un archivo:
with open("my_teleport.qasm", "r") as f: circuit = f.read()
Envíe el circuito al Quantinuum target. El siguiente ejemplo utiliza el validador de API de Quantinuum, que devuelve un objeto
Job
.target = workspace.get_targets(name="quantinuum.sim.h1-1sc") job = target.submit(circuit, shots=500)
Espere hasta que se complete el trabajo y obtenga los resultados.
results = job.get_results() print(results)
........ {'c0': ['000', '000', '000', '000', '000', '000', '000', ... ]}
A continuación, podemos visualizar los resultados mediante Matplotlib.
import pylab as pl pl.hist(results["c0"]) pl.ylabel("Counts") pl.xlabel("Bitstring")
Si observa el histograma, puede observar que el generador de números aleatorios devuelve 0 cada vez, lo que no es muy aleatorio. Esto se debe a que, aunque el validador de API garantiza que el código se ejecutará correctamente en el hardware de Quantinuum, también devuelve 0 para cada medida cuántica. Para que el generador de números aleatorios sea verdadero, debe ejecutar el circuito en hardware cuántico.
Antes de ejecutar un trabajo en la QPU, debe calcular cuánto costará ejecutarse.
Nota:
Para obtener los detalles de precios más actuales, consulte Precios de Azure Quantum o busque el área de trabajo y vea las opciones de precios en la pestaña "Proveedor" del área de trabajo a través de: aka.ms/aq/myworkspaces.
Envío de un circuito a Rigetti mediante Quil
La manera más fácil de enviar trabajos de Quil es usar el paquete pyquil-for-azure-quantum , ya que permite usar las herramientas y la documentación de la biblioteca pyQuil . Sin este paquete, pyQuil se puede usar para construir programas de Quil, pero no para enviarlos a Azure Quantum.
También puede construir programas de Quil manualmente y enviarlos directamente mediante el paquete azure-quantum
.
En primer lugar, cargue las importaciones necesarias.
from pyquil.gates import CNOT, MEASURE, H from pyquil.quil import Program from pyquil.quilbase import Declare from pyquil_for_azure_quantum import get_qpu, get_qvm
Use la
get_qvm
función oget_qpu
para obtener una conexión a QVM o QPU.qc = get_qvm() # For simulation # qc = get_qpu("Ankaa-9Q-3") for submitting to a QPU
Cree un programa Quil. Se acepta cualquier programa Quil válido, pero el readout debe denominarse
ro
.program = Program( Declare("ro", "BIT", 2), H(0), CNOT(0, 1), MEASURE(0, ("ro", 0)), MEASURE(1, ("ro", 1)), ).wrap_in_numshots_loop(5) # Optionally pass to_native_gates=False to .compile() to skip the compilation stage result = qc.run(qc.compile(program)) data_per_shot = result.readout_data["ro"]
Aquí,
data_per_shot
es unanumpy
matriz, por lo que puede usarnumpy
métodos.assert data_per_shot.shape == (5, 2) ro_data_first_shot = data_per_shot[0] assert ro_data_first_shot[0] == 1 or ro_data_first_shot[0] == 0
Imprima todos los datos.
print("Data from 'ro' register:") for i, shot in enumerate(data_per_shot): print(f"Shot {i}: {shot}")
Importante
Actualmente, no se admite el envío de varios circuitos en un solo trabajo. Como solución alternativa, puede llamar al método backend.run
para enviar cada circuito de forma asincrónica y, a continuación, capturar los resultados de cada trabajo. Por ejemplo:
jobs = []
for circuit in circuits:
jobs.append(backend.run(circuit, shots=N))
results = []
for job in jobs:
results.append(job.result())