%matplotlib inline
import os
import shutil
import numpy as np
import pandas as pd
import random 
import matplotlib.pyplot as plt
import seaborn as sns
from string import ascii_lowercase
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile
from qiskit.quantum_info import Statevector
from qiskit.visualization import array_to_latex, plot_distribution, plot_histogram, plot_bloch_multivector
from qiskit_algorithms import AmplificationProblem, Grover
from qiskit.circuit.library import CPhaseGate
from qiskit.primitives import Sampler
from qiskit_aer import AerSimulator
from utils import get_image_path

def random_file_name(size=5):
    return ''.join(random.sample(list(ascii_lowercase), size))

def create_test_folder(folder):
    if(os.path.exists(folder)):
        print("Path already exists")
        print(f"Removing {folder}")
        shutil.rmtree(folder, ignore_errors=True)
    
    print(f"Creating: {folder}")
    os.mkdir(folder)
        
def create_test_files(folder, n=5):
    create_test_folder(folder)
    for _ in range(n):
        file = random_file_name()+".txt"
        
        try:
            print(f"creating file: {file}")
            file_path = os.path.join(folder, file)

            file = open(file_path, "a")
            file.close()
        
        except Exception as error:
            print(f"Failed creating {file}")


test_folder = os.path.join('.', 'files-test')
create_test_files(test_folder)

files = os.listdir(test_folder)
files

selected_file = files[random.randint(0,len(files))]
selected_file

def classical_method_1(files, target):
    #in this example, we are ignoring the extension
    #just for this case, in real life a good idea is to use the entire name, or a hash of it

    #unsing no type of lookup table, or sorting, the algorithm takes O(n)
    total_tests = 0
    for file in files:
        total_tests += 1
        print(f"total tests: {total_tests}")
        if(file == target):
            print("found file")
            break

classical_method_1(files, selected_file)

def classical_method_2(files, target):

    # this should be done after adding a file into the filesystem
    # it takes O(1) for adding a new file, O(n) for adding n files and O(1) for searching
    lut = {}
    for file in files:
        file_hash = hash(file)
        lut[file_hash] = file

    target_hash = hash(target)
    if lut.get(target_hash):
        print("File found")
    else:
        print("File doesnt exists in the filesystem")
        
classical_method_2(files, selected_file)

n_qubits = int(np.ceil(np.log2(len(files))))

def encoding_files_to_oracle(n_qubits, files):
    oracle = QuantumCircuit(n_qubits, name="LUT")

    encoded_bin = [bin(i)[2:].zfill(n_qubits)[::-1] for i in range(len(files))]

    for encoded in encoded_bin:
        x_positions = [i for i,char in enumerate(encoded) if char == '0']
        oracle.x(x_positions)
        oracle.mcp(np.pi, list(range(1,n_qubits)), 0)
        oracle.x(x_positions)

    return encoded_bin,oracle

binary,oracle = encoding_files_to_oracle(n_qubits,files)
oracle.draw('mpl', filename=get_image_path("look-up-table.png"))

def get_oracle_overload(files, targets):
    oracle = QuantumCircuit(n_qubits, name="$LUT^{-1}$")

    encode = lambda x,n: bin(x)[2:].zfill(n)[::-1]
    encoded_bin = [encode(i,n_qubits) for i, file in enumerate(files) if file not in targets]

    for encoded in encoded_bin:
        x_positions = [i for i,char in enumerate(encoded) if char == '0']
        oracle.x(x_positions)
        oracle.mcp(np.pi, list(range(1,n_qubits)), 0)
        oracle.x(x_positions)

    return encoded_bin,oracle

lut_binary,oracle = encoding_files_to_oracle(n_qubits,files)
unvalid_states,oracle_inv = get_oracle_overload(files, [selected_file])

good_state = list(set(lut_binary) - set(unvalid_states))

n_qubits = len(oracle.qubits)

qc = QuantumCircuit(n_qubits)
qc.append(oracle, range(n_qubits))
qc.barrier(label="LUT")
qc.append(oracle_inv, range(n_qubits))
qc.draw('mpl', filename=get_image_path("sets-difference-look-up-table-oracle.png"))

qc.decompose().draw('mpl')

Statevector(qc).draw('latex')

qc_copy = qc.copy()
qc_copy.save_unitary()

sim = AerSimulator()
unitary = sim.run(transpile(qc_copy, sim)).result().get_unitary()
array_to_latex(unitary, max_size=10000)

problem = AmplificationProblem(qc, is_good_state=good_state)
problem.grover_operator.decompose().draw(output='mpl', filename=get_image_path("classical_grover_file_search.png"))

grover = Grover(sampler=Sampler())
result = grover.amplify(problem)
result.top_measurement

qc = QuantumCircuit(3)
qc.h(range(3))
qc.compose(problem.grover_operator.decompose(), range(3), inplace=True)
qc.draw('mpl', filename=get_image_path("file_explorer.png"))

sim = AerSimulator()
qc_copy = qc.copy()
qc_copy.measure_all()
aer_result = sim.run(transpile(qc_copy, sim), shots=1000).result().get_counts()
plot_histogram(aer_result, filename=get_image_path('AER_file_explorer_result.png'))

def create_new_grover(file):
    n_qubits = int(np.ceil(np.log2(len(files))))
    lut_binary, oracle = encoding_files_to_oracle(n_qubits,files)
    unvalid_states,oracle_inv = get_oracle_overload(files, [file])

    
    good_state = list(set(lut_binary) - set(unvalid_states))
    print('good state: ', good_state)
    t_qubits = len(oracle.qubits)

    final_oracle = QuantumCircuit(t_qubits)
    final_oracle.append(oracle, range(t_qubits))
    final_oracle.barrier(label="LUT")
    final_oracle.append(oracle_inv, range(t_qubits))


    total_zeros = good_state[0].count('0')
    """
    angles from the grover tests
    000		1.013620
    001		1.385071
    010		1.479508
    011		1.680972
    100		1.397662
    101		1.712451
    110		1.699860
    111		2.058719
    """
    angles = {
        "000":1.013620,
        "001":1.385071,
        "010":1.479508,
        "011":1.680972,
        "100":1.397662,
        "101":1.712451,
        "110":1.699860,
        "111":2.058719
    }


    problem = AmplificationProblem(final_oracle, is_good_state=good_state)
    
    qc = QuantumCircuit(t_qubits)
    qc.ry(angles[good_state[0]], range(t_qubits))
    qc.compose(problem.grover_operator.decompose(), range(t_qubits), inplace=True)
    
    return qc

new_qc = create_new_grover(selected_file)
new_qc.draw('mpl', filename=get_image_path("improved_file_explorer.png"))

sim = AerSimulator()
qc_copy = new_qc.copy()
qc_copy.measure_all()
aer_result = sim.run(transpile(qc_copy, sim), shots=1000).result().get_counts()
hist = plot_histogram(aer_result)
plt.savefig(get_image_path("AER-file-explorer-hist-new-grover-mapped-rotations.png"))
display(hist)

x = np.array(list(range(0, 60)), dtype=np.int64)

classical_find = lambda x: x
classical_lut = lambda x: 1
quantum = lambda x: np.sqrt(x)

vec_cf = np.vectorize(classical_find)
vec_cl = np.vectorize(classical_lut)
vec_q = np.vectorize(quantum)

y_classical_f = vec_cf(x)
y_classical_l = vec_cl(x)
y_quantum = vec_q(x)

df = pd.DataFrame({"x":x, "cf":y_classical_f, "cl":y_classical_l, "q":y_quantum})
df.head()

plot = sns.lineplot(data=pd.melt(df, ["x"]), x="x", y="value", hue="variable", palette="flare")

legend = plot.axes.get_legend()
legend.set_title("O(x) comparation")
for old, new in zip(legend.texts, ["classical find = O(n)", "classical LUT = O(1)", "quantum = O(√N)"]):
    old.set_text(new)

    
plt.savefig(get_image_path("algorithms-finding-comparation.png"))
plt.show(plot)

def get_result(qc, sim=AerSimulator()):
    g_copy = qc.copy()
    g_copy.measure_all()
    sim = AerSimulator()
    return sim.run(transpile(g_copy, sim), shots=1000).result().get_counts()

def new_g(superposition, oracle):
    qc = QuantumCircuit(3)
    qc.append(superposition, range(3))
    qc.append(oracle, range(3))
    qc.h(range(3))
    qc.x(range(3))
    qc.h(-1)
    qc.ccx(0, 1,2)
    qc.h(-1)
    qc.x(range(3))
    qc.h(range(3))
    return qc

oracle = QuantumCircuit(3, name="oracle")
oracle.ccz(0, 1,2)
oracle.draw('mpl')

s = QuantumCircuit(3, name="s")
s.ry(np.pi/3, range(3))
display(s.draw('mpl'))
plot_bloch_multivector(s)

qc = new_g(s, oracle)
qc.draw('mpl')

plot_histogram(get_result(qc))

oracle = QuantumCircuit(3, name="oracle")
oracle.x(range(3))
oracle.ccz(0, 1,2)
oracle.x(range(3))

oracle.draw('mpl')

qc = new_g(s, oracle)
plot_histogram(get_result(qc))

oracle = QuantumCircuit(3, name="oracle")
oracle.x(range(3))
oracle.ccz(0, 1,2)
oracle.x(range(3))
oracle.ccz(0,1,2)
oracle.x(0)
oracle.ccz(0,1,2)
oracle.x(0)

oracle.draw('mpl')

qc = new_g(s, oracle)
plot_histogram(get_result(qc))

oracle = QuantumCircuit(3, name="oracle")
oracle.x(0)
oracle.ccz(0,1,2)
oracle.x(0)
oracle.x(range(3))
oracle.ccz(0, 1,2)
oracle.x(range(3))
oracle.ccz(0,1,2)


oracle.draw('mpl')

qc = new_g(s, oracle)
plot_histogram(get_result(qc))

oracle = QuantumCircuit(3, name="oracle")
oracle.x(0)
oracle.ccz(0,1,2)
oracle.x(0)


oracle.draw('mpl')

qc = new_g(s, oracle)
plot_histogram(get_result(qc))

sim = AerSimulator()

def find_best_angle(encoded_bit_string, oracle):
    max_ = 0
    best_angle = 0
    
    for angle in np.linspace(0, np.pi, num=120):        
        s = QuantumCircuit(3)
        s.ry(angle, range(3))

        qc = new_g(s, oracle)
    
        qc.measure_all()
        result = sim.run(transpile(qc, sim), shots=1000).result().get_counts()
        if(not result.get(encoded_bit_string)):
            continue
        result = result[encoded_bit_string]
        if(result > max_):
            max_ = result
            best_angle = angle
    
        print(angle, result)
    return best_angle, max_

oracle = QuantumCircuit(3)
oracle.x(0)
oracle.ccz(0,1,2)
oracle.x(0)


best_angle, max_ = find_best_angle('110', oracle)

best_angle, max_

np.degrees(best_angle)

np.pi/4

(np.pi*11)/20

np.pi - ((np.pi*10)/18) - (np.pi/4)

qc = QuantumCircuit(1)
qc.ry(best_angle, 0)
plot_bloch_multivector(qc)

s = QuantumCircuit(3, name="S")
s.ry(best_angle, range(3))

oracle = QuantumCircuit(3, name="oracle")
oracle.x(0)
oracle.ccz(0,1,2)
oracle.x(0)

qc = new_g(s, oracle)
qc.draw('mpl')

plot_histogram(get_result(qc))

s = QuantumCircuit(3, name="S")
s.ry(best_angle, range(3))

oracle = QuantumCircuit(3, name="oracle")
oracle.ccz(0,1,2)

qc = new_g(s, oracle)

plot_histogram(get_result(qc))

s = QuantumCircuit(3, name="S")
s.ry(best_angle, range(3))

oracle = QuantumCircuit(3, name="oracle")
#oracle.ccz(0,1,2)

oracle.x(range(3))
oracle.ccz(0,1,2)
oracle.x(range(3))

oracle.x(0)
oracle.ccz(0,1,2)
oracle.x(0)

qc = new_g(s, oracle)

plot_histogram(get_result(qc))

oracle = QuantumCircuit(3, name="oracle")
oracle.ccz(0,1,2)

oracle.x(range(3))
oracle.ccz(0,1,2)
oracle.x(range(3))

oracle.x(0)
oracle.ccz(0,1,2)
oracle.x(0)

oracle.x(0)
oracle.x(1)
oracle.ccz(0,1,2)
oracle.x(0)
oracle.x(1)

qc = new_g(s, oracle)
plot_histogram(get_result(qc))

oracle = QuantumCircuit(3, name="oracle")
oracle.ccz(0,1,2)

oracle.x(range(3))
oracle.ccz(0,1,2)
oracle.x(range(3))
oracle.draw('mpl')

problem = AmplificationProblem(oracle, is_good_state=['000', '111'])
problem.grover_operator.decompose().draw(output='mpl')

grover = Grover(sampler=Sampler())
result = grover.amplify(problem)
result.circuit_results, result.iterations

qc = QuantumCircuit(3)
qc.h(range(3))
qc.compose(problem.grover_operator.decompose(), inplace=True)
qc.draw('mpl')

plot_histogram(get_result(qc))

oracle = QuantumCircuit(3, name="oracle")

oracle.x(0)
oracle.ccz(0,1,2)
oracle.x(0)

problem = AmplificationProblem(oracle, is_good_state=['110'])
problem.grover_operator.decompose().draw(output='mpl')

grover = Grover(sampler=Sampler())
result = grover.amplify(problem)
result.circuit_results, result.iterations

def p_oracle(bit_string):
    bit_string = bit_string[::-1]
    zeros = [i for i, v in enumerate(bit_string) if v == '0']
    qc = QuantumCircuit(3)
    if(zeros):qc.x(zeros)
    qc.ccz(0,1,2)
    if(zeros):qc.x(zeros)
    return qc

p_oracle('001').draw('mpl')

best_angle

def compare(best_angle):
    for b in ['000', '001', '010', '011', '100', '101', '110', '111']:
        oracle = p_oracle(b)
        
        s = QuantumCircuit(3, name="S")
        s.ry(best_angle, range(3))
        new = new_g(s, oracle)
    
        old_problem = AmplificationProblem(oracle, is_good_state=[b])
        old = QuantumCircuit(3)
        old.h(range(3))
        old.compose(old_problem.grover_operator.decompose(), inplace=True)
    
        new_r = get_result(new)
        if(not new_r.get(b)):
            new_r = 0
        else:
            new_r = new_r[b]
            
        
        old_r = get_result(old)
        if(not old_r.get(b)):
            old_r = 0
        else:
            old_r = old_r[b]
    
        print(b, ' - ', old_r,new_r)
compare(best_angle)

best_angle, _ = find_best_angle('000', p_oracle('000'))
best_angle

compare(best_angle)

qc = QuantumCircuit(1)
qc.ry(best_angle, 0)
plot_bloch_multivector(qc)

best_angle, _ = find_best_angle('010', p_oracle('010'))
best_angle

compare(best_angle)

qc = QuantumCircuit(1)
qc.ry(best_angle, 0)
plot_bloch_multivector(qc)

old = {}
new = {}
bit_strings = ['000', '001', '010', '011', '100', '101', '110', '111']

for b in bit_strings:
    oracle = p_oracle(b)

    best_angle, _ = find_best_angle(b, oracle) 
    
    s = QuantumCircuit(3, name="S")
    s.ry(best_angle, range(3))
    new_ = new_g(s, oracle)
    
    old_problem = AmplificationProblem(oracle, is_good_state=[b])
    old_ = QuantumCircuit(3)
    old_.h(range(3))
    old_.compose(old_problem.grover_operator.decompose(), inplace=True)

    new_r = get_result(new_)
    if(not new_r.get(b)):
        new_r = 0
    else:
        new_r = new_r[b]
        
    
    old_r = get_result(old_)
    if(not old_r.get(b)):
        old_r = 0
    else:
        old_r = old_r[b]

    old[b] = old_r
    new[b] = new_r

old, new

data = {"new": new, "old":old}
df = pd.DataFrame(data, columns=["new", "old"], index=bit_strings)
df

plt.plot(df.index.tolist(),df['new'], label="new")
plt.plot(df.index.tolist(),df['old'], label="old")
plt.title('old vs new Grover improvement (range [0, $\pi$])')
plt.xlabel("bit string")
plt.ylabel("counts")
plt.legend(loc="upper right")

plt.savefig(get_image_path("superposition-improvement-grover-algorithm.png"))
plt.show()


def find_best_angle_d_range(encoded_bit_string, oracle):
    max_ = 0
    best_angle = 0
    
    for angle in np.linspace(0, 2*np.pi, num=240):        
        s = QuantumCircuit(3)
        s.ry(angle, range(3))

        qc = new_g(s, oracle)
    
        qc.measure_all()
        result = sim.run(transpile(qc, sim), shots=1000).result().get_counts()
        if(not result.get(encoded_bit_string)):
            continue
        result = result[encoded_bit_string]
        if(result > max_):
            max_ = result
            best_angle = angle
    
        print(angle, result)
    return best_angle, max_

old = {}
new = {}
bit_strings = ['000', '001', '010', '011', '100', '101', '110', '111']

for b in bit_strings:
    oracle = p_oracle(b)

    best_angle, _ = find_best_angle_d_range(b, oracle) 
    
    s = QuantumCircuit(3, name="S")
    s.ry(best_angle, range(3))
    new_ = new_g(s, oracle)
    
    old_problem = AmplificationProblem(oracle, is_good_state=[b])
    old_ = QuantumCircuit(3)
    old_.h(range(3))
    old_.compose(old_problem.grover_operator.decompose(), inplace=True)

    new_r = get_result(new_)
    if(not new_r.get(b)):
        new_r = 0
    else:
        new_r = new_r[b]
        
    
    old_r = get_result(old_)
    if(not old_r.get(b)):
        old_r = 0
    else:
        old_r = old_r[b]

    old[b] = old_r
    new[b] = new_r

data = {"new": new, "old":old}
df = pd.DataFrame(data, columns=["new", "old"], index=bit_strings)
df

plt.plot(df.index.tolist(),df['new'], label="new")
plt.plot(df.index.tolist(),df['old'], label="old")
plt.title('old vs new Grover improvement (range [0, $2\pi$])')
plt.xlabel("bit string")
plt.ylabel("counts")
plt.legend(loc="upper right")

#plt.savefig(get_image_path("superposition-improvement-grover-algorithm.png"))
plt.show()

s = QuantumCircuit(3)
s.ry(np.pi/3, 0)
s.ry(np.pi/5, 1)
s.ry(np.pi/9, 2)
s.draw('mpl')

plot_bloch_multivector(s)

oracle = p_oracle('000')
qc = new_g(s, oracle)
plot_histogram(get_result(qc))

oracle = p_oracle('010')
qc = new_g(s, oracle)
plot_histogram(get_result(qc))

CCP = CPhaseGate(-np.pi/8).control(1)
oracle = QuantumCircuit(3)
oracle.append(CCP, [0, 1, 2])
oracle.draw('mpl')

qc_copy = oracle.copy()
qc_copy.save_unitary()

sim = AerSimulator()
unitary = sim.run(transpile(qc_copy, sim)).result().get_unitary()
array_to_latex(unitary, max_size=10000)



s = QuantumCircuit(3)
s.ry(1.7423959255203896, range(3))

qc = new_g(s, oracle)
plot_histogram(get_result(qc))

sim = AerSimulator()

def find_best_angle_rx(encoded_bit_string, oracle):
    max_ = 0
    best_angle = 0
    
    for angle in np.linspace(0, np.pi, num=120):        
        s = QuantumCircuit(3)
        s.rx(angle, range(3))

        qc = new_g(s, oracle)
    
        qc.measure_all()
        result = sim.run(transpile(qc, sim), shots=1000).result().get_counts()
        if(not result.get(encoded_bit_string)):
            continue
        result = result[encoded_bit_string]
        if(result > max_):
            max_ = result
            best_angle = angle
            
        #transpiled = transpile(qc, sim)
        #display(transpiled.draw('mpl'))
    
        #print(angle, result)
    return best_angle, max_

def find_best_angle_rz(encoded_bit_string, oracle):
    max_ = 0
    best_angle = 0
    
    for angle in np.linspace(0, np.pi, num=120):        
        s = QuantumCircuit(3)
        s.rz(angle, range(3))

        qc = new_g(s, oracle)
    
        qc.measure_all()
        result = sim.run(transpile(qc, sim), shots=1000).result().get_counts()
        if(not result.get(encoded_bit_string)):
            continue
        result = result[encoded_bit_string]
        if(result > max_):
            max_ = result
            best_angle = angle
            
        #transpiled = transpile(qc, sim)
        #display(transpiled.draw('mpl'))
    
        #print(angle, result)
    return best_angle, max_

def find_best_angle_ry(encoded_bit_string, oracle):
    max_ = 0
    best_angle = 0
    
    for angle in np.linspace(0, np.pi, num=120):        
        s = QuantumCircuit(3)
        s.ry(angle, range(3))

        qc = new_g(s, oracle)
    
        qc.measure_all()
        result = sim.run(transpile(qc, sim), shots=1000).result().get_counts()
        if(not result.get(encoded_bit_string)):
            continue
        result = result[encoded_bit_string]
        if(result > max_):
            max_ = result
            best_angle = angle
            
        #transpiled = transpile(qc, sim)
        #display(transpiled.draw('mpl'))
    
        #print(angle, result)
    return best_angle, max_

rx = {}
rz = {}
ry = {}
bit_strings = ['000', '001', '010', '011', '100', '101', '110', '111']

for s in bit_strings:
    oracle = p_oracle(s)
    
    angle_rx, max_rx = find_best_angle_rx(s, oracle)
    angle_rz, max_rz = find_best_angle_rz(s, oracle)
    angle_ry, max_ry = find_best_angle_ry(s, oracle)
    
    print(s, angle_rx, max_rx, angle_rz, max_rz, angle_ry, max_ry)
    rx[s] = max_rx
    rz[s] = max_rz
    ry[s] = max_ry

data = {"ry": ry, "rx": rx, "rz": rz}
df = pd.DataFrame(data, columns=("ry", "rx", "rz"), index=bit_strings)

plt.plot(df.index.tolist(),df['ry'], label="ry")
plt.plot(df.index.tolist(),df['rx'], label="rx")
plt.plot(df.index.tolist(),df['rz'], label="rz")
plt.title('test rotations')
plt.xlabel("bit string")
plt.ylabel("counts")
plt.legend(loc="upper right")

plt.savefig(get_image_path("rotations-test-grover-algorithm.png"))
plt.show()


test = QuantumCircuit(1)
test.ry(3*np.pi/2, 0)

display(test.draw('mpl'))
plot_bloch_multivector(test)

def find_best_angle_2(encoded_bit_string, oracle, values_range, sim=AerSimulator()):
    max_ = 0
    best_angle = 0
    
    for angle in values_range:        
        s = QuantumCircuit(3)
        s.ry(angle, range(3))

        qc = new_g(s, oracle)
    
        qc.measure_all()
        result = sim.run(transpile(qc, sim), shots=1000).result().get_counts()
        if(not result.get(encoded_bit_string)):
            continue
            
        result = result[encoded_bit_string]
        if(result > max_):
            max_ = result
            best_angle = angle
    
        #print(angle, result)
    return best_angle, max_

def new_compare(values_range, bit_strings=['000', '001', '010', '011', '100', '101', '110', '111']):
    old = {}
    new = {}

    total_qubits = len(bit_strings[0])
    
    for b in bit_strings:
        oracle = p_oracle(b)
    
        best_angle, _ = find_best_angle_2(b, oracle, values_range=values_range) 
        
        s = QuantumCircuit(total_qubits, name="S")
        s.ry(best_angle, range(total_qubits))

        new_ = QuantumCircuit(total_qubits)
        new_.append(s, range(total_qubits))
        new_.append(oracle, range(total_qubits))
      
        new_.h(range(total_qubits))
        new_.x(range(total_qubits))
        new_.h(-1)
        new_.mcmt(list(range(tota_qubits-1)),-1)
        new_.h(-1)
        new_.x(range(total_qubits))
        new_.h(range(total_qubits))
        
        old_problem = AmplificationProblem(oracle, is_good_state=[b])
        old_ = QuantumCircuit(total_qubits)
        old_.h(range(total_qubits))
        old_.compose(old_problem.grover_operator.decompose(), inplace=True)
    
        new_r = get_result(new_)
        if(not new_r.get(b)):
            new_r = 0
        else:
            new_r = new_r[b]
            
        
        old_r = get_result(old_)
        if(not old_r.get(b)):
            old_r = 0
        else:
            old_r = old_r[b]
    
        old[b] = old_r
        new[b] = new_r

    data = {"old": old, "new": new}
    df = pd.DataFrame(data, columns=("old", "new"), index=bit_strings)
    return df

df_0_to_pi = new_compare(np.linspace(0, np.pi, num=240))
df_0_to_pi

plt.plot(df_0_to_pi.index.tolist(),df_0_to_pi['old'], label="old")
plt.plot(df_0_to_pi.index.tolist(),df_0_to_pi['new'], label="new")
plt.xlabel("bit string")
plt.ylabel("counts")
plt.legend(loc="upper right")
plt.show()

df_0_to_2pi = new_compare(np.linspace(0, 2*np.pi, num=240))
df_0_to_2pi

plt.plot(df_0_to_2pi.index.tolist(),df_0_to_2pi['old'], label="old")
plt.plot(df_0_to_2pi.index.tolist(),df_0_to_2pi['new'], label="new")
plt.xlabel("bit string")
plt.ylabel("counts")
plt.legend(loc="upper right")
plt.show()

df_0_to_pi = new_compare(np.linspace(0, np.pi, num=240), bit_strings=['00', '01', '10', '11'])
df_0_to_pi