Jak przesłać określone sformatowane obwody do usługi Azure Quantum
Dowiedz się, jak używać azure-quantum
Python 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:
Środowisko Python z zainstalowanym programem Python i programem .
Program VS Code z zainstalowanym zestawem Azure Quantum Development Kit i Pythonrozszerzeniami Jupyter.
Pakiet Azure Quantum
qsharp
,azure-quantum
iipykernel
.python -m pip install --upgrade qsharp azure-quantum ipykernel
Tworzenie nowego notesu Jupyter
Notes można utworzyć w programie VS Code lub bezpośrednio w witrynie Azure Quantum Portal.
- Zaloguj się do witryny Azure Portal i wybierz obszar roboczy z poprzedniego kroku.
- W bloku po lewej stronie wybierz pozycję Notesy.
- Kliknij pozycję Moje notesy i kliknij pozycję Dodaj nowy.
- W obszarze Typ jądra wybierz pozycję IPython.
- 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).
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} """
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 jakoqir.v1
imicrosoft.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
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
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 }, ] }
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)
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}}
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")
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.
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 elementuVirtualDevice
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")
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.
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
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 napasqal.pulser-results.v1
wartość .# 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.
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
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()
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)
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', ... ]}
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")
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.
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.
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
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
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"]
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
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())