Pre-release notes: v0.2.7-b2#
Status: beta 2 (
v0.2.7-b2) Branch merged:SpeysideHEP/multiparam-fitvia PR #63
Highlights#
This release is centred on two substantial extensions: support for fits with multiple
parameters of interest (multi-POI), and a richer treatment of signal uncertainties
through callable signal-yield functions. A new confidence-contour mapping algorithm,
an improved chi2_test profiler, a thread-safe result cache, and a migration from
setup.py to pyproject.toml round out the changes.
Multi-parameter-of-interest fit#
poi_test now accepts a dictionary#
Every method that previously required a single float for poi_test now also accepts
Dict[Union[int, str], float]. Each entry in the dictionary fixes the corresponding
parameter (identified by its integer index or by the name stored in
ModelConfig.parameter_names) at the given value during the fit; the remaining
parameters are optimised freely. A plain float retains exactly the previous
single-POI behaviour.
The new type alias PoiTest is exported from spey.interface.statistical_model for
type-annotation convenience. Affected interfaces include
StatisticalModel.likelihood, StatisticalModel.asimov_likelihood,
StatisticalModel.fixed_poi_sampler, StatisticalModel.sigma_mu_from_hessian,
HypothesisTestingBase.chi2, HypothesisTestingBase.sigma_mu,
HypothesisTestingBase.exclusion_confidence_level, and the corresponding methods on
UnCorrStatisticsCombiner.
ModelConfig helpers#
Two new methods have been added to ModelConfig:
resolve_poi_indicesconverts anyPoiTestvalue into a normalised{param_index: value}mapping.fixed_poi_bounds_multigeneralises the existingfixed_poi_boundsto an arbitrary set of simultaneously fixed parameters.
optimizer.fit extended#
The fixed_poi_value keyword argument of optimizer.fit now accepts
Dict[int, float], allowing any combination of parameters to be held fixed
in a single optimisation call.
Retrieving multi-POI fit results#
maximize_likelihood and maximize_asimov_likelihood gain an optional
poi_indices keyword argument (Optional[List[Union[int, str]]], default None).
When a list of indices or names is supplied, both methods return
(Dict[Union[int, str], float], nll) with the fitted value for each requested
parameter, rather than the usual (muhat, nll) scalar form.
muhat_dict, nll = model.maximize_likelihood(poi_indices=[0, 1])
Both StatisticalModel and UnCorrStatisticsCombiner support this interface.
Confidence-contour mapping: spey.multiparameter.find_contour#
A new sub-package, spey.multiparameter, provides find_contour, which maps the
\((1-\alpha)\) chi-squared confidence region boundary for a
StatisticalModel backed by "default.multivariate_normal" in its full
parameter space.
The algorithm operates in four stages:
Pre-whitening. The Hessian of the negative log-likelihood at the MLE is Cholesky-factored to produce a whitened coordinate system in which the contour is approximately a sphere of radius \(\sqrt{\Delta_\alpha}\). This removes the strong anisotropy that is typical of correlated parameter spaces and gives approximately uniform coverage when directions are sampled at random.
Radial search. \(N\) random rays are fired from the MLE in whitened space. Each ray’s crossing of the NLL threshold \(T = \mathrm{NLL}(\hat\theta) + \Delta_\alpha/2\) is located by Brent’s method.
Gap detection. A large pool of candidate directions is scored by distance to the radial set; the least-covered directions seed the subsequent HMC chains.
Constrained RATTLE HMC. Each seed direction initialises a Hamiltonian Monte Carlo chain that walks along the constraint manifold \(\mathrm{NLL}(\theta) = T\), projecting both position and momentum at every step to fill in gaps left by the radial pass.
The function returns a ContourResult dataclass containing the MLE, the NLL minimum,
the threshold, the chi-squared quantile, the array of contour points, a Boolean mask
distinguishing radial from HMC-derived points, optional parameter names, the
confidence level, and the degrees of freedom.
A multi-start scan (n_multistart) over the parameter space is performed before the
root search to reduce the risk of the NLL minimum anchor being trapped in a local
minimum.
Signal uncertainties and callable signal yields#
Callable signal_yields in default PDF backends#
All default-PDF backends (UncorrelatedBackground, CorrelatedBackground,
ThirdMomentExpansion, EffectiveSigma, and MultivariateNormal) now accept
signal_yields as a callable with signature
(extra_pars: np.ndarray) -> np.ndarray in addition to a plain array. The
companion keyword argument n_signal_parameters (default 0) declares how many
free parameters beyond \(\mu\) the callable requires; they are appended to the
optimiser parameter vector as signal_par_0, signal_par_1, and so on, and
are included in ModelConfig with (None, None) bounds. The plain-array path is
unchanged.
signal_parameter_bounds#
All default-PDF backends gain a signal_parameter_bounds keyword argument
(Optional[List[Tuple[Optional[float], Optional[float]]]], default None).
When provided, each entry supplies the (lower, upper) optimiser bound for the
corresponding extra signal parameter; a None element leaves that side unbounded.
The list must have exactly n_signal_parameters entries. These bounds are stored
in ModelConfig.suggested_bounds and propagated to the optimiser automatically.
signal_uncertainty_synthesizer updated#
The signal_uncertainty_synthesizer helper now accepts n_signal_parameters so
that the domain index offsets for nuisance parameters are shifted correctly when
the backend uses a callable signal_yields. This keeps the parameter-vector
layout consistent across all default-PDF backends.
Improved handling of zero yields and absolute uncertainty regions#
Bins with zero background yields are now handled without raising an exception.
Absolute uncertainty regions are validated on construction, with informative
warnings rather than silent failures when delta_up or delta_dn would resolve
to a physically dubious value.
chi2_test improvements#
The chi2_test method has been updated to handle non-convex profile likelihoods
with disjoint confidence regions correctly. The profile is now enumerated by a
coarse grid scan followed by bracketed root refinement using toms748, so every
crossing of the threshold is found and returned in ascending order. Two new
keyword arguments control the precision-vs-cost trade-off:
n_scan(default3): number of uniformly-spaced grid points for the coarse scan. Values below 3 are clamped to 3.n_multistart(default2): number of evenly-spaced evaluations used by the internal scan that re-anchors the NLL minimum. Values below 2 are clamped to 2.
Additionally, chi2_test can now profile any nuisance parameter by setting the
parameter keyword to the index or name of the parameter of interest, whilst
fixing the primary POI to a specified poi_value. Setting poi_value=None allows
the primary POI to float freely during the scan.
Backend NLL dispatch#
StatisticalModel.likelihood, StatisticalModel.asimov_likelihood, and
StatisticalModel.maximize_likelihood now attempt to call native
negative_loglikelihood, asimov_negative_loglikelihood, and
minimize_negative_loglikelihood methods on the backend before falling back to the
general numerical-optimisation path. Backends that implement these stubs can bypass
the generic fitting loop entirely and supply their own analytically or numerically
superior NLL evaluations. Backends that do not implement a stub raise
NotImplementedError, which is caught silently at debug level.
Thread-safe result cache: spey.system.cache#
A new cache_results decorator is provided in spey.system.cache. Unlike
functools.lru_cache, it handles non-hashable arguments (NumPy arrays, lists,
dicts) by constructing a deterministic cache key from the SHA-256 digest of each
array buffer and a recursive tokenisation of other containers. A threading lock
with per-key events serialises concurrent reads and writes, ensuring that each
unique input is computed at most once even under contention. When applied to
instance methods, _PerInstanceCacheDescriptor provides per-instance storage so
that two distinct model objects never share a cache entry.
generate_asimov_data on StatisticalModel is now decorated with
cache_results(maxsize=128, copy_on_return=True, per_instance=True).
Performance improvements#
For
pdf_type="multivariategauss"with a constant covariance matrix, a singleMultivariateNormalinstance is now created at construction time and only itsmeanattribute is updated on each likelihood call. The previous implementation re-instantiated the distribution on every call, incurring two \(O(n^3)\) operations (np.linalg.invandnp.linalg.slogdet) per evaluation.Redundant array slices in
Normal.log_probandMultivariateNormal.log_probhave been reduced from four to one per call.
Build system#
setup.py has been removed. The project now uses pyproject.toml exclusively,
including sdist generation in the CI build workflow.
Bug fixes#
fixed_poi_valueis now correctly resolved throughmlewhen passed as a keyword argument.chi2_testno longer miscategorises the sign of therightlimit whenallow_negative_signalis inferred automatically.Config caching that could return stale
ModelConfigobjects after parameter changes has been removed.