#!/usr/bin/env python # coding: utf-8 # # 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](http://qutip.org) # # #### Installation: # The circuit image visualization requires LaTeX and [ImageMagick](https://imagemagick.org/index.php) for display. The module automatically process the LaTeX code for plotting the circuit, generate the pdf and convert it to the png format. # On Mac and Linux, 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. # # On windows, you need to download and install ImageMagick installer. In addition, you also need [perl](https://www.perl.org/get.html) (for pdfcrop) and [Ghostscript](https://ghostscript.com/releases/index.html) (additional dependency of ImageMagick for png conversion). # # To test if the installation is complete, try the following three commands working correctly in Command Prompt: `pdflatex`, `pdfcrop` and `magick anypdf.pdf antpdf.png`, where `anypdf.pdf` is any pdf file you have. # In[1]: import numpy as np from numpy import pi from qutip import Qobj, about from qutip_qip.circuit import QubitCircuit from qutip_qip.operations import (Gate, berkeley, cnot, cphase, csign, fredkin, gate_sequence_product, globalphase, iswap, molmer_sorensen, phasegate, qrot, rx, ry, rz, snot, sqrtiswap, sqrtnot, sqrtswap, swap, swapalpha, toffoli) get_ipython().run_line_magic('matplotlib', 'inline') # ## Introduction # http://en.wikipedia.org/wiki/Quantum_gate # # ## Gates in QuTiP and their representation # ### Controlled-PHASE # In[2]: cphase(pi / 2) # In[3]: q = QubitCircuit(2, reverse_states=False) q.add_gate("CSIGN", controls=[0], targets=[1]) q.png # ### Rotation about X-axis # In[4]: rx(pi / 2) # In[5]: q = QubitCircuit(1, reverse_states=False) q.add_gate("RX", targets=[0], arg_value=pi / 2, arg_label=r"\frac{\pi}{2}") q.png # ### Rotation about Y-axis # In[6]: ry(pi / 2) # In[7]: q = QubitCircuit(1, reverse_states=False) q.add_gate("RY", targets=[0], arg_value=pi / 2, arg_label=r"\frac{\pi}{2}") q.png # ### Rotation about Z-axis # In[8]: rz(pi / 2) # In[9]: q = QubitCircuit(1, reverse_states=False) q.add_gate("RZ", targets=[0], arg_value=pi / 2, arg_label=r"\frac{\pi}{2}") q.png # ### CNOT # In[10]: cnot() # In[11]: q = QubitCircuit(2, reverse_states=False) q.add_gate("CNOT", controls=[0], targets=[1]) q.png # ### CSIGN # In[12]: csign() # In[13]: q = QubitCircuit(2, reverse_states=False) q.add_gate("CSIGN", controls=[0], targets=[1]) q.png # ### Berkeley # In[14]: berkeley() # In[15]: q = QubitCircuit(2, reverse_states=False) q.add_gate("BERKELEY", targets=[0, 1]) q.png # ### SWAPalpha # In[16]: swapalpha(pi / 2) # ### FREDKIN # In[17]: fredkin() # ### TOFFOLI # In[18]: toffoli() # ### SWAP # In[19]: swap() q = QubitCircuit(2, reverse_states=False) q.add_gate("SWAP", targets=[0, 1]) q.png # ### ISWAP # In[20]: iswap() q = QubitCircuit(2, reverse_states=False) q.add_gate("ISWAP", targets=[0, 1]) q.png # ### SQRTiSWAP # In[21]: sqrtiswap() # ### SQRTSWAP # In[22]: sqrtswap() # ### SQRTNOT # In[23]: sqrtnot() # ### HADAMARD # In[24]: snot() # ### PHASEGATE # In[25]: phasegate(pi / 2) # ### GLOBALPHASE # In[26]: globalphase(pi / 2) # ### Mølmer–Sørensen gate # In[27]: molmer_sorensen(pi / 2) # ### Qubit rotation gate # In[28]: qrot(pi / 2, pi / 4) # ### 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: # In[29]: cnot(N=3) # In[30]: q = QubitCircuit(3, reverse_states=False) q.add_gate("CNOT", controls=[1], targets=[2]) q.png # 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[31]: cnot(N=3, control=2, target=0) # In[32]: q = QubitCircuit(3, reverse_states=False) q.add_gate("CNOT", controls=[0], targets=[2]) q.png # ## 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. # In[33]: N = 2 qc0 = QubitCircuit(N) qc0.add_gate("ISWAP", [0, 1], None) qc0.png # In[34]: U_list0 = qc0.propagators() U0 = gate_sequence_product(U_list0) U0 # In[35]: qc1 = QubitCircuit(N) qc1.add_gate("CNOT", 0, 1) qc1.add_gate("CNOT", 1, 0) qc1.add_gate("CNOT", 0, 1) qc1.png # In[36]: U_list1 = qc1.propagators() U1 = gate_sequence_product(U_list1) U1 # In place of manually converting the SWAP gate to CNOTs, it can be automatically converted using an inbuilt function in QubitCircuit # In[37]: qc2 = qc0.resolve_gates("CNOT") qc2.png # In[38]: U_list2 = qc2.propagators() U2 = gate_sequence_product(U_list2) U2 # From QuTiP 4.4, we can also add gate at arbitrary position in a circuit. # In[39]: qc1.add_gate("CSIGN", index=[1], targets=[0], controls=[1]) qc1.png # ## Example of basis transformation # In[40]: 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 # In[41]: U3 = gate_sequence_product(qc3.propagators()) U3 # ### The transformation can either be only in terms of 2-qubit gates: # In[42]: qc4 = qc3.resolve_gates("CNOT") qc4.png # In[43]: U4 = gate_sequence_product(qc4.propagators()) U4 # In[44]: qc5 = qc3.resolve_gates("ISWAP") qc5.png # In[45]: U5 = gate_sequence_product(qc5.propagators()) U5 # ### Or the transformation can be in terms of any 2 single qubit rotation gates along with the 2-qubit gate. # In[46]: qc6 = qc3.resolve_gates(["ISWAP", "RX", "RY"]) qc6.png # In[47]: U6 = gate_sequence_product(qc6.propagators()) U6 # In[48]: qc7 = qc3.resolve_gates(["CNOT", "RZ", "RX"]) qc7.png # In[49]: U7 = gate_sequence_product(qc7.propagators()) U7 # ## 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. # In[50]: qc8 = QubitCircuit(3) qc8.add_gate("CNOT", 2, 0) qc8.png # In[51]: U8 = gate_sequence_product(qc8.propagators()) U8 # In[52]: qc9 = qc8.adjacent_gates() qc9.gates # In[53]: U9 = gate_sequence_product(qc9.propagators()) U9 # In[54]: qc10 = qc9.resolve_gates("CNOT") qc10.png # In[55]: U10 = gate_sequence_product(qc10.propagators()) U10 # ## 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. # In[56]: qc = QubitCircuit(1) qc.add_gate("RX", targets=1, arg_value=np.pi / 2) qc.add_gate("RX", targets=1, arg_value=np.pi / 2) qc.add_gate("RY", targets=1, arg_value=np.pi / 2, index=[0]) qc.gates # ## 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. # In[57]: def user_gate1(arg_value): # controlled rotation X mat = np.zeros((4, 4), dtype=complex) mat[0, 0] = mat[1, 1] = 1.0 mat[2:4, 2:4] = rx(arg_value).full() return Qobj(mat, dims=[[2, 2], [2, 2]]) def user_gate2(): # S gate mat = np.array([[1.0, 0], [0.0, 1.0j]]) 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[58]: 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[59]: # 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[60]: props[0] # qubit 0 controls qubit 1 # In[61]: props[1] # qubit 1 controls qubit 0 # In[62]: props[2] # S gate acts on qubit 1 # ## Software versions # In[63]: about()