"""
DOJČOV (DEUTSCH) ALGORITAM
==========================
Prvi algoritam koji pokazuje kvantnu prednost nad klasičnim računanjem.

Problem: data je funkcija f koja jednom bitu pridružuje jedan bit. Ona je ili
  - KONSTANTNA  (f(0) = f(1)), ili
  - BALANSIRANA (f(0) ≠ f(1)).
Pitanje je samo: koja je od te dve vrste?

Klasično moramo da pozovemo f DVA puta (i za x=0 i za x=1) da bismo bili sigurni.
Dojčov algoritam to rešava sa SAMO JEDNIM pozivom funkcije (oracle-a), koristeći
superpoziciju i "phase kickback".

Trik je u pomoćnom kubitu koji stavljamo u |−⟩: tada oracle umesto da promeni
njegovu vrednost, "vrati" informaciju o f kao FAZU na prvi kubit. Završni Adamar
tu fazu pretvara u merljiv rezultat:
  - merenje  0  → funkcija je KONSTANTNA
  - merenje  1  → funkcija je BALANSIRANA

Ispod su definisana dva oracle-a; menjaj koji je aktivan u KORAKU 4 da uporediš ishode.
"""

from qiskit import QuantumCircuit
import quantum_utils_v1 as q

# ============================================================================
# DEFINICIJA ORACLE FUNKCIJA (Uf: |x>|y> -> |x>|y ⊕ f(x)>)
# ============================================================================


def balanced_oracle(qc):
    # f(x) = x  => y ⊕ x  (CNOT: kontrola x=q0, cilj y=q1)
    qc.cx(0, 1)
    return qc


def constant_oracle(qc):
    # f(x) = 1  
    qc.id(1)
    return qc


# ----------------------------------------------------------------------------
# KORAK 1: INICIJALIZACIJA 
# ----------------------------------------------------------------------------
qc = QuantumCircuit(2, 1)
qc.barrier()
q.show_bloch_sphere(qc)  # početno stanje oba kubita: |00⟩

# ----------------------------------------------------------------------------
# KORAK 2: PRIPREMA POMOĆNOG KUBITA q1 U |1⟩
# Napomena: u ket redosledu |q1 q0⟩, posle X na q1 fizičko stanje je |10⟩
# ----------------------------------------------------------------------------
qc.x(1)
qc.barrier()
q.show_bloch_sphere(qc)

# ----------------------------------------------------------------------------
# KORAK 3: H-gejt NA OBA KUBITA (priprema |+⟩ i |−⟩)
# q0: |0⟩ -> |+⟩
# q1: |1⟩ -> |−⟩
# ----------------------------------------------------------------------------
qc.h(0)
qc.h(1)
qc.barrier()
q.show_bloch_sphere(qc)

# ----------------------------------------------------------------------------
# KORAK 4: PRIMENA ORACLE-A
# ----------------------------------------------------------------------------
# qc = constant_oracle(qc)      # f(x)=1
qc = balanced_oracle(qc)    # f(x)=x
qc.barrier()
q.show_bloch_sphere(qc)

# ----------------------------------------------------------------------------
# KORAK 5: FINALNI H-gejt 
# ----------------------------------------------------------------------------
qc.h(0)
qc.h(1)
qc.barrier()
q.show_bloch_sphere(qc)

# ----------------------------------------------------------------------------
# KORAK 6: MERENJE PRVOG KUBITA
# ----------------------------------------------------------------------------
qc.measure(0, 0)
q.show_qc(qc)
q.show_measurement(qc)
