# Bernstein-Vazirani Algorithm

from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator

def bernstein_vazirani_oracle(n, s):
    # n+1 qubits: qubits 0..n-1 are inputs, qubit n is the ancilla
    oracle = QuantumCircuit(n + 1)

    for i in range(n):          # loop over each bit position of s
        if s[i] == '1':         # only act where s has a '1' — zero bits contribute nothing
            oracle.cx(i, n)     # CNOT: control=xᵢ, target=ancilla → triggers phase kickback

    return oracle

def bernstein_vazirani_circuit(n, s):
    # n+1 quantum qubits, n classical bits for measurement
    qc = QuantumCircuit(n + 1, n)

    # ── Step 1: flip ancilla |0⟩ → |1⟩ ──────────────────────────────────────
    # Must be |1⟩ so that H turns it into |−⟩ = (|0⟩−|1⟩)/√2
    # Only |−⟩ enables phase kickback in the oracle
    qc.x(n)

    # ── Step 2: Hadamard on ALL qubits ───────────────────────────────────────
    # Input qubits: |0⟩ → superposition over all 2ⁿ inputs
    # Ancilla:      |1⟩ → |−⟩  (ready for phase kickback)
    for i in range(n + 1):
        qc.h(i)

    # ── Step 3: apply oracle ──────────────────────────────────────────────────
    # Encodes s into phases: each |x⟩ picks up phase (−1)^(s·x)
    oracle = bernstein_vazirani_oracle(n, s)
    qc.compose(oracle, inplace=True)

    # ── Step 4: Hadamard on INPUT qubits only ─────────────────────────────────
    # Interference: all |z⟩ with z ≠ s cancel out; only |s⟩ survives
    for i in range(n):
        qc.h(i)

    # ── Step 5: measure ───────────────────────────────────────────────────────
    # State is now exactly |s⟩ → always reads out s with probability 1
    for i in range(n):
        qc.measure(i, i)

    return qc


n = int(input("Enter number of input qubits: "))
s = input(f"Enter hidden bitstring of length {n}: ")

qc = bernstein_vazirani_circuit(n, s)
print(qc.draw())

# Run the circuit 1024 times on the simulator
sim = AerSimulator()
result = sim.run(qc, shots=1024).result()
counts = result.get_counts()

# All 1024 shots produce the same result — BV is deterministic
print("Measurement counts:", counts)

# Qiskit outputs bits in little-endian order, so reverse to get s
recovered = list(counts.keys())[0][::-1]
print("Recovered hidden string:", recovered)




# Deutsch–Jozsa Algorithm


from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
import random

# -------- ORACLE --------
def deutsch_jozsa_oracle(n, case):
    oracle = QuantumCircuit(n + 1)

    # Case 1: constant 0 — do nothing
    if case == 1:
        pass

    # Case 2: constant 1 — flip ancilla
    elif case == 2:
        oracle.x(n)

    # Case 3: balanced
    elif case == 3:
        # Randomly pick half of all 2^n inputs to map to f(x) = 1
        for x in random.sample(range(2**n), 2**(n-1)):

            # Find which bit positions in x are 0
            zeros = []
            for i in range(n):
                bit = (x >> i) & 1
                if bit == 0:
                    zeros.append(i)

            # Flip 0-bits to 1 so MCX can fire, then unflip after
            if zeros:
                oracle.x(zeros)
            oracle.mcx(list(range(n)), n)
            if zeros:
                oracle.x(zeros)

    return oracle

# -------- CIRCUIT --------
def deutsch_jozsa_circuit(n, case):
    qc = QuantumCircuit(n + 1, n)

    # Step 1: prepare |0...01>
    qc.x(n)

    # Step 2: superposition over all qubits
    for i in range(n + 1):
        qc.h(i)

    # Step 3: apply oracle
    oracle = deutsch_jozsa_oracle(n, case)
    qc.compose(oracle, inplace=True)

    # Step 4: interference on input qubits only
    for i in range(n):
        qc.h(i)

    # Step 5: measure input qubits
    for i in range(n):
        qc.measure(i, i)

    return qc


# -------- USER INPUT --------
n = int(input("Enter number of input qubits (recommended: 2 to 4): "))

print("Choose a function f(x):")
print("1 → constant 0")
print("2 → constant 1")
print("3 → balanced")

case = int(input("Enter 1, 2, or 3: "))


# -------- RUN --------
qc = deutsch_jozsa_circuit(n, case)
print(qc.draw())


# -------- SIMULATE --------
sim = AerSimulator()
result = sim.run(qc, shots=1024).result()
counts = result.get_counts()
print(counts)

# -------- DECISION --------
if list(counts.keys())[0] == '0' * n:
    print("Function is CONSTANT")
else:
    print("Function is BALANCED")


# Deutsch Algorithm

from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator

def deutsch_function(case):
    
    oracle = QuantumCircuit(2)

    # Case 1 : f(0) = 0; f(1)=0 (constant)
    if case == 1:
        pass

    # Case 2 : f(0) = 0; f(1)=1 (balanced)
    elif case == 2:
        oracle.cx(0,1)

    # Case 3 : f(0) =1; f(1)=0  (balanced)
    elif case == 3:
        oracle.cx(0,1)
        oracle.x(1)

    # Case 4 : f(0) = 1; f(1)=1 (constant)
    elif case == 4:
        oracle.x(1)

    return oracle


def deutsch_algorithm(oracle):

    qc = QuantumCircuit(2,1)

    # Step 1: prepare |01>
    qc.x(1)

    # Step 2: create superposition
    qc.h(0)
    qc.h(1)

    # Step 3: apply oracle
    qc.compose(oracle, inplace=True)

    # Step 4: interference
    qc.h(0)

    # Step 5: measure
    qc.measure(0,0)

    return qc


oracle = deutsch_function(3)

qc = deutsch_algorithm(oracle)

print(qc.draw())

sim = AerSimulator()

result = sim.run(qc, shots=1).result()

counts = result.get_counts()

print(counts)

if '0' in counts:
    print("Function is CONSTANT")

if '1' in counts:
    print("Function is BALANCED")


# Quantum Teleportation using Qiskit


from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram
from qiskit.compiler import transpile
import numpy as np

# Create circuit (3 qubits, 2 classical bits)
qc = QuantumCircuit(3, 2)



# ----------------------------
# 1. Prepare the state to teleport
# ----------------------------
theta = np.pi/4
qc.ry(theta, 0)   # arbitrary state on qubit 0




# ----------------------------
# 2. Create entanglement
# ----------------------------
qc.h(1)
qc.cx(1, 2)



# ----------------------------
# 3. Bell measurement (Alice)
# ----------------------------
qc.cx(0, 1)
qc.h(0)

qc.measure(0, 0)
qc.measure(1, 1)


# ----------------------------
# 4. Conditional correction (Bob)
# ----------------------------
qc.cx(1, 2)
qc.cz(0, 2)



# ----------------------------
# Simulation
# ----------------------------
sim = AerSimulator()
compiled = transpile(qc, sim)
result = sim.run(compiled, shots=1024).result()
counts = result.get_counts()
print(counts)




# Quantum Entanglement


from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.compiler import transpile

# Create 2 qubits and 2 classical bits
circuit = QuantumCircuit(2, 2)

# Step 1: Superposition
circuit.h(0)

# Step 2: Entanglement
circuit.cx(0, 1)
# Measure both qubits
circuit.measure([0,1], [0,1])
# Draw the circuit
circuit.draw()
simulator = AerSimulator()
compiled_circuit = transpile(circuit, simulator)

job = simulator.run(compiled_circuit, shots=1000)
result = job.result()
counts = result.get_counts()
counts


simulator = AerSimulator()
compiled_circuit = transpile(circuit, simulator)

job = simulator.run(compiled_circuit, shots=1000)
result = job.result()
counts = result.get_counts()

counts


from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.compiler import transpile

circuit=QuantumCircuit(2,2)
circuit.h(0)
circuit.cx(0,1)
circuit.measure([0,1],[0,1])
simulator=AerSimulator()
compiled_circuit=transpile(circuit,simulator)

job=simulator.run(compiled_circuit,shots=1000)


result=job.result()
counts=result.get_counts()
print(counts)

from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.compiler import transpile
circuit=QuantumCircuit(2,2)
circuit.h(0)
circuit.cx(0,1)
circuit.measure([0,1],[0,1])
circuit.draw()

simulator = AerSimulator()
compiled_circuit = transpile(circuit, simulator)
job = simulator.run(compiled_circuit, shots=1000)
result = job.result()
counts = result.get_counts()
counts

simulator = AerSimulator()
compiled_circuit = transpile(circuit, simulator)

job = simulator.run(compiled_circuit, shots=1000)
result = job.result()
counts = result.get_counts()
counts



#Quantum Coin Flip using Qiskit 


# Import required modules
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.compiler import transpile

simulator = AerSimulator()

# Create a quantum circuit with:
# 1 qubit  (quantum bit)
# 1 classical bit (to store measurement result)
circuit = QuantumCircuit(1, 1) #QuantumCircuit(num_qubits, num_classical_bits)



# Apply Hadamard gate
circuit.h(0)


# Measure qubit into classical bit
circuit.measure(0, 0)


# Draw circuit
circuit.draw()

# Compile circuit for simulator
compiled_circuit = transpile(circuit, simulator)



# Run experiment 1000 times
job = simulator.run(compiled_circuit, shots=1000)


# Retrieve results
result = job.result()


# Get measurement counts
counts = result.get_counts()

print(counts)


