Specifieke geformatteerde circuits verzenden naar Azure Quantum

Meer informatie over het gebruik van de module qdk.azurePython om circuits in specifieke indelingen naar de Azure Quantum-service te verzenden. In dit artikel leest u hoe u circuits in de volgende indelingen verzendt:

Zie Quantum-circuits voor meer informatie.

Vereisten

Als u uw circuits wilt ontwikkelen en uitvoeren in Visual Studio Code (VS Code), moet u het volgende hebben:

Een nieuw Jupyter-notebook maken en verbinding maken met uw Quantum-werkruimte

Volg deze stappen om verbinding te maken met uw werkruimte in een Jupyter-notebook in VS Code:

  1. Open in VS Code het menu Beeld en kies Opdrachtpalet.

  2. Voer Create: New Jupyter Notebook in. Er wordt een leeg Jupyter Notebook bestand geopend op een nieuw tabblad.

  3. Voer in de eerste cel van het notebook de volgende code uit. U vindt de resource-id in het deelvenster Overview voor uw werkruimte in de Azure-portal.

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

QIR-geformatteerde circuits verzenden

Quantum Intermediate Representation (QIR) is een tussenliggende weergave die fungeert als een gemeenschappelijke interface tussen kwantumprogrammeertalen en doelplatforms voor kwantumberekeningen. Zie Quantum Intermediate Representation voor meer informatie.

Voer de volgende stappen uit om een QIR-geformatteerd circuit te verzenden:

  1. Maak het QIR-circuit. Voer bijvoorbeeld de volgende code uit in een nieuwe cel om een eenvoudig verstrengelingscircuit te maken.

    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. Maak een submit_qir_job helper functie om het QIR-circuit naar een target in te dienen. In dit voorbeeld zijn de gegevensindelingen voor invoer en uitvoer respectievelijk qir.v1 en microsoft.quantum-results.v1.

    # 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. Verzend het QIR-circuit naar een specifieke Azure Quantum target. Als u bijvoorbeeld het QIR-circuit wilt verzenden naar de IonQ-simulator target, voert u de volgende code uit:

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

Een circuit met een providerspecifieke indeling verzenden naar Azure Quantum

Elke Azure Quantum provider heeft een eigen indeling die kwantumcircuits vertegenwoordigt. U kunt circuits verzenden naar Azure Quantum in providerspecifieke indelingen in plaats van QIR-talen, zoals Q# of Qiskit.

Een circuit verzenden naar IonQ in JSON-indeling

IonQ gebruikt JSON-formaat om circuits op hun targets uit te voeren. Zie ionQ en de IonQ targetsAPI-documentatie voor meer informatie.

In het volgende voorbeeld wordt een superpositie gemaakt tussen drie qubits in JSON-indeling.

  1. Maak in een nieuwe cel een kwantumcircuit in JSON-indeling.

    circuit = {
        "qubits": 3,
        "circuit": [
            {
            "gate": "h",
            "target": 0
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 1
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 2
            },
        ]
    }
    
  2. Verzend het circuit naar de IonQ target. In het volgende voorbeeld wordt de IonQ-simulator gebruikt, die een Job object retourneert.

    target = workspace.get_targets(name="ionq.simulator")
    job = target.submit(circuit)
    
  3. Wanneer de taak is voltooid, haalt u de resultaten op.

    results = job.get_results()
    print(results)
    

Een circuit verzenden naar Pasqal in Pulser SDK-indeling

U kunt de Pulser SDK gebruiken om pulse-reeksen te maken en deze naar Pasqal targetste verzenden.

De Pulser SDK installeren

Pulser is een framework waarmee u pulse-reeksen kunt maken, simuleren en uitvoeren voor kwantumapparaten met neutraal atoom. Pulser is door PASQAL ontworpen als een doorgeefluik om kwantumexperimenten naar hun kwantumprocessors te verzenden. Zie de Pulser-documentatie voor meer informatie.

Als u de pulse-reeksen wilt verzenden, installeert u eerst de Pulser SDK-pakketten:

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

Een kwantumregister maken

Definieer zowel een register als een indeling. In het register wordt aangegeven waar de atomen moeten worden gerangschikt en in de indeling worden de posities van traps opgegeven die de atomen in het register vastleggen en structuren.

Zie de Pulser-documentatie voor meer informatie over indelingen.

Maak een devices object om de Pasqal-kwantumcomputer targette importeren , FRESNEL_CAN1.

from pulser_pasqal import PasqalCloud

devices = PasqalCloud().fetch_available_devices()
QPU = devices["FRESNEL_CAN1"]
Vooraf gekalibreerde indelingen

Het apparaat definieert een lijst met vooraf gekalibreerde indelingen. U kunt uw register maken op basis van een van deze indelingen.

Gebruik indien mogelijk vooraf gekalibreerde indelingen omdat ze de prestaties van de QPU verbeteren.

In het volgende voorbeeld wordt de eerste vooraf gekalibreerde indeling op het apparaat gebruikt:

# 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()
Willekeurige indelingen

Gebruik een aangepaste indeling wanneer de vooraf gekalibreerde indelingen niet voldoen aan de vereisten van uw experiment.

Voor een bepaald willekeurig register plaatst een neutraal-atom QPU traps volgens de indeling, die vervolgens moeten worden gekalibreerd. Omdat elke kalibratie tijd kost, is het een best practice om een bestaande gekalibreerde indeling indien mogelijk opnieuw te gebruiken.

Als u een willekeurige indeling wilt maken, kiest u een van de volgende opties:

  • Automatisch een indeling genereren op basis van een opgegeven register. Voor grote registers kan dit proces suboptimale oplossingen produceren. Voorbeeld:

    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) 
    
  • Definieer handmatig een indeling om uw register te maken. Maak bijvoorbeeld een willekeurige indeling met 20 traps die willekeurig in een 2D-vlak worden geplaatst:

    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()
    

Een pulsreeks schrijven

Neutrale atomen worden geregeld met laserpulsen. Met de Pulser SDK kunt u pulse-reeksen maken die van toepassing zijn op het kwantumregister.

  1. Definieer de pulsreekskenmerken door de kanalen te declareren die de atomen beheren. Als u een Sequenceexemplaar wilt maken, geeft u een Register exemplaar op samen met het apparaat waarop de reeks wordt uitgevoerd. De volgende code declareert bijvoorbeeld één kanaal: 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")
    

    Notitie

    U kunt het QPU = devices["FRESNEL_CAN1"] apparaat gebruiken of een virtueel apparaat importeren vanuit Pulser voor meer flexibiliteit. Het gebruik van een VirtualDevice maakt het mogelijk om reeksen te maken die minder beperkt zijn door apparaatspecificaties, waardoor op een emulator kan worden uitgevoerd. Zie de Pulser-documentatie voor meer informatie.

  2. Voeg pulsen toe aan uw reeks. Hiervoor maakt en voegt u pulsen toe aan de kanalen die u hebt gedeclareerd. Met de volgende code wordt bijvoorbeeld een puls gemaakt en toegevoegd aan het kanaal 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()
    

    In de volgende afbeelding ziet u de pulsreeks:

    Pulssequentie

De reeks converteren naar een JSON-tekenreeks

Als u de pulse-reeksen wilt verzenden, converteert u de Pulser-objecten naar een JSON-tekenreeks die kan worden gebruikt als invoergegevens.

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

De pulsreeks verzenden naar Pasqal target

  1. Stel de juiste indelingen voor invoer- en uitvoergegevens in. Met de volgende code wordt bijvoorbeeld de indeling van de invoergegevens ingesteld op pasqal.pulser.v1 en de indeling van de uitvoergegevens op 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
    

    Notitie

    De tijd die nodig is om een taak uit te voeren op de QPU, is afhankelijk van de huidige wachtrijtijden. U kunt de gemiddelde wachtrijtijd voor een target in het deelvenster Providers van uw werkruimte bekijken.

  2. Verzend het programma naar Pasqal. Voordat u uw code naar echte kwantumhardware verzendt, is het een best practice om uw code op de emulator pasqal.sim.emu-mpstargette testen.

    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
    }
    

Een OpenQASM-circuit verzenden naar Quantinuum

  1. Maak een kwantumcircuit in de OpenQASM-weergave . Met de volgende code wordt bijvoorbeeld een teleportatiecircuit gemaakt:

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

    Of laad het circuit vanuit een OpenQASM-bestand:

    with open("my_teleport.qasm", "r") as f:
        circuit = f.read()
    
  2. Verzend het circuit naar een Quantinuum target. In het volgende voorbeeld wordt de taak verzonden naar een van de Quantinuum-simulator targets.

    target = workspace.get_targets(name="quantinuum.sim.h2-1sc")
    job = target.submit(circuit, shots=500)
    
  3. Wacht totdat de taak is voltooid en haal vervolgens de resultaten op.

    results = job.get_results()
    print(results)
    

Notitie

Deze resultaten geven 000 terug voor elke poging, wat geen toeval is. Dit komt doordat de API Validator alleen controleert of uw code kan worden uitgevoerd op Quantinuum-hardware, maar 0 retourneert voor elke kwantummeting. Voor een echte generator voor willekeurige getallen moet u uw circuit uitvoeren op kwantumhardware.

Een Quil-circuit verzenden naar Rigetti

Als u een Quil-taak naar een Rigetti-target wilt verzenden, gebruikt u de module qdk.azurePython.

  1. Laad de vereiste importen.

    from azure.quantum import Workspace
    from azure.quantum.target.rigetti import Result, Rigetti, RigettiTarget, InputParams
    
  2. Maak een target object en geef de naam door van de Rigetti target waarnaar u uw taak wilt verzenden. Met de volgende code selecteert u bijvoorbeeld de QVMtarget.

    target = Rigetti(workspace=workspace, name=RigettiTarget.QVM)
    
  3. Maak een Quil-programma. Als u uw programma wilt accepteren, moet u de leesbewerking instellen op "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. U kunt een resultaat indexeren met de naam van de uitlezing. In de volgende code data_per_shot is een lijst met lengte num_shots en elk item in de lijst is een andere lijst die de gegevens voor het register van die opname bevat.

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

    Omdat het type van het register BIT is, is het type geheel getal en de waarde 0 of 1.

    assert isinstance(ro_data_first_shot[0], int)
    assert ro_data_first_shot[0] == 1 or ro_data_first_shot[0] == 0
    
  5. Alle gegevens afdrukken.

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

Belangrijk

U kunt niet meerdere circuits verzenden voor één taak. Als tijdelijke oplossing kunt u de backend.run methode aanroepen om elk circuit asynchroon te verzenden en vervolgens de resultaten van elke taak op te halen. Voorbeeld:

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

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