Repository: qutip/qutip

Issue:
Callback function form for c_ops in mesolve does not work
This issue is on behalf of [a post in the forums by Louis Fry](https://groups.google.com/forum/#!topic/qutip/93uSuJGK_Jk) who is trying to implement Lindblad operators which cannot be written in the form of a known `f(t) * A`, because the operators are related to the instantaneous eigenstates of a time-dependent Hamiltonian.

The documentation in the docstring of `mesolve` says that `c_ops` may be a callback function of the signature `f(t, args) -> Qobj`, similarly to the Hamiltonian.  This is not the case, and as best as I can tell, has never been the case.

Basic reproduction (though the functionality is just completely missing, so this won't ever succeed).
```python
import qutip
def c_ops(t, args):
    return qutip.create(3)
qutip.mesolve(qutip.num(3), qutip.basis(3, 1).proj(), [0, 1], c_ops)
```

## History

- @jrjohansson originally wrote the docstring saying that `c_ops` could be a callback function in 2012 (pre-QuTiP 1.1.4) in commit 1bf006d4, although the code did not support it at this time
- in issue #40 (2013), he commented that this functionality should be implemented, and this issue was closed without further action in 2018.
- in issue #223 (2014) there is some further discussion, but nothing is done until @Ericgig closes it in 2019, saying it is addressed by QuTiP 4.4.0 (the big swap to QobjEvo), but this doesn't appear to be the case.

As of tag `v4.4.0`, the code does not support it.  The failing line of code would be https://github.com/qutip/qutip/blob/fc9d9915d0f6242587828ce716ff3cd444edf82a/qutip/mesolve.py#L227-L236
as a function type is truth-y but has no length, but the underlying problem is more just that there's no support for it at all - there's no check for `callable(c_ops)`.

At the current `master` at the time of writing (QuTiP ~4.5.1, 4102b99), we're in the same place as at tag `v4.4.0`.

## Solution

In previous issues, people have been told to rewrite the Liouvillian in the supported form, however that's not reasonably possible here.  Technically it can be well-approximated by using an interpolation for each individual matrix element, as the time-dependent Hamiltonian can be solved ahead of time, but that's not at all sensible.

Most pressingly, the documentation is currently wrong and should be changed to reflect reality.  Secondly, we have to decide whether we're going to implement this functionality at all, perhaps as a part of @Ericgig's refactor of the solvers?


Discussion:
There is no support at all right now, but it is in progress.
It should work with the QobjEvoFunc PR, but it will take a quite some time before it is finished, an more before it is merged and released, it probably won't come before v5.

For now, H can be a callback to a liouvillian. So he can make it work like this:
```
def L(t, args):
     H = H(t, ...)
     c_op = C(H, t, ...)
     return qutip.liouvillian(H, [c_op])

mesolve(L, ..., c_ops=[])
```







Oh of course, I had `mcsolve` in mind as to why that couldn't be done (and in that case, my interpolation wouldn't work either because of the separation of the decay channels)... Whoops!

So the only solution right now is to correct the documentation.  I'll do that now.
Mentioning issue #1254 for book-keeping 

Resolving patch:
diff --git a/qutip/mesolve.py b/qutip/mesolve.py
--- a/qutip/mesolve.py
+++ b/qutip/mesolve.py
@@ -65,7 +65,7 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None,
     set of collapse operators, or a Liouvillian.
 
     Evolve the state vector or density matrix (`rho0`) using a given
-    Hamiltonian (`H`) and an [optional] set of collapse operators
+    Hamiltonian or Liouvillian (`H`) and an optional set of collapse operators
     (`c_ops`), by integrating the set of ordinary differential equations
     that define the system. In the absence of collapse operators the system is
     evolved according to the unitary evolution of the Hamiltonian.
@@ -78,25 +78,25 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None,
 
     If either `H` or the Qobj elements in `c_ops` are superoperators, they
     will be treated as direct contributions to the total system Liouvillian.
-    This allows to solve master equations that are not on standard Lindblad
-    form by passing a custom Liouvillian in place of either the `H` or `c_ops`
-    elements.
+    This allows the solution of master equations that are not in standard
+    Lindblad form.
 
     **Time-dependent operators**
 
-    For time-dependent problems, `H` and `c_ops` can be callback
-    functions that takes two arguments, time and `args`, and returns the
-    Hamiltonian or Liouvillian for the system at that point in time
-    (*callback format*).
-
-    Alternatively, `H` and `c_ops` can be a specified in a nested-list format
-    where each element in the list is a list of length 2, containing an
-    operator (:class:`qutip.qobj`) at the first element and where the
-    second element is either a string (*list string format*), a callback
+    For time-dependent problems, `H` and `c_ops` can be a specified in a
+    nested-list format where each element in the list is a list of length 2,
+    containing an operator (:class:`qutip.qobj`) at the first element and where
+    the second element is either a string (*list string format*), a callback
     function (*list callback format*) that evaluates to the time-dependent
     coefficient for the corresponding operator, or a NumPy array (*list
     array format*) which specifies the value of the coefficient to the
-    corresponding operator for each value of t in tlist.
+    corresponding operator for each value of t in `tlist`.
+
+    Alternatively, `H` (but not `c_ops`) can be a callback function with the
+    signature `f(t, args) -> Qobj` (*callback format*), which can return the
+    Hamiltonian or Liouvillian superoperator at any point in time.  If the
+    equation cannot be put in standard Lindblad form, then this time-dependence
+    format must be used.
 
     *Examples*
 
@@ -114,7 +114,7 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None,
 
     In all cases of time-dependent operators, `args` is a dictionary of
     parameters that is used when evaluating operators. It is passed to the
-    callback functions as second argument.
+    callback functions as their second argument.
 
     **Additional options**
 
@@ -128,20 +128,20 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None,
 
         If an element in the list-specification of the Hamiltonian or
         the list of collapse operators are in superoperator form it will be
-        added to the total Liouvillian of the problem with out further
+        added to the total Liouvillian of the problem without further
         transformation. This allows for using mesolve for solving master
-        equations that are not on standard Lindblad form.
+        equations that are not in standard Lindblad form.
 
     .. note::
 
-        On using callback function: mesolve transforms all :class:`qutip.qobj`
+        On using callback functions: mesolve transforms all :class:`qutip.Qobj`
         objects to sparse matrices before handing the problem to the integrator
         function. In order for your callback function to work correctly, pass
-        all :class:`qutip.qobj` objects that are used in constructing the
-        Hamiltonian via args. mesolve will check for :class:`qutip.qobj` in
+        all :class:`qutip.Qobj` objects that are used in constructing the
+        Hamiltonian via `args`. mesolve will check for :class:`qutip.Qobj` in
         `args` and handle the conversion to sparse matrices. All other
-        :class:`qutip.qobj` objects that are not passed via `args` will be
-        passed on to the integrator in scipy which will raise an NotImplemented
+        :class:`qutip.Qobj` objects that are not passed via `args` will be
+        passed on to the integrator in scipy which will raise a NotImplemented
         exception.
 
     Parameters


Test patch: