Skicka specifika formaterade kretsar till Azure Quantum

Lär dig hur du använder modulen qdk.azurePython för att skicka kretsar i specifika format till Azure Quantum-tjänsten. Den här artikeln visar hur du skickar kretsar i följande format:

Mer information finns i Kvantkretsar.

Förutsättningar

Om du vill utveckla och köra dina kretsar i Visual Studio Code (VS Code) måste du ha följande:

Skapa en ny Jupyter-anteckningsbok och anslut till din Quantum-arbetsyta

Följ dessa steg för att ansluta till din arbetsyta i en Jupyter-anteckningsbok i VS Code:

  1. Öppna menyn Visa i VS Code och välj Kommandopalett.

  2. Skriv in Skapa: En Ny Jupyter Notebook. En tom Jupyter Notebook fil öppnas på en ny flik.

  3. Kör följande kod i den första cellen i notebook-filen. Du hittar resurs-ID:t i fönstret Overview för arbetsytan i Azure-portalen.

    from qdk.azure import Workspace
    
    workspace = Workspace (resource_id="") # Add your resource ID 
    

Skicka QIR-formaterade kretsar

Quantum Intermediate Representation (QIR) är en mellanliggande representation som fungerar som ett gemensamt gränssnitt mellan kvantprogrammeringsspråk och riktade kvantberäkningsplattformar. Mer information finns i Quantum Intermediate Representation(Kvantintermediär representation).

Följ dessa steg för att skicka en QIR-formaterad krets:

  1. Skapa QIR-kretsen. Kör till exempel följande kod i en ny cell för att skapa en enkel sammanflätningskrets.

    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. Skapa en submit_qir_job hjälpfunktion för att skicka QIR-kretsen till en target. I det här exemplet är qir.v1 indata- och utdataformaten, och microsoft.quantum-results.v1 är respektive.

    # 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. Skicka QIR-kretsen till en specifik Azure Quantum target. Om du till exempel vill skicka QIR-kretsen till IonQ-simulatorn targetkör du följande kod:

    target = workspace.get_targets(name="ionq.simulator") 
    result = submit_qir_job(target, QIR_routine, "QIR routine")
    result
    

Skicka en krets med ett providerspecifikt format till Azure Quantum

Varje Azure Quantum-provider har sitt eget format för att representera kvantkretsar. Du kan skicka kretsar till Azure Quantum i providerspecifika format i stället för QIR-språk, till exempel Q# eller Qiskit.

Skicka en krets till IonQ i JSON-format

IonQ använder JSON-format för att köra kretsar på deras targets. Mer information finns i IonQ targets och IonQ API-dokumentationen.

Följande exempel skapar en superposition mellan tre kvantbitar i JSON-format.

  1. Skapa en kvantkrets i JSON-format i en ny cell.

    circuit = {
        "qubits": 3,
        "circuit": [
            {
            "gate": "h",
            "target": 0
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 1
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 2
            },
        ]
    }
    
  2. Skicka kretsen till IonQ target. I följande exempel används IonQ-simulatorn, som returnerar ett Job objekt.

    target = workspace.get_targets(name="ionq.simulator")
    job = target.submit(circuit)
    
  3. När jobbet är klart får du resultatet.

    results = job.get_results()
    print(results)
    

Skicka en krets till Pasqal i Pulser SDK-format

Du kan använda Pulser SDK för att skapa pulssekvenser och skicka dem till Pasqal targets.

Installera Pulser SDK

Pulser är ett ramverk som gör att du kan skapa, simulera och köra pulssekvenser för kvantenheter med neutral atom. Pulser är utformat av PASQAL som ett gränssnitt för att lämna in kvantexperiment till sina kvantprocessorer. Mer information finns i Pulser-dokumentationen.

Om du vill skicka pulssekvenserna installerar du först Pulser SDK-paketen:

try:
    import pulser
    import pulser_pasqal
except ImportError:
    !pip -q install pulser pulser-pasqal --index-url https://pypi.org/simple

Skapa ett kvantregister

Definiera både ett register och en layout. Registret anger var atomerna ska ordnas, och layouten anger positionerna för traps som fångar in och strukturerar atomerna i registret.

Mer information om layouter finns i Pulser-dokumentationen.

Skapa ett devices objekt för att importera Pasqal-kvantdatorn target, FRESNEL_CAN1.

from pulser_pasqal import PasqalCloud

devices = PasqalCloud().fetch_available_devices()
QPU = devices["FRESNEL_CAN1"]
Förkalibrerade layouter

Enheten definierar en lista över förkalibrerade layouter. Du kan skapa ditt register från någon av dessa layouter.

Använd förkalibrerade layouter när det är möjligt eftersom de förbättrar QPU:ns prestanda.

I följande exempel används den första förkalibrerade layouten på enheten:

# Use 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,v3,v5]
reg = layout.define_register(*traps)

# Draw the register to verify that it matches your expectations
reg.draw()
Godtyckliga layouter

Använd en anpassad layout när de förkalibrerade layouterna inte uppfyller kraven för experimentet.

För ett godtyckligt register placerar en QPU med neutrala atomer fällor enligt layouten, som sedan måste kalibreras. Eftersom varje kalibrering tar tid är det bästa praxis att återanvända en befintlig kalibrerad layout när det är möjligt.

Om du vill skapa en godtycklig layout väljer du något av följande alternativ:

  • Generera automatiskt en layout baserat på ett angivet register. För stora register kan den här processen skapa suboptimala lösningar. Till exempel:

    from pulser import Register
    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) 
    
  • Definiera en layout manuellt för att skapa registret. Skapa till exempel en godtycklig layout med 20 traps som placeras slumpmässigt i ett 2D-plan:

    import numpy as np
    from pulser.register.register_layout import RegisterLayout
    
    # Generate 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)
    
    # Create the layout
    layout = RegisterLayout(traps, slug="random_20")
    
    # Define your register with specific trap IDs
    trap_ids = [4, 8, 19, 0]
    reg = layout.define_register(*trap_ids, qubit_ids=["a", "b", "c", "d"])
    reg.draw()
    

Skriva en pulssekvens

Neutrala atomer styrs med laserpulser. Med Pulser SDK kan du skapa pulssekvenser som ska tillämpas på kvantregistret.

  1. Definiera pulssekvensattributen genom att deklarera kanalerna som styr atomerna. För att skapa en Sequence, tillhandahåll en Register instans tillsammans med enheten där sekvensen skall utföras. Följande kod deklarerar till exempel en kanal: ch0.

    from pulser import Sequence
    
    seq = Sequence(reg, QPU)
    
    # Print the available channels for your sequence
    print(seq.available_channels)
    
    # Declare a channel. For example, `rydberg_global`
    seq.declare_channel("ch0", "rydberg_global")
    

    Anteckning

    Du kan använda QPU = devices["FRESNEL_CAN1"] enheten eller importera en virtuell enhet från Pulser för mer flexibilitet. Med hjälp av en VirtualDevice kan du skapa sekvenser som är mindre begränsade av enhetsspecifikationer, vilket gör att du kan köra på en emulator. Mer information finns i Pulser-dokumentationen.

  2. Lägg till pulser i sekvensen. Det gör du genom att skapa och lägga till pulser i de kanaler som du deklarerade. Följande kod skapar till exempel en puls och lägger till den i kanalen 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()
    

    Följande bild visar pulssekvensen:

    Pulssekvens

Konvertera sekvensen till en JSON-sträng

Om du vill skicka pulssekvenserna konverterar du Pulser-objekten till en JSON-sträng som kan användas som indata.

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

Skicka pulssekvensen till Pasqal target

  1. Ange rätt indata- och utdataformat. Följande kod anger till exempel indataformatet till pasqal.pulser.v1 och utdataformatet till 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
    

    Anteckning

    Den tid som krävs för att köra ett jobb på QPU:n beror på aktuella kötider. Du kan visa den genomsnittliga kötiden för en target i Providers-rutan i arbetsytan.

  2. Skicka programmet till Pasqal. Innan du skickar koden till verklig kvantmaskinvara är det bästa praxis att testa koden på emulatorn pasqal.sim.emu-mpstarget.

    target = workspace.get_targets(name="pasqal.sim.emu-mps") # Change to "pasqal.qpu.fresnel-can1" to use FRESNEL_CAN1 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
    }
    

Skicka en OpenQASM-krets till Quantinuum

  1. Skapa en kvantkrets i OpenQASM-representationen. Följande kod skapar till exempel en teleporteringskrets:

    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];
    """
    

    Eller läs in kretsen från en OpenQASM-fil:

    with open("my_teleport.qasm", "r") as f:
        circuit = f.read()
    
  2. Skicka kretsen till en Quantinuum target. I följande exempel skickas jobbet till en av Quantinuum-simulatorn targets.

    target = workspace.get_targets(name="quantinuum.sim.h2-1sc")
    job = target.submit(circuit, shots=500)
    
  3. Vänta tills jobbet är klart och hämta sedan resultatet.

    results = job.get_results()
    print(results)
    

Anteckning

Dessa resultat returnerar 000 för varje skott, vilket inte är slumpmässigt. Det beror på att API-validatorn endast kontrollerar om koden kan köras på Quantinuum-maskinvara, men returnerar 0 för varje kvantmätning. För en verklig slumptalsgenerator måste du köra kretsen på kvantmaskinvara.

Skicka en Quil-krets till Rigetti

Om du vill skicka ett Quil-jobb till en Rigetti target använder du modulen qdk.azurePython.

  1. Läs in nödvändiga importer.

    from azure.quantum import Workspace
    from azure.quantum.target.rigetti import Result, Rigetti, RigettiTarget, InputParams
    
  2. Skapa ett target objekt och skicka namnet på den Rigetti target som du vill skicka jobbet till. Till exempel väljer följande kod QVMtarget.

    target = Rigetti(workspace=workspace, name=RigettiTarget.QVM)
    
  3. Skapa ett Quil-program. För att programmet ska accepteras måste du ställa in avläsningen på "ro".

    readout = "ro"
    bell_state_quil = f"""
    DECLARE {readout} BIT[2]
    
    H 0
    CNOT 0 1
    
    MEASURE 0 {readout}[0]
    MEASURE 1 {readout}[1]
    """
    
    num_shots = 5
    job = target.submit(
        input_data=bell_state_quil, 
        name="bell state", 
        shots=100, 
        input_params=InputParams(skip_quilc=False)
    )
    
    print(f"Job completed with state: {job.details.status}")
    result = Result(job)  # This throws an exception if the job failed
    
  4. Du kan indexera ett resultat med namnet på avläsningen. I följande kod är data_per_shot en lista med längd num_shots, och varje objekt i listan är en annan lista som innehåller data för registret från det skottet.

    data_per_shot = result[readout]
    
    ro_data_first_shot = data_per_shot[0]
    

    I det här fallet, eftersom typen av registret är BIT, är typen heltal och värdet 0 eller 1.

    assert isinstance(ro_data_first_shot[0], int)
    assert ro_data_first_shot[0] == 1 or ro_data_first_shot[0] == 0
    
  5. Skriv ut alla data.

    print(f"Data from '{readout}' register:")
    for i, shot in enumerate(data_per_shot):
        print(f"Shot {i}: {shot}")
    

Viktigt!

Du kan inte skicka flera kretsar i ett och samma jobb. Som en lösning kan du anropa backend.run metoden för att skicka varje krets asynkront och sedan hämta resultatet av varje jobb. Till exempel:

jobs = []
for circuit in circuits:
    jobs.append(backend.run(circuit, shots=N))

results = []
for job in jobs:
    results.append(job.result())