%pip install qiskit

%pip install pylatexenc
%pip install matplotlib

#initialization
import matplotlib.pyplot as plt
import numpy as np

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, BasicAer, execute, IBMQ, assemble, Aer, transpile
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_histogram, plot_state_qsphere, plot_bloch_multivector, plot_bloch_vector
import getpass

def hash_oracle(qc, qubits, clause_qubits, output, hash_value):
    
    # Prepares the circuit to "compare" the clause_qubits to the hash_value
    exp = 0
    while exp < len(clause_qubits):
        if not hash_value & (1 << exp) > 0:
            qc.x(clause_qubits[exp])
        exp += 1
    qc.barrier()
    
    # XORs every two qubits and XORs the result of that into one of the clause_qubits
    for i in range(0, len(qubits), 1):
        qc.cx(qubits[i], clause_qubits[i % len(clause_qubits)])
        qc.cx(qubits[(i + 1) % len(qubits)], clause_qubits[i % len(clause_qubits)])
    
    # Checks if all the clause_qubits are satisfied
    qc.mct(clause_qubits, output)
    
    # Repeat all the steps to ensure that clause_qubits are |0000>
    for i in range(0, len(qubits), 1):
        qc.cx(qubits[i], clause_qubits[i % len(clause_qubits)])
        qc.cx(qubits[(i + 1) % len(qubits)], clause_qubits[i % len(clause_qubits)])
    
    qc.barrier()
    exp = 0
    while exp < len(clause_qubits):
        if not hash_value & (1 << exp) > 0:
            qc.x(clause_qubits[exp])
        exp += 1

def diffuser(n):
    qc = QuantumCircuit(n)
    
    for qubit in range(n):
        qc.h(qubit)

    for qubit in range(n):
        qc.x(qubit)

    qc.h(n-1)
    qc.mct(list(range(n-1)), n-1)
    qc.h(n-1)

    for qubit in range(n):
        qc.x(qubit)
        
    for qubit in range(n):
        qc.h(qubit)

    d = qc.to_gate()
    d.name = "Diffuser"
    return d

n_qubits = 4
iterations = int(n_qubits ** (1/2))
hash_value = 10

var_qubits = QuantumRegister(n_qubits, name='v')
clause_qubits = QuantumRegister(4, name='c')  # bits to store clause-checks
output_qubit = QuantumRegister(1, name='out')
cbits = ClassicalRegister(n_qubits, name='cbits')
qc = QuantumCircuit(var_qubits, clause_qubits, output_qubit, cbits)

# Initialize the output qubit to |->
qc.initialize([1, -1]/np.sqrt(2), output_qubit)

# Initialize the qubits to the state |s> (intially the equal superposition or |+> state)
qc.h(var_qubits)
qc.barrier()

# Repeat the process _iterations_ times
for iteration in range(iterations):
    # Apply our oracle
    hash_oracle(qc, var_qubits, clause_qubits, output_qubit, hash_value)
    qc.barrier()
    # Apply the diffuser
    qc.append(diffuser(n_qubits), list(range(n_qubits)))

# Measure the variable qubits
qc.measure(var_qubits, cbits)

qc.draw(fold=-1)


aer_simulator = Aer.get_backend('aer_simulator')
transpiled_qc = transpile(qc, aer_simulator)
qobj = assemble(transpiled_qc)
result = aer_simulator.run(qobj).result()
plot_histogram(result.get_counts())