Repository: scipy/scipy

Issue:
UnboundLocalError in Radau when given a NaN
<!-- 
Thank you for taking the time to file a bug report. 
Please fill in the fields below, deleting the sections that 
don't apply to your issue. You can view the final output
by clicking the preview button above.
Note: This is a comment, and won't appear in the output.
-->

Integration with method Radau raises an UnboundLocalError when the return from fun contains a NaN. Failure in this case is of course expected, but the UnboundLocalError is not.

#### Reproducing code example:
<!-- 
If you place your code between the triple backticks below, 
it will be rendered as a code block. 
-->

```python
import numpy as np
from scipy.integrate import Radau

def fun(t, y):
    return (1,) if t==0 else (np.nan,)

radau = Radau(fun, t0=0, y0=(0,), t_bound=1)
radau.step()  # UnboundLocalError
```

#### Error message:
<!-- If any, paste the *full* error message inside a code block
as above (starting from line Traceback)
-->

```
Traceback (most recent call last):
  File "/Users/gabriel/radau_issue.py", line 8, in <module>
    radau.step()  # UnboundLocalError
  File "/Users/gabriel/Library/Python/3.7/lib/python/site-packages/scipy/integrate/_ivp/base.py", line 182, in step
    success, message = self._step_impl()
  File "/Users/gabriel/Library/Python/3.7/lib/python/site-packages/scipy/integrate/_ivp/radau.py", line 451, in _step_impl
    LU_real, LU_complex, self.solve_lu)
  File "/Users/gabriel/Library/Python/3.7/lib/python/site-packages/scipy/integrate/_ivp/radau.py", line 138, in solve_collocation_system
    return converged, k + 1, Z, rate
UnboundLocalError: local variable 'rate' referenced before assignment
```

#### Scipy/Numpy/Python version information:
<!-- You can simply run the following and paste the result in a code block
```
import sys, scipy, numpy; print(scipy.__version__, numpy.__version__, sys.version_info)
```
-->

```
1.3.1 1.17.0 sys.version_info(major=3, minor=7, micro=4, releaselevel='final', serial=0)
```


Discussion:
@gerlero interesting, it looks like a bug in the code indeed. I will investigate.
Setting `rate=None` before the loop will solve it 

https://github.com/scipy/scipy/blob/master/scipy/integrate/_ivp/radau.py#L101

Will you send a pull request for that? With a test if possible.
Thanks! Sure. I've fixed it by moving the ``rate=None`` statement so that `rate` is already assigned if `isfinite` causes a break. 

https://github.com/gerlero/scipy/tree/fix_radau

The reproducing code now raises ``ValueError: array must not contain infs or NaNs`` from the function `lu_factor`. 

Now I don't have to feel dirty catching the `UnboundLocalError`, so to me it's a clear improvement.

Regarding a possible test case, would it be a sensible choice to require this `ValueError`? 
I have tested the other `OdeSolver`s with the reproducing code: 

- `BDF`: raises the same `ValueError` that `Radau` now does.
- `RK45` and `RK23`: don't raise anything but set their `status` attributes to ``'failed'``. 
- `LSODA`: actually steps forward successfully; but if you check its `y` attribute, it is equal to ``[nan]``. 

@nmayorov I am wondering if this behavior is too inconsistent for a proper test case (whether on just `Radau` or on every method as the other test cases do). I'll wait for your reply before proceeding with a pull request.
@gerlero in this case, I'd say don't bother with the test. Please send a PR.

Resolving patch:
diff --git a/scipy/integrate/_ivp/radau.py b/scipy/integrate/_ivp/radau.py
--- a/scipy/integrate/_ivp/radau.py
+++ b/scipy/integrate/_ivp/radau.py
@@ -98,6 +98,7 @@ def solve_collocation_system(fun, t, y, h, Z0, scale, tol,
     dW_norm_old = None
     dW = np.empty_like(W)
     converged = False
+    rate = None
     for k in range(NEWTON_MAXITER):
         for i in range(3):
             F[i] = fun(t + ch[i], y + Z[i])
@@ -118,8 +119,6 @@ def solve_collocation_system(fun, t, y, h, Z0, scale, tol,
         dW_norm = norm(dW / scale)
         if dW_norm_old is not None:
             rate = dW_norm / dW_norm_old
-        else:
-            rate = None
 
         if (rate is not None and (rate >= 1 or
                 rate ** (NEWTON_MAXITER - k) / (1 - rate) * dW_norm > tol)):


Test patch: