<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 QuTiP example
Step1:  Introduction
http
Step2:  Rotation about X-axis
Step3:  Rotation about Y-axis
Step4:  Rotation about Z-axis
Step5:  CNOT
Step6:  CSIGN
Step7:  Berkeley
Step8:  SWAPalpha
Step9:  FREDKIN
Step10:  TOFFOLI
Step11:  SWAP
Step12:  ISWAP
Step13:  SQRTiSWAP
Step14:  SQRTSWAP
Step15:  SQRTNOT
Step16:  HADAMARD
Step17:  PHASEGATE
Step18:  GLOBALPHASE
Step19:  Mølmer–Sørensen gate
Step20:  Qubit rotation gate
Step21:  Expanding gates to larger qubit registers
The example above show how to generate matrice representations of the gates implemented in QuTiP, in their minimal qubit requirements. If the same gates is to be represented in a qubit register of size $N$, the optional keywork argument N can be specified when calling the gate function. For example, to generate the matrix for the CNOT gate for a $N=3$ bit register
Step22:  Furthermore, the control and target qubits (when applicable) can also be similarly specified using keyword arguments control and target (or in some cases controls or targets)
Step23:  Setup of a Qubit Circuit
The gates implemented in QuTiP can be used to build any qubit circuit using the class QubitCircuit. The output can be obtained in the form of a unitary matrix or a latex representation.
In the following example, we take a SWAP gate. It is known that a swap gate is equivalent to three CNOT gates applied in the given format.
Step24:  In place of manually converting the SWAP gate to CNOTs, it can be automatically converted using an inbuilt function in QubitCircuit
Step25:  From QuTiP 4.4, we can also add gate at arbitrary position in a circuit.
Step26:  Example of basis transformation
Step27:  The transformation can either be only in terms of 2-qubit gates
Step28:  Or the transformation can be in terms of any 2 single qubit rotation gates along with the 2-qubit gate.
Step29:  Resolving non-adjacent interactions
Interactions between non-adjacent qubits can be resolved by QubitCircuit to a series of adjacent interactions, which is useful for systems such as spin chain models.
Step30:  Adding gate in the middle of a circuit
From QuTiP 4.4 one can add a gate at an arbitrary position of a circuit. All one needs to do is to specify the parameter index. With this, we can also add the same gate at multiple positions at the same time.
Step31:  User defined gates
From QuTiP 4.4 on, user defined gates can be defined by a python function that takes at most one parameter and return a Qobj, the dimension of the Qobj has to match the qubit system.
Step32:  To let the QubitCircuit process those gates, we need to modify its attribute QubitCircuit.user_gates, which is a python dictionary in the form {name
Step33:  When calling the add_gate method, the target qubits and the argument need to be given.
Step34:  Software versions
<ASSISTANT_TASK:>
Python Code:
%matplotlib inline
from IPython.display import Image
from numpy import pi
import numpy as np
from qutip import *
from qutip.qip.operations import *
from qutip.qip.circuit import QubitCircuit, Gate
Explanation: QuTiP example: Quantum Gates and their usage
Author: Anubhav Vardhan (anubhavvardhan@gmail.com)
User-defined gate added by: Boxi Li (etamin1201@gmail.com)
For more information about QuTiP see http://qutip.org
Note: The circuit image visualizations require ImageMagick for display.
ImageMagick can be easily installed with the command conda install imagemagick if you have conda installed.
Otherwise, please follow the installation instructions on the ImageMagick documentation.
End of explanation
cphase(pi/2)
q = QubitCircuit(2, reverse_states=False)
q.add_gate("CSIGN", controls=[0], targets=[1])
q.png
Explanation: Introduction
http://en.wikipedia.org/wiki/Quantum_gate
Gates in QuTiP and their representation
Controlled-PHASE
End of explanation
rx(pi/2)
q = QubitCircuit(1, reverse_states=False)
q.add_gate("RX", targets=[0], arg_value=pi/2, arg_label=r'\frac{\pi}{2}')
q.png
Explanation: Rotation about X-axis
End of explanation
ry(pi/2)
q = QubitCircuit(1, reverse_states=False)
q.add_gate("RY", targets=[0], arg_value=pi/2, arg_label=r'\frac{\pi}{2}')
q.png
Explanation: Rotation about Y-axis
End of explanation
rz(pi/2)
q = QubitCircuit(1, reverse_states=False)
q.add_gate("RZ", targets=[0], arg_value=pi/2, arg_label=r'\frac{\pi}{2}')
q.png
Explanation: Rotation about Z-axis
End of explanation
cnot()
q = QubitCircuit(2, reverse_states=False)
q.add_gate("CNOT", controls=[0], targets=[1])
q.png
Explanation: CNOT
End of explanation
csign()
q = QubitCircuit(2, reverse_states=False)
q.add_gate("CSIGN", controls=[0], targets=[1])
q.png
Explanation: CSIGN
End of explanation
berkeley()
q = QubitCircuit(2, reverse_states=False)
q.add_gate("BERKELEY", targets=[0, 1])
q.png
Explanation: Berkeley
End of explanation
swapalpha(pi/2)
Explanation: SWAPalpha
End of explanation
fredkin()
Explanation: FREDKIN
End of explanation
toffoli()
Explanation: TOFFOLI
End of explanation
swap()
Explanation: SWAP
End of explanation
iswap()
Explanation: ISWAP
End of explanation
sqrtiswap()
Explanation: SQRTiSWAP
End of explanation
sqrtswap()
Explanation: SQRTSWAP
End of explanation
sqrtnot()
Explanation: SQRTNOT
End of explanation
snot()
Explanation: HADAMARD
End of explanation
phasegate(pi/2)
Explanation: PHASEGATE
End of explanation
globalphase(pi/2)
Explanation: GLOBALPHASE
End of explanation
molmer_sorensen(pi/2)
Explanation: Mølmer–Sørensen gate
End of explanation
qrot(pi/2, pi/4)
Explanation: Qubit rotation gate
End of explanation
cnot(N=3)
q = QubitCircuit(3, reverse_states=False)
q.add_gate("CNOT", controls=[1], targets=[2])
q.png
Explanation: Expanding gates to larger qubit registers
The example above show how to generate matrice representations of the gates implemented in QuTiP, in their minimal qubit requirements. If the same gates is to be represented in a qubit register of size $N$, the optional keywork argument N can be specified when calling the gate function. For example, to generate the matrix for the CNOT gate for a $N=3$ bit register:
End of explanation
cnot(N=3, control=2, target=0)
q = QubitCircuit(3, reverse_states=False)
q.add_gate("CNOT", controls=[0], targets=[2])
q.png
Explanation: Furthermore, the control and target qubits (when applicable) can also be similarly specified using keyword arguments control and target (or in some cases controls or targets):
End of explanation
N = 2
qc0 = QubitCircuit(N)
qc0.add_gate("SWAP", [0, 1], None)
qc0.png
U_list0 = qc0.propagators()
U0 = gate_sequence_product(U_list0)
U0
qc1 = QubitCircuit(N)
qc1.add_gate("CNOT", 0, 1)
qc1.add_gate("CNOT", 1, 0)
qc1.add_gate("CNOT", 0, 1)
qc1.png
U_list1 = qc1.propagators()
U1 = gate_sequence_product(U_list1)
U1
Explanation: Setup of a Qubit Circuit
The gates implemented in QuTiP can be used to build any qubit circuit using the class QubitCircuit. The output can be obtained in the form of a unitary matrix or a latex representation.
In the following example, we take a SWAP gate. It is known that a swap gate is equivalent to three CNOT gates applied in the given format.
End of explanation
qc2 = qc0.resolve_gates("CNOT")
qc2.png
U_list2 = qc2.propagators()
U2 = gate_sequence_product(U_list2)
U2
Explanation: In place of manually converting the SWAP gate to CNOTs, it can be automatically converted using an inbuilt function in QubitCircuit
End of explanation
qc1.add_gate("CSIGN", index=1)
qc1.png
Explanation: From QuTiP 4.4, we can also add gate at arbitrary position in a circuit.
End of explanation
qc3 = QubitCircuit(3)
qc3.add_gate("CNOT", 1, 0)
qc3.add_gate("RX", 0, None, pi/2, r"\pi/2")
qc3.add_gate("RY", 1, None, pi/2, r"\pi/2")
qc3.add_gate("RZ", 2, None, pi/2, r"\pi/2")
qc3.add_gate("ISWAP", [1, 2])
qc3.png
U3 = gate_sequence_product(qc3.propagators())
U3
Explanation: Example of basis transformation
End of explanation
qc4 = qc3.resolve_gates("CNOT")
qc4.png
U4 = gate_sequence_product(qc4.propagators())
U4
qc5 = qc3.resolve_gates("ISWAP")
qc5.png
U5 = gate_sequence_product(qc5.propagators())
U5
Explanation: The transformation can either be only in terms of 2-qubit gates:
End of explanation
qc6 = qc3.resolve_gates(["ISWAP", "RX", "RY"])
qc6.png
U6 = gate_sequence_product(qc6.propagators())
U6
qc7 = qc3.resolve_gates(["CNOT", "RZ", "RX"])
qc7.png
U7 = gate_sequence_product(qc7.propagators())
U7
Explanation: Or the transformation can be in terms of any 2 single qubit rotation gates along with the 2-qubit gate.
End of explanation
qc8 = QubitCircuit(3)
qc8.add_gate("CNOT", 2, 0)
qc8.png
U8 = gate_sequence_product(qc8.propagators())
U8
qc9 = qc8.adjacent_gates()
qc9.png
U9 = gate_sequence_product(qc9.propagators())
U9
qc10 = qc9.resolve_gates("CNOT")
qc10.png
U10 = gate_sequence_product(qc10.propagators())
U10
Explanation: Resolving non-adjacent interactions
Interactions between non-adjacent qubits can be resolved by QubitCircuit to a series of adjacent interactions, which is useful for systems such as spin chain models.
End of explanation
qc = QubitCircuit(1)
qc.add_gate("RX", targets=1)
qc.add_gate("RX", targets=1)
qc.add_gate("RY", targets=1, index=[1,0])
qc.gates
Explanation: Adding gate in the middle of a circuit
From QuTiP 4.4 one can add a gate at an arbitrary position of a circuit. All one needs to do is to specify the parameter index. With this, we can also add the same gate at multiple positions at the same time.
End of explanation
def user_gate1(arg_value):
    # controlled rotation X
    mat = np.zeros((4, 4), dtype=np.complex)
    mat[0, 0] = mat[1, 1] = 1.
    mat[2:4, 2:4] = rx(arg_value)
    return Qobj(mat, dims=[[2, 2], [2, 2]])
def user_gate2():
    # S gate
    mat = np.array([[1.,   0],
                    [0., 1.j]])
    return Qobj(mat, dims=[[2], [2]])
Explanation: User defined gates
From QuTiP 4.4 on, user defined gates can be defined by a python function that takes at most one parameter and return a Qobj, the dimension of the Qobj has to match the qubit system.
End of explanation
qc = QubitCircuit(2)
qc.user_gates = {"CTRLRX": user_gate1, 
                 "S"     : user_gate2}
Explanation: To let the QubitCircuit process those gates, we need to modify its attribute QubitCircuit.user_gates, which is a python dictionary in the form {name: gate_function}.
End of explanation
# qubit 0 controls qubit 1
qc.add_gate("CTRLRX", targets=[0,1], arg_value=pi/2)
# qubit 1 controls qubit 0
qc.add_gate("CTRLRX", targets=[1,0], arg_value=pi/2)
# a gate can also be added using the Gate class
g_T = Gate("S", targets=[1])
qc.add_gate("S", targets=[1])
props = qc.propagators()
props[0] # qubit 0 controls qubit 1
props[1] # qubit 1 controls qubit 0
props[2] # S  gate acts on qubit 1
Explanation: When calling the add_gate method, the target qubits and the argument need to be given.
End of explanation
from qutip.ipynbtools import version_table
version_table()
Explanation: Software versions
End of explanation
<END_TASK>