Repository: scipy/scipy

Issue:
ydata that needs broadcasting renders curve_fit unable to compute covariance matrix
Consider the following example:
```
import numpy as np
from scipy.optimize import curve_fit

np.random.seed(0)

xdata = np.arange(10)
target = 4.7 * xdata ** 2 + 3.5 * xdata + np.random.rand(len(xdata))

opt = curve_fit(lambda x, a, b: a*x**2 + b*x - target,
                xdata=xdata,
                ydata=0,
                p0=[1, 1],
                )
print(opt)
```
As of scipy 0.18.1, this produces
```
/usr/lib/python3.6/site-packages/scipy/optimize/minpack.py:715: OptimizeWarning: Covariance of the parameters could not be estimated
  category=OptimizeWarning)
(array([ 4.67945133,  3.74603061]), array([[ inf,  inf],
       [ inf,  inf]]))
```

Passing `ydata=np.zeros_like(xdata)` instead of `ydata=0` (which should be functionally equivalent) just works:
```
(array([ 4.67945133,  3.74603061]), array([[ 0.00011662, -0.00082862],
       [-0.00082862,  0.00627416]]))
```

It would be appreciated if a `ydata` that needs to be broadcasted could be just as well supported.

(I know in this case I could just pass `ydata=target`, but that's not really the point.)


Discussion:

Resolving patch:
diff --git a/scipy/optimize/minpack.py b/scipy/optimize/minpack.py
--- a/scipy/optimize/minpack.py
+++ b/scipy/optimize/minpack.py
@@ -755,6 +755,7 @@ def curve_fit(f, xdata, ydata, p0=None, sigma=None, absolute_sigma=False,
         return_full = kwargs.pop('full_output', False)
         res = leastsq(func, p0, Dfun=jac, full_output=1, **kwargs)
         popt, pcov, infodict, errmsg, ier = res
+        ysize = len(infodict['fvec'])
         cost = np.sum(infodict['fvec'] ** 2)
         if ier not in [1, 2, 3, 4]:
             raise RuntimeError("Optimal parameters not found: " + errmsg)
@@ -769,6 +770,7 @@ def curve_fit(f, xdata, ydata, p0=None, sigma=None, absolute_sigma=False,
         if not res.success:
             raise RuntimeError("Optimal parameters not found: " + res.message)
 
+        ysize = len(res.fun)
         cost = 2 * res.cost  # res.cost is half sum of squares!
         popt = res.x
 
@@ -787,8 +789,8 @@ def curve_fit(f, xdata, ydata, p0=None, sigma=None, absolute_sigma=False,
         pcov.fill(inf)
         warn_cov = True
     elif not absolute_sigma:
-        if ydata.size > p0.size:
-            s_sq = cost / (ydata.size - p0.size)
+        if ysize > p0.size:
+            s_sq = cost / (ysize - p0.size)
             pcov = pcov * s_sq
         else:
             pcov.fill(inf)


Test patch:
diff --git a/scipy/optimize/tests/test_minpack.py b/scipy/optimize/tests/test_minpack.py
--- a/scipy/optimize/tests/test_minpack.py
+++ b/scipy/optimize/tests/test_minpack.py
@@ -771,6 +771,21 @@ def hyperbola(x, s_1, s_2, o_x, o_y, c):
 
         assert_allclose(popt_32, popt_64, atol=2e-5)
 
+    def test_broadcast_y(self):
+        xdata = np.arange(10)
+        target = 4.7 * xdata ** 2 + 3.5 * xdata + np.random.rand(len(xdata))
+        fit_func = lambda x, a, b: a*x**2 + b*x - target
+        for method in ['lm', 'trf', 'dogbox']:
+            popt0, pcov0 = curve_fit(fit_func,
+                                     xdata=xdata,
+                                     ydata=np.zeros_like(xdata),
+                                     method=method)
+            popt1, pcov1 = curve_fit(fit_func,
+                                     xdata=xdata,
+                                     ydata=0,
+                                     method=method)
+            assert_array_equal(pcov0, pcov1)
+
 
 class TestFixedPoint(object):