%pip install qiskit
%pip install qiskit[visualization]
%pip install numpy
%pip install matplotlib

import math
from random import *
from IPython.display import Image, display, YouTubeVideo
import numpy as np
import qiskit
from qiskit import Aer, QuantumCircuit, execute, IBMQ, QuantumRegister, ClassicalRegister
from qiskit.visualization import plot_histogram, plot_bloch_multivector
qiskit.__qiskit_version__

# Initialize Qasm simulator backend
qasm = Aer.get_backend('qasm_simulator')

# Initialize Real Quantum computer
# If you run qiskit on a real computer for the first time, you will need to create an account on https://quantum-computing.ibm.com/login
# and run the following line with you token.
#IBMQ.save_account('MY_API_TOKEN')

IBMQ.load_account()
provider = IBMQ.get_provider('ibm-q')
quantum_computer = provider.get_backend('ibmq_16_melbourne')

# Here we will use the local simulator --> Aer
backend_sim = qasm  # Choose your backend : <quantum_computer> or <qasm>

YouTubeVideo('C19O5xm51dk')

Image("images_jupyter/structure.png", width="700")

attack = 0.7 # Accuracy of the attack, most of the attacks are of precision 100%, 90% or 70%
crit = 0.0417 # Critical 
burn = 0.1
poison = 0.3
para = 0.3
freeze = 0.1
speedtie = 0.5

qc = QuantumCircuit(1, 1)

qc.rx(math.pi * attack, 0) # The gate RX is like a NOT gate which allows us to define the angle theta and apply rotation
qc.measure(0, 0)

qc.draw()

job = execute(qc, backend_sim, shots=1, memory=True)
result_job = job.result().get_memory()
to_return = int(result_job[0], 2)
if to_return == 1:
    print("Result : {}, the attack succeeded!".format(to_return))
else:
    print("Result : {}, the attack missed the target!".format(to_return))

Image("images_jupyter/types_graph.png", width="500")

def diffuser(nqubits):
    qc = QuantumCircuit(nqubits)
    for qubit in range(nqubits):
        qc.h(qubit)
    for qubit in range(nqubits):
        qc.x(qubit)
    qc.h(nqubits - 1)
    qc.mct(list(range(nqubits - 1)), nqubits - 1)
    qc.h(nqubits - 1)
    for qubit in range(nqubits):
        qc.x(qubit)
    for qubit in range(nqubits):
        qc.h(qubit)
    U_s = qc.to_gate()
    U_s.name = "$Diff$"
    return U_s

algo = QuantumRegister(3, 'algo')
oracle = QuantumRegister(1, 'oracle')
c = ClassicalRegister(3, 'measurement')
qc = QuantumCircuit(algo, oracle, c)

qc.append(diffuser(3), [0, 1, 2])

qc.draw()
algo = QuantumRegister(3, 'algo')
oracle = QuantumRegister(1, 'oracle')
c = ClassicalRegister(3, 'measurement')
qc = QuantumCircuit(algo, oracle, c)

# Init
qc.h(algo)
qc.x(oracle)
qc.h(oracle)
qc.barrier()

# Algorithm
qc.mcx(algo, oracle)
qc.barrier()

# Diffuser
qc.append(diffuser(3), [0, 1, 2])

# Measure of the ouputs
qc.barrier()
qc.measure(algo, c)

qc.draw()

job = execute(qc, backend_sim, shots=512)
result_job = job.result()
plot_histogram(result_job.get_counts())

# We have our Blastoise (Tortank in French) with 4 attacks : hydroblast, earthquake, ice beam, giga-impact
# We want to map all our attacks to their respective type(s) like: 
# [hydroblast : water, earthquake : ground , ice beam : ice , giga-impact : normal]

# Init register 
qram_q = QuantumRegister(2, 'qram') # We have 4 elements to map to 2 qubits are enough
normal_q = QuantumRegister(1, 'normal')
fire_q = QuantumRegister(1, 'fire')
grass_q = QuantumRegister(1, 'grass')
ground_q = QuantumRegister(1, 'ground')
fly_q = QuantumRegister(1, 'fly')
water_q = QuantumRegister(1, 'water')
elec_q = QuantumRegister(1, 'electric')
ice_q = QuantumRegister(1, 'ice')
bug_q = QuantumRegister(1, 'bug')
steel_q = QuantumRegister(1, 'steel')
qc_init = QuantumCircuit(qram_q)

# Init qubits
qc_init.h(qram_q) # Init our inputs to superposition
#qc.barrier()

qc_qram = QuantumCircuit(qram_q, normal_q, fire_q, grass_q, ground_q, fly_q, water_q, elec_q, ice_q, bug_q, steel_q, c)

# Mapping attacks
## First attack is going to be on the address 00, hydroblast
qc_qram.x(qram_q)
qc_qram.ccx(qram_q[0], qram_q[1], water_q)
qc_qram.x(qram_q)
qc_qram.barrier()
## Second attack -> 01, earthquake
qc_qram.x(qram_q[0])
qc_qram.ccx(qram_q[0], qram_q[1], ground_q)
qc_qram.x(qram_q[0])
qc_qram.barrier()
## Third attack -> 10, ice beam
qc_qram.x(qram_q[1])
qc_qram.ccx(qram_q[0], qram_q[1], ice_q)
qc_qram.x(qram_q[1])
qc_qram.barrier()
## Fourth attack -> 11, giga impact
qc_qram.ccx(qram_q[0], qram_q[1], normal_q)
qc_qram.barrier()

qc_initQram = qc_init + qc_qram
qc_initQram.draw()

c = ClassicalRegister(8, 'measure')
qram_measure = QuantumCircuit(c)
qc = qc_initQram + qram_measure
qc.measure(range(8), range(8))
qc = qc.reverse_bits()
job = execute(qc, backend_sim, shots=512)
result_job = job.result()
plot_histogram(result_job.get_counts())

# Init type qubits
fire_q = QuantumRegister(1, 'fire')
grass_q = QuantumRegister(1, 'grass')
ground_q = QuantumRegister(1, 'ground')
fly_q = QuantumRegister(1, 'fly')
water_q = QuantumRegister(1, 'water')
elec_q = QuantumRegister(1, 'electrick')
ice_q = QuantumRegister(1, 'ice')
bug_q = QuantumRegister(1, 'bug')
steel_q = QuantumRegister(1, 'steel')
qc_types = QuantumCircuit(fire_q, grass_q, ground_q, fly_q, water_q, elec_q, ice_q, bug_q, steel_q)

# Init charizard
fire_weak = [water_q, ground_q]
fly_weak = [elec_q, ice_q]
fire_resist = [fire_q, bug_q, grass_q, steel_q, ice_q]
fly_resist = [grass_q, bug_q]
fire_immune = ["None"]
fly_immune = [ground_q]

# Charamenter type map
weakFire_q = QuantumRegister(1, 'weak_fire')
weakFly_q = QuantumRegister(1, 'weak_fly')
checkWeak_q = QuantumRegister(1, 'check_weak')

resistFire_q = QuantumRegister(1, 'resist_fire')
resistFly_q = QuantumRegister(1, 'resist_fly')
immuneFire_q = QuantumRegister(1, 'immunity_fire')
immuneFly_q = QuantumRegister(1, 'immunity_fly')
qc_charizard = QuantumCircuit(weakFire_q, weakFly_q, checkWeak_q, resistFire_q, resistFly_q, immuneFire_q, immuneFly_q)

qc_defenderMapping = qc_types + qc_charizard

# Apply gates
## Weakness
if fire_weak[0] != "None":
    for i in fire_weak:
        qc_defenderMapping.cx(i, weakFire_q)
if fly_weak[0] != "None":
    for i in fly_weak:
        qc_defenderMapping.cx(i, weakFly_q)
qc_defenderMapping.barrier()
## Resistances
if fire_resist[0] != "None":
    for i in fire_resist:
        qc_defenderMapping.cx(i, resistFire_q)
if fly_resist[0] != "None":
    for i in fly_resist:
        qc_defenderMapping.cx(i, resistFly_q)
qc_defenderMapping.barrier()
## Immunity
if fire_immune[0] != "None":
    for i in fire_immune:
        qc_defenderMapping.cx(i, immuneFire_q)
if fly_immune[0] != "None":
    for i in fly_immune:
        qc_defenderMapping.cx(i, immuneFly_q)

# Check of the weakness
def or_weak(qc):
    qc.barrier()
    qc.cx(weakFire_q[0], checkWeak_q[0])
    qc.cx(weakFly_q[0], checkWeak_q[0])
    qc.ccx(weakFly_q[0], weakFire_q[0], checkWeak_q[0])
    qc.barrier()
qc_orweak = qc_types + qc_charizard

or_weak(qc_orweak)

(qc_defenderMapping + qc_orweak).draw()

qc_algo = qc_init + qc_qram + qc_defenderMapping + qc_orweak

qc_algo.draw()

oracle_q = QuantumRegister(1, 'oracle')
qc_oracle = QuantumCircuit(oracle_q)
qc_oracle.x(oracle_q)
qc_oracle.h(oracle_q)

# Glueing everything together
qc_finale = qc_oracle + qc_init + qc_qram + qc_defenderMapping + qc_orweak

qc_finale.x([resistFire_q[0], resistFly_q[0], immuneFire_q[0], immuneFly_q[0]])
qc_finale.mcx([checkWeak_q, resistFire_q, resistFly_q, immuneFire_q, immuneFly_q], oracle_q)
qc_finale.x([resistFire_q[0], resistFly_q[0], immuneFire_q[0], immuneFly_q[0]])
qc_finale.barrier()

qc_finale.draw()

def diffuser(nqubits):
    qc = QuantumCircuit(nqubits)
    for qubit in range(nqubits):
        qc.h(qubit)
    for qubit in range(nqubits):
        qc.x(qubit)
    qc.h(nqubits - 1)
    qc.mct(list(range(nqubits - 1)), nqubits - 1)
    qc.h(nqubits - 1)
    for qubit in range(nqubits):
        qc.x(qubit)
    for qubit in range(nqubits):
        qc.h(qubit)
    U_s = qc.to_gate()
    U_s.name = "$U_s$"
    return qc


qc_finale = qc_oracle + qc_init 
for i in range(1):
    qc_finale = qc_finale + qc_qram + qc_defenderMapping + qc_orweak

    qc_finale.x([resistFire_q[0], resistFly_q[0], immuneFire_q[0], immuneFly_q[0]])
    qc_finale.mcx([checkWeak_q, resistFire_q, resistFly_q, immuneFire_q, immuneFly_q], oracle_q)
    qc_finale.x([resistFire_q[0], resistFly_q[0], immuneFire_q[0], immuneFly_q[0]])

    qc_finale = qc_finale + qc_orweak + qc_defenderMapping + qc_qram

    qc_finale.append(diffuser(2), [1, 2])
    
qc_finale.barrier()
qc_finale.measure(qram_q, range(2))

qc_finale.draw()

qc = qc_finale.reverse_bits()
job = execute(qc, backend_sim, shots=512)
result_job = job.result()
plot_histogram(result_job.get_counts())

oracle_q = QuantumRegister(1, 'oracle')
qram_q = QuantumRegister(2, 'qram')

fire_q = QuantumRegister(1, 'fire')
grass_q = QuantumRegister(1, 'grass')
ground_q = QuantumRegister(1, 'ground')
fly_q = QuantumRegister(1, 'fly')
water_q = QuantumRegister(1, 'water')
elec_q = QuantumRegister(1, 'electrick')
ice_q = QuantumRegister(1, 'ice')
bug_q = QuantumRegister(1, 'bug')
steel_q = QuantumRegister(1, 'steel')
normal_q = QuantumRegister(1, 'normal')

resistFire_q = QuantumRegister(1, 'resist_fire')
resistFly_q = QuantumRegister(1, 'resist_fly')
immuneFire_q = QuantumRegister(1, 'immunity_fire')
immuneFly_q = QuantumRegister(1, 'immunity_fly')
weakFire_q = QuantumRegister(1, 'weak_fire')
weakFly_q = QuantumRegister(1, 'weak_fly')
checkWeak_q = QuantumRegister(1, 'check_weak')

c = ClassicalRegister(2, 'measure')

qc = QuantumCircuit(oracle_q, qram_q, fire_q, grass_q, ground_q, fly_q, water_q, elec_q, ice_q, bug_q, 
                    steel_q, normal_q, resistFire_q, resistFly_q, immuneFire_q, immuneFly_q, 
                    weakFire_q, weakFly_q, checkWeak_q, c)

def qram(nqubits):
    qc = QuantumCircuit(nqubits)
    U_s = qc.to_gate()
    U_s.name = "$QRAM$"
    return U_s

def defenderMapping(nqubits):
    qc = QuantumCircuit(nqubits)
    U_s = qc.to_gate()
    U_s.name = "$mappingChara$"
    return U_s

def orweak(nqubits):
    qc = QuantumCircuit(nqubits)
    U_s = qc.to_gate()
    U_s.name = "$weakCheck$"
    return U_s

def diffuser(nqubits):
    qc = QuantumCircuit(nqubits)
    U_s = qc.to_gate()
    U_s.name = "$diffuser$"
    return U_s

# Init
qc.x(oracle_q)
qc.h(oracle_q)
qc.h(qram_q)
qc.barrier()

for i in range(1):
    # Qram
    qc.append(qram(12), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
    # Algo
    qc.append(defenderMapping(16), [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18])
    qc.append(orweak(3), [17, 18, 19])

    # Oracle
    qc.barrier()
    qc.x([resistFire_q[0], resistFly_q[0], immuneFire_q[0], immuneFly_q[0]])
    qc.mcx([checkWeak_q, resistFire_q, resistFly_q, immuneFire_q, immuneFly_q], oracle_q)
    qc.x([resistFire_q[0], resistFly_q[0], immuneFire_q[0], immuneFly_q[0]])
    qc.barrier()

    # Uncompute
    qc.append(orweak(3), [17, 18, 19])
    qc.append(defenderMapping(16), [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18])
    qc.append(qram(12), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])

    # Diffuser
    qc.append(diffuser(2), [1, 2])
    
qc.barrier()
qc.measure(qram_q, range(2))

qc.draw()