#!pip install git+https://github.com/qiskit-community/qiskit-textbook.git#subdirectory=qiskit-textbook-src

import numpy as np
from qiskit_textbook.tools import array_to_latex
Y=np.array([[0,complex(0,-1)],[complex(0,1),0]])
#print ('Y:',Y)
print (array_to_latex(Y,pretext="\\text{Y = } "))
Y_dag= Y.conj().transpose()
print (array_to_latex(Y_dag, pretext="\\text{Y_dag = } "))
A=np.dot(Y,Y_dag)
#print ('A:',A)
print (array_to_latex(A,pretext="\\text{A = } "))

from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import execute
from qiskit import Aer
from qiskit.tools.visualization import circuit_drawer

# Calling a simulator to see what happened after applying the gate
backend_statevector = Aer.get_backend('statevector_simulator')
# create a quantum register and a classical register
q = QuantumRegister(1)
c = ClassicalRegister(1) #used to measure the result
circuit = QuantumCircuit(q, c) # Now we created a circuit
circuit.x(q[0]) # Applied a Pauli X-gate to q0
job = execute(circuit, backend_statevector)
print(array_to_latex(job.result().get_statevector(circuit),pretext="\\text{Psi after X-gate is applied = } "))
circuit.x(q[0]) # Apllied another Pauli X-gate to q
print(circuit.draw(style='mpl'))
job1 = execute(circuit, backend_statevector) # ran the job
#print("Psi after X-gate applied again:",job1.result().get_statevector(circuit))
print(array_to_latex(job1.result().get_statevector(circuit),pretext="\\text{Psi after X-gate is applied again = } "))

np.eye(4) # Creates an identity matrix of 4 X 4 

def mixed_state(pure_state, visibility):
    density_matrix = pure_state @ pure_state.T.conj()
    maximally_mixed_state = np.eye(4)/2**2
    return visibility*density_matrix + (1-visibility)*maximally_mixed_state

ϕ = np.array([[1],[0],[0],[1]])/np.sqrt(2)
#####################
print(array_to_latex(ϕ,pretext="\\text{ϕ = } "))
d_m= ϕ @ ϕ.T.conj()
print ('Density_matrix')
print (d_m)
###############################
print("Maximum visibility is a pure state:")
print(mixed_state(ϕ, 1.0))
#############################
print("The state is still entangled with visibility 0.8:")
print(mixed_state(ϕ, 0.8))
################################
print("Entanglement is lost by 0.6:")
print(mixed_state(ϕ, 0.6))
############################
print("Barely any coherence remains by 0.2:")
print(mixed_state(ϕ, 0.2))

import matplotlib.pyplot as plt

temperatures = [.5, 5, 200]
energies = np.linspace(0, 20, 100) # Creating uniform energy levels
fig, ax = plt.subplots()
for i, T in enumerate(temperatures):
    probabilities = np.exp(-energies/T)
    Z = probabilities.sum()
    probabilities /= Z
    ax.plot(energies, probabilities, linewidth=3, label = "$T_" + str(i+1)+"$="+str(T))
ax.set_xlim(0, 20)
ax.set_ylim(0, 1.2*probabilities.max())
#ax.set_xticks([])
#ax.set_yticks([])
ax.set_xlabel('Energy')
ax.set_ylabel('Probability')
ax.legend()

from qiskit.providers.aer.noise import NoiseModel
from qiskit.providers.aer.noise.errors import pauli_error, depolarizing_error

def get_noise(p_meas,p_gate):

    error_meas = pauli_error([('X',p_meas), ('I', 1 - p_meas)])
    error_gate1 = depolarizing_error(p_gate, 1)
    error_gate2 = error_gate1.tensor(error_gate1)

    noise_model = NoiseModel()
    # measurement error is applied to measurements
    noise_model.add_all_qubit_quantum_error(error_meas, "measure") 
    # single qubit gate error is applied to x gates
    noise_model.add_all_qubit_quantum_error(error_gate1, ["x"]) 
     # two qubit gate error is applied to cx gates
    noise_model.add_all_qubit_quantum_error(error_gate2, ["cx"])
        
    return noise_model

#Create noise_model with a probability of 1% for each type of error
noise_model=get_noise(0.01,0.01)

# initialize circuit with three qubits in the 0 state
qc0 = QuantumCircuit(3,3,name='0') 
 # measure the qubits
qc0.measure(qc0.qregs[0],qc0.cregs[0])
print(qc0.draw(style='mpl'))
# run the circuit with th noise model and extract the counts
counts_noise = execute( qc0, Aer.get_backend('qasm_simulator'),noise_model=noise_model).result().get_counts()
counts = execute( qc0, Aer.get_backend('qasm_simulator')).result().get_counts()

print(' No noise:',counts,'\n','Noise:',counts_noise)

n =1024 # number of shots
#Running same with '111' this time!
qc1 = QuantumCircuit(3, 3, name='0') 
qc1.x(qc1.qregs[0]) # flip each 0 to 1

qc1.measure(qc1.qregs[0],qc1.cregs[0]) # measure the qubits
print(qc1.draw(style='mpl'))
# run the circuit with th noise model and extract the counts
counts_noise = execute( qc1, Aer.get_backend('qasm_simulator'),noise_model=noise_model,shots=n).result().get_counts()
counts = execute( qc1, Aer.get_backend('qasm_simulator'),shots=n).result().get_counts()

print(' No noise:',counts,'\n','Noise:',counts_noise)

#Increase measurement_noise probability
noise_model = get_noise(0.5,0.0)
counts = execute(qc1, Aer.get_backend('qasm_simulator'),noise_model=noise_model).result().get_counts()
print(counts)

