28std::string at(
const char *section, std::size_t
index, std::string
const &
name)
30 return std::string(section) +
"[" + std::to_string(
index) +
"](" +
name +
")";
35 Validator(HS3Document
const &doc,
Diagnostics &diagnostics, ValidateOptions
const &options)
47 for (std::size_t i = 0; i <
_doc.distributions.size(); ++i) {
48 auto const &
dist =
_doc.distributions[i];
49 if (
auto const *histFactory = std::get_if<HistFactoryDist>(&
dist.body)) {
50 checkHistFactory(*histFactory,
dist.name, at(
"distributions", i,
dist.name));
53 for (
auto const &item :
_doc.rooFit.combinedDistributions) {
54 for (
auto const &ref : item.second.distributions) {
55 requireDistribution(ref.name,
"misc.ROOT_internal.combined_distributions." + item.first,
"distribution");
58 for (
auto const &item :
_doc.rooFit.channelConstraints) {
59 requireDistribution(item.first,
"misc.ROOT_internal.histfactory_constraints",
"distribution");
60 for (
auto const &ref : item.second) {
61 requireDistribution(ref.name,
"misc.ROOT_internal.histfactory_constraints." + item.first,
"constraint");
66 void error(std::string
const &path, std::string
const &message)
71 void warning(std::string
const &path, std::string
const &message)
77 void strictError(std::string
const &path, std::string
const &message)
83 void checkHistFactory(HistFactoryDist
const &dist, std::string
const &
name, std::string
const &path);
88 char const *sectionOf(std::string
const &
name)
const
91 return found !=
_sections.end() ? found->second.front() :
nullptr;
94 bool isDefinedIn(std::string
const &
name,
char const *section)
const
100 return std::any_of(found->second.begin(), found->second.end(),
101 [&](
char const *s) { return std::string(s) == section; });
104 void requireDistribution(std::string
const &
name, std::string
const &path, std::string
const &
what)
107 if (!isDefinedIn(
name,
"distributions") && !
_doc.rooFit.combinedDistributions.count(
name) && !isExternal(
name)) {
108 error(path,
"the " +
what +
" '" +
name +
"' does not exist");
115 std::size_t checkAxis(BinnedAxis
const &axis, std::string
const &path);
116 std::size_t checkAxis(CategoricalAxis
const &axis, std::string
const &path);
117 template <
class... Axis>
118 std::size_t checkAxis(std::variant<Axis...>
const &axis, std::string
const &path)
120 return std::visit([&](
auto const &
a) {
return checkAxis(
a, path); }, axis);
123 template <
class Axes>
124 std::optional<std::size_t> numBins(Axes
const &axes, std::string
const &path);
127 void checkFixedAxes();
128 void checkLikelihoods();
129 void checkAnalyses();
135 std::map<std::string, std::vector<char const *>>
_sections;
141void Validator::collectNames()
143 auto check = [&](
char const *section, std::string
const &
name) {
145 sections.push_back(section);
146 if (sections.size() == 1) {
149 std::string
const firstSection = sections.front();
153 auto isLegacyNamed = [](std::string
const &s) {
return s ==
"likelihoods" || s ==
"analyses"; };
156 auto isReferable = [](std::string
const &s) {
return s ==
"distributions" || s ==
"functions"; };
157 const bool ambiguous = firstSection == section || (isReferable(firstSection) && isReferable(section));
158 if (!
_options.strict || !ambiguous || isLegacyNamed(firstSection) || isLegacyNamed(section)) {
159 warning(
"",
"the name '" +
name +
"' is used in \"" + firstSection +
"\" and in \"" + section +
160 "\", which is not allowed by the HS3 standard.");
163 error(
"",
"duplicate name '" +
name +
"': it is defined in \"" + firstSection +
"\" and in \"" + section +
164 "\", but names must be unique within the document");
166 auto checkSection = [&](
char const *section,
auto const &objects) {
167 for (
auto const &
object : objects) {
168 check(section,
object.
name);
171 checkSection(
"distributions",
_doc.distributions);
172 checkSection(
"functions",
_doc.functions);
173 checkSection(
"data",
_doc.data);
174 checkSection(
"domains",
_doc.domains);
175 checkSection(
"parameter_points",
_doc.parameterPoints);
176 checkSection(
"likelihoods",
_doc.likelihoods);
177 checkSection(
"analyses",
_doc.analyses);
181 for (
auto const &domain :
_doc.domains) {
182 for (
auto const &axis : domain.axes) {
183 addParameter(std::visit([](
auto const &
a) -> std::string
const & {
return a.name; }, axis));
186 for (
auto const &point :
_doc.parameterPoints) {
187 for (
auto const ¶meter : point.parameters) {
188 addParameter(parameter.name);
191 for (
auto const &item :
_doc.rooFit.combinedDistributions) {
192 addParameter(item.second.indexCat);
194 for (
auto const &dist :
_doc.distributions) {
195 auto const *histFactory = std::get_if<HistFactoryDist>(&
dist.body);
199 for (
auto const &sample : histFactory->samples) {
200 for (
auto const &
modifier : sample.modifiers) {
201 std::visit(overloaded{[&](NormFactor
const &
m) { addParameter(
m.parameter.name); },
202 [&](NormSys
const &
m) { addParameter(
m.parameter.name); },
203 [&](HistoSys
const &
m) { addParameter(
m.parameter.name); },
204 [&](ShapeSys
const &
m) {
205 for (
auto const &
p :
m.parameters) {
206 addParameter(
p.name);
209 [&](StatError
const &
m) {
210 for (
auto const &
p :
m.parameters) {
211 addParameter(
p.name);
214 [](CustomModifier
const &) {}},
220 if (
char const *section = sectionOf(parameter)) {
221 strictError(
"",
"the name '" + parameter +
"' is used in \"" + section +
222 "\" and for a parameter, which is not allowed by the HS3 standard.");
229void Validator::checkFixedAxes()
233 std::string
const *domain =
nullptr;
235 std::map<std::string, FixedAxis> fixedAxes;
236 auto forEachAxis = [&](
auto &&fn) {
237 for (std::size_t i = 0; i <
_doc.domains.size(); ++i) {
238 for (
auto const &axis :
_doc.domains[i].axes) {
240 fn(i, std::visit([](
auto const &
a) -> std::string
const & {
return a.name; }, axis),
241 std::get_if<DomainAxis>(&axis));
245 forEachAxis([&](std::size_t i, std::string
const &, DomainAxis
const *continuous) {
246 if (!continuous || !continuous->fixedValue) {
249 DomainAxis
const &axis = *continuous;
250 Domain
const &domain =
_doc.domains[i];
251 if (!std::isfinite(*axis.fixedValue)) {
252 error(at(
"domains", i, domain.name),
253 "the single value of the parameter '" + axis.name +
"' is not finite, which JSON can't represent");
255 auto inserted = fixedAxes.insert({axis.name, FixedAxis{*axis.fixedValue, &domain.name}});
256 if (!inserted.second && inserted.first->second.value != *axis.fixedValue) {
257 error(at(
"domains", i, domain.name),
258 "the parameter '" + axis.name +
"' has different single values in the domains '" +
259 *inserted.first->second.domain +
"' and '" + domain.name +
"'");
262 if (fixedAxes.empty()) {
265 forEachAxis([&](std::size_t i, std::string
const &
name, DomainAxis
const *continuous) {
266 auto found = fixedAxes.find(
name);
267 if (found != fixedAxes.end() && !(continuous && continuous->fixedValue)) {
268 Domain const &domain = _doc.domains[i];
269 error(at(
"domains", i, domain.name),
270 "the parameter '" + name +
"' has the single value " + std::to_string(found->second.value) +
271 " in the domain '" + *found->second.domain +
"', but " + (continuous ?
"a range" :
"categories") +
272 " in the domain '" + domain.name +
"'");
275 for (std::size_t i = 0; i <
_doc.parameterPoints.size(); ++i) {
276 ParameterPoint
const &point =
_doc.parameterPoints[i];
277 for (ParameterValue
const ¶meter : point.parameters) {
278 auto found = fixedAxes.find(parameter.name);
279 double const *
value = parameter.value ? std::get_if<double>(&*parameter.value) : nullptr;
280 if (
value && found != fixedAxes.end() && *
value != found->second.value) {
281 error(at(
"parameter_points", i, point.name),
282 "the parameter point '" + point.name +
"' gives the parameter '" + parameter.name +
"' the value " +
283 std::to_string(*
value) +
", but its domain only has the value " +
284 std::to_string(found->second.value));
290void Validator::checkLikelihoods()
292 for (std::size_t i = 0; i <
_doc.likelihoods.size(); ++i) {
293 auto const &likelihood =
_doc.likelihoods[i];
294 const std::string path = at(
"likelihoods", i, likelihood.name);
295 for (
auto const &term : likelihood.terms) {
296 requireDistribution(term.distribution.name, path +
".distributions",
"distribution");
297 if (
auto const *
data = std::get_if<DataRef>(&term.data)) {
298 if (!isDefinedIn(
data->name,
"data")) {
299 error(path +
".data",
"dataset '" +
data->name +
"' cannot be found!");
303 for (
auto const &aux : likelihood.auxDistributions) {
304 if (!isDefinedIn(aux.name,
"distributions") && !isExternal(aux.name)) {
305 strictError(path +
".aux_distributions",
"the distribution '" + aux.name +
"' does not exist");
311void Validator::checkAnalyses()
313 for (std::size_t i = 0; i <
_doc.analyses.size(); ++i) {
314 auto const &analysis =
_doc.analyses[i];
315 const std::string path = at(
"analyses", i, analysis.name);
316 if (!isDefinedIn(analysis.likelihood.name,
"likelihoods")) {
317 error(path +
".likelihood",
"likelihood node not found!");
319 for (
auto const &poi : analysis.parametersOfInterest) {
320 if (
char const *section = sectionOf(poi.name)) {
321 error(path,
"the parameter of interest '" + poi.name +
"' of analysis '" + analysis.name +
322 "' is defined in \"" + section +
"\", but it must be a parameter");
323 }
else if (!
_parameters.count(poi.name) && !isExternal(poi.name)) {
324 error(path,
"the object '" + poi.name +
"' in \"parameters_of_interest\" of analysis '" + analysis.name +
328 for (
auto const &domain : analysis.domains) {
329 if (!isDefinedIn(domain.name,
"domains")) {
330 strictError(path +
".domains",
"the domain '" + domain.name +
"' does not exist");
333 if (analysis.init && !isDefinedIn(analysis.init->name,
"parameter_points")) {
334 warning(path,
"the parameter point '" + analysis.init->name +
"' used as \"init\" of analysis '" +
335 analysis.name +
"' does not exist.");
337 if (analysis.prior && !isDefinedIn(analysis.prior->name,
"distributions") && !isExternal(analysis.prior->name)) {
338 strictError(path +
".prior",
"the distribution '" + analysis.prior->name +
"' does not exist");
343std::size_t Validator::checkAxis(BinnedAxis
const &axis, std::string
const &path)
345 if (
auto const *regular = std::get_if<RegularBinning>(&axis.binning)) {
346 if (regular->nbins <= 0) {
347 error(path,
"the axis '" + axis.name +
"' needs a positive number of bins");
350 if (!(axis.min < axis.max)) {
351 error(path,
"the axis '" + axis.name +
"' needs \"min\" smaller than \"max\"");
353 return regular->nbins;
355 auto const &edges = std::get<EdgesBinning>(axis.binning).edges;
356 return edges.empty() ? 0 : edges.size() - 1;
359std::size_t Validator::checkAxis(CategoricalAxis
const &axis, std::string
const &path)
361 if (axis.categories.empty()) {
362 error(path,
"the axis '" + axis.name +
"' needs at least one category");
364 return axis.categories.size();
368std::optional<std::size_t> Validator::numBins(Axes
const &axes, std::string
const &path)
370 std::size_t nBins = 1;
372 for (std::size_t i = 0; i < axes.size(); ++i) {
373 const std::size_t
n = checkAxis(axes[i], path +
".axes[" + std::to_string(i) +
"]");
376 }
else if (nBins > std::numeric_limits<std::size_t>::max() /
n) {
377 error(path +
".axes",
"the axes have more bins than can be stored");
382 return valid ? std::optional<std::size_t>{nBins} : std::nullopt;
385void Validator::checkData()
387 for (std::size_t i = 0; i <
_doc.data.size(); ++i) {
389 auto const *
binned = std::get_if<BinnedData>(&
data.body);
393 const std::string path = at(
"data", i,
data.name);
394 std::optional<std::size_t> nBinsOrNone = numBins(
binned->axes, path);
398 const std::size_t nBins = *nBinsOrNone;
399 if (
binned->contents.size() != nBins) {
400 error(path,
"inconsistent bin numbers: contents=" + std::to_string(
binned->contents.size()) +
401 ", bins=" + std::to_string(nBins));
403 if (
binned->uncertainty) {
406 error(path,
"inconsistent number of errors and contents in '" +
data.name +
407 "': errors=" + std::to_string(
sigma.size()) +
408 ", contents=" + std::to_string(
binned->contents.size()));
410 auto const &correlation =
binned->uncertainty->correlation;
411 if (!correlation.empty()) {
412 bool square = correlation.size() ==
binned->contents.size();
413 for (
auto const &row : correlation) {
414 square = square && row.size() ==
binned->contents.size();
417 error(path +
".uncertainty.correlation",
"must be a square matrix of the size of \"contents\"");
424void Validator::checkHistFactory(HistFactoryDist
const &dist, std::string
const &
name, std::string
const &path)
426 if (
dist.axes.empty()) {
427 error(path +
".axes",
"a HistFactory distribution needs at least one axis");
430 std::optional<std::size_t> nBinsOrNone = numBins(
dist.axes, path);
434 const std::size_t nBins = *nBinsOrNone;
441 auto checkSize = [&](std::string
const &where, std::size_t
size,
char const *
what) {
443 error(where, std::string(
what) +
" has " + std::to_string(
size) +
" entries, but the channel has " +
444 std::to_string(nBins) +
" bins");
449 std::map<std::string, std::pair<Constraint, std::string>> constraintOfParameter;
450 auto checkCorrelated = [&](std::string
const &where, std::string
const ¶meter,
Constraint const &constraint) {
451 auto inserted = constraintOfParameter.emplace(parameter, std::make_pair(constraint, where));
452 if (inserted.second) {
455 if (!(inserted.first->second.first == constraint)) {
456 warning(where,
"the parameter '" + parameter +
"' has a different constraint than in " +
457 inserted.first->second.second +
458 ", but modifiers with the same parameter must share the constraint");
462 auto checkConstraint = [&](std::string
const &where,
Constraint const &constraint) {
463 if (
auto const *pdf = std::get_if<ConstraintPdf>(&constraint)) {
464 requireDistribution(pdf->pdf.name, where,
"constraint");
469 auto checkGammaConstraint = [&](std::string
const &where,
GammaConstraint const &constraint) {
470 auto const *pdfs = std::get_if<GammaConstraintPdfs>(&constraint);
474 checkSize(where +
".constraints", pdfs->pdfs.size(),
"\"constraints\"");
475 for (
auto const &pdf : pdfs->pdfs) {
477 requireDistribution(pdf->name, where +
".constraints",
"constraint");
482 auto checkParameter = [&](std::string
const &where, std::string
const ¶meter, std::string
const &
modifier) {
483 if (
char const *section = sectionOf(parameter)) {
484 error(where,
"the parameter '" + parameter +
"' of the modifier '" +
modifier +
"' is defined in \"" +
485 section +
"\", but it must be a parameter");
489 checkGammaConstraint(path +
".staterror",
dist.statError.constraint);
491 std::set<std::string> sampleNames;
492 for (std::size_t iSample = 0; iSample <
dist.samples.size(); ++iSample) {
493 auto const &sample =
dist.samples[iSample];
494 const std::string samplePath = path +
".samples[" + std::to_string(iSample) +
"](" + sample.name +
")";
495 if (!sampleNames.insert(sample.name).second) {
496 error(samplePath,
"the name of the sample is not unique in the channel");
498 checkSize(samplePath +
".data.contents", sample.contents.size(),
"\"contents\"");
500 std::set<std::string> objectNames;
501 auto checkUniqueName = [&](std::string
const &where,
char const *
type, std::string
const &modifierName) {
502 if (!objectNames.insert(std::string(
type) +
" " + modifierName).second) {
504 std::string(
"there is another ") +
type +
" modifier named '" + modifierName +
"' in the sample");
507 if (sample.errors && sample.errors->size() != sample.contents.size()) {
508 error(samplePath +
".data",
"inconsistent number of errors and contents in '" + sample.name +
509 "': errors=" + std::to_string(sample.errors->size()) +
510 ", contents=" + std::to_string(sample.contents.size()));
512 for (std::size_t iModifier = 0; iModifier < sample.modifiers.size(); ++iModifier) {
513 std::string modifierPath = samplePath +
".modifiers[" + std::to_string(iModifier) +
"]";
514 auto checkInterpolation = [&](std::optional<Interpolation>
const &interpolation,
char const *
type,
515 std::string
const &modifierName) {
516 if (!interpolation && !
dist.defaultInterpolation) {
517 error(modifierPath, std::string(
"interpolation for ") +
type +
" modifier '" + modifierName +
518 "' in sample '" + sample.name +
"' of channel '" +
channelName +
519 "' is not given, and the channel has no \"default_interpolation\"");
522 std::visit(overloaded{
523 [&](NormFactor
const &
m) {
524 modifierPath +=
"(" +
m.name +
")";
525 checkParameter(modifierPath,
m.parameter.name,
m.name);
526 checkConstraint(modifierPath,
m.constraint);
527 checkCorrelated(modifierPath,
m.parameter.name,
m.constraint);
529 [&](NormSys
const &
m) {
530 modifierPath +=
"(" +
m.name +
")";
531 checkParameter(modifierPath,
m.parameter.name,
m.name);
532 checkConstraint(modifierPath,
m.constraint);
533 checkCorrelated(modifierPath,
m.parameter.name,
m.constraint);
534 checkInterpolation(
m.interpolation,
"normsys",
m.name);
536 [&](HistoSys
const &
m) {
537 modifierPath +=
"(" +
m.name +
")";
538 checkUniqueName(modifierPath,
"histosys",
m.name);
539 checkParameter(modifierPath,
m.parameter.name,
m.name);
540 checkConstraint(modifierPath,
m.constraint);
541 checkCorrelated(modifierPath,
m.parameter.name,
m.constraint);
542 checkInterpolation(
m.interpolation,
"histosys",
m.name);
543 checkSize(modifierPath +
".data.hi.contents",
m.hi.contents.size(),
"\"contents\"");
544 checkSize(modifierPath +
".data.lo.contents",
m.lo.contents.size(),
"\"contents\"");
546 [&](ShapeSys
const &
m) {
547 modifierPath +=
"(" +
m.name +
")";
548 checkUniqueName(modifierPath,
"shapesys",
m.name);
549 if (!
m.vals.empty()) {
550 checkSize(modifierPath +
".data.vals",
m.vals.size(),
"\"vals\"");
552 if (!
m.parameters.empty()) {
553 checkSize(modifierPath +
".parameters",
m.parameters.size(),
"\"parameters\"");
555 for (
auto const &
p :
m.parameters) {
556 checkParameter(modifierPath,
p.name,
m.name);
558 checkGammaConstraint(modifierPath,
m.constraint);
560 [&](StatError
const &
m) {
561 modifierPath +=
"(" +
m.name +
")";
562 if (!
m.parameters.empty()) {
563 checkSize(modifierPath +
".parameters",
m.parameters.size(),
"\"parameters\"");
565 checkGammaConstraint(modifierPath,
m.constraint);
567 [&](CustomModifier
const &
m) {
568 modifierPath +=
"(" +
m.name +
")";
569 if (!isDefinedIn(
m.name,
"functions") && !isDefinedIn(
m.name,
"distributions") &&
570 !isExternal(
m.name)) {
571 error(modifierPath,
"unable to find custom modifier '" +
m.name +
"'");
575 sample.modifiers[iModifier]);
584 Validator{doc, diagnostics, options}.run();
601 options.
isExternal = [](std::string
const &) {
return true; };
602 Validator{empty, diagnostics, options}.checkHistFactory(dist,
name,
"");
ValidateOptions const & _options
Diagnostics & _diagnostics
std::map< std::string, std::vector< char const * > > _sections
The sections where a name is defined.
std::set< std::string > _parameters
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 index
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
An HS3 document. It is move-only, because generic objects refer to the JSON tree that the document wa...
A document that passed validate().
double dist(Rotation3D const &r1, Rotation3D const &r2)
Types that represent an HS3 document.
void validateHistFactoryDist(HistFactoryDist const &dist, std::string const &name, Diagnostics &diagnostics)
Same as validate(), but for a single HistFactory distribution that doesn't come with a document.
void checkDocument(HS3Document const &doc, Diagnostics &diagnostics, ValidateOptions const &options={})
The checks of validate(), for a document that is still needed if it doesn't pass them.
std::optional< ValidatedHS3Document > validate(HS3Document doc, Diagnostics &diagnostics, ValidateOptions const &options)
Checks what can be checked without knowing how the objects are built: unique names,...
bool hasErrors(Diagnostics const &diagnostics)
Whether there is a diagnostic with error severity.
std::vector< Diagnostic > Diagnostics
std::variant< Unconstrained, ConstraintType, ConstantGammas, GammaConstraintPdfs > GammaConstraint
std::variant< Unconstrained, ConstraintType, ConstraintPdf > Constraint
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
std::function< bool(std::string const &) isExternal)
Whether a name that is not defined in the document is known in some other way, e.g....