Przejdź do głównej treści

Implementacja w Qiskit

W tej sekcji przyjrzymy się kilku implementacjom w Qiskit pojęć wprowadzonych w tej lekcji. Jeśli chcesz uruchomić te implementacje samodzielnie — co jest gorąco zalecane — zajrzyj na stronę Instalacja Qiskit w dokumentacji IBM Quantum, gdzie znajdziesz szczegóły dotyczące konfiguracji Qiskit.

Należy pamiętać, że Qiskit jest stale rozwijany i skupia się przede wszystkim na maksymalizacji wydajności komputerów kwantowych, do obsługi których jest używany, a same komputery również ciągle ewoluują. W związku z tym Qiskit podlega zmianom, które mogą sporadycznie prowadzić do deprecacji kodu. Mając to na uwadze, przed każdym przykładem kodu Qiskit w tym kursie będziemy zawsze wykonywać poniższe polecenia, aby było jasne, która wersja Qiskit została użyta. Począwszy od Qiskit v1.0, jest to prosty sposób na sprawdzenie aktualnie zainstalowanej wersji Qiskit.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
from qiskit import __version__

print(__version__)
2.1.1

Jeśli uruchamiasz ten kod w chmurowym środowisku Python, może być konieczne zainstalowanie niektórych z poniższych pakietów:

#!pip install qiskit
#!pip install jupyter
#!pip install sympy
#!pip install matplotlib
#!pip install pylatexenc

Wektory i macierze w Python

Qiskit używa języka programowania Python, więc zanim przejdziemy do omówienia samego Qiskit, może być pomocne bardzo krótkie omówienie obliczeń na macierzach i wektorach w Python.

W Python obliczenia na macierzach i wektorach można wykonywać za pomocą klasy array z biblioteki NumPy, która dostarcza funkcjonalności do wielu obliczeń numerycznych i naukowych. Poniższy kod wczytuje tę bibliotekę, definiuje dwa wektory kolumnowe, ket0 i ket1, odpowiadające wektorom stanu qubit 0\vert 0\rangle i 1,\vert 1\rangle, a następnie wypisuje ich średnią.

import numpy as np

ket0 = np.array([[1], [0]])
ket1 = np.array([[0], [1]])

print(ket0 / 2 + ket1 / 2)
[[0.5]
[0.5]]

Możemy również użyć array do tworzenia macierzy reprezentujących operacje.

M1 = np.array([[1, 1], [0, 0]])
M2 = np.array([[1, 0], [0, 1]])
M = M1 / 2 + M2 / 2
print(M)
[[1.  0.5]
[0. 0.5]]

Pamiętaj, że oczekuje się, iż cały kod w danej lekcji tego kursu będzie uruchamiany sekwencyjnie. Dlatego nie musimy ponownie importować NumPy, ponieważ zostało już zaimportowane wcześniej.

Mnożenie macierzy, w tym mnożenie macierz–wektor jako szczególny przypadek, można wykonywać za pomocą funkcji matmul z NumPy.

print(np.matmul(M1, ket1))
print(np.matmul(M1, M2))
print(np.matmul(M, M))
[[1]
[0]]
[[1 1]
[0 0]]
[[1. 0.75]
[0. 0.25]]

To formatowanie wyjścia pozostawia wiele do życzenia pod względem wizualnym. Jednym z rozwiązań, w sytuacjach wymagających czegoś ładniejszego, jest użycie funkcji array_to_latex w Qiskit, z modułu qiskit.visualization. Zwróć uwagę, że w poniższym kodzie używamy ogólnej funkcji display dostępnej w Python. W odróżnieniu od niej, konkretne zachowanie funkcji print może się różnić w zależności od tego, co jest drukowane — tak jak ma to miejsce w przypadku tablic zdefiniowanych przez NumPy.

from qiskit.visualization import array_to_latex

display(array_to_latex(np.matmul(M1, ket1)))
display(array_to_latex(np.matmul(M1, M2)))
display(array_to_latex(np.matmul(M, M)))
[10] \begin{bmatrix} 1 \\ 0 \\ \end{bmatrix} [1100] \begin{bmatrix} 1 & 1 \\ 0 & 0 \\ \end{bmatrix} [134014] \begin{bmatrix} 1 & \frac{3}{4} \\ 0 & \frac{1}{4} \\ \end{bmatrix}

Stany, pomiary i operacje

Qiskit zawiera kilka klas umożliwiających tworzenie stanów, pomiarów i operacji oraz manipulowanie nimi — nie trzeba więc samodzielnie programować wszystkiego, co jest potrzebne do symulacji kwantowych stanów, pomiarów i operacji w Python. Poniżej znajdziesz kilka przykładów na dobry start.

Definiowanie i wyświetlanie wektorów stanu

Klasa Statevector w Qiskit udostępnia funkcjonalność służącą do definiowania wektorów stanu kwantowego i manipulowania nimi. W poniższym kodzie importowana jest klasa Statevector i definiowanych jest kilka wektorów. (Importujemy też funkcję sqrt z biblioteki NumPy, aby obliczyć pierwiastek kwadratowy. Tę funkcję można alternatywnie wywoływać jako np.sqrt, pod warunkiem że NumPy został wcześniej zaimportowany — tak jak powyżej; jest to po prostu inny sposób zaimportowania i użycia tej konkretnej funkcji.)

from qiskit.quantum_info import Statevector
from numpy import sqrt

u = Statevector([1 / sqrt(2), 1 / sqrt(2)])
v = Statevector([(1 + 2.0j) / 3, -2 / 3])
w = Statevector([1 / 3, 2 / 3])

Klasa Statevector zawiera metodę draw służącą do wyświetlania wektorów stanu na różne sposoby, m.in.: text — zwykły tekst, latex — wyrenderowany LaTeX, oraz latex_source — kod LaTeX, który można wygodnie kopiować i wklejać do dokumentów. (Aby uzyskać najlepsze rezultaty przy wyświetlaniu kodu LaTeX, użyj print zamiast display.)

display(u.draw("text"))
display(u.draw("latex"))
print(u.draw("latex_source"))
[0.70710678+0.j,0.70710678+0.j]

220+221\frac{\sqrt{2}}{2} |0\rangle+\frac{\sqrt{2}}{2} |1\rangle

\frac{\sqrt{2}}{2} |0\rangle+\frac{\sqrt{2}}{2} |1\rangle

Klasa Statevector zawiera również metodę is_valid, która sprawdza, czy dany wektor jest poprawnym wektorem stanu kwantowego (innymi słowy, czy jego norma euklidesowa jest równa 1):

display(u.is_valid())
display(w.is_valid())
True
False

Symulowanie pomiarów przy użyciu Statevector

Teraz zobaczymy jeden ze sposobów symulowania pomiarów stanów kwantowych w Qiskit — przy użyciu metody measure z klasy Statevector. Użyjmy tego samego wektora stanu kubitu v, zdefiniowanego wcześniej.

display(v.draw("latex"))

(13+2i3)0231(\frac{1}{3} + \frac{2 i}{3}) |0\rangle- \frac{2}{3} |1\rangle

Wywołanie metody measure symuluje pomiar w standardowej bazie. Zwraca ona wynik tego pomiaru oraz nowy wektor stanu kwantowego układu po pomiarze. (Używamy tu funkcji print w Python z prefiksem f, co umożliwia formatowane wypisywanie z osadzonymi wyrażeniami.)

outcome, state = v.measure()
print(f"Measured: {outcome}\nPost-measurement state:")
display(state.draw("latex"))
Measured: 1
Post-measurement state:

1- |1\rangle

Wyniki pomiarów są probabilistyczne, więc metoda ta może zwracać różne rezultaty przy kolejnych uruchomieniach. Dla konkretnego przykładu wektora v zdefiniowanego powyżej metoda measure wyznacza wektor stanu kwantowego po dokonaniu pomiaru jako

(1+2i5)0\biggl(\frac{1 + 2i}{\sqrt{5}}\biggr) \vert 0\rangle

(zamiast 0\vert 0\rangle) lub

1- \vert 1\rangle

(zamiast 1\vert 1\rangle), w zależności od wyniku pomiaru. W obu przypadkach alternatywy dla 0\vert 0\rangle i 1\vert 1\rangle są w istocie równoważne tym wektorom stanu; mówi się, że są równoważne z dokładnością do globalnej fazy, ponieważ jeden jest równy drugiemu pomnożonemu przez liczbę zespoloną leżącą na okręgu jednostkowym. Zagadnienie to jest omówione bardziej szczegółowo w lekcji Obwody kwantowe i na razie można je spokojnie pominąć.

Statevector zgłosi błąd, jeśli metoda measure zostanie zastosowana do niepoprawnego wektora stanu kwantowego.

Statevector oferuje też metodę sample_counts, która pozwala symulować dowolną liczbę pomiarów układu — za każdym razem zaczynając od świeżej kopii stanu. Na przykład poniższy kod pokazuje wynik pomiaru wektora v wykonanego 10001000 razy; z dużym prawdopodobieństwem wynik 00 pojawi się około 55 razy na 99 (czyli około 556556 razy na 10001000 prób), a wynik 11 — około 44 razy na 99 (czyli około 444444 razy na 10001000 prób). Poniższy kod demonstruje też funkcję plot_histogram z modułu qiskit.visualization służącą do wizualizacji wyników.

from qiskit.visualization import plot_histogram

statistics = v.sample_counts(1000)
plot_histogram(statistics)

Output of the previous code cell

Wielokrotne uruchamianie tego kodu z różnymi liczbami próbek zamiast 10001000 może być pomocne w rozwijaniu intuicji dotyczącej tego, jak liczba prób wpływa na liczbę wystąpień każdego wyniku. Wraz ze wzrostem liczby próbek udział każdej możliwości coraz bardziej zbliża się do odpowiadającego jej prawdopodobieństwa. To zjawisko, w szerszym ujęciu, znane jest jako prawo wielkich liczb w rachunku prawdopodobieństwa.

Wykonywanie operacji za pomocą Operator i Statevector

Operacje unitarne można definiować w Qiskit za pomocą klasy Operator, jak pokazano w poniższym przykładzie. Klasa ta zawiera metodę draw z podobnymi argumentami jak Statevector. Zwróć uwagę, że opcja latex daje wyniki równoważne z array_from_latex.

from qiskit.quantum_info import Operator

Y = Operator([[0, -1.0j], [1.0j, 0]])
H = Operator([[1 / sqrt(2), 1 / sqrt(2)], [1 / sqrt(2), -1 / sqrt(2)]])
S = Operator([[1, 0], [0, 1.0j]])
T = Operator([[1, 0], [0, (1 + 1.0j) / sqrt(2)]])

display(T.draw("latex"))
[10022+2i2] \begin{bmatrix} 1 & 0 \\ 0 & \frac{\sqrt{2}}{2} + \frac{\sqrt{2} i}{2} \\ \end{bmatrix}

Możemy zastosować operację unitarną na wektorze stanu za pomocą metody evolve.

v = Statevector([1, 0])

v = v.evolve(H)
v = v.evolve(T)
v = v.evolve(H)
v = v.evolve(S)
v = v.evolve(Y)

display(v.draw("latex"))

(0.14644660940.3535533906i)0+(0.3535533906+0.8535533906i)1(0.1464466094 - 0.3535533906 i) |0\rangle+(-0.3535533906 + 0.8535533906 i) |1\rangle

Zapowiedź Circuit kwantowych

Circuit kwantowe zostaną formalnie wprowadzone dopiero w lekcji Circuit kwantowe, która jest trzecią lekcją w tym kursie, ale już teraz możemy eksperymentować z układaniem unitarnych operacji qubitowych za pomocą klasy QuantumCircuit w Qiskit. W szczególności możemy zdefiniować Circuit kwantowy (który w tym przypadku będzie po prostu sekwencją operacji unitarnych wykonywanych na jednym qubicie) w następujący sposób.

from qiskit import QuantumCircuit

circuit = QuantumCircuit(1)

circuit.h(0)
circuit.t(0)
circuit.h(0)
circuit.s(0)
circuit.y(0)

display(circuit.draw(output="mpl"))

Output of the previous code cell

Tutaj używamy metody draw z klasy QuantumCircuit z renderowaniem mpl (skrót od Matplotlib, biblioteki wizualizacji dla Pythona). Jest to jedyne renderowanie, którego będziemy używać dla Circuit kwantowych w tym kursie, ale istnieją też inne opcje, w tym renderowanie tekstowe i oparte na LaTeX.

Operacje są stosowane kolejno, od lewej do prawej na diagramie. Wygodnym sposobem na uzyskanie macierzy unitarnej odpowiadającej temu Circuit jest użycie metody from_circuit z klasy Operator.

display(Operator.from_circuit(circuit).draw("latex"))
[0.14644660940.3535533906i0.8535533906+0.3535533906i0.3535533906+0.8535533906i0.3535533906+0.1464466094i] \begin{bmatrix} 0.1464466094 - 0.3535533906 i & 0.8535533906 + 0.3535533906 i \\ -0.3535533906 + 0.8535533906 i & 0.3535533906 + 0.1464466094 i \\ \end{bmatrix}

Możemy też zainicjować wyjściowy wektor stanu kwantowego, a następnie ewoluować ten stan zgodnie z sekwencją operacji opisaną przez Circuit.

ket0 = Statevector([1, 0])
v = ket0.evolve(circuit)
display(v.draw("latex"))

(0.14644660940.3535533906i)0+(0.3535533906+0.8535533906i)1(0.1464466094 - 0.3535533906 i) |0\rangle+(-0.3535533906 + 0.8535533906 i) |1\rangle

Poniższy kod symuluje eksperyment, w którym stan uzyskany z powyższego Circuit jest mierzony pomiarem w standardowej bazie 4000 razy (za każdym razem używając nowej kopii stanu).

statistics = v.sample_counts(4000)
display(plot_histogram(statistics))

Output of the previous code cell