Compartir a través de


Envío de circuitos con formato específicos a Azure Quantum

Aprenda a usar el azure-quantumPython 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:

Para desarrollar y ejecutar los circuitos en Visual Studio Code, también necesita lo siguiente:

Crear un nuevo cuaderno de Jupyter Notebook

Puede crear un cuaderno en VS Code o directamente en el portal de Azure Quantum.

  1. Inicie sesión en Azure Portal y seleccione el área de trabajo del paso anterior.
  2. En la hoja izquierda, seleccione Cuadernos.
  3. Haga clic en Mis cuadernos y, luego, en Agregar nuevo.
  4. En Tipo de kernel, seleccione IPython.
  5. 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.

  1. 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}
    """
    
  2. 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 como qir.v1 y microsoft.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
    
  3. 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

  1. 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
            },
        ]
    }
    
  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)
    
  3. 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}}
    
  4. 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")
    

    Salida del trabajo de IonQ

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

  1. 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 una Register 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 un VirtualDevice 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")
    
  2. 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. 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

  1. 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 en pasqal.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.

  2. 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 .target

    target = 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

  1. 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()
    
  2. 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)
    
  3. 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',
    ...
    ]}
    
  4. A continuación, podemos visualizar los resultados mediante Matplotlib.

    import pylab as pl
    pl.hist(results["c0"])
    pl.ylabel("Counts")
    pl.xlabel("Bitstring")
    

    Salida del trabajo de Quantinuum

    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.

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

  1. 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
    
  2. Use la get_qvm función o get_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
    
  3. 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"]
    
  4. Aquí, data_per_shot es una numpy matriz, por lo que puede usar numpy 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
    
  5. 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())