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.
In [1]:
%matplotlib inline
In [2]:
from IPython.display import Image
In [3]:
from numpy import pi
import numpy as np
In [4]:
from qutip import *
from qutip.qip.operations import *
from qutip.qip.circuit import QubitCircuit, Gate
In [5]:
cphase(pi/2)
Out[5]:
In [6]:
q = QubitCircuit(2, reverse_states=False)
q.add_gate("CSIGN", controls=[0], targets=[1])
q.png
Out[6]:
In [7]:
rx(pi/2)
Out[7]:
In [8]:
q = QubitCircuit(1, reverse_states=False)
q.add_gate("RX", targets=[0], arg_value=pi/2, arg_label=r'\frac{\pi}{2}')
q.png
Out[8]:
In [9]:
ry(pi/2)
Out[9]:
In [10]:
q = QubitCircuit(1, reverse_states=False)
q.add_gate("RY", targets=[0], arg_value=pi/2, arg_label=r'\frac{\pi}{2}')
q.png
Out[10]:
In [11]:
rz(pi/2)
Out[11]:
In [12]:
q = QubitCircuit(1, reverse_states=False)
q.add_gate("RZ", targets=[0], arg_value=pi/2, arg_label=r'\frac{\pi}{2}')
q.png
Out[12]:
In [13]:
cnot()
Out[13]:
In [14]:
q = QubitCircuit(2, reverse_states=False)
q.add_gate("CNOT", controls=[0], targets=[1])
q.png
Out[14]:
In [15]:
csign()
Out[15]:
In [16]:
q = QubitCircuit(2, reverse_states=False)
q.add_gate("CSIGN", controls=[0], targets=[1])
q.png
Out[16]:
In [17]:
berkeley()
Out[17]:
In [18]:
q = QubitCircuit(2, reverse_states=False)
q.add_gate("BERKELEY", targets=[0, 1])
q.png
Out[18]:
In [19]:
swapalpha(pi/2)
Out[19]:
In [20]:
fredkin()
Out[20]:
In [21]:
toffoli()
Out[21]:
In [22]:
swap()
Out[22]:
In [23]:
iswap()
Out[23]:
In [24]:
sqrtiswap()
Out[24]:
In [25]:
sqrtswap()
Out[25]:
In [26]:
sqrtnot()
Out[26]:
In [27]:
snot()
Out[27]:
In [28]:
phasegate(pi/2)
Out[28]:
In [29]:
globalphase(pi/2)
Out[29]:
In [30]:
molmer_sorensen(pi/2)
Out[30]:
In [31]:
qrot(pi/2, pi/4)
Out[31]:
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:
In [32]:
cnot(N=3)
Out[32]:
In [33]:
q = QubitCircuit(3, reverse_states=False)
q.add_gate("CNOT", controls=[1], targets=[2])
q.png
Out[33]:
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
):
In [34]:
cnot(N=3, control=2, target=0)
Out[34]:
In [35]:
q = QubitCircuit(3, reverse_states=False)
q.add_gate("CNOT", controls=[0], targets=[2])
q.png
Out[35]:
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.
In [36]:
N = 2
qc0 = QubitCircuit(N)
qc0.add_gate("SWAP", [0, 1], None)
qc0.png
Out[36]:
In [37]:
U_list0 = qc0.propagators()
U0 = gate_sequence_product(U_list0)
U0
Out[37]:
In [38]:
qc1 = QubitCircuit(N)
qc1.add_gate("CNOT", 0, 1)
qc1.add_gate("CNOT", 1, 0)
qc1.add_gate("CNOT", 0, 1)
qc1.png
Out[38]:
In [39]:
U_list1 = qc1.propagators()
U1 = gate_sequence_product(U_list1)
U1
Out[39]:
In place of manually converting the SWAP gate to CNOTs, it can be automatically converted using an inbuilt function in QubitCircuit
In [40]:
qc2 = qc0.resolve_gates("CNOT")
qc2.png
Out[40]:
In [41]:
U_list2 = qc2.propagators()
U2 = gate_sequence_product(U_list2)
U2
Out[41]:
From QuTiP 4.4, we can also add gate at arbitrary position in a circuit.
In [ ]:
qc1.add_gate("CSIGN", index=1)
qc1.png
In [42]:
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
Out[42]:
In [43]:
U3 = gate_sequence_product(qc3.propagators())
U3
Out[43]:
In [44]:
qc4 = qc3.resolve_gates("CNOT")
qc4.png
Out[44]:
In [45]:
U4 = gate_sequence_product(qc4.propagators())
U4
Out[45]:
In [46]:
qc5 = qc3.resolve_gates("ISWAP")
qc5.png
Out[46]:
In [47]:
U5 = gate_sequence_product(qc5.propagators())
U5
Out[47]:
In [48]:
qc6 = qc3.resolve_gates(["ISWAP", "RX", "RY"])
qc6.png
Out[48]:
In [49]:
U6 = gate_sequence_product(qc6.propagators())
U6
Out[49]:
In [50]:
qc7 = qc3.resolve_gates(["CNOT", "RZ", "RX"])
qc7.png
Out[50]:
In [51]:
U7 = gate_sequence_product(qc7.propagators())
U7
Out[51]:
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.
In [52]:
qc8 = QubitCircuit(3)
qc8.add_gate("CNOT", 2, 0)
qc8.png
Out[52]:
In [53]:
U8 = gate_sequence_product(qc8.propagators())
U8
Out[53]:
In [54]:
qc9 = qc8.adjacent_gates()
qc9.png
Out[54]:
In [55]:
U9 = gate_sequence_product(qc9.propagators())
U9
Out[55]:
In [56]:
qc10 = qc9.resolve_gates("CNOT")
qc10.png
Out[56]:
In [57]:
U10 = gate_sequence_product(qc10.propagators())
U10
Out[57]:
In [58]:
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
Out[58]:
In [59]:
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]])
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}
.
In [60]:
qc = QubitCircuit(2)
qc.user_gates = {"CTRLRX": user_gate1,
"S" : user_gate2}
When calling the add_gate
method, the target qubits and the argument need to be given.
In [64]:
# 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()
In [65]:
props[0] # qubit 0 controls qubit 1
Out[65]:
In [66]:
props[1] # qubit 1 controls qubit 0
Out[66]:
In [67]:
props[2] # S gate acts on qubit 1
Out[67]:
In [68]:
from qutip.ipynbtools import version_table
version_table()
Out[68]:
In [ ]: