Jak przesłać określone sformatowane obwody do usługi Azure Quantum

Dowiedz się, jak używać azure-quantumPython pakietu do przesyłania obwodów w określonych formatach do usługi Azure Quantum. W tym artykule przedstawiono sposób przesyłania obwodów w następujących formatach:

Aby uzyskać więcej informacji, zobacz Quantum circuits (Obwody kwantowe).

Uwaga

Zestaw Microsoft Quantum Development Kit (klasyczny zestaw QDK) nie będzie już obsługiwany po 30 czerwca 2024 r. Jeśli jesteś istniejącym deweloperem zestawu QDK, zalecamy przejście do nowego zestawu Azure Quantum Development Kit (Modern QDK), aby kontynuować opracowywanie rozwiązań kwantowych. Aby uzyskać więcej informacji, zobacz Migrowanie kodu Q# do nowoczesnego zestawu QDK.

Wymagania wstępne

Aby uruchomić obwody w notesie w witrynie Azure Portal, potrzebne są następujące elementy:

  • Konto platformy Azure z aktywną subskrypcją. Jeśli nie masz konta platformy Azure, zarejestruj się bezpłatnie i zarejestruj się w celu korzystania z subskrypcji z płatnością zgodnie z rzeczywistym użyciem.
  • Obszar roboczy usługi Azure Quantum. Aby uzyskać więcej informacji, zobacz Tworzenie obszaru roboczego usługi Azure Quantum.

Aby opracowywać i uruchamiać obwody w programie Visual Studio Code, potrzebne są również następujące elementy:

Tworzenie nowego notesu Jupyter

Notes można utworzyć w programie VS Code lub bezpośrednio w witrynie Azure Quantum Portal.

  1. Zaloguj się do witryny Azure Portal i wybierz obszar roboczy z poprzedniego kroku.
  2. W bloku po lewej stronie wybierz pozycję Notesy.
  3. Kliknij pozycję Moje notesy i kliknij pozycję Dodaj nowy.
  4. W obszarze Typ jądra wybierz pozycję IPython.
  5. Wpisz nazwę pliku, a następnie kliknij przycisk Utwórz plik.

Po otwarciu nowego notesu automatycznie tworzy kod dla pierwszej komórki na podstawie informacji o subskrypcji i obszarze roboczym.

from azure.quantum import Workspace
workspace = Workspace ( 
    resource_id = "", # Your resource_id 
    location = ""  # Your workspace location (for example, "westus") 
)

Przesyłanie obwodów w formacie QIR

Quantum Intermediate Representation (QIR) to pośrednia reprezentacja, która służy jako wspólny interfejs między językami/strukturami programowania kwantowego i targetplatformami obliczeń kwantowych. Aby uzyskać więcej informacji, zobacz Quantum Intermediate Representation (Reprezentacja pośrednia kwantowa).

  1. Utwórz obwód QIR. Na przykład poniższy kod tworzy prosty obwód splątania.

    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. submit_qir_job Utwórz funkcję pomocnika, aby przesłać obwód QIR do elementu target. Należy pamiętać, że formaty danych wejściowych i wyjściowych są określane odpowiednio jako qir.v1 i 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. target Wybierz obwód QIR i prześlij go do usługi Azure Quantum. Aby na przykład przesłać obwód QIR do symulatora targetIonQ:

    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]}
    

Przesyłanie obwodu z formatem specyficznym dla dostawcy do usługi Azure Quantum

Oprócz języków QIR, takich jak Q# lub Qiskit, można przesyłać obwody kwantowe w formatach specyficznych dla dostawcy do usługi Azure Quantum. Każdy dostawca ma własny format reprezentujący obwody kwantowe.

Przesyłanie obwodu do usługi IonQ przy użyciu formatu JSON

  1. Utwórz obwód kwantowy przy użyciu niezależnego od języka formatu JSON obsługiwanego przez IonQ targets, zgodnie z opisem w dokumentacji interfejsu API IonQ. Na przykład poniższy przykład tworzy superpozycję między trzema kubitami:

    circuit = {
        "qubits": 3,
        "circuit": [
            {
            "gate": "h",
            "target": 0
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 1
            },
            {
            "gate": "cnot",
            "control": 0,
            "target": 2
            },
        ]
    }
    
  2. Prześlij obwód do IonQ target. W poniższym przykładzie użyto symulatora IonQ, który zwraca Job obiekt.

    target = workspace.get_targets(name="ionq.simulator")
    job = target.submit(circuit)
    
  3. Poczekaj, aż zadanie zostanie ukończone, a następnie pobierz wyniki.

    results = job.get_results()
    print(results)
    
    .....
    {'duration': 8240356, 'histogram': {'0': 0.5, '7': 0.5}}
    
  4. Następnie można wizualizować wyniki przy użyciu biblioteki Matplotlib.

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

    Dane wyjściowe zadania IonQ

  5. Przed uruchomieniem zadania w QPU można oszacować, ile będzie kosztować uruchomienie. Aby oszacować koszt uruchamiania zadania w QPU, możesz użyć estimate_cost metody :

    target = workspace.get_targets(name="ionq.qpu")
    cost = target.estimate_cost(circuit, shots=500)
    
    print(f"Estimated cost: {cost.estimated_total}")
    

    Spowoduje to wyświetlenie szacowanych kosztów w USD.

    Uwaga

    Aby uzyskać najbardziej aktualne szczegóły cennika, zobacz Cennik IonQ lub znajdź obszar roboczy i wyświetl opcje cennika na karcie "Dostawca" obszaru roboczego za pośrednictwem: aka.ms/aq/myworkspaces.

Przesyłanie obwodu do biblioteki PASQAL przy użyciu zestawu SDK pulsera

Aby przesłać obwód do ZESTAWU PASQAL, możesz użyć zestawu SDK pulsera do tworzenia sekwencji impulsów i przesyłania ich do zestawu PASQAL target.

Instalowanie zestawu SDK pulsera

Pulser to struktura do komponowania, symulowania i wykonywania sekwencji impulsów dla urządzeń kwantowych neutral-atom. Jest on zaprojektowany przez PASQAL jako przekazywanie w celu przesyłania eksperymentów kwantowych do ich procesorów kwantowych. Aby uzyskać więcej informacji, zobacz dokumentację pulsera.

Aby przesłać sekwencje impulsów, najpierw zainstaluj pakiety zestawu SDK pulsera:

try:
    import pulser
except ImportError:
    !pip -q install pulser
    !pip -q install pulser-core

Tworzenie rejestru kwantowego

  1. Najpierw załaduj wymagane importy:

    import numpy as np
    import pulser
    from pprint import pprint
    from pulser import Pulse, Sequence, Register
    
  2. QPU PASQAL składa się z neutralnych atomów uwięzionych w dobrze zdefiniowanych pozycjach w kratce. Aby zdefiniować rejestry kwantowe, należy utworzyć tablicę kubitów na daszku. Na przykład poniższy kod tworzy 4x4 kwadratową siatkę kubitów:

    L = 4
    square = np.array([[i, j] for i in range(L) for j in range(L)], dtype=float)
    square -= np.mean(square, axis=0)
    square *= 5
    
    qubits = dict(enumerate(square))
    reg = Register(qubits)
    reg.draw()
    

    Wykres 4x4 kwadratowej siatki z 16 kubitami.

Pisanie sekwencji impulsów

Neutralne atomy są kontrolowane za pomocą impulsów laserowych. Zestaw SDK pulsera umożliwia tworzenie sekwencji impulsów, które mają być stosowane do rejestru kwantowego.

  1. Najpierw należy skonfigurować sekwencję impulsów i zadeklarować kanały, które będą używane do kontrolowania atomów. Na przykład następujący kod deklaruje dwa kanały: ch0 i ch1.

    from pulser.devices import Chadoq2
    
    seq = Sequence(reg, Chadoq2)
    
    seq.declare_channel("ch0", "raman_local")
    print("Available channels after declaring 'ch0':")
    pprint(seq.available_channels)
    
    seq.declare_channel("ch1", "rydberg_local", initial_target=4)
    print("\nAvailable channels after declaring 'ch1':")
    pprint(seq.available_channels)
    

    Kilka kwestii, które należy wziąć pod uwagę:

    • A Sequence in Pulser to seria operacji, które mają być wykonywane w rejestrze kwantowym.
    • Kod konfiguruje sekwencję operacji do wykonania na urządzeniu AnalogDevice . AnalogDevice jest wstępnie zdefiniowanym urządzeniem w pulserze, które reprezentuje komputer kwantowy fresnel1.
  2. Utwórz sekwencję impulsów. W tym celu należy utworzyć i dodać impulsy do zadeklarowanych kanałów. Na przykład poniższy kod tworzy prosty impuls i dodaje go do kanału ch0, a następnie tworzy złożony impuls i dodaje go do kanału ch1.

    seq.target(1, "ch0") # Target qubit 1 with channel "ch0"
    simple_pulse = Pulse.ConstantPulse(200, 2, -10, 0)
    seq.add(simple_pulse, "ch0") # Add the pulse to channel "ch0"
    seq.delay(100, "ch1")
    from pulser.waveforms import RampWaveform, BlackmanWaveform
    
    duration = 1000
    # Create a Blackman waveform with a duration of 1000 ns and an area of pi/2 rad
    amp_wf = BlackmanWaveform(duration, np.pi / 2)  
    # Create a ramp waveform with a duration of 1000 ns and a linear sweep from -20 to 20 rad/µs
    detuning_wf = RampWaveform(duration, -20, 20) 
    
    # Create a pulse with the amplitude waveform amp_wf, the detuning waveform detuning_wf, and a phase of 0 rad.
    complex_pulse = Pulse(amp_wf, detuning_wf, phase=0) 
    complex_pulse.draw()
    seq.add(complex_pulse, "ch1") # Add the pulse to channel "ch1"
    

Obraz przedstawia prosty i złożony impuls.

Wykres prostego i złożonego impulsu.

Konwertowanie sekwencji na ciąg JSON

Aby przesłać sekwencje impulsów, należy przekonwertować obiekty Pulser na ciąg JSON, który może być używany jako dane wejściowe.

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)
    #print(json.dumps(input_data, indent=4, sort_keys=True))
    return to_send

Przesyłanie sekwencji impulsów do USŁUGI PASQAL target

  1. Najpierw należy ustawić odpowiednie formaty danych wejściowych i wyjściowych. Na przykład poniższy kod ustawia format danych wejściowych na pasqal.pulser.v1 , a format danych wyjściowych na pasqal.pulser-results.v1wartość .

    # Submit the job with proper input and output data formats
    def submit_job(target, seq):
        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=100 # Number of shots
        )
    
        print(f"Queued job: {job.id}")
        job.wait_until_completed()
        print(f"Job completed with state: {job.details.status}")
        result = job.get_results()
    
        return result
    

    Uwaga

    Czas wymagany do uruchomienia obwodu na QPU zależy od bieżących czasów kolejki. Średni czas kolejki dla elementu target można wyświetlić, wybierając blok Dostawcy obszaru roboczego.

  2. Prześlij program do APLIKACJI PASQAL. Możesz na przykład przesłać program do PASQAL Emu-TN target.

    target = workspace.get_targets(name="pasqal.sim.emu-tn")
    submit_job(target, seq)
    
    {'0000000000000000': 59,
     '0000100000000000': 39,
     '0100000000000000': 1,
     '0100100000000000': 1}
    

Przesyłanie obwodu do quantinuum przy użyciu programu OpenQASM

  1. Utwórz obwód kwantowy w reprezentacji OpenQASM . Na przykład poniższy przykład tworzy obwód teleportacji:

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

    Opcjonalnie można załadować obwód z pliku:

    with open("my_teleport.qasm", "r") as f:
        circuit = f.read()
    
  2. Prześlij obwód do kwantynauum target. W poniższym przykładzie użyto modułu sprawdzania poprawności interfejsu API Quantinuum, który zwraca Job obiekt.

    target = workspace.get_targets(name="quantinuum.sim.h1-1sc")
    job = target.submit(circuit, shots=500)
    
  3. Poczekaj, aż zadanie zostanie ukończone, a następnie pobierz wyniki.

    results = job.get_results()
    print(results)
    
    ........
    {'c0': ['000',
    '000',
    '000',
    '000',
    '000',
    '000',
    '000',
    ...
    ]}
    
  4. Następnie można wizualizować wyniki przy użyciu biblioteki Matplotlib.

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

    Dane wyjściowe zadania Quantinuum

    Patrząc na histogram, można zauważyć, że generator liczb losowych zwrócił 0 za każdym razem, co nie jest bardzo losowe. Jest to spowodowane tym, że podczas gdy moduł sprawdzania poprawności interfejsu API gwarantuje, że kod zostanie pomyślnie uruchomiony na sprzęcie Quantinuum, zwraca również wartość 0 dla każdego pomiaru kwantowego. W przypadku rzeczywistego generatora liczb losowych należy uruchomić obwód na sprzęcie kwantowym.

  5. Przed uruchomieniem zadania w QPU można oszacować, ile będzie kosztować uruchomienie. Aby oszacować koszt uruchamiania zadania w QPU, możesz użyć estimate_cost metody .

    target = workspace.get_targets(name="quantinuum.qpu.h1-1")
    cost = target.estimate_cost(circuit, shots=500)
    
    print(f"Estimated cost: {cost.estimated_total}")
    

    Spowoduje to wyświetlenie szacowanych kosztów w H-System Quantum Credit (HQCs).

    Uwaga

    Aby uruchomić oszacowanie kosztów względem kwantuum target, należy najpierw ponownie załadować pakiet azure-quantumPython za pomocą parametru [qiskit] i upewnić się, że masz najnowszą wersję zestawu Qiskit. Aby uzyskać więcej informacji, zobacz Aktualizowanie pakietu azure-quantumPython.

    Uwaga

    Aby uzyskać najbardziej aktualne szczegóły cennika, zobacz Cennik usługi Azure Quantum lub znajdź obszar roboczy i wyświetl opcje cennika na karcie "Dostawca" obszaru roboczego za pośrednictwem: aka.ms/aq/myworkspaces.

Przesyłanie obwodu do Rigetti przy użyciu Quil

Najprostszym sposobem przesyłania zadań Quil jest użycie pakietu pyquil-for-azure-quantum , ponieważ umożliwia korzystanie z narzędzi i dokumentacji biblioteki pyQuil . Bez tego pakietu narzędzie pyQuil może służyć do konstruowania programów Quil, ale nie do przesyłania ich do usługi Azure Quantum.

Można również tworzyć programy Quil ręcznie i przesyłać je bezpośrednio przy użyciu azure-quantum pakietu.

  1. Najpierw załaduj wymagane importy.

    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. get_qvm Użyj funkcji orget_qpu, aby uzyskać połączenie z maszyną QVM lub QPU.

    qc = get_qvm()  # For simulation
    # qc = get_qpu("Ankaa-2") for submitting to a QPU
    
  3. Utwórz program Quil. Każdy prawidłowy program Quil jest akceptowany, ale odczyt musi mieć nazwę ro.

    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 W tym miejscu znajduje się tablicanumpy, dzięki czemu można użyć numpy metod.

    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. Wydrukuj wszystkie dane.

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

Ważne

Przesyłanie wielu obwodów w jednym zadaniu nie jest obecnie obsługiwane. Aby obejść ten problem, możesz wywołać backend.run metodę , aby przesłać każdy obwód asynchronicznie, a następnie pobrać wyniki każdego zadania. Na przykład:

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

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