Repository navigation
[RF][HS3] Combined data sets and combined/conditional distributions (HS3 #119; depends on #24001) - #24005
Draft
cburgard wants to merge 22 commits into
Draft
[RF][HS3] Combined data sets and combined/conditional distributions (HS3 #119; depends on #24001)#24005cburgard wants to merge 22 commits into
cburgard wants to merge 22 commits into
Conversation
This was referenced Nov 5, 2026
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-combined
branch
from
November 6, 2026 06:16
c6531c5 to
b1887f7
Compare
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
…ed_dist and read it back A RooSimultaneous used to be exported only as ROOT-internal information (misc.ROOT_internal.combined_distributions). HS3 PR root-project#119 defines the "combined_dist": its components are selected by a key with one label per column, and the columns are categorical axes of the domains. Export: a RooSimultaneous becomes a "combined_dist". The columns are its index category or, for a RooSuperCategory index, the input categories, sorted by name so that a distribution and a data set agree on the order without looking at each other. The columns are declared as categorical axes of the default domain. The components are written in the order of the columns and their states, which does not depend on the order of the inputs of a RooSuperCategory, so the document is stable under round trips. States without a pdf are not written. Category indices are not part of HS3: after a round trip, the index is the position in the domain's "categories". Import: a "combined_dist" becomes a RooSimultaneous over the column category, or over a RooSuperCategory of the columns in the order of the document. The columns must be declared by categorical axes of a domain; keys are validated against them. The ROOT-internal form of files written by earlier ROOT versions is still read. Tests: CombinedDist (structure, round trip, legacy form), CombinedDistSeveralColumns, CombinedDistImportErrors. Assisted-by: claude-code:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… HS3 form and read them back A RooDataSet with a category observable used to be written as its components plus ROOT-internal information on how to combine them (misc.ROOT_internal.combined_datasets), and the likelihood of a ModelConfig listed the components of the RooSimultaneous paired with the components of the data. HS3 PR root-project#119 defines the "combined" data set: sub-data sets selected by a key, with the columns declared as categorical axes of the domains. Export: a data set whose observables include RooCategories becomes a "combined" entry with the categories as columns, sorted by name, and one sub-data set per key, named after the data set and the labels. The categories are declared in the default domain. A key is written also without entries, since RooFit treats a channel without entries as present and empty. If the workspace has a RooSimultaneous with the same columns, each sub-data set carries only the observables of its component, as before, and keys without entries for which it has no component are left out; otherwise all observables are written. The likelihood of a ModelConfig now pairs the RooSimultaneous with the combined data set, and an analysis is written for every combined data set with the same columns. A RooSimultaneous without matching data gets an analysis named after the ModelConfig, like any other pdf, instead of one named after the pdf. Import: a "combined" data set becomes a RooDataSet with one category column per entry of "columns", filled from the components, which are then not imported on their own. One code path for one and for several columns. The ModelConfig observables then include the index category. The ROOT-internal form of files written by earlier ROOT versions is still read; its likelihoods list the components, so it is handled after the analyses as before. An empty weighted data set over a variable without bins was detected as binned data with zero bins, which made the document of a present but empty sub-data set unstable under round trips. The detection now requires a binning. Tests: CombinedData (structure, round trip, data without pdf, legacy form), CombinedDataSeveralColumns, CombinedDataImportErrors. Assisted-by: claude-code:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…istributions and data sets HS3 root-project#119 treats a key of a combined distribution without a sub-data set in the paired combined data set as an unobserved component, which is marginalised, and a present but empty sub-data set as observed with zero events. RooFit has no notion of an unobserved component: a combined RooDataSet without entries for a channel is a present and empty channel, and both evaluation backends then add the expected number of events of an extended component to the likelihood (see ROOT issue root-project#24004). When a likelihood pairs a combined_dist with a combined data set, the import now checks the keys. A missing sub-data set of an extended component is an error, since the fit would differ from what the document means. A missing sub-data set of a non-extended component changes nothing and is a warning, as is a sub-data set for a key without a component, which RooFit ignores. Differing columns are an error. Test: CombinedDataPairing. Assisted-by: claude-code:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…onal_dist HS3 root-project#119 defines the "category_conditional_dist": one of several sub-distributions, selected by the value of categorical parameters and conditional on it. RooFit has this as the "switch mode" of RooSimultaneous: with an index category that is not an observable, it evaluates to the selected component, and its expected number of events is the one of that component. Import: a category_conditional_dist becomes a RooSimultaneous over its categorical parameters, which must be declared by categorical axes of a domain like the columns of a combined_dist. The RooSimultaneous carries the attribute "hs3_category_conditional_dist": RooFit decides between joint and conditional by the normalization set, i.e. by context, and the attribute makes the export context-free. A RooSimultaneous built in RooFit is a combined_dist unless the user sets the attribute. Export: a tagged RooSimultaneous is written as category_conditional_dist with "categorical_parameter" instead of "columns". Its analysis is paired with data like any other pdf. The value of a RooCategory in a snapshot is written to the parameter point as the label, and the category to the default domain; the columns of combined distributions are not parameters and are left out. Test: CategoryConditionalDist. Assisted-by: claude-code:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…HistFactory reference workspace The reference workspace of ROOT 6.16 is the standard case of a RooSimultaneous with combined data sets. The test checks that it is written as a combined_dist with combined data sets, read back into the same objects with the same likelihood values, and that the document is stable. Stability holds from the third export on: the 6.16 workspace has binWidth constants that the imported model doesn't use, and the HistFactory importer sets the default parameter errors, which the 6.16 workspace lacks. Both are unrelated to the combined objects. Assisted-by: claude-code:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… their names without misc The names of a RooSimultaneous and of a combined data set used to live in misc.ROOT_internal, so a document without "misc" lost them. Now they are part of the combined_dist and combined entries. The test exports a workspace with a RooSimultaneous (not named "simPdf", the historical default), removes "misc", imports the document and checks that the pdf, its components, the index category and the data set keep their names, that the ModelConfig finds its pdf through the likelihood, and that the re-export describes the same objects. Assisted-by: claude-code:claude-fable-5-1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hose components share an observable HS3 PR root-project#119 requires that the components of a combined distribution share no variate, and that the sub-data sets of a combined data set have distinct axis names: a quantity measured in several components is a separate random variable in each of them, e.g. "mass_SR" and "mass_CR", since it follows a different distribution there. A RooSimultaneous whose components use the same observable can therefore not be written together with its data set. Exporting such a data set, split by the components of the RooSimultaneous, is now an error that asks to give the observable a separate name in each component. A document whose sub-data sets share an axis is still read, with a warning. The test of a data set with several columns now uses a separate observable per component. Assisted-by: claude-code:claude-opus-5-5
… as categorical axes without a combined distribution A combined data set is paired with a combined distribution, and HS3 PR root-project#119 requires its sub-data sets to have distinct axis names. A data set with a category observable but without a matching RooSimultaneous was split by the category anyway, so all sub-data sets carried the same observables. Without a matching RooSimultaneous, the category is an ordinary observable: such a data set is now written as a single data set with a categorical axis (HS3 PR root-project#108), binned or unbinned, which RooFit reads back since the axis handling PR. The categories are declared by a categorical axis of the default domain, and the entries of an unbinned categorical axis are the labels. Assisted-by: claude-code:claude-opus-5-5
…es of combined data sets per key HS3 PR root-project#119 requires that the components of a combined distribution share no variate. Workspaces whose RooSimultaneous components share an observable (common in older workspaces and in CMS Combine) can therefore not be exported. The new option RooJSONFactoryWSTool::config() .renameCombinedObservables (off by default) renames the observables of every combined data set per key on export: "obs_<key>" (the labels of the key joined by "_"), or "obs_<key>_<observable>" if a component has several observables. The components of the matching RooSimultaneous are written with the renamed observables: they are rebuilt with RooCustomizer, which clones every node that depends on a renamed observable with the suffix "_<key>", so nodes shared by several components get one copy per component. Observables that already have their new name are left alone, so that a second export of an imported document gives the same document. The likelihood is unchanged. Assisted-by: claude-code:claude-opus-5-5
…_dist HS3 PR root-project#119 now states that a combined_dist must not have the component "extended": whether it is extended follows from its components. The importer ignored such a component; it now raises an error, whatever its value. RooFit never writes it. Assisted-by: claude-code:claude-opus-5-5
cburgard
force-pushed
the
hs3-combined
branch
2 times, most recently
from
November 6, 2026 10:25
a594b72 to
9dcd02b
Compare
1 of 2 tasks
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 10 commits. It stays in draft until HS3 PR #119 is merged, 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).This Pull request:
Combined distributions and data sets in RooFit's HS3 import/export, following HS3 PR #119 ("combined data sets and combined/conditional distributions"):
RooSimultaneousis written as acombined_distand a data set with category observables as acombineddata set, instead of the ROOT-internal description inmisc.ROOT_internalthat only RooFit could read;category_conditional_dist(a distribution selected by the value of a categorical parameter) is read and written;ModelConfigpairs the combined objects, and the import checks that pairing.Files written by earlier ROOT versions, with
misc.ROOT_internal.combined_distributions/combined_datasets, are still read.Changes or fixes:
1.
RooSimultaneous↔combined_distRooSimultaneousis acombined_dist. Itscolumnsare the index category or, for aRooSuperCategoryindex, the input categories, sorted by name. The columns are declared as categorical axes ofdefault_domain(with the states in their order).componentshas one entry per state with a pdf, with thekeyas one label per column. ARooSuperCategoryindex used to be an export error.combined_distis aRooSimultaneousover the column category, or over aRooSuperCategoryof the columns in the order of the document. The columns must be declared by categorical axes of a domain; keys are validated against them (length, labels, duplicates). The ROOT-internal form is still read.The components are written in the order of the columns and their states, which does not depend on the order of the inputs of a
RooSuperCategory, so the document is stable under round trips. Category indices are not part of HS3: after a round trip, the index of a state is its position in the domain'scategories.Tests:
CombinedDist(structure, round trip, legacy form),CombinedDistSeveralColumns,CombinedDistImportErrors.CombinedObjectsKeepNamesWithoutMiscchecks that a document withoutmiscimports with the same names for the pdf, its index category and the data set (they used to be encoded inmisc.ROOT_internal), and that theModelConfigfinds its pdf through the likelihood.2. Combined data sets ↔
combinedRooCategorys is acombinedentry with the categories ascolumns(sorted by name) and one sub-data set per key, named<data>_<label>[_<label>…], written like any other data set. A key is written also without entries: RooFit treats a channel without entries as present and empty. Several category observables used to be an export error.RooSimultaneouswith the same columns, each sub-data set carries only the observables of its component (as before), and keys without entries for which it has no component are left out. Otherwise all observables are written.ModelConfigpairs theRooSimultaneouswith the combined data set ("distributions": ["simPdf"], "data": ["obsData"]) instead of listing the components of both. An analysis is written for every combined data set with the same columns.combineddata set is aRooDataSetwith one category column per entry ofcolumns, filled from the components, which are then not imported on their own. One code path for one and for several columns. The components of combined data sets are assembled before the analyses are read, so theModelConfigobservables include the index category, as in the original HistFactory workspace. The ROOT-internal form is still read, and handled after the analyses as before, because its likelihoods list the components.Tests:
CombinedData(structure, round trip, data without pdf, legacy form),CombinedDataSeveralColumns,CombinedDataImportErrors, andHS3RoundTripOfReferenceWorkspaceintestHistFactoryfor the ROOT 6.16 reference workspace (same objects, the same likelihood up to a constant, stable document). The existingSimultaneousFitandHS3ClosureLooptests now exercise the new form.3. Pairing checks
HS3 #119 treats a key of a
combined_distwithout a sub-data set in the pairedcombineddata set as an unobserved component, which is marginalised, and a present but empty sub-data set as observed with zero events. RooFit has no notion of an unobserved component: a combinedRooDataSetwithout entries for a channel is a present and empty channel, and both evaluation backends then add the expected number of events of an extended component to the likelihood (#24004).When a likelihood pairs a
combined_distwith acombineddata set, the import now checks the keys:Test:
CombinedDataPairing.3b.
extendedon acombined_distHS3 #119 states that a
combined_distmust not have the componentextended: whether it is extended follows from its components. The importer rejects it with an error, whatever its value (test:CombinedDistImportErrors). RooFit never writes it.4.
category_conditional_distRooFit has this distribution as the "switch mode" of
RooSimultaneous: with an index category that is not an observable, it evaluates to the component selected by the current state, and its expected number of events is the one of that component.category_conditional_distis aRooSimultaneousover its categorical parameters (declared by categorical axes of a domain), tagged with the attributehs3_category_conditional_dist. RooFit decides between joint and conditional by the normalization set, i.e. by context; the tag makes the export context-free.RooSimultaneouswith the tag is written ascategory_conditional_dist, withcategorical_parameterinstead ofcolumns, and its analysis is paired with data like any other pdf. ARooSimultaneousbuilt in RooFit is acombined_distunless the user sets the attribute.RooCategoryin a snapshot is written to the parameter point as its label (categorical parameters). The columns of combined distributions are not parameters and are left out.Test:
CategoryConditionalDist.5. Fix: empty weighted data sets
An empty weighted data set over a variable without bins was detected as binned data with zero bins. Unbinned data sets imported from HS3 leave their axes without binning, so a present but empty sub-data set was not stable under round trips. The detection now requires a binning.
6. Components must not share observables
HS3 #119 requires that the components of a combined distribution share no variate, and that the sub-data sets of a combined data set have distinct axis names: a quantity measured in several components is a separate random variable in each of them (e.g.
mass_SRandmass_CR), since it follows a different distribution there. Exporting a data set that is split by aRooSimultaneouswhose components share an observable is therefore an error, which asks to give the observable a separate name per component. A document whose sub-data sets share an axis is still read, with a warning. Tests:CombinedComponentsMustNotShareObservables;CombinedDataSeveralColumnsnow uses one observable per component.7. Data sets with categories but without a combined distribution
A combined data set only makes sense paired with a combined distribution. Without a matching
RooSimultaneous, the category of a data set is an ordinary observable, so such a data set is now written as a single data set with a categorical axis (the form of HS3 #108, read since #24001), binned or unbinned. Before, it was split by the category into sub-data sets that all carried the same observables, which #119 forbids. Test: the data-only part ofCombinedData.8. Option: rename the observables of combined data sets
Workspaces whose
RooSimultaneouscomponents share an observable (common in older workspaces and in CMS Combine) can't be exported because of 6. The new optionRooJSONFactoryWSTool::config().renameCombinedObservables(off by default) renames the observables of every combined data set per key on export:obs_<key>(the labels of the key joined by_), orobs_<key>_<observable>if a component has several observables. The components of the matchingRooSimultaneousare written with the renamed observables: they are rebuilt withRooCustomizer, which clones every node depending on a renamed observable with the suffix_<key>, so nodes shared by several components get one copy per component (e.g.shift(mass)→shift_SR(obs_SR),shift_CR(obs_CR)). Observables that already have their new name are left alone, so a second export of an imported document gives the same document. Test:RenameCombinedObservables(the likelihood is the same after the round trip).Not covered, deferred
categoricalfunction of Master tmva dataloader vi #119 andRooMultiPdf/RooMultiReal(both select by category index = list position,RooMultiPdfis never extended and adds a penalty term): #24006.RooSuperCategorywhose inputs are notRooCategorys (nested super categories): explicit error.Behaviour changes users may notice
misc.ROOT_internal.combined_distributionsandcombined_datasetsare no longer written; the standard objects replace them. Files with the old form are still read.ModelConfigwith aRooSimultaneouslists one pair (theRooSimultaneousand the combined data set) instead of the components. Analyses are named after theirModelConfig(<ModelConfig>_<data>), like all analyses since [RF][HS3] Bring the HS3 import and export closer to the HS3 standard #23996.ModelConfigobservables include the index category.categoriesafter a round trip.combinedentry indata(they used to be written before all other data sets).RooSimultaneouswhose components share an observable can no longer be exported together with its data set, unlessrenameCombinedObservablesis enabled (see 6 and 8).RooSimultaneousis written as one data set with a categorical axis, not as sub-data sets (see 7).Checklist:
Tests run (Linux, Ubuntu 26.04, GCC 15.2, RelWithDebInfo,
-Droofit=ON -Dtesting=ON):testRooFitHS3115 → 126 tests (11 new),testJSONInterface1,testHistFactory69 → 70 (1 new), all passing; each commit built and tested on its own. clang-format clean.AI assistance: Claude Code implemented the changes, the tests and this description under review of the author: claude-fable-5-1 the first 6 commits (sections 1–5), claude-opus-5-5 the last 4 (sections 3b, 6–8). The commits carry the corresponding
Assisted-bytrailer.🤖 Generated with Claude Code