Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Proizvodna nasuprot spregnutim stanjima

Tenzorski proizvod nam omogućava da od stanja pojedinačnih kubita sagradimo stanje registra:

ψ1ψ2ψN\ket{\psi_1}\otimes\ket{\psi_2}\otimes\cdots \otimes \ket{\psi_N}

Takva stanja zovemo proizvodna (eng. “product state”) ili separabilna (eng. “separable”).

Za dva kubita, opšte proizvodno stanje je

(α0+β1)(γ0+δ1)=αγ00+αδ01+βγ10+βδ11.(\alpha\ket0 + \beta\ket1)\otimes(\gamma\ket0 + \delta\ket1) = \alpha\gamma\ket{00} + \alpha\delta\ket{01} + \beta\gamma\ket{10} + \beta\delta\ket{11}.

Odatle su amplitude a00=αγ, a01=αδ, a10=βγ, a11=βδa_{00}=\alpha\gamma,\ a_{01}=\alpha\delta,\ a_{10}=\beta\gamma,\ a_{11}=\beta\delta. Postoji jednostavan i elegantan način da proverimo da li ovo stanje može da se razloži u tenzorski produkt. Složimo četiri amplitude u matricu čiji red bira prvi, a kolona drugi kubit:

C=(a00a01a10a11)=(αγαδβγβδ).C = \begin{pmatrix} a_{00} & a_{01} \\ a_{10} & a_{11} \end{pmatrix} = \begin{pmatrix}\alpha\gamma & \alpha\delta \\ \beta\gamma & \beta\delta\end{pmatrix}.

Sa sledećim preciznim uslovom:

separabilno    detC=a00a11a01a10=0.\text{separabilno} \;\Longleftrightarrow\; \det C = a_{00}a_{11} - a_{01}a_{10} = 0.

Ako je determinanta različita od nule, stanje je spregnuto i nema rastavljanja na „prvi kubit puta drugi kubit"!

Hajde da kroz primere pokažemo kako funkcioniše ovaj uslov i kako se koristimo Python-om u ovom slučaju.
(a) Napiši 0+\ket0\otimes\ket{+} eksplicitno kao vektor u C4\mathbb{C}^4 i potvrdi np.kron-om.
(b) Prekroji amplitude u matricu CC iz (3) i pomoću determinantei i odredi da li je stanje proizvodno.
(c) Testiraj uslov separabilnosti na 0+\ket0\otimes\ket{+} (očekivano separabilno) i na Bell stanju 12(00+11)\tfrac{1}{\sqrt2}(\ket{00}+\ket{11}) (očekivano spregnuto).

Bell-ova stanja

Za sistem od dva kubita postoji poseban skup stanja koja su, prema ranije predstavljenoj definiciji, istorijski i praktično veoma važna u kvantnoj mehanici. Ta stanja nazivamo Bell-ovim stanjima (Bell state).

Ona su definisana kao:

Φ+=12(00+11),Φ=12(0011),Ψ+=12(01+10),Ψ=12(0110).\begin{align*} \ket{\Phi^{+}} &= \dfrac{1}{\sqrt{2}} \left( \ket{00} + \ket{11} \right), \\ \ket{\Phi^{-}} &= \dfrac{1}{\sqrt{2}} \left( \ket{00} - \ket{11} \right), \\ \ket{\Psi^{+}} &= \dfrac{1}{\sqrt{2}} \left( \ket{01} + \ket{10} \right), \\ \ket{\Psi^{-}} &= \dfrac{1}{\sqrt{2}} \left( \ket{01} - \ket{10} \right). \end{align*}

Sva četiri Bell-ova stanja su spregnuta — lako se proverava da za svako od njih matrica koeficijenata CC iz (3) ima detC=±120\det C = \pm\tfrac12 \neq 0. Štaviše, ona su maksimalno spregnuta: izmerimo li samo jedan kubit, oba ishoda (0 i 1) dobijamo sa verovatnoćom 12\tfrac12, pa pojedinačni kubit ne nosi nikakvu informaciju — sva informacija je „sakrivena" u korelaciji između kubita. Uz to, četiri Bell-ova stanja su međusobno ortonormirana i čine Bell-ovu bazu četvorodimenzionog prostora dva kubita, alternativu računskoj bazi {00,01,10,11}\{\ket{00},\ket{01},\ket{10},\ket{11}\}.

Ova stanja igraju veoma važnu ulogu u kvantnoj mehanici i u kontekstu Bell-ovih nejednakosti. Time ćemo se baviti u nekim narednim poglavljima.

Kapije na delu registra

Kada kapiju primenjujemo na jedan kubit unutar registra, na ostale kubite deluje jedinična matrica II. Delovanje na kubit jj dobijamo tenzorskim proizvodom u kom je na mestu jj tražena kapija, a svuda drugde II. Na primer, HH samo na srednjem od tri kubita je IHII\otimes H\otimes I (matrica 8×88\times 8). Tako svaki simulator zapravo „diže" jednokubitne kapije na ceo prostor.

Recimo da moramo da kreiramo 8×88\times 8 matricu koja odgovara IHII\otimes H\otimes I i primeni je na 000\ket{000}. Rezultat pravi superpoziciju samo po srednjem bitu, tj. daje 0+0\ket0\otimes\ket{+}\otimes\ket0.

import numpy as np
ket0 = np.array([1, 0], dtype=complex) 
ket1 = np.array([0, 1], dtype=complex)

plus = (ket0 + ket1)/np.sqrt(2)
I = np.eye(2)
H = np.array([[1, 1], [1, -1]])/np.sqrt(2)

op = np.kron(np.kron(I, H), I)                 # I ⊗ H ⊗ I  (8x8)
out = op @ np.kron(np.kron(ket0, ket0), ket0)  # deluje na |000>
exp = np.kron(np.kron(ket0, plus), ket0)       # očekivano |0>|+>|0>
print("I⊗H⊗I |000> == |0>|+>|0> ? ", np.allclose(out, exp))

Prva spregnuta kapija: CNOT i Bell stanje

Proizvodna stanja gradimo tenzorskim proizvodom, ali da bismo napravili spregnutost treba nam kapija koja povezuje dva kubita — jednokubitne kapije, koliko god ih ređali, uvek preslikavaju proizvodno stanje u proizvodno. Najvažnija dvokubitna kapija je CNOT (kontrolisano-NE): ako je kontrolni kubit 1\ket1, ona obrne ciljni kubit; ako je 0\ket0, ne radi ništa. Baš zato što ishod na ciljnom kubitu zavisi od kontrolnog, CNOT „upliće" dva kubita jedan u drugi. U računskoj bazi {00,01,10,11}\{\ket{00},\ket{01},\ket{10},\ket{11}\} (kontrola = prvi kubit) njena matrica je

CNOT=(1000010000010010).\mathrm{CNOT} = \begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1\\ 0 & 0 & 1 & 0 \end{pmatrix}.

Ispod možete da vidite grafički kako se predstavlja ova kapija

purity

Slika 1:CNOT grafički prikaz kapije diagramatički u kolu.

Operaciju CNOT možemo najdirektnije videti tako što počev od stanja

Ψ=a000+a101+a210+a311\ket{\Psi} = a_{0} \ket{00} + a_{1} \ket{01} + a_{2} \ket{10} + a_{3} \ket{11}

dobijamo

Ψ=CNOT12Ψ=a000+a101+a211+a310,\ket{\Psi'} ={\rm CNOT}_{1\to2}\ket{\Psi} = a_{0} \ket{00} + a_{1} \ket{01} + a_{2} \ket{11} + a_{3} \ket{10},

gde smo subscriptom označili koji je ciljni kubit a koji je kontrolni sa strelicom.

Kako dobijamo Bell-ova stanja

Primenimo li HH na prvi kubit, pa CNOT, iz 00\ket{00} dobijamo čuveno Bell stanje što znači da imamo proizvodni ulaz, a spregnuti izlaz.

Stoga imamo

Φ+=CNOT12(HI)00=12(00+11)\ket{\Phi^{+}} = {\rm CNOT}_{1 \to 2}(H\otimes I)\ket{00} = \tfrac{1}{\sqrt2}(\ket{00}+\ket{11})

CNOT obrne ciljni kubit tamo gde je kontrola 1\ket1, pa 1011\ket{10}\to\ket{11}, čime dobijamo 12(00+11)\tfrac{1}{\sqrt2}(\ket{00}+\ket{11}).

Odgovarajući kod u Pythonu jeste:

import numpy as np
ket0 = np.array([1, 0], dtype=complex)
ket1 = np.array([0, 1], dtype=complex)
H = np.array([[1, 1], [1, -1]])/np.sqrt(2)
I = np.eye(2)
CNOT = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]], dtype=complex)

psi  = np.kron(H @ ket0, ket0)                  # (H⊗I)|00>
out  = CNOT @ psi
bell = (np.kron(ket0, ket0) + np.kron(ket1, ket1))/np.sqrt(2)
print("izlaz == Bell? ", np.allclose(out, bell))

def faktorabilno(psi, tol=1e-9):
    a = psi.reshape(4); return abs(a[0]*a[3] - a[1]*a[2]) < tol
print("izlaz faktorabilan? ", faktorabilno(out))   # False -> spregnuto

QisKit kod

Hajde da vidimo sledeće kako izgleda QisKit kolo koje priprema ovo stanje.

Kolo za Bell stanje

Slika 2:Kolo za Bell stanje: HH na kubitu q0q_0 napravi superpoziciju, a CNOT (tačka = kontrola, \oplus = meta/cilj) je „prepiše" na kubit q1q_1. Merenjem dobijamo dva korelisana klasična bita.

# instalacije QisKit-a i dodatka zabolju vizuelizaciju
!pip install qiskit 
!pip install pylatexenc

# učitati potrebne funkcije iz QisKit paketa
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector, Operator


bell = QuantumCircuit(2)
bell.h(0)            # superpozicija na prvom kjubitu
bell.cx(0, 1)        # CNOT: kontrola = kjubit 0, meta = kjubit 1

bell.measure_all()

bell.draw('mpl', style='iqp')

Hajde da obavimo jedan brzi numerički eksperiment sa QisKit-om: kolo pokrećemo hiljadu puta i brojimo koliko puta se pojavi svaki od četiri moguća bitstringa.

import numpy as np
from qiskit import QuantumCircuit, ClassicalRegister
from qiskit.primitives import StatevectorSampler
import matplotlib.pyplot as plt
from itertools import product

# lokalni "simulator"
sampler = StatevectorSampler()

broj_uzorkovanja = 1000

# --- Kreiranje kola (Bell state) ---
qc = QuantumCircuit(2)
qc.h(0)                # superpozicija na prvom kubitu
qc.cx(0, 1)            # CNOT: kontrola = kubit 0, meta = kubit 1

c = ClassicalRegister(2, "c")
qc.add_register(c)
qc.measure([0, 1], [0, 1])   # kubit -> klasični bit

# --- Merenje ---
result = sampler.run([qc], shots=broj_uzorkovanja).result()
counts = result[0].data.c.get_counts()   # ime registra je 'c'

# --- Popunjavanje svih baznih stanja (uključujući ona sa 0 merenja) ---
n = qc.num_qubits
all_states = ["".join(bits) for bits in product("01", repeat=n)]
full_counts = {state: counts.get(state, 0) for state in all_states}
print("Svi ishodi:", full_counts)
# npr. {'00': 508, '01': 0, '10': 0, '11': 492}

# --- Verovatnoće ---
probabilities = [full_counts[s] / broj_uzorkovanja for s in all_states]

# --- Kreiranje histograma ---
plt.figure(figsize=(12, 6))
plt.bar(all_states, probabilities)

plt.xlabel("Bit Stringovi")
plt.ylabel("Verovatnoća")
plt.title("Histogram verovatnoća")

plt.tight_layout()
plt.show()

Rezultat je iznenađujući: pojavljuju se samo ishodi 00 i 11, nikad 01 ili 10. Dva kubita su savršeno korelisana.

Histogram Bell

Slika 3:Rezultati klasične simulacije.

Bell-ovo stanje na kvantno računaru

!pip install qiskit 
!pip install pylatexenc
!pip install iqm-client[qiskit]
# potrebni alati
from qiskit import transpile
from iqm.qiskit_iqm import IQMProvider
from qiskit import QuantumCircuit


# Definisanje konekcije to IQM 
iqm_server_url = "https://resonance.iqm.tech"
my_token = "97tntBkqWiVm4idfd+tZQDn3iUgcXCLeGJA/Dtf9JNgBn6nbIVt9AZbOx3VojD9s"

# Instantiate the IQMProvider directly with the URL, token, and quantum_computer
provider = IQMProvider(url=iqm_server_url, token=my_token, quantum_computer='garnet')

# Get the 'sirius' backend from the provider. This step will now correctly use the configured provider.
qiskit_iqm_backend = provider.get_backend("garnet")


# --- Kreiranje kola (Bell state) ---
qc = QuantumCircuit(2,2)
qc.h(0)                # superpozicija na prvom kubitu
qc.cx(0, 1)            # CNOT: kontrola = kubit 0, meta = kubit 1

qc.measure([0, 1], [0, 1])   # kubit -> klasični bit



transpiled_circuit = transpile(qc, backend=qiskit_iqm_backend)
job = qiskit_iqm_backend.run(transpiled_circuit, shots=1000)

result = job.result()
counts = result.get_counts() # Removed transpiled_circuit argument
print("Measurement counts:", counts)
import matplotlib.pyplot as plt
from itertools import product

broj_uzorkovanja = 1000

# --- Popunjavanje svih baznih stanja (uključujući ona sa 0 merenja) ---
n = qc.num_qubits
all_states = ["".join(bits) for bits in product("01", repeat=n)]
full_counts = {state: counts.get(state, 0) for state in all_states}
print("Svi ishodi:", full_counts)
# npr. {'00': 508, '01': 0, '10': 0, '11': 492}

# --- Verovatnoće ---
probabilities = [full_counts[s] / broj_uzorkovanja for s in all_states]

# --- Kreiranje histograma ---
plt.figure(figsize=(12, 6))
plt.bar(all_states, probabilities)

plt.xlabel("Bit Stringovi")
plt.ylabel("Verovatnoća")
plt.title("Histogram verovatnoća")

plt.tight_layout()
plt.show()
Histogram Bell

Slika 4:Rezultati sa kvantnog računara

Hajde dvaput da primenimo CNOT gates

# potrebni alati
from qiskit import transpile
from iqm.qiskit_iqm import IQMProvider
from qiskit import QuantumCircuit


# Definisanje konekcije to IQM 
iqm_server_url = "https://resonance.iqm.tech"
my_token = "97tntBkqWiVm4idfd+tZQDn3iUgcXCLeGJA/Dtf9JNgBn6nbIVt9AZbOx3VojD9s"

# Instantiate the IQMProvider directly with the URL, token, and quantum_computer
provider = IQMProvider(url=iqm_server_url, token=my_token, quantum_computer='garnet')

# Get the 'sirius' backend from the provider. This step will now correctly use the configured provider.
qiskit_iqm_backend = provider.get_backend("garnet")


# --- Kreiranje kola (Bell state) ---
qc = QuantumCircuit(2,2)
qc.h(0)                # superpozicija na prvom kubitu
qc.cx(0, 1)            # CNOT: kontrola = kubit 0, meta = kubit 1

qc.barrier()

qc.h(0)                # superpozicija na prvom kubitu
qc.cx(0, 1)            # CNOT: kontrola = kubit 0, meta = kubit 1


qc.measure([0, 1], [0, 1])   # kubit -> klasični bit



transpiled_circuit = transpile(qc, backend=qiskit_iqm_backend)
job = qiskit_iqm_backend.run(transpiled_circuit, shots=1000)

result = job.result()
counts = result.get_counts() # Removed transpiled_circuit argument
print("Measurement counts:", counts)
import matplotlib.pyplot as plt
from itertools import product

broj_uzorkovanja = 1000

# --- Popunjavanje svih baznih stanja (uključujući ona sa 0 merenja) ---
n = qc.num_qubits
all_states = ["".join(bits) for bits in product("01", repeat=n)]
full_counts = {state: counts.get(state, 0) for state in all_states}
print("Svi ishodi:", full_counts)
# npr. {'00': 508, '01': 0, '10': 0, '11': 492}

# --- Verovatnoće ---
probabilities = [full_counts[s] / broj_uzorkovanja for s in all_states]

# --- Kreiranje histograma ---
plt.figure(figsize=(12, 6))
plt.bar(all_states, probabilities)

plt.xlabel("Bit Stringovi")
plt.ylabel("Verovatnoća")
plt.title("Histogram verovatnoća")

plt.tight_layout()
plt.show()
Histogram Bell

Slika 5:Rezultati sa kvantnog računara posle primene dvaput istog kola.

Ovde možemo videti koliko kolo nam treba za faktorizaciju broja 21: https://algassert.com/post/2500

Vidite da u suštini ukoliko je dvokubitna kapija više pod uticajem šuma ukupni algoritam i njegov izlaz je upitan.

Kako se CNOT zaista izvodi na superprovodničkom hardveru

U kolu smo CNOT napisali kao jednu „kutiju", ali na stvarnom čipu on nije elementarna operacija. Kod superprovodničkih kubita (transmona) prirodna, hardverski „native" dvokubitna kapija najčešće je kontrolisano-ZZ (CZ), a ne CNOT. Njena matrica u bazi {00,01,10,11}\{\ket{00},\ket{01},\ket{10},\ket{11}\} je dijagonalna:

CZ=(1000010000100001),\mathrm{CZ} = \begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & -1 \end{pmatrix},

tj. ona samo doda znak minus stanju 11\ket{11}, a sve ostalo ostavi netaknuto.

Kako onda dobiti CNOT ako hardver „ume" samo CZ? Setimo se da Hadamardova kapija zamenjuje ulogu XX i ZZ ose, tj. HZH=XH Z H = X. Odatle sledi ključni identitet: CNOT dobijamo tako što ciljni kubit „obučemo" u dve Hadamardove kapije oko jedne CZ:

CNOT12=(IH)CZ(IH).\mathrm{CNOT}_{1\to 2} = (I\otimes H)\,\mathrm{CZ}\,(I\otimes H).

Proverimo identitet (11) i numerički, i pomoću QisKit-a (transpilacijom u bazu koja sadrži samo CZ):

import numpy as np
H = np.array([[1, 1], [1, -1]])/np.sqrt(2)
I = np.eye(2)
CZ   = np.diag([1, 1, 1, -1]).astype(complex)
CNOT = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]], dtype=complex)

lhs = np.kron(I, H) @ CZ @ np.kron(I, H)     # (I⊗H) CZ (I⊗H)
print("(I⊗H) CZ (I⊗H) == CNOT ?", np.allclose(lhs, CNOT))   # True

Zaključak: kada u kolu nacrtamo CNOT, on je samo logička apstrakcija dok na čipu se u pozadini izvršava jedna CZ „umotana" u nekoliko jeftinih jednokubitnih kapija. Zato se pri proceni dubine i tačnosti kola broje pre svega dvokubitne kapije (CZ), jer su one najsporije i unose najviše šuma.


Jovan Odavić · dzovan90 [at] gmail [dot] com · www.jovanodavic.com