Skip to content

Commit

Permalink
fix: respect fixed parameters in n_dof calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzennio committed May 30, 2024
1 parent 7167074 commit 02dce09
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 7 deletions.
11 changes: 8 additions & 3 deletions src/cabinetry/fit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,10 @@ def _run_minos(


def _goodness_of_fit(
model: pyhf.pdf.Model, data: List[float], best_twice_nll: float
model: pyhf.pdf.Model,
data: List[float],
best_twice_nll: float,
fix_pars: Optional[List[bool]] = None,
) -> float:
"""Calculates goodness-of-fit p-value with a saturated model.
Expand All @@ -381,6 +384,8 @@ def _goodness_of_fit(
data (List[float]): the observed data
best_twice_nll (float): best-fit -2 log(likelihood) of fit for which goodness-
of-fit should be calculated
fix_pars (Optional[List[bool]], optional): list of booleans specifying which
parameters are held constant, defaults to None (use ``pyhf`` suggestion)
Returns:
float: goodness-of-fit p-value
Expand Down Expand Up @@ -416,7 +421,7 @@ def _goodness_of_fit(
# of bins minus the number of unconstrained parameters
n_dof = sum(
model.config.channel_nbins.values()
) - model_utils.unconstrained_parameter_count(model)
) - model_utils.unconstrained_parameter_count(model, fix_pars)
log.debug(f"number of degrees of freedom: {n_dof}")

if n_dof <= 0:
Expand Down Expand Up @@ -502,7 +507,7 @@ def fit(

if goodness_of_fit:
# calculate goodness-of-fit with saturated model
p_val = _goodness_of_fit(model, data, fit_results.best_twice_nll)
p_val = _goodness_of_fit(model, data, fit_results.best_twice_nll, fix_pars)
fit_results = fit_results._replace(goodness_of_fit=p_val)

return fit_results
Expand Down
17 changes: 14 additions & 3 deletions src/cabinetry/model_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,24 +481,35 @@ def prediction(
)


def unconstrained_parameter_count(model: pyhf.pdf.Model) -> int:
"""Returns the number of unconstrained parameters in a model.
def unconstrained_parameter_count(
model: pyhf.pdf.Model,
fix_pars: Optional[List[bool]] = None,
) -> int:
"""Returns the number of unconstrained, non-constant parameters in a model.
The number is the sum of all independent parameters in a fit. A shapefactor that
affects multiple bins enters the count once for each independent bin. Parameters
that are set to constant are not included in the count.
Args:
model (pyhf.pdf.Model): model to count parameters for
fix_pars (Optional[List[bool]], optional): list of booleans specifying which
parameters are held constant, defaults to None (use ``pyhf`` suggestion)
Returns:
int: number of unconstrained parameters
"""
n_pars = 0
# custom fixed parameters overrule suggested fixed parameters
if fix_pars is None:
fix_pars = model.config.suggested_fixed()

for parname in model.config.par_order:
if not model.config.param_set(parname).constrained:
# only consider non-constant parameters
n_pars += model.config.param_set(parname).suggested_fixed.count(False)
par_slice = model.config.par_slice(parname)
n_pars += fix_pars[par_slice].count(False)

return n_pars


Expand Down
2 changes: 1 addition & 1 deletion tests/fit/test_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ def test_fit(mock_fit, mock_print, mock_gof):

# goodness-of-fit test
fit_results_gof = fit.fit(model, data, goodness_of_fit=True)
assert mock_gof.call_args_list == [((model, data, 2.0), {})]
assert mock_gof.call_args_list == [((model, data, 2.0, None), {})]
assert fit_results_gof.goodness_of_fit == 0.1


Expand Down
6 changes: 6 additions & 0 deletions tests/test_model_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,12 @@ def test_unconstrained_parameter_count(example_spec, example_spec_shapefactor):
model = pyhf.Workspace(example_spec_shapefactor).model()
assert model_utils.unconstrained_parameter_count(model) == 3

# fixed parameters can be provided to function
model = pyhf.Workspace(example_spec_shapefactor).model()
fix_pars = model.config.suggested_fixed()
fix_pars[0] = True
assert model_utils.unconstrained_parameter_count(model, fix_pars) == 2

# fixed parameters are skipped in counting
example_spec_shapefactor["measurements"][0]["config"]["parameters"].append(
{"name": "Signal strength", "fixed": True}
Expand Down

0 comments on commit 02dce09

Please sign in to comment.