Freigeben über


So übermitteln Sie bestimmte formatierte Schaltkreise an Azure Quantum

Erfahren Sie, wie Sie das azure-quantumPython Paket 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 einem Notizbuch in Azure-Portal auszuführen, benötigen Sie Folgendes:

  • Ein Azure-Konto mit einem aktiven Abonnement. Wenn Sie nicht über ein Azure-Konto verfügen, registrieren Sie sich kostenlos, und registrieren Sie sich für ein Kostenpflichtiges Abonnement.
  • Azure Quantum-Arbeitsbereich Weitere Informationen finden Sie unter Erstellen eines Azure Quantum-Arbeitsbereichs.

Um Ihre Schaltkreise in Visual Studio Code zu entwickeln und auszuführen, benötigen Sie außerdem Folgendes:

Erstellen eines neuen Jupyter Notebooks

Sie können ein Notizbuch im VS Code oder direkt im Azure Quantum-Portal erstellen.

  1. Melden Sie sich beim Azure-Portal an, und wählen Sie den Arbeitsbereich aus dem vorherigen Schritt aus.
  2. Wählen Sie auf dem linken Blatt Notebooks aus.
  3. Klicken Sie auf Meine Notebooks und dann auf Neu hinzufügen.
  4. Wählen Sie in Kerneltyp die Option IPython aus.
  5. Geben Sie einen Namen für die Datei ein, und klicken Sie auf " Datei erstellen".

Wenn Ihr neues Notebook geöffnet wird, wird automatisch basierend auf Ihren Abonnement- und Arbeitsbereichsinformationen der Code für die erste Zelle erstellt.

from azure.quantum import Workspace
workspace = Workspace ( 
    resource_id = "", # Your resource_id 
    location = ""  # 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.v1 microsoft.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
except ImportError:
    !pip -q install pulser

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 nicht den Anforderungen Ihres Experiments entsprechen, 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.

    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
    
    # 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 reale Quantenhardware übermitteln, können Sie ihren Code mit dem Emulator als eins pasqal.sim.emu-tn targettesten.

    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.h1-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

    Im Histogramm sehen Sie, dass der Zufallszahlengenerator jedes Mal 0 zurückgegeben hat, was nicht gerade unserer Vorstellung von Zufälligkeit entspricht. 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-9Q-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

Das Übermitteln mehrerer Schaltungen in einem einzelnen Auftrag wird derzeit nicht unterstützt. 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())