Przejdź do głównej treści

Uruchom swój pierwszy obwód na sprzęcie

Package versions

Kod na tej stronie został opracowany z użyciem poniższych wymagań. Zalecamy używanie tych lub nowszych wersji.

qiskit[all]~=2.5.2
qiskit-ibm-runtime~=0.47.0

Ten przykład składa się z dwóch części. Najpierw stworzysz prosty program kwantowy „Hello world" i uruchomisz go na jednostce przetwarzania kwantowego (QPU). Ponieważ prawdziwe badania kwantowe wymagają znacznie bardziej rozbudowanych programów, w drugiej części (Skalowanie do dużej liczby qubitów) przeskalujesz ten prosty program do poziomu użytkowego.

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib qiskit qiskit-ibm-runtime

Instalacja i uwierzytelnianie​

  1. Jeśli nie masz jeszcze zainstalowanego Qiskit, znajdź instrukcje w przewodniku Quickstart.

    • Zainstaluj klienta IBM Quantum Compute, aby uruchamiać zadania na sprzęcie kwantowym:

      pip install qiskit-ibm-runtime
    • Skonfiguruj środowisko do lokalnego uruchamiania notebooków Jupyter:

      pip install jupyter
  2. Skonfiguruj uwierzytelnianie w celu uzyskania dostępu do sprzętu kwantowego przez bezpłatny Plan Open.

    (Jeśli otrzymałeś e-mail z zaproszeniem do dołączenia do konta, zamiast tego wykonaj kroki dla zaproszonych użytkowników.)

    • Przejdź na IBM Quantum Platform, aby się zalogować lub utworzyć konto.

      Ważne

      Jeśli łączysz się przez serwer proxy, musisz używać qiskit-ibm-runtime w wersji v0.44.0 lub nowszej.

    • Wygeneruj swój klucz API (zwany też tokenem API) na pulpicie nawigacyjnym, a następnie skopiuj go w bezpieczne miejsce.

    • Przejdź na stronę Instances i znajdź instancję, której chcesz użyć. Najedź kursorem na jej CRN i kliknij, aby skopiować.

    • Zapisz swoje dane uwierzytelniające lokalnie za pomocą tego kodu:

      from qiskit_ibm_runtime import QiskitRuntimeService

      QiskitRuntimeService.save_account(
      # For `token`, use the 44-character API_KEY you created
      # and saved from the IBM Quantum Platform Home dashboard
      token="<your-api-key>",
      instance="<CRN>", # Optional
      )
  3. Teraz możesz korzystać z tego kodu w Pythonie za każdym razem, gdy chcesz uwierzytelnić się w usłudze IBM Quantum Compute Service:

    from qiskit_ibm_runtime import QiskitRuntimeService

    # Run every time you need the service
    service = QiskitRuntimeService()
Nie korzystasz z zaufanego środowiska Python?

Jeśli używasz komputera publicznego lub innego niezabezpieczonego środowiska, zamiast tego postępuj zgodnie z instrukcją ręcznego uwierzytelniania, aby chronić swoje dane uwierzytelniające.

Tworzenie i uruchamianie prostego programu kwantowego​

Cztery kroki pisania programu kwantowego przy użyciu wzorców Qiskit to:

  1. Odwzorowanie problemu na format natywny dla komputerów kwantowych.

  2. Optymalizacja Circuit i operatorów.

  3. Wykonanie przy użyciu kwantowej funkcji prymitywnej.

  4. Analiza wyników.

Krok 1. Odwzorowanie problemu na format natywny dla komputerów kwantowych​

W programie kwantowym obwody kwantowe (Circuit) są natywnym formatem reprezentowania instrukcji kwantowych, a operatory reprezentują obserwable, które mają być mierzone. Tworząc Circuit, zazwyczaj tworzysz nowy obiekt QuantumCircuit, a następnie sekwencyjnie dodajesz do niego instrukcje. Poniższy blok kodu tworzy Circuit, który generuje stan Bella — stan, w którym dwa Qubit są ze sobą w pełni splątane.

Uwaga: kolejność bitów

Qiskit SDK używa numeracji bitów LSb 0, gdzie nn-ta cyfra ma wartość 1≪n1 \ll n lub 2n2^n. Więcej szczegółów znajdziesz w temacie Kolejność bitów w Qiskit SDK.

# This cell is hidden from users. It hides several unnecessary warnings.

import warnings
import logging

warnings.filterwarnings("ignore", message=".*Instance was not set*")
warnings.filterwarnings("ignore", message=".*loading instance*")
warnings.filterwarnings("ignore", message=".*using instance*")

# This cell is hidden from users. It hides several unnecessary warnings.

class IgnoreSpecificMessages(logging.Filter):
def filter(self, record):
for text in [
"Instance was not set",
"Loading default saved account",
"Loading instance",
"Instance was not set",
"using instance",
]:
if text in record.getMessage():
return False
return True

logger = logging.getLogger("qiskit_ibm_runtime")
logger.addFilter(IgnoreSpecificMessages())

for handler in logger.handlers:
handler.addFilter(IgnoreSpecificMessages())
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorOptions
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from matplotlib import pyplot as plt
# Uncomment the next line if you want to use a simulator:
# from qiskit_ibm_runtime.fake_provider import FakeBelemV2

# Create a new circuit with two qubits
qc = QuantumCircuit(2)

# Add a Hadamard gate to qubit 0
qc.h(0)

# Perform a controlled-X gate on qubit 1, controlled by qubit 0
qc.cx(0, 1)

# Return a drawing of the circuit using MatPlotLib ("mpl").
# These guides are written by using Jupyter notebooks, which
# display the output of the last line of each cell.
# If you're running this in a script, use `print(qc.draw())` to
# print a text drawing.
qc.draw("mpl")

Output of the previous code cell

Pełną listę dostępnych operacji znajdziesz w dokumentacji QuantumCircuit. Tworząc obwody kwantowe, musisz także zastanowić się, jaki rodzaj danych ma być zwracany po wykonaniu. Qiskit oferuje dwa sposoby zwracania danych: możesz uzyskać dane próbkowania dla wybranego zestawu Qubit lub wartość oczekiwaną obserwabla. Przygotuj swoje zadanie, aby mierzyć Circuit za pomocą jednego z tych dwóch podejść, korzystając z prymitywów (szczegółowo omówionych w Kroku 3).

Ten przykład mierzy wartości oczekiwane przy użyciu podmodułu qiskit.quantum_info, który określa się za pomocą operatorów (obiektów matematycznych reprezentujących działanie lub proces zmieniający stan kwantowy). Poniższy blok kodu tworzy sześć dwuqubitowych operatorów Pauliego: IZ, IX, ZI, XI, ZZ i XX.

# Set up six different observables.

observables_labels = ["IZ", "IX", "ZI", "XI", "ZZ", "XX"]
observables = [SparsePauliOp(label) for label in observables_labels]
Notacja operatorów

Tutaj operator taki jak ZZ jest skrótem dla iloczynu tensorowego Z⊗ZZ\otimes Z, co oznacza jednoczesny pomiar Z na Qubit 1 i Z na Qubit 0, dający informacje o korelacji między nimi. Wartości oczekiwane tego typu są zazwyczaj zapisywane jako ⟨Z1Z0⟩\langle Z_1 Z_0 \rangle.

Jeśli stan jest splątany, pomiar ⟨Z1Z0⟩\langle Z_1 Z_0 \rangle powinien różnić się od iloczynu pomiarów ⟨I1⊗Z0⟩⟨Z1⊗I0⟩\langle I_1 \otimes Z_0 \rangle \langle Z_1 \otimes I_0 \rangle. Dla konkretnego stanu splątanego tworzonego przez opisany powyżej Circuit, pomiar ⟨Z1Z0⟩\langle Z_1 Z_0 \rangle powinien wynosić 1, a iloczyn ⟨I1⊗Z0⟩⟨Z1⊗I0⟩\langle I_1 \otimes Z_0 \rangle \langle Z_1 \otimes I_0 \rangle powinien być równy zero.

Krok 2. Optymalizacja Circuit i operatorów​

Wykonując Circuit na urządzeniu, ważne jest, aby zoptymalizować zestaw instrukcji zawartych w Circuit i zminimalizować całkowitą głębokość (w przybliżeniu liczbę instrukcji) Circuit. Dzięki temu uzyskasz jak najlepsze wyniki poprzez ograniczenie wpływu błędów i szumów. Ponadto instrukcje w Circuit muszą być zgodne z architekturą zestawu instrukcji (ISA) urządzenia Backend i muszą uwzględniać bramki bazowe oraz łączność Qubit tego urządzenia.

Poniższy kod tworzy instancję rzeczywistego urządzenia, do którego zostanie przesłane zadanie, i przekształca Circuit oraz operatory tak, aby były zgodne z ISA tego Backend. Wymaga to wcześniejszego zapisania danych uwierzytelniających.

service = QiskitRuntimeService()

backend = service.least_busy(simulator=False, operational=True)

# Convert to an ISA circuit and layout-mapped observables.
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(qc)

isa_circuit.draw("mpl", idle_wires=False)

Output of the previous code cell

Krok 3. Wykonanie przy użyciu kwantowych prymitywów​

Komputery kwantowe mogą generować losowe wyniki, dlatego zazwyczaj zbiera się próbkę wyjść, uruchamiając Circuit wiele razy. Możesz oszacować wartość obserwabla przy użyciu klasy Estimator. Estimator jest jednym z dwóch prymitywów; drugim jest Sampler, który służy do pobierania danych z komputera kwantowego. Obiekty te posiadają metodę run(), która wykonuje wybrany zestaw Circuit, obserwabli i parametrów (jeśli dotyczy) przy użyciu bloku ujednoliconego prymitywu (PUB).

# Construct the Estimator instance.

estimator = Estimator(mode=backend)
estimator.options.resilience_level = 1
estimator.options.default_shots = 5000

mapped_observables = [
observable.apply_layout(isa_circuit.layout) for observable in observables
]

# One pub, with one circuit to run against five different observables.
job = estimator.run([(isa_circuit, mapped_observables)])

# Use the job ID to retrieve your job data later
print(f">>> Job ID: {job.job_id()}")
>>> Job ID: dab7lm9l216s739p6q10

Po przesłaniu zadania możesz albo poczekać na jego zakończenie w bieżącej instancji Pythona, albo użyć job_id, aby pobrać dane w późniejszym czasie. (Szczegóły znajdziesz w sekcji dotyczącej pobierania zadań.)

Po zakończeniu zadania sprawdź jego dane wyjściowe za pomocą atrybutu result() zadania.

# This is the result of the entire submission. You submitted one Pub,
# so this contains one inner result (and some metadata of its own).
job_result = job.result()

# This is the result from our single pub, which had six observables,
# so contains information on all six.
pub_result = job.result()[0]
# Check there are six observables.
# If not, edit the comments in the previous cell and update this test.
assert len(pub_result.data.evs) == 6
Alternatywa: uruchomienie przykładu na symulatorze

Gdy uruchamiasz swój program kwantowy na prawdziwym urządzeniu, twoje zadanie musi czekać w kolejce przed wykonaniem. Aby zaoszczędzić czas, możesz zamiast tego użyć poniższego kodu, by uruchomić to małe zadanie na fake_provider w trybie lokalnego testowania qiskit-ibm-runtime. Pamiętaj, że jest to możliwe tylko dla małego Circuit. Gdy przeskalujesz program w następnej sekcji, będziesz potrzebować prawdziwego urządzenia.


# Use the following code instead if you want to run on a simulator:

from qiskit_ibm_runtime.fake_provider import FakeBelemV2
backend = FakeBelemV2()
estimator = Estimator(backend)

# Convert to an ISA circuit and layout-mapped observables.

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(qc)
mapped_observables = [
observable.apply_layout(isa_circuit.layout) for observable in observables
]

job = estimator.run([(isa_circuit, mapped_observables)])
result = job.result()

# This is the result of the entire submission. You submitted one Pub,
# so this contains one inner result (and some metadata of its own).

job_result = job.result()

# This is the result from our single pub, which had five observables,
# so contains information on all five.

pub_result = job.result()[0]

Krok 4. Analiza wyników​

Etap analizy to zazwyczaj miejsce, w którym możesz przetwarzać wyniki końcowe, korzystając na przykład z mitygacji błędów pomiarowych lub ekstrapolacji zerowego szumu (ZNE). Możesz przekazać te wyniki do kolejnego etapu przepływu pracy w celu dalszej analizy albo przygotować wykres kluczowych wartości i danych. Ogólnie rzecz biorąc, ten krok jest specyficzny dla danego problemu. W tym przykładzie narysuj wykres każdej ze zmierzonych wartości oczekiwanych dla naszego Circuit.

Wartości oczekiwane i odchylenia standardowe dla obserwabli podanych do Estimator są dostępne przez atrybuty PubResult.data.evs oraz PubResult.data.stds wynikowego zadania. Aby uzyskać wyniki z Sampler, użyj funkcji PubResult.data.meas.get_counts(), która zwróci dict pomiarów w postaci łańcuchów bitowych jako kluczy i odpowiadających im liczności. Więcej informacji znajdziesz w przewodniku Sampler — szybki start.

# Plot the result

values = pub_result.data.evs

errors = pub_result.data.stds

# plotting graph
plt.plot(observables_labels, values, "-o")
plt.xlabel("Observables")
plt.ylabel("Values")
plt.show()

Wynik poprzedniej komórki kodu

Zauważ, że dla Qubit 0 i 1 niezależne wartości oczekiwane zarówno X, jak i Z wynoszą 0, podczas gdy korelacje (XX i ZZ) wynoszą 1. To charakterystyczny przejaw splątania kwantowego.

# Make sure the results follow the claim from the previous markdown cell.
# This can happen when the device occasionally behaves strangely. If this cell
# fails, you may just need to run the notebook again.

_results = {obs: val for obs, val in zip(observables_labels, values)}
for _label in ["IZ", "IX", "ZI", "XI"]:
assert abs(_results[_label]) < 0.2
for _label in ["XX", "ZZ"]:
assert _results[_label] > 0.8

Skalowanie do dużej liczby Qubitów​

W obliczeniach kwantowych praca na skalę użytkową ma kluczowe znaczenie dla postępu w tej dziedzinie. Wymaga ona wykonywania obliczeń na znacznie większą skalę — z Circuit, które mogą wykorzystywać ponad 100 Qubitów i ponad 1000 Gate. Ten przykład pokazuje, jak wykonywać pracę na skalę użytkową na procesorach QPU IBM® poprzez tworzenie i analizę stanu GHZ złożonego ze 100 Qubitów. Wykorzystuje przepływ pracy Qiskit patterns i kończy się pomiarem wartości oczekiwanej ⟨Z0Zi⟩\langle Z_0 Z_i \rangle dla każdego Qubitu.

Krok 1. Sformułowanie problemu​

Napisz funkcję zwracającą QuantumCircuit, który przygotowuje nn-Qubitowy stan GHZ (będący rozszerzeniem stanu Bella), a następnie użyj tej funkcji do przygotowania 100-Qubitowego stanu GHZ i zebrania obserwabli do zmierzenia.

def get_qc_for_n_qubit_GHZ_state(n: int) -> QuantumCircuit:
"""This function will create a qiskit.QuantumCircuit (qc)
for an n-qubit GHZ state.

Args:
n (int): Number of qubits in the n-qubit GHZ state

Returns:
QuantumCircuit: Quantum circuit that generate the n-qubit GHZ state,
assuming all qubits start in the 0 state
"""
if isinstance(n, int) and n >= 2:
qc = QuantumCircuit(n)
qc.h(0)
for i in range(n - 1):
qc.cx(i, i + 1)
else:
raise Exception("n is not a valid input")
return qc

# Create a new circuit with 100 qubits in the GHZ state
n = 100
qc = get_qc_for_n_qubit_GHZ_state(n)

Następnie przypisz interesujące operatory. W tym przykładzie użyto operatorów ZZ między Qubitami, aby zbadać zachowanie wraz ze wzrostem odległości między nimi. Coraz mniej dokładne (zaburzone) wartości oczekiwane pomiędzy odległymi Qubitami ujawnią poziom obecnego szumu.

# ZZII...II, ZIZI...II, ... , ZIII...IZ
operator_strings = [
"Z" + "I" * i + "Z" + "I" * (n - 2 - i) for i in range(n - 1)
]

operators = [SparsePauliOp(operator) for operator in operator_strings]

Krok 2. Optymalizacja problemu pod kątem wykonania na sprzęcie kwantowym​

Poniższy kod przekształca Circuit i obserwable tak, aby pasowały do ISA Backend. Wymaga uprzedniego zapisania swoich danych uwierzytelniających.

service = QiskitRuntimeService()

backend = service.least_busy(
simulator=False, operational=True, min_num_qubits=100
)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)

isa_circuit = pm.run(qc)
isa_operators_list = [op.apply_layout(isa_circuit.layout) for op in operators]

Krok 3. Wykonanie na sprzęcie​

Prześlij zadanie i włącz tłumienie błędów, korzystając z techniki redukcji błędów zwanej dynamicznym odsprzęganiem. Poziom odporności określa, jak duże zabezpieczenie przed błędami ma zostać zastosowane. Wyższe poziomy generują dokładniejsze wyniki, kosztem dłuższego czasu przetwarzania. Szczegółowe wyjaśnienie opcji ustawionych w poniższym kodzie znajdziesz w artykule Error mitigation and suppression techniques.

options = EstimatorOptions()
options.resilience_level = 1
options.dynamical_decoupling.enable = True
options.dynamical_decoupling.sequence_type = "XY4"

# Create an Estimator object
estimator = Estimator(backend, options=options)
# Submit the circuit to Estimator
job = estimator.run([(isa_circuit, isa_operators_list)])
job_id = job.job_id()
print(job_id)
dab84omrrl7c7386h52g

Krok 4. Przetwarzanie końcowe wyników​

Po zakończeniu zadania narysuj wykres wyników i zauważ, że ⟨Z0Zi⟩\langle Z_0 Z_i \rangle maleje wraz ze wzrostem ii, choć w idealnej symulacji wszystkie ⟨Z0Zi⟩\langle Z_0 Z_i \rangle powinny wynosić 1.

# data
data = list(range(1, len(operators) + 1)) # Distance between the Z operators
result = job.result()[0]
values = result.data.evs # Expectation value at each Z operator.
values = [
v / values[0] for v in values
] # Normalize the expectation values to evaluate how they decay with distance.

# plotting graph
plt.plot(data, values, marker="o", label="100-qubit GHZ state")
plt.xlabel("Distance between qubits $i$")
plt.ylabel(r"$\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle $")
plt.legend()
plt.show()

Wynik poprzedniej komórki kodu

Poprzedni wykres pokazuje, że wraz ze wzrostem odległości między Qubitami sygnał zanika z powodu obecności szumu.

Następne kroki​

Rekomendacje