Share via


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 realizar la transición 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 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 o marcos cuánticos y targetplataformas de cálculo cuánticos. 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, 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 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
    !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 un entramado cuadrado de 4x4 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 para aplicar 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 es 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 que declaró. 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 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):
        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 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 Quantinuum target, primero debe volver a cargar el paquete azure-quantumPython con el parámetro [qiskit] y asegurarse de que tiene la versión más reciente de 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 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())