Skip to content

axisnorm, axisexp, axisbernstein poi models#131

Draft
pmlugato wants to merge 2 commits intoWMass:mainfrom
pmlugato:new-poi-models
Draft

axisnorm, axisexp, axisbernstein poi models#131
pmlugato wants to merge 2 commits intoWMass:mainfrom
pmlugato:new-poi-models

Conversation

@pmlugato
Copy link
Copy Markdown

@pmlugato pmlugato commented Apr 21, 2026

N.B. This is a draft as it will follow #130, and conform to new naming scheme and file structure introduced there.

Summary

Adds three new POI model classes (AxisNormModel, AxisExpModel, AxisBernsteinModel) for per-cell
signal normalization and exponential or Bernstein polynomial (preliminary just 1st order) background parameterization, enables composing multiple POI models on a single fit via repeated --poiModel flags,
and fixes a bug in CompositePOIModel that froze parameters of sub-models with
unconstrained POIs.

Changes

poi_model.py

AxisNormModel: new class. Assigns one independent normalization POI per
(process, cell) where cells are defined by a caller-specified set of axes
looked up from indata.channel_info. POI = x² reparameterization enforces
non-negativity. All other channels and processes are left at 1.0.

AxisExpModel: new class. Assigns per-cell lnAmpl and per-group slope
POIs producing rnorm = exp(lnAmpl + slope·x) across the shape axis. Both
parameters are unconstrained reals (allowNegativePOI=True); slope=0 (flat
background) is an interior point so the Hessian is non-degenerate there. Slope
can optionally be shared across a coarser axis grouping via an optional 5th CLI
argument slope_axes, reducing parameters and stabilizing the Hessian when
per-cell slopes are under-constrained.

CompositePOIModel bug fix: previously hardcoded allowNegativePOI=False,
causing the fitter to apply x² to the entire combined parameter vector. This
silently froze sub-models with allowNegativePOI=True at initialization (zero
gradient via chain rule at x=0). Fixed by setting allowNegativePOI=True on
the composite and applying the x² transform per-sub-model inside compute()
based on each sub-model's own flag. Also corrects is_linear to require all
sub-models to be linear rather than inheriting the composite flag.

AxisBernsteinModel: new class. Assigns two non-negative POIs (c₀, c₁)
per (process, cell) and produces rnorm(x_m) = c₀·(1−x_m) + c₁·x_m (first-
order Bernstein polynomial) where x_m ∈ [0,1] is the normalized mass bin
center. Non-negativity is enforced via the x² reparameterization
(allowNegativePOI=False); default c₀=c₁=1 gives a flat unit background at
an interior point in parameter space. Structurally analogous to AxisExpModel
but bounded and linear, with no slope-grouping option.

helpers.py: registers AxisNormModel and AxisExpModel in the
built-in model loader so they resolve without a dotted module path.

parsing.py

--poiModel changed from a single nargs="+" argument to action="append" nargs="+", allowing the flag to be repeated. Multiple models are automatically
composed via CompositePOIModel in rabbit_fit.py.

rabbit_fit.py

Updated model instantiation to iterate over all --poiModel specs and wrap
them in CompositePOIModel when more than one is provided.

Usage example

# Exponential background with slope shared per eta bin
rabbit_fit.py tensor.hdf5 -o results/ \
  --poiModel AxisNormModel <channel> signal   pt,eta,charge \
  --poiModel AxisExpModel  <channel> bkgExp   mass pt,eta,charge eta

# First-order Bernstein background, one (c₀,c₁) per cell
rabbit_fit.py tensor.hdf5 -o results/ \
  --poiModel AxisNormModel    <channel> signal     pt,eta,charge \
  --poiModel AxisBernsteinModel <channel> background mass pt,eta,charge

@pmlugato pmlugato changed the title axisnorm and axisexp poi models axisnorm, axisexp, axisbernstein poi models Apr 22, 2026
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

Successfully merging this pull request may close these issues.

1 participant