from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, execute, Aer, IBMQ
import qiskit_tools as qt
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

phase = True

digit_A = 5.25
digit_B = 7.5

print(digit_A,'x',digit_B,'=',digit_A*digit_B)

nintA = qt.get_nint(digit_A)
npresA = qt.get_npres(digit_A)
nA = nintA + npresA

if phase:
    nA += 1

print(nA, nintA, npresA)

nintB = qt.get_nint(digit_B)
npresB = qt.get_npres(digit_B)
nB = nintB + npresB

if phase:
    nB += 1
print(nB, nintB, npresB)

nintC = qt.get_nint(digit_A*digit_B)
npresC = qt.get_npres(digit_A*digit_B)
nC = nintC + npresC

if phase:
    nC += 1
print(nC, nintC, npresC)

binary_A = qt.my_binary_repr(digit_A, nA, nint=nintA, phase=phase)
binary_B = qt.my_binary_repr(digit_B, nB, nint=nintB, phase=phase)
binary_C = qt.my_binary_repr(digit_A*digit_B, nC, nint=nintC, phase=phase)

print(binary_A,binary_B,binary_C)

qregA = QuantumRegister(nA, 'A')
qregB = QuantumRegister(nB, 'B')
qregC = QuantumRegister(nC, 'C')
out_reg = ClassicalRegister(nC,'out_reg')
circ = QuantumCircuit(qregA, qregB, qregC, out_reg)

A_gate = qt.input_bits_to_qubits(binary_A, circ, reg=qregA, wrap=True)
B_gate = qt.input_bits_to_qubits(binary_B, circ, reg=qregB, wrap=True)
circ.append(A_gate, qregA);
circ.append(B_gate, qregB);

mult_gate = qt.QFTMultiply(circ, qregA, qregB, qregC, nint1=nintA, nint2=nintB, nint3=nintC, phase=True, wrap=True)
circ.append(mult_gate, [*qregA, *qregB, *qregC]);

circ.measure(qregC, out_reg);

circ.draw('latex')

shots=10
emulator = Aer.get_backend('qasm_simulator')
job = execute(circ, emulator, shots=shots )
hist = job.result().get_counts()

print('Target:')
print(digit_B,'x',digit_A,'=',digit_B*digit_A,'->',binary_C)

print('Result:')
for label in hist.keys():
    print(digit_B,'x',digit_A,'=',qt.bin_to_dec(label, nint=nintC, phase=phase),'->',label,'with probability',float(hist[label])/shots)

phase = True

digit_A = 5.25
digit_B = -7.5

print(digit_A,'x',digit_B,'=',digit_A*digit_B)

binary_A = qt.my_binary_repr(digit_A, nA, nint=nintA, phase=phase)
binary_B = qt.my_binary_repr(digit_B, nB, nint=nintB, phase=phase)
binary_C = qt.my_binary_repr(digit_A*digit_B, nC, nint=nintC, phase=phase)

print(binary_A,binary_B,binary_C)

qregA = QuantumRegister(nA, 'A')
qregB = QuantumRegister(nB, 'B')
qregC = QuantumRegister(nC, 'C')
out_reg = ClassicalRegister(nC,'out_reg')
circ = QuantumCircuit(qregA, qregB, qregC, out_reg)

A_gate = qt.input_bits_to_qubits(binary_A, circ, reg=qregA, wrap=True)
B_gate = qt.input_bits_to_qubits(binary_B, circ, reg=qregB, wrap=True)
circ.append(A_gate, qregA);
circ.append(B_gate, qregB);

mult_gate = qt.QFTMultiply(circ, qregA, qregB, qregC, nint1=nintA, nint2=nintB, nint3=nintC, phase=True, wrap=True)
circ.append(mult_gate, [*qregA, *qregB, *qregC]);

circ.measure(qregC, out_reg);

shots=10
emulator = Aer.get_backend('qasm_simulator')
job = execute(circ, emulator, shots=shots )
hist = job.result().get_counts()

print('Target:')
print(digit_B,'x',digit_A,'=',digit_B*digit_A,'->',binary_C)

print('Result:')
for label in hist.keys():
    print(digit_B,'x',digit_A,'=',qt.bin_to_dec(label, nint=nintC, phase=phase),'->',label,'with probability',float(hist[label])/shots)

qregA = QuantumRegister(nA, 'A')
qregB = QuantumRegister(nB, 'B')
qregC = QuantumRegister(nC, 'C')
out_reg = ClassicalRegister(nC,'out_reg')
circ = QuantumCircuit(qregA, qregB, qregC, out_reg)

# Input the A and B binary strings into their registers
A_gate = qt.input_bits_to_qubits(binary_A, circ, reg=qregA, wrap=True)
B_gate = qt.input_bits_to_qubits(binary_B, circ, reg=qregB, wrap=True)
circ.append(A_gate, qregA);
circ.append(B_gate, qregB);

circ.barrier()

# Define the twos-complement operations
tc_gate_A = qt.TwosCompliment(circ, qregA[:-1], wrap=True).control(1)
tc_gate_B = qt.TwosCompliment(circ, qregB[:-1], wrap=True).control(1)
tc_gate_C = qt.TwosCompliment(circ, qregC[:-1], wrap=True).control(2)

# Add the twos-complement operations to convert registers A and B to sign-magnitude notation
circ.append(tc_gate_A, [qregA[-1], *qregA[:-1]]);
circ.append(tc_gate_B, [qregB[-1], *qregB[:-1]]);

circ.barrier()

# Apply simple QFT multiplication
mult_gate = qt.QFTMultiply(circ, qregA[:-1], qregB[:-1], qregC[:-1], nint1=nintA, nint2=nintB, nint3=nintC, wrap=True)
circ.append(mult_gate, [*qregA[:-1], *qregB[:-1], *qregC[:-1]]);

circ.barrier()

# If register A is positive, but register B is negative, then twos-complement operator on C
circ.x(qregA[-1]);
circ.append(tc_gate_C, [qregA[-1], qregB[-1], *qregC[:-1]]);
circ.x(qregA[-1]);

# If register B is positive, but register A is negative, then twos-complement operator on C
circ.x(qregB[-1]);
circ.append(tc_gate_C, [qregA[-1], qregB[-1], *qregC[:-1]]);
circ.x(qregB[-1]);

circ.barrier()

# If A is negative flip phase qubit of C, and if B is negative flip phase qubit of C
circ.cx(qregA[-1], qregC[-1]);
circ.cx(qregB[-1], qregC[-1]);

circ.barrier()

# Restore registers A and B to original twos-complement notation
circ.append(tc_gate_A, [qregA[-1], *qregA[:-1]]);
circ.append(tc_gate_B, [qregB[-1], *qregB[:-1]]);

circ.barrier()

circ.measure(qregC, out_reg);

circ.draw('latex')

shots=10
emulator = Aer.get_backend('qasm_simulator')
job = execute(circ, emulator, shots=shots )
hist = job.result().get_counts()

print('Target:')
print(digit_B,'x',digit_A,'=',digit_B*digit_A,'->',binary_C)

print('Result:')
for label in hist.keys():
    print(digit_B,'x',digit_A,'=',qt.bin_to_dec(label, nint=nintC, phase=phase),'->',label,'with probability',float(hist[label])/shots)

phase = True

digit_A = 0.
digit_B = -7.5

print(digit_A,'x',digit_B,'=',digit_A*digit_B)

binary_A = qt.my_binary_repr(digit_A, nA, nint=nintA, phase=phase)
binary_B = qt.my_binary_repr(digit_B, nB, nint=nintB, phase=phase)
binary_C = qt.my_binary_repr(digit_A*digit_B, nC, nint=nintC, phase=phase)

print(binary_A,binary_B,binary_C)

qregA = QuantumRegister(nA, 'A')
qregB = QuantumRegister(nB, 'B')
qregC = QuantumRegister(nC, 'C')
out_reg = ClassicalRegister(nC,'out_reg')
circ = QuantumCircuit(qregA, qregB, qregC, out_reg)

# Input the A and B binary strings into their registers
A_gate = qt.input_bits_to_qubits(binary_A, circ, reg=qregA, wrap=True)
B_gate = qt.input_bits_to_qubits(binary_B, circ, reg=qregB, wrap=True)
circ.append(A_gate, qregA);
circ.append(B_gate, qregB);

circ.barrier()

# Define the twos-complement operations
tc_gate_A = qt.TwosCompliment(circ, qregA[:-1], wrap=True).control(1)
tc_gate_B = qt.TwosCompliment(circ, qregB[:-1], wrap=True).control(1)
tc_gate_C = qt.TwosCompliment(circ, qregC[:-1], wrap=True).control(2)

# Add the twos-complement operations to convert registers A and B to sign-magnitude notation
circ.append(tc_gate_A, [qregA[-1], *qregA[:-1]]);
circ.append(tc_gate_B, [qregB[-1], *qregB[:-1]]);

circ.barrier()

# Apply simple QFT multiplication
mult_gate = qt.QFTMultiply(circ, qregA[:-1], qregB[:-1], qregC[:-1], nint1=nintA, nint2=nintB, nint3=nintC, wrap=True)
circ.append(mult_gate, [*qregA[:-1], *qregB[:-1], *qregC[:-1]]);

circ.barrier()

# If register A is positive, but register B is negative, then twos-complement operator on C
circ.x(qregA[-1]);
circ.append(tc_gate_C, [qregA[-1], qregB[-1], *qregC[:-1]]);
circ.x(qregA[-1]);

# If register B is positive, but register A is negative, then twos-complement operator on C
circ.x(qregB[-1]);
circ.append(tc_gate_C, [qregA[-1], qregB[-1], *qregC[:-1]]);
circ.x(qregB[-1]);

circ.barrier()

# If A is negative flip phase qubit of C, and if B is negative flip phase qubit of C
circ.cx(qregA[-1], qregC[-1]);
circ.cx(qregB[-1], qregC[-1]);

circ.barrier()

# Restore registers A and B to original twos-complement notation
circ.append(tc_gate_A, [qregA[-1], *qregA[:-1]]);
circ.append(tc_gate_B, [qregB[-1], *qregB[:-1]]);

circ.barrier()

circ.measure(qregC, out_reg);

shots=10
emulator = Aer.get_backend('qasm_simulator')
job = execute(circ, emulator, shots=shots )
hist = job.result().get_counts()

print('Target:')
print(digit_B,'x',digit_A,'=',digit_B*digit_A,'->',binary_C)

print('Result:')
for label in hist.keys():
    print(digit_B,'x',digit_A,'=',qt.bin_to_dec(label, nint=nintC, phase=phase),'->',label,'with probability',float(hist[label])/shots)

qregA = QuantumRegister(nA, 'A')
qregB = QuantumRegister(nB, 'B')
qregC = QuantumRegister(nC, 'C')
out_reg = ClassicalRegister(nC,'out_reg')
circ = QuantumCircuit(qregA, qregB, qregC, out_reg)

# Input the A and B binary strings into their registers
A_gate = qt.input_bits_to_qubits(binary_A, circ, reg=qregA, wrap=True)
B_gate = qt.input_bits_to_qubits(binary_B, circ, reg=qregB, wrap=True)
circ.append(A_gate, qregA);
circ.append(B_gate, qregB);

circ.barrier()

# Define the twos-complement operations
tc_gate_A = qt.TwosCompliment(circ, qregA[:-1], wrap=True).control(1)
tc_gate_B = qt.TwosCompliment(circ, qregB[:-1], wrap=True).control(1)
tc_gate_C = qt.TwosCompliment(circ, qregC[:-1], wrap=True).control(2)

# Add the twos-complement operations to convert registers A and B to sign-magnitude notation
circ.append(tc_gate_A, [qregA[-1], *qregA[:-1]]);
circ.append(tc_gate_B, [qregB[-1], *qregB[:-1]]);

circ.barrier()

# Apply simple QFT multiplication
mult_gate = qt.QFTMultiply(circ, qregA[:-1], qregB[:-1], qregC[:-1], nint1=nintA, nint2=nintB, nint3=nintC, wrap=True)
circ.append(mult_gate, [*qregA[:-1], *qregB[:-1], *qregC[:-1]]);

circ.barrier()

# If register A is positive, but register B is negative, then twos-complement operator on C
circ.x(qregA[-1]);
circ.append(tc_gate_C, [qregA[-1], qregB[-1], *qregC[:-1]]);
circ.x(qregA[-1]);

# If register B is positive, but register A is negative, then twos-complement operator on C
circ.x(qregB[-1]);
circ.append(tc_gate_C, [qregA[-1], qregB[-1], *qregC[:-1]]);
circ.x(qregB[-1]);

circ.barrier()

# If A is negative flip phase qubit of C, and if B is negative flip phase qubit of C
circ.cx(qregA[-1], qregC[-1]);
circ.cx(qregB[-1], qregC[-1]);

circ.barrier()

# Restore registers A and B to original twos-complement notation
circ.append(tc_gate_A, [qregA[-1], *qregA[:-1]]);
circ.append(tc_gate_B, [qregB[-1], *qregB[:-1]]);

circ.barrier()

# Flip register A
for qubit in np.arange(nA):
    circ.x(qregA[qubit]);

# If register A is all zeros and phase of B is negative, then flip the phase of C
circ.mcx([*qregA, qregB[-1]], qregC[-1], mode='noancilla')

# Flip back register A
for qubit in np.arange(nA):
    circ.x(qregA[qubit]);

circ.barrier()
    
# Flip register B
for qubit in np.arange(nB):
    circ.x(qregB[qubit]);
    
# If register B is all zeros and phase of A is negative, then flip the phase of C
circ.mcx([*qregB, qregA[-1]], qregC[-1], mode='noancilla')

# Flip back register B
for qubit in np.arange(nB):
    circ.x(qregB[qubit]);

circ.barrier()

circ.measure(qregC, out_reg);

circ.draw('latex')

shots=10
emulator = Aer.get_backend('qasm_simulator')
job = execute(circ, emulator, shots=shots )
hist = job.result().get_counts()

print('Target:')
print(digit_B,'x',digit_A,'=',digit_B*digit_A,'->',binary_C)

print('Result:')
for label in hist.keys():
    print(digit_B,'x',digit_A,'=',qt.bin_to_dec(label, nint=nintC, phase=phase),'->',label,'with probability',float(hist[label])/shots)

phase = True

digit_A = 5.25
digit_B = -7.5

print(digit_A,'x',digit_B,'=',digit_A*digit_B)

binary_A = qt.my_binary_repr(digit_A, nA, nint=nintA, phase=phase)
binary_B = qt.my_binary_repr(digit_B, nB, nint=nintB, phase=phase)
binary_C = qt.my_binary_repr(digit_A*digit_B, nC, nint=nintC, phase=phase)

print(binary_A,binary_B,binary_C)

qregA = QuantumRegister(nA, 'A')
qregB = QuantumRegister(nB, 'B')
qregC = QuantumRegister(nC, 'C')
out_reg = ClassicalRegister(nC,'out_reg')
circ = QuantumCircuit(qregA, qregB, qregC, out_reg)

A_gate = qt.input_bits_to_qubits(binary_A, circ, reg=qregA, wrap=True)
B_gate = qt.input_bits_to_qubits(binary_B, circ, reg=qregB, wrap=True)
circ.append(A_gate, qregA);
circ.append(B_gate, qregB);

mult_gate = qt.QFTMultPhase(circ, qregA, qregB, qregC, nint1=nintA, nint2=nintB, nint3=nintC, wrap=True)
circ.append(mult_gate, [*qregA, *qregB, *qregC]);

circ.measure(qregC, out_reg);

circ.draw(output='latex')

shots=10
emulator = Aer.get_backend('qasm_simulator')
job = execute(circ, emulator, shots=shots )
hist = job.result().get_counts()

print('Target:')
print(digit_B,'x',digit_A,'=',digit_B*digit_A,'->',binary_C)

print('Result:')
for label in hist.keys():
    print(digit_B,'x',digit_A,'=',qt.bin_to_dec(label, nint=nintC, phase=phase),'->',label,'with probability',float(hist[label])/shots)

phase = True
signmag = True

digit_A = -5.25
digit_B = 0

print(digit_A,'x',digit_B,'=',digit_A*digit_B)

binary_A = qt.my_binary_repr(digit_A, nA, nint=nintA, phase=phase, signmag=signmag)
binary_B = qt.my_binary_repr(digit_B, nB, nint=nintB, phase=phase, signmag=signmag)
binary_C = qt.my_binary_repr(digit_A*digit_B, nC, nint=nintC, phase=phase, signmag=signmag)

print(binary_A,binary_B,binary_C)

qregA = QuantumRegister(nA, 'A')
qregB = QuantumRegister(nB, 'B')
qregC = QuantumRegister(nC, 'C')
out_reg = ClassicalRegister(nC,'out_reg')
circ = QuantumCircuit(qregA, qregB, qregC, out_reg)

A_gate = qt.input_bits_to_qubits(binary_A, circ, reg=qregA, wrap=True)
B_gate = qt.input_bits_to_qubits(binary_B, circ, reg=qregB, wrap=True)
circ.append(A_gate, qregA);
circ.append(B_gate, qregB);

mult_gate = qt.QFTMultPhase(circ, qregA, qregB, qregC, nint1=nintA, nint2=nintB, nint3=nintC, wrap=True, signmag1=True, signmag2=True, signmag3=True)
circ.append(mult_gate, [*qregA, *qregB, *qregC]);

circ.measure(qregC, out_reg);

circ.decompose(reps=1).draw(output='latex')

shots=10
emulator = Aer.get_backend('qasm_simulator')
job = execute(circ, emulator, shots=shots )
hist = job.result().get_counts()

print('Target:')
print(digit_B,'x',digit_A,'=',digit_B*digit_A,'->',binary_C)

print('Result:')
for label in hist.keys():
    print(digit_B,'x',digit_A,'=',qt.bin_to_dec(label, nint=nintC, phase=phase, signmag=signmag),'->',label,'with probability',float(hist[label])/shots)

phase = True
signmag = True

digit_A = -5.25
digit_B = 0

print(digit_A,'x',digit_B,'=',digit_A*digit_B)

binary_A = qt.my_binary_repr(digit_A, nA, nint=nintA, phase=phase, signmag=signmag)
binary_B = qt.my_binary_repr(digit_B, nB, nint=nintB, phase=phase, signmag=signmag)
binary_C = qt.my_binary_repr(digit_A*digit_B, nC, nint=nintC, phase=phase, signmag=signmag)

print(binary_A,binary_B,binary_C)

qregA = QuantumRegister(nA, 'A')
qregB = QuantumRegister(nB, 'B')
qregC = QuantumRegister(nC, 'C')
out_reg = ClassicalRegister(nC,'out_reg')
circ = QuantumCircuit(qregA, qregB, qregC, out_reg)

A_gate = qt.input_bits_to_qubits(binary_A, circ, reg=qregA, wrap=True)
B_gate = qt.input_bits_to_qubits(binary_B, circ, reg=qregB, wrap=True)
circ.append(A_gate, qregA);
circ.append(B_gate, qregB);

circ = qt.QFTMultPhase(circ, qregA, qregB, qregC, nint1=nintA, nint2=nintB, nint3=nintC, poszero=True, signmag1=True, signmag2=True, signmag3=True)

circ.measure(qregC, out_reg);

circ.decompose(reps=1).draw(output='latex')

shots=10
emulator = Aer.get_backend('qasm_simulator')
job = execute(circ, emulator, shots=shots )
hist = job.result().get_counts()

print('Target:')
print(digit_B,'x',digit_A,'=',digit_B*digit_A,'->',binary_C)

print('Result:')
for label in hist.keys():
    print(digit_B,'x',digit_A,'=',qt.bin_to_dec(label, nint=nintC, phase=phase, signmag=signmag),'->',label,'with probability',float(hist[label])/shots)