import projectq.setups.ibm # Imports the default compiler to map to IBM QE
from projectq.backends import IBMBackend
from projectq.ops import All, Entangle, Measure
from projectq import MainEngine

"""
Explanation: ProjectQ Demo
Compiling code for IBM QE
Import the IBM setup, the gates, and the compiler engine:
End of explanation
"""


engine = MainEngine(IBMBackend(use_hardware=True, num_runs=1024, verbose=True, device='ibmqx4'),
                    engine_list=projectq.setups.ibm.get_engine_list())
qureg = engine.allocate_qureg(3)

"""
Explanation: Create the compiler using the default compiler engines for the IBM backend and allocate a quantum register of 3 qubits:
End of explanation
"""


Entangle | qureg

"""
Explanation: Entangle the quantum register:
End of explanation
"""


All(Measure) | qureg
engine.flush()

"""
Explanation: Measure the quantum register and run the circuit:
End of explanation
"""


print([int(q) for q in qureg])

"""
Explanation: Output the measurement result:
End of explanation
"""


from projectq.backends import CommandPrinter
engine2 = MainEngine(CommandPrinter(),
                     engine_list=projectq.setups.ibm.get_engine_list())

"""
Explanation: Inspecting the gates required for a 5-qubit entangle on IBM QE
Create a new compiler with a command printer as a back-end:
End of explanation
"""


qureg2 = engine2.allocate_qureg(5)

"""
Explanation: Allocate a quantum register of 5 qubits:
End of explanation
"""


Entangle | qureg2
engine2.flush()

"""
Explanation: Entangle quantum register (and run the circuit):
A 5-qubit entangle operation requires one H gate and 4 CNOTs
if the qubit chip would support CNOTs between arbitrary qubits:
H | Qubit[0]
CX | ( Qubit[0], Qubit[1] )
CX | ( Qubit[0], Qubit[2] )
CX | ( Qubit[0], Qubit[3] )
CX | ( Qubit[0], Qubit[4] )
End of explanation
"""


from projectq.backends import Simulator
engine3 = MainEngine(Simulator())

"""
Explanation: As IBM's QE chip does not support CNOTs between arbitrary qubits, the compiler automatically
changes the abstract entangle circuit to an equivalent circuit which takes
into account the hardware constraints of the IBM QE chip. The resulting circuit is shown above as the output of the command printer.
Simulating a circuit for the IBM QE chip
Create a new compiler with our high-performance quantum simulator as a back-end:

Note
Our Simulator can simulate much larger circuits but here we imported the compiler for the IBM QE chip:
import projectq.setups.ibm
and therefore the compiler will only allow circuits which can be mapped to the IBM QE chip. Running larger simulations is easily possible by using the default compiler (see other example codes)
End of explanation
"""


qureg3 = engine3.allocate_qureg(5)
Entangle | qureg3

"""
Explanation: Allocate a 5-qubit quantum register and apply an entangle operations:
End of explanation
"""


All(Measure) | qureg3
engine3.flush()

"""
Explanation: Measure the quantum register and run the circuit:
End of explanation
"""


print([int(q) for q in qureg3])

"""
Explanation: Output the measurement result:
End of explanation
"""


