Repository: scipy/scipy

Issue:
Solving sparse linear systems in CSR format with complex values
Hello, I'm working on using some of scipy's sparse solvers for both real and complex problems, and I seem to have found an issue when solving linear systems Ax=b where A is a real -valued matrix in csc format, and b is a complex numpy array.  When I try to solve such a system, I get the following warning:

>/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/numpy/core/numeric.py:460: 
> ComplexWarning: Casting complex values to real discards the imaginary part 
> return array(a, dtype, copy=False, order=order)

and the vector x that is returned only contains the real part of the solution.  Now, if we explicitly cast A to be a complex data type by way of the dtype=np.cfloat flag in the constructor, then everything turns out fine.  Now the rub is that I often use the same matrix A to solve linear systems with both real and complex right-hand-sides, and it would make sense for the output to be the same as the input, i.e. x should be real when A and b are real, and it should be complex when either A or b is complex.  Casting A to a complex data type returns x as a complex array, which it may not be when b is real.  Here's a minimal working example to show the warning being thrown:

> import numpy as np
 >import scipy.sparse.linalg
 >import scipy.sparse

> A = scipy.sparse.csr_matrix([[1, 2, 0], [0, 0, 3], [4, 0, 5]])

> b = np.array([1,1,1]) +1j*np.array([1,1,1])

>  #throws warning and returns erroneous result
> x_complex = scipy.sparse.linalg.spsolve(A,b) 

Solving sparse linear systems in CSR format with complex values
Hello, I'm working on using some of scipy's sparse solvers for both real and complex problems, and I seem to have found an issue when solving linear systems Ax=b where A is a real -valued matrix in csc format, and b is a complex numpy array.  When I try to solve such a system, I get the following warning:

>/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/numpy/core/numeric.py:460: 
> ComplexWarning: Casting complex values to real discards the imaginary part 
> return array(a, dtype, copy=False, order=order)

and the vector x that is returned only contains the real part of the solution.  Now, if we explicitly cast A to be a complex data type by way of the dtype=np.cfloat flag in the constructor, then everything turns out fine.  Now the rub is that I often use the same matrix A to solve linear systems with both real and complex right-hand-sides, and it would make sense for the output to be the same as the input, i.e. x should be real when A and b are real, and it should be complex when either A or b is complex.  Casting A to a complex data type returns x as a complex array, which it may not be when b is real.  Here's a minimal working example to show the warning being thrown:

> import numpy as np
 >import scipy.sparse.linalg
 >import scipy.sparse

> A = scipy.sparse.csr_matrix([[1, 2, 0], [0, 0, 3], [4, 0, 5]])

> b = np.array([1,1,1]) +1j*np.array([1,1,1])

>  #throws warning and returns erroneous result
> x_complex = scipy.sparse.linalg.spsolve(A,b) 



Discussion:
Seems like a pretty easy fix: after making sure `A` is a floating-point type [here](https://github.com/scipy/scipy/blob/25a38df0df0e03c1b49833651ce37e9f4b2e6393/scipy/sparse/linalg/dsolve/linsolve.py#L110), we can get the result dtype using [np.promote_types](https://docs.scipy.org/doc/numpy/reference/generated/numpy.promote_types.html) and cast `A` and/or `b` to it as needed.
Seems like a pretty easy fix: after making sure `A` is a floating-point type [here](https://github.com/scipy/scipy/blob/25a38df0df0e03c1b49833651ce37e9f4b2e6393/scipy/sparse/linalg/dsolve/linsolve.py#L110), we can get the result dtype using [np.promote_types](https://docs.scipy.org/doc/numpy/reference/generated/numpy.promote_types.html) and cast `A` and/or `b` to it as needed.

Resolving patch:
diff --git a/scipy/sparse/linalg/dsolve/linsolve.py b/scipy/sparse/linalg/dsolve/linsolve.py
--- a/scipy/sparse/linalg/dsolve/linsolve.py
+++ b/scipy/sparse/linalg/dsolve/linsolve.py
@@ -108,6 +108,11 @@ def spsolve(A, b, permc_spec=None, use_umfpack=True):
 
     A.sort_indices()
     A = A.asfptype()  # upcast to a floating point format
+    result_dtype = np.promote_types(A.dtype, b.dtype)
+    if A.dtype != result_dtype:
+        A = A.astype(result_dtype)
+    if b.dtype != result_dtype:
+        b = b.astype(result_dtype)
 
     # validate input shapes
     M, N = A.shape


Test patch:
diff --git a/scipy/sparse/linalg/dsolve/tests/test_linsolve.py b/scipy/sparse/linalg/dsolve/tests/test_linsolve.py
--- a/scipy/sparse/linalg/dsolve/tests/test_linsolve.py
+++ b/scipy/sparse/linalg/dsolve/tests/test_linsolve.py
@@ -243,6 +243,24 @@ def test_sparsity_preservation(self):
         assert_equal(x.nnz, 2)
         assert_allclose(x.A, b.A, atol=1e-12, rtol=1e-12)
 
+    def test_dtype_cast(self):
+        A_real = scipy.sparse.csr_matrix([[1, 2, 0],
+                                          [0, 0, 3],
+                                          [4, 0, 5]])
+        A_complex = scipy.sparse.csr_matrix([[1, 2, 0],
+                                             [0, 0, 3],
+                                             [4, 0, 5 + 1j]])
+        b_real = np.array([1,1,1])
+        b_complex = np.array([1,1,1]) + 1j*np.array([1,1,1])
+        x = spsolve(A_real, b_real)
+        assert_(np.issubdtype(x.dtype, np.floating))
+        x = spsolve(A_real, b_complex)
+        assert_(np.issubdtype(x.dtype, np.complexfloating))
+        x = spsolve(A_complex, b_real)
+        assert_(np.issubdtype(x.dtype, np.complexfloating))
+        x = spsolve(A_complex, b_complex)
+        assert_(np.issubdtype(x.dtype, np.complexfloating))
+
 
 class TestSplu(object):
     def setUp(self):