Repository: qutip/qutip

Issue:
Qobj.expm() not functioning properly for diagonal states which have a zero element in the diagonal
Qobj.expm() is currently not working properly for diagonal states which have a zero in the diagonal (these zeros don't become ones). An example:

``` python
>>> foo = qutip.Qobj([[1.,0.],[0.,0.]])
>>> foo.expm()
Quantum object: dims = [[2], [2]], shape = [2, 2], type = oper, isherm = True
 Qobj data =
 [[ 2.71828183  0.        ]
  [ 0.          0.        ]]
```

While the correct result should have been (notice the element in second row, second column):

``` python
Quantum object: dims = [[2], [2]], shape = [2, 2], type = oper, isherm = True
 Qobj data =
 [[ 2.71828183  0.        ]
  [ 0.          1.        ]]
```

On analysing the source code I found the the function `sp_expm` in `sparse.py` is responsible for this computation. The particular code is reproduced below:

``` python
def sp_expm(A, sparse=False):
    """
    Sparse matrix exponential.    
    """
    if _isdiag(A.indices, A.indptr, A.shape[0]):
        A.data = np.exp(A.data)
        return A
    if sparse:
        E = spla.expm(A.tocsc())
    else:
        E = spla.expm(A.toarray())
    return sp.csr_matrix(E)
```

Since `A.data` is stored in sparse matrix format, only non-zero elements are exponentiated. A way to avoid this problem would be to either completely remove the case of optimizing for diagonal states or modifying the appropriate code as:

``` python
def sp_expm(A, sparse=False):
    """
    Sparse matrix exponential.    
    """
    if _isdiag(A.indices, A.indptr, A.shape[0]):
        A = sp.diags(np.exp(A.diagonal()),format='csr')
        return A
    if sparse:
        E = spla.expm(A.tocsc())
    else:
        E = spla.expm(A.toarray())
    return sp.csr_matrix(E)
```

I am running qutip 3.2.0.dev-cb698ef
Python 3.5.1, numpy 1.10.4, scipy 0.17.0



Discussion:

Resolving patch:
diff --git a/qutip/sparse.py b/qutip/sparse.py
--- a/qutip/sparse.py
+++ b/qutip/sparse.py
@@ -383,7 +383,8 @@ def sp_expm(A, sparse=False):
     Sparse matrix exponential.    
     """
     if _isdiag(A.indices, A.indptr, A.shape[0]):
-        A.data = np.exp(A.data)
+        A = sp.diags(np.exp(A.diagonal()), shape=A.shape, 
+                    format='csr', dtype=complex)
         return A
     if sparse:
         E = spla.expm(A.tocsc())


Test patch:
diff --git a/qutip/tests/test_qobj.py b/qutip/tests/test_qobj.py
--- a/qutip/tests/test_qobj.py
+++ b/qutip/tests/test_qobj.py
@@ -40,7 +40,7 @@
 from qutip.random_objects import (rand_ket, rand_dm, rand_herm, rand_unitary,
                                   rand_super, rand_super_bcsz, rand_dm_ginibre)
 from qutip.states import basis, fock_dm, ket2dm
-from qutip.operators import create, destroy, num, sigmax, sigmay
+from qutip.operators import create, destroy, num, sigmax, sigmay, qeye
 from qutip.superoperator import spre, spost, operator_to_vector
 from qutip.superop_reps import to_super, to_choi, to_chi
 from qutip.tensor import tensor, super_tensor, composite
@@ -512,6 +512,13 @@ def test_QobjExpmExplicitlySparse():
     assert_((B.data.todense() - np.matrix(la.expm(data)) < 1e-10).all())
 
 
+def test_QobjExpmZeroOper():
+    "Qobj expm zero_oper (#493)"
+    A = Qobj(np.zeros((5,5), dtype=complex))
+    B = A.expm()
+    assert_(B == qeye(5))
+
+
 def test_Qobj_sqrtm():
     "Qobj sqrtm"
     data = np.random.random(