Freigeben über


So übermitteln Sie bestimmte formatierte Schaltkreise an Azure Quantum

Erfahren Sie, wie Sie das qdk.azurePython Modul 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, benötigen Sie Folgendes:

Erstellen eines neuen Jupyter-Notizbuchs

Führen Sie die folgenden Schritte aus, um ein Notizbuch in VS Code zu erstellen:

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

  2. Geben Sie ein und wählen Sie Erstellen: Neues Jupyter-Notizbuch aus.

  3. Um eine Verbindung mit dem Azure Quantum-Dienst herzustellen, benötigt Ihr Programm die Ressourcen-ID und den Standort Ihres Azure Quantum-Arbeitsbereichs.

    1. Melden Sie sich bei Ihrem Azure-Konto an, https://portal.azure.com
    2. Wählen Sie Ihren Azure Quantum-Arbeitsbereich aus, und navigieren Sie zu "Übersicht".
    3. Kopieren Sie die Parameter in den Feldern.
  4. Fügen Sie in der ersten Zelle Ihres Notizbuchs die Werte in den folgenden Workspace Konstruktor ein, um ein workspace Objekt zu erstellen, das eine Verbindung mit Ihrem Azure Quantum-Arbeitsbereich herstellt.

    from qdk.azure import Workspace
    workspace = Workspace ( 
        resource_id = "", # Add your resource_id 
        location = ""  # Add your workspace location (for example, "westus") 
    )
    

Übermitteln von QIR-formatierten Schaltkreisen

Die Quantenzwischendarstellung (Quantum Intermediate Representation, QIR) ist eine Zwischendarstellung, die als allgemeine Schnittstelle zwischen Quantenprogrammiersprachen/-frameworks und zielgerichteten Quantencomputingplattformen fungiert. Weitere Informationen finden Sie unter Quantum Intermediate Representation.

  1. Erstellen Sie den QIR-Schaltkreis. Der folgende Code erstellt z. B. einen einfachen Veranglement-Schaltkreis.

    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 eine target. Beachten Sie, dass die Eingabe- und Ausgabedatenformate entsprechend qir.v1microsoft.quantum-results.v1angegeben werden.

    # 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. Wählen Sie einen target QIR-Schaltkreis aus, und übermitteln Sie sie an Azure Quantum. Um z. B. 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
    
    {'Histogram': ['(0, 0)', 0.5, '(1, 1)', 0.5]}
    

Übermitteln eines Schaltkreises mit einem anbieterspezifischen Format an Azure Quantum

Neben QIR-Sprachen wie Q# oder Qiskit können Sie Quantenschaltungen in anbieterspezifischen Formaten an Azure Quantum übermitteln. Jeder Anbieter hat ein eigenes Format für die Darstellung von Quantenschaltungen.

Übermitteln eines Schaltkreises an IonQ im JSON-Format

  1. Erstellen Sie eine Quantenschaltung mit dem sprachunabhängigen JSON-Format, das vom IonQ unterstützt wird, wie in der IonQ-API-Dokumentation targetsbeschrieben. So wird etwa im folgenden Beispiel eine Superposition zwischen drei Qubits erstellt:

    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. Warten Sie, bis der Auftrag abgeschlossen ist, und rufen Sie dann die Ergebnisse ab.

    results = job.get_results()
    print(results)
    
    .....
    {'duration': 8240356, 'histogram': {'0': 0.5, '7': 0.5}}
    
  4. Anschließend können die Ergebnisse mithilfe von Matplotlib visualisiert werden.

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

    IonQ-Auftragsausgabe

  5. Bevor Sie einen Auftrag auf der QPU ausführen, sollten Sie schätzen, wie viel die Ausführung kostet.

    Hinweis

    Die aktuellen Preisdetails finden Sie unter Preise. Suchen Sie alternativ nach Ihrem Arbeitsbereich, und zeigen Sie die Preisoptionen auf der Registerkarte „Anbieter“ Ihres Arbeitsbereichs über aka.ms/aq/myworkspaces an.

Übermitteln eines Schaltkreises an PASQAL mit Pulser SDK

Um einen Schaltkreis an PASQAL zu übermitteln, können Sie das Pulser SDK verwenden, um Impulssequenzen zu erstellen und an den PASQAL targetzu übermitteln.

Installieren des Pulser SDK

Pulser ist ein Framework zum Verfassen, Simulieren und Ausführen von Impulssequenzen für neutrale Atom-Quantengeräte. Es 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

Sie müssen sowohl ein Register als auch ein Layout definieren, bevor Sie fortfahren. Das Register gibt an, wo Atome angeordnet werden sollen, während das Layout die Positionierung von Fallen angibt, die zum Erfassen und Strukturieren dieser Atome innerhalb des Registers erforderlich sind.

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

  • Zunächst 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.

Dies ist die empfohlene Option, da sie die Leistung der QPU verbessert.

  • Option 1: Definieren Ihres Registers mithilfe von vorkalibrierungsbasierten Layouts

    Überprüfen Sie die auf Fresnel verfügbaren Layouts, und definieren Sie Ihr Register aus diesem Layout. Weitere Informationen dazu finden Sie in der Pulserdokumentation.

    Beispiel:

    # let's say we are interested in 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,3,5]
    reg = layout.define_register(*traps)
    # You can draw the resulting register to verify it matches your expectations
    reg.draw()
    
Beliebige Layouts

Wenn vordefinierte Layouts die Anforderungen Ihres Experiments nicht erfüllen, können Sie ein benutzerdefiniertes Layout erstellen.

Für jedes gegebene beliebige Register platziert eine neutrale Atom-QPU Fallen nach dem Layout, die dann einer Kalibrierung unterzogen werden müssen. Da jede Kalibrierung Zeit erfordert, ist es im Allgemeinen ratsam, ein vorhandenes kalibriertes Layout nach Möglichkeit wiederzuverwenden.

  • Option 2: Automatisches Ableiten eines Layouts aus dem definierten Register

    Diese Option ermöglicht die automatische Generierung eines Layouts basierend auf einem angegebenen Register. Bei großen Registern kann dieser Prozess jedoch aufgrund von Einschränkungen im Algorithmus, der zum Erstellen des Layouts verwendet wird, suboptimale Lösungen liefern.

    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) 
    
  • Option 3: Definieren Des Registers mithilfe eines manuell definierten Layouts

    • Erstellen eines beliebigen Layouts mit 20 Fallen, die zufällig in einer 2D-Ebene positioniert sind

      import numpy as np
      from pulser.register.register_layout import RegisterLayout
      
      # Generating 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)
      # Creating the layout
      layout = RegisterLayout(traps, slug="random_20")
      
      • Definieren Des Registers mit bestimmten 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

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

  1. Zunächst definieren Sie die Impulssequenzattribute, indem Sie die Kanäle deklarieren, die zum Steuern der Atome verwendet werden. Zum Erstellen eines Sequence, müssen Sie eine Register Instanz zusammen mit dem Gerät bereitstellen, auf dem die Sequenz ausgeführt wird. Der folgende Code deklariert beispielsweise einen Kanal: ch0.

    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 von Gerätespezifikationen weniger eingeschränkt ist, sodass sie für die Ausführung in einem Emulator geeignet ist. Weitere Informationen finden Sie in der Pulser-Dokumentation.

    from pulser import Sequence
    
    seq = Sequence(reg, QPU)
    # print the available channels for your sequence
    print(seq.available_channels)
    # Declare a channel. In this example we will be using `rydberg_global`
    seq.declare_channel("ch0", "rydberg_global")
    
  2. Fügen Sie Ihrer Sequenz Impulse hinzu. Dazu erstellen und fügen Sie 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, müssen Sie die Pulser-Objekte in eine JSON-Zeichenfolge konvertieren, 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 PASQAL target

  1. Zunächst müssen Sie die richtigen Eingabe- und Ausgabedatenformate festlegen. 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 ein Objekt target anzeigen, indem Sie das Blatt "Anbieter " Ihres Arbeitsbereichs auswählen.

  2. Übermitteln Sie das Programm an PASQAL. Bevor Sie Ihren Code an echte Quantenhardware übermitteln, können Sie Ihren Code mit dem Emulator pasqal.sim.emu-tn als target 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
    }
    

Übermitteln eines Schaltkreises an Quantinuum mit OpenQASM

  1. Erstellen Sie eine Quantenschaltung in der OpenQASM-Darstellung. Im folgenden Beispiel wird etwa eine Teleportationsschaltung erstellt:

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

    Die Schaltung kann optional auch aus einer Datei geladen werden:

    with open("my_teleport.qasm", "r") as f:
        circuit = f.read()
    
  2. Übermitteln Sie den Schaltkreis an das Quantinuum target. Im folgenden Beispiel wird die Quantinuum-API-Validierung verwendet, die ein Job-Objekt zurückgibt.

    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)
    
    ........
    {'c0': ['000',
    '000',
    '000',
    '000',
    '000',
    '000',
    '000',
    ...
    ]}
    
  4. Anschließend können die Ergebnisse mithilfe von Matplotlib visualisiert werden.

    import pylab as pl
    pl.hist(results["c0"])
    pl.ylabel("Counts")
    pl.xlabel("Bitstring")
    

    Quantinuum-Auftragsausgabe

    Wenn Sie das Histogramm betrachten, stellen Sie möglicherweise fest, dass der Zufallszahlengenerator jedes Mal 0 zurückgegeben hat, was nicht sehr zufällig ist. Das liegt daran, dass die API-Validierung zwar sicherstellt, dass Ihr Code erfolgreich auf Quantinuum-Hardware ausgeführt wird, aber auch 0 für jede Quantenmessung zurückgegeben wird. Für einen echten Zufallszahlengenerator müssen Sie Ihre Schaltung auf Quantenhardware ausführen.

  5. Bevor Sie einen Auftrag auf der QPU ausführen, sollten Sie schätzen, wie viel die Ausführung kostet.

    Hinweis

    Die aktuellsten Preisdetails finden Sie unter Azure Quantum-Preise, oder suchen Sie Ihren Arbeitsbereich und zeigen Sie Preisoptionen auf der Registerkarte "Anbieter" Ihres Arbeitsbereichs über: aka.ms/aq/myworkspaces an.

Übermitteln eines Schaltkreises an Rigetti mithilfe von Quil

Die einfachste Möglichkeit zum Übermitteln von Quil-Aufträgen ist die Verwendung des pyquil-for-azure-quantum-Pakets , da Sie die Tools und Dokumentationen der pyQuil-Bibliothek verwenden können. Ohne dieses Paket kann pyQuil verwendet werden, um Quil-Programme zu erstellen , aber nicht, um sie an Azure Quantum zu übermitteln.

Sie können quil-Programme auch manuell erstellen und mithilfe des azure-quantum Pakets direkt übermitteln.

  1. Laden Sie zunächst die erforderlichen Importe.

    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. Verwenden Sie die get_qvm Funktion, get_qpu um eine Verbindung mit QVM oder QPU abzurufen.

    qc = get_qvm()  # For simulation
    # qc = get_qpu("Ankaa-3") for submitting to a QPU
    
  3. Erstellen Sie ein Quil-Programm. Jedes gültige Quil-Programm wird akzeptiert, aber das Lesen muss benannt rowerden.

    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. data_per_shot Hier ist ein numpy Array, sodass Sie Methoden verwenden numpy können.

    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. Drucken Sie alle Daten aus.

    print("Data from 'ro' 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. Sie können allerdings die Methode backend.run aufrufen, um die einzelnen Schaltungen asynchron zu übermitteln, und anschließend die Ergebnisse jedes Auftrags abrufen. Zum Beispiel:

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

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