import warnings
from qiskit import QuantumRegister, ClassicalRegister
from qiskit import QuantumCircuit, execute, Aer, transpile, IBMQ
from qiskit.tools.monitor import job_monitor
from qiskit.circuit.library import QFT
from qiskit.visualization import plot_histogram, plot_bloch_multivector
warnings.filterwarnings("ignore", category=DeprecationWarning)
import numpy as np
import math

pi = np.pi

qc2 = QuantumCircuit(2)
# qc2.x(1)
# qc2.h(0)
# qc2.cnot(0,1)

# THIS DOESNT WORK
# qc2.h(0)
# qc2.cnot(0,1)
# qc2.h(1)
# qc2.h(0)

# qc2.x(0)
# qc2.h(0)
# qc2.cnot(0,1)
# qc2.h(1)
# qc2.h(0)

qc2.x(0)
print(qc2) 

backend = Aer.get_backend('aer_simulator') 
qc2.measure_all()
job = execute(qc2, backend, shots=10000)#run the circuit 1000000 times
counts = job.result().get_counts()#return the result counts
print(counts)

def getList(dict):
    return list(dict.keys())

umar = {0: [1,0], 1: [0,1]}

values = getList(counts)
if(len(values) == 1):
    print("They are not entangled")
else:
    abStr = values[0]
    cdStr = values[1]

    print(abStr)
    print(cdStr)

    aStr = abStr[0]
    bStr = abStr[1]
    cStr = cdStr[0]
    dStr = cdStr[1]

    a = int(aStr)
    b = int(bStr)
    c = int(cStr)
    d = int(dStr)

    haha = [a,b,c,d]

    abArr = np.array([umar[a], umar[b]])
    cdArr = np.array([umar[c], umar[d]])
    print("lol")
    print(abArr)
    print(cdArr)
    print("jahja")
    tensorprod_ab= np.tensordot(abArr[0], abArr[1], axes=0)
    tensorprod_cd = np.tensordot(cdArr[0], cdArr[1], axes=0)
    print(type(tensorprod_ab))
    print(tensorprod_cd)

    print("final")
    column = np.add(tensorprod_ab,tensorprod_cd)
    print(column)
    abVal = column[0]
    cdVal = column[1]
    aVal = abVal[0]
    bVal = abVal[1]
    cVal = cdVal[0]
    dVal = cdVal[1]

    if(aVal*dVal!=bVal*cVal):
        print("They are entangled")
    else:
        print("Not Entangled")

    print("first " + values[0])
    print("second " + values[1])

def set_measure_x(circuit, n):
    for num in range(n):
        circuit.h(num)


def measure_x(circuit, qubitIndexes):
    cBitIndex = 0
    for index in qubitIndexes:
        circuit.h(index)
        circuit.measure(index, cBitIndex)
        cBitIndex+=1
    return circuit
    
def set_measure_y(circuit, n):
    for num in range(n):
        circuit.sdg(num)
        circuit.h(num)

def qft_rotations(circuit, n):
    #if qubit amount is 0, then do nothing and return
    if n == 0:
        #set it to measure the x axis
        # set_measure_x(qc, 4)
        # qc.measure_all()
        return circuit
    n -= 1
    circuit.h(n)
    for qubit in range(n):
        circuit.cp(pi/2**(n-qubit), qubit, n)
    return qft_rotations(circuit, n)

backend = Aer.get_backend('aer_simulator') 
qc = QuantumCircuit(2)

qc.x(0)
qc.x(1)
print(qc)

after = qft_rotations(qc,2)#call the recursive qft method
print(after)
#set it to measure the x axis
after.measure_all()
job = execute(after, backend, shots=1000)#run the circuit 1000000 times
counts = job.result().get_counts()#return the result counts
print(counts)
print(max(counts, key=counts.get))

def qft_dagger(qc, n):
    for j in range(n):
        for m in range(j):
            qc.cp(math.pi/float(2**(j-m)), m, j)
        qc.h(j)

qc = QuantumCircuit(2)

# qc.x(0)
qc.x(0)
qc.x(1)
qc.measure_all()
print(qc)

qft_dagger(qc,2)#call the recursive qft method

backend = Aer.get_backend('aer_simulator') 
job = execute(qc, backend, shots=1000000)#run the circuit 1000000 times
counts = job.result().get_counts()
print(counts)#return the result counts
highest = max(counts, key=counts.get)
reverse = highest[::-1]
print(reverse)
print(type(highest))
print(str(highest))

lol3 = [0,1]
yes1 = [0,0,0,0]
for i in lol3:
    qc.x(i)
    yes1[i] = 1
answer = ''.join(map(str,yes1))
print(answer)

if (reverse == answer):
    print("True")
else:
    print("False")

#OTHER QFT 
def qft_rotations(circuit, n):
    """Performs qft on the first n qubits in circuit (without swaps)"""
    if n == 0:
        # set_measure_x(qc, 3)
        # set_measure_y(qc,3)
        # qc.measure_all()
        return circuit
    n -= 1
    circuit.h(n)
    for qubit in range(n):
        circuit.cp(pi/2**(n-qubit), qubit, n)
    # At the end of our function, we call the same function again on
    # the next qubits (we reduced n by one earlier in the function)
    qft_rotations(circuit, n)

# Let's see how it looks:
qc = QuantumCircuit(3)
qc.x(0)
qc.x(2)

lol= [0,0,0]

print(lol)
qft_rotations(qc,3)
qc.measure_all()
qc.draw()

# set_measure_x(qc, 3)
backend = Aer.get_backend('aer_simulator') 
job = execute(qc, backend, shots=2048)
counts = job.result().get_counts()
print(counts)



def swap_registers(circuit, n):
    for qubit in range(n//2):
        circuit.swap(qubit, n-qubit-1)
    return circuit

def qft(circuit, n):
    """QFT on the first n qubits in circuit"""
    qft_rotations(circuit, n)
    swap_registers(circuit, n)
    return circuit

# Let's see how it looks:
qc = QuantumCircuit(2)
qc.x(0)
qc.x(1)
qft(qc,2)

qc.draw()

def inverse_qft(circuit, n):
    """Does the inverse QFT on the first n qubits in circuit"""
    # First we create a QFT circuit of the correct size:
    qft_circ = qft(QuantumCircuit(n), n)
    # Then we take the inverse of this circuit
    invqft_circ = qft_circ.inverse()
    # And add it to the first n qubits in our existing circuit
    circuit.append(invqft_circ, circuit.qubits[:n])
    return circuit.decompose() # .decompose() allows us to see the individual gates
    
nqubits = 3
number = 5
qc = QuantumCircuit(nqubits)
for qubit in range(nqubits):
    qc.h(qubit)
qc.p(number*pi/4,0)
qc.p(number*pi/2,1)
qc.p(number*pi,2)

qc.draw()

qc = inverse_qft(qc, nqubits)
qc.measure_all()
qc.draw()

backend = Aer.get_backend('aer_simulator') 
shots = 2048
transpiled_qc = transpile(qc, backend, optimization_level=3)
# transpiled_qc = transpile(qc, backend)
job = backend.run(transpiled_qc, shots=shots)
job_monitor(job)
counts = job.result().get_counts()
print(counts)

pip install hypothesis

import unittest
import hypothesis.strategies as st
from hypothesis import given, settings

# @st.composite
# def qubit_combos(draw):
#     qubit_string = draw(st.sampled_from(['000','001','010','011','100','101','110','111']))
#     return int(qubit_string)

# qubit_combos().example()

@st.composite
def circuit(draw):
    nQubits = [0,1,2,3]
    nQubitsLength = len(nQubits)
    n= draw(st.sampled_from(nQubits))
    x1 = []
    x1.append(n)
    nQubits.remove(n)
    m = draw(st.sampled_from(nQubits))
    x1.append(m)
    nQubits.remove(m)
    # l = draw(st.sampled_from(nQubits))
    # x1.append(l)
    # nQubits.remove(l)
    noOfRegisters = nQubitsLength
    return [x1,noOfRegisters]
print(circuit().example())
# ex = circuit().example()
# print(type(ex[0]))
# print(type(ex[1]))
# print(type(ex))

# lol = [1,2,0,4,6,0]
# for i in lol:
#     if i == 0: 
#         lol.remove(0)
# print(lol)

lol2 = [0,1]
yes = [0,0,0,0]
qc = QuantumCircuit(4)
for i in lol2:
    qc.x(i)
    yes[i] = 1
answer = ''.join(map(str,yes))
# strings = [str(integer) for integer in yes]
# a_string = "".join(strings)
# an_integer = int(a_string)
print (qc)
# print(an_integer)
print(answer)

qc.measure_all()
qft_dagger(qc,4)
backend = Aer.get_backend('aer_simulator') 
job = execute(qc, backend, shots=2048)
counts = job.result().get_counts()
inverseOutput = max(counts, key=counts.get)
print(inverseOutput)
reverse = inverseOutput[::-1]
print(reverse)

if(reverse == answer):
    print("They are equal")


@given(circuit())
@settings(deadline=None)
def test_inverse_holds_true(xList):
    x1 = xList[0]
    noOfRegisters = xList[1]
    baseQubit = [0,0,0,0]
    qc= QuantumCircuit(noOfRegisters)
    for i in x1:
        qc.x(i)
        baseQubit[i] = 1
    iQFT = ''.join(map(str,baseQubit))
    qc.measure_all()
    qft_dagger(qc,noOfRegisters)
    backend = Aer.get_backend('aer_simulator') 
    job = execute(qc, backend, shots=2048)
    counts = job.result().get_counts()
    inverseOutput = max(counts, key=counts.get)
    reverse = inverseOutput[::-1]
    assert(reverse == iQFT)   

if __name__ == "__main__":
    test_inverse_holds_true()

_bits_types = dict() 
bits_template = """"""

def mk_bits( nbits ):
  assert nbits > 0, "We don't allow Bits0"
  # assert nbits < 512, "We don't allow bitwidth to exceed 512."
  if nbits not in _bits_types:
    custom_exec(compile( bits_template.format(nbits), filename=f"Bits{nbits}", mode="exec" ),
                globals(), locals() )
  return _bits_types[nbits]


def custom_exec( prog, _globals, _locals ):
    assert _globals is not None
    assert _locals  is not None

    norm_globals = _globals
    norm_locals  = _locals
    if _globals is not None:
      norm_globals = _normalize_dict( _globals )
    if _locals is not None:
      norm_locals  = _normalize_dict( _locals )

    exec( prog, norm_globals, norm_locals )
    # Local may have more stuff generated by the code.
    # We need to put this back to the original _locals
    if _locals is not None:
      _locals.update( norm_locals )

def bits( nbits, signed=False, min_value=None, max_value=None ):
  BitsN = mk_bits( nbits )

  if (min_value is not None or max_value is not None) and signed:
    raise ValueError("bits strategy currently doesn't support setting "
                     "signedness and min/max value at the same time")

  if min_value is None:
    min_value = (-(2**(nbits-1))) if signed else 0
  if max_value is None:
    max_value = (2**(nbits-1)-1)  if signed else (2**nbits - 1)

  strat = st.booleans() if nbits == 1 else st.integers( min_value, max_value )

  @st.composite
  def strategy_bits( draw ):
    return BitsN( draw( strat ) )
    
  strategy_bits().example()

  return strategy_bits().example()

bits(3).example()

@st.composite
def list_and_index(draw, elements=st.integers()):
    xs = draw(st.lists(elements, min_size=1))
    i = draw(st.integers(min_value=0, max_value=len(xs) - 1))
    return (xs, i)

list_and_index().example()