Freigeben über


So übermitteln Sie bestimmte formatierte Schaltkreise an Azure Quantum

Erfahren Sie, wie Sie das Modul qdk.azurePython verwenden, um Schaltkreise in bestimmten Formaten an den Azure Quantum-Dienst zu übermitteln. In diesem Artikel wird gezeigt, wie Sie Schaltkreise in den folgenden Formaten übermitteln:

Weitere Informationen finden Sie unter Quantenschaltungen.

Voraussetzungen

Um Ihre Schaltkreise in Visual Studio Code (VS Code) zu entwickeln und auszuführen, müssen Sie folgendes haben:

Erstellen eines neuen Jupyter-Notizbuchs und Herstellen einer Verbindung mit Ihrem Quantum-Arbeitsbereich

Führen Sie die folgenden Schritte aus, um eine Verbindung mit Ihrem Arbeitsbereich in einem Jupyter-Notizbuch in VS Code herzustellen:

  1. Öffnen Sie im VS Code das Menü "Ansicht" , und wählen Sie "Befehlspalette" aus.

  2. Geben Sie Create: New Jupyter Notebook ein. Eine leere Jupyter Notebook Datei wird auf einer neuen Registerkarte geöffnet.

  3. Führen Sie in der ersten Zelle des Notizbuchs den folgenden Code aus. Sie finden die Ressourcen-ID im Bereich Overview für Ihren Arbeitsbereich im Azure-Portal.

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

Übermitteln Sie QIR-formatierte Schaltkreise

Quantum Intermediate Representation (QIR) ist eine Zwischendarstellung, die als gemeinsame Schnittstelle zwischen Quantenprogrammiersprachen und gezielten Quantenberechnungsplattformen dient. Weitere Informationen finden Sie unter Quantum Intermediate Representation.

Führen Sie die folgenden Schritte aus, um einen QIR-formatierten Schaltkreis zu übermitteln:

  1. Erstellen Sie den QIR-Schaltkreis. Führen Sie beispielsweise den folgenden Code in einer neuen Zelle aus, um eine einfache Veranglementschaltung zu erstellen.

    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. Erstellen Sie eine submit_qir_job-Hilfsfunktion, um den QIR-Schaltkreis an einen target zu übermitteln. In diesem Beispiel sind die Formate der Eingabe- und Ausgabedaten jeweils qir.v1 und 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. Übermitteln Sie den QIR-Schaltkreis an eine bestimmte Azure Quantum target. Führen Sie beispielsweise den folgenden Code aus, um den QIR-Schaltkreis an den IonQ-Simulator targetzu übermitteln:

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

Übermitteln eines Schaltkreises mit einem anbieterspezifischen Format an Azure Quantum

Jeder Azure Quantum Anbieter hat ein eigenes Format, um Quantenschaltungen darzustellen. Sie können Schaltkreise in anbieterspezifischen Formaten anstelle von QIR-Sprachen, z. B. Q# oder Qiskit, an Azure Quantum übermitteln.

Übermitteln eines Schaltkreises an IonQ im JSON-Format

IonQ nutzt das JSON-Format zum Ausführen von Schaltkreisen auf ihren targets. Weitere Informationen finden Sie in der IonQ targets- und ionQ-API-Dokumentation.

Im folgenden Beispiel wird eine Superposition zwischen drei Qubits im JSON-Format erstellt.

  1. Erstellen Sie in einer neuen Zelle einen Quantenkreis im JSON-Format.

    circuit = {
        "qubits": 3,
        "circuit": [
            {
            "gate": "h",
            "target": 0
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 1
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 2
            },
        ]
    }
    
  2. Übermitteln Sie den Schaltkreis an den IonQ target. Im folgenden Beispiel wird der IonQ-Simulator verwendet, der ein Job-Objekt zurückgibt.

    target = workspace.get_targets(name="ionq.simulator")
    job = target.submit(circuit)
    
  3. Wenn der Auftrag abgeschlossen ist, können Sie die Ergebnisse abrufen.

    results = job.get_results()
    print(results)
    

Übermitteln eines Schaltkreises an PASQAL im Pulser SDK-Format

Sie können das Pulser SDK verwenden, um Pulssequenzen zu erstellen und sie an PASQAL targetszu übermitteln.

Installieren des Pulser SDK

Pulser ist ein Framework, mit dem Sie Impulssequenzen für neutrale Atom-Quantengeräte erstellen, simulieren und ausführen können. Pulser wurde von PASQAL als Pass-Through entwickelt, um Quantenexperimente an ihre Quantenprozessoren zu übermitteln. Weitere Informationen finden Sie in der Pulser-Dokumentation.

Um die Impulssequenzen zu übermitteln, installieren Sie zuerst die Pulser SDK-Pakete:

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

Erstellen eines Quantenregisters

Definieren Sie sowohl ein Register als auch ein Layout. Das Register gibt an, wo die Atome angeordnet werden sollen, und das Layout gibt die Positionen von Fallen an, die die Atome innerhalb des Registers erfassen und strukturieren.

Ausführliche Informationen zu Layouts finden Sie in der Pulser-Dokumentation.

Erstellen Sie ein devices Objekt zum Importieren des PASQAL-Quantencomputers targetFresnel.

from pulser_pasqal import PasqalCloud

devices = PasqalCloud().fetch_available_devices()
QPU = devices["FRESNEL"]
Vorkalibrierungslayouts

Das Gerät definiert eine Liste der vordefinierten Layouts. Sie können Ihr Register aus einem dieser Layouts erstellen.

Verwenden Sie vorab kalibrierte Layouts, wenn möglich, da sie die Leistung der QPU verbessern.

Im folgenden Beispiel wird das erste vorab kalibrierte Layout auf dem Gerät verwendet:

# 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()
Beliebige Layouts

Verwenden Sie ein benutzerdefiniertes Layout, wenn die vordefinierten Layouts nicht den Anforderungen Ihres Experiments entsprechen.

Für ein bestimmtes beliebiges Register platziert ein neutrales Atom QPU Fallen nach dem Layout, das dann kalibriert werden muss. Da jede Kalibrierung Zeit in Anspruch nimmt, empfiehlt es sich, ein vorhandenes kalibriertes Layout nach Möglichkeit wiederzuverwenden.

Um ein beliebiges Layout zu erstellen, wählen Sie eine der folgenden Optionen aus:

  • Automatisches Generieren eines Layouts basierend auf einem angegebenen Register. Bei großen Registern kann dieser Prozess suboptimale Lösungen erzeugen. Zum Beispiel:

    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) 
    
  • Definieren Sie manuell ein Layout, um Ihr Register zu erstellen. Erstellen Sie beispielsweise ein beliebiges Layout mit 20 Fallen, die zufällig in einer 2D-Ebene positioniert sind:

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

Schreiben einer Impulssequenz

Neutrale Atome werden mit Laserpulsen gesteuert. Mit dem Pulser SDK können Sie Impulssequenzen erstellen, die auf das Quantenregister angewendet werden.

  1. Definieren Sie die Impulssequenzattribute, indem Sie die Kanäle deklarieren, die die Atome steuern. Geben Sie eine Register-Instanz zusammen mit dem Gerät an, auf dem die Sequenz ausgeführt wird, um eine Sequence zu erstellen. Der folgende Code deklariert beispielsweise einen 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")
    

    Hinweis

    Sie können das QPU = devices["FRESNEL"] Gerät verwenden oder ein virtuelles Gerät aus Pulser importieren, um mehr Flexibilität zu erhalten. Die Verwendung einer VirtualDevice Ermöglicht die Sequenzerstellung, die weniger durch Gerätespezifikationen eingeschränkt ist, sodass Sie auf einem Emulator ausführen können. Weitere Informationen finden Sie in der Pulser-Dokumentation.

  2. Fügen Sie Ihrer Sequenz Impulse hinzu. Erstellen und fügen Sie dazu Impulse zu den Kanälen hinzu, die Sie deklariert haben. Der folgende Code erstellt z. B. einen Impuls und fügt ihn dem Kanal ch0hinzu:

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

    Die folgende Abbildung zeigt die Impulssequenz:

    Impulsfolge

Konvertieren der Sequenz in eine JSON-Zeichenfolge

Um die Impulssequenzen zu übermitteln, konvertieren Sie die Pulser-Objekte in eine JSON-Zeichenfolge, die als Eingabedaten verwendet werden kann.

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

Übermitteln der Impulssequenz an einen PASQAL target

  1. Legen Sie die richtigen Eingabe- und Ausgabedatenformate fest. Der folgende Code legt z. B. das Eingabedatenformat pasqal.pulser.v1 und das Ausgabedatenformat auf 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
    

    Hinweis

    Die zum Ausführen eines Auftrags auf der QPU erforderliche Zeit hängt von den aktuellen Warteschlangenzeiten ab. Sie können die durchschnittliche Warteschlangenzeit für einen target im Bereich "Anbieter " Ihres Arbeitsbereichs anzeigen.

  2. Übermitteln Sie das Programm an PASQAL. Bevor Sie Ihren Code an echte Quantenhardware übermitteln, empfiehlt es sich, Den Code im Emulator pasqal.sim.emu-tntargetzu testen.

    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
    }
    

Reichen Sie einen OpenQASM-Schaltkreis bei Quantinuum ein

  1. Erstellen Sie eine Quantenschaltung in der OpenQASM-Darstellung. Der folgende Code erstellt z. B. einen Teleportation-Schaltkreis:

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

    Oder laden Sie den Schaltkreis aus einer OpenQASM-Datei:

    with open("my_teleport.qasm", "r") as f:
        circuit = f.read()
    
  2. Übermitteln Sie den Schaltkreis an einen Quantinuum target. Im folgenden Beispiel wird der Auftrag an einen der Quantinuum-Simulator targetsübermittelt.

    target = workspace.get_targets(name="quantinuum.sim.h2-1sc")
    job = target.submit(circuit, shots=500)
    
  3. Warten Sie, bis der Auftrag abgeschlossen ist, und rufen Sie dann die Ergebnisse ab.

    results = job.get_results()
    print(results)
    

Hinweis

Diese Ergebnisse geben für jeden Schuss 000 zurück, was nicht zufällig ist. Dies liegt daran, dass der API-Validator nur überprüft, ob Ihr Code auf Quantinuum-Hardware ausgeführt werden kann, gibt aber für jede Quantenmessung 0 zurück. Für einen echten Zufallszahlengenerator müssen Sie Ihre Schaltung auf Quantenhardware ausführen.

Reichen Sie einen Quil-Schaltkreis bei Rigetti ein

Um einen Quil-Auftrag an einen Rigetti target zu übermitteln, verwenden Sie das Modul qdk.azurePython.

  1. Laden Sie die erforderlichen Importe.

    from azure.quantum import Workspace
    from azure.quantum.target.rigetti import Result, Rigetti, RigettiTarget, InputParams
    
  2. Erstellen Sie ein target Objekt, und übergeben Sie den Namen der Rigetti target , an die Sie Ihren Auftrag übermitteln möchten. Der folgende Code wählt z. B. die QVMtarget.

    target = Rigetti(workspace=workspace, name=RigettiTarget.QVM)
    
  3. Erstellen Sie ein Quil-Programm. Damit Ihr Programm akzeptiert wird, müssen Sie den Ausgabewert auf "ro" setzen.

    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. Sie können ein Ergebnis mit dem Namen des Messwerts indizieren. Im folgenden Code ist data_per_shot eine Liste mit der Länge num_shots, wobei jedes Element in dieser Liste eine weitere Liste ist, die die Daten für das Register aus dieser Aufnahme enthält.

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

    In diesem Fall, da der Typ des Registers BIT ist, ist der Typ ganzzahlig und der Wert entweder 0 oder 1.

    assert isinstance(ro_data_first_shot[0], int)
    assert ro_data_first_shot[0] == 1 or ro_data_first_shot[0] == 0
    
  5. Drucken Sie alle Daten aus.

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

Wichtig

Sie können nicht mehrere Schaltkreise an einem einzigen Auftrag übermitteln. Als Problemumgehung können Sie die backend.run Methode aufrufen, um jeden Schaltkreis asynchron zu übermitteln, und dann die Ergebnisse jedes Auftrags abzurufen. Zum Beispiel:

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

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