Przejdź do głównej treści
Ten projekt jest w wersji beta. Znalazłeś błąd? Otwórz zgłoszenie na GitHub. Masz pytanie lub pomysł? Zapytaj w Discussions — chętnie poznamy Twoją opinię!
Jak oceniasz jakość tłumaczenia tej strony?

Kubity kwantowe, bramki i obwody

uwaga

Kifumi Numata (19 Apr 2024)

Kliknij tutaj, aby pobrać plik PDF oryginalnego wykładu. Pamiętaj, że niektóre fragmenty kodu mogą być przestarzałe, ponieważ są to statyczne obrazy.

Szacowany czas QPU potrzebny do przeprowadzenia tego eksperymentu wynosi 5 sekund.

1. Wprowadzenie​

Bity, bramki i obwody to podstawowe elementy składowe obliczeń kwantowych. Nauczysz się obliczeń kwantowych z modelem obwodów za pomocą kubitów i bramek kwantowych, a także powtórzysz zasady superpozycji, pomiaru i splątania.

W tej lekcji dowiesz się o:

  • Bramkach jednokubitowych
  • Sferze Blocha
  • Superpozycji
  • Pomiarze
  • Bramkach dwukubitowych i stanie splątania

Na końcu tego wykładu poznasz pojęcie głębokości obwodu, która jest kluczowa dla obliczeń kwantowych w skali użytkowej.

2. Obliczenia jako diagram​

Gdy używamy kubitów lub bitów, musimy nimi manipulować, aby przekształcić dane wejściowe w potrzebne wyniki. W przypadku najprostszych programów z bardzo małą liczbą bitów przydatne jest przedstawienie tego procesu w formie diagramu zwanego diagramem obwodu.

Rysunek w lewym dolnym rogu to przykład obwodu klasycznego, a rysunek w prawym dolnym rogu to przykład obwodu kwantowego. W obu przypadkach dane wejściowe znajdują się po lewej stronie, a wyjściowe po prawej, natomiast operacje są reprezentowane przez symbole. Symbole używane dla operacji nazywane są „bramkami" — głównie z przyczyn historycznych.

"classical logic and quantum circuit"

3. Jednokubitowa bramka kwantowa​

3.1 Stan kwantowy i sfera Blocha​

Stan kubitu jest reprezentowany jako superpozycja ∣0⟩|0\rangle i ∣1⟩|1\rangle. Dowolny stan kwantowy jest reprezentowany jako

∣ψ⟩=α∣0⟩+β∣1⟩|\psi\rangle =\alpha|0\rangle+ \beta|1\rangle

gdzie α\alpha i β\beta są liczbami zespolonymi takimi, że ∣α∣2+∣β∣2=1|\alpha|^2+|\beta|^2=1.

∣0⟩|0\rangle i ∣1⟩|1\rangle są wektorami w dwuwymiarowej zespolonej przestrzeni wektorowej:

∣0⟩=(10),∣1⟩=(01)|0\rangle = \begin{pmatrix} 1 \\0 \end{pmatrix}, |1\rangle = \begin{pmatrix} 0\\1 \end{pmatrix}

Dlatego dowolny stan kwantowy jest również reprezentowany jako

∣ψ⟩=α(10)+β(01)=(αβ)|\psi\rangle = \alpha\begin{pmatrix} 1 \\ 0 \end{pmatrix} + \beta\begin{pmatrix}0\\ 1 \end{pmatrix} = \begin{pmatrix} \alpha \\ \beta \end{pmatrix}

Z tego wynika, że stan bitu kwantowego jest wektorem jednostkowym w dwuwymiarowej zespolonej przestrzeni z iloczynem wewnętrznym, z ortonormalną bazą ∣0⟩|0\rangle i ∣1⟩|1\rangle. Jest znormalizowany do 1.

⟨ψ∣ψ⟩=(α∗β∗)(αβ)=1\langle\psi|\psi\rangle = \begin{pmatrix} \alpha^* & \beta^* \end{pmatrix} \begin{pmatrix} \alpha \\ \beta \end{pmatrix} = 1

∣ψ⟩=(αβ) |\psi\rangle =\begin{pmatrix} \alpha \\ \beta \end{pmatrix} jest również nazywany wektorem stanu (statevector).

Jednokubitowy stan kwantowy jest również reprezentowany jako

∣ψ⟩=cos⁡θ2∣0⟩+eiφsin⁡θ2∣1⟩=((cos⁡θ2eiφsin⁡θ2))|\psi\rangle =\cos\frac{\theta}{2}|0\rangle+e^{i\varphi}\sin\frac{\theta}{2}|1\rangle =\left( \begin{pmatrix} \cos\frac{\theta}{2}\\ e^{i\varphi}\sin\frac{\theta}{2} \end{pmatrix}\right)

gdzie θ\theta i φ\varphi są kątami sfery Blocha przedstawionej na poniższym rysunku.

Bloch sphere W kolejnych kilku komórkach kodu zbudujemy podstawowe obliczenia z elementów składowych w Qiskit. Skonstruujemy pusty Circuit, a następnie będziemy dodawać operacje kwantowe, omawiając bramki i wizualizując ich efekty w miarę postępu. Możesz uruchomić komórkę za pomocą "Shift" + "Enter". Najpierw zaimportuj biblioteki.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
# Import the qiskit library
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector
from qiskit_ibm_runtime import Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.visualization import plot_histogram

Przygotowanie Circuit kwantowego​

Stworzymy i narysujemy jednokubitowy Circuit.

# Create the single-qubit quantum circuit
qc = QuantumCircuit(1)

# Draw the circuit
qc.draw("mpl")

Output of the previous code cell

Gate X​

Gate X to obrót o π\pi wokół osi xx sfery Blocha. Zastosowanie Gate X do ∣0⟩|0\rangle daje ∣1⟩|1\rangle, a zastosowanie Gate X do ∣1⟩|1\rangle daje ∣0⟩|0\rangle, więc jest to operacja podobna do klasycznej bramki NOT i znana jest również jako odwrócenie bitu (bit flip). Macierzowa reprezentacja Gate X jest poniżej.

X=(0110)X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \\ \end{pmatrix}
qc = QuantumCircuit(1) # Prepare the single-qubit quantum circuit

# Apply a X gate to qubit 0
qc.x(0)

# Draw the circuit
qc.draw("mpl")

Output of the previous code cell

W IBM Quantum® stan początkowy jest ustawiony na ∣0⟩|0\rangle, więc powyższy Circuit kwantowy w reprezentacji macierzowej ma postać

X∣0⟩=(0110)(10)=(01)=∣1⟩X|0\rangle= \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix} 1 \\ 0 \end{pmatrix} =\begin{pmatrix} 0 \\ 1 \end{pmatrix} = |1\rangle

Następnie uruchommy ten obwód przy użyciu symulatora wektora stanu.

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([0.+0.j, 1.+0.j],
dims=(2,))

Output of the previous code cell

Wektor pionowy jest wyświetlany jako wektor wierszowy, z liczbami zespolonymi (część urojona jest indeksowana przez jj ).

Gate H​

Gate Hadamarda to obrót o π\pi wokół osi leżącej w połowie drogi między osiami xx i zz na sferze Blocha. Zastosowanie Gate H do ∣0⟩|0\rangle tworzy stan superpozycji taki jak ∣0⟩+∣1⟩2\frac{|0\rangle + |1\rangle}{\sqrt{2}}. Macierzowa reprezentacja Gate H jest poniżej.

H=12(111−1)H = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \\ \end{pmatrix}
qc = QuantumCircuit(1) # Create the single-qubit quantum circuit

# Apply an Hadamard gate to qubit 0
qc.h(0)

# Draw the circuit
qc.draw(output="mpl")

Output of the previous code cell

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([0.70710678+0.j, 0.70710678+0.j],
dims=(2,))

Output of the previous code cell

To jest

H∣0⟩=12(111−1)(10)=12(11)=(0.7070.707)=12(∣0⟩+∣1⟩)H|0\rangle= \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix} \begin{pmatrix} 1 \\0 \end{pmatrix} =\frac{1}{\sqrt{2}}\begin{pmatrix} 1 \\ 1 \end{pmatrix} =\begin{pmatrix} 0.707 \\ 0.707 \end{pmatrix} =\frac{1}{\sqrt{2}}(|0\rangle+|1\rangle)

Ten stan superpozycji jest tak powszechny i ważny, że otrzymał własny symbol:

∣+⟩≡12(∣0⟩+∣1⟩).|+\rangle \equiv \frac{1}{\sqrt{2}}(|0\rangle+|1\rangle).

Stosując Gate HH na ∣0⟩|0\rangle, stworzyliśmy superpozycję ∣0⟩|0\rangle i ∣1⟩|1\rangle, gdzie pomiar w bazie obliczeniowej (wzdłuż osi z na sferze Blocha) dałby każdy stan z równym prawdopodobieństwem.

Stan ∣−⟩|-\rangle​

Być może zgadłeś, że istnieje odpowiadający stan ∣−⟩|-\rangle:

∣−⟩≡∣0⟩−∣1⟩2.|-\rangle \equiv \frac{|0\rangle -|1\rangle}{\sqrt{2}}.

Aby stworzyć ten stan, najpierw zastosuj Gate X, aby uzyskać ∣1⟩|1\rangle, a następnie zastosuj Gate H.

qc = QuantumCircuit(1) # Create the single-qubit quantum circuit

# Apply a X gate to qubit 0
qc.x(0)

# Apply an Hadamard gate to qubit 0
qc.h(0)

# draw the circuit
qc.draw(output="mpl")

Output of the previous code cell

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([ 0.70710678+0.j, -0.70710678+0.j],
dims=(2,))

Output of the previous code cell

To jest

H∣1⟩=12(11 1−1)(0 1)=12(1 −1)=(0.707 −0.707)=12(∣0⟩−∣1⟩)=∣−⟩H|1\rangle= \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\\ 1 & -1 \end{pmatrix} \begin{pmatrix} 0 \\\ 1 \end{pmatrix} =\frac{1}{\sqrt{2}}\begin{pmatrix} 1 \\\ -1 \end{pmatrix} =\begin{pmatrix} 0.707 \\\ -0.707 \end{pmatrix} =\frac{1}{\sqrt{2}}(|0\rangle-|1\rangle) = |-\rangle

Zastosowanie Gate HH na ∣1⟩|1\rangle daje równą superpozycję ∣0⟩|0\rangle i ∣1⟩|1\rangle, ale znak przy ∣1⟩|1\rangle jest ujemny.

3.2 Stan kwantowy pojedynczego kubitu i ewolucja unitarna​

Działanie wszystkich bramek, które do tej pory poznaliśmy, jest unitarne, co oznacza, że można je opisać operatorem unitarnym. Innymi słowy, stan wyjściowy otrzymujemy, działając na stan początkowy macierzą unitarną:

∣ψ′⟩=U∣ψ⟩|\psi^{'}\rangle = U|\psi\rangle

Macierz unitarna to macierz spełniająca warunek

U†U=UU†=I.U^{\dagger}U =U U^{\dagger} = I.

Jeśli chodzi o działanie komputera kwantowego, powiemy, że zastosowanie Gate'u kwantowego do kubitu ewoluuje stan kwantowy. Do typowych Gate'ów jednokubitowych należą poniższe.

Bramki Pauliego:

X=(0110)=∣0⟩⟨1∣+∣1⟩⟨0∣X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \\ \end{pmatrix} = |0\rangle \langle 1|+|1\rangle \langle 0| Y=(0−ii0)=−i∣0⟩⟨1∣+i∣1⟩⟨0∣Y = \begin{pmatrix} 0 & -i \\ i & 0 \\ \end{pmatrix} = -i|0\rangle \langle 1|+i|1\rangle \langle 0| Z=(100−1)=∣0⟩⟨0∣−∣1⟩⟨1∣Z = \begin{pmatrix} 1 & 0 \\ 0 & -1 \\ \end{pmatrix} = |0\rangle \langle 0|-|1\rangle \langle 1|

gdzie iloczyn zewnętrzny obliczono następująco:

∣0⟩⟨0∣=[10][10]=[1000],∣1⟩⟨0∣=[01][10]=[0010],|0\rangle \langle 0|= \begin{bmatrix} 1 \\ 0 \end{bmatrix} \begin{bmatrix} 1 & 0 \end{bmatrix} =\begin{bmatrix} 1 & 0 \\ 0 & 0 \\ \end{bmatrix}, \quad |1\rangle \langle 0|= \begin{bmatrix} 0 \\ 1 \end{bmatrix} \begin{bmatrix} 1 & 0 \end{bmatrix} =\begin{bmatrix} 0 & 0 \\ 1 & 0 \\ \end{bmatrix}, \quad ∣0⟩⟨1∣=[10][01]=[0100],∣1⟩⟨1∣=[01][01]=[0001],|0\rangle \langle 1|= \begin{bmatrix} 1 \\ 0 \end{bmatrix} \begin{bmatrix} 0 & 1 \end{bmatrix} =\begin{bmatrix} 0 & 1 \\ 0 & 0 \\ \end{bmatrix}, \quad |1\rangle \langle 1|= \begin{bmatrix} 0 \\ 1 \end{bmatrix} \begin{bmatrix} 0 & 1 \end{bmatrix} =\begin{bmatrix} 0 & 0 \\ 0 & 1 \\ \end{bmatrix}, \quad

Inne typowe Gate'y jednokubitowe:

H=12[111−1],S=[100i],T=[100exp(iπ/4)]H= \frac{1}{\sqrt{2}}\begin{bmatrix} 1 & 1 \\ 1 & -1 \\ \end{bmatrix},\quad S = \begin{bmatrix} 1 & 0 \\ 0 & i \\ \end{bmatrix}, \quad T = \begin{bmatrix} 1 & 0 \\ 0 & exp(i\pi/4) \\ \end{bmatrix} Rx(θ)=e−iθX/2=cosθ2I−isinθ2X=[cosθ2−isinθ2−isinθ2cosθ2]R_x(\theta) = e^{-i\theta X/2} = cos\frac{\theta}{2}I - i sin \frac{\theta}{2}X = \begin{bmatrix} cos\frac{\theta}{2} & -i sin \frac{\theta}{2} \\ -i sin \frac{\theta}{2} & cos\frac{\theta}{2} \\ \end{bmatrix} Ry(θ)=e−iθY/2=cosθ2I−isinθ2Y=[cosθ2−sinθ2sinθ2cosθ2]R_y(\theta) = e^{-i\theta Y/2} = cos\frac{\theta}{2}I - i sin \frac{\theta}{2}Y = \begin{bmatrix} cos\frac{\theta}{2} & - sin \frac{\theta}{2} \\ sin \frac{\theta}{2} & cos\frac{\theta}{2} \\ \end{bmatrix} Rz(θ)=e−iθZ/2=cosθ2I−isinθ2Z=[e−iθ/200eiθ/2]R_z(\theta) = e^{-i\theta Z/2} = cos\frac{\theta}{2}I - i sin \frac{\theta}{2}Z = \begin{bmatrix} e^{-i\theta /2} & 0 \\ 0 & e^{i\theta /2} \\ \end{bmatrix}

Znaczenie i zastosowanie tych bramek opisano szczegółowo w kursie Podstawy informacji kwantowej.

Ćwiczenie 1​

Użyj Qiskit, aby utworzyć Circuit kwantowe przygotowujące stany opisane poniżej. Następnie uruchom każdy obwód za pomocą symulatora wektora stanu i wyświetl wynikowy stan na sferze Blocha. Jako dodatkowe zadanie sprawdź, czy potrafisz przewidzieć stan końcowy na podstawie intuicji dotyczącej Gate'ów i obrotów na sferze Blocha.

(1) XX∣0⟩XX|0\rangle

(2) HH∣0⟩HH|0\rangle

(3) HZH∣0⟩HZH|0\rangle

Wskazówka: Gate Z można zastosować za pomocą

qc.z(0)

Rozwiązanie:

### (1) XX|0> ###

# Create the single-qubit quantum circuit
qc = QuantumCircuit(1) ##your code goes here##

# Add a X gate to qubit 0
qc.x(0) ##your code goes here##

# Add a X gate to qubit 0
qc.x(0) ##your code goes here##

# Draw a circuit
qc.draw(output="mpl")

Output of the previous code cell

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([1.+0.j, 0.+0.j],
dims=(2,))

Output of the previous code cell

### (2) HH|0> ###
##your code goes here##
qc = QuantumCircuit(1)
qc.h(0)
qc.h(0)
qc.draw("mpl")

Output of the previous code cell

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([1.+0.j, 0.+0.j],
dims=(2,))

Output of the previous code cell

### (3) HZH|0> ###
##your code goes here##
qc = QuantumCircuit(1)
qc.h(0)
qc.z(0)
qc.h(0)
qc.draw("mpl")

Output of the previous code cell

# See the statevector
out_vector = Statevector(qc)
print(out_vector)

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)
Statevector([0.+0.j, 1.+0.j],
dims=(2,))

Output of the previous code cell

3.3 Pomiar​

Pomiar jest teoretycznie bardzo skomplikowanym zagadnieniem. W praktyce jednak wykonanie pomiaru wzdłuż osi zz (jak robią to wszystkie komputery kwantowe IBM®) po prostu wymusza, aby stan kubitu α∣0⟩+β∣1⟩(s.t.∣α∣2+∣β∣2=1)\alpha|0\rangle+\beta|1\rangle \quad (s.t.|\alpha|^2+|\beta|^2=1) przyjął wartość ∣0⟩|0\rangle lub ∣1⟩|1\rangle, a my obserwujemy wynik.

  • ∣α∣2|\alpha|^2 to prawdopodobieństwo uzyskania ∣0⟩|0\rangle przy pomiarze.
  • ∣β∣2|\beta|^2 to prawdopodobieństwo uzyskania ∣1⟩|1\rangle przy pomiarze.

Dlatego α\alpha i β\beta nazywa się amplitudami prawdopodobieństwa (zob. „reguła Borna").

Na przykład 22∣0⟩+22∣1⟩\frac{\sqrt{2}}{2}|0\rangle+\frac{\sqrt{2}}{2}|1\rangle ma jednakowe prawdopodobieństwo przyjęcia wartości ∣0⟩|0\rangle lub ∣1⟩|1\rangle po pomiarze. 32∣0⟩−12i∣1⟩\frac{\sqrt{3}}{2}|0\rangle-\frac{1}{2}i|1\rangle ma 75% szans na przyjęcie wartości ∣0⟩|0\rangle.

Symulator Qiskit Aer​

Następnie zmierzmy Circuit przygotowujący opisaną powyżej superpozycję o równym prawdopodobieństwie. Powinniśmy dodać Gate'y pomiaru, ponieważ symulator Qiskit Aer domyślnie symuluje idealne (bezszumowe) sprzętowe środowisko kwantowe. Uwaga: symulator Aer może również stosować model szumów oparty na rzeczywistym komputerze kwantowym. Do modeli szumów wrócimy później.

# Create a new circuit with one qubits (first argument) and one classical bits (second argument)
qc = QuantumCircuit(1, 1)
qc.h(0)
qc.measure(0, 0) # Add the measurement gate

qc.draw(output="mpl")

Output of the previous code cell

Jesteśmy teraz gotowi uruchomić nasz obwód na symulatorze Aer. W tym przykładzie zastosujemy domyślne ustawienie shots=1024, co oznacza, że dokonamy 1024 pomiarów. Następnie przedstawimy te wyniki na histogramie.

# Run the circuit on a simulator to get the results
# Define backend
backend = AerSimulator()

# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

# Run the job
sampler = Sampler(mode=backend)
job = sampler.run([isa_qc])
result = job.result()

# Print the results
counts = result[0].data.c.get_counts()
print(counts)

# Plot the counts in a histogram
plot_histogram(counts)
{'0': 521, '1': 503}

Output of the previous code cell

Widzimy, że 0 i 1 były mierzone z prawdopodobieństwem bliskim 50% każde. Mimo że szum nie był tu symulowany, stany nadal mają charakter probabilistyczny. Dlatego chociaż oczekujemy rozkładu mniej więcej 50-50, rzadko uzyskamy dokładnie taki wynik — podobnie jak 100 rzutów monetą rzadko daje dokładnie 50 wyników każdej strony.

4. Wielokubitowe bramki kwantowe i splątanie​

4.1 Wielokubitowy Circuit kwantowy​

Dwukubitowy Circuit kwantowy możemy utworzyć za pomocą poniższego kodu. Zastosujemy bramkę H do każdego kubitu.

# Create the two qubits quantum circuit
qc = QuantumCircuit(2)

# Apply an H gate to qubit 0
qc.h(0)

# Apply an H gate to qubit 1
qc.h(1)

# Draw the circuit
qc.draw(output="mpl")

Output of the previous code cell

# See the statevector
out_vector = Statevector(qc)
print(out_vector)
Statevector([0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j],
dims=(2, 2))

Uwaga: kolejność bitów w Qiskit​

Qiskit stosuje notację Little Endian przy porządkowaniu kubitów i bitów, co oznacza, że kubit 0 jest skrajnym prawym bitem w ciągach bitów. Przykład: ∣01⟩|01\rangle oznacza, że q0 jest w stanie ∣1⟩|1\rangle, a q1 jest w stanie ∣0⟩|0\rangle. Uważaj, ponieważ część literatury z zakresu informatyki kwantowej używa notacji Big Endian (kubit 0 jest skrajnym lewym bitem) — podobnie jak wiele tekstów z dziedziny mechaniki kwantowej.

Należy też zauważyć, że w reprezentacji Circuitu kwantowego ∣q0⟩|q_0\rangle jest zawsze umieszczany na górze. Mając to na uwadze, stan kwantowy powyższego Circuitu można zapisać jako iloczyn tensorowy stanów jednokubitowych.

∣q1⟩⊗∣q0⟩=(a∣0⟩+b∣1⟩)⊗(c∣0⟩+d∣1⟩)|q1\rangle \otimes|q0\rangle = (a|0\rangle+b|1\rangle) \otimes (c|0\rangle+d|1\rangle)

=ac∣0⟩∣0⟩+ad∣0⟩∣1⟩+bc∣1⟩∣0⟩+bd∣1⟩∣1⟩= ac|0\rangle|0\rangle+ad|0\rangle|1\rangle+bc|1\rangle|0\rangle+bd|1\rangle|1\rangle

=ac∣00⟩+ad∣01⟩+bc∣10⟩+bd∣11⟩= ac|00\rangle+ad|01\rangle+bc|10\rangle+bd|11\rangle

( ∣ac∣2+∣ad∣2+∣bc∣2+∣bd∣2=1|ac|^2+ |ad|^2+ |bc|^2+ |bd|^2=1 )

Stanem początkowym w Qiskit jest ∣0⟩∣0⟩=∣00⟩|0\rangle|0\rangle=|00\rangle, więc przez zastosowanie HH do każdego kubitu stan zmienia się w stan równej superpozycji.

H∣0⟩⊗H∣0⟩=12(∣0⟩+∣1⟩)⊗12(∣0⟩+∣1⟩)=12(∣00⟩+∣01⟩+∣10⟩+∣11⟩)H|0\rangle \otimes H|0\rangle=\frac{1}{\sqrt{2}}(|0\rangle+|1\rangle) \otimes \frac{1}{\sqrt{2}}(|0\rangle+|1\rangle) = \frac{1}{2}(|00\rangle+|01\rangle+|10\rangle+|11\rangle)

=12((11)⊗(11))=12(1111)=12((1000)+(0100)+(0010)+(0001))=\frac{1}{2}\left( \begin{pmatrix} 1 \\ 1 \end{pmatrix} \otimes \begin{pmatrix} 1 \\ 1 \end{pmatrix}\right) = \frac{1}{2}\begin{pmatrix} 1 \\ 1 \\ 1 \\ 1 \end{pmatrix}=\frac{1}{2}\left(\begin{pmatrix} 1 \\ 0 \\ 0 \\ 0 \end{pmatrix}+\begin{pmatrix} 0 \\ 1 \\ 0 \\ 0 \end{pmatrix}+\begin{pmatrix} 0 \\ 0 \\ 1 \\ 0 \end{pmatrix}+\begin{pmatrix} 0 \\ 0 \\ 0 \\ 1 \end{pmatrix}\right)

Reguła pomiaru jest taka sama jak w przypadku jednego kubitu — prawdopodobieństwo zmierzenia ∣00⟩|00\rangle wynosi ∣ac∣2|ac|^2.

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)

Output of the previous code cell

Zmierzmy teraz ten obwód.

# Create a new circuit with two qubits (first argument) and two classical bits (second argument)
qc = QuantumCircuit(2, 2)

# Apply the gates
qc.h(0)
qc.h(1)

# Add the measurement gates
qc.measure(0, 0) # Measure qubit 0 and save the result in bit 0
qc.measure(1, 1) # Measure qubit 1 and save the result in bit 1

# Draw the circuit
qc.draw(output="mpl")

Output of the previous code cell

Użyjemy teraz symulatora Aer, aby eksperymentalnie potwierdzić, że względne prawdopodobieństwa wszystkich możliwych stanów wyjściowych są w przybliżeniu równe.

# Run the circuit on a simulator to get the results
# Define backend
backend = AerSimulator()

# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

# Run the job
sampler = Sampler(mode=backend)
job = sampler.run([isa_qc])
result = job.result()

# Print the results
counts = result[0].data.c.get_counts()
print(counts)

# Plot the counts in a histogram
plot_histogram(counts)
{'10': 262, '01': 246, '00': 265, '11': 251}

Output of the previous code cell

Zgodnie z oczekiwaniami stany ∣00⟩|00\rangle, ∣01⟩|01\rangle, ∣10⟩|10\rangle, ∣11⟩|11\rangle były mierzone z prawdopodobieństwem bliskim 25% każdy.

4.2 Wielokubitowe bramki kwantowe​

Bramka CNOT​

Bramka CNOT (ang. „controlled NOT" lub CX) jest dwukubitową bramką, co oznacza, że jej działanie obejmuje jednocześnie dwa kubity: kubit sterujący (ang. control) i kubit docelowy (ang. target). Bramka CNOT odwraca stan kubitu docelowego tylko wtedy, gdy kubit sterujący jest w stanie ∣1⟩|1\rangle.

Wejście (target, control)Wyjście (target, control)
0000
0111
1010
1101

Zasymulujmy najpierw działanie tej dwukubitowej bramki, gdy q0 i q1 są oba w stanie ∣0⟩|0\rangle, i uzyskajmy wyjściowy wektor stanu. Składnia Qiskit to qc.cx(control qubit, target qubit).

# Create a circuit with two quantum registers and two classical registers
qc = QuantumCircuit(2, 2)

# Apply the CNOT (cx) gate to a |00> state.
qc.cx(0, 1) # Here the control is set to q0 and the target is set to q1.

# Draw the circuit
qc.draw(output="mpl")

Output of the previous code cell

# See the statevector
out_vector = Statevector(qc)
print(out_vector)
Statevector([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
dims=(2, 2))

Zgodnie z oczekiwaniami zastosowanie bramki CNOT do stanu ∣00⟩|00\rangle nie zmieniło stanu, ponieważ kubit sterujący był w stanie ∣0⟩|0\rangle. Wróćmy do operacji CNOT. Tym razem zastosujemy bramkę CNOT do stanu ∣01⟩|01\rangle i zobaczmy, co się stanie.

qc = QuantumCircuit(2, 2)

# q0=1, q1=0
qc.x(0) # Apply a X gate to initialize q0 to 1
qc.cx(0, 1) # Set the control bit to q0 and the target bit to q1.

# Draw the circuit
qc.draw(output="mpl")

Output of the previous code cell

# See the statevector
out_vector = Statevector(qc)
print(out_vector)
Statevector([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
dims=(2, 2))

Po zastosowaniu bramki CNOT stan ∣01⟩|01\rangle stał się stanem ∣11⟩|11\rangle.

Zweryfikujmy te wyniki, uruchamiając Circuit na symulatorze.

# Add measurements
qc.measure(0, 0)
qc.measure(1, 1)

# Draw the circuit
qc.draw(output="mpl")

Output of the previous code cell

# Run the circuit on a simulator to get the results
# Define backend
backend = AerSimulator()

# Transpile to backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

# Run the job
sampler = Sampler(backend)
job = sampler.run([isa_qc])
result = job.result()

# Print the results
counts = result[0].data.c.get_counts()
print(counts)

# Plot the counts in a histogram
plot_histogram(counts)
{'11': 1024}

Output of the previous code cell

Wyniki powinny pokazać, że stan ∣11⟩|11\rangle był mierzony ze 100-procentowym prawdopodobieństwem.

4.3 Splątanie kwantowe i wykonanie na prawdziwym urządzeniu kwantowym​

Zacznijmy od wprowadzenia konkretnego stanu splątanego, który jest szczególnie ważny w obliczeniach kwantowych, a następnie zdefiniujemy pojęcie „splątania":

12∣00⟩+12∣11⟩\frac{1}{\sqrt{2}}|00\rangle + \frac{1}{\sqrt{2}}|11\rangle

i ten stan jest nazywany stanem Bella.

Stan splątany to taki stan ∣ψAB⟩|\psi_{AB}\rangle, składający się ze stanów kwantowych ∣ψA⟩|\psi_A\rangle i ∣ψB⟩|\psi_B\rangle, który nie może być przedstawiony jako iloczyn tensorowy poszczególnych stanów kwantowych.

Jeśli ∣ψAB⟩|\psi_{AB}\rangle poniżej zawiera dwa stany ∣ψ⟩A|\psi\rangle_A i ∣ψ⟩B|\psi\rangle_B:

∣ψAB⟩=12(∣00⟩+∣11⟩)=12(∣0⟩A∣0⟩B+∣1⟩A∣1⟩B)|\psi_{AB}\rangle = \frac{1}{\sqrt{2}}(|00\rangle +|11\rangle) = \frac{1}{\sqrt{2}}(|0\rangle_A|0\rangle_B +|1\rangle_A|1\rangle_B) ∣ψ⟩A=a0∣0⟩+a1∣1⟩|\psi\rangle_A = a_0|0\rangle+a_1|1\rangle ∣ψ⟩B=b0∣0⟩+b1∣1⟩|\psi\rangle_B = b_0|0\rangle+b_1|1\rangle

to iloczyn tensorowy tych dwóch stanów jest następujący

∣ψ⟩A⊗∣ψ⟩B=a0b0∣00⟩+a0b1∣01⟩+a1b0∣10⟩+a1b1∣11⟩|\psi\rangle _A\otimes |\psi\rangle _B = a_0 b_0|00\rangle+a_0 b_1|01\rangle+a_1 b_0|10\rangle+a_1 b_1|11\rangle

jednak nie istnieją współczynniki a0a_0, a1a_1, b0b_0, i b1b_1 spełniające oba powyższe równania. Dlatego ∣ψAB⟩|\psi_{AB}\rangle nie może być przedstawiony jako iloczyn tensorowy poszczególnych stanów kwantowych, ∣ψ⟩A|\psi\rangle_A i ∣ψ⟩B|\psi\rangle_B, co oznacza, że ∣ψAB⟩=12(∣00⟩+∣11⟩)|\psi_{AB}\rangle = \frac{1}{\sqrt{2}}(|00\rangle +|11\rangle) jest stanem splątanym.

Utwórzmy stan Bella i uruchommy go na prawdziwym komputerze kwantowym. Skorzystamy z czterech kroków pisania programu kwantowego, zwanych wzorcami Qiskit:

  1. Odwzoruj problem na obwody kwantowe i operatory

  2. Zoptymalizuj pod kątem docelowego sprzętu

  3. Wykonaj na docelowym sprzęcie

  4. Przetwórz wyniki końcowe

Krok 1. Odwzorowanie problemu na Circuity kwantowe i operatory​

W programie kwantowym Circuity kwantowe są natywnym formatem reprezentowania instrukcji kwantowych. Tworząc Circuit, zazwyczaj tworzysz nowy obiekt QuantumCircuit, a następnie kolejno dodajesz do niego instrukcje.

Poniższy fragment kodu tworzy Circuit, który przygotowuje stan Bella — konkretny dwukubitowy stan splątany opisany powyżej.

qc = QuantumCircuit(2, 2)

qc.h(0)
qc.cx(0, 1)

qc.measure(0, 0)
qc.measure(1, 1)

qc.draw("mpl")

Output of the previous code cell

Krok 2. Optymalizacja pod kątem docelowego sprzętu​

Qiskit konwertuje abstrakcyjne Circuity na Circuity QISA (Quantum Instruction Set Architecture), które spełniają ograniczenia docelowego sprzętu, i optymalizuje wydajność Circuitu. Przed optymalizacją musimy więc wskazać docelowy sprzęt. Jeśli nie masz zainstalowanego qiskit-ibm-runtime, najpierw musisz go zainstalować. Więcej informacji znajdziesz w dokumentacji API.

# Install
# !pip install qiskit-ibm-runtime

Wskażemy teraz docelowy sprzęt.

from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
service.backends()
# You can specify the device
# backend = service.backend('ibm_kingston')
# You can also identify the least busy device
backend = service.least_busy(operational=True)
print("The least busy device is ", backend)

Transpilacja Circuitu to kolejny złożony proces. W skrócie polega on na przepisaniu Circuitu do logicznie równoważnej postaci z użyciem „natywnych bramek" (bramek, które dany komputer kwantowy potrafi realizować) i odwzorowaniu kubitów z twojego Circuitu na optymalne fizyczne kubity docelowego komputera kwantowego. Więcej na temat transpilacji znajdziesz w tej dokumentacji.

# Transpile the circuit into basis gates executable on the hardware
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
target_circuit = pm.run(qc)

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

Widać, że podczas transpilacji Circuit został przepisany z użyciem nowych bramek. Więcej informacji znajdziesz w dokumentacji ECRGate.

Krok 3. Wykonanie docelowego Circuitu​

Teraz uruchomimy docelowy Circuit na prawdziwym urządzeniu.

sampler = Sampler(backend)
job_real = sampler.run([target_circuit])

job_id = job_real.job_id()
print("job id:", job_id)

Wykonanie na prawdziwym urządzeniu może wymagać oczekiwania w kolejce, ponieważ komputery kwantowe są cennymi i bardzo pożądanymi zasobami. Identyfikator job_id służy do późniejszego sprawdzania statusu wykonania i wyników zadania.

# Check the job status (replace the job id below with your own)
job_real.status(job_id)

Status zadania możesz też sprawdzić na swoim panelu IBM Quantum:https://quantum.cloud.ibm.com/workloads

# If the Notebook session got disconnected you can also check your job status
# by running the following code
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
job_real = service.job(job_id) # Input your job-id between the quotations
job_real.status()
# Execute after job has successfully run
result_real = job_real.result()
print(result_real[0].data.c.get_counts())

Krok 4. Przetwarzanie końcowe wyników​

Na koniec musimy przetworzyć nasze wyniki, aby uzyskać dane wyjściowe w oczekiwanym formacie, na przykład w postaci wartości lub wykresów.

plot_histogram(result_real[0].data.c.get_counts())

Jak widać, ∣00⟩|00\rangle i ∣11⟩|11\rangle są obserwowane najczęściej. Pojawia się kilka wyników innych niż oczekiwane — wynikają one z szumów i dekoherencji kubitów. Więcej o błędach i szumach w komputerach kwantowych dowiemy się w późniejszych lekcjach tego kursu.

4.4 Stan GHZ​

Pojęcie splątania można rozszerzyć na układy składające się z więcej niż dwóch kubitów. Stan GHZ (stan Greenbergera-Horne'a-Zeilingera) to maksymalnie splątany stan trzech lub więcej kubitów. Stan GHZ dla trzech kubitów jest zdefiniowany jako

12(∣000⟩+∣111⟩)\frac{1}{\sqrt 2}(|000\rangle + |111\rangle)

Można go wytworzyć za pomocą następującego obwodu kwantowego.

qc = QuantumCircuit(3, 3)

qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)

qc.measure(0, 0)
qc.measure(1, 1)
qc.measure(2, 2)

qc.draw("mpl")

Output of the previous code cell

„Głębokość" obwodu kwantowego to przydatna i powszechnie stosowana miara opisująca obwody kwantowe. Prześledź ścieżkę przez obwód kwantowy, poruszając się od lewej do prawej i zmieniając kubit tylko wtedy, gdy jest on połączony z innym kubitem bramką wielokubitową. Policz liczbę bramek wzdłuż tej ścieżki. Maksymalna liczba bramek na dowolnej takiej ścieżce w obwodzie to jego głębokość. We współczesnych zaszumionych komputerach kwantowych obwody o małej głębokości mają mniej błędów i częściej zwracają dobre wyniki. Bardzo głębokie obwody — nie.

Używając QuantumCircuit.depth(), możemy sprawdzić głębokość naszego obwodu kwantowego. Głębokość powyższego obwodu wynosi 4. Górny qubit ma tylko trzy bramki, łącznie z pomiarem. Istnieje jednak ścieżka od górnego qubitu w dół do qubitu 1 lub qubitu 2, która obejmuje jeszcze jedną bramkę CNOT.

qc.depth()
4

Ćwiczenie 2​

Stan GHZ układu 8-qubitowego to

12(∣00000000⟩+∣11111111⟩)\frac{1}{\sqrt 2}(|00000000\rangle + |11111111\rangle)

Napisz kod przygotowujący ten stan za pomocą możliwie najpłytszego obwodu. Głębokość najpłytszego obwodu kwantowego wynosi 5, wliczając bramki pomiarowe.

Rozwiązanie:

# Step 1
qc = QuantumCircuit(8, 8)

##your code goes here##
qc.h(0)
qc.cx(0, 4)
qc.cx(4, 6)
qc.cx(6, 7)

qc.cx(4, 5)

qc.cx(0, 2)
qc.cx(2, 3)

qc.cx(0, 1)
qc.barrier() # for visual separation

# measure
for i in range(8):
qc.measure(i, i)

qc.draw("mpl")
# print(qc.depth())

Output of the previous code cell

print(qc.depth())
5
from qiskit.visualization import plot_histogram
# Step 2
# For this exercise, the circuit and operators are simple, so no optimizations are needed.

# Step 3
# Run the circuit on a simulator to get the results
backend = AerSimulator()

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc = pm.run(qc)

sampler = Sampler(mode=backend)
job = sampler.run([isa_qc], shots=1024)
result = job.result()

counts = result[0].data.c.get_counts()
print(counts)

# Step 4
# Plot the counts in a histogram

plot_histogram(counts)
{'11111111': 535, '00000000': 489}

Output of the previous code cell

5. Podsumowanie​

Poznałeś obliczenia kwantowe w modelu obwodowym z użyciem kubitów i bramek kwantowych oraz powtórzyłeś zagadnienia superpozycji, pomiaru i splątania. Nauczyłeś się również metody uruchamiania obwodu kwantowego na prawdziwym urządzeniu kwantowym.

W końcowym ćwiczeniu dotyczącym tworzenia obwodu GHZ starałeś się zmniejszyć głębokość obwodu, co jest ważnym czynnikiem przy uzyskiwaniu użytecznych w praktyce rozwiązań (utility scale) na zaszumionych komputerach kwantowych. W kolejnych lekcjach tego kursu szczegółowo poznasz szumy i metody mitygacji błędów. W tej lekcji, jako wstęp, rozważaliśmy zmniejszanie głębokości obwodu na idealnym urządzeniu, jednak w praktyce należy brać pod uwagę ograniczenia rzeczywistego urządzenia, takie jak łączność kubitów. Więcej na ten temat dowiesz się w kolejnych lekcjach tego kursu.

# See the version of Qiskit
import qiskit

qiskit.__version__
'2.0.2'