Sari la conținutul principal

Introducere în Qiskit

În acest notebook vom explora cum putem programa porți cuantice și circuite cuantice cu Qiskit și chiar cum le putem executa pe simulatoare și calculatoare cuantice reale folosind tipare Qiskit. Ulterior vom introduce diferite modalități de codificare a informației și vom încheia cu un exemplu bonus de Teleportare Cuantică.

Înainte să începi

Urmează instrucțiunile de Instalare și configurare dacă nu ai făcut-o deja, inclusiv pașii pentru Configurarea pentru utilizarea IBM Quantum™ Platform.

Se recomandă să folosești mediul de dezvoltare Jupyter pentru a interacționa cu calculatoarele cuantice. Asigură-te că instalezi suportul suplimentar recomandat pentru vizualizare ('qiskit[visualization]'). Vei mai avea nevoie și de pachetul matplotlib pentru a doua parte a acestui exemplu.

Pentru a învăța despre calculul cuantic în general, vizitează cursul Bazele informației cuantice pe IBM Quantum Learning

Importuri

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime
# Import necessary modules for this notebook
import time
import qiskit

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector, plot_state_qsphere
from qiskit_aer import AerSimulator
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.visualization import plot_histogram
print(qiskit.__version__)
2.3.1

Pentru a executa circuitele tale cuantice pe hardware trebuie mai întâi să îți configurezi contul. Poți face asta astfel:

  1. Accesează IBM Quantum® Platform îmbunătățit.
  2. Mergi în colțul din dreapta sus (conform imaginii de mai sus), creează-ți token-ul API și copiază-l într-un loc sigur.
  3. În celula următoare, înlocuiește deleteThisAndPasteYourAPIKeyHere cu cheia ta API.
  4. Mergi în colțul din stânga jos (conform imaginii de mai sus) și creează-ți instanța. Asigură-te că alegi planul deschis.
  5. După ce instanța este creată, copiază codul CRN asociat. Este posibil să fie nevoie să reîmprospătezi pagina pentru a vedea instanța.
  6. În celula de mai jos, înlocuiește deleteThisAndPasteYourCRNHere cu codul tău CRN.

Consultă acest ghid pentru mai multe detalii despre configurarea contului tău IBM Cloud®.

⚠️ Notă: Tratează cheia ta API ca pe o parolă securizată. Consultă ghidul Configurare Cloud pentru mai multe informații despre utilizarea cheii tale API în medii sigure și nesigure.

#your_api_key = "deleteThisAndPasteYourAPIKeyHere"
#your_crn = "deleteThisAndPasteYourCRNHere"

QiskitRuntimeService.save_account(
channel="ibm_quantum_platform",
token=your_api_key,
instance=your_crn,
overwrite=True
)

1. Porți Cuantice și Circuite Cuantice

Circuitele cuantice sunt modele pentru calculul cuantic în care o computație este o secvență de porți cuantice. Să aruncăm o privire asupra câtorva porți cuantice populare.

X Gate

Un X Gate corespunde unei rotații în jurul axei X a sferei Bloch cu π\pi radiani. Mapează 0|0\rangle la 1|1\rangle și 1|1\rangle la 0|0\rangle. Este echivalentul cuantic al porții NOT pentru calculatoarele clasice și uneori este numit bit-flip.

X=(0110)X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \\ \end{pmatrix}

# Let's apply an X-gate on a |0> qubit
qc = QuantumCircuit(1)
qc.x(0)
qc.draw(output='mpl')

Quantum circuit diagram

# Let's see Bloch sphere visualization
sv = Statevector(qc)
plot_bloch_multivector(sv)

Code output

H Gate

Un Hadamard gate reprezintă o rotație de π\pi în jurul axei aflate la mijlocul axei XX și axei ZZ. Mapează starea de bază 0|0\rangle la 0+12\frac{|0\rangle + |1\rangle}{\sqrt{2}}, ceea ce înseamnă că o măsurătoare va avea probabilități egale de a fi 1 sau 0, creând o 'superpoziție' de stări. Această stare se mai scrie și ca +|+\rangle.

H=12(1111)H = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \\ \end{pmatrix}

# Let's apply an H-gate on a |0> qubit
qc = QuantumCircuit(1)
qc.x(0)
qc.h(0)
qc.draw(output='mpl')

Quantum circuit diagram

# Let's see Bloch sphere visualization
sv = Statevector(qc)
plot_bloch_multivector(sv)

Code output

CX Gate (CNOT Gate)

Poarta NOT controlată (sau CNOT sau CX) acționează pe doi qubiți. Efectuează operația NOT (echivalent cu aplicarea unui X gate) pe al doilea qubit doar când primul qubit este 1|1\rangle și altfel îl lasă neschimbat. Notă: Qiskit numerotează biții dintr-un șir de la dreapta la stânga.

CX=(1000010000010010)CX = \begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1\\ 0 & 0 & 1 & 0\\ \end{pmatrix}

# Let's apply a CX-gate on |11>
qc = QuantumCircuit(2)
qc.x(0)
qc.x(1)
qc.cx(0,1)
qc.draw(output='mpl')

Quantum circuit diagram

sv=Statevector(qc)
plot_state_qsphere(sv)

Code output

Creează prima stare Bell

ϕ+=12(00+11)|\phi^+ \rangle = \frac{1}{\sqrt 2}(|00 \rangle + |11 \rangle)

# Create a Bell state circuit

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

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

Quantum circuit diagram

# Plot the state using q-sphere visualization
sv = Statevector(qc)
plot_state_qsphere(sv)
# q-sphere is useful for visualizing states when Bloch sphere fails to

Code output

Creează a doua stare Bell

ϕ=12(0011)|\phi^- \rangle = \frac{1}{\sqrt 2}(|00 \rangle - |11 \rangle)

# Create a circuit with the second Bell state

qc = QuantumCircuit(2)
qc.x(0)
qc.h(0)
qc.cx(0,1)

qc.draw("mpl")

Quantum circuit diagram

Explicația este că:

H1=12(01)=H|1\rangle=\frac{1}{\sqrt{2} }(|0\rangle-|1\rangle) = |-\rangle
# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

Creează starea GHZ cu 3 qubiți

GHZ=12(000+111)|GHZ \rangle = \frac{1}{\sqrt 2}(|000 \rangle + |111 \rangle)

# Create a circuit with 3-qubit GHZ state

qc= QuantumCircuit(3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)

qc.draw("mpl")

Quantum circuit diagram

# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

Creează starea logo Qiskit

Qiskit=12(0010+1101)|Qiskit \rangle = \frac{1}{\sqrt 2}(|0010 \rangle + |1101 \rangle)

Centered Image
# Create a circuit with the Qiskit logo state

qc = QuantumCircuit(4)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
qc.cx(0,3)
qc.x(1)

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

Quantum circuit diagram

# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

2. Creează și rulează un program cuantic simplu

Cei patru pași pentru scrierea unui program cuantic folosind tiparele Qiskit sunt:

  1. Mapează problema la un format nativ cuantic.

  2. Optimizează circuitele și operatorii.

  3. Execută folosind o funcție primitivă cuantică.

  4. Analizează rezultatele.

2.1 Mapează problema într-un format nativ cuantic

Într-un program cuantic, circuitele cuantice sunt formatul nativ în care se reprezintă instrucțiunile cuantice, iar operatorii reprezintă observabilele de măsurat. Când creezi un circuit, de obicei vei crea un nou obiect QuantumCircuit, apoi vei adăuga instrucțiuni în secvență.

Următoarea celulă de cod creează un circuit care produce starea GHZ, adică o stare în care trei qubiți sunt complet întrețesuti unul cu celălalt.

SDK-ul Qiskit folosește numerotarea biților LSb 0, unde cifra nthn^{th} are valoarea 1n1 \ll n sau 2n2^n. Pentru mai multe detalii, consultă subiectul Bit-ordering in the Qiskit SDK.

# Create a GHZ state circuit

qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

Consultă QuantumCircuit din documentație pentru toate operațiunile disponibile.

Când creezi circuite cuantice, trebuie să iei în considerare și ce tip de date vrei să fie returnate după execuție. Qiskit oferă două modalități de a returna date: poți obține o distribuție de probabilitate pentru un set de qubiți pe care alegi să îi măsori, sau poți obține valoarea de așteptare a unui observabil. Pregătește-ți sarcina de lucru pentru a măsura circuitul prin una dintre aceste două metode cu primitivele Qiskit (explicate în detaliu la Pasul 3).

Acest exemplu măsoară valorile de așteptare folosind submodulul qiskit.quantum_info, specificat prin operatori (obiecte matematice folosite pentru a reprezenta o acțiune sau un proces care modifică o stare cuantică). Următoarea celulă de cod creează șase operatori Pauli pe trei qubiți: ZZZ, ZZX, ZII, XXI, ZZI și III.

# Set up six different observables.

observables_labels = ["ZZZ", "ZZX", "ZII", "XXI", "ZZI", "III"]

observables = [SparsePauliOp(label) for label in observables_labels]
print(observables)
[SparsePauliOp(['ZZZ'],
coeffs=[1.+0.j]), SparsePauliOp(['ZZX'],
coeffs=[1.+0.j]), SparsePauliOp(['ZII'],
coeffs=[1.+0.j]), SparsePauliOp(['XXI'],
coeffs=[1.+0.j]), SparsePauliOp(['ZZI'],
coeffs=[1.+0.j]), SparsePauliOp(['III'],
coeffs=[1.+0.j])]

Aici, un operator precum ZZI este o prescurtare pentru produsul tensorial ZZIZ\otimes Z\otimes I, ceea ce înseamnă măsurarea Z pe qubitul 2 și Z pe qubitul 1 împreună, obținând informații despre corelația dintre qubitul 2 și qubitul 1. Valorile de așteptare de acest fel sunt de obicei scrise și ca Z2Z1\langle Z_2 Z_1 \rangle.

Dacă starea pe care o observăm este starea GHZ cu trei qubiți, atunci măsurătoarea Z2Z1\langle Z_2 Z_1 \rangle ar trebui să fie 1.

2.2 Optimizează circuitele și operatorii

Când execuți circuite pe un dispozitiv, este important să optimizezi setul de instrucțiuni pe care le conține circuitul și să minimizezi adâncimea totală (aproximativ numărul de instrucțiuni) a circuitului. Aceasta garantează că obții cele mai bune rezultate posibile prin reducerea efectelor erorilor și zgomotului. În plus, instrucțiunile circuitului trebuie să respecte Arhitectura Setului de Instrucțiuni (ISA) a unui dispozitiv Backend și trebuie să ia în considerare porțile de bază și conectivitatea qubiților dispozitivului.

Următorul cod instanțiază un dispozitiv real la care să trimiți un job și transformă circuitul și observabilele pentru a corespunde ISA-ului acelui Backend. Dacă nu ți-ai salvat anterior acreditările, urmează instrucțiunile de aici pentru a te autentifica cu tokenul tău API.

# Choose a real backend
service = QiskitRuntimeService(channel='ibm_quantum_platform',)
backend = service.least_busy(min_num_qubits=156)
# print backend details
print(
f"Name: {backend.name}\n"
f"Version: {backend.backend_version}\n"
f"No. of qubits: {backend.num_qubits}\n"
f"Processor type: {backend.processor_type}\n"
)
Name: ibm_marrakesh
Version: 1.0.21
No. of qubits: 156
Processor type: {'family': 'Heron', 'revision': '2'}
# option to use the AerSimulator instead of a real quantum device
seed_sim=42
backend=AerSimulator.from_backend(backend,seed_simulator=seed_sim)

Transpilează circuitul în circuit ISA

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

pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(qc)

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

Quantum circuit diagram

mapped_observables = [
observable.apply_layout(isa_circuit.layout) for observable in observables
]
print(mapped_observables)
[SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIXIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIXIIIIIIIIIIIIIIIIIIXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])]

2.3 Execută folosind primitivele cuantice

Calculatoarele cuantice pot produce rezultate aleatorii, așa că de obicei colectezi un eșantion de ieșiri rulând circuitul de mai multe ori. Poți estima valoarea observabilului folosind clasa Estimator. Estimator este una dintre cele două primitive; cealaltă este Sampler, care poate fi folosită pentru a obține date de la un calculator cuantic. Aceste obiecte au o metodă run() care execută selecția de circuite, observabile și parametri (dacă este cazul), folosind un bloc unificat primitiv (PUB). Când rulezi acest cod pe hardware cuantic real, ia în considerare aplicarea tehnicilor de atenuare și suprimare a erorilor pentru a reduce zgomotul intrinsec al calculatorului cuantic.

# Construct the Estimator instance.
estimator = Estimator(mode=backend)
estimator.options.resilience_level = 1
estimator.options.default_shots = 5000

Trimite un job folosind primitiva Estimator.

# One pub, with one circuit to run against six 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: 97ecd036-1767-49b0-a1dc-c71638c3c3c4
/Users/jma/miniconda3/envs/3122/lib/python3.12/site-packages/qiskit_ibm_runtime/fake_provider/local_service.py:187: UserWarning: The resilience_level option has no effect in local testing mode.
warnings.warn("The resilience_level option has no effect in local testing mode.")

După ce un job este trimis, poți aștepta fie ca job-ul să se finalizeze în instanța curentă de Python, fie să folosești job_id pentru a prelua datele ulterior. (Consultă secțiunea despre preluarea job-urilor pentru detalii.)

După finalizarea job-ului, examinează ieșirea sa prin atributul result() al job-ului.

# 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]

Acum putem executa de asemenea circuitul folosind primitiva Sampler

# We include the measurements in the circuit
qc.measure_all()
sampler = Sampler(mode=backend)
qc.draw(output="mpl")

Quantum circuit diagram

Trimite un job folosind primitiva Sampler.

job_sampler = sampler.run(pm.run([qc]))

# Use the job ID to retrieve your job data later
print(f">>> Job ID: {job_sampler.job_id()}")
# Get the results
results_sampler = job_sampler.result()
>>> Job ID: a6ee4d2f-c80d-4a86-9a76-e4b1a74502e7

2.4 Analizează rezultatele

Pasul de analiză este, în general, locul în care ai putea post-procesa rezultatele folosind, de exemplu, atenuarea erorilor de măsurare sau extrapolarea zero-zgomot (ZNE). Poți introduce aceste rezultate într-un alt flux de lucru pentru analize suplimentare sau poți pregăti un grafic al valorilor și datelor cheie. În general, acest pas este specific problemei tale. Pentru acest exemplu, trasează fiecare dintre valorile de așteptare măsurate pentru circuitul nostru.

Valorile de așteptare și abaterile standard pentru observabilele pe care le-ai specificat Estimator sunt accesate prin atributele PubResult.data.evs și PubResult.data.stds ale rezultatului jobului. Pentru a obține rezultatele de la Sampler, folosește funcția PubResult.data.meas.get_counts(), care va returna un dict de măsurători sub formă de bitstringuri ca și chei și numărători ca valori corespunzătoare. Pentru mai multe informații, consultă Începe cu Sampler.

# Plot the result
from matplotlib import pyplot as plt
values = pub_result.data.evs
errors = pub_result.data.stds
# plotting graph
# Plotting with error bars
plt.errorbar(observables_labels, values, yerr=errors, fmt='-o', capsize=5)
plt.xlabel("Observables")
plt.ylabel("Values")
plt.title("Plot of Observables vs Values with Error Bars")
plt.grid(True)
plt.tight_layout()
plt.show()

Plot output

Observăm că observabilele ZZIZZI și IIIIII au o valoare de așteptare de 1, deoarece ZZIZZI introduce două semne minus care se anulează, iar IIIIII acționează ca identitatea, lăsând starea GHZ nemodificată. Restul observabilelor au o valoare de așteptare de 0, deoarece operatorii ZZ ai acestora introduc un număr impar de semne minus sau operatorii XX inversează un număr de qubiți care fac stările suprapuse ortogonale.

Acum trasăm rezultatele pentru Sampler

counts_list = results_sampler[0].data.meas.get_counts()
print(counts_list)
print(f"Outcomes : {counts_list}")
display(plot_histogram(counts_list, title="GHZ state"))
{'111': 480, '000': 503, '101': 8, '100': 9, '001': 3, '011': 6, '010': 10, '110': 5}
Outcomes : {'111': 480, '000': 503, '101': 8, '100': 9, '001': 3, '011': 6, '010': 10, '110': 5}

Code output

2.5 Scalează la un număr mare de qubiți

În calculul cuantic, munca la scară de utilitate este esențială pentru a progresa în domeniu. O astfel de muncă necesită calcule realizate la o scară mult mai mare; lucrând cu circuite care ar putea folosi peste 100 de qubiți și peste 1000 de porți. Acest exemplu face un mic pas în acea direcție, scalând problema GHZ la n=10n=10 qubiți. Folosește fluxul de lucru al tiparelor Qiskit și se încheie prin măsurarea valorii de așteptare Z0Zi\langle Z_0 Z_i \rangle .

Pasul 1. Mapează problema

Scrie o funcție care returnează un QuantumCircuit ce pregătește o stare GHZ cu nn qubiți (în esență o stare Bell extinsă), apoi folosește acea funcție pentru a pregăti o stare GHZ cu 10 qubiți și colectează observabilele de măsurat.

def get_qc_for_n_qubit_GHZ_state(n: int) -> QuantumCircuit:

qc = QuantumCircuit(n)
qc.h(0)
for i in range(n-1):
qc.cx(i, i+1)
return qc
n = 10
qc_n_GHZ = get_qc_for_n_qubit_GHZ_state(n)
qc_n_GHZ.draw("mpl")

Quantum circuit diagram

Apoi, mapează operatorii de interes. Acest exemplu folosește operatorii ZZ dintre qubiți pentru a examina comportamentul pe măsură ce se îndepărtează unii de alții. Valorile de așteptare din ce în ce mai inexacte (corupte) dintre qubiții îndepărtați ar dezvălui nivelul de zgomot prezent.

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

operators = [SparsePauliOp(operator) for operator in operator_strings]
['ZZIIIIIIII', 'ZIZIIIIIII', 'ZIIZIIIIII', 'ZIIIZIIIII', 'ZIIIIZIIII', 'ZIIIIIZIII', 'ZIIIIIIZII', 'ZIIIIIIIZI', 'ZIIIIIIIIZ']
9

Pasul 2. Optimizează problema pentru execuția pe Backend-ul cuantic

Transformă circuitul și observabilele pentru a corespunde ISA-ului Backend-ului.

# Convert to an ISA circuit and layout-mapped observables.
pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(qc_n_GHZ)
isa_operators_list = [operator.apply_layout(isa_circuit.layout) for operator in operators]

Pasul 3. Execută pe Backend

Trimite jobul și, dacă îl execuți pe hardware, activează suprimarea erorilor folosind o tehnică de reducere a erorilor numită decuplare dinamică. Nivelul de reziliență specifică cât de multă reziliență să construiești împotriva erorilor. Niveluri mai mari generează rezultate mai precise, cu prețul unor timpi de procesare mai lungi. Pentru o explicație suplimentară a opțiunilor setate în codul următor, consultă Configurează atenuarea erorilor pentru Qiskit Runtime.

# Submit the circuit to Estimator
job = estimator.run([(isa_circuit, isa_operators_list)])
job_id = job.job_id()
/Users/jma/miniconda3/envs/3122/lib/python3.12/site-packages/qiskit_ibm_runtime/fake_provider/local_service.py:187: UserWarning: The resilience_level option has no effect in local testing mode.
warnings.warn("The resilience_level option has no effect in local testing mode.")

Pasul 4. Post-procesează rezultatele

Pentru a înțelege mai bine comportamentul stărilor cuantice întricate pe hardware real, analizăm corelațiile perechi dintre qubiți în baza Z. Mai precis, analizăm valorile de așteptare ⟨Z₀Zᵢ⟩, care măsoară cât de puternic este corelat qubitul 0 cu fiecare alt qubit i. În particular, vom trasa:

ZiZ0/Z1Z0\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle

Ce valori ale ZiZ0/Z1Z0\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle te aștepți să vezi în grafic?

Opțiuni:

a) Descrescătoare pe măsură ce creștem ii

b) Constante în 1

c) Deviații mici în jurul valorii 1

d) Alternând 1 și 0 pentru valori impare și pare ale lui ii

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.

plt.plot(data, values, marker="o", label=f"{n}-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()

Plot output

În acest grafic observăm că Z0Zi\langle Z_0 Z_i \rangle fluctuează în jurul valorii 1, chiar dacă într-o simulare ideală toate Z0Zi\langle Z_0 Z_i \rangle ar trebui să fie 1.

După cum poți vedea, rezultatele experimentelor cu 10 qubiți sunt bune, dar au totuși unele erori. O modalitate de a îmbunătăți rezultatele este să implementezi starea GHZ mai eficient.

De obicei, starea GHZ se implementează cu o secvență de porți CNOT în scară. Cu toate acestea, poți implementa starea GHZ mai eficient, reducând adâncimea cu 2 qubiți de la n la n/2 sau mai puțin.

O metrică importantă pentru a evalua cât de precise vor fi rezultatele, sau cât de puțin zgomot va exista pentru un circuit, este adâncimea porților cu 2 qubiți. Aceasta se datorează faptului că ratele de eroare pentru porțile cu 2 qubiți (~de 10 ori mai mari decât pentru porțile cu un singur qubit) domină erorile întregului circuit. Folosește codul următor pentru a obține adâncimea porților cu 2 qubiți a unui circuit.

qc.depth(lambda x: x.operation.num_qubits == 2)
def better_ghz(n):
"fan out"
s = int(n / 2)
qc = QuantumCircuit(n)
qc.h(s)
for m in range(s, 0, -1):
qc.cx(m, m - 1)
if not (n % 2 == 0 and m == s):
qc.cx(n - m - 1, n - m)
return qc

better_ghz(n).draw("mpl")

Quantum circuit diagram

# Check 2-qubit gate depth before transpilation
qc_better_ghz = better_ghz(n)
qc_better_ghz.depth(lambda x: x.operation.num_qubits == 2)
5

Un lucru interesant de remarcat aici este că am reușit să reducem adâncimea cuantică a circuitului pe care dorim să îl executăm, pur și simplu fiind inteligenți și gândindu-ne la o modalitate diferită de a-l programa. Cu toate acestea, vor exista situații și algoritmi în care nu ne putem baza pe aceste trucuri ingenioase. Aici intervine Transpiler-ul, care ne ajută să optimizăm eficient toate aceste aspecte, astfel încât să nu trebuiască să ne facem prea multe griji în legătură cu ele.

3. Codificarea informației

3.1 Amplitude encoding

Acum că am văzut cum să construim circuite cuantice, este interesant să explorăm cum putem codifica informații clasice în stări cuantice. O metodă puternică este amplitude encoding, unde amplitudinile unei stări cuantice reprezintă componentele unui vector clasic.

Să considerăm un exemplu simplu. Presupunem că dorim să codificăm vectorul clasic

x=[x0x1x2x3]\vec{x} = \begin{bmatrix} x_0 \\ x_1 \\ x_2 \\ x_3 \end{bmatrix}

într-o stare cuantică de doi qubiți. Scopul este să pregătim starea cuantică:

ψ=x000+x101+x210+x311\ket{\psi} = x_0\ket{00} + x_1\ket{01} + x_2\ket{10} + x_3\ket{11}

unde x0,x1,x2,x3Rx_0, x_1, x_2, x_3 \in \mathbb{R} (sau C\mathbb{C}) și vectorul este normalizat astfel încât:

x02+x12+x22+x32=1|x_0|^2 + |x_1|^2 + |x_2|^2 + |x_3|^2 = 1

Acum considerăm exemplul particular: x=[0.8924,0.3696,0.2391,0.0990]\vec{x} = [0.8924, 0.3696, 0.2391, 0.0990]

Atunci starea cuantică corespunzătoare este:

ψ=0.892400+0.369601+0.239110+0.099011\begin{aligned} \ket{\psi} &= 0.8924\,\ket{00} + 0.3696\,\ket{01} + 0.2391\,\ket{10} + 0.0990\,\ket{11} \end{aligned}

Această stare poate fi pregătită folosind o combinație de Gate-uri de rotație RyR_y cu unghiurile π/6\pi/6 și respectiv π/4\pi/4 pentru qubiții 0 și 1

from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
import numpy as np

qc = QuantumCircuit(2)

qc.ry(np.pi / 6, 0)
qc.ry(np.pi / 4, 1)

simulator = AerSimulator()
qc.save_statevector()
result = simulator.run(qc).result()
statevector = result.get_statevector()

print("Statevector:", statevector)
qc.draw(output="mpl")
Statevector: Statevector([0.8923991 +0.j, 0.23911762+0.j, 0.36964381+0.j,
0.09904576+0.j],
dims=(2, 2))

Quantum circuit diagram

from qiskit.quantum_info import Statevector

# Define our vector
v = np.array([0.8924, 0.3696, 0.2391, 0.0990])
v = v/np.linalg.norm(v)
# Create a statevector from the vector
state = Statevector(v)

# Initialize a quantum circuit with 2 qubits
qc = QuantumCircuit(2)
qc.initialize(state.data, [0, 1])

# Optional: simulate the state
print("Statevector:", state)

# Visualize the circuit
qc.decompose().decompose().decompose().decompose().decompose().draw("mpl")
Statevector: Statevector([0.89242154+0.j, 0.36960892+0.j, 0.23910577+0.j,
0.09900239+0.j],
dims=(2, 2))

Quantum circuit diagram

Prin urmare, am văzut cum să codificăm informații folosind Gate-uri de rotație.

3.2 Angle encoding și circuite parametrizate

O modalitate deosebit de interesantă de a codifica informații într-un calculator cuantic este proiectarea unui Circuit cuantic ce conține anumite unghiuri de rotație θ\vec{\theta} sau parametri care pot fi ajustați pentru a reprezenta o familie de funcții f(θ)f(\vec{\theta}). Să considerăm, de exemplu, următorul Circuit cuantic parametrizat:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# Define a symbolic parameter
theta = Parameter("θ")

qc = QuantumCircuit(2)
# We applied a parametrized RX gate
qc.rx(theta, 0)
qc.cx(0, 1)
qc.draw("mpl")

Quantum circuit diagram

Din punct de vedere matematic, putem analiza care este familia de funcții pe care o putem reprezenta cu acest Circuit:

CNOT01Rx{0}(θ)00=CNOT01(cos(θ/2)00isin(θ/2)10)=cos(θ/2)00isin(θ/2)11\text{CNOT}_{01} \, R_x^{\{0\}}(\theta) |00\rangle = \text{CNOT}_{01} \left( \cos(\theta/2)\ket{00} - i\sin(\theta/2)\ket{10} \right) = \cos(\theta/2)\ket{00} - i\sin(\theta/2)\ket{11}

Este destul de clar că numărul de stări pe care le putem reprezenta cu acest Circuit cuantic este limitat, deoarece nu putem reprezenta stări precum 10\ket{10} sau 01\ket{01}, de exemplu. Cu toate acestea, familia de stări pe care o putem reprezenta începe să crească atunci când introducem mai multe rotații în locurile potrivite:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# Define a symbolic parameter
theta1 = Parameter("θ1")
theta2 = Parameter("θ2")

qc = QuantumCircuit(2)
qc.rx(theta1, 0)
qc.rx(theta2, 1)
qc.cx(0, 1)
qc.draw("mpl")

Quantum circuit diagram

În acest caz, stările cuantice pe care le vom reprezenta sunt:

\begin{align*} \text{CNOT}_{01} \, R_x^{\{1}}(\theta_2) R_x^{\{0}}(\theta_1) \ket{00} &= \text{CNOT}_{01} \, R_x^{\{1}}(\theta_2)\left( \cos(\theta_1/2)\ket{00} - i\sin(\theta_1/2)\ket{10} \right) \\ &= \text{CNOT}_{01}\left( \cos(\theta_1/2)\cos(\theta_2/2)\ket{00} - i\cos(\theta_1/2)\sin(\theta_2/2)\ket{01} \right. \\ &\quad \left. - i\sin(\theta_1/2)\cos(\theta_2/2)\ket{10} + \sin(\theta_1/2)\sin(\theta_2/2)\ket{11} \right) \\ &= \cos(\theta_1/2)\cos(\theta_2/2)\ket{00} - i\cos(\theta_1/2)\sin(\theta_2/2)\ket{01} \\ &\quad + \sin(\theta_1/2)\sin(\theta_2/2)\ket{10} - i\sin(\theta_1/2)\cos(\theta_2/2)\ket{11} \end{align*}

Putem observa că acest Circuit generează o familie mai largă de stări cuantice față de cel anterior. În special, acum poate produce stări cu amplitudini nenule pentru 01\ket{01} sau 10\ket{10}, care nu erau posibile cu Circuitul de mai sus. Cu toate acestea, acest Circuit nu este încă un generator universal de stări cuantice, deși poate fi suficient de expresiv pentru a proiecta Circuite cu o anumită flexibilitate în reprezentarea unor funcții. În general, cu cât introducem mai mulți parametri (unghiuri) independenți, cu atât Circuitul are mai multă expresivitate pentru a aproxima stări cuantice arbitrare.

Ansatze și biblioteca de circuite

Acest tip de Circuit cuantic parametrizat poate fi folosit pentru a construi Ansatze, stări cuantice de încercare care urmăresc să aproximeze soluția unei probleme. Aceste Ansatze sunt o componentă centrală a Algoritmilor Cuantici Variaționali, o clasă de algoritmi hibrizi cuantice-clasice care folosesc un calculator cuantic pentru a evalua o funcție de cost și un optimizator clasic pentru a o minimiza. Vom intra în detaliu despre aceste subiecte într-o unitate ulterioară, dar deocamdată vom prezenta cum să construim un Ansatz simplu folosind biblioteca de Circuite din Qiskit.

from qiskit.circuit.library import efficient_su2

SU2_ansatz = efficient_su2(4, su2_gates=["rx", "y"], entanglement="linear", reps=1)
SU2_ansatz.decompose().draw(output="mpl")

Quantum circuit diagram

Am văzut cum să construim un Ansatz simplu folosind funcția efficient_su2 din qiskit.circuit.library, care va fi capabil să genereze o gamă largă de stări cuantice prin ajustarea parametrilor săi θ\vec{\theta}.

Concluzie

În acest notebook, ai învățat cum să construiești Circuite cuantice, de la construirea Gate-urilor cuantice până la definirea și măsurarea observabilelor, și cum să execuți aceste Circuite eficient atât pe simulatoare, cât și pe hardware cuantic real. Ai văzut, de asemenea, importanța proiectării atente a Circuitului pentru a minimiza erorile atunci când lucrezi cu dispozitive cuantice reale, precum și strategii pentru scalarea Circuitelor la un număr mai mare de qubiți, în special prin exemplul stării GHZ. Mai mult, ai explorat diferite tehnici de codificare a informațiilor clasice în stări cuantice, inclusiv amplitude encoding și angle encoding. Cu toate acestea, ești complet echipat să treci la sesiunea următoare și să începi să lucrezi cu algoritmi cuantici.

Instalarea Qiskit Code Assistant în VSCode

Dă click pe link și urmează instrucțiunile.

Bonus: Teleportare Cuantică

Când auzi termenul teleportare cuantică, poate că îți imaginezi o tehnologie sci-fi futuristă care dezintegrează un obiect într-un loc și îl face să reapară undeva departe. Dar teleportarea cuantică nu seamănă deloc cu asta. În realitate, ceea ce se teleportează nu este materia, ci informația.

Teleportarea cuantică permite transferul stării cuantice a unui Qubit dintr-un loc în altul. Deși acest transfer pare instantaneu, nu încalcă legile fizicii. Cum este posibil? Hai să intrăm în detalii!

Teleportarea cuantică este un protocol ce permite unui expeditor (Alice) să transmită starea ψ|\psi\rangle a unui Qubit q unui receptor (Bob) folosind două resurse cheie: o pereche de qubiți împletită partajată a și b și două biți de comunicare clasică c0 și c1.

În esență, protocolul necesită:

  • q: Qubitul lui Alice, inițial în starea ψ|\psi\rangle pe care dorim să o teleportăm.
  • a: Jumătatea lui Alice dintr-o pereche împletită partajată.
  • b: Jumătatea lui Bob din perechea împletită partajată.
  • c0, c1: Biți clasici pentru a stoca rezultatele măsurătorilor lui Alice.

Și cum funcționează? Fluxul de lucru este următorul

  1. Pregătește starea lui Alice ψ|\psi\rangle pe q. Vom crea o stare specifică precum +|+\rangle pentru verificare.
  2. Creează împletire: Generează o pereche Bell între a și b.
  3. Operațiile lui Alice: Alice efectuează o „măsurătoare Bell" pe cei doi qubiți ai săi (q și a) și stochează rezultatele clasice în c0 și c1.
  4. Comunicare clasică: Alice trimite cei doi biți clasici (c0, c1) lui Bob.
  5. Corecțiile lui Bob: Bob aplică Gate-uri cuantice specifice (X și/sau Z) pe Qubitul său (b), condiționate de valorile c0 și c1 pe care le-a primit.

Dacă totul este făcut corect, Qubitul b al lui Bob va ajunge în starea ψ|\psi\rangle, starea originală a lui q al lui Alice!

Pentru o explicație și o explorare mai aprofundată a teleportării cuantice, inclusiv parcurgerea explicației matematice a motivului pentru care funcționează acest protocol, poți consulta resursele IBM Quantum Learning: Quantum Teleportation. Aceasta face parte din cursul Basics of Quantum Information.


import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram, plot_bloch_multivector

# Define individual quantum registers for each qubit
q = QuantumRegister(1, name='q') # message qubit
a = QuantumRegister(1, name='a') # Alice's entangled qubit
b = QuantumRegister(1, name='b') # Bob's entangled qubit

# Classical register for Alice's measurements
cr_alice = ClassicalRegister(2, name='c_alice')

# Create quantum circuit
teleport_qc = QuantumCircuit(q, a, b, cr_alice, name='Teleportation')

# Step 1: Prepare message state |+⟩ on q
teleport_qc.h(q[0])
teleport_qc.barrier()

# Step 2: Create entanglement between a and b
teleport_qc.h(a[0])
teleport_qc.cx(a[0], b[0])
teleport_qc.barrier()

# Step 3: Alice's Bell measurement
teleport_qc.cx(q[0], a[0])
teleport_qc.h(q[0])
teleport_qc.barrier()

# Step 4: Alice measures q and a
teleport_qc.measure(q[0], cr_alice[0])
teleport_qc.measure(a[0], cr_alice[1])
teleport_qc.barrier()

# Step 5: Bob's conditional measurements
with teleport_qc.if_test((cr_alice[1], 1)):
teleport_qc.x(b[0])
with teleport_qc.if_test((cr_alice[0], 1)):
teleport_qc.z(b[0])

# Draw the circuit
teleport_qc.draw(output='mpl')

Quantum circuit diagram

După executarea protocolului apare o întrebare cheie: cum verificăm că teleportarea a funcționat? Nu putem „vedea" direct starea Qubitului lui Bob după protocol. Cu toate acestea, deoarece noi am pregătit starea inițială a lui Alice ψ|\psi\rangle (am ales +|+\rangle), putem folosi un tip special de simulare pentru a verifica dacă Qubitul b al lui Bob a ajuns în acea stare.

Vom folosi AerSimulator cu save_statevector pentru a verifica dacă Qubitul b al lui Bob ajunge în starea originală a lui Alice (+|+\rangle). Acest simulator calculează vectorul de stare cuantică finală și apoi îl reprezintă folosind plot_bloch_multivector pentru a vizualiza Qubitul lui Bob (b) comparativ cu starea inițială a lui Alice (q).

# Simulate the teleportation circuit
sv_simulator = AerSimulator(method='statevector')
teleport_qc_sv = teleport_qc.copy()
teleport_qc_sv.save_statevector()

# Execute the circuit on the statevector simulator
job_sv = sv_simulator.run(teleport_qc_sv)
result_sv = job_sv.result()

# Get the final statevector
final_statevector = result_sv.get_statevector()
print("Visualizing final qubit states:")
display(plot_bloch_multivector(final_statevector))
print("Note that Alice's qubits have collapsed to |00⟩, |01⟩, |10⟩, or |11⟩, while Bob's qubit is in the original state |+⟩.")
Visualizing final qubit states:

Quantum circuit diagram

Note that Alice's qubits have collapsed to |00⟩, |01⟩, |10⟩, or |11⟩, while Bob's qubit is in the original state |+⟩.

După cum putem vedea din vizualizare, primii doi qubiți (aparținând lui Alice) s-au colaps la 0 sau 1. Între timp, al treilea Qubit (aparținând lui Bob), reprezentat în a treia sferă Bloch, indică de-a lungul axei x, ceea ce indică faptul că se află în starea +|+\rangle, astfel am implementat cu succes protocolul de teleportare cuantică!

Rezumat

În acest moment este convenabil să facem un rezumat rapid al celor realizate:

  • Alice a transmis o stare cuantică necunoscută lui Bob.
  • Nicio particulă fizică nu a fost transferată.
  • Starea originală de pe Qubitul lui Alice este distrusă, în concordanță cu teorema No-Cloning.