Repository: qutip/qutip

Issue:
qutip.qip.device.processor.Processor.run_analytically does not include drift
When calculating the propagators analytically it does not include the drift hamiltonian, which can result in a significantly different operator. This is particularly relevant to me as the drift hamiltonian changes depending on which qubit is being controlled because the basis is changed to a different rotating frame. This means I replace the drift when operating a pulse. If the drift is not included in the propagator calculation it results in a substantially incorrect matrix.

I think there should be an include_drift=True/False option in the function definition which if true adds the drift hamiltonian before it exponentiates.


Discussion:

Resolving patch:
diff --git a/qutip/qip/device/processor.py b/qutip/qip/device/processor.py
--- a/qutip/qip/device/processor.py
+++ b/qutip/qip/device/processor.py
@@ -637,19 +637,25 @@ def run_analytically(self, init_state=None, qc=None):
 
         Returns
         -------
-        evo_result: :class:`qutip.Result`
-            An instance of the class
-            :class:`qutip.Result` will be returned.
+        U_list: list
+            A list of propagators obtained for the physical implementation.
         """
         if init_state is not None:
             U_list = [init_state]
         else:
             U_list = []
         tlist = self.get_full_tlist()
-        # TODO replace this by get_complete_coeff
         coeffs = self.get_full_coeffs()
+
+        # Compute drift Hamiltonians
+        H_drift = 0
+        for drift_ham in self.drift.drift_hamiltonians:
+            H_drift += drift_ham.get_qobj(self.dims)
+
+        # Compute control Hamiltonians
         for n in range(len(tlist)-1):
-            H = sum([coeffs[m, n] * self.ctrls[m]
+            H = H_drift + sum(
+                [coeffs[m, n] * self.ctrls[m]
                     for m in range(len(self.ctrls))])
             dt = tlist[n + 1] - tlist[n]
             U = (-1j * H * dt).expm()
@@ -658,7 +664,9 @@ def run_analytically(self, init_state=None, qc=None):
 
         try:  # correct_global_phase are defined for ModelProcessor
             if self.correct_global_phase and self.global_phase != 0:
-                U_list.append(globalphase(self.global_phase, N=self.N))
+                U_list.append(globalphase(
+                    self.global_phase, N=self.num_qubits)
+                )
         except AttributeError:
             pass
 


Test patch:
diff --git a/qutip/tests/test_processor.py b/qutip/tests/test_processor.py
--- a/qutip/tests/test_processor.py
+++ b/qutip/tests/test_processor.py
@@ -324,11 +324,19 @@ def testDrift(self):
         Test for the drift Hamiltonian
         """
         processor = Processor(N=1)
-        processor.add_drift(sigmaz(), 0)
-        tlist = np.array([0., 1., 2.])
-        processor.add_pulse(Pulse(identity(2), 0, tlist, False))
+        processor.add_drift(sigmax() / 2, 0)
+        tlist = np.array([0., np.pi, 2*np.pi, 3*np.pi])
+        processor.add_pulse(Pulse(None, None, tlist, False))
         ideal_qobjevo, _ = processor.get_qobjevo(noisy=True)
-        assert_equal(ideal_qobjevo.cte, sigmaz())
+        assert_equal(ideal_qobjevo.cte, sigmax() / 2)
+
+        init_state = basis(2)
+        propagators = processor.run_analytically()
+        analytical_result = init_state
+        for unitary in propagators:
+            analytical_result = unitary * analytical_result
+        fid = fidelity(sigmax() * init_state, analytical_result)
+        assert((1 - fid) < 1.0e-6)
 
     def testChooseSolver(self):
         # setup and fidelity without noise