Repository: scipy/scipy
Pull request: BUG: Fix typecasting problem in scipy.sparse.lil_matrix truediv
<!-- 
Thanks for contributing a pull request! Please ensure that
your PR satisfies the checklist before submitting:
http://scipy.github.io/devdocs/dev/contributor/development_workflow.html#checklist-before-submitting-a-pr

Also, please name and describe your PR as you would write a
commit message:
http://scipy.github.io/devdocs/dev/contributor/development_workflow.html#writing-the-commit-message

Depending on your changes, you can skip CI operations and save time and energy: 
http://scipy.github.io/devdocs/dev/contributor/continuous_integration.html#skipping

Note that we are a team of volunteers; we appreciate your
patience during the review process.

Again, thanks for contributing!
-->

#### Reference issue
<!--Example: Closes gh-WXYZ.-->
Closes #19403 

#### What does this implement/fix?
<!--Please explain your changes.-->
This fixes the inconsistent behavior I observed when dividing a `scipy.sparse.lil_matrix` by a scalar of differing type. Now the resulting `dtype` is checked and adjusted if necessary.



Problem statement:
BUG: scipy.sparse.lil_matrix division by complex number leads to inconsistent behavior
### Describe your issue.

When dividing a `scipy.sparse.lil_array` by a complex number, and subsequently trying to convert it to `scipy.sparse.csr_array`, a `TypeError` is raised during the conversion.

The same does not happen for other sparse matrix types.

### Reproducing Code Example

```python
from scipy import sparse

matrix = sparse.random(10, 10, 0.1, format="lil")
matrix = matrix / 1j
matrix.tocsr()
```

The above example results in an error, while with `format="coo"` the following works as expected:
```python
from scipy import sparse

matrix = sparse.random(10, 10, 0.1, format="coo")
matrix = matrix / 1j
matrix.tocsr()
```



### Error message

```shell
File ~/.conda/envs/ntcad/lib/python3.11/site-packages/scipy/sparse/_lil.py:474, in lil_matrix.tocsr(self, copy)
    472 data = np.empty(nnz, dtype=self.dtype)
    473 _csparsetools.lil_flatten_to_array(self.rows, indices)
--> 474 _csparsetools.lil_flatten_to_array(self.data, data)
    476 # init csr matrix
    477 return self._csr_container((data, indices, indptr), shape=self.shape)

File _csparsetools.pyx:135, in scipy.sparse._csparsetools.lil_flatten_to_array()

File _csparsetools.pyx:244, in scipy.sparse._csparsetools._lil_flatten_to_array_float64()

TypeError: must be real number, not complex
```


### SciPy/NumPy/Python version and system information

```shell
1.10.1 1.23.5 sys.version_info(major=3, minor=11, micro=3, releaselevel='final', serial=0)
lapack_armpl_info:
  NOT AVAILABLE
lapack_mkl_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/home/vetschn/.conda/envs/ntcad/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/home/vetschn/.conda/envs/ntcad/include']
lapack_opt_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/home/vetschn/.conda/envs/ntcad/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/home/vetschn/.conda/envs/ntcad/include']
blas_armpl_info:
  NOT AVAILABLE
blas_mkl_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/home/vetschn/.conda/envs/ntcad/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/home/vetschn/.conda/envs/ntcad/include']
blas_opt_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/home/vetschn/.conda/envs/ntcad/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/home/vetschn/.conda/envs/ntcad/include']
Supported SIMD extensions in this NumPy install:
    baseline = SSE,SSE2,SSE3
    found = SSSE3,SSE41,POPCNT,SSE42,AVX,F16C,FMA3,AVX2
    not found = AVX512F,AVX512CD,AVX512_KNL,AVX512_KNM,AVX512_SKX,AVX512_CLX,AVX512_CNL,AVX512_ICL
```

Hints from issue discussion:
Hi @dschult!

Thanks for the quick reply and the proposed solutions. IMO the first option seems alright and avoids unnecessary conversions to and from CSR.

I'll open a PR with your proposed changes.

Commit message: Fix typecasting problem

Patch to review:
diff --git a/scipy/sparse/_lil.py b/scipy/sparse/_lil.py
index d0323a9c2297..deff7aa9e0aa 100644
--- a/scipy/sparse/_lil.py
+++ b/scipy/sparse/_lil.py
@@ -287,6 +287,10 @@ def _mul_scalar(self, other):
     def __truediv__(self, other):           # self / other
         if isscalarlike(other):
             new = self.copy()
+            new_dtype = np.result_type(self.dtype, np.array(other).dtype)
+            if new_dtype != self.dtype:
+                new.data = new.data.astype(new_dtype)
+                new.dtype = new_dtype
             # Divide every element by this scalar
             for j, rowvals in enumerate(new.data):
                 new.data[j] = [val/other for val in rowvals]


Code review comments:
diff_hunk: @@ -287,6 +287,10 @@ def _mul_scalar(self, other):
     def __truediv__(self, other):           # self / other
         if isscalarlike(other):
             new = self.copy()
+            new_dtype = np.result_type(self.dtype, np.array(other).dtype)
+            if new_dtype != self.dtype:
+                new.data = new.data.astype(new_dtype)
+                new.dtype = new_dtype
path: scipy/sparse/_lil.py
text: @user1:
```suggestion
```
My mistake: this last step is not needed. Changing the dtype of self.data is enough to change the result of the property `new.dtype`.  

Thanks for putting this together!