Skip to content

[RF][HS3] Array distributions and HistFactory constraint terms (HS3 #109, #124; depends on #24001) - #24013

Draft
cburgard wants to merge 17 commits into
root-project:masterfrom
cburgard:hs3-array-dist
Draft

cburgard wants to merge 17 commits into
root-project:masterfrom
cburgard:hs3-array-dist

Conversation

@cburgard

@cburgard cburgard commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Important

Depends on #24001 (which depends on #23840). This branch is stacked on it: please review only the last 5 commits. It stays in draft until HS3 PR #109 is merged (the HistFactory part also follows HS3 PR #124), and will be rebased when the PRs below it are merged.

Rebased onto master after the merge of #23996, including the follow-up commits added there during review (e.g. isHS3VersionBefore() instead of isLegacyHS3Version(), analyses named after their ModelConfig). The test FormulaNormSysConstraint (added in #23996) is adapted: the constraint is now written into the array distribution of its type, and its name after the round trip depends on that grouping, so the test checks that the parameter is still constrained by a Gaussian. Likewise, HistFactoryShapeFactor (added in #23840) expects the constrained gamma to name the array distribution.

This Pull request:

Implements array_dist from HS3 PR #109 in RooFit's HS3 import and export, and uses it for the constraint terms of HistFactory models, which are also listed in the likelihood from now on.

An array_dist is a product of one-dimensional distributions of one type (component_type), each over its own observable x, with the parameters given per component or in default_parameters. It is the form that is to replace barlow_beeston_lite_poisson_constraint_dist as the recommended staterror constraint of #124 (to be changed in #109 once #124 is merged; the BB-lite distribution is then going to be deprecated), and the form of the HS3 appendix on auxiliary measurements (one array of constraint terms paired with one data set of observed values).

RooFit gets no dedicated class for it: an array is split into one pdf per component, and a tagged RooProdPdf records the grouping. A vectorized implementation is left for later.

Changes or fixes:

1. Import of array_dist

What How
Components One pdf per component through the importer of the component type (RooPoisson, RooGaussian, RooLognormal), with default_parameters filled in where a component omits a parameter. Numbers and names may be mixed. Named after the observable: <x>_constraint.
The array A RooProdPdf named after the array, with the attribute hs3_array_dist, so that the array is written back as array_dist.
Supported types poisson_dist, gaussian_dist, lognormal_dist; anything else is an explicit error. A poisson_dist component without RooFit's integer flag is the continued Poisson distribution.
Validation Errors for duplicate or numeric observables, x in default_parameters, empty components, and parameters that depend on an observable of the array (directly or through a function).

An array that no HistFactory modifier references, paired with a one-entry unbinned data set in a likelihood (the appendix form), gives the expected likelihood: one term per component.

2. Export of array_dist

A tagged RooProdPdf (imported, or built by hand) is written as array_dist: one component per factor with the fields of the standalone distribution, a field with the same value in every component once in default_parameters. The observable of a component has to be named, and no two components may share it. The tag is not written as an attribute; it is restored from the type on import.

New: RooJSONFactoryWSTool::exportAsArrayComponent(). The components are registered while the objects that use them are exported, and the arrays are written after all other objects, so that the position of an array in the document does not depend on when its components were registered (the HistFactory channels are exported before the arrays they reference, and a re-export must give the same document).

HistFactory builds its Gaussian constraints with the parameter as x and the global observable as mean, a long-known oddity. The HistFactory exporter records for every constraint which side is the observable (the one that does not depend on the modifier parameter, also for constant parameters such as Lumi), and Gaussians are written with the observable as x (the Gaussian is symmetric in the two), both as array components and as standalone gaussian_dist. Re-imported workspaces therefore have the usual orientation, and the data pairing of section 4 refers to the observable. Gaussians with a number as mean keep the parameter as x, there is nothing to swap with.

3. HistFactory import

A modifier may name an array_dist in constraints. The importer picks the component that depends on the parameter (directly or through a function, e.g. the product of a gamma and its τ) out of the array; no or several matching components is an error. A constraint shared by several channels is one object in all of them, so it is counted once, as with individually named constraints. Components of an array that no parameter uses are reported with a warning at the end of the import, since by the standard the whole array enters the likelihood.

4. HistFactory export

What How
Grouping One array_dist per constraint type and top-level pdf: <toplevel>_poisson_constraints, <toplevel>_gaussian_constraints, <toplevel>_lognormal_constraints. The modifiers name the array for each constrained parameter ("constraints": ["simPdf_poisson_constraints", "simPdf_poisson_constraints"]). A constraint shared by several channels is one component. A constraint that already belongs to an array (imported document) stays in that array.
Order Channels, samples and modifiers in the order they are written; the gammas of staterror and shapesys in bin order.
Not grouped Constraints that cannot be components: a number as observable (as in rf515_hfJSON.json), a RooLognormal with the transformed parametrization. They are written on their own as before.
Likelihood The constraint terms of the channels of a model are listed in its likelihood: by default in aux_distributions (observed values as constant parameters, the global observables), or with config().exportConstraintData = true in distributions, each paired with a generated data set <term>_data (unbinned, one axis per observable, one entry) for terms whose observables are global observables of the ModelConfig. Constraint terms not attached to any modifier parameter are listed too, instead of being lost.
Opt-out config().exportArrayDist = false: no array_dist is written at all. HistFactory constraints are named individually as before, a tagged product is a plain product_dist.

On import, a term listed in the likelihood that is a constraint of one of the listed HistFactory distributions is not added again (as external constraint or as a channel); its data set sets the observed values, keeps them constant, puts them into the global observables of the ModelConfig, and is not imported into the workspace.

The normfactor export now finds the constraint of a parameter among its clients (like the other modifiers), independently of which side of a Gaussian the parameter is.

Behaviour changes users may notice

  • RooFit's HistFactory export changes for every constrained modifier: constraints names one array per type instead of one distribution per parameter, the arrays stand at the end of distributions, and the likelihood lists them in aux_distributions. Consumers of these documents need array_dist (HS3 0.3). config().exportArrayDist = false restores the previous form of the constraints.
  • After an HS3 round trip, the constraint pdfs of a HistFactory workspace are named after their observable (nom_gamma_stat_channel1_bin_0_constraint, nom_alpha_syst1_constraint) instead of ..._constraint/...Constraint after the parameter, Gaussian constraints of normsys/histosys/normfactor parameters have the global observable as x (in every export mode), and the workspace holds one top-level tagged RooProdPdf per array. The likelihood is unchanged (tested).
  • Documents written before this change, and documents without array_dist, import as before.

Checklist:

  • tested changes locally
  • updated the docs (if necessary)

Tests run (Linux, Ubuntu 26.04, GCC 15.2, RelWithDebInfo, -Droofit=ON -Dtesting=ON): testRooFitHS3 115 → 125 tests (10 new), testJSONInterface 1, testHistFactory 69 → 70 (1 new), all passing; each commit built and tested on its own. clang-format clean.

New tests: ArrayDistImport, ArrayDistImportErrors, ArrayDistExport, ArrayDistExportOfTaggedProduct, ArrayDistLikelihood, HistFactoryArrayDistConstraints (two channels with a shared normsys, staterror and shapesys, written with individual constraints and with arrays: same constraint terms, same NLL), HistFactoryArrayDistConstraintErrors, HistFactoryArrayDistExport (structure and order of the written arrays, NLL unchanged by the round trip, second and third export identical, opt-out), HistFactoryConstraintTermsInLikelihood (both routes, with and without arrays), TutorialRF515RoundTrip, and HS3ArrayDistRoundTrip_ROOT6_16_Combined_Model in testHistFactory (reference workspace of ROOT 6.16: model sections stable under a round trip, NLL unchanged up to a constant).

Updated to the grouped form: HistFactoryConstraintKeyMigration, HistFactoryLegacyConstraintTypeInConstraintKey, HistFactoryStatErrorConstraint, HistFactoryConstraintsByName.

AI assistance: Claude Code (claude-fable-5-1) implemented the changes, the tests and this description under review of the author. The commits carry Assisted-by: claude-code:claude-fable-5-1.

🤖 Generated with Claude Code

HS3 PR "json-fixes-0.3" defines `hs3_version` as a `"MAJOR.MINOR"` string
and the version of the revised standard as `"0.3"`. RooFit now writes this
version. Documents with a newer version are imported with a warning.

This commit depends on the HS3 PR and should only be merged together with
or after it.

Assisted-by: claude-code:claude-opus-5-5
HS3 PR "json-fixes-0.3" renames the analysis keys to
`parameters_of_interest` and `domains` (a list of domain names), which is
what RooFit already writes, including one entry each for the nuisance
parameter, global observable and parameter-of-interest domains.

The keys of HS3 v0.2.9, `parameter_of_interest` and `domain` (a single
name), are now also read, with a deprecation warning. The plural keys take
precedence.

Assisted-by: claude-code:claude-opus-5-5
…f writing null

RooFit wrote infinite bounds of product-domain axes as JSON `null`, which
HS3 doesn't allow. HS3 PR "json-fixes-0.3" makes `min` and `max` of domain
axes optional, with an absent bound meaning an unbounded range. Infinite
bounds are now omitted on export. Since that PR also requires both bounds
for a binning, the default binning is only written for bounded axes.

Axes without any bound are still written (with only their `name`), which
the HS3 PR keeps valid. RooFit needs them to tell an unbounded RooRealVar
from a RooConstVar. On import, an absent bound means -inf/+inf, and `null`
is still accepted for files written by older ROOT versions.

The RooGaussianConstVarSigmaExport and ProductDomainEntriesExportExplicitBounds
tests are updated to expect omitted instead of null bounds.

Assisted-by: claude-code:claude-opus-5-5
…ll_dist and crystalball_doublesided_dist

HS3 PR root-project#135 defines `crystalball_dist` as the single-sided Crystal Ball
(`sigma`, `alpha`, `n`, as RooCBShape) and the new
`crystalball_doublesided_dist` as the double-sided one (`alpha_L`, `n_L`,
`alpha_R`, `n_R`, and `sigma` or `sigma_L`/`sigma_R`, without a symmetric
shorthand).

* RooCBShape is exported as `crystalball_dist`, as before.
* A single-sided RooCrystalBall is exported as `crystalball_dist`, and read
  back as a RooCBShape (same function, same sign convention for alpha).
  Before, it was written as an incomplete `crystalball_doublesided_dist`
  without `alpha_R`/`n_R`, which could not be read back.
* A double-sided RooCrystalBall is exported as
  `crystalball_doublesided_dist`, with `sigma` if both sides share it, and
  the tails always as `alpha_L`/`n_L`/`alpha_R`/`n_R`.
* `crystalball_doublesided_dist` is imported as RooCrystalBall, also with a
  single `sigma`, and invalid field combinations are rejected.
* `crystalball_dist` with any `_L`/`_R` field is the double-sided form of
  HS3 versions before 0.3. It is still read as RooCrystalBall (including
  `alpha`/`n` for both tails with `sigma_L`/`sigma_R`), with a one-time
  deprecation warning.

The `crystalball_doublesided_dist` factory expression and the RooCrystalBall
export keys are removed, since the new importer and exporter handle all
cases.

Assisted-by: claude-code:claude-opus-5-5
…on't contribute to the staterror

HS3 PRs root-project#135 and root-project#124 make the "errors" of a HistFactory sample optional: a
sample without "errors" contributes no statistical uncertainty to a
staterror modifier. RooFit used the content as squared error instead, i.e.
assumed Poisson uncertainties. That is kept for documents before HS3 0.3.

In HS3 >= 0.3 documents, this only matters if the staterror is constrained
through the legacy "constraint" key, since otherwise the gammas are either
unconstrained or use the named constraint distributions.

Assisted-by: claude-code:claude-opus-5-5
…eference constraints by name

HS3 PR root-project#124 replaces the `parameter` and `constraint` (a constraint type
like `Gauss`) of HistFactory modifiers with the lists `parameters` and
`constraints`. Each entry of `constraints` names a distribution in the
top-level `distributions`, and `null` leaves a parameter unconstrained.
`shapesys` loses its `data.vals`, since the uncertainties are encoded in
the constraint distributions. HS3 PR root-project#129 states that the constraint terms
belong to the prediction, which is how RooFit already builds the model.

Import:
* `parameters` and `constraints` are read for all modifiers, and take
  precedence over the legacy `parameter`, `constraint`, `constraint_name`
  and `constraint_type`, which are still read for older files.
* For `staterror`, the named constraint distributions are used instead of
  creating Poisson terms. Without any constraint information, the gammas
  are unconstrained in documents with `hs3_version` >= 0.3; older documents
  still get the Poisson constraints that RooFit used to create implicitly.
* The `nom_*` global observable is now only created when RooFit builds a
  constraint itself (legacy `Gauss`).

Export:
* All modifiers write `parameters` and, if any parameter is constrained,
  `constraints` with the names of the constraint distributions, which are
  exported alongside. `parameter`, `constraint` and `shapesys` `data.vals`
  are no longer written.
* The RooPoisson (or RooGaussian) constraint terms of the staterror gammas
  are exported as named `poisson_dist` (or `gaussian_dist`) and referenced
  from the staterror modifiers. root-project#124 recommends a
  `barlow_beeston_lite_poisson_constraint_dist`, which RooFit doesn't
  support yet; any distribution type is allowed.
* The channel-level `staterror.constraint_type` and the `constraint` type
  on staterror modifiers (written since the previous HistFactory commits)
  are no longer written, but still read.

Tests that checked the exported `constraint` key are updated to the new
form, and a new test covers import and export by name.

This commit depends on HS3 PRs root-project#124 and root-project#129 and should only be merged
together with or after them.

Assisted-by: claude-code:claude-opus-5-5
…max on irregular binned axes

RooFit wrote only `edges` for binned axes with an irregular binning. HS3
PR root-project#124 requires `name`, `min`, `max` and exactly one of `nbins` or
`edges` on the axes of a `histfactory_dist`, while HS3 PR root-project#135 makes
`min`/`max` optional next to `edges`, but allows them if they equal the
first and last edge. Writing them satisfies both, so binned axes written
via `exportAxis()` (binned data, histograms, ParamHistFunc and HistFactory
axes) now always carry `min` and `max`. The `axes` of generic formulas are
unchanged, because their reader requires either `edges` or
`min`/`max`/`nbins`.

Assisted-by: claude-code:claude-opus-5-5
… named constraints in rf515_hfJSON

Write the HistFactory modifiers of the rf515_hfJSON tutorial in the form
of HS3 PR root-project#124: every modifier names its parameters in `parameters`, and
its constraint distributions by name in `constraints`. The constraint
terms that RooFit used to create implicitly are now explicit:

* unit Gaussians (mean 0, sigma 1) for the normsys parameters, as
  recommended by root-project#124;
* Poisson terms for the two staterror gammas, with the tau values
  (400 and 100) that follow from the sample errors. root-project#124 recommends a
  `barlow_beeston_lite_poisson_constraint_dist`, which RooFit doesn't
  support yet, so `poisson_dist` is used.

The gammas are declared in a domain and in the default parameter point, so
that the functions referring to them can be imported. The document now
declares `hs3_version` 0.3.

The likelihood is the same as before up to an additive constant of
1.3e-5 (checked at several parameter points), and the fit result agrees
within the minimizer tolerance.

Assisted-by: claude-code:claude-opus-5-5
… shapefactor as "shapefactor"

Following the proposal for HS3 PR root-project#124, HS3 has a single per-bin modifier
type, `shapefactor`, whose parameters are constrained by the distributions
named in `constraints`, if given, and free otherwise. `shapesys` is a
deprecated alias.

* Export: HistFactory's ShapeSys and ShapeFactor are both written as
  `shapefactor`, with the constraint distributions found for the gammas in
  `constraints` (null for free gammas, omitted if all are free). The type is
  no longer derived from the name of the ParamHistFunc, so free gammas no
  longer make the export fail with "cannot find constraint", and constraints
  of any type are allowed (the unused relative errors, which only supported
  Gaussian and Poisson constraints, are no longer computed).
* Import: `shapesys` is read as `shapefactor`, with a deprecation warning
  (once per channel) for HS3 >= 0.3. The ParamHistFunc is named
  `<channel>_<name>_ShapeSys` if constrained and
  `<channel>_<name>_shapeFactor` if free, as in HistFactory.
* Without `constraints`, RooFit only creates constraints from `data.vals`
  for documents before HS3 0.3 or with a legacy constraint key; otherwise the
  parameters are free and `data.vals` is ignored with a warning.

The HistFactory test of the HS3 import of ShapeFactors now round-trips a
free ShapeFactor, which needed constant gammas before.

Assisted-by: claude-code:claude-opus-5-5
HS3 PR root-project#108 added categorical axes (`{"name": ..., "categories": [...]}`) to
domains and data sets. RooFit used to read a categorical domain axis as an
unbounded RooRealVar and could not read categorical axes of data.

* Domains: a categorical axis creates a RooCategory with one state per
  category, in the order of the document and with the position as index. An
  existing RooCategory of that name must have the same labels. The axis must
  not have `min`, `max`, `nbins` or `edges`, and the labels must be unique
  non-empty strings. A string value in a parameter point sets the state.
* Unbinned data: the entries of a categorical axis are labels of the
  category, which is declared by a domain.
* Binned data: a categorical axis is one bin per category. HS3 identifies
  categories by name, so the data may list them in another order than the
  domain. RooDataHist looks up the bin of a category by the order of the
  states of the category it is given, so the data's category takes over the
  states and their order from the workspace category, and the contents and
  errors are permuted from the order of the document accordingly.
* histfactory_dist: categorical axes are rejected with an explicit error.

Export is unchanged: datasets and distributions with categories are still
written with the ROOT-internal combined form until the HS3 representation of
combined data (HS3 PR root-project#119) is final.

Assisted-by: claude-code:claude-opus-5-5
…ned contents

HS3 PR root-project#126 defines the storage order of multi-dimensional binned contents:
the last axis listed in "axes" varies fastest. This is RooDataHist's own
order, and RooFit writes the axes in the order of the variables of the
RooDataHist, so no change is needed for binned data. A new test pins it.

HistFactory channels write the axes from the first sample's histogram, and
the contents of every sample from its own histogram. If two histograms of a
channel had their observables in different orders, the contents would be
written along different axes. This is now an explicit error.

Assisted-by: claude-code:claude-opus-5-5
…amHistFunc in HS3 bin order

HS3 PR root-project#126 lays out every per-bin array with the last axis varying fastest,
like RooDataHist. ParamHistFunc, however, orders its parameters like the bins
of a TH1, with the first variable varying fastest. For more than one
dimension, RooFit therefore wrote the parameters of ParamHistFunc ("step")
and of the staterror and shapesys/shapefactor modifiers of HistFactory in a
different order than the bin contents. On import, it paired the relative
uncertainty of bin k with the k-th gamma in TH1 order, so the gammas of
multi-dimensional channels got the constraints of other bins.

The parameters are now converted between the two orders at the HS3
boundary: they are written in bin order (via ParamHistFunc::getParameter),
paired with the per-bin values in bin order, and only then put into TH1
order for the ParamHistFunc. One-dimensional functions are unchanged.

Documents before HS3 0.3 with multi-dimensional ParamHistFuncs don't define
the order; none are known, and they are rejected with an error instead of
being guessed.

New tests check the order in the written file for binned data, RooHistFunc
and ParamHistFunc (each with both orders of the variables), and for a
two-dimensional HistFactory channel with staterror.

Assisted-by: claude-code:claude-opus-5-5
…bution per component

HS3 PR root-project#109 adds `array_dist`: a product of one-dimensional distributions
of one type (`component_type`), each over its own observable `x`, with the
parameters given per component or in `default_parameters`.

RooFit has no dedicated class for it. The importer builds one pdf per
component through the importer of the component type (RooPoisson,
RooGaussian, RooLognormal), named `<x>_constraint` after its observable,
and a RooProdPdf named after the array that holds the components together
and carries the attribute `hs3_array_dist`, so that the array can be
written back as `array_dist` later.

Other component types are rejected with an explicit error, as are
duplicate or numeric observables, `x` in `default_parameters`, and
parameters that depend on an observable of the array (directly or through
a function). A `poisson_dist` component without RooFit's `integer` flag
uses the continued Poisson distribution.

Tests: ArrayDistImport (each type, default parameters, numbers and names,
values), ArrayDistImportErrors.

Assisted-by: claude-code:claude-fable-5-1
…_dist

A RooProdPdf with the attribute `hs3_array_dist` (as created by the
import of an `array_dist`, or set by hand) is written as `array_dist`:
one component per factor with the fields of the standalone distribution
(RooPoisson, RooGaussian, RooLognormal in the standard parametrization),
and a field with the same value in every component once in
`default_parameters`. The observable of a component has to be named,
and no two components may share it.

The components are registered with the new
RooJSONFactoryWSTool::exportAsArrayComponent(), which keeps them from
being written on their own, and the arrays are written after all other
objects, followed by the servers of their components. This way the
position of the arrays in the document does not depend on when their
components were registered, which the HistFactory export will rely on
(its channels are exported before the arrays they reference). The tag
itself is not written as an attribute, it is restored from the type.

The new option RooJSONFactoryWSTool::config().exportArrayDist (default
true) switches the arrays off: a tagged product is then an ordinary
product_dist with standalone components.

Tests: ArrayDistExport (round trip of imported arrays, default
parameters), ArrayDistExportOfTaggedProduct (hand-built array, opt-out,
write errors), ArrayDistLikelihood (array paired with a one-entry
unbinned data set as in the HS3 appendix).

Assisted-by: claude-code:claude-fable-5-1
…of HistFactory modifiers

A HistFactory modifier may name an `array_dist` as the constraint of a
parameter (HS3 root-project#124 names constraints per parameter, root-project#109 adds the
arrays). The importer then picks the component that depends on the
parameter, directly or through a function, out of the array: that is the
term that enters the likelihood for this parameter, and the whole array
enters once through the components of all of its parameters. A constraint
shared by several channels is one object in all of them, as with
individually named constraints. No component or several components
depending on the parameter is an error.

The constraints of shapesys and staterror modifiers are resolved per
gamma after the gammas exist, in the bin order of "constraints".

Components of an array that no parameter uses are reported with a warning
at the end of the import, since by the standard the whole array enters the
likelihood.

Tests: HistFactoryArrayDistConstraints (two channels with a shared normsys,
staterror and shapesys, written with individual constraints and with
arrays: same constraint terms and same NLL),
HistFactoryArrayDistConstraintErrors.

Assisted-by: claude-code:claude-fable-5-1
…ory constraints into array_dist on export

The HistFactory exporter collects the constraint distributions of its
modifiers (RooPoisson, RooGaussian, RooLognormal) into one `array_dist`
per type and top-level pdf, named `<toplevel>_poisson_constraints`,
`<toplevel>_gaussian_constraints` and `<toplevel>_lognormal_constraints`,
and the modifiers name that array in "constraints" for each constrained
parameter. This is the form HS3 root-project#124 will recommend for staterror after
root-project#109. A constraint shared by several channels is one component of the
array, so it enters the likelihood once, as with individually named
constraints. A constraint that already belongs to an array distribution
(an imported document) stays in that array, which keeps the grouping of
the document. The components follow the order of the channels, samples
and modifiers, with the gammas of staterror and shapesys in bin order.
Constraints that cannot be components (a literal number as observable,
as in the rf515 tutorial, or a RooLognormal with the transformed
parametrization) are written on their own as before.

With RooJSONFactoryWSTool::config().exportArrayDist = false, every
constraint is written on its own and named individually, as before.

Tests: HistFactoryArrayDistExport (structure of the written document,
order of the components, likelihood unchanged by the round trip, second
and third export identical, opt-out), TutorialRF515RoundTrip, the
reference workspace of ROOT 6.16 in testHistFactory
(HS3ArrayDistRoundTrip_ROOT6_16_Combined_Model: model sections stable,
likelihood unchanged). Updated to the grouped form:
HistFactoryConstraintKeyMigration, HistFactoryLegacyConstraintTypeInConstraintKey,
HistFactoryStatErrorConstraint, HistFactoryConstraintsByName.

Assisted-by: claude-code:claude-fable-5-1
…raint terms of HistFactory models in the likelihood

So far the constraint terms of HistFactory modifiers entered the
likelihood only implicitly, through the `histfactory_dist`. They are now
listed in the likelihood as well, so that the likelihood section states
the full likelihood:

- by default in "aux_distributions", with the observed values of their
  observables as constant parameters (global observables), after the
  external constraints of the ModelConfig;
- with RooJSONFactoryWSTool::config().exportConstraintData = true in
  "distributions", each paired with a generated data set "<term>_data"
  (unbinned, one axis per observable, one entry with the observed
  values), the form of the HS3 appendix on auxiliary measurements. Only
  terms whose observables are global observables of the ModelConfig are
  paired; the others stay in "aux_distributions". Ranges without a bound
  are written with the largest representable value.

Constraint terms that are not attached to any modifier parameter are
listed too, so they are no longer lost; the warning becomes an info.

HistFactory builds its Gaussian constraints with the parameter as x and
the global observable as mean. The exporter records, for every
constraint of a modifier parameter, which side is the observable (the
one that does not depend on the parameter, also for constant parameters
like Lumi), and Gaussians are written with the observable as x, both as
array components and as standalone gaussian_dist, so that the data
pairing refers to the observable and re-imported workspaces have the
usual orientation. The normfactor export finds constraints among the
clients of the parameter, independently of the orientation.

On import, a term listed in "aux_distributions" or in "distributions"
that is a constraint of one of the listed HistFactory distributions is
not added again (as external constraint or as a channel). Its data set
sets the observed values, keeps them constant, puts them into the global
observables of the ModelConfig, and is not imported.

Tests: HistFactoryConstraintTermsInLikelihood (both routes, with and
without arrays: structure, transported observed value, likelihood,
stability), the data route in TutorialRF515RoundTrip, the orientation of
the Gaussians in HS3ArrayDistRoundTrip_ROOT6_16_Combined_Model.

Assisted-by: claude-code:claude-fable-5-1

This branch has not been deployed

No deployments
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.

2 participants