Repository: qutip/qutip

Issue:
Consider deprecation of eseries and essolve
As part of the creation of the `core` package and the data layer, I'm hitting basically every single part of QuTiP, so I'm flagging parts we might want to consider for removal.

`eseries` is completely eclipsed by `QobjEvo`, so I think we can definitely get rid of that.  `essolve` is rather specific and not very used, I suspect.  We _could_ keep `essolve` without too much effort, but we should change the internals to use `QobjEvo` (though I also wouldn't be averse to removing it too).

@ajgpitch, @Ericgig, @nathanshammah.


Discussion:
I agree that on top level, these may seldom be used. It is not straightforwardly clear what is the added value of removing them, if no one is complaining or they are not hard to maintain. It may make things cleaner, but I am not aware of the implications. `eseries` seems used in the correlation module, possibly (marginally) also in `expect`. 

If QobjEvo is a more modern way of applying`essolve`, that is fine with me, it would be nice to restructure it that way. My advice before switching to QobjEvo, is that its use be first well documented in the documentation's Users guide, which could be done by copy-pasting chunks from the notebook.  
`eseries` isn't really used by `correlation`, it's just a possible solver, and `expect` doesn't use it, it just currently has to support it.  Removing `eseries` would involve just removing that part of the solver.

`essolve` only functions with time-independent Hamiltonians anyway, and `QobjEvo` can represent a full superset of `eseries` operations, and is an awful lot faster at it too.  I agree that the documentation can definitely be improved - I'll be working on that later on in the project.

No-one's complaining about the difficult of maintaining them, because nobody _is_ maintaining them.  `eseries` hasn't been meaningfully touched since 2014, and there's been an issue recently (#1262) where somebody pointed out that `essolve` doesn't do really simple parts of its spec.
Removed in `dev.major` already.  We'll issue deprecation warnings in an upcoming 4.x branch.

Resolving patch:
diff --git a/qutip/correlation.py b/qutip/correlation.py
--- a/qutip/correlation.py
+++ b/qutip/correlation.py
@@ -1152,14 +1152,13 @@ def _correlation_es_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, c_op):
     L = liouvillian(H, c_ops)
 
     corr_mat = np.zeros([np.size(tlist), np.size(taulist)], dtype=complex)
-    solES_t = ode2es(L, rho0)
 
+    solES_t = ode2es(L, rho0)
     # evaluate the correlation function
     for t_idx in range(len(tlist)):
         rho_t = esval(solES_t, [tlist[t_idx]])
         solES_tau = ode2es(L, c_op * rho_t * a_op)
         corr_mat[t_idx, :] = esval(expect(b_op, solES_tau), taulist)
-
     return corr_mat
 
 
@@ -1182,19 +1181,15 @@ def _spectrum_es(H, wlist, c_ops, a_op, b_op):
 
     # eseries solution for (b * rho0)(t)
     es = ode2es(L, b_op * rho0)
-
     # correlation
     corr_es = expect(a_op, es)
-
     # covariance
     cov_es = corr_es - a_op_ss * b_op_ss
-    # tidy up covariance (to combine, e.g., zero-frequency components that cancel)
+    # tidy up covariance (to combine, e.g., zero-frequency components that
+    # cancel)
     cov_es.tidyup()
-
     # spectrum
-    spectrum = esspec(cov_es, wlist)
-
-    return spectrum
+    return esspec(cov_es, wlist)
 
 
 # Monte Carlo solvers
diff --git a/qutip/eseries.py b/qutip/eseries.py
--- a/qutip/eseries.py
+++ b/qutip/eseries.py
@@ -37,6 +37,8 @@
 import scipy.sparse as sp
 from qutip.qobj import Qobj
 
+import warnings
+
 
 class eseries():
     """
@@ -67,6 +69,11 @@ class eseries():
     __array_priority__ = 101
 
     def __init__(self, q=None, s=np.array([])):
+        warnings.warn(
+            "eseries is to be removed in QuTiP 5.0,"
+            " consider swapping to QobjEvo for general time dependence.",
+            DeprecationWarning, stacklevel=2,
+        )
 
         if isinstance(s, (int, float, complex)):
             s = np.array([s])
diff --git a/qutip/essolve.py b/qutip/essolve.py
--- a/qutip/essolve.py
+++ b/qutip/essolve.py
@@ -44,11 +44,31 @@
 from qutip.solver import Result
 from qutip.operators import qzero
 
+# Only used for deprecation warnings.
+import functools
+import warnings
+
+
+def _deprecate(alternative):
+    def decorated(f):
+        message = (
+            f"{f.__name__} is to be removed in QuTiP 5.0"
+            f", consider swapping to {alternative}."
+        )
+
+        @functools.wraps(f)
+        def out(*args, **kwargs):
+            warnings.warn(message, DeprecationWarning, stacklevel=2)
+            return f(*args, **kwargs)
+        return out
+    return decorated
+
 
 # -----------------------------------------------------------------------------
 # pass on to wavefunction solver or master equation solver depending on whether
 # any collapse operators were given.
 #
+@_deprecate("mesolve")
 def essolve(H, rho0, tlist, c_op_list, e_ops):
     """
     Evolution of a state vector or density matrix (`rho0`) for a given
@@ -118,6 +138,7 @@ def essolve(H, rho0, tlist, c_op_list, e_ops):
 # -----------------------------------------------------------------------------
 #
 #
+@_deprecate("direct eigenstate and -value calculation")
 def ode2es(L, rho0):
     """Creates an exponential series that describes the time evolution for the
     initial density matrix (or state vector) `rho0`, given the Liouvillian


Test patch:
diff --git a/qutip/tests/test_qubit_evolution.py b/qutip/tests/test_qubit_evolution.py
--- a/qutip/tests/test_qubit_evolution.py
+++ b/qutip/tests/test_qubit_evolution.py
@@ -33,8 +33,11 @@
 
 import numpy as np
 from numpy.testing import run_module_suite, assert_equal
-from qutip import (sigmax, sigmay, sigmaz, sigmam, mesolve, mcsolve, essolve,
-                   basis)
+import pytest
+
+from qutip import (
+    sigmax, sigmay, sigmaz, sigmam, mesolve, mcsolve, essolve, basis,
+)
 
 
 def _qubit_integrate(tlist, psi0, epsilon, delta, g1, g2, solver):
@@ -122,7 +125,8 @@ def test_ESSolverCase1():
     psi0 = basis(2, 0)          # initial state
     tlist = np.linspace(0, 5, 200)
 
-    sx, sy, sz = _qubit_integrate(tlist, psi0, epsilon, delta, g1, g2, "es")
+    with pytest.deprecated_call():
+        sx, sy, sz = _qubit_integrate(tlist, psi0, epsilon, delta, g1, g2, "es")
 
     sx_analytic = np.zeros(np.shape(tlist))
     sy_analytic = -np.sin(2 * np.pi * tlist) * np.exp(-tlist * g2)