# Importing standard Qiskit libraries
from qiskit import QuantumCircuit, execute, Aer, IBMQ
from qiskit.visualization import *
from qiskit.quantum_info import state_fidelity
# Magic function to render plots in the notebook after the cell executing the plot command
%matplotlib inline

def run_on_qasm_simulator(quantum_circuit, num_shots):
    """Takes a circuit, the number of shots and a backend and returns the counts for running the circuit
    on the qasm_simulator backend."""
    qasm_simulator = Aer.get_backend('qasm_simulator')
    job = execute(quantum_circuit, backend=qasm_simulator, shots=num_shots)
    result = job.result()
    counts = result.get_counts(quantum_circuit)
    return counts

# Create a quantum circuit with a single qubit
qc = QuantumCircuit(1)
# Add the Hadamard gate

# Add the final measurement

# Visualize the circuit
qc.draw('mpl')

# Now we run the circuit various number of shots

# Visualize the results in form of a histogram


# Create a quantum circuit with two qubits

# Add the gates creating a Bell state

# Add the final measurement

# Visualize the circuit


# Now we run the circuit various number of shots

# Visualize the results in form of a histogram


# We prepare a similar function for running on the state vector simulator
# This way we can obtain the state vectors and check for orthogonality
def run_on_statevector_simulator(quantum_circuit, decimals=6):
    """Takes a circuit, and runs it on the state vector simulator backend."""
    statevector_simulator = Aer.get_backend('statevector_simulator')
    job = execute(quantum_circuit, backend=statevector_simulator)
    result = job.result()    
    statevector = result.get_statevector(quantum_circuit, decimals=decimals)
    return statevector

# A quantum circuit for two qubits
qc_phi_minus = QuantumCircuit(2)
# Now add the gates

# Visualize the circuit
qc_phi_minus.draw('mpl')

# To obtain the statevector, we run on Aer's state vector simulator. Note, that there is no measurement at the end
# when running on the state vector simulator, as otherwise the state would collapse onto one of the computational
# basis states and we do not get the actual state vector prepared by the circuit
phi_minus_state = run_on_statevector_simulator(qc_phi_minus)
print('|Phi^-> =', phi_minus_state)

#The psi^+ state
qc_psi_plus = QuantumCircuit(2)
# Now add the gates

# Visualize the circuit
qc_psi_plus.draw('mpl')

psi_plus_state = run_on_statevector_simulator(qc_psi_plus)
print('|Psi^+> =', psi_plus_state)

# Let us first prepare the psi^- state
qc_psi_minus = QuantumCircuit(2)
# Now add the gates

# Visualize the circuit
qc_psi_minus.draw('mpl')

psi_minus_state = run_on_statevector_simulator(qc_psi_minus)
print('|Psi^-> =', psi_minus_state)

# The Phi^+ state
qc_phi_plus = QuantumCircuit(2)
qc_phi_plus.h(0)
qc_phi_plus.cnot(0,1)
qc_phi_plus.draw('mpl')
phi_plus_state = run_on_statevector_simulator(qc_phi_plus)
print('|Phi^+> =', phi_plus_state)

# Check all of the six possible combinations
print('|<Phi^+|Phi^->|^2 =', state_fidelity(phi_plus_state, phi_minus_state))
# ...