Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
HS3Parser.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#include "HS3Schema.h"
14#include "JSONIOUtils.h"
15
16#include <algorithm>
17#include <cctype>
18#include <charconv>
19#include <cmath>
20#include <limits>
21#include <exception>
22#include <sstream>
23
25
26namespace RooFit {
27namespace HS3 {
28
29constexpr auto supportedVersion = "0.3";
30
31namespace {
32
33// Single source of truth for the mapping between the structured HS3 interpolation
34// descriptors and the RooFit integer codes of PiecewiseInterpolation and
35// FlexibleInterpVar. A code of `kUnrepresentable` means the descriptor cannot be
36// expressed by that class: FlexibleInterpVar internally remaps code 4 to code 5,
37// so it has no additive-linear or multiplicative-linear poly6 variant.
38struct InterpolationCodes {
39 Interpolation descriptor;
42};
43
44constexpr int kUnrepresentable = -1;
45
46// clang-format off
47constexpr InterpolationCodes interpolationTable[]{
51 {additivePolynomialLinear, 4, kUnrepresentable},
53 {multiplicativePolynomialLinear, 6, kUnrepresentable},
54};
55// clang-format on
56
57int codeForClass(InterpolationCodes const &row, InterpolationClass interpolationClass)
58{
59 return interpolationClass == InterpolationClass::Piecewise ? row.piecewise : row.flexible;
60}
61
62} // namespace
63
64std::optional<Interpolation> interpolationFromCode(int code, InterpolationClass interpolationClass)
65{
66 // Code 3 was historically an unimplemented alias of code 2 for both classes,
67 // and FlexibleInterpVar treats code 5 identically to its canonical code 4.
68 if (code == 3) {
69 code = 2;
70 }
71 if (interpolationClass == InterpolationClass::Flexible && code == 5) {
72 code = 4;
73 }
74 for (auto const &row : interpolationTable) {
75 const int rowCode = codeForClass(row, interpolationClass);
76 if (rowCode != kUnrepresentable && rowCode == code) {
77 return row.descriptor;
78 }
79 }
80 return std::nullopt;
81}
82
83std::optional<int> codeFromInterpolation(Interpolation const &interpolation, InterpolationClass interpolationClass)
84{
85 for (auto const &row : interpolationTable) {
86 const int rowCode = codeForClass(row, interpolationClass);
87 if (rowCode != kUnrepresentable && row.descriptor == interpolation) {
88 return rowCode;
89 }
90 }
91 return std::nullopt;
92}
93
94bool hasErrors(Diagnostics const &diagnostics)
95{
96 return std::any_of(diagnostics.begin(), diagnostics.end(),
97 [](Diagnostic const &d) { return d.severity == Diagnostic::Severity::Error; });
98}
99
100std::string Cursor::path() const
101{
102 if (!_parent) {
103 return "";
104 }
105 std::string out = _parent->path();
106 if (_key) {
107 if (!out.empty()) {
108 out += ".";
109 }
110 out += _key;
111 return out;
112 }
113 out += "[" + std::to_string(_index) + "]";
114 if (_node.is_map() && _node.has_child("name") && !_node["name"].is_container() && !_node["name"].is_null()) {
115 out += "(" + _node["name"].val() + ")";
116 }
117 return out;
118}
119
120void Cursor::error(std::string const &message) const
121{
122 _diagnostics.push_back({Diagnostic::Severity::Error, path(), message});
123}
124
125void Cursor::warning(std::string const &message) const
126{
127 _diagnostics.push_back({Diagnostic::Severity::Warning, path(), message});
128}
129
130std::size_t Cursor::numErrors() const
131{
132 return std::count_if(_diagnostics.begin(), _diagnostics.end(),
133 [](Diagnostic const &d) { return d.severity == Diagnostic::Severity::Error; });
134}
135
136Reader::Reader(Cursor const &cursor) : _cursor{cursor}, _nErrorsBefore{cursor.numErrors()} {}
137
138bool Reader::has(const char *key)
139{
140 if (!_cursor.node().has_child(key)) {
141 return false;
142 }
143 if (std::find(_seen.begin(), _seen.end(), key) == _seen.end()) {
144 _seen.emplace_back(key);
145 }
146 return true;
147}
148
150{
151 JSONNode const &node = _cursor.node();
152 // There is nothing to report if all components were read, and after an error it would only be noise.
153 if (!node.is_map() || node.num_children() == _seen.size() || _cursor.numErrors() != _nErrorsBefore) {
154 return;
155 }
156 for (JSONNode const &child : node.children()) {
157 const std::string key = child.key();
158 if (std::find(_seen.begin(), _seen.end(), key) == _seen.end()) {
159 _cursor.warning("unknown component \"" + key + "\" is ignored");
160 }
161 }
162}
163
164void readValue(Cursor const &c, std::string &out)
165{
166 if (c.node().is_container() || c.node().is_null()) {
167 c.error("must be a string");
168 return;
169 }
170 out = c.node().val();
171}
172
173void readValue(Cursor const &c, double &out)
174{
175 if (!c.node().is_number()) {
176 c.error("must be a number");
177 return;
178 }
179 out = c.node().val_double();
180}
181
182void readValue(Cursor const &c, int &out)
183{
184 // Read through double, such that an integer that is encoded as a JSON float like 1e6 is accepted.
185 const double value = c.node().is_number() ? c.node().val_double() : 0.5;
186 if (value != std::floor(value) || std::abs(value) > static_cast<double>(std::numeric_limits<int>::max())) {
187 c.error("must be an integer");
188 return;
189 }
190 out = static_cast<int>(value);
191}
192
193void readValue(Cursor const &c, bool &out)
194{
195 if (c.node().is_container() || c.node().is_null()) {
196 c.error("must be a boolean");
197 return;
198 }
199 try {
200 out = c.node().val_bool();
201 } catch (std::exception const &) {
202 c.error("must be a boolean");
203 }
204}
205
206bool parseVersion(std::string const &version, Version &out)
207{
208 std::size_t pos = 0;
209 auto readNumber = [&](int &number) {
210 const std::size_t begin = pos;
211 while (pos < version.size() && std::isdigit(static_cast<unsigned char>(version[pos]))) {
212 ++pos;
213 }
214 // Reject empty and absurdly long numbers, which std::stoi could not represent.
215 if (pos == begin || pos - begin > 9) {
216 return false;
217 }
218 number = std::stoi(version.substr(begin, pos - begin));
219 return true;
220 };
221 Version parsed;
222 if (!readNumber(parsed.major) || pos >= version.size() || version[pos] != '.') {
223 return false;
224 }
225 ++pos;
226 if (!readNumber(parsed.minor)) {
227 return false;
228 }
229 if (pos < version.size() && version[pos] == '.') {
230 ++pos;
231 if (!readNumber(parsed.patch)) {
232 parsed.patch = 0;
233 }
234 }
235 out = parsed;
236 return true;
237}
238
239namespace {
240
241// Read a bound of an axis. Infinite bounds are written as null.
242void readBound(Reader &ar, const char *key, std::optional<double> &out, double valueIfNull)
243{
244 if (!ar.has(key)) {
245 return;
246 }
247 Cursor c{ar.cursor().node()[key], ar.cursor(), key};
248 if (c.node().is_null()) {
249 out = valueIfNull;
250 return;
251 }
252 out.emplace();
253 readValue(c, *out);
254}
255
256constexpr double infinity = std::numeric_limits<double>::infinity();
257
258// Read bin edges, which must be at least two strictly ascending numbers.
259void readEdges(Reader &ar, std::string const &axisName, std::vector<double> &edges)
260{
261 Cursor c{ar.cursor().node()["edges"], ar.cursor(), "edges"};
262 const std::string context = "\"edges\" of axis '" + axisName + "' must ";
263 if (!c.node().is_seq()) {
264 c.error(context + "be a list");
265 return;
266 }
267 readValue(c, edges);
268 for (std::size_t i = 1; i < edges.size(); ++i) {
269 if (!(edges[i] > edges[i - 1])) {
270 c.error(context + "be strictly ascending");
271 return;
272 }
273 }
274 if (edges.size() < 2) {
275 c.error(context + "contain at least two values");
276 }
277}
278
279} // namespace
280
282{
283 ar("name", p.name);
284 ar("version", p.version);
285}
286
287void describe(Reader &ar, DomainAxis &axis)
288{
289 ar("name", axis.name);
290 if (ar.has(fixedValueKey)) {
291 // The domain of the parameter is the set with this single value: no range, binning or categories.
292 for (const char *key : {"min", "max", "nbins", "edges", "categories"}) {
293 if (ar.has(key)) {
294 ar.cursor().error("the axis '" + axis.name + "' has \"" + fixedValueKey + "\" and \"" + key +
295 "\", which exclude each other");
296 }
297 }
298 // Not read as an optional component, for which null would be the same as leaving it out.
299 ar(fixedValueKey, axis.fixedValue.emplace());
300 return;
301 }
302 readBound(ar, "min", axis.min, -infinity);
303 readBound(ar, "max", axis.max, infinity);
304 if (ar.has("edges")) {
305 // Not necessarily ascending, because RooFit also writes parameterized ranges as edges.
306 EdgesBinning binning;
307 ar("edges", binning.edges);
308 if (!binning.edges.empty()) {
309 if (!axis.min) {
310 axis.min = binning.edges.front();
311 }
312 if (!axis.max) {
313 axis.max = binning.edges.back();
314 }
315 }
316 axis.binning = std::move(binning);
317 } else if (ar.has("nbins")) {
318 RegularBinning binning;
319 ar("nbins", binning.nbins);
320 if (binning.nbins != 0) {
321 axis.binning = binning;
322 }
323 }
324}
325
327{
328 ar("name", axis.name);
329 readBound(ar, "min", axis.min, -infinity);
330 readBound(ar, "max", axis.max, infinity);
331}
332
333void describe(Reader &ar, BinnedAxis &axis)
334{
335 ar("name", axis.name);
336 if (ar.has("edges")) {
337 EdgesBinning binning;
338 readEdges(ar, axis.name, binning.edges);
339 if (binning.edges.size() >= 2) {
340 axis.min = binning.edges.front();
341 axis.max = binning.edges.back();
342 }
343 // The standard requires "min" and "max" also for irregular binnings, which older versions of RooFit didn't write.
344 std::optional<double> min;
345 std::optional<double> max;
346 ar("min", min);
347 ar("max", max);
348 if ((min && *min != axis.min) || (max && *max != axis.max)) {
349 ar.cursor().error("\"min\" and \"max\" of axis '" + axis.name + "' must agree with the \"edges\"");
350 }
351 if (ar.has("nbins")) {
352 ar.cursor().error("axis '" + axis.name + "' must not have both \"edges\" and \"nbins\"");
353 }
354 axis.binning = std::move(binning);
355 return;
356 }
357 ar("min", axis.min);
358 ar("max", axis.max);
359 RegularBinning binning;
360 ar("nbins", binning.nbins);
361 axis.binning = binning;
362}
363
365{
366 ar("name", axis.name);
367 ar("categories", axis.categories);
368}
369
370namespace {
371
372// The kind of an axis is determined by its components.
373template <class ContinuousAxis_t>
374void readAxis(Cursor const &c, std::variant<ContinuousAxis_t, CategoricalAxis> &out)
375{
376 if (!c.node().is_map()) {
377 c.error("must be a struct");
378 return;
379 }
380 Reader reader{c};
381 // A fixed axis is not categorical. That it has no categories is checked when it is read.
382 if (reader.has("categories") && !(std::is_same_v<ContinuousAxis_t, DomainAxis> && reader.has(fixedValueKey))) {
383 CategoricalAxis axis;
384 describe(reader, axis);
385 out = std::move(axis);
386 } else {
387 ContinuousAxis_t axis;
388 describe(reader, axis);
389 out = std::move(axis);
390 }
391}
392
393} // namespace
394
395void readValue(Cursor const &c, std::variant<DomainAxis, CategoricalAxis> &out)
396{
397 readAxis(c, out);
398}
399
400void readValue(Cursor const &c, std::variant<ContinuousAxis, CategoricalAxis> &out)
401{
402 readAxis(c, out);
403}
404
405void readValue(Cursor const &c, std::variant<BinnedAxis, CategoricalAxis> &out)
406{
407 readAxis(c, out);
408}
409
410void describe(Reader &ar, Domain &domain)
411{
412 ar("name", domain.name);
413 std::string type;
414 if (ar.has("type")) {
415 ar("type", type);
416 }
417 if (type != "product_domain") {
418 ar.cursor().error("only domains of type \"product_domain\" are currently supported!");
419 return;
420 }
421 ar.list("axes", domain.axes);
422}
423
425{
426 if (!c.node().is_map()) {
427 c.error("must be a struct");
428 return;
429 }
430 Reader ar{c};
431 ar("name", p.name);
432 if (ar.has("value")) {
433 Cursor crsr{ar.cursor().node()["value"], ar.cursor(), "value"};
434 if (crsr.node().is_number()) {
435 p.value = crsr.node().val_double();
436 } else {
437 std::string label;
438 readValue(crsr, label);
439 p.value = std::move(label);
440 }
441 }
442 std::optional<bool> isConst;
443 ar("const", isConst);
444 p.isConst = isConst.value_or(false);
445 ar("err", p.error);
446 ar("relErr", p.relError);
447 ar("nbins", p.nbins);
448}
449
451{
452 ar("name", point.name);
453 ar.list("parameters", point.parameters);
454}
455
457{
458 ar("value", data.value);
459 ar("uncertainty", data.uncertainty);
460 ar.list("axes", data.axes);
461}
462
464{
465 ar.list("axes", data.axes);
466 ar("weights", data.weights);
467 data.hasEntriesUncertainties = ar.has("entries_uncertainties");
468
469 if (!ar.has("entries")) {
470 ar.cursor().error("does not define the required component \"entries\"");
471 return;
472 }
473 Cursor entries{ar.cursor().node()["entries"], ar.cursor(), "entries"};
474 if (!entries.node().is_seq()) {
475 entries.error("key 'entries' is not a list!");
476 return;
477 }
478 if (data.weights && data.weights->size() != entries.node().num_children()) {
479 ar.cursor().error("inconsistent number of entries and weights!");
480 return;
481 }
482 data.entries.reserve(entries.node().num_children() * data.axes.size());
483 std::size_t iRow = 0;
484 for (JSONNode const &row : entries.node().children()) {
485 Cursor rowCursor{row, entries, iRow};
486 if (!row.is_seq()) {
487 rowCursor.error("coordinate point '" + std::to_string(iRow) + "' is not a list!");
488 return;
489 }
490 if (row.num_children() != data.axes.size()) {
491 rowCursor.error("inconsistent number of entries and observables!");
492 return;
493 }
494 std::size_t iAxis = 0;
495 for (JSONNode const &value : row.children()) {
496 Cursor valueCursor{value, rowCursor, iAxis};
497 if (auto const *category = std::get_if<CategoricalAxis>(&data.axes[iAxis])) {
498 std::string label;
499 readValue(valueCursor, label);
500 auto found = std::find(category->categories.begin(), category->categories.end(), label);
501 if (found == category->categories.end()) {
502 valueCursor.error("'" + label + "' is not a category of the axis '" + category->name + "'");
503 return;
504 }
505 data.entries.push_back(static_cast<double>(std::distance(category->categories.begin(), found)));
506 } else {
507 double coordinate = 0.0;
508 readValue(valueCursor, coordinate);
509 data.entries.push_back(coordinate);
510 }
511 ++iAxis;
512 }
513 ++iRow;
514 }
515}
516
517void describe(Reader &ar, GaussianUncertainty &uncertainty)
518{
519 if (ar.has("type")) {
520 std::string type;
521 ar("type", type);
522 if (type != "gaussian_uncertainty") {
523 ar.cursor().error("unsupported type of uncertainty '" + type + "'");
524 }
525 }
526 ar("sigma", uncertainty.sigma);
527 if (ar.has("correlation")) {
528 Cursor c{ar.cursor().node()["correlation"], ar.cursor(), "correlation"};
529 // Besides a matrix, it can be the number 0 to indicate that there is no correlation.
530 if (c.node().is_seq()) {
531 readValue(c, uncertainty.correlation);
532 } else if (!c.node().is_number() || c.node().val_double() != 0.0) {
533 c.error("must be a matrix or 0");
534 }
535 }
536}
537
539{
540 ar.list("axes", data.axes);
541 ar("contents", data.contents);
542 ar("uncertainty", data.uncertainty);
543 // RooFit extension: older versions read the errors like in the samples of a HistFactory distribution.
544 if (ar.has("errors")) {
545 Cursor errors{ar.cursor().node()["errors"], ar.cursor(), "errors"};
546 if (!errors.node().is_seq()) {
547 errors.error("errors are not in list form");
548 } else if (!data.uncertainty) {
549 data.uncertainty.emplace();
550 readValue(errors, data.uncertainty->sigma);
551 }
552 }
553}
554
555void readValue(Cursor const &c, Data &out)
556{
557 if (!c.node().is_map()) {
558 c.error("must be a struct");
559 return;
560 }
561 Reader ar{c};
562 ar("name", out.name);
563 std::string type;
564 ar("type", type);
565 if (type == "binned") {
566 BinnedData body;
567 describe(ar, body);
568 out.body = std::move(body);
569 } else if (type == "unbinned") {
570 UnbinnedData body;
571 describe(ar, body);
572 out.body = std::move(body);
573 } else if (type == "point") {
574 PointData body;
575 describe(ar, body);
576 out.body = std::move(body);
577 } else if (ar.has("type")) {
578 c.error("unknown type of dataset '" + type + "'");
579 }
580}
581
582void describe(Reader &ar, Likelihood &likelihood)
583{
584 ar("name", likelihood.name);
585 ar.list("aux_distributions", likelihood.auxDistributions);
586
587 std::vector<DistributionRef> distributions;
588 ar("distributions", distributions);
589 if (!ar.has("data")) {
590 ar.cursor().error("does not define the required component \"data\"");
591 return;
592 }
593 Cursor data{ar.cursor().node()["data"], ar.cursor(), "data"};
594 if (!data.node().is_seq()) {
595 data.error("must be an array");
596 return;
597 }
598 if (data.node().num_children() != distributions.size()) {
599 ar.cursor().error("\"distributions\" and \"data\" must have the same length");
600 return;
601 }
602 likelihood.terms.reserve(distributions.size());
603 std::size_t i = 0;
604 for (JSONNode const &node : data.node().children()) {
605 LikelihoodTerm term;
606 term.distribution = std::move(distributions[i]);
607 // Older versions of RooFit used the string "0" to say that there is no dataset.
608 if (node.is_number() || (!node.is_container() && !node.is_null() && node.val() == "0")) {
609 term.data = node.is_number() ? node.val_double() : 0.0;
610 } else {
611 DataRef ref;
612 readValue(Cursor{node, data, i}, ref);
613 term.data = std::move(ref);
614 }
615 likelihood.terms.push_back(std::move(term));
616 ++i;
617 }
618}
619
620void describe(Reader &ar, Analysis &analysis)
621{
622 ar("name", analysis.name);
623 ar("likelihood", analysis.likelihood);
624 // HS3 v0.2.9 used the singular keys "parameter_of_interest" (a list) and "domain" (a single name). They are still
625 // accepted, but the plural keys take precedence.
626 auto isLegacy = [&](const char *key, const char *legacyKey) {
627 if (ar.has(key) || !ar.has(legacyKey)) {
628 return false;
629 }
630 ar.cursor().warning(std::string("the key \"") + legacyKey + "\" of analysis '" + analysis.name +
631 "' is deprecated, please use \"" + key + "\" instead.");
632 return true;
633 };
634 ar.list(isLegacy("parameters_of_interest", "parameter_of_interest") ? "parameter_of_interest"
635 : "parameters_of_interest",
636 analysis.parametersOfInterest);
637 if (!isLegacy("domains", "domain")) {
638 ar.list("domains", analysis.domains);
639 } else if (ar.cursor().node()["domain"].is_seq()) {
640 ar.list("domain", analysis.domains);
641 } else {
642 analysis.domains.emplace_back();
643 ar("domain", analysis.domains.back());
644 }
645 ar("init", analysis.init);
646 ar("prior", analysis.prior);
647}
648
649namespace {
650
651// The strings of the interpolation types and functions in the standard.
652std::optional<InterpolationType> interpolationTypeFromString(std::string const &str)
653{
655 if (str == toString(type)) {
656 return type;
657 }
658 }
659 return std::nullopt;
660}
661
662std::optional<InterpolationFunction> interpolationFunctionFromString(std::string const &str)
663{
666 if (str == toString(function)) {
667 return function;
668 }
669 }
670 return std::nullopt;
671}
672
673} // namespace
674
676{
677 return type == InterpolationType::Add ? "add" : "mult";
678}
679
680char const *toString(InterpolationFunction function)
681{
682 switch (function) {
683 case InterpolationFunction::Poly1: return "poly1";
684 case InterpolationFunction::Poly2: return "poly2";
685 case InterpolationFunction::Poly6: return "poly6";
686 case InterpolationFunction::Exp: return "exp";
687 }
688 return "";
689}
690
692{
693 switch (type) {
694 case ConstraintType::Gauss: return "Gauss";
695 case ConstraintType::Poisson: return "Poisson";
696 case ConstraintType::LogNormal: return "LogNormal";
697 }
698 return "";
699}
700
701void readValue(Cursor const &c, Interpolation &out)
702{
703 JSONNode const &node = c.node();
704 if (!node.is_map()) {
705 c.error("must be a struct with components 'type', 'in', and 'out'");
706 return;
707 }
708 for (const char *component : {"type", "in", "out"}) {
709 if (!node.has_child(component)) {
710 c.error(std::string("does not define the required '") + component + "' component");
711 return;
712 }
713 }
714 JSONNode const &typeNode = node["type"];
715 JSONNode const &inNode = node["in"];
716 JSONNode const &outNode = node["out"];
717 if (typeNode.is_container() || typeNode.is_null() || inNode.is_container() || inNode.is_null()) {
718 c.error("components 'type' and 'in' must be strings");
719 return;
720 }
721
722 auto type = interpolationTypeFromString(typeNode.val());
723 if (!type) {
724 c.error("has unknown interpolation type '" + typeNode.val() + "'");
725 return;
726 }
727 auto in = interpolationFunctionFromString(inNode.val());
728 if (!in) {
729 c.error("has unknown interpolation function '" + inNode.val() + "'");
730 return;
731 }
732 out.type = *type;
733 out.in = *in;
734 out.out.reset();
735
736 if (!outNode.is_null()) {
737 if (outNode.is_container()) {
738 c.error("component 'out' must be a string or null");
739 return;
740 }
741 out.out = interpolationFunctionFromString(outNode.val());
742 if (!out.out) {
743 c.error("has unknown extrapolation function '" + outNode.val() + "'");
744 return;
745 }
746 }
747 Reader reader{c};
748 for (const char *component : {"type", "in", "out"}) {
749 reader.has(component);
750 }
751}
752
753namespace {
754
755// Constraints: "constraints" names the constraint distribution of each parameter, or is null for an unconstrained one.
756// Older documents give a type in "constraint", or a distribution in "constraint_name", to which upgradeDocument()
757// translated the even older forms.
758std::optional<ConstraintType> readConstraintType(Cursor const &c)
759{
760 std::string value;
761 readValue(c, value);
763 if (value == toString(type)) {
764 return type;
765 }
766 }
767 c.error("unknown constraint type '" + value + "', supported are \"Gauss\", \"Poisson\" and \"LogNormal\"");
768 return std::nullopt;
769}
770
771void readConstraint(Reader &ar, Constraint &out)
772{
773 if (ar.has("constraints")) {
774 Cursor c{ar.cursor().node()["constraints"], ar.cursor(), "constraints"};
775 if (!c.node().is_seq() || c.node().num_children() != 1) {
776 c.error("must be an array with one entry");
777 return;
778 }
779 JSONNode const &item = c.node().child(0);
780 if (item.is_null()) {
781 out = Unconstrained{};
782 return;
783 }
784 ConstraintPdf pdf;
785 readValue(Cursor{item, c, std::size_t{0}}, pdf.pdf);
786 out = std::move(pdf);
787 } else if (ar.has("constraint_name")) {
788 ConstraintPdf pdf;
789 ar("constraint_name", pdf.pdf);
790 out = std::move(pdf);
791 } else if (ar.has("constraint")) {
792 if (auto type = readConstraintType(Cursor{ar.cursor().node()["constraint"], ar.cursor(), "constraint"})) {
793 out = *type;
794 }
795 }
796}
797
798void readGammaConstraint(Reader &ar, GammaConstraint &out)
799{
800 if (ar.has("constraints")) {
801 GammaConstraintPdfs constraints;
802 Cursor c{ar.cursor().node()["constraints"], ar.cursor(), "constraints"};
803 if (!c.node().is_seq()) {
804 c.error("must be an array");
805 return;
806 }
807 std::size_t i = 0;
808 for (JSONNode const &item : c.node().children()) {
809 constraints.pdfs.emplace_back();
810 if (!item.is_null()) {
811 constraints.pdfs.back().emplace();
812 readValue(Cursor{item, c, i}, *constraints.pdfs.back());
813 }
814 ++i;
815 }
816 out = std::move(constraints);
817 } else if (ar.has("constraint")) {
818 Cursor c{ar.cursor().node()["constraint"], ar.cursor(), "constraint"};
819 // "Const" is a RooFit extension.
820 if (!c.node().is_container() && !c.node().is_null() && c.node().val() == "Const") {
821 out = ConstantGammas{};
822 } else if (auto type = readConstraintType(c)) {
823 out = *type;
824 }
825 }
826}
827
828} // namespace
829
830// The standard doesn't require a name for modifiers, but RooFit uses it to name the objects it creates.
831void readModifierName(Reader &ar, std::string &name)
832{
833 std::optional<std::string> optionalName;
834 ar("name", optionalName);
835 name = optionalName.value_or("");
836}
837
838// The parameter of a modifier with a single parameter is given in "parameters", or in "parameter" in older documents.
839void readParameter(Reader &ar, ParameterRef &parameter)
840{
841 if (!ar.has("parameters")) {
842 ar("parameter", parameter);
843 return;
844 }
845 Cursor c{ar.cursor().node()["parameters"], ar.cursor(), "parameters"};
846 if (!c.node().is_seq() || c.node().num_children() != 1) {
847 c.error("must be an array with one entry");
848 return;
849 }
850 readValue(Cursor{c.node().child(0), c, std::size_t{0}}, parameter);
851}
852
854{
855 readModifierName(ar, m.name);
856 readParameter(ar, m.parameter);
857 readConstraint(ar, m.constraint);
858}
859
861{
862 readModifierName(ar, m.name);
863 readParameter(ar, m.parameter);
864 ar.nested("data", [&](Reader &data) {
865 data("hi", m.hi);
866 data("lo", m.lo);
867 });
868 readConstraint(ar, m.constraint);
869 ar("interpolation", m.interpolation);
870}
871
872void describe(Reader &ar, HistoSysVariation &variation)
873{
874 ar("contents", variation.contents);
875 ar("errors", variation.errors);
876}
877
879{
880 readModifierName(ar, m.name);
881 readParameter(ar, m.parameter);
882 ar.nested("data", [&](Reader &data) {
883 data("hi", m.hi);
884 data("lo", m.lo);
885 });
886 readConstraint(ar, m.constraint);
887 ar("interpolation", m.interpolation);
888}
889
891{
892 readModifierName(ar, m.name);
893 if (ar.has("parameter")) {
894 // A single name stands for one parameter per bin, which are named after it.
895 ar("parameter", m.name);
896 }
897 ar.list("parameters", m.parameters);
898 if (ar.has("data")) {
899 ar.nested("data", [&](Reader &data) { data.list("vals", m.vals); });
900 }
901 readGammaConstraint(ar, m.constraint);
902}
903
905{
906 readModifierName(ar, m.name);
907 ar.list("parameters", m.parameters);
908 readGammaConstraint(ar, m.constraint);
909}
910
912{
913 ar("name", m.name);
914}
915
916void readValue(Cursor const &c, Modifier &out)
917{
918 if (!c.node().is_map()) {
919 c.error("must be a struct");
920 return;
921 }
922 Reader ar{c};
923 std::string type;
924 ar("type", type);
925 auto read = [&](auto modifier) {
926 describe(ar, modifier);
927 out = std::move(modifier);
928 };
929 if (type == "normfactor") {
930 read(NormFactor{});
931 } else if (type == "normsys") {
932 read(NormSys{});
933 } else if (type == "histosys") {
934 read(HistoSys{});
935 } else if (type == "shapesys" || type == "shapefactor") {
936 read(ShapeSys{});
937 } else if (type == "staterror") {
938 read(StatError{});
939 } else if (type == "custom") {
940 read(CustomModifier{});
941 } else if (ar.has("type")) {
942 std::string name;
943 if (ar.has("name")) {
944 ar("name", name);
945 }
946 c.error("modifier '" + name + "' of unknown type '" + type + "'");
947 }
948}
949
950void describe(Reader &ar, Sample &sample)
951{
952 ar("name", sample.name);
953 ar.nested("data", [&](Reader &data) {
954 data("contents", sample.contents);
955 data("errors", sample.errors);
956 });
957 ar.list("modifiers", sample.modifiers);
958}
959
961{
962 std::optional<double> relThreshold;
963 ar("relThreshold", relThreshold);
964 config.relThreshold = relThreshold.value_or(0.0);
965 readGammaConstraint(ar, config.constraint);
966 // Written by upgradeDocument() for older documents.
967 std::optional<bool> impliedPoissonErrors;
968 ar("implied_poisson_errors", impliedPoissonErrors);
969 config.impliedPoissonErrors = impliedPoissonErrors.value_or(false);
970}
971
973{
974 ar("axes", dist.axes);
975 ar("default_interpolation", dist.defaultInterpolation);
976 ar("samples", dist.samples);
977 if (ar.has("staterror")) {
978 readValue(Cursor{ar.cursor().node()["staterror"], ar.cursor(), "staterror"}, dist.statError);
979 }
980}
981
982namespace {
983
984void readDistribution(Cursor const &c, Distribution &out, ParseOptions const &options)
985{
986 if (!c.node().is_map()) {
987 c.error("must be a struct");
988 return;
989 }
990 Reader ar{c};
991 ar("name", out.name);
992 std::string type;
993 ar("type", type);
994 if (options.typedHistFactory && type == "histfactory_dist") {
995 HistFactoryDist body;
996 describe(ar, body);
997 out.body = std::move(body);
998 } else {
999 out.body = GenericObject{type, &c.node()};
1000 // The importers read the other components.
1001 for (JSONNode const &child : c.node().children()) {
1002 ar.has(child.key().c_str());
1003 }
1004 }
1005}
1006
1007} // namespace
1008
1009void readValue(Cursor const &c, Function &out)
1010{
1011 if (!c.node().is_map()) {
1012 c.error("must be a struct");
1013 return;
1014 }
1015 Reader ar{c};
1016 ar("name", out.name);
1017 ar("type", out.body.type);
1018 out.body.node = &c.node();
1019 // The importers read the other components.
1020 for (JSONNode const &child : c.node().children()) {
1021 ar.has(child.key().c_str());
1022 }
1023}
1024
1025void describe(Reader &ar, Attributes &attributes)
1026{
1027 if (ar.has("dict")) {
1028 Cursor dict{ar.cursor().node()["dict"], ar.cursor(), "dict"};
1029 if (!dict.node().is_map()) {
1030 dict.error("must be a struct");
1031 return;
1032 }
1033 for (JSONNode const &item : dict.node().children()) {
1034 readValue(Cursor{item, dict, item.key().c_str()}, attributes.dict[item.key()]);
1035 }
1036 }
1037 ar.list("tags", attributes.tags);
1038 if (ar.has("is_const_var")) {
1039 int isConstVar = 0;
1040 ar("is_const_var", isConstVar);
1041 attributes.isConstVar = isConstVar == 1;
1042 }
1043}
1044
1046{
1047 ar("index_cat", info.indexCat);
1048 ar("labels", info.labels);
1049 ar("indices", info.indices);
1050 ar("distributions", info.distributions);
1051 if (info.labels.size() != info.indices.size() || info.labels.size() != info.distributions.size()) {
1052 ar.cursor().error("mismatch in number of labels, indices and distributions!");
1053 }
1054}
1055
1057{
1058 ar("index_cat", info.indexCat);
1059 ar("labels", info.labels);
1060 ar("indices", info.indices);
1061 if (info.labels.size() != info.indices.size()) {
1062 ar.cursor().error("mismatch in number of indices and labels!");
1063 }
1064}
1065
1067{
1068 ar("mcName", info.mcName);
1069 ar("pdfName", info.pdfName);
1070 ar("combined_data_name", info.combinedDataName);
1071}
1072
1073namespace {
1074
1075template <class Map_t>
1076void readMap(Reader &ar, const char *key, Map_t &out)
1077{
1078 if (!ar.has(key)) {
1079 return;
1080 }
1081 Cursor c{ar.cursor().node()[key], ar.cursor(), key};
1082 if (c.node().is_null()) {
1083 return;
1084 }
1085 if (!c.node().is_map()) {
1086 c.error("must be a struct");
1087 return;
1088 }
1089 for (JSONNode const &item : c.node().children()) {
1090 const std::string itemKey = item.key();
1091 readValue(Cursor{item, c, itemKey.c_str()}, out[itemKey]);
1092 }
1093}
1094
1095// The content of "misc" is not supposed to change the result of evaluating the model, so problems with the step widths
1096// are only warnings.
1097void readStepWidths(Cursor const &misc, std::vector<StepWidth> &out)
1098{
1099 JSONNode const *node = misc.node().find("minimization", "parameter_stepwidths");
1100 if (!node) {
1101 return;
1102 }
1103 if (!node->is_seq()) {
1104 misc.warning("minimization.parameter_stepwidths is not a sequence, skipping.");
1105 return;
1106 }
1107 for (JSONNode const &item : node->children()) {
1108 if (!item.is_map() || !item.has_child("name") || !item.has_child("step_width") ||
1109 !item["step_width"].is_number() || item["name"].is_container() || item["name"].is_null()) {
1110 misc.warning("skipping malformed parameter_stepwidths entry.");
1111 continue;
1112 }
1113 out.push_back({item["name"].val(), item["step_width"].val_double()});
1114 }
1115}
1116
1117void readMisc(Cursor const &root, RooFitInternal &out)
1118{
1119 if (!root.node().has_child("misc")) {
1120 return;
1121 }
1122 Cursor misc{root.node()["misc"], root, "misc"};
1123 if (!misc.node().is_map()) {
1124 return;
1125 }
1126 readStepWidths(misc, out.stepWidths);
1127 if (!misc.node().has_child("ROOT_internal")) {
1128 return;
1129 }
1130 Cursor internal{misc.node()["ROOT_internal"], misc, "ROOT_internal"};
1131 if (!internal.node().is_map()) {
1132 return;
1133 }
1134 Reader ar{internal};
1135 readMap(ar, "attributes", out.attributes);
1136 readMap(ar, "combined_distributions", out.combinedDistributions);
1137 readMap(ar, "combined_datasets", out.combinedDatasets);
1138 readMap(ar, "ModelConfigs", out.modelConfigs);
1139 readMap(ar, "histfactory_constraints", out.channelConstraints);
1140}
1141
1142void readMetadata(Cursor const &root, Metadata &out, ParseOptions const &options)
1143{
1144 JSONNode const *metadata = root.node().find("metadata");
1145 JSONNode const *versionNode = metadata ? metadata->find("hs3_version") : nullptr;
1146 if (!versionNode) {
1147 if (options.requireMetadata) {
1148 std::stringstream ss;
1149 ss << "The HS3 version is missing in the JSON!\n"
1150 << "Please include the HS3 version in the metadata field, e.g.:\n"
1151 << " \"metadata\" :\n"
1152 << " {\n"
1153 << " \"hs3_version\" : \"" << supportedVersion << "\"\n"
1154 << " }";
1155 root.error(ss.str());
1156 }
1157 return;
1158 }
1159 Cursor metadataCursor{*metadata, root, "metadata"};
1160 Cursor versionCursor{*versionNode, metadataCursor, "hs3_version"};
1161 if (versionNode->is_container() || versionNode->is_null() || versionNode->is_number()) {
1162 versionCursor.error(std::string("The HS3 version in the metadata must be a string, e.g. \"") + supportedVersion +
1163 "\"");
1164 return;
1165 }
1166 out.hs3Version = versionNode->val();
1167
1168 Version supported;
1169 parseVersion(supportedVersion, supported);
1170 if (!parseVersion(out.hs3Version, out.version)) {
1171 versionCursor.warning("cannot parse hs3_version '" + out.hs3Version +
1172 "', expected a string of the form \"MAJOR.MINOR\".");
1173 } else if (supported < Version{out.version.major, out.version.minor}) {
1174 versionCursor.warning("the document has hs3_version '" + out.hs3Version +
1175 "', which is newer than the version supported by this RooFit ('" + supportedVersion +
1176 "'). Some content might not be imported correctly.");
1177 }
1178
1179 Reader ar{metadataCursor};
1180 ar.has("hs3_version");
1181 ar.list("packages", out.packages);
1182 ar.list("authors", out.authors);
1183 ar.list("publications", out.publications);
1184 ar("description", out.description);
1185}
1186
1187} // namespace
1188
1189HS3Document parseDocument(JSONNode const &root, Diagnostics &diagnostics, ParseOptions const &options)
1190{
1191 HS3Document doc;
1192 Cursor cursor{root, diagnostics};
1193 readMetadata(cursor, doc.metadata, options);
1194 if (hasErrors(diagnostics)) {
1195 return doc;
1196 }
1197
1198 Reader ar{cursor};
1199 ar.has("metadata");
1200 ar.has("misc");
1201 if (ar.has("distributions") && !root["distributions"].is_null()) {
1202 Cursor distributions{root["distributions"], cursor, "distributions"};
1203 if (!distributions.node().is_seq()) {
1204 distributions.error("must be an array");
1205 } else {
1206 std::size_t i = 0;
1207 doc.distributions.reserve(distributions.node().num_children());
1208 for (JSONNode const &item : distributions.node().children()) {
1209 doc.distributions.emplace_back();
1210 readDistribution(Cursor{item, distributions, i}, doc.distributions.back(), options);
1211 ++i;
1212 }
1213 }
1214 }
1215 ar.list("functions", doc.functions);
1216 ar.list("data", doc.data);
1217 ar.list("likelihoods", doc.likelihoods);
1218 ar.list("domains", doc.domains);
1219 ar.list("parameter_points", doc.parameterPoints);
1220 ar.list("analyses", doc.analyses);
1221 readMisc(cursor, doc.rooFit);
1222 return doc;
1223}
1224
1226{
1227 HistFactoryDist out;
1228 Cursor cursor{distribution, diagnostics};
1229 if (!distribution.is_map()) {
1230 cursor.error("must be a struct");
1231 return out;
1232 }
1233 Reader ar{cursor};
1234 ar.has("name");
1235 ar.has("type");
1236 describe(ar, out);
1237 return out;
1238}
1239
1240void readValue(Cursor const &c, NameOrNumber &out)
1241{
1242 if (c.node().is_number()) {
1243 out = c.node().val_double();
1244 } else {
1245 readValue(c, out.emplace<std::string>());
1246 }
1247}
1248
1249std::optional<Interpolation> readInterpolationCode(Cursor const &c, InterpolationClass interpolationClass)
1250{
1251 JSONNode const &node = c.node();
1252 if (node.is_container() || node.is_null() || !node.has_val()) {
1253 c.error("must be a structured interpolation or a legacy integer code");
1254 return std::nullopt;
1255 }
1256 const std::string value = node.val();
1257 int code = 0;
1258 const auto result = std::from_chars(value.data(), value.data() + value.size(), code);
1259 if (result.ec != std::errc{} || result.ptr != value.data() + value.size()) {
1260 c.error("has invalid legacy interpolation code '" + value + "'");
1261 return std::nullopt;
1262 }
1263 auto interpolation = interpolationFromCode(code, interpolationClass);
1264 if (!interpolation) {
1265 c.error(std::string("has unsupported ") +
1266 (interpolationClass == InterpolationClass::Piecewise ? "PiecewiseInterpolation" : "FlexibleInterpVar") +
1267 " code " + value);
1268 }
1269 return interpolation;
1270}
1271
1272namespace {
1273
1274// The "interpolations" of the functions "interpolation" and "interpolation0d": one for all parameters, or one per
1275// parameter. Older versions of RooFit wrote integer "interpolationCodes". Without both, the interpolation is linear.
1276void readInterpolations(Reader &ar, std::size_t nParameters, InterpolationClass interpolationClass,
1277 std::vector<Interpolation> &out)
1278{
1279 out.assign(nParameters, additivePiecewiseLinear);
1280 if (ar.has("interpolations")) {
1281 Cursor c{ar.cursor().node()["interpolations"], ar.cursor(), "interpolations"};
1282 const std::size_t errorsBefore = c.numErrors();
1283 std::vector<Interpolation> interpolations;
1284 readValue(c, interpolations);
1285 const std::size_t size = interpolations.size();
1286 if (c.numErrors() != errorsBefore) {
1287 return;
1288 }
1289 if (nParameters == 0 ? size != 0 : size != 1 && size != nParameters) {
1290 c.error("component 'interpolations' must contain either one descriptor or one descriptor per parameter (got " +
1291 std::to_string(size) + " for " + std::to_string(nParameters) + " parameters)");
1292 } else if (size == 1) {
1293 out.assign(nParameters, interpolations.front());
1294 } else {
1295 out = std::move(interpolations);
1296 }
1297 return;
1298 }
1299 if (!ar.has("interpolationCodes")) {
1300 return;
1301 }
1302 Cursor c{ar.cursor().node()["interpolationCodes"], ar.cursor(), "interpolationCodes"};
1303 if (!c.node().is_seq() || c.node().num_children() != nParameters) {
1304 c.error("legacy component 'interpolationCodes' must contain one code per parameter (got " +
1305 std::to_string(c.node().is_seq() ? c.node().num_children() : 0) + " for " + std::to_string(nParameters) +
1306 " parameters)");
1307 return;
1308 }
1309 std::size_t i = 0;
1310 for (JSONNode const &child : c.node().children()) {
1311 if (auto interpolation = readInterpolationCode(Cursor{child, c, i}, interpolationClass)) {
1312 out[i] = *interpolation;
1313 }
1314 ++i;
1315 }
1316}
1317
1318// The functions "interpolation" and "interpolation0d" have the variations "high" and "low" for each of the "vars".
1319template <class Function_t, class Fn>
1320Function_t parseInterpolated(JSONNode const &node, Diagnostics &diagnostics, InterpolationClass interpolationClass,
1321 Fn &&readOthers)
1322{
1323 Function_t out;
1324 Cursor cursor{node, diagnostics};
1325 if (!node.is_map()) {
1326 cursor.error("must be a struct");
1327 return out;
1328 }
1329 const std::size_t errorsBefore = cursor.numErrors();
1330 Reader ar{cursor};
1331 ar.has("name");
1332 ar.has("type");
1333 ar("nom", out.nominal);
1334 ar("vars", out.parameters);
1335 ar("high", out.high);
1336 ar("low", out.low);
1337 readOthers(ar, out);
1338 if (cursor.numErrors() == errorsBefore &&
1339 (out.high.size() != out.parameters.size() || out.low.size() != out.parameters.size())) {
1340 cursor.error("has non-matching lengths of 'vars', 'high' and 'low'");
1341 }
1342 readInterpolations(ar, out.parameters.size(), interpolationClass, out.interpolations);
1343 return out;
1344}
1345
1346} // namespace
1347
1348std::vector<BinnedAxis> parseBinnedAxes(JSONNode const &axes, Diagnostics &diagnostics)
1349{
1350 std::vector<BinnedAxis> out;
1351 readValue(Cursor{axes, diagnostics}, out);
1352 return out;
1353}
1354
1356{
1357 return parseInterpolated<InterpolatedFunction>(
1358 node, diagnostics, InterpolationClass::Piecewise,
1359 [](Reader &ar, InterpolatedFunction &out) { ar("positiveDefinite", out.positiveDefinite); });
1360}
1361
1363{
1364 return parseInterpolated<InterpolatedNumber>(node, diagnostics, InterpolationClass::Flexible,
1365 [](Reader &, InterpolatedNumber &) {});
1366}
1367
1368} // namespace HS3
1369} // namespace RooFit
Interpolation descriptor
Definition HS3Parser.cxx:39
int piecewise
Definition HS3Parser.cxx:40
int flexible
Definition HS3Parser.cxx:41
constexpr const char * fixedValueKey
Definition JSONIOUtils.h:17
#define d(i)
Definition RSha256.hxx:102
#define c(i)
Definition RSha256.hxx:101
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 cursor
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 result
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 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
XID Cursor
Definition TGX11.h:36
std::string val() const
JSONNode const * find(std::string const &key) const
bool has_child(std::string const &) const
std::string key() const
JSONNode & child(size_t pos)
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::Function > functions
std::vector< HS3::ParameterPoint > parameterPoints
HS3::RooFitInternal rooFit
std::vector< HS3::Domain > domains
std::vector< HS3::Distribution > distributions
A position in the JSON tree.
Definition HS3Schema.h:32
const char * _key
Definition HS3Schema.h:54
std::size_t numErrors() const
void warning(std::string const &message) const
Detail::JSONNode const & node() const
Definition HS3Schema.h:44
std::string path() const
std::size_t _index
Definition HS3Schema.h:55
Diagnostics & _diagnostics
Definition HS3Schema.h:52
Cursor const * _parent
Definition HS3Schema.h:53
Detail::JSONNode const & _node
Definition HS3Schema.h:51
void error(std::string const &message) const
Reads the components of a struct.
Definition HS3Schema.h:59
~Reader()
Reports the components that were not read, unless reading the struct gave an error.
Reader(Cursor const &cursor)
bool has(const char *key)
Whether the struct has a component. Checking for a component counts as reading it.
std::vector< std::string > _seen
Definition HS3Schema.h:88
void nested(const char *key, Fn &&fn)
Read the components of a struct that is nested in a component.
Definition HS3Schema.h:178
std::size_t _nErrorsBefore
Definition HS3Schema.h:89
Cursor const & cursor() const
Definition HS3Schema.h:84
Cursor const & _cursor
Definition HS3Schema.h:87
void list(const char *key, std::vector< T > &field)
Read an array that can be omitted, which is equivalent to an empty one.
Definition HS3Schema.h:165
Types that represent an HS3 document.
std::variant< std::string, double > NameOrNumber
A name, or a number for a constant.
HistFactoryDist parseHistFactoryDist(Detail::JSONNode const &distribution, Diagnostics &diagnostics)
Reads a HistFactory distribution that doesn't come with a document.
std::variant< NormFactor, NormSys, HistoSys, ShapeSys, StatError, CustomModifier > Modifier
constexpr auto supportedVersion
Definition HS3Parser.cxx:29
InterpolationClass
The RooFit classes that implement the interpolations, which encode them with integer codes.
constexpr Interpolation multiplicativePolynomialExponential
void readValue(Cursor const &c, std::string &out)
constexpr Interpolation additivePolynomialLinear
void readModifierName(Reader &ar, std::string &name)
void readParameter(Reader &ar, ParameterRef &parameter)
void describe(Reader &ar, Package &p)
ConstraintType
The constraint types that older documents named, for which RooFit creates the constraint terms.
std::optional< Interpolation > interpolationFromCode(int code, InterpolationClass interpolationClass)
The interpolation for an integer code, or nothing if the class doesn't support the code.
Definition HS3Parser.cxx:64
char const * toString(InterpolationType type)
InterpolatedNumber parseInterpolatedNumber(Detail::JSONNode const &node, Diagnostics &diagnostics)
std::optional< Interpolation > readInterpolationCode(Cursor const &c, InterpolationClass interpolationClass)
Reads an integer interpolation code, as older versions of RooFit wrote them.
InterpolatedFunction parseInterpolatedFunction(Detail::JSONNode const &node, Diagnostics &diagnostics)
Reads the functions "interpolation" and "interpolation0d", which don't come with a document.
std::optional< int > codeFromInterpolation(Interpolation const &interpolation, InterpolationClass interpolationClass)
The integer code of an interpolation, or nothing if the class can't represent the interpolation.
Definition HS3Parser.cxx:83
constexpr Interpolation multiplicativePolynomialLinear
bool hasErrors(Diagnostics const &diagnostics)
Whether there is a diagnostic with error severity.
Definition HS3Parser.cxx:94
constexpr Interpolation multiplicativePiecewiseExponential
std::vector< Diagnostic > Diagnostics
std::variant< Unconstrained, ConstraintType, ConstantGammas, GammaConstraintPdfs > GammaConstraint
bool parseVersion(std::string const &version, Version &out)
Reads the leading "MAJOR.MINOR" and the optional ".PATCH" of an HS3 version string.
std::variant< Unconstrained, ConstraintType, ConstraintPdf > Constraint
std::vector< BinnedAxis > parseBinnedAxes(Detail::JSONNode const &axes, Diagnostics &diagnostics)
Reads the axes of binned data that doesn't come with a document.
constexpr Interpolation additiveQuadraticLinear
constexpr Interpolation additivePiecewiseLinear
HS3Document parseDocument(Detail::JSONNode const &root, Diagnostics &diagnostics, ParseOptions const &options={})
Reads a document.
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
Definition CodegenImpl.h:73
std::optional< DistributionRef > prior
std::optional< ParameterPointRef > init
LikelihoodRef likelihood
std::vector< DomainRef > domains
std::vector< ParameterRef > parametersOfInterest
bool isConstVar
How ROOT versions before HS3 0.3 marked named RooConstVars.
std::vector< std::string > tags
std::map< std::string, std::string > dict
The axis of binned data.
std::vector< std::string > categories
std::vector< int > indices
std::vector< std::string > labels
std::vector< std::string > labels
std::vector< DistributionRef > distributions
The axis of point data or unbinned data, which can't have a binning.
std::optional< double > max
std::optional< double > min
Multiplies the sample with a function of the document.
std::string name
std::variant< PointData, UnbinnedData, BinnedData > body
The axis of a domain.
Definition HS3Document.h:98
std::optional< double > min
std::optional< double > max
std::optional< Binning > binning
std::optional< double > fixedValue
std::vector< std::variant< DomainAxis, CategoricalAxis > > axes
std::vector< double > edges
Definition HS3Document.h:92
std::vector< std::vector< double > > correlation
Empty if there is no correlation.
std::vector< double > sigma
Detail::JSONNode const * node
std::vector< double > contents
std::optional< std::vector< double > > errors
Not in the standard, but older versions of RooFit read them.
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.
DistributionRef distribution
std::variant< DataRef, double > data
std::vector< DistributionRef > auxDistributions
std::vector< LikelihoodTerm > terms
std::optional< std::string > pdfName
std::optional< std::string > mcName
std::optional< std::string > combinedDataName
std::vector< ParameterValue > parameters
Options for upgradeDocument() and parseDocument().
A name that says what it refers to, so that for example a DataRef can't be used as a DomainRef.
Definition HS3Document.h:63
std::vector< double > contents
std::optional< std::vector< double > > errors
std::vector< Modifier > modifiers
Scales each bin by its own parameter. The modifier type "shapesys" is a deprecated alias of "shapefac...
Settings of the staterror modifiers that apply to the whole channel. This is a RooFit extension.
bool impliedPoissonErrors
Samples without "errors" have Poisson uncertainties, as in documents before HS3 0....
Scales each bin by a parameter, that is constrained by the statistical uncertainty of all samples tha...
TMarker m
Definition textangle.C:8