-
Notifications
You must be signed in to change notification settings - Fork 21
Fix curve fit ignore kwonlydefault #3465
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
c605b59 to
2a6dd6e
Compare
| if not set(coords).issubset(args): | ||
| raise ValueError("Function must take the provided coords as arguments") | ||
| default_arguments = dict( | ||
| zip(spec.args[-len(spec.defaults) :], spec.defaults, strict=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about spec.defaults?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that we are doing (almost) the same as scipy curve fit here, that is, ignoring default values unless they are kwargonly.
Example:
def model(x, a, c=3, *, d, e=5):
return ...Let's say x is a coordinate. Then a, c and d will be counted as parameters here in curve_fit.
e will not be counted as a parameter because it is kwargonly and has a default value, and might have been set by partial.
The default value of c will be discarded because it is not a kwargonly argument.
We differ from scipy because we also allow kwargonly arguments without defaults to be counted as parameters. That is, scipy would only have counted a and c as parameters in the above example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default value of c will be discarded because it is not a kwargonly argument.
Yes. My question is, why do we do that?
Also see #3465 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because that is what scipy does here, and I think it makes sense to try to do the same thing there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scipy does not allow keyword-only arguments. And it never seems to respect default arguments. So this argument does not work. We are deviating from Scipy by allowing keyword-only arguments. In the Scipy wrapper, we even require keyword-only arguments.
So my questions are:
- Why would
scipp.curve_fitallow positional arguments? - Why would
scipp.curve_fittread positional and keyword-only arguments differently if both are allowed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scipy does not allow keyword-only arguments. And it never seems to respect default arguments.
But it does? Scipy respects kwargonlydefault arguments, but not default arguments.
See here:
from scipy.optimize import curve_fit
curve_fit(lambda x, a, *, b=5: x * a, [0, 1, 2], [4, 5, 6])
# returns 1 parameter value, not 2It's true that Scipy does not allow keyword-only arguments. Scipy curve_fit will fail on functions with kwargonly arguments without default values.
Scipp doing something different in that case is not a deviation from scipy, more an extension, which I think makes sense in this case.
Answers to the questions:
Why would scipp.curve_fit allow positional arguments?
Why would it not? There's no reason to disallow it, and creating artificial constraints harms usability.
Why would scipp.curve_fit tread positional and keyword-only arguments differently if both are allowed?
Yes I see that this is a bit strange. But I think all the choices here have drawbacks. If we want to always treat positional and keyword arguments the same way we could either:
- respect default (non-keyword) arguments - but this differs significantly from scipy
- only allow parameters to be keyword arguments - makes the interface more cumbersome
src/scipp/curve_fit.py
Outdated
| spec = getfullargspec(f) | ||
| all_args = {*spec.args, *spec.kwonlyargs} | ||
| if not set(coords).issubset(all_args): | ||
| args = (set(spec.args) | set(spec.kwonlyargs)) - set(spec.kwonlydefaults or ()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scipp.scipy.optimize.curve_fit requires keyword-only args for all but the first argument. Is scipp.curve_fit different? If not, spec.args can be removed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes scipp.curve_fit is different in that regard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document this behaviour clearly! It is not clear from the signature how model arguments are handled.
jl-wynen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A small change but then it's ok.
src/scipp/curve_fit.py
Outdated
| non_default_args = ( | ||
| spec.args[: -len(spec.defaults)] if spec.defaults is not None else spec.args | ||
| ) | ||
| args = ( | ||
| {*non_default_args, *spec.kwonlyargs} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| non_default_args = ( | |
| spec.args[: -len(spec.defaults)] if spec.defaults is not None else spec.args | |
| ) | |
| args = ( | |
| {*non_default_args, *spec.kwonlyargs} | |
| args = ( | |
| {*spec.args, *spec.kwonlyargs} |
given that the defaults are removed below.
de3928e to
d5a68d8
Compare
d5a68d8 to
1cc552f
Compare
1cc552f to
2e0dc63
Compare
Fixes #3463
The inspection code in the curve fit function was complicated and didn't allow partial functions.
This change makes the logic a bit more transparent and allows for partial model functions.