Repository: qutip/qutip

Issue:
Defining the Mølmer–Sørensen gate
I know the mathematical definition of the Mølmer–Sørensen gate. How can I define it as a gate in qutip to use in a circuit? Thanks.


Discussion:
I looked up Mølmer–Sørensen but I'm unsure of the definition.  

The definition on the wikipedia article must be wrong because it is not unitary.  

Anyway you should be able to define it exactly the same as all the other gates are defined in qip/gates.py. I've copied and pasted the definition of CNOT. Just change the matrix and function name to fit your usecase.  

    def cnot(N=None, control=0, target=1):
        if (control == 1 and target == 0) and N is None:
            N = 2
    
        if N is not None:
            return gate_expand_2toN(cnot(), N, control, target)
        else:
            return Qobj([[1, 0, 0, 0],
                         [0, 1, 0, 0],
                         [0, 0, 0, 1],
                         [0, 0, 1, 0]],
                        dims=[[2, 2], [2, 2]])
@sahmed95 Can a user defined gate be used in the qutip QIP?
@IIAOPSW : Thanks for the suggestion. I will try that. Nevertheless, it would be nice to have it in the official version of qutip.

You can use the following definition from Maslov, Dmitri, and Yunseong Nam. "Use of global interactions in efficient quantum circuit constructions." New Journal of Physics (2017).

![image](https://user-images.githubusercontent.com/4205827/36633263-2beff2a2-1960-11e8-83cd-2df60fb8e4da.png)

If I may, I would also suggest to add single qubit arbitrary rotation.

![image](https://user-images.githubusercontent.com/4205827/36633286-9c05c65c-1960-11e8-91f7-32c0990eee81.png)

In that case we will be able to write complete physical representation of any quantum circuit for trapped ion quantum computer.





This is a good, apparently simple issue to address within the upcoming GSOC project on `qip`. 
This might be of interest in the future months to you @BoxiLi.
This sounds good, I'll work on it.
Cc @BoxiLi @quantshah.

Resolving patch:
diff --git a/qutip/qip/circuit.py b/qutip/qip/circuit.py
--- a/qutip/qip/circuit.py
+++ b/qutip/qip/circuit.py
@@ -33,6 +33,9 @@
 
 from collections.abc import Iterable
 import warnings
+import inspect
+
+import numpy as np
 
 import numpy as np
 
@@ -47,7 +50,7 @@ class Gate(object):
     """
     Representation of a quantum gate, with its required parametrs, and target
     and control qubits.
-    
+
     Parameters
     ----------
     name : string
@@ -95,18 +98,18 @@ def __init__(self, name, targets=None, controls=None, arg_value=None,
             if self.controls is not None:
                 raise ValueError("Gate %s cannot have a control" % name)
 
-        if name in ["CNOT", "CSIGN", "CRX", "CRY", "CRZ"]:
+        elif name in ["CNOT", "CSIGN", "CRX", "CRY", "CRZ"]:
             if self.targets is None or len(self.targets) != 1:
                 raise ValueError("Gate %s requires one target" % name)
             if self.controls is None or len(self.controls) != 1:
                 raise ValueError("Gate %s requires one control" % name)
 
-        if name in ["SNOT", "RX", "RY", "RZ", "PHASEGATE"]:
+        elif name in ["SNOT", "RX", "RY", "RZ", "PHASEGATE"]:
             if self.controls is not None:
                 raise ValueError("Gate %s does not take controls" % name)
 
-        if name in ["RX", "RY", "RZ", "CPHASE", "SWAPalpha", "PHASEGATE",
-                    "GLOBALPHASE", "CRX", "CRY", "CRZ"]:
+        elif name in ["RX", "RY", "RZ", "CPHASE", "SWAPalpha", "PHASEGATE",
+                            "GLOBALPHASE", "CRX", "CRY", "CRZ"]:
             if arg_value is None:
                 raise ValueError("Gate %s requires an argument value" % name)
 
@@ -169,10 +172,28 @@ class QubitCircuit(object):
     """
     Representation of a quantum program/algorithm, maintaining a sequence
     of gates.
+
+    Parameters
+    ----------
+    N : int
+        Number of qubits in the system.
+    user_gates : dict
+        Define a dictionary of the custom gates. See examples for detail.
+    input_states : list
+        A list of string such as `0`,'+', "A", "Y". Only used for latex.
+
+    Examples
+    --------
+    >>> def user_gate():
+    ...     mat = np.array([[1.,   0],
+    ...                     [0., 1.j]])
+    ...     return Qobj(mat, dims=[[2], [2]])
+    >>> qc.QubitCircuit(2, user_gates={"T":user_gate})
+    >>> qc.add_gate("T", targets=[0])
     """
 
     def __init__(self, N, input_states=None, output_states=None,
-                 reverse_states=True):
+                 reverse_states=True, user_gates=None):
         # number of qubits in the register
         self.N = N
         self.reverse_states = reverse_states
@@ -180,6 +201,15 @@ def __init__(self, N, input_states=None, output_states=None,
         self.U_list = []
         self.input_states = [None for i in range(N)]
         self.output_states = [None for i in range(N)]
+        if user_gates is None:
+            self.user_gates = {}
+        else:
+            if isinstance(user_gates, dict):
+                self.user_gates = user_gates
+            else:
+                raise ValueError(
+                    "`user_gate` takes a python dictionary of the form"
+                    "{{str: gate_function}}, not {}".format(user_gates))
 
     def add_state(self, state, targets=None, state_type="input"):
         """
@@ -326,6 +356,10 @@ def add_circuit(self, qc, start=0):
                               [gate.targets[0] + start,
                                gate.targets[1] + start],
                               gate.controls + start, None, None)
+            elif gate.name in self.user_gates:
+                self.add_gate(
+                              gate.name, targets=gate.targets,
+                              arg_value=gate.arg_value)
 
     def remove_gate(self, index=None, end=None, name=None, remove="first"):
         """
@@ -408,34 +442,29 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
         qc_temp = QubitCircuit(self.N, self.reverse_states)
         temp_resolved = []
 
-        basis_1q = []
-        basis_2q = None
-
         basis_1q_valid = ["RX", "RY", "RZ"]
         basis_2q_valid = ["CNOT", "CSIGN", "ISWAP", "SQRTSWAP", "SQRTISWAP"]
 
         if isinstance(basis, list):
+            basis_1q = []
+            basis_2q = []
             for gate in basis:
-                if gate not in (basis_1q_valid + basis_2q_valid):
-                    raise ValueError("%s is not a valid basis gate" % gate)
-
                 if gate in basis_2q_valid:
-                    if basis_2q is not None:
-                        raise ValueError("At most one two-qubit gate allowed")
-                    basis_2q = gate
-
-                else:
+                    basis_2q.append(gate)
+                elif gate in basis_1q_valid:
                     basis_1q.append(gate)
-
+                else:
+                    raise NotImplementedError(
+                        "%s is not a valid basis gate" % gate)
             if len(basis_1q) == 1:
                 raise ValueError("Not sufficient single-qubit gates in basis")
             elif len(basis_1q) == 0:
                 basis_1q = ["RX", "RY", "RZ"]
 
-        else:
+        else:  # only one 2q gate is given as basis
             basis_1q = ["RX", "RY", "RZ"]
             if basis in basis_2q_valid:
-                basis_2q = basis
+                basis_2q = [basis]
             else:
                 raise ValueError("%s is not a valid two-qubit basis gate"
                                  % basis)
@@ -469,11 +498,13 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
                                           arg_label=gate.arg_label))
                 temp_resolved.append(Gate("RZ", gate.targets, None,
                                           gate.arg_value, gate.arg_label))
+            elif gate.name in basis_2q:  # ignore all gate in 2q basis
+                temp_resolved.append(gate)
             elif gate.name == "CPHASE":
                 raise NotImplementedError("Cannot be resolved in this basis")
             elif gate.name == "CNOT":
                 temp_resolved.append(gate)
-            elif gate.name == "CSIGN" and basis_2q is not "CSIGN":
+            elif gate.name == "CSIGN":
                 temp_resolved.append(Gate("RY", gate.targets, None,
                                           arg_value=np.pi / 2,
                                           arg_label=r"\pi/2"))
@@ -491,14 +522,17 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
                 raise NotImplementedError("Cannot be resolved in this basis")
             elif gate.name == "SWAPalpha":
                 raise NotImplementedError("Cannot be resolved in this basis")
-            elif gate.name == "SWAP" and basis_2q is not "ISWAP":
-                temp_resolved.append(Gate("CNOT", gate.targets[0],
-                                          gate.targets[1]))
-                temp_resolved.append(Gate("CNOT", gate.targets[1],
-                                          gate.targets[0]))
-                temp_resolved.append(Gate("CNOT", gate.targets[0],
-                                          gate.targets[1]))
-            elif gate.name == "ISWAP" and basis_2q is not "ISWAP":
+            elif gate.name == "SWAP":
+                if "ISWAP" in basis_2q:  # dealed with separately
+                    temp_resolved.append(gate)
+                else:
+                    temp_resolved.append(
+                        Gate("CNOT", gate.targets[0], gate.targets[1]))
+                    temp_resolved.append(
+                        Gate("CNOT", gate.targets[1], gate.targets[0]))
+                    temp_resolved.append(
+                        Gate("CNOT", gate.targets[0], gate.targets[1]))
+            elif gate.name == "ISWAP":
                 temp_resolved.append(Gate("CNOT", gate.targets[0],
                                           gate.targets[1]))
                 temp_resolved.append(Gate("CNOT", gate.targets[1],
@@ -528,11 +562,9 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
                 temp_resolved.append(Gate("GLOBALPHASE", None, None,
                                           arg_value=np.pi / 2,
                                           arg_label=r"\pi/2"))
-            elif gate.name == "SQRTSWAP" and basis_2q not in ["SQRTSWAP",
-                                                              "ISWAP"]:
+            elif gate.name == "SQRTSWAP":
                 raise NotImplementedError("Cannot be resolved in this basis")
-            elif gate.name == "SQRTISWAP" and basis_2q not in ["SQRTISWAP",
-                                                               "ISWAP"]:
+            elif gate.name == "SQRTISWAP":
                 raise NotImplementedError("Cannot be resolved in this basis")
             elif gate.name == "FREDKIN":
                 temp_resolved.append(Gate("CNOT", gate.targets[0],
@@ -663,9 +695,11 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
                                           gate.controls,
                                           gate.arg_value, gate.arg_label))
             else:
-                temp_resolved.append(gate)
+                raise NotImplementedError(
+                    "Gate {} "
+                    "cannot be resolved.".format(gate.name))
 
-        if basis_2q == "CSIGN":
+        if "CSIGN" in basis_2q:
             for gate in temp_resolved:
                 if gate.name == "CNOT":
                     qc_temp.gates.append(Gate("RY", gate.targets, None,
@@ -678,7 +712,7 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
                                               arg_label=r"\pi/2"))
                 else:
                     qc_temp.gates.append(gate)
-        elif basis_2q == "ISWAP":
+        elif "ISWAP" in basis_2q:
             for gate in temp_resolved:
                 if gate.name == "CNOT":
                     qc_temp.gates.append(Gate("GLOBALPHASE", None, None,
@@ -725,7 +759,7 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
                                               arg_label=r"-\pi/2"))
                 else:
                     qc_temp.gates.append(gate)
-        elif basis_2q == "SQRTSWAP":
+        elif "SQRTSWAP" in basis_2q:
             for gate in temp_resolved:
                 if gate.name == "CNOT":
                     qc_temp.gates.append(Gate("RY", gate.targets, None,
@@ -751,7 +785,7 @@ def resolve_gates(self, basis=["CNOT", "RX", "RY", "RZ"]):
                                               arg_label=r"-\pi/2"))
                 else:
                     qc_temp.gates.append(gate)
-        elif basis_2q == "SQRTISWAP":
+        elif "SQRTISWAP" in basis_2q:
             for gate in temp_resolved:
                 if gate.name == "CNOT":
                     qc_temp.gates.append(Gate("RY", gate.controls, None,
@@ -837,7 +871,7 @@ def adjacent_gates(self):
             resolved non-adjacent gates.
 
         """
-        temp = QubitCircuit(self.N, self.reverse_states)
+        temp = QubitCircuit(self.N, reverse_states=self.reverse_states)
         swap_gates = ["SWAP", "ISWAP", "SQRTISWAP", "SQRTSWAP", "BERKELEY",
                       "SWAPalpha"]
 
@@ -902,7 +936,9 @@ def adjacent_gates(self):
                     i += 1
 
             else:
-                temp.gates.append(gate)
+                raise NotImplementedError(
+                    "`adjacent_gates` is not defined for "
+                    "gate {}.".format(gate.name))
 
         return temp
 
@@ -933,7 +969,7 @@ def propagators(self):
             elif gate.name == "PHASEGATE":
                 self.U_list.append(phasegate(gate.arg_value, self.N,
                                              gate.targets[0]))
-            if gate.name == "CRX":
+            elif gate.name == "CRX":
                 self.U_list.append(controlled_gate(rx(gate.arg_value),
                                                    N=self.N,
                                                    control=gate.controls[0],
@@ -978,6 +1014,25 @@ def propagators(self):
                                            gate.targets[0]))
             elif gate.name == "GLOBALPHASE":
                 self.U_list.append(globalphase(gate.arg_value, self.N))
+            elif gate.name in self.user_gates:
+                if gate.controls is not None:
+                    raise ValueError(
+                        "A user defined gate {} takes only  "
+                        "`targets` variable.".format(gate.name))
+                func = self.user_gates[gate.name]
+                para_num = len(inspect.getfullargspec(func)[0])
+                if para_num == 0:
+                    oper = func()
+                elif para_num == 1:
+                    oper = func(gate.arg_value)
+                else:
+                    raise ValueError(
+                        "gate function takes at most one parameters.")
+                self.U_list.append(expand_oper(oper, self.N, gate.targets))
+
+            else:
+                raise NotImplementedError(
+                    "{} gate is an unknown gate.".format(gate.name))
 
         return self.U_list
 
@@ -994,10 +1049,11 @@ def latex_code(self):
                     if len(gate.targets) > 1:
                         if gate.name == "SWAP":
                             col.append(r" \qswap \qwx ")
- 
-                        elif ((self.reverse_states and n == max(gate.targets)) or
-                            (not self.reverse_states
-                             and n == min(gate.targets))):
+
+                        elif ((self.reverse_states and
+                                n == max(gate.targets)) or
+                                (not self.reverse_states and
+                                    n == min(gate.targets))):
                             col.append(r" \multigate{%d}{%s} " %
                                        (len(gate.targets) - 1,
                                         _gate_label(gate.name,
@@ -1022,8 +1078,8 @@ def latex_code(self):
 
                 elif (not gate.controls and not gate.targets):
                     # global gate
-                    if ((self.reverse_states and n == self.N - 1)
-                            or (not self.reverse_states and n == 0)):
+                    if ((self.reverse_states and n == self.N - 1) or
+                            (not self.reverse_states and n == 0)):
                         col.append(r" \multigate{%d}{%s} " %
                                    (self.N - 1,
                                     _gate_label(gate.name, gate.arg_label)))
diff --git a/qutip/qip/gates.py b/qutip/qip/gates.py
--- a/qutip/qip/gates.py
+++ b/qutip/qip/gates.py
@@ -30,7 +30,11 @@
 #    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 #    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ###############################################################################
-from __future__ import division
+import numbers
+from collections.abc import Iterable
+from itertools import product
+from functools import partial, reduce
+from operator import mul
 
 import numpy as np
 import scipy.sparse as sp
@@ -39,16 +43,15 @@
 from qutip.tensor import tensor
 from qutip.states import fock_dm
 
-from itertools import product
-from functools import partial, reduce
-from operator import mul
 
-__all__ = ['rx', 'ry', 'rz', 'sqrtnot', 'snot', 'phasegate', 'cphase', 'cnot',
+__all__ = ['rx', 'ry', 'rz', 'sqrtnot', 'snot', 'phasegate', 'qrot',
+           'cphase', 'cnot',
            'csign', 'berkeley', 'swapalpha', 'swap', 'iswap', 'sqrtswap',
-           'sqrtiswap', 'fredkin', 'toffoli', 'rotation', 'controlled_gate',
+           'sqrtiswap', 'fredkin', 'molmer_sorensen',
+           'toffoli', 'rotation', 'controlled_gate',
            'globalphase', 'hadamard_transform', 'gate_sequence_product',
            'gate_expand_1toN', 'gate_expand_2toN', 'gate_expand_3toN',
-           'qubit_clifford_group']
+           'qubit_clifford_group', 'expand_oper']
 
 #
 # Single Qubit Gates
@@ -176,6 +179,37 @@ def phasegate(theta, N=None, target=0):
                     dims=[[2], [2]])
 
 
+def qrot(theta, phi, N=None, target=0):
+    """
+    Single qubit rotation driving by Rabi oscillation with 0 detune.
+
+    Parameters
+    ----------
+    phi : float
+        The inital phase of the rabi pulse.
+    theta : float
+        The duration of the rabi pulse.
+    N : int
+        Number of qubits in the system.
+    target : int
+        The index of the target qubit.
+
+    Returns
+    -------
+    qrot_gate : :class:`qutip.Qobj`
+        Quantum object representation of physical qubit rotation under
+        a rabi pulse.
+    """
+    if N is not None:
+        return expand_oper(qrot(theta, phi), N=N, targets=target)
+    else:
+        return Qobj(
+            [
+                [np.cos(theta/2.), -1.j*np.exp(-1.j*phi)*np.sin(theta/2.)],
+                [-1.j*np.exp(1.j*phi)*np.sin(theta/2.), np.cos(theta/2.)]
+            ])
+
+
 #
 # 2 Qubit Gates
 #
@@ -487,6 +521,40 @@ def sqrtiswap(N=None, targets=[0, 1]):
                               [0, 0, 0, 1]]), dims=[[2, 2], [2, 2]])
 
 
+def molmer_sorensen(theta, N=None, targets=[0, 1]):
+    """
+    Quantum object of a Mølmer–Sørensen gate.
+
+    Parameters
+    ----------
+    theta: float
+        The duration of the interaction pulse.
+    N: int
+        Number of qubits in the system.
+    target: int
+        The indices of the target qubits.
+
+    Returns
+    -------
+    molmer_sorensen_gate: :class:`qutip.Qobj`
+        Quantum object representation of the Mølmer–Sørensen gate.
+    """
+    if targets != [0, 1] and N is None:
+        N = 2
+
+    if N is not None:
+        return expand_oper(molmer_sorensen(theta), N, targets=targets)
+    else:
+        return Qobj(
+            [
+                [np.cos(theta/2.), 0, 0, -1.j*np.sin(theta/2.)],
+                [0, np.cos(theta/2.), -1.j*np.sin(theta/2.), 0],
+                [0, -1.j*np.sin(theta/2.), np.cos(theta/2.), 0],
+                [-1.j*np.sin(theta/2.), 0, 0, np.cos(theta/2.)]
+            ],
+            dims=[[2, 2], [2, 2]])
+
+
 #
 # 3 Qubit Gates
 #
@@ -1045,3 +1113,97 @@ def gate_expand_3toN(U, N, controls=[0, 1], target=2):
         p = [p[p1[p2[k]]] for k in range(N)]
 
     return tensor([U] + [identity(2)] * (N - 3)).permute(p)
+
+
+def _check_qubits_oper(oper):
+    """
+    Check if it is an operator acting on a qubit system.
+
+    Parameters
+    ----------
+    oper : :class:`qutip.Qboj`
+        The quantum object to be checked.
+    """
+    check = True
+    if oper.dims[0] != oper.dims[1]:
+        check = False
+    if oper.dims[0] != len(oper.dims[0]) * [2]:
+        check = False
+    if not check:
+        raise ValueError(
+            "The following operator is not an "
+            "operator on a qubits system:\n{}".format(oper))
+
+
+def expand_oper(oper, N, targets):
+    """
+    Expand a qubits operator to one that acts on a N-qutbis system.
+
+    Parameters
+    ----------
+    oper : :class:`qutip.Qobj`
+        An operator acts on qubits, the type of the `Qobj`
+        has to be an operator
+        and the dimension matches the tensored qubit Hilbertspace
+        e.g. dims = [[2,2,2],[2,2,2]]
+    N : int
+        The number of qubits in the system.
+    targets : int or list of int
+        The indices of qubits that are acted on.
+
+    Returns
+    -------
+    expanded_oper : :class:`qutip.Qobj`
+        The expanded qubits operator acting on a system with N qubits
+
+    Note
+    ----
+    This is equivalent to gate_expand_1toN, gate_expand_2toN,
+    gate_expand_3toN in `qutip.qip.gate.py`, but works for any dimension.
+    """
+    _check_qubits_oper(oper)
+    req_num = len(oper.dims[0])
+    if req_num > N:
+        raise ValueError(
+            "The dimension of the operator "
+            "cannot be larger than the system dimension "
+            "N={}.".format(N))
+
+    # Check validity of targets
+    if targets is None:
+        targets = list(range(len(oper.dims[0])))
+    elif isinstance(targets, numbers.Integral):
+        targets = [targets]
+    elif isinstance(targets, Iterable):  # correct type
+        if not all([isinstance(t, numbers.Integral) for t in targets]):
+            raise ValueError(
+                "targets should be "
+                "an integer or a list of integer, but {} "
+                "was given.".format(targets))
+        if len(targets) != req_num:  # correct number of targets
+            raise ValueError(
+                "The given operator needs {} "
+                "target qutbis, "
+                "but {} given.".format(
+                    req_num, len(targets)))
+    else:
+        raise ValueError(
+            "targets should be "
+            "an integer or a list of integer, but {} "
+            "was given.".format(targets))
+
+    # Generate the correct order for qubits permutation,
+    # eg. if N = 5, targets = [3,0], the order is [1,x,x,0,x].
+    # If the operator is cnot,
+    # this order means that the 3rd qubit control the 0th qubit.
+    new_order = list(range(N))
+    old_ind = range(len(targets))
+    for i in old_ind:
+        new_order[targets[i]] = i
+    old_ind_set = set(old_ind)
+    targets_set = set(targets)
+    mod_value = targets_set.difference(old_ind_set)
+    mod_ind = old_ind_set.difference(targets_set)
+    for ind, value in zip(mod_ind, mod_value):
+        new_order[ind] = value
+    return tensor([oper] + [identity(2)]*(N-len(targets))).permute(new_order)
diff --git a/qutip/qip/models/spinchain.py b/qutip/qip/models/spinchain.py
--- a/qutip/qip/models/spinchain.py
+++ b/qutip/qip/models/spinchain.py
@@ -360,6 +360,13 @@ def adjacent_gates(self, qc, setup="linear"):
 
         return qc_t
 
+    def optimize_circuit(self, qc):
+        self.qc0 = qc
+        self.qc1 = self.adjacent_gates(self.qc0)
+        self.qc2 = self.qc1.resolve_gates(
+            basis=["SQRTISWAP", "ISWAP", "RX", "RZ"])
+        return self.qc2
+
 
 class LinearSpinChain(SpinChain):
     """
@@ -380,11 +387,8 @@ def get_ops_labels(self):
                 [r"$\sigma_x^%d\sigma_x^{%d} + \sigma_y^%d\sigma_y^{%d}$"
                  % (n, n, n + 1, n + 1) for n in range(self.N - 1)])
 
-    def optimize_circuit(self, qc):
-        self.qc0 = qc
-        self.qc1 = self.adjacent_gates(self.qc0, "linear")
-        self.qc2 = self.qc1.resolve_gates(basis=["ISWAP", "RX", "RZ"])
-        return self.qc2
+    def adjacent_gates(self, qc):
+        return super(LinearSpinChain, self).adjacent_gates(qc, "linear")
 
 
 class CircularSpinChain(SpinChain):
@@ -420,8 +424,5 @@ def get_ops_labels(self):
                  % (n, n, (n + 1) % self.N, (n + 1) % self.N)
                  for n in range(self.N)])
 
-    def optimize_circuit(self, qc):
-        self.qc0 = qc
-        self.qc1 = self.adjacent_gates(self.qc0, "circular")
-        self.qc2 = self.qc1.resolve_gates(basis=["ISWAP", "RX", "RZ"])
-        return self.qc2
+    def adjacent_gates(self, qc):
+        return super(CircularSpinChain, self).adjacent_gates(qc, "circular")


Test patch:
diff --git a/qutip/tests/test_gates.py b/qutip/tests/test_gates.py
--- a/qutip/tests/test_gates.py
+++ b/qutip/tests/test_gates.py
@@ -33,13 +33,16 @@
 
 import itertools
 import numpy as np
-from numpy.testing import assert_, run_module_suite
+from numpy.testing import assert_, assert_allclose, run_module_suite
 from qutip.states import basis, ket2dm
 from qutip.operators import identity, qeye, sigmax, sigmay, sigmaz
-from qutip.qip import (rx, ry, rz, phasegate, cnot, swap, iswap,
-                       sqrtswap, toffoli, fredkin, gate_expand_3toN, qubit_clifford_group)
-from qutip.random_objects import rand_ket, rand_herm
+from qutip.qip import (rx, ry, rz, phasegate, qrot, cnot, swap, iswap,
+                       sqrtswap, molmer_sorensen,
+                       toffoli, fredkin, gate_expand_3toN, 
+                       qubit_clifford_group, expand_oper)
+from qutip.random_objects import rand_ket, rand_herm, rand_unitary
 from qutip.tensor import tensor
+from qutip.qobj import Qobj
 
 
 class TestGates:
@@ -74,7 +77,7 @@ def _prop_identity(self, U, tol=1e-6):
         Returns True if and only if U is proportional to the
         identity.
         """
-        U0 = complex(U[0, 0]) #scipy 1.3 return 0 dims array.
+        U0 = complex(U[0, 0])  # scipy 1.3 return 0 dims array.
         if U0 != 0:
             norm_U = U / U0
             return (qeye(U.dims[0]) - norm_U).norm() <= tol
@@ -283,6 +286,76 @@ def _rand_3qubit_gate(N=None, controls=None, k=None):
                         o2 = psi_ref_out.overlap(psi_ref_in)
                         assert_(abs(o1 - o2) < 1e-12)
 
+    def test_expand_oper(self):
+        """
+        gate : expand qubits operator to a N qubits system.
+        """
+        # random single qubit gate test, integer as target
+        r = rand_unitary(2)
+        assert(expand_oper(r, 3, 0) == tensor([r, identity(2), identity(2)]))
+        assert(expand_oper(r, 3, 1) == tensor([identity(2), r, identity(2)]))
+        assert(expand_oper(r, 3, 2) == tensor([identity(2), identity(2), r]))
+
+        # random 2qubits gate test, list as target
+        r2 = rand_unitary(4)
+        r2.dims = [[2, 2], [2, 2]]
+        assert(expand_oper(r2, 3, [2, 1]) == tensor(
+            [identity(2), r2.permute([1, 0])]))
+        assert(expand_oper(r2, 3, [0, 1]) == tensor(
+            [r2, identity(2)]))
+        assert(expand_oper(r2, 3, [0, 2]) == tensor(
+            [r2, identity(2)]).permute([0, 2, 1]))
+
+        # cnot expantion, qubit 2 control qubit 0
+        assert(expand_oper(cnot(), 3, [2, 0]) == Qobj([
+            [1., 0., 0., 0., 0., 0., 0., 0.],
+            [0., 0., 0., 0., 0., 1., 0., 0.],
+            [0., 0., 1., 0., 0., 0., 0., 0.],
+            [0., 0., 0., 0., 0., 0., 0., 1.],
+            [0., 0., 0., 0., 1., 0., 0., 0.],
+            [0., 1., 0., 0., 0., 0., 0., 0.],
+            [0., 0., 0., 0., 0., 0., 1., 0.],
+            [0., 0., 0., 1., 0., 0., 0., 0.]],
+            dims=[[2, 2, 2], [2, 2, 2]]))
+
+    def test_molmer_sorensen(self):
+        """
+        gate: test for the molmer_sorensen gate
+        """
+        assert_allclose(
+            molmer_sorensen(np.pi, targets=[0, 1]),
+            Qobj(-1j*np.array(
+                 [[0, 0, 0, 1],
+                  [0, 0, 1, 0],
+                  [0, 1, 0, 0],
+                  [1, 0, 0, 0]]), dims=[[2, 2], [2, 2]]),
+            atol=1e-15)
+        assert_allclose(
+            molmer_sorensen(2*np.pi, targets=[0, 1]),
+            Qobj(-np.array(
+                 [[1, 0, 0, 0],
+                  [0, 1, 0, 0],
+                  [0, 0, 1, 0],
+                  [0, 0, 0, 1]]), dims=[[2, 2], [2, 2]]),
+            atol=1e-15)
+        assert_allclose(
+            molmer_sorensen(np.pi/2, N=4, targets=[1, 2]),
+            tensor([identity(2), molmer_sorensen(np.pi/2), identity(2)])
+        )
+
+    def test_qrot(self):
+        """
+        gate: test for the qubit rotation gate
+        """
+        assert_allclose(qrot(0, 0), identity(2))
+        assert_allclose(
+            qrot(np.pi, np.pi/2),
+            Qobj([[0, -1], [1, 0]]),
+            atol=1e-15)
+        assert_allclose(
+            qrot(np.pi/4., np.pi/3., N=2, target=1),
+            tensor([identity(2), qrot(np.pi/4., np.pi/3.)])
+        )
 
 if __name__ == "__main__":
     run_module_suite()
diff --git a/qutip/tests/test_qubitcircuit.py b/qutip/tests/test_qubitcircuit.py
--- a/qutip/tests/test_qubitcircuit.py
+++ b/qutip/tests/test_qubitcircuit.py
@@ -31,9 +31,12 @@
 #    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ###############################################################################
 
-from numpy.testing import assert_, run_module_suite
-from qutip.qip.gates import gate_sequence_product
+import numpy as np
+from numpy.testing import assert_, assert_allclose, run_module_suite
+from qutip.qip.gates import gate_sequence_product, rx, identity, expand_oper
 from qutip.qip.circuit import QubitCircuit, Gate
+from qutip.tensor import tensor
+from qutip.qobj import Qobj
 
 
 class TestQubitCircuit:
@@ -228,5 +231,32 @@ def test_reverse(self):
         assert_(qc.input_states[2] == None)
         assert_(qc.output_states[1] == "+")
 
+    def test_user_gate(self):
+        """
+        User defined gate for QubitCircuit
+        """
+        def customer_gate1(arg_values):
+            mat = np.zeros((4, 4), dtype=np.complex)
+            mat[0, 0] = mat[1, 1] = 1.
+            mat[2:4, 2:4] = rx(arg_values)
+            return Qobj(mat, dims=[[2, 2], [2, 2]])
+
+        def customer_gate2(arg_values):
+            mat = np.array([[1.,   0],
+                            [0., 1.j]])
+            return Qobj(mat, dims=[[2], [2]])
+
+        qc = QubitCircuit(3)
+        qc.user_gates = {"CTRLRX": customer_gate1,
+                         "T": customer_gate2}
+        qc.add_gate("CTRLRX", targets=[1, 2], arg_value=np.pi/2)
+        qc.add_gate("T", targets=[1])
+        props = qc.propagators()
+        result1 = tensor(identity(2), customer_gate1(np.pi/2))
+        assert_allclose(props[0], result1)
+        result2 = tensor(identity(2), customer_gate2(None), identity(2))
+        assert_allclose(props[1], result2)
+
+
 if __name__ == "__main__":
     run_module_suite()