!pip install qiskit matplotlib

import qiskit
import matplotlib

from qiskit import QuantumCircuit, assemble, Aer, circuit, QuantumRegister, ClassicalRegister, transpile
from qiskit.visualization import plot_histogram
from qiskit.quantum_info.operators import Operator

def hash_operator():
  hash_gate = Operator([
    [0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0],
    [0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
    [0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0],
    [0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0],
    [0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0],
    [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
  ])
  hash_gate.name = "Hash Gate"
  return hash_gate

def dehash_operator():
  hash_dagger = hash_operator().conjugate().transpose()
  hash_dagger.name = "Hash Dagger"
  return hash_dagger

def oracle():
  qreg_q = QuantumRegister(4, 'q')
  oracle = QuantumCircuit(qreg_q, name='oracle')
  oracle.x(qreg_q[2:4])
  oracle.cz(qreg_q[2], qreg_q[3])
  oracle.x(qreg_q[2:4])
  oracle_op = oracle.to_gate()
  oracle_op.name = 'oracle'
  return oracle

oracle().draw()

# diffuser from https://qiskit.org/textbook/ch-algorithms/grover.html
def diffuser(nqubits):
    qc = QuantumCircuit(nqubits)
    # Apply transformation |s> -> |00..0> (H-gates)
    for qubit in range(nqubits):
        qc.h(qubit)
    # Apply transformation |00..0> -> |11..1> (X-gates)
    for qubit in range(nqubits):
        qc.x(qubit)
    # Do multi-controlled-Z gate
    qc.h(nqubits-1)
    qc.mct(list(range(nqubits-1)), nqubits-1)  # multi-controlled-toffoli
    qc.h(nqubits-1)
    # Apply transformation |11..1> -> |00..0>
    for qubit in range(nqubits):
        qc.x(qubit)
    # Apply transformation |00..0> -> |s>
    for qubit in range(nqubits):
        qc.h(qubit)
    # We will return the diffuser as a gate
    U_s = qc.to_gate()
    U_s.name = "diffusor"
    return U_s

n_qubits = 4
controls = QuantumRegister(n_qubits)
circuit = QuantumCircuit(controls)

circuit.append(hash_operator(), [0, 1, 2, 3])
circuit.append(oracle(), [0, 1, 2, 3])
circuit.append(dehash_operator(), [0, 1, 2, 3])
circuit.append(diffuser(n_qubits), [0, 1, 2, 3])

#circuit.measure_all()
circuit.draw()

n_qubits = 4
controls = QuantumRegister(n_qubits)
circuit = QuantumCircuit(controls)

#Create uniform superposition of possible inputs
circuit.h([0,1,2,3])

#Run Grover's 2^(4/2) times
circuit.append(hash_operator(), [0, 1, 2, 3])
circuit.append(oracle(), [0, 1, 2, 3])
circuit.append(dehash_operator(), [0, 1, 2, 3])
circuit.append(diffuser(n_qubits), [0, 1, 2, 3])

circuit.append(hash_operator(), [0, 1, 2, 3])
circuit.append(oracle(), [0, 1, 2, 3])
circuit.append(dehash_operator(), [0, 1, 2, 3])
circuit.append(diffuser(n_qubits), [0, 1, 2, 3])

circuit.append(hash_operator(), [0, 1, 2, 3])
circuit.append(oracle(), [0, 1, 2, 3])
circuit.append(dehash_operator(), [0, 1, 2, 3])
circuit.append(diffuser(n_qubits), [0, 1, 2, 3])

circuit.append(hash_operator(), [0, 1, 2, 3])
circuit.append(oracle(), [0, 1, 2, 3])
circuit.append(dehash_operator(), [0, 1, 2, 3])
circuit.append(diffuser(n_qubits), [0, 1, 2, 3])

circuit.measure_all()
circuit.draw()

sim = Aer.get_backend('aer_simulator') 
# We run the simulator with sim.run(QUANTUM CIRCUIT),
# And we get the resulting values with .result()
transpiled_grover_circuit = transpile(circuit, sim)
qobj = assemble(transpiled_grover_circuit)
result = sim.run(qobj).result()
# We then collect the results using .get_counts()
counts = result.get_counts()
# Visualization
plot_histogram(counts)

