Repository: scipy/scipy

Issue:
BUG: MATLAB expm vs scipy.linalg.expm: overflow/invalid value warnings, resulting in nan
### Describe your issue.

I want to reproduce MATLAB expm-function in scipy

scipy.linalg.expm produces nan array

scipy.sparse.linalg.expm works, the result is exactly the same as in MATLAB.

All use the same algorithms, so i think there is some issue in scipy.linalg

### Reproducing Code Example

```python
import numpy as np
from scipy.linalg import expm
from scipy.sparse import csc_matrix
from scipy.sparse.linalg import expm as expm_sparse

def test():
    # this produces nan
    A = np.array([[-494.08845191, 0], [12566.3706, - 12566.3706]], dtype=np.float64)
    w = 2 * np.pi
    A = (np.pi / w) * A
    Aexp  = expm(A)
    print("scipy.linalg.expm: ",Aexp)
    # output is:
    # scipy.linalg.expm:  [[nan nan]
    #  [nan nan]]

    # this works
    A = csc_matrix([[-494.08845191, 0], [12566.3706, - 12566.3706]])
    w = 2* np.pi
    A = (np.pi / w) * A
    Aexp  = expm_sparse(A)
    print("scipy.sparse.linalg.expm: ",Aexp)
    # output is:
    # scipy.sparse.linalg.expm:    (1, 0)	5.339202199043829e-108
    #   (0, 0)	5.129273793067958e-108
    print("Aexp[0, 0] ", Aexp[0, 0])
    # Aexp[0, 0]  5.129273793067958e-108
    print("Aexp[0, 1] ", Aexp[0, 1])
    # Aexp[0, 1]  0.0
    print("Aexp[1, 0] ", Aexp[1, 0])
    # Aexp[1, 0]  5.339202199043829e-108
    print("Aexp[1, 1] ", Aexp[1, 1])
    # Aexp[1, 1]  0.0
```


### Error message

```shell
C:\Users\jne\.conda\envs\py310\lib\site-packages\scipy\linalg\_matfuncs.py:314: RuntimeWarning: overflow encountered in cosh
  coshMu = np.cosh(mu)
C:\Users\jne\.conda\envs\py310\lib\site-packages\scipy\linalg\_matfuncs.py:317: RuntimeWarning: overflow encountered in sinh
  sinchMu[mask] = np.sinh(mu[mask]) / mu[mask]
C:\Users\jne\.conda\envs\py310\lib\site-packages\scipy\linalg\_matfuncs.py:319: RuntimeWarning: invalid value encountered in multiply
  eA[..., [0], [0]] = eApD2 * (coshMu + AmD2*sinchMu)
C:\Users\jne\.conda\envs\py310\lib\site-packages\scipy\linalg\_matfuncs.py:320: RuntimeWarning: invalid value encountered in multiply
  eA[..., [0], [1]] = eApD2 * a2 * sinchMu
C:\Users\jne\.conda\envs\py310\lib\site-packages\scipy\linalg\_matfuncs.py:321: RuntimeWarning: invalid value encountered in multiply
  eA[..., [1], [0]] = eApD2 * a3 * sinchMu
C:\Users\jne\.conda\envs\py310\lib\site-packages\scipy\linalg\_matfuncs.py:322: RuntimeWarning: invalid value encountered in subtract
  eA[..., [1], [1]] = eApD2 * (coshMu - AmD2*sinchMu)

Process finished with exit code 0
```


### SciPy/NumPy/Python version and system information

```shell
C:\Users\jne\.conda\envs\py310\lib\site-packages\scipy\__config__.py:146: UserWarning: Install `pyyaml` for better output
  warnings.warn("Install `pyyaml` for better output", stacklevel=1)
{
  "Compilers": {
    "c": {
      "name": "gcc",
      "linker": "ld.bfd",
      "version": "10.3.0",
      "commands": "cc"
    },
    "cython": {
      "name": "cython",
      "linker": "cython",
      "version": "0.29.36",
      "commands": "cython"
    },
    "c++": {
      "name": "gcc",
      "linker": "ld.bfd",
      "version": "10.3.0",
      "commands": "c++"
    },
    "fortran": {
      "name": "gcc",
      "linker": "ld.bfd",
      "version": "10.3.0",
      "commands": "gfortran"
    },
    "pythran": {
      "version": "0.14.0",
      "include directory": "C:\\Users\\runneradmin\\AppData\\Local\\Temp\\pip-build-env-qjiwhfk5\\overlay\\Lib\\site-packages/pythran"
    }
  },
  "Machine Information": {
    "host": {
      "cpu": "x86_64",
      "family": "x86_64",
      "endian": "little",
      "system": "windows"
    },
    "build": {
      "cpu": "x86_64",
      "family": "x86_64",
      "endian": "little",
      "system": "windows"
    },
    "cross-compiled": false
  },
  "Build Dependencies": {
    "blas": {
      "name": "openblas",
    },
    "pybind11": {
      "name": "pybind11",
      "version": "2.11.0",
      "detection method": "config-tool",
      "include directory": "unknown"
    }
  },
  "Python Information": {
    "path": "C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-8hexkyrv\\cp310-win_amd64\\build\\venv\\Scripts\\python.exe",
    "version": "3.10"
  }
}
```



Discussion:
I forgot to post the Matlab code. Here it is:

>> A = [-494.08845191 0; 12566.3706 -12566.3706;]

A =

   1.0e+04 *

   -0.0494         0
    1.2566   -1.2566

>> w = 2*pi

w =

    6.2832

>> expm((pi/w)*A)

ans =

  1.0e-107 *

    0.5129         0
    0.5339         0
Might be a different issue but xref gh-19020 
Probably this is because of the 2x2 special case formula saturating because I get 

```python
A = np.array([[-494.08845191, 0], [12566.3706, - 12566.3706]], dtype=np.float64)
w = 2 * np.pi
A = (np.pi / w) * A
Aexp  = la.expm(A)
C:\Users\ilhan\AppData\Local\Programs\Python\Python310\lib\site-packages\scipy\linalg\_matfuncs.py:314: RuntimeWarning: overflow encountered in cosh
  coshMu = np.cosh(mu)
C:\Users\ilhan\AppData\Local\Programs\Python\Python310\lib\site-packages\scipy\linalg\_matfuncs.py:317: RuntimeWarning: overflow encountered in sinh
  sinchMu[mask] = np.sinh(mu[mask]) / mu[mask]
C:\Users\ilhan\AppData\Local\Programs\Python\Python310\lib\site-packages\scipy\linalg\_matfuncs.py:319: RuntimeWarning: invalid value encountered in multiply
  eA[..., [0], [0]] = eApD2 * (coshMu + AmD2*sinchMu)
C:\Users\ilhan\AppData\Local\Programs\Python\Python310\lib\site-packages\scipy\linalg\_matfuncs.py:320: RuntimeWarning: invalid value encountered in multiply
  eA[..., [0], [1]] = eApD2 * a2 * sinchMu
C:\Users\ilhan\AppData\Local\Programs\Python\Python310\lib\site-packages\scipy\linalg\_matfuncs.py:321: RuntimeWarning: invalid value encountered in multiply
  eA[..., [1], [0]] = eApD2 * a3 * sinchMu
C:\Users\ilhan\AppData\Local\Programs\Python\Python310\lib\site-packages\scipy\linalg\_matfuncs.py:322: RuntimeWarning: invalid value encountered in subtract
  eA[..., [1], [1]] = eApD2 * (coshMu - AmD2*sinchMu)
```

But if I extend the array with a dummy 0

```python
AA = la.block_diag(A, [[0]])

AA
Out[6]: 
array([[ -247.04422596,     0.        ,     0.        ],
       [ 6283.1853    , -6283.1853    ,     0.        ],
       [    0.        ,     0.        ,     0.        ]])

la.expm(AA)
Out[7]: 
array([[5.12927379e-108, 0.00000000e+000, 0.00000000e+000],
       [5.33920220e-108, 0.00000000e+000, 0.00000000e+000],
       [0.00000000e+000, 0.00000000e+000, 1.00000000e+000]])
```

So much so for the fast lane I guess. We have to scale the arrays before we go fast apparently.

Resolving patch:
diff --git a/scipy/linalg/_matfuncs.py b/scipy/linalg/_matfuncs.py
--- a/scipy/linalg/_matfuncs.py
+++ b/scipy/linalg/_matfuncs.py
@@ -6,7 +6,6 @@
 import numpy as np
 from numpy import (dot, diag, prod, logical_not, ravel, transpose,
                    conjugate, absolute, amax, sign, isfinite, triu)
-from numpy.lib.scimath import sqrt as csqrt
 
 # Local imports
 from scipy.linalg import LinAlgError, bandwidth
@@ -87,7 +86,7 @@ def _maybe_real(A, B, tol=None):
     # Note that booleans and integers compare as real.
     if np.isrealobj(A) and np.iscomplexobj(B):
         if tol is None:
-            tol = {0:feps*1e3, 1:eps*1e6}[_array_precision[B.dtype.char]]
+            tol = {0: feps*1e3, 1: eps*1e6}[_array_precision[B.dtype.char]]
         if np.allclose(B.imag, 0.0, atol=tol):
             B = B.real
     return B
@@ -204,8 +203,8 @@ def logm(A, disp=True):
     F = scipy.linalg._matfuncs_inv_ssq._logm(A)
     F = _maybe_real(A, F)
     errtol = 1000*eps
-    #TODO use a better error approximation
-    errest = norm(expm(F)-A,1) / norm(A,1)
+    # TODO use a better error approximation
+    errest = norm(expm(F)-A, 1) / norm(A, 1)
     if disp:
         if not isfinite(errest) or errest >= errtol:
             print("logm result may be inaccurate, approximate err =", errest)
@@ -299,35 +298,14 @@ def expm(A):
         return np.exp(a)
 
     if not np.issubdtype(a.dtype, np.inexact):
-        a = a.astype(float)
+        a = a.astype(np.float64)
     elif a.dtype == np.float16:
         a = a.astype(np.float32)
 
-    # Explicit formula for 2x2 case, formula (2.2) in [1]
-    # without Kahan's method numerical instabilities can occur.
-    if a.shape[-2:] == (2, 2):
-        a1, a2, a3, a4 = (a[..., [0], [0]],
-                          a[..., [0], [1]],
-                          a[..., [1], [0]],
-                          a[..., [1], [1]])
-        mu = csqrt((a1-a4)**2 + 4*a2*a3)/2.  # csqrt slow but handles neg.vals
-
-        eApD2 = np.exp((a1+a4)/2.)
-        AmD2 = (a1 - a4)/2.
-        coshMu = np.cosh(mu)
-        sinchMu = np.ones_like(coshMu)
-        mask = mu != 0
-        sinchMu[mask] = np.sinh(mu[mask]) / mu[mask]
-        eA = np.empty((a.shape), dtype=mu.dtype)
-        eA[..., [0], [0]] = eApD2 * (coshMu + AmD2*sinchMu)
-        eA[..., [0], [1]] = eApD2 * a2 * sinchMu
-        eA[..., [1], [0]] = eApD2 * a3 * sinchMu
-        eA[..., [1], [1]] = eApD2 * (coshMu - AmD2*sinchMu)
-        if np.isrealobj(a):
-            return eA.real
-        return eA
-
-    # larger problem with unspecified stacked dimensions.
+    # An explicit formula for 2x2 case exists (formula (2.2) in [1]). However, without
+    # Kahan's method, numerical instabilities can occur (See gh-19584). Hence removed
+    # here until we have a more stable implementation.
+
     n = a.shape[-1]
     eA = np.empty(a.shape, dtype=a.dtype)
     # working memory to hold intermediate arrays
@@ -700,36 +678,36 @@ def funm(A, func, disp=True):
     A = _asarray_square(A)
     # Perform Shur decomposition (lapack ?gees)
     T, Z = schur(A)
-    T, Z = rsf2csf(T,Z)
-    n,n = T.shape
+    T, Z = rsf2csf(T, Z)
+    n, n = T.shape
     F = diag(func(diag(T)))  # apply function to diagonal elements
     F = F.astype(T.dtype.char)  # e.g., when F is real but T is complex
 
-    minden = abs(T[0,0])
+    minden = abs(T[0, 0])
 
     # implement Algorithm 11.1.1 from Golub and Van Loan
     #                 "matrix Computations."
-    for p in range(1,n):
-        for i in range(1,n-p+1):
+    for p in range(1, n):
+        for i in range(1, n-p+1):
             j = i + p
-            s = T[i-1,j-1] * (F[j-1,j-1] - F[i-1,i-1])
-            ksl = slice(i,j-1)
-            val = dot(T[i-1,ksl],F[ksl,j-1]) - dot(F[i-1,ksl],T[ksl,j-1])
+            s = T[i-1, j-1] * (F[j-1, j-1] - F[i-1, i-1])
+            ksl = slice(i, j-1)
+            val = dot(T[i-1, ksl], F[ksl, j-1]) - dot(F[i-1, ksl], T[ksl, j-1])
             s = s + val
-            den = T[j-1,j-1] - T[i-1,i-1]
+            den = T[j-1, j-1] - T[i-1, i-1]
             if den != 0.0:
                 s = s / den
-            F[i-1,j-1] = s
-            minden = min(minden,abs(den))
+            F[i-1, j-1] = s
+            minden = min(minden, abs(den))
 
     F = dot(dot(Z, F), transpose(conjugate(Z)))
     F = _maybe_real(A, F)
 
-    tol = {0:feps, 1:eps}[_array_precision[F.dtype.char]]
+    tol = {0: feps, 1: eps}[_array_precision[F.dtype.char]]
     if minden == 0.0:
         minden = tol
-    err = min(1, max(tol,(tol/minden)*norm(triu(T,1),1)))
-    if prod(ravel(logical_not(isfinite(F))),axis=0):
+    err = min(1, max(tol, (tol/minden)*norm(triu(T, 1), 1)))
+    if prod(ravel(logical_not(isfinite(F))), axis=0):
         err = np.inf
     if disp:
         if err > 1000*tol:
@@ -782,7 +760,7 @@ def rounded_sign(x):
             c = 1e3*eps*amax(x)
         return sign((absolute(rx) > c) * rx)
     result, errest = funm(A, rounded_sign, disp=0)
-    errtol = {0:1e3*feps, 1:1e3*eps}[_array_precision[result.dtype.char]]
+    errtol = {0: 1e3*feps, 1: 1e3*eps}[_array_precision[result.dtype.char]]
     if errest < errtol:
         return result
 
@@ -806,8 +784,8 @@ def rounded_sign(x):
     for i in range(100):
         iS0 = inv(S0)
         S0 = 0.5*(S0 + iS0)
-        Pp = 0.5*(dot(S0,S0)+S0)
-        errest = norm(dot(Pp,Pp)-Pp,1)
+        Pp = 0.5*(dot(S0, S0)+S0)
+        errest = norm(dot(Pp, Pp)-Pp, 1)
         if errest < errtol or prev_errest == errest:
             break
         prev_errest = errest


Test patch: