Repository: scipy/scipy

Issue:
UnboundLocalError in scipy.interpolate.splrep
The documentation states that 

> There must have been a previous call with task=0 or task=1 for the same set of 
> data.

Doing it results in an UnboundLocalError in the second call

``` python
import numpy as np
from scipy.interpolate import splrep

x = np.arange(100)
y = np.arange(100)

spline1 = splrep(x, y, task=0)
spline2 = splrep(x, y, task=1)
```

with the following traceback

`````` python
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-1-2f4cd8293fca> in <module>()
      6 
      7 spline1 = splrep(x, y, task=0)
----> 8 spline2 = splrep(x, y, task=1)

C:\Python27\lib\site-packages\scipy\interpolate\fitpack.pyc in splrep(x, y, w, xb, xe, k, task, s, t, full_output, per, quiet)
    445         _curfit_cache['iwrk'] = empty((nest,),int32)
    446     try:
--> 447         t=_curfit_cache['t']
    448         wrk=_curfit_cache['wrk']
    449         iwrk=_curfit_cache['iwrk']

UnboundLocalError: local variable '_curfit_cache' referenced before assignment

I guess the error comes from this line(436)

```python
    if task <= 0:
        _curfit_cache = {}
``````

Because of it, I guess the name `_curfit_cache` is always local. An example to show what I mean follows

``` python
d = dict(key=1337)
def will_fail():
    if False:
        d = {}
    t = d['key']

def will_succeed():
    t = d['key']
will_succeed()
will_fail()
```

This will fail with a similar traceback

``` python
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-17-4fbf3d67062c> in <module>()
      8     t = d['key']
      9 will_succeed()
---> 10 will_fail()

<ipython-input-17-4fbf3d67062c> in will_fail()
      3     if False:
      4         d = {}
----> 5     t = d['key']
      6 
      7 def will_succeed():

UnboundLocalError: local variable 'd' referenced before assignment
```



Discussion:

Resolving patch:
diff --git a/scipy/interpolate/_fitpack_impl.py b/scipy/interpolate/_fitpack_impl.py
--- a/scipy/interpolate/_fitpack_impl.py
+++ b/scipy/interpolate/_fitpack_impl.py
@@ -311,12 +311,8 @@ def splprep(x, w=None, u=None, ub=None, ue=None, k=3, task=0, s=None, t=None,
         return tcku
 
 
-_curfit_cache = {'t': array([], float), 'wrk': array([], float),
-                 'iwrk': array([], intc)}
-
-
 def splrep(x, y, w=None, xb=None, xe=None, k=3, task=0, s=None, t=None,
-           full_output=0, per=0, quiet=1):
+           full_output=0, per=0, quiet=1, cache=None):
     """
     Find the B-spline representation of 1-D curve.
 
@@ -374,6 +370,9 @@ def splrep(x, y, w=None, xb=None, xe=None, k=3, task=0, s=None, t=None,
         Non-zero to suppress messages.
         This parameter is deprecated; use standard Python warning filters
         instead.
+    cache : dict, optional
+        Stores the results of a previous call of splrep for the same data, to
+        be used when task==1 after a previous call with task==0 or task==-1.
 
     Returns
     -------
@@ -440,8 +439,12 @@ def splrep(x, y, w=None, xb=None, xe=None, k=3, task=0, s=None, t=None,
     >>> plt.show()
 
     """
-    if task <= 0:
-        _curfit_cache = {}
+    if task == 1 and cache is None:
+        raise ValueError("Must call splrep with cache argument for task=1")
+    if task <= 0 and cache is None:
+        cache = {'t': array([], float), 'wrk': array([], float),
+                 'iwrk': array([], intc)}
+
     x, y = map(atleast_1d, [x, y])
     m = len(x)
     if w is None:
@@ -474,26 +477,26 @@ def splrep(x, y, w=None, xb=None, xe=None, k=3, task=0, s=None, t=None,
         if t is None:
             raise TypeError('Knots must be given for task=-1')
         numknots = len(t)
-        _curfit_cache['t'] = empty((numknots + 2*k + 2,), float)
-        _curfit_cache['t'][k+1:-k-1] = t
-        nest = len(_curfit_cache['t'])
+        cache['t'] = empty((numknots + 2*k + 2,), float)
+        cache['t'][k+1:-k-1] = t
+        nest = len(cache['t'])
     elif task == 0:
         if per:
             nest = max(m + 2*k, 2*k + 3)
         else:
             nest = max(m + k + 1, 2*k + 3)
         t = empty((nest,), float)
-        _curfit_cache['t'] = t
+        cache['t'] = t
     if task <= 0:
         if per:
-            _curfit_cache['wrk'] = empty((m*(k + 1) + nest*(8 + 5*k),), float)
+            cache['wrk'] = empty((m*(k + 1) + nest*(8 + 5*k),), float)
         else:
-            _curfit_cache['wrk'] = empty((m*(k + 1) + nest*(7 + 3*k),), float)
-        _curfit_cache['iwrk'] = empty((nest,), intc)
+            cache['wrk'] = empty((m*(k + 1) + nest*(7 + 3*k),), float)
+        cache['iwrk'] = empty((nest,), intc)
     try:
-        t = _curfit_cache['t']
-        wrk = _curfit_cache['wrk']
-        iwrk = _curfit_cache['iwrk']
+        t = cache['t']
+        wrk = cache['wrk']
+        iwrk = cache['iwrk']
     except KeyError:
         raise TypeError("must call with task=1 only after"
                         " call with task=0,-1")
@@ -800,7 +803,7 @@ def spalde(x, tck):
 
 def bisplrep(x, y, z, w=None, xb=None, xe=None, yb=None, ye=None,
              kx=3, ky=3, task=0, s=None, eps=1e-16, tx=None, ty=None,
-             full_output=0, nxest=None, nyest=None, quiet=1):
+             full_output=0, nxest=None, nyest=None, quiet=1, cache=None):
     """
     Find a bivariate B-spline representation of a surface.
 
@@ -851,6 +854,9 @@ def bisplrep(x, y, z, w=None, xb=None, xe=None, yb=None, ye=None,
         Non-zero to suppress printing of messages.
         This parameter is deprecated; use standard Python warning filters
         instead.
+    cache : dict, optional
+        Stores the results of a previous call of bisplrep for the same data, to
+        be used when task==1 after a previous call with task==0 or task==-1.
 
     Returns
     -------
@@ -887,6 +893,11 @@ def bisplrep(x, y, z, w=None, xb=None, xe=None, yb=None, ye=None,
        Numerical Analysis, Oxford University Press, 1993.
 
     """
+    if task == 1 and cache is None:
+        raise ValueError("Must call splrep with cache argument for task=1")
+    if task <= 0 and cache is None:
+        cache = {'t': array([], float), 'wrk': array([], float),
+                 'iwrk': array([], intc)}
     x, y, z = map(ravel, [x, y, z])  # ensure 1-d arrays.
     m = len(x)
     if not (m == len(y) == len(z)):
@@ -957,9 +968,9 @@ def bisplrep(x, y, z, w=None, xb=None, xe=None, yb=None, ye=None,
     tx, ty, c, o = _fitpack._surfit(x, y, z, w, xb, xe, yb, ye, kx, ky,
                                     task, s, eps, tx, ty, nxest, nyest,
                                     wrk, lwrk1, lwrk2)
-    _curfit_cache['tx'] = tx
-    _curfit_cache['ty'] = ty
-    _curfit_cache['wrk'] = o['wrk']
+    cache['tx'] = tx
+    cache['ty'] = ty
+    cache['wrk'] = o['wrk']
     ier, fp = o['ier'], o['fp']
     tck = [tx, ty, c, kx, ky]
 
diff --git a/scipy/interpolate/fitpack.py b/scipy/interpolate/fitpack.py
--- a/scipy/interpolate/fitpack.py
+++ b/scipy/interpolate/fitpack.py
@@ -159,7 +159,7 @@ def splprep(x, w=None, u=None, ub=None, ue=None, k=3, task=0, s=None, t=None,
 
 
 def splrep(x, y, w=None, xb=None, xe=None, k=3, task=0, s=None, t=None,
-           full_output=0, per=0, quiet=1):
+           full_output=0, per=0, quiet=1, cache=None):
     """
     Find the B-spline representation of 1-D curve.
 
@@ -217,6 +217,9 @@ def splrep(x, y, w=None, xb=None, xe=None, k=3, task=0, s=None, t=None,
         Non-zero to suppress messages.
         This parameter is deprecated; use standard Python warning filters
         instead.
+    cache : dict, optional
+        Stores the results of a previous call of bisplrep for the same data, to
+        be used when task==1 after a previous call with task==0 or task==-1.
 
     Returns
     -------
@@ -286,7 +289,7 @@ def splrep(x, y, w=None, xb=None, xe=None, k=3, task=0, s=None, t=None,
     >>> plt.show()
 
     """
-    res = _impl.splrep(x, y, w, xb, xe, k, task, s, t, full_output, per, quiet)
+    res = _impl.splrep(x, y, w, xb, xe, k, task, s, t, full_output, per, quiet, cache)
     return res
 
 


Test patch:
diff --git a/scipy/interpolate/tests/test_fitpack.py b/scipy/interpolate/tests/test_fitpack.py
--- a/scipy/interpolate/tests/test_fitpack.py
+++ b/scipy/interpolate/tests/test_fitpack.py
@@ -3,11 +3,12 @@
 import os
 
 import numpy as np
+import pytest
 from numpy.testing import (assert_equal, assert_allclose, assert_,
                            assert_almost_equal, assert_array_almost_equal)
 from pytest import raises as assert_raises
 
-from numpy import array, asarray, pi, sin, cos, arange, dot, ravel, sqrt, round
+from numpy import array, asarray, pi, sin, cos, arange, dot, ravel, sqrt, round, intc
 from scipy import interpolate
 from scipy.interpolate.fitpack import (splrep, splev, bisplrep, bisplev,
      sproot, splprep, splint, spalde, splder, splantider, insert, dblint)
@@ -259,6 +260,21 @@ def test_smoke_bisplrep_bisplev(self):
         self.check_5()
 
 
+class TestSplrep(object):
+    def test_task_argument(self):
+        x, y = range(5), range(5, 10)
+        splrep(x, y, task=0)
+        with pytest.raises(ValueError):
+            splrep(x, y, task=1)
+
+    def test_cache_argument(self):
+        x, y = range(5), range(5, 10)
+        splrep_cache = {'t': array([], float), 'wrk': array([], float),
+                        'iwrk': array([], intc)}
+        splrep(x, y, task=0, cache=splrep_cache)
+        splrep(x, y, task=1, cache=splrep_cache)
+
+
 class TestSplev(object):
     def test_1d_shape(self):
         x = [1,2,3,4,5]
@@ -400,6 +416,19 @@ def test_regression_1310(self):
         bisplrep(data[:,0], data[:,1], data[:,2], kx=3, ky=3, s=0,
                  full_output=True)
 
+    def test_task_argument(self):
+        x, y, z = np.arange(16), np.arange(16, 32), np.arange(32, 48)
+        bisplrep(x, y, z, task=0)
+        with pytest.raises(ValueError):
+            bisplrep(x, y, z, task=1)
+
+    def test_cache_argument(self):
+        x, y, z = np.arange(16), np.arange(16, 32), np.arange(32, 48)
+        bisplrep_cache = {'t': array([], float), 'wrk': array([], float),
+                 'iwrk': array([], intc)}
+        bisplrep(x, y, z, task=0, cache=bisplrep_cache)
+        bisplrep(x, y, z, task=1, cache=bisplrep_cache)
+
 
 def test_dblint():
     # Basic test to see it runs and gives the correct result on a trivial