lmfit / lmfit-py

Non-Linear Least Squares Minimization, with flexible Parameter settings, based on scipy.optimize, and with many additional classes and methods for curve fitting.
https://lmfit.github.io/lmfit-py/
Other
1.08k stars 276 forks source link

How are parameters objects managed when call minimize and leastsq? #89

Closed PhML closed 10 years ago

PhML commented 10 years ago

Let's use the documentation example.

After running the minimize function, params seems to be updated since we use report_fit(params) that gives the same output than report_fit(result.params). So, if I change, let’s say params['amp'].value, I expect this to change result.params['amp'].value also. But it does not.

In fact my use case is to use a parameter with vary set to False, run minimize, then change vary to True and run again the fit with initial values set by previous run using result.leastsq(). In order to do what I want, either I have to call again minimise, but I create a new result object, either I have to change result.params instead of params but it means between two runs, I do not look to the result at the same place.

Could you explain me what happens that I don't understand, please ?

newville commented 10 years ago

Hi,

There is a little bit of a confusing concept for whether the params object passed to leastsq() should be mutable, and whether results.params should be identical or a copy. Right now it is (or supposed to be) that the params passed in to leastsq() are changed to reflect the best values and that results.params is a copy made at output time.

That means for "fit, change param attribute, re-fit", it should be preferable to update the original params between the two runs of minimize. That way the second results "results2.params" will be different from the results of the first fit, which will be preserved, say "results1.params", for comparison of the two fits.

Probably, the doc should be clearer about this. It would also be reasonable to change to behavior to make a copy of params on input, so that the original values were not changed at all during the fit, and only results.params were changed. Opinions?

On Tue, Apr 1, 2014 at 4:51 AM, PhML notifications@github.com wrote:

Let's use the documentation examplehttp://cars9.uchicago.edu/software/python/lmfit/parameters.html#simple-example .

After running the minimize function, params seems to be updated since we use report_fit(params) that gives the same output than report_fit(result.params). So, if I change, let's say params['amp'].value, I expect this to change result.params['amp'].value also. But it does not.

In fact my use case is to use a parameter with vary set to False, run minimize, then change vary to True and run again the fit with initial values set by previous run using result.leastsq(). In order to do what I want, either I have to call again minimise, but I create a new result object, either I have to change result.params instead of params but it means between two runs, I do not look to the result at the same place.

Could you explain me what happens that I don't understand, please ?

Reply to this email directly or view it on GitHubhttps://github.com/lmfit/lmfit-py/issues/89 .

--Matt Newville 630-252-0431

PhML commented 10 years ago

The way I see things is that we should always update original parameters or never. I mean we could have an option when we instantiate Parameters, like copy_parameters:

What do you think about this option?

newville commented 10 years ago

We do always update the original parameters.

Is your proposal to add a copy_parameters option to Parameters() that changes minimize() and Minimize.leastsq()? I'd rather add a keyword to minimize() and Minimize() for that -- otherwise there's "action at a distance". More importantly, I don't see when we ever want results.params and params to be identical and yet in two places.

I'm probably biased, but the current behavior seems to make sense to me. With a typical params = Parameters(....) result = minimize(objfunction, params, ...)

the parameters held in params will be changed by the fit -- this is worth reconsidering, but I'm not persuaded to change this. (it's unusual for function arguments to be mutable in python, but not so unusual in numeric work, and it does follow the behavior for fitting functions in scipy.optimize and other languages, so seems natural here). I also think it's not what your suggesting, but perhaps I'm not understanding.

OK, params is changed on output. In addition, result.params holds a copy of the parameters, preserving the results of that fit. So (and intended to exactly meet your use case!) you can change params and do another fit:

params['amplitude'].vary = True result2 = minimize(objfunction, params, ...)

params will hold the latest values, and result2.params will hold the parameters from the second fit, while result.params will still hold the parameters from the first fit, unaltered.

Comparing two similar fits seems like an important need, and I'm reluctant to change this -- I could be persuaded, but am not yet. Without this, there's really no point in having a results.params, and the user would have to keep making copies of results in order to be able to compare two fits.

Perhaps I'm not understanding, or perhaps this just needs to be documented better?

PhML commented 10 years ago

Sorry if I was not clear, my only concern is that changing params is not taken into account when running result.leastsq() and that result.leastsq() doesn’t update params. When I called result.leastsq() I thought it would had the same effects than calling minimize. Because (in my mind) calling result.leastsq() it’s when you don't want to keep result of the first run. But maybe I was wrong about that? I agree, if I do want to compare two fits, I create two results by calling minimize twice and store results in two different variables. Perhaps I just misunderstood the purpose of result.leastsq and tried to use it the wrong way?

newville commented 10 years ago

It's always harder to understand a question without an actual working example to show the issue. Like, I somehow didn't pick up on the fact that you were running results.leastsq(), but that raises other questions, and it seems like we're not reading each others minds very well.

Can you send a short example and explain what you see and what you expect?

Thanks, --Matt

On Tue, Apr 1, 2014 at 4:26 PM, PhML notifications@github.com wrote:

Sorry if I was not clear, my only concern is that changing params is not taken into account when running result.leastsq() and that result.leastsq()doesn't update params. When I called result.leastsq() I thought it would had the same effects than calling minimize. Because (in my mind) calling result.leastsq() it's when you don't want to keep result of the first run. But maybe I was wrong about that? I agree, if I do want to compare two fits, I create two results by calling minimize twice and store results in two different variables. Perhaps I just misunderstood the purpose of result.leastsq and tried to use it the wrong way?

Reply to this email directly or view it on GitHubhttps://github.com/lmfit/lmfit-py/issues/89#issuecomment-39261237 .

--Matt Newville 630-252-0431

newville commented 10 years ago

just to be clear, this

params = Parameters()
params.add('x', value=10, min=0, vary=False)

fitter = Minimizer(objfun, params)
fitter.leastsq()

fitter.params['x'].vary  = True
fitter.leastsq()

will change the fit parameter from fixed to varied for the second fit. Changing params['x'] will not (the fitter object uses its params attributes). Is that what you're trying to do?

PhML commented 10 years ago

Still from the example:

params = Parameters()
params.add('amp',   value= 10,  min=0, vary=False)
params.add('decay', value= 0.1)
params.add('shift', value= 0.0, min=-np.pi/2., max=np.pi/2)
params.add('omega', value= 3.0)

# do fit, here with leastsq model
result = minimize(fcn2min, params, args=(x, data))

# calculate final result
final = data + result.residual

# write error report
print("——————————————————")
print("report_fit(params)")
print("——————————————————")
report_fit(params, show_correl=False)
print("—————————————————————————")
print("report_fit(result.params)")
print("—————————————————————————")
report_fit(result.params, show_correl=False)

print("#####################################################################")
print("Change params['amp'].vary from False to True and run result.leastsq()")
print("#####################################################################")
params['amp'].vary = True
result.leastsq()

print("——————————————————")
print("report_fit(params)")
print("——————————————————")
report_fit(params, show_correl=False)
print("—————————————————————————")
print("report_fit(result.params)")
print("—————————————————————————")
report_fit(result.params, show_correl=False)

print("############################################################################")
print("Change result.params['amp'].vary from False to True and run result.leastsq()")
print("############################################################################")
result.params['amp'].vary = True
result.leastsq()

print("——————————————————")
print("report_fit(params)")
print("——————————————————")
report_fit(params, show_correl=False)
print("—————————————————————————")
print("report_fit(result.params)")
print("—————————————————————————")
report_fit(result.params, show_correl=False)

gives:

—————————————————— report_fit(params) —————————————————— [[Variables]] amp: 10 (fixed) decay: 0.1029322 +/- 0.006524682 (6.34%) initial = 0.1 omega: 2.069038 +/- 0.02659877 (1.29%) initial = 3 shift: -0.2771565 +/- 0.03962326 (14.30%) initial = 0 ————————————————————————— report_fit(result.params) ————————————————————————— [[Variables]] amp: 10 (fixed) decay: 0.1029322 +/- 0.006524682 (6.34%) initial = 0.1 omega: 2.069038 +/- 0.02659877 (1.29%) initial = 3 shift: -0.2771565 +/- 0.03962326 (14.30%) initial = 0 ##################################################################### Change params['amp'].vary from False to True and run result.leastsq() ##################################################################### —————————————————— report_fit(params) —————————————————— [[Variables]] amp: 10 +/- 0 (0.00%) initial = 10 decay: 0.1029322 +/- 0.006524682 (6.34%) initial = 0.1 omega: 2.069038 +/- 0.02659877 (1.29%) initial = 3 shift: -0.2771565 +/- 0.03962326 (14.30%) initial = 0 ————————————————————————— report_fit(result.params) ————————————————————————— [[Variables]] amp: 10 (fixed) decay: 0.1029217 +/- 0.006523303 (6.34%) initial = 0.1029322 omega: 2.069033 +/- 0.02659563 (1.29%) initial = 2.069038 shift: -0.2771503 +/- 0.03962212 (14.30%) initial = -0.2771565 ############################################################################ Change result.params['amp'].vary from False to True and run result.leastsq() ############################################################################ —————————————————— report_fit(params) —————————————————— [[Variables]] amp: 10 +/- 0 (0.00%) initial = 10 decay: 0.1029322 +/- 0.006524682 (6.34%) initial = 0.1 omega: 2.069038 +/- 0.02659877 (1.29%) initial = 3 shift: -0.2771565 +/- 0.03962326 (14.30%) initial = 0 ————————————————————————— report_fit(result.params) ————————————————————————— [[Variables]] amp: 4.95749 +/- 0.03897985 (0.79%) initial = 10 decay: 0.02511303 +/- 0.0004511665 (1.80%) initial = 0.1029217 omega: 2.000501 +/- 0.003232409 (0.16%) initial = 2.069033 shift: -0.09966166 +/- 0.0101004 (10.13%) initial = -0.2771503

You can see that when you call result.leastsq() there is no more interaction with params. I don’t know if result.leastsq() should read params or result.params, but shouldn’t it update params at least? Sorry, I should have provided this example in my first comment.

newville commented 10 years ago

Yes, as I said above, doing

 result = minimize(fcn2min, params, args=(x, data))
 params['amp'].vary = True
 result.leastsq()

won't do what you want. Then again, how could it -- you changed a variable but didn't tell the result object of the change.

The minimizer() returns a Minimizer instance, which uses its own .params member in the fit. result.params is copied when the fit is done. All parameters in params and result.params are equal but not identical. If you do

 result = minimize(fcn2min, params, args=(x, data))
 result.params['amp'].vary = True
 result.leastsq()

you will get a different fit.

I think this is a non-issue. I'm not even convinced any of the docs are wrong on this....