Repository: lmfit/lmfit-py

Issue:
Error when fitting multi-dimensional data by using Model.fit.
Model.fit cannot fit multi-dimensional data and I get an error message (error: Result from function call is not a proper array of floats).

As mentioned in [FAQ page](http://lmfit.github.io/lmfit-py/faq.html#how-can-i-fit-multi-dimensional-data), this is due to need for converting residual to a one dimensional array.
So I think `Model._residual` should call `numpy.ndarray.flatten()` before returning.

```
 class Model(object):
 ...
     def _residual(self, params, data, weights, **kwargs):
         "default residual:  (data-model)*weights"
         diff = self.eval(params, **kwargs) - data
         if weights is not None:
             diff *= weights
-        return np.asarray(diff)  # for compatibility with pandas.Series
+        return np.asarray(diff).flatten()
```



Discussion:
@tom1nose  Thanks, that's a great suggestion.   Anyone see any downsides to doing this?  Perhaps the residual for the Minimizer class should do a similar flattening....

I think it would it be better to use `ravel`.  `ravel` uses a view, not a copy, and will be faster.  Speed may be important here as this function gets called on every evaluation of the model.

In any case shouldn't the code be using `asfarray`, not asarray?  What happens if the residuals aren't float?  Wouldn't this lead to issues with numerical stability?

@newville Thanks for replying. Searching "_residual" on GItHub returns only `Model._residual` (sorry, I'm not familiar with GitHub and Git), therefore it seems that any subclasses derived from `Model` doesn't override `_residual`.

One thing: use `ravel`  instead of `flatten` to save one copy.

@andyfaff , @Tillsten 
As you say, `np.asarray(diff).ravel()` is faster (especially for large size data). I didn't even know `ndarray.ravel` well. Thanks!


Resolving patch:
diff --git a/lmfit/minimizer.py b/lmfit/minimizer.py
--- a/lmfit/minimizer.py
+++ b/lmfit/minimizer.py
@@ -289,7 +289,7 @@ def __residual(self, fvars):
         if callable(self.iter_cb):
             self.iter_cb(self.params, self.nfev, out,
                          *self.userargs, **self.userkws)
-        return out
+        return np.asarray(out).ravel()
 
     def __jacobian(self, fvars):
         """
diff --git a/lmfit/model.py b/lmfit/model.py
--- a/lmfit/model.py
+++ b/lmfit/model.py
@@ -314,7 +314,7 @@ def _residual(self, params, data, weights, **kwargs):
         diff = self.eval(params, **kwargs) - data
         if weights is not None:
             diff *= weights
-        return np.asarray(diff)  # for compatibility with pandas.Series
+        return np.asarray(diff).ravel()  # for compatibility with pandas.Series
 
     def _handle_missing(self, data):
         "handle missing data"


Test patch:
diff --git a/tests/test_nose.py b/tests/test_nose.py
--- a/tests/test_nose.py
+++ b/tests/test_nose.py
@@ -282,6 +282,33 @@ def fcn2min(params, x, data):
     assert_(mini.errorbars == False)
 
 
+def test_multidimensional_fit_GH205():
+    # test that you don't need to flatten the output from the objective
+    # function. Tests regression for GH205.
+    pos = np.linspace(0, 99, 100)
+    xv, yv = np.meshgrid(pos, pos)
+    f = lambda xv, yv, lambda1, lambda2: (np.sin(xv * lambda1)
+                                             + np.cos(yv * lambda2))
+
+    data = f(xv, yv, 0.3, 3)
+    assert_(data.ndim, 2)
+
+    def fcn2min(params, xv, yv, data):
+        """ model decaying sine wave, subtract data"""
+        lambda1 = params['lambda1'].value
+        lambda2 = params['lambda2'].value
+        model = f(xv, yv, lambda1, lambda2)
+        return model - data
+
+    # create a set of Parameters
+    params = Parameters()
+    params.add('lambda1', value=0.4)
+    params.add('lambda2', value=3.2)
+
+    mini = Minimizer(fcn2min, params, fcn_args=(xv, yv, data))
+    res = mini.minimize()
+
+
 class CommonMinimizerTest(unittest.TestCase):
 
     def setUp(self):