Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
HS3Writer.cxx
Go to the documentation of this file.
1/*
2 * Project: RooFit
3 * Authors:
4 * Jonas Rembser, CERN, Nov 2026
5 *
6 * Copyright (c) 2026, CERN
7 *
8 * Redistribution and use in source and binary forms,
9 * with or without modification, are permitted according to the terms
10 * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
11 */
12
13// Writes the typed representation of HS3 documents. The keys are the same as in HS3Parser.cxx, which the round-trip
14// tests in testHS3Document.cxx check.
15
16#include "HS3Document.h"
17#include "JSONIOUtils.h"
18
20
21#include <algorithm>
22#include <cmath>
23#include <limits>
24
26
27namespace RooFit {
28namespace HS3 {
29namespace {
30
31// Infinite bounds are written as null.
32void writeBound(JSONNode &node, double value)
33{
34 if (std::isinf(value)) {
35 node.set_null();
36 } else {
37 node << value;
38 }
39}
40
41void writeStrings(JSONNode &node, std::vector<std::string> const &values)
42{
43 node.fill_seq(values);
44}
45
46template <class Tag>
47void writeRefs(JSONNode &node, std::vector<Ref<Tag>> const &refs)
48{
49 node.set_seq();
50 for (auto const &ref : refs) {
51 node.append_child() << ref.name;
52 }
53}
54
55JSONNode &appendNamed(JSONNode &array, std::string const &name)
56{
58 child["name"] << name;
59 return child;
60}
61
62void writeBinning(JSONNode &node, Binning const &binning)
63{
64 if (auto const *edges = std::get_if<EdgesBinning>(&binning)) {
65 node["edges"].fill_seq(edges->edges);
66 } else {
67 node["nbins"] << std::get<RegularBinning>(binning).nbins;
68 }
69}
70
71void writeAxis(JSONNode &node, BinnedAxis const &axis)
72{
73 node.set_map();
74 node["name"] << axis.name;
75 // Binned axes always carry their range, also if it is redundant with the "edges" of an irregular binning.
76 node["min"] << axis.min;
77 node["max"] << axis.max;
78 writeBinning(node, axis.binning);
79}
80
81void writeAxis(JSONNode &node, ContinuousAxis const &axis)
82{
83 node.set_map();
84 node["name"] << axis.name;
85 if (axis.min) {
86 writeBound(node["min"], *axis.min);
87 }
88 if (axis.max) {
89 writeBound(node["max"], *axis.max);
90 }
91}
92
93void writeAxis(JSONNode &node, CategoricalAxis const &axis)
94{
95 node.set_map();
96 node["name"] << axis.name;
97 writeStrings(node["categories"], axis.categories);
98}
99
100// An infinite or missing bound of a domain axis is omitted, which means that the range is unbounded.
101void writeAxis(JSONNode &node, DomainAxis const &axis)
102{
103 node.set_map();
104 node["name"] << axis.name;
105 if (axis.fixedValue) {
106 node[fixedValueKey] << *axis.fixedValue;
107 return;
108 }
109 if (axis.min && !std::isinf(*axis.min)) {
110 node["min"] << *axis.min;
111 }
112 if (axis.max && !std::isinf(*axis.max)) {
113 node["max"] << *axis.max;
114 }
115 if (axis.binning) {
116 writeBinning(node, *axis.binning);
117 }
118}
119
120template <class... Axes>
121void writeAxes(JSONNode &node, std::vector<std::variant<Axes...>> const &axes)
122{
123 node.set_seq();
124 for (auto const &axis : axes) {
125 std::visit([&](auto const &a) { writeAxis(node.append_child(), a); }, axis);
126 }
127}
128
129template <class Axis>
130void writeAxes(JSONNode &node, std::vector<Axis> const &axes)
131{
132 node.set_seq();
133 for (auto const &axis : axes) {
134 writeAxis(node.append_child(), axis);
135 }
136}
137
138// The parameter of a modifier with a single parameter, and the name of its constraint distribution. A constraint type
139// is written in the form of older documents.
140void writeParameter(JSONNode &mod, ParameterRef const &parameter, Constraint const &constraint)
141{
142 mod["parameters"].set_seq().append_child() << parameter.name;
143 std::visit(
144 overloaded{[](Unconstrained) {}, [&](ConstraintType type) { mod["constraint"] << toString(type); },
145 [&](ConstraintPdf const &pdf) { mod["constraints"].set_seq().append_child() << pdf.pdf.name; }},
146 constraint);
147}
148
149void writeGammaConstraint(JSONNode &node, GammaConstraint const &constraint, const char *typeKey)
150{
151 std::visit(overloaded{[](Unconstrained) {}, [&](ConstraintType type) { node[typeKey] << toString(type); },
152 [&](ConstantGammas) { node[typeKey] << "Const"; },
153 [&](GammaConstraintPdfs const &pdfs) {
154 JSONNode &names = node["constraints"].set_seq();
155 for (auto const &pdf : pdfs.pdfs) {
156 if (pdf) {
157 names.append_child() << pdf->name;
158 } else {
159 names.append_child().set_null();
160 }
161 }
162 }},
163 constraint);
164}
165
166JSONNode &appendModifier(JSONNode &modifiers, std::string const &name, const char *type)
167{
168 JSONNode &mod = modifiers.append_child().set_map();
169 mod["name"] << name;
170 mod["type"] << type;
171 return mod;
172}
173
174void writeModifier(JSONNode &modifiers, Modifier const &modifier)
175{
176 std::visit(overloaded{
177 [&](NormFactor const &m) {
178 JSONNode &mod = appendModifier(modifiers, m.name, "normfactor");
179 writeParameter(mod, m.parameter, m.constraint);
180 },
181 [&](NormSys const &m) {
182 JSONNode &mod = appendModifier(modifiers, m.name, "normsys");
183 writeParameter(mod, m.parameter, m.constraint);
184 if (m.interpolation) {
185 writeInterpolation(mod["interpolation"], *m.interpolation);
186 }
187 JSONNode &data = mod["data"].set_map();
188 data["lo"] << m.lo;
189 data["hi"] << m.hi;
190 },
191 [&](HistoSys const &m) {
192 JSONNode &mod = appendModifier(modifiers, m.name, "histosys");
193 writeParameter(mod, m.parameter, m.constraint);
194 if (m.interpolation) {
195 writeInterpolation(mod["interpolation"], *m.interpolation);
196 }
197 JSONNode &data = mod["data"].set_map();
198 writeContents(data["lo"].set_map()["contents"], m.lo.contents);
199 writeContents(data["hi"].set_map()["contents"], m.hi.contents);
200 },
201 [&](ShapeSys const &m) {
202 JSONNode &mod = appendModifier(modifiers, m.name, "shapefactor");
203 writeRefs(mod["parameters"], m.parameters);
204 writeGammaConstraint(mod, m.constraint, "constraint");
205 // The values are only used by the constraint types of older documents.
206 if (!m.vals.empty()) {
207 mod["data"].set_map()["vals"].fill_seq(m.vals);
208 }
209 },
210 [&](StatError const &m) {
211 JSONNode &mod = appendModifier(modifiers, m.name, "staterror");
212 writeRefs(mod["parameters"], m.parameters);
213 writeGammaConstraint(mod, m.constraint, "constraint");
214 },
215 [&](CustomModifier const &m) { appendModifier(modifiers, m.name, "custom"); },
216 },
217 modifier);
218}
219
220void writeParameterPoint(JSONNode &points, ParameterPoint const &point)
221{
222 JSONNode &parameters = appendNamed(points, point.name)["parameters"].set_seq();
223 for (auto const &p : point.parameters) {
224 JSONNode &node = appendNamed(parameters, p.name);
225 if (p.value) {
226 std::visit([&](auto const &value) { node["value"] << value; }, *p.value);
227 }
228 if (p.isConst) {
229 node["const"] << true;
230 }
231 }
232}
233
234void writeData(JSONNode &dataArray, Data const &data)
235{
236 JSONNode &node = appendNamed(dataArray, data.name);
237 std::visit(overloaded{
238 [&](BinnedData const &d) {
239 node["type"] << "binned";
240 writeAxes(node["axes"], d.axes);
241 writeContents(node["contents"], d.contents);
242 if (d.uncertainty) {
243 JSONNode &uncertainty = node["uncertainty"].set_map();
244 uncertainty["type"] << "gaussian_uncertainty";
245 uncertainty["sigma"].fill_seq(d.uncertainty->sigma);
246 }
247 },
248 [&](UnbinnedData const &d) {
249 node["type"] << "unbinned";
250 writeAxes(node["axes"], d.axes);
251 JSONNode &entries = node["entries"].set_seq();
252 const std::size_t nAxes = d.axes.size();
253 for (std::size_t i = 0; i < d.numEntries(); ++i) {
254 JSONNode &row = entries.append_child().set_seq();
255 for (std::size_t j = 0; j < nAxes; ++j) {
256 const double value = d.entries[i * nAxes + j];
257 if (auto const *category = std::get_if<CategoricalAxis>(&d.axes[j])) {
258 row.append_child() << category->categories[static_cast<std::size_t>(value)];
259 } else {
260 row.append_child() << value;
261 }
262 }
263 }
264 if (d.weights) {
265 node["weights"].fill_seq(*d.weights);
266 }
267 },
268 [&](PointData const &d) {
269 node["type"] << "point";
270 node["value"] << d.value;
271 if (d.uncertainty) {
272 node["uncertainty"] << *d.uncertainty;
273 }
274 if (!d.axes.empty()) {
275 writeAxes(node["axes"], d.axes);
276 }
277 },
278 },
279 data.body);
280}
281
282void writeLikelihood(JSONNode &likelihoods, Likelihood const &likelihood)
283{
284 JSONNode &node = appendNamed(likelihoods, likelihood.name);
285 JSONNode &distributions = node["distributions"].set_seq();
286 JSONNode &data = node["data"].set_seq();
287 for (auto const &term : likelihood.terms) {
288 distributions.append_child() << term.distribution.name;
289 std::visit(overloaded{[&](DataRef const &ref) { data.append_child() << ref.name; },
290 [&](double value) { writeNumber(data.append_child(), value); }},
291 term.data);
292 }
293 if (!likelihood.auxDistributions.empty()) {
294 writeRefs(node["aux_distributions"], likelihood.auxDistributions);
295 }
296}
297
298void writeAnalysis(JSONNode &analyses, Analysis const &analysis)
299{
300 JSONNode &node = appendNamed(analyses, analysis.name);
301 writeRefs(node["domains"], analysis.domains);
302 node["likelihood"] << analysis.likelihood.name;
303 if (!analysis.parametersOfInterest.empty()) {
304 writeRefs(node["parameters_of_interest"], analysis.parametersOfInterest);
305 }
306 if (analysis.init) {
307 node["init"] << analysis.init->name;
308 }
309 if (analysis.prior) {
310 node["prior"] << analysis.prior->name;
311 }
312}
313
314void writeRooFitInternal(JSONNode &root, RooFitInternal const &internal)
315{
316 auto rootInternal = [&]() -> JSONNode & { return root.get("misc", "ROOT_internal"); };
317 if (!internal.attributes.empty()) {
318 JSONNode &attributes = rootInternal()["attributes"].set_map();
319 for (auto const &item : internal.attributes) {
320 JSONNode &node = attributes[item.first].set_map();
321 if (!item.second.dict.empty()) {
322 JSONNode &dict = node["dict"].set_map();
323 for (auto const &entry : item.second.dict) {
324 dict[entry.first] << entry.second;
325 }
326 }
327 if (!item.second.tags.empty()) {
328 writeStrings(node["tags"], item.second.tags);
329 }
330 }
331 }
332 if (!internal.combinedDistributions.empty()) {
333 JSONNode &combined = rootInternal()["combined_distributions"].set_map();
334 for (auto const &item : internal.combinedDistributions) {
335 JSONNode &node = combined[item.first].set_map();
336 node["index_cat"] << item.second.indexCat;
337 writeStrings(node["labels"], item.second.labels);
338 node["indices"].fill_seq(item.second.indices);
339 writeRefs(node["distributions"], item.second.distributions);
340 }
341 }
342 if (!internal.combinedDatasets.empty()) {
343 JSONNode &combined = rootInternal()["combined_datasets"].set_map();
344 for (auto const &item : internal.combinedDatasets) {
345 JSONNode &node = combined[item.first].set_map();
346 node["index_cat"] << item.second.indexCat;
347 writeStrings(node["labels"], item.second.labels);
348 node["indices"].fill_seq(item.second.indices);
349 }
350 }
351 if (!internal.modelConfigs.empty()) {
352 JSONNode &modelConfigs = rootInternal()["ModelConfigs"].set_map();
353 for (auto const &item : internal.modelConfigs) {
354 JSONNode &node = modelConfigs[item.first].set_map();
355 if (item.second.mcName) {
356 node["mcName"] << *item.second.mcName;
357 }
358 if (item.second.pdfName) {
359 node["pdfName"] << *item.second.pdfName;
360 }
361 if (item.second.combinedDataName) {
362 node["combined_data_name"] << *item.second.combinedDataName;
363 }
364 }
365 }
366 if (!internal.channelConstraints.empty()) {
367 JSONNode &constraints = rootInternal()["histfactory_constraints"].set_map();
368 for (auto const &item : internal.channelConstraints) {
369 writeRefs(constraints[item.first], item.second);
370 }
371 }
372 if (!internal.stepWidths.empty()) {
373 JSONNode &stepWidths = root.get("misc", "minimization")["parameter_stepwidths"].set_seq();
374 for (auto const &stepWidth : internal.stepWidths) {
375 appendNamed(stepWidths, stepWidth.name)["step_width"] << stepWidth.width;
376 }
377 }
378}
379
380} // namespace
381
382void writeNumber(JSONNode &node, double value)
383{
384 // To make sure there are no unnecessary floating points in the JSON
385 if (static_cast<int>(value) == value) {
386 node << static_cast<int>(value);
387 } else {
388 node << value;
389 }
390}
391
392void writeContents(JSONNode &node, std::vector<double> const &values)
393{
394 node.set_seq();
395 for (double value : values) {
397 }
398}
399
400void writeInterpolation(JSONNode &node, Interpolation const &interpolation)
401{
402 node.set_map();
403 node["type"] << toString(interpolation.type);
404 node["in"] << toString(interpolation.in);
405 if (interpolation.out) {
406 node["out"] << toString(*interpolation.out);
407 } else {
408 node["out"].set_null();
409 }
410}
411
412namespace {
413
414void writeValue(JSONNode &node, NameOrNumber const &value)
415{
416 std::visit([&](auto const &v) { node << v; }, value);
417}
418
419template <class T>
420void writeValue(JSONNode &node, T const &value)
421{
422 node << value;
423}
424
425template <class T>
426void writeList(JSONNode &node, std::vector<T> const &values)
427{
428 node.set_seq();
429 for (auto const &value : values) {
430 writeValue(node.append_child(), value);
431 }
432}
433
434template <class Function_t>
435void writeInterpolated(JSONNode &node, Function_t const &function, char const *type)
436{
437 node["type"] << type;
438 auto const &interpolations = function.interpolations;
439 const bool allEqual = std::all_of(interpolations.begin(), interpolations.end(),
440 [&](Interpolation const &i) { return i == interpolations.front(); });
441 JSONNode &interpolationsNode = node["interpolations"].set_seq();
442 for (std::size_t i = 0; i < (allEqual ? std::min<std::size_t>(interpolations.size(), 1) : interpolations.size());
443 ++i) {
444 writeInterpolation(interpolationsNode.append_child(), interpolations[i]);
445 }
446 writeList(node["vars"], function.parameters);
447 node["nom"] << function.nominal;
448 writeList(node["high"], function.high);
449 writeList(node["low"], function.low);
450}
451
452} // namespace
453
455{
456 writeInterpolated(node, function, "interpolation");
457 node["positiveDefinite"] << function.positiveDefinite;
458}
459
461{
462 writeInterpolated(node, function, "interpolation0d");
463}
464
466{
467 node.set_map();
468 node["type"] << "histfactory_dist";
469 if (!std::holds_alternative<Unconstrained>(dist.statError.constraint)) {
470 writeGammaConstraint(node["staterror"].set_map(), dist.statError.constraint, "constraint_type");
471 }
472 if (dist.defaultInterpolation) {
473 writeInterpolation(node["default_interpolation"], *dist.defaultInterpolation);
474 }
475 writeAxes(node["axes"], dist.axes);
476 JSONNode &samples = node["samples"].set_seq();
477 for (auto const &sample : dist.samples) {
478 JSONNode &s = appendNamed(samples, sample.name);
479 JSONNode &modifiers = s["modifiers"].set_seq();
480 for (auto const &modifier : sample.modifiers) {
481 writeModifier(modifiers, modifier);
482 }
483 JSONNode &data = s["data"].set_map();
484 writeContents(data["contents"], sample.contents);
485 if (sample.errors) {
486 writeContents(data["errors"], *sample.errors);
487 }
488 }
489}
490
491void writeDocument(JSONNode &root, HS3Document const &doc)
492{
493 root.set_map();
494 JSONNode &metadata = root["metadata"].set_map();
495 metadata["hs3_version"] << doc.metadata.hs3Version;
496 for (auto const &package : doc.metadata.packages) {
497 appendNamed(metadata["packages"], package.name)["version"] << package.version;
498 }
499
500 for (auto const &point : doc.parameterPoints) {
501 writeParameterPoint(root["parameter_points"], point);
502 }
503 for (auto const &domain : doc.domains) {
504 JSONNode &node = appendNamed(root["domains"], domain.name);
505 node["type"] << "product_domain";
506 writeAxes(node["axes"], domain.axes);
507 }
508 for (auto const &data : doc.data) {
509 writeData(root["data"], data);
510 }
511 for (auto const &likelihood : doc.likelihoods) {
512 writeLikelihood(root["likelihoods"], likelihood);
513 }
514 for (auto const &analysis : doc.analyses) {
515 writeAnalysis(root["analyses"], analysis);
516 }
517 writeRooFitInternal(root, doc.rooFit);
518}
519
520} // namespace HS3
521} // namespace RooFit
constexpr const char * fixedValueKey
Definition JSONIOUtils.h:17
#define d(i)
Definition RSha256.hxx:102
#define a(i)
Definition RSha256.hxx:99
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t child
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t modifier
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t points
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t type
char name[80]
Definition TGX11.cxx:142
JSONNode & get(std::string const &key)
void fill_seq(Collection const &coll)
An HS3 document. It is move-only, because generic objects refer to the JSON tree that the document wa...
std::vector< HS3::Analysis > analyses
std::vector< HS3::Data > data
HS3::Metadata metadata
std::vector< HS3::Likelihood > likelihoods
std::vector< HS3::ParameterPoint > parameterPoints
HS3::RooFitInternal rooFit
std::vector< HS3::Domain > domains
Types that represent an HS3 document.
std::variant< std::string, double > NameOrNumber
A name, or a number for a constant.
void writeNumber(Detail::JSONNode &node, double value)
Writes a number, as an integer if it has no fractional part.
void writeInterpolatedFunction(Detail::JSONNode &node, InterpolatedFunction const &function)
Writes the functions "interpolation" and "interpolation0d", with a single interpolation if they are a...
std::variant< NormFactor, NormSys, HistoSys, ShapeSys, StatError, CustomModifier > Modifier
std::variant< RegularBinning, EdgesBinning > Binning
Definition HS3Document.h:94
ConstraintType
The constraint types that older documents named, for which RooFit creates the constraint terms.
Ref< struct DataTag > DataRef
Definition HS3Document.h:68
char const * toString(InterpolationType type)
void writeDocument(Detail::JSONNode &root, HS3Document const &doc)
Writes everything but the distributions and functions, which are written one by one while they are ex...
void writeContents(Detail::JSONNode &node, std::vector< double > const &values)
Writes an array of numbers, each as an integer if it has no fractional part.
void writeInterpolatedNumber(Detail::JSONNode &node, InterpolatedNumber const &function)
void writeHistFactoryDist(Detail::JSONNode &node, HistFactoryDist const &dist)
Writes a HistFactory distribution, without its name.
Ref< struct ParameterTag > ParameterRef
Definition HS3Document.h:72
void writeInterpolation(Detail::JSONNode &node, Interpolation const &interpolation)
std::variant< Unconstrained, ConstraintType, ConstantGammas, GammaConstraintPdfs > GammaConstraint
overloaded(Ts...) -> overloaded< Ts... >
std::variant< Unconstrained, ConstraintType, ConstraintPdf > Constraint
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
Definition CodegenImpl.h:73
The function "interpolation" of RooFit, implemented by PiecewiseInterpolation: a nominal function plu...
The function "interpolation0d" of RooFit, implemented by FlexibleInterpVar: a nominal value plus the ...
How a modifier of a HistFactory distribution interpolates and extrapolates its effect.
InterpolationType type
InterpolationFunction in
std::optional< InterpolationFunction > out
If not set, the function inside the bounds is used outside as well.
std::string hs3Version
As it was written in the document, empty for fragments of a document.
Definition HS3Document.h:80
std::vector< Package > packages
Definition HS3Document.h:82
TMarker m
Definition textangle.C:8