Repository navigation
[RF][HS3] Array distributions and HistFactory constraint terms (HS3 #109, #124; depends on #24001) - #24013
Draft
cburgard wants to merge 17 commits into
Draft
[RF][HS3] Array distributions and HistFactory constraint terms (HS3 #109, #124; depends on #24001)#24013cburgard wants to merge 17 commits into
cburgard wants to merge 17 commits into
Conversation
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
cburgard
force-pushed
the
hs3-array-dist
branch
from
November 6, 2026 06:16
4384079 to
2d7b9df
Compare
This was referenced Nov 6, 2026
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
cburgard
force-pushed
the
hs3-array-dist
branch
from
November 6, 2026 09:46
2d7b9df to
488c992
Compare
1 of 2 tasks
cburgard
force-pushed
the
hs3-array-dist
branch
from
November 6, 2026 10:25
488c992 to
a91db49
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
masterafter the merge of #23996, including the follow-up commits added there during review (e.g.isHS3VersionBefore()instead ofisLegacyHS3Version(), analyses named after their ModelConfig). The testFormulaNormSysConstraint(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_distfrom 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_distis a product of one-dimensional distributions of one type (component_type), each over its own observablex, with the parameters given per component or indefault_parameters. It is the form that is to replacebarlow_beeston_lite_poisson_constraint_distas the recommendedstaterrorconstraint 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
RooProdPdfrecords the grouping. A vectorized implementation is left for later.Changes or fixes:
1. Import of
array_distRooPoisson,RooGaussian,RooLognormal), withdefault_parametersfilled in where a component omits a parameter. Numbers and names may be mixed. Named after the observable:<x>_constraint.RooProdPdfnamed after the array, with the attributehs3_array_dist, so that the array is written back asarray_dist.poisson_dist,gaussian_dist,lognormal_dist; anything else is an explicit error. Apoisson_distcomponent without RooFit'sintegerflag is the continued Poisson distribution.xindefault_parameters, emptycomponents, 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_distA tagged
RooProdPdf(imported, or built by hand) is written asarray_dist: one component per factor with the fields of the standalone distribution, a field with the same value in every component once indefault_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
xand the global observable asmean, 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 asLumi), and Gaussians are written with the observable asx(the Gaussian is symmetric in the two), both as array components and as standalonegaussian_dist. Re-imported workspaces therefore have the usual orientation, and the data pairing of section 4 refers to the observable. Gaussians with a number asmeankeep the parameter asx, there is nothing to swap with.3. HistFactory import
A modifier may name an
array_distinconstraints. The importer picks the component that depends on the parameter (directly or through a function, e.g. theproductof 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
array_distper 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.staterrorandshapesysin bin order.rf515_hfJSON.json), aRooLognormalwith the transformed parametrization. They are written on their own as before.aux_distributions(observed values as constant parameters, the global observables), or withconfig().exportConstraintData = trueindistributions, 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.config().exportArrayDist = false: noarray_distis written at all. HistFactory constraints are named individually as before, a tagged product is a plainproduct_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
constraintsnames one array per type instead of one distribution per parameter, the arrays stand at the end ofdistributions, and the likelihood lists them inaux_distributions. Consumers of these documents needarray_dist(HS3 0.3).config().exportArrayDist = falserestores the previous form of the constraints.nom_gamma_stat_channel1_bin_0_constraint,nom_alpha_syst1_constraint) instead of..._constraint/...Constraintafter the parameter, Gaussian constraints of normsys/histosys/normfactor parameters have the global observable asx(in every export mode), and the workspace holds one top-level taggedRooProdPdfper array. The likelihood is unchanged (tested).array_dist, import as before.Checklist:
Tests run (Linux, Ubuntu 26.04, GCC 15.2, RelWithDebInfo,
-Droofit=ON -Dtesting=ON):testRooFitHS3115 → 125 tests (10 new),testJSONInterface1,testHistFactory69 → 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, andHS3ArrayDistRoundTrip_ROOT6_16_Combined_ModelintestHistFactory(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