Repository: lmfit/lmfit-py

Issue:
scalar_minimize does not reset stderr and correl when a Minimizer instance is reused.
`scalar_minimize` does not calculate parameter uncertainties (Related to #169).  As such, I would expect the `stderr` and `correl` attributes to be `None` if I do a fit using that method. Indeed, a `Parameter` is initialised with `None` for those attributes.

However, if you have done a fit previously with `leastsq` the `stderr` and `correl` will have been calculated.  If you then refit the data with `scalar_minimize` the `stderr` and `correl` attributes are not reset to `None` and you are left with stale values.  The following code illustrates the problem:

```
from __future__ import print_function
from lmfit import Parameters, Minimizer, fit_report
from numpy import random, linspace, pi, exp, sin, sign
from copy import deepcopy

p_true = Parameters()
p_true.add('amp', value=14.0)
p_true.add('period', value=5.46)
p_true.add('shift', value=0.123)
p_true.add('decay', value=0.032)

def residual(pars, x, data=None):
    vals = pars.valuesdict()
    amp =  vals['amp']
    per =  vals['period']
    shift = vals['shift']
    decay = vals['decay']
    if abs(shift) > pi/2:
        shift = shift - sign(shift)*pi
    model = amp * sin(shift + x/per) * exp(-x*x*decay*decay)
    if data is None:
        return model
    return (model - data)

noise = random.normal(scale=0.7215, size=1001)
x     = linspace(0., 250., 1001)
data  = residual(p_true, x) + noise

#create another dataset with different values
p_true2 = deepcopy(p_true)
p_true2['amp'].value = 5
p_true2['period'].value = 10
data2  = residual(p_true2, x) + noise

fit_params = Parameters()
fit_params.add('amp', value=13.0)
fit_params.add('period', value=2)
fit_params.add('shift', value=0.0)
fit_params.add('decay', value=0.02)

mini = Minimizer(residual, fit_params, fcn_args=(x,), fcn_kws={'data':data})
mini.minimize(method='leastsq')
print(fit_report(mini.params))
mini.userkws = {'data': data2}
mini.minimize(method='nelder-mead')
print(fit_report(mini.params))
```

Results in:

```
[[Variables]]
amp:      13.8883869 +/- 0.149409 (1.08%) (init= 13)
period:   5.45389166 +/- 0.027174 (0.50%) (init= 2)
shift:    0.12329457 +/- 0.014567 (11.82%) (init= 0)
decay:    0.03226311 +/- 0.000396 (1.23%) (init= 0.02)
[[Correlations]] (unreported correlations are <  0.100)
C(period, shift)             =  0.793 
C(amp, decay)                =  0.585 
C(amp, shift)                = -0.291 
C(amp, period)               = -0.236 
C(shift, decay)              = -0.179 
C(period, decay)             = -0.145
```

for the first fit and 

```
[[Variables]]
amp:      5.01674659 +/- 0.149409 (2.98%) (init= 13.88839)
period:   10.2063042 +/- 0.027174 (0.27%) (init= 5.453892)
shift:    0.10819598 +/- 0.014567 (13.46%) (init= 0.1232946)
decay:    0.03253109 +/- 0.000396 (1.22%) (init= 0.03226311)
[[Correlations]] (unreported correlations are <  0.100)
C(period, shift)             =  0.793
C(amp, decay)                =  0.585
C(amp, shift)                = -0.291
C(amp, period)               = -0.236
C(shift, decay)              = -0.179
C(period, decay)             = -0.145
```

for the second.  As you can see the values for the second fit are 'correct', but the `stderr` and `correl` are stale from the previous fit.



Discussion:
I can write a PR to fix this.  Should the `stderr` and `correl` be reset in `prepare_fit`?

It gets slightly worse. Now lets fix a parameter (after previously letting it vary in a leastsq fit):

```
>>>mini.params['amp'].vary = False
>>>mini.minimize(method='nelder-mead')
>>>print(mini.params['amp'].stderr)
0.137414374655
```

Oops, the stderr is still there even though the parameter is fixed.


Resolving patch:
diff --git a/lmfit/minimizer.py b/lmfit/minimizer.py
--- a/lmfit/minimizer.py
+++ b/lmfit/minimizer.py
@@ -355,7 +355,11 @@ def prepare_fit(self, params=None):
         self.nfev = 0
         self.var_map = []
         self.vars = []
+        self.errorbars = False
         for name, par in self.params.items():
+            par.stderr = None
+            par.correl = None
+
             if par.expr is not None:
                 par.ast = self.asteval.parse(par.expr)
                 check_ast_errors(self.asteval.error)


Test patch:
diff --git a/tests/test_nose.py b/tests/test_nose.py
--- a/tests/test_nose.py
+++ b/tests/test_nose.py
@@ -5,6 +5,7 @@
 from lmfit.lineshapes import gaussian
 import numpy as np
 from numpy import pi
+from numpy.testing import assert_
 import unittest
 import nose
 from nose import SkipTest
@@ -48,7 +49,6 @@ def fcn2min(params, x, data):
     params.add('shift', value= 0.0, min=-pi / 2., max=pi / 2)
     params.add('omega', value= 3.0)
 
-
     # do fit, here with leastsq model
     result = minimize(fcn2min, params, args=(x, data))
 
@@ -233,6 +233,55 @@ def residual(pars, x, data=None):
     check_paras(fit_params, org_params)
 
 
+def test_scalar_minimize_has_no_uncertainties():
+    # scalar_minimize doesn't calculate uncertainties.
+    # when a scalar_minimize is run the stderr and correl for each parameter
+    # should be None. (stderr and correl are set to None when a Parameter is
+    # initialised).
+    # This requires a reset after a leastsq fit has been done.
+    # Only when scalar_minimize calculates stderr and correl can this test
+    # be removed.
+
+    np.random.seed(1)
+    x = np.linspace(0, 15, 301)
+    data = (5. * np.sin(2 * x - 0.1) * np.exp(-x*x*0.025) +
+            np.random.normal(size=len(x), scale=0.2) )
+
+    # define objective function: returns the array to be minimized
+    def fcn2min(params, x, data):
+        """ model decaying sine wave, subtract data"""
+        amp = params['amp'].value
+        shift = params['shift'].value
+        omega = params['omega'].value
+        decay = params['decay'].value
+
+        model = amp * np.sin(x * omega + shift) * np.exp(-x*x*decay)
+        return model - data
+
+    # create a set of Parameters
+    params = Parameters()
+    params.add('amp',   value= 10,  min=0)
+    params.add('decay', value= 0.1)
+    params.add('shift', value= 0.0, min=-pi / 2., max=pi / 2)
+    params.add('omega', value= 3.0)
+
+    mini = Minimizer(fcn2min, params, fcn_args=(x, data))
+    mini.minimize()
+    assert_(np.isfinite(mini.params['amp'].stderr))
+    print(mini.errorbars)
+    assert_(mini.errorbars == True)
+    mini.minimize(method='nelder-mead')
+    assert_(mini.params['amp'].stderr is None)
+    assert_(mini.params['decay'].stderr is None)
+    assert_(mini.params['shift'].stderr is None)
+    assert_(mini.params['omega'].stderr is None)
+    assert_(mini.params['amp'].correl is None)
+    assert_(mini.params['decay'].correl is None)
+    assert_(mini.params['shift'].correl is None)
+    assert_(mini.params['omega'].correl is None)
+    assert_(mini.errorbars == False)
+
+
 class CommonMinimizerTest(unittest.TestCase):
 
     def setUp(self):