Udostępnij za pośrednictwem


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

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 docelowymi platformami obliczeniowymi kwantowymi. 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 należy oszacować, ile będzie kosztować uruchomienie.

    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

Tworzenie rejestru kwantowego

Przed kontynuowaniem należy zdefiniować zarówno rejestr, jak i układ. Rejestr określa, gdzie będą rozmieszczone atomy, podczas gdy układ określa położenie pułapek niezbędnych do przechwycenia i struktury tych atomów w rejestrze.

Aby uzyskać szczegółowe informacje na temat układów, zobacz dokumentację pulsera.

  • Najpierw należy utworzyć obiekt "devices", aby zaimportować komputer targetkwantowy PASQAL , Fresnel.

    from pulser_pasqal import PasqalCloud
    
    devices = PasqalCloud().fetch_available_devices()
    QPU = devices["FRESNEL"]
    
Wstępnie skalibrowane układy

Urządzenie definiuje listę wstępnie skalibrowanych układów. Możesz utworzyć rejestr z jednego z tych układów.

Jest to zalecana opcja, ponieważ poprawi wydajność QPU.

  • Opcja 1. Definiowanie rejestru przy użyciu wstępnie skalibrowanych układów

    Sprawdź układy dostępne w usłudze Fresnel i zdefiniuj rejestr z tego układu. Zapoznaj się z dokumentacją pulsatora, aby uzyskać więcej informacji na temat tego, jak to zrobić.

    Przykład:

    # 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()
    
Dowolne układy

Jeśli wstępnie skalibrowane układy nie spełniają wymagań eksperymentu, możesz utworzyć układ niestandardowy.

Dla każdego dowolnego rejestru, neutral-atom QPU umieści pułapki zgodnie z układem, który musi zostać poddany kalibracji. Ponieważ każda kalibracja wymaga czasu, zaleca się ponowne użycie istniejącego układu skalibrowanego zawsze wtedy, gdy jest to możliwe

  • Opcja 2. Automatyczne uzyskiwanie układu ze zdefiniowanego rejestru

    Ta opcja umożliwia automatyczne generowanie układu na podstawie określonego rejestru. Jednak w przypadku dużych rejestrów ten proces może przynieść nie optymalne rozwiązania ze względu na ograniczenia w algorytmie używanym do tworzenia układu.

    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) 
    
  • Opcja 3. Definiowanie rejestru przy użyciu ręcznie zdefiniowanego układu

    • Tworzenie dowolnego układu z 20 pułapkami losowo umieszczonymi w płaszczyźnie 2D
    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")
    
    • Definiowanie rejestru przy użyciu określonych identyfikatorów pułapek
    trap_ids = [4, 8, 19, 0]
    reg = layout.define_register(*trap_ids, qubit_ids=["a", "b", "c", "d"])
    reg.draw()
    

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 zdefiniować atrybuty sekwencji impulsów, deklarując kanały, które będą używane do kontrolowania atomów. Aby utworzyć element Sequence, należy podać Register wystąpienie wraz z urządzeniem, na którym zostanie wykonana sekwencja. Na przykład następujący kod deklaruje jeden kanał: ch0.

    Uwaga

    Możesz użyć QPU = devices["FRESNEL"] urządzenia lub zaimportować urządzenie wirtualne z urządzenia Pulser, aby uzyskać większą elastyczność. Użycie elementu VirtualDevice umożliwia tworzenie sekwencji, które jest mniej ograniczone przez specyfikacje urządzeń, dzięki czemu nadaje się do wykonywania w emulatorze. Aby uzyskać więcej informacji, zobacz dokumentację pulsera.

    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. Dodaj impulsy do sekwencji. W tym celu należy utworzyć i dodać impulsy do zadeklarowanych kanałów. Na przykład poniższy kod tworzy impuls i dodaje go do kanału 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()
    

    Na poniższej ilustracji przedstawiono sekwencję impulsów. Sekwencja impulsów

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

    Uwaga

    Czas wymagany do uruchomienia zadania 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. Przed przesłaniem kodu do rzeczywistego sprzętu kwantowego możesz przetestować kod przy użyciu emulatora pasqal.sim.emu-tn targetjako .

    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
    }
    

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 należy oszacować, ile będzie kosztować uruchomienie.

    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-9Q-3") 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())