Proizvodna nasuprot spregnutim stanjima¶
Tenzorski proizvod nam omogućava da od stanja pojedinačnih kubita sagradimo stanje registra:
Takva stanja zovemo proizvodna (eng. “product state”) ili separabilna (eng. “separable”).
Hajde da vidimo to na sledećem primeru!
Za dva kubita, opšte proizvodno stanje je
Odatle su amplitude . 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:
Sa sledećim preciznim uslovom:
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 eksplicitno kao vektor u i potvrdi np.kron-om.
(b) Prekroji amplitude u matricu iz (3) i pomoću determinantei i odredi da li je stanje proizvodno.
(c) Testiraj uslov separabilnosti na (očekivano separabilno) i na Bell stanju (očekivano spregnuto).
Rešenje
(a) , pa je .
(b) Za je , rang 1, → separabilno. Za Bell stanje je , rang 2, → spregnuto.
import numpy as np
ket0 = np.array([1, 0], dtype=complex); ket1 = np.array([0, 1], dtype=complex)
plus = (ket0 + ket1)/np.sqrt(2)
def je_separabilno(psi, tol=1e-9):
C = psi.reshape(2, 2) # amplitude -> 2x2 matrica
return np.linalg.matrix_rank(C, tol=tol) == 1 # rang 1 <=> proizvodno
prod = np.kron(ket0, plus)
bell = (np.kron(ket0, ket0) + np.kron(ket1, ket1))/np.sqrt(2)
print("|0>|+> =", np.round(prod, 3))
print("|0>|+> separabilno? ", je_separabilno(prod)) # True
print("Bell separabilno? ", je_separabilno(bell)) # False
# ista provera preko determinante (samo za 2x2):
for ime, psi in [("|0>|+>", prod), ("Bell", bell)]:
C = psi.reshape(2, 2)
print(f"det C ({ime}) =", round(abs(np.linalg.det(C)), 3))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:
Sva četiri Bell-ova stanja su spregnuta — lako se proverava da za svako od njih matrica koeficijenata iz (3) ima . Štaviše, ona su maksimalno spregnuta: izmerimo li samo jedan kubit, oba ishoda (0 i 1) dobijamo sa verovatnoćom , 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 .
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 . Delovanje na kubit dobijamo tenzorskim proizvodom u kom je na mestu tražena kapija, a svuda drugde . Na primer, samo na srednjem od tri kubita je (matrica ). Tako svaki simulator zapravo „diže" jednokubitne kapije na ceo prostor.
Recimo da moramo da kreiramo matricu koja odgovara i primeni je na . Rezultat pravi superpoziciju samo po srednjem bitu, tj. daje .
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 , ona obrne ciljni kubit; ako je , 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 (kontrola = prvi kubit) njena matrica je
Ispod možete da vidite grafički kako se predstavlja ova kapija

Slika 1:CNOT grafički prikaz kapije diagramatički u kolu.
Operaciju CNOT možemo najdirektnije videti tako što počev od stanja
dobijamo
gde smo subscriptom označili koji je ciljni kubit a koji je kontrolni sa strelicom.
Kako dobijamo Bell-ova stanja¶
Primenimo li na prvi kubit, pa CNOT, iz dobijamo čuveno Bell stanje što znači da imamo proizvodni ulaz, a spregnuti izlaz.
Stoga imamo
CNOT obrne ciljni kubit tamo gde je kontrola , pa , čime dobijamo .
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 -> spregnutoQisKit kod¶
Hajde da vidimo sledeće kako izgleda QisKit kolo koje priprema ovo stanje.

Slika 2:Kolo za Bell stanje: na kubitu napravi superpoziciju, a CNOT (tačka = kontrola, = meta/cilj) je „prepiše" na kubit . 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.

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()
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()
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://
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- (CZ), a ne CNOT. Njena matrica u bazi je dijagonalna:
tj. ona samo doda znak minus stanju , a sve ostalo ostavi netaknuto.
Kako onda dobiti CNOT ako hardver „ume" samo CZ? Setimo se da Hadamardova kapija zamenjuje ulogu i ose, tj. . Odatle sledi ključni identitet: CNOT dobijamo tako što ciljni kubit „obučemo" u dve Hadamardove kapije oko jedne CZ:
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)) # TrueZaključ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