Przejdź do głównej treści

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 jednoQubitowych
  • Sferze Blocha
  • Superpozycji
  • Pomiarze
  • Bramkach dwuQubitowych 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 Qubitu 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θ20+eiφsinθ21=((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 — required packages for this notebook
!pip install -q qiskit qiskit-aer qiskit-ibm-runtime
# 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 przerzut bitu. 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ć

X0=(0110)(10)=(01)=1X|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 Circuit 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+12\frac{|0\rangle + |1\rangle}{\sqrt{2}}. Macierzowa reprezentacja Gate H jest poniżej.

H=12(1111)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

H0=12(1111)(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:

012.|-\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

H1=12(11 11)(0 1)=12(1 1)=(0.707 0.707)=12(01)=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 Qubit 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

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

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

Bramki Pauliego:

X=(0110)=01+10X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \\ \end{pmatrix} = |0\rangle \langle 1|+|1\rangle \langle 0| Y=(0ii0)=i01+i10Y = \begin{pmatrix} 0 & -i \\ i & 0 \\ \end{pmatrix} = -i|0\rangle \langle 1|+i|1\rangle \langle 0| Z=(1001)=0011Z = \begin{pmatrix} 1 & 0 \\ 0 & -1 \\ \end{pmatrix} = |0\rangle \langle 0|-|1\rangle \langle 1|

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

00=[10][10]=[1000],10=[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 01=[10][01]=[0100],11=[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 jednoQubitowe:

H=12[1111],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(θ)=eiθX/2=cosθ2Iisinθ2X=[cosθ2isinθ2isinθ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(θ)=eiθY/2=cosθ2Iisinθ2Y=[cosθ2sinθ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(θ)=eiθZ/2=cosθ2Iisinθ2Z=[eiθ/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 Gate'ów zostały opisane 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 Circuit 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) XX0XX|0\rangle

(2) HH0HH|0\rangle

(3) HZH0HZH|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 Qubit α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 220+221\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. 32012i1\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 Circuit 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. Wieloqubitowe bramki kwantowe i splątanie

4.1 Wieloqubitowy Circuit kwantowy

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

# 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 qubitów i bitów, co oznacza, że Qubit 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 (Qubit 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 jednoQubitowych.

q1q0=(a0+b1)(c0+d1)|q1\rangle \otimes|q0\rangle = (a|0\rangle+b|1\rangle) \otimes (c|0\rangle+d|1\rangle)

=ac00+ad01+bc10+bd11= ac|0\rangle|0\rangle+ad|0\rangle|1\rangle+bc|1\rangle|0\rangle+bd|1\rangle|1\rangle

=ac00+ad01+bc10+bd11= ac|00\rangle+ad|01\rangle+bc|10\rangle+bd|11\rangle

( ac2+ad2+bc2+bd2=1|ac|^2+ |ad|^2+ |bc|^2+ |bd|^2=1 )

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

H0H0=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 Qubit — prawdopodobieństwo zmierzenia 00|00\rangle wynosi ac2|ac|^2.

# Draw a Bloch sphere
plot_bloch_multivector(out_vector)

Output of the previous code cell

Zmierzmy teraz ten Circuit.

# 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 Wieloqubitowe bramki kwantowe

Bramka CNOT

Bramka CNOT (ang. „controlled NOT" lub CX) jest dwuqubitową bramką, co oznacza, że jej działanie obejmuje dwa Qubit jednocześnie: Qubit sterujący (ang. control) i Qubit docelowy (ang. target). Bramka CNOT odwraca stan Qubit docelowego tylko wtedy, gdy Qubit 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 dwuqubitowej 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ż Qubit 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":

1200+1211\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(0A0B+1A1B)|\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=a00+a11|\psi\rangle_A = a_0|0\rangle+a_1|1\rangle ψB=b00+b11|\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=a0b000+a0b101+a1b010+a1b111|\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 a0,a1,b0,a_0, a_1, b_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. Map problem to quantum circuits and operators
  2. Optimize for target hardware
  3. Execute on target hardware
  4. Post-process the results

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 dwuqubitowy 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 o Qiskit Runtime 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 qubitów z twojego Circuitu na optymalne fizyczne Qubit 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 qubitó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 qubitów. Stan GHZ (stan Greenbergera-Horne'a-Zeilingera) to maksymalnie splątany stan trzech lub więcej qubitów. Stan GHZ dla trzech qubitó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 qubit tylko wtedy, gdy jest on połączony z innym qubitem bramką wieloqubitową. 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 qubitó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 rozwiązań w skali użytkowej 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 spójność qubitó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'