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.

Nota

El Kit de desarrollo de Microsoft Quantum (QDK clásico) ya no se admitirá después del 30 de junio de 2024. Si es un desarrollador de QDK existente, se recomienda pasar al nuevo Kit de desarrollo de Azure Quantum (QDK moderno) para seguir desarrollando soluciones cuánticas. Para obtener más información, consulte Migración del código de Q# al QDK moderno.

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:

Crear un nuevo cuaderno de Jupyter Notebook

  1. Inicie sesión en Azure Portal y seleccione el área de trabajo del paso anterior.
  2. En la hoja de la 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") 
)

Envío de 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 los lenguajes de programación cuántica y las plataformas targetde cálculo cuántico. 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 targetionQ :

    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 mediante formato JSON

  1. Cree un circuito cuántico con el formato JSON independiente del lenguaje admitido por IonQ targets, tal como se describe en la documentación de La API de IonQ. 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, puede calcular cuánto cuesta ejecutarse. Para calcular el costo de ejecutar un trabajo en la QPU, puede usar el método estimate_cost.

    target = workspace.get_targets(name="ionq.qpu")
    cost = target.estimate_cost(circuit, shots=500)
    
    print(f"Estimated cost: {cost.estimated_total}")
    

    El costo estimado se muestra en USD.

    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 pulsos y enviarlos al PASQAL target.

Instalación del SDK de Pulser

Pulser es un marco para componer, simular y ejecutar secuencias de pulso para dispositivos cuánticos neutral-atom. Está diseñado por PASQAL como 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
    !pip -q install pulser-core

Creación de un registro cuántico

  1. En primer lugar, cargue las importaciones necesarias:

    import numpy as np
    import pulser
    from pprint import pprint
    from pulser import Pulse, Sequence, Register
    
  2. La QPU de PASQAL está hecha de átomos neutros atrapados en posiciones bien definidas en una red. Para definir los registros cuánticos, cree una matriz de cúbits en una red. Por ejemplo, el código siguiente crea una red de 4x4 cuadrados de cúbits:

    L = 4
    square = np.array([[i, j] for i in range(L) for j in range(L)], dtype=float)
    square -= np.mean(square, axis=0)
    square *= 5
    
    qubits = dict(enumerate(square))
    reg = Register(qubits)
    reg.draw()
    

    Trazado de una red de 4x4 cuadrados con 16 cúbits.

Escribir una secuencia de pulsos

Los átomos neutros se controlan con pulsos láser. El SDK de Pulser permite crear secuencias de pulso que se aplicarán al registro cuántico.

  1. En primer lugar, debe configurar una secuencia de pulsos y declarar los canales que se usarán para controlar los átomos. Por ejemplo, el código siguiente declara dos canales: ch0 y ch1.

    from pulser.devices import Chadoq2
    
    seq = Sequence(reg, Chadoq2)
    
    seq.declare_channel("ch0", "raman_local")
    print("Available channels after declaring 'ch0':")
    pprint(seq.available_channels)
    
    seq.declare_channel("ch1", "rydberg_local", initial_target=4)
    print("\nAvailable channels after declaring 'ch1':")
    pprint(seq.available_channels)
    

    Algunos aspectos que se deben tener en cuenta son:

    • En Sequence Pulser se trata de una serie de operaciones que se van a ejecutar en un registro cuántico.
    • El código configura una secuencia de operaciones que se ejecutarán en un AnalogDevice dispositivo. AnalogDevice es un dispositivo predefinido en Pulser que representa un equipo cuántico equivalente a Fresnel1.
  2. Cree una secuencia de pulsos. Para ello, cree y agregue pulsos a los canales declarados. Por ejemplo, el código siguiente crea un pulso simple y lo agrega al canal ch0y, a continuación, crea un pulso complejo y lo agrega al canal ch1.

    seq.target(1, "ch0") # Target qubit 1 with channel "ch0"
    simple_pulse = Pulse.ConstantPulse(200, 2, -10, 0)
    seq.add(simple_pulse, "ch0") # Add the pulse to channel "ch0"
    seq.delay(100, "ch1")
    from pulser.waveforms import RampWaveform, BlackmanWaveform
    
    duration = 1000
    # Create a Blackman waveform with a duration of 1000 ns and an area of pi/2 rad
    amp_wf = BlackmanWaveform(duration, np.pi / 2)  
    # Create a ramp waveform with a duration of 1000 ns and a linear sweep from -20 to 20 rad/µs
    detuning_wf = RampWaveform(duration, -20, 20) 
    
    # Create a pulse with the amplitude waveform amp_wf, the detuning waveform detuning_wf, and a phase of 0 rad.
    complex_pulse = Pulse(amp_wf, detuning_wf, phase=0) 
    complex_pulse.draw()
    seq.add(complex_pulse, "ch1") # Add the pulse to channel "ch1"
    

La imagen muestra el pulso simple y complejo.

Trazado del pulso simple y complejo.

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)
    #print(json.dumps(input_data, indent=4, sort_keys=True))
    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 de datos de entrada en pasqal.pulser.v1 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):
        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=100 # Number of shots
        )
    
        print(f"Queued job: {job.id}")
        job.wait_until_completed()
        print(f"Job completed with state: {job.details.status}")
        result = job.get_results()
    
        return result
    

    Nota

    El tiempo necesario para ejecutar un circuito en la QPU puede variar en función de los tiempos de cola actuales. Para ver el tiempo medio de cola de un elemento target , seleccione la hoja Proveedores del área de trabajo.

  2. Envíe el programa a PASQAL. Por ejemplo, puede enviar el programa a PASQAL Emu-TN target.

    target = workspace.get_targets(name="pasqal.sim.emu-tn")
    submit_job(target, seq)
    
    {'0000000000000000': 59,
     '0000100000000000': 39,
     '0100000000000000': 1,
     '0100100000000000': 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, puede calcular cuánto cuesta ejecutarse. Para calcular el costo de ejecutar un trabajo en la QPU, puede usar el método estimate_cost.

    target = workspace.get_targets(name="quantinuum.qpu.h1-1")
    cost = target.estimate_cost(circuit, shots=500)
    
    print(f"Estimated cost: {cost.estimated_total}")
    

    Esto imprime el costo estimado en créditos cuánticos de H-System (HQC).

    Nota

    Para ejecutar una estimación de costos con un Quantinuumtarget, primero debe volver a cargar el paquete azure-quantumPython con el parámetro [qiskit]. Para más información, consulte Actualización del paquete azure-quantumPython.

    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-2") for submitting to a QPU
    
  3. Cree un programa Quil. Se acepta cualquier programa Quil válido, pero el archivo readout debe tener el nombre 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())

Pasos siguientes