path_to_file = '/content/drive/My Drive/Colab Notebooks/Quantum_Compute/'

from google.colab import drive
drive.mount('/content/drive/')


###### can we visualize the period 

import math 
import matplotlib.pyplot as plt 

N = 21 #15 
a_1 = 5 #13 # starting co-prime
a_2 = 11


x = list(range(N))
K_1 = [a_1**r % N for r in x]
K_2 = [a_2**r % N for r in x]
# K_3 = [a_3**r % N for r in x]

period = x[K_2[1:].index(1) + 1]

print ('period of the function: ', x[K_1[1:].index(1) + 1]) # (index of the 1 after the first 1) + 1
print ('period of the function: ', x[K_2[1:].index(1) + 1])


fig=plt.figure(figsize=(15, 5))
fig.add_subplot(121)
plt.plot(x, K_1, label='a = %d'%(a_1))
plt.xlabel('x', fontsize=14)
plt.ylabel(r'$a^x\, (\mathrm{mod}\, N)$', fontsize=14)
plt.legend(fontsize=12)
fig.add_subplot(122)
plt.plot(x, K_2, linestyle='--', color='purple', alpha=0.6, label='a = %d'%(a_2))
plt.axvline(x=period, lw=0.8, ls='--', color='magenta', alpha=0.6, label='Period=%d'%(period))
plt.axvline(x=2*period, lw=0.8, ls='--', color='magenta', alpha=0.6)
plt.xlabel('x', fontsize=14)
# plt.legend(fontsize=12)
# fig.add_subplot(133)
# plt.plot(x, K_3, linestyle=':', color='lime', alpha=0.6, label='a = 8')
# plt.xlabel('x', fontsize=12)

plt.legend(fontsize=12)
plt.savefig(path_to_file +'/Period_Mod_N.png', dpi=250)
plt.show()


# print ('period of the function: ', x[K_3[1:].index(1) + 1])

import numpy as np

for y in range(15):
  exp = np.exp(-1j*2*np.pi*(3*y/16)) + np.exp(-1j*2*np.pi*(7*y/16)) + np.exp(-1j*2*np.pi*(11*y/16)) + np.exp(-1j*2*np.pi*(15*y/16))
  if abs(exp) < 1e-10:
    exp = 0
  print(exp, y, abs(exp))    

# !pip install qiskit

import qiskit as q
import numpy as np

import matplotlib.pyplot as plt

def c_amod15(a, power):
    """Controlled multiplication by a mod 15"""
    if a not in [2,4,7,8,11,13]:
        raise ValueError("'a' must be 2,4,7,8,11 or 13")
    U = q.QuantumCircuit(4)        
    for iteration in range(power):
        if a in [2,13]:
            U.swap(0,1)
            U.swap(1,2)
            U.swap(2,3)
        if a in [7,8]:
            U.swap(2,3)
            U.swap(1,2)
            U.swap(0,1)
        if a in [4, 11]:
            U.swap(1,3)
            U.swap(0,2)
        if a in [7,11,13]:
            for i in range(4):
                U.x(i)
    U = U.to_gate()
    U.name = "%i^%i mod 15" % (a, power)
    c_U = U.control()
    return c_U

def qft_dagger(n):
    """n-qubit QFTdagger the first n qubits in circ"""
    qc = q.QuantumCircuit(n)
    # Don't forget the Swaps!
    for qubit in range(n//2):
        qc.swap(qubit, n-qubit-1)
    for j in range(n):
        for m in range(j):
            qc.cp(-np.pi/float(2**(j-m)), m, j)
        qc.h(j)
    qc.name = "QFT†"
    return qc

n_count = 8
a = 7
# Create QuantumCircuit with n_count counting qubits
# plus 4 qubits for U to act on
qc = q.QuantumCircuit(n_count + 4, n_count)

# # Initialize counting qubits
# # in state |+>
for i in range(n_count):
    qc.h(i)
    
# # And auxiliary register in state |1>
qc.x(3+n_count)

# # Do controlled-U operations
for k in range(n_count):
    qc.append(c_amod15(a, 2**k), 
             [k] + [i+n_count for i in range(4)])

# # Do inverse-QFT
qc.append(qft_dagger(n_count), range(n_count))

# # Measure circuit
qc.measure(range(n_count), range(n_count))
qc.draw()  # -1 means 'do not fold'        

