Skip to content
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

CostSum support for cost functions accepting a parameter array #941

Open
lvarriano opened this issue Sep 20, 2023 · 3 comments
Open

CostSum support for cost functions accepting a parameter array #941

lvarriano opened this issue Sep 20, 2023 · 3 comments

Comments

@lvarriano
Copy link

Hello - I would like to add a NormalConstraint to an ExtendedBinnedNLL for one or more parameters. As noted in the docs, CostSum does not currently support cost functions that accept a parameter array. However, my model has tens of background components, each with a different strength parameter. I would like the ability to quickly adjust the number of components in the fit, which would be limited if I need to explicitly define each parameter in the model function.

Using a model function that takes an array of parameters, if I set the ExtendedBinnedNLL._parameters to a dictionary of the parameters, then I can add a NormalConstraint to it, and the fit seems to work perfectly. I attached a MWE. mwe.txt

Instead of accessing the internal _parameters myself, would it be possible to add a method to the Cost class that looks like the following? This seems to work for my problem, but I have not done extensive testing.

    @property
    def parameters(self):
        return self._parameters

    @parameters.setter
    def parameters(self, parameters: Dict[str, Optional[Tuple[float, float]]]):
        self._parameters = parameters

Alternatively, if it were possible to pass a name option to the ExtendedBinnedNLL class (and similar classes) as in the Template class, this should also fix the issue and is probably more consistent with the existing structure. It is already possible to add a Template to a NormalConstraint with no problem thanks to this.

@HDembinski
Copy link
Member

HDembinski commented Dec 7, 2023

The cost functions that accept a parametric model like ExtendedBinnedNLL use the function iminuit.util.describe(model, annotations=True) to detect the parameters of the model (the first argument is skipped).

You can override self._parameters in the cost function, but the intention is to add the special attribute _parameters to your model function. This can be done even if that function is a normal Python function or a lambda, because in Python everything is an object.

def my_model(x, *args): ...

# use my model with 3 parameters
my_model._parameters = {x: None, a: None, b: None, c: None}
nll = ExtendedBinnedNLL(counts, bin_edges, my_model)
Minuit(nll, ...).migrad()  # get results for 3 parameters

# use my model with 2 parameters
my_model._parameters = {x: None, a: None, b: None}
nll = ExtendedBinnedNLL(counts, bin_edges, my_model)
Minuit(nll, ...).migrad()  # get results for 2 parameters

Your solution works as well, but I don't think it is a good idea to add a public interface for this.

I don't want normal users to set parameters manually, because this is supposed to be handled behind the scenes by iminuit. For experts like you who need this, there is the option to provide the special _parameters attribute. This is documented in iminuit.util.describe.

@HDembinski
Copy link
Member

Adding a name keyword to the constructor of the cost functions seems like a possibility to me, but I need to think about the possible consequences.

@lvarriano
Copy link
Author

Thanks for the detailed explanation and for pointing me towards iminuit.utils.describe!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants