Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RooEvaluatorWrapper.cxx
Go to the documentation of this file.
1/// \cond ROOFIT_INTERNAL
2
3/*
4 * Project: RooFit
5 * Authors:
6 * Jonas Rembser, CERN 2023
7 *
8 * Copyright (c) 2023, CERN
9 *
10 * Redistribution and use in source and binary forms,
11 * with or without modification, are permitted according to the terms
12 * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
13 */
14
15/**
16\internal
17\file RooEvaluatorWrapper.cxx
18\class RooEvaluatorWrapper
19\ingroup Roofitcore
20
21Wraps a RooFit::Evaluator that evaluates a RooAbsReal back into a RooAbsReal.
22**/
23
24#include "RooEvaluatorWrapper.h"
25
26#include <RooAbsData.h>
27#include <RooAbsPdf.h>
28#include <RooMsgService.h>
29#include <RooRealVar.h>
30#include <RooSimultaneous.h>
31
33#include "RooFitImplHelpers.h"
34
35#include <TInterpreter.h>
36
37#include <fstream>
38
39namespace {
40
41// Throws an exception if any value in `span` is outside of the (default,
42// unnamed) range of `var`. The `obsName` is used only for the error message,
43// because it might differ from `var.GetName()` (e.g. the per-channel prefix
44// that RooSimultaneous adds is stripped for readability).
45void checkObservableSpanInRange(RooRealVar const &var, std::string const &obsName, std::string const &datasetName,
46 std::span<const double> span)
47{
48 for (double val : span) {
49 if (!var.inRange(val, nullptr)) {
50 const double lo = var.getMin();
51 const double hi = var.getMax();
52 std::stringstream errMsg;
53 errMsg << "RooAbsPdf::fitTo/createNLL: cannot evaluate the likelihood because dataset \"" << datasetName
54 << "\" has an entry for observable \"" << obsName << "\" with value " << val
55 << ", which is outside of its range [" << lo << ", " << hi << "]. The probability density is "
56 << "normalized over exactly that range, so events outside of it would silently bias the fit. If "
57 << "you want to fit only a subset of the data, define a named range and use it in the fit, for example:\n"
58 << " " << obsName << ".setRange(\"fitRange\", " << lo << ", " << hi << ");\n"
59 << " pdf.fitTo(data, RooFit::Range(\"fitRange\"));\n"
60 << "This way, only the events inside \"fitRange\" enter the likelihood, consistent with how the "
61 << "pdf is normalized.";
62 oocoutE(nullptr, InputArguments) << errMsg.str() << std::endl;
63 throw std::runtime_error(errMsg.str());
64 }
65 }
66}
67
68// Validates that no dataset entry lies outside of the range of the
69// corresponding observable, for every real-valued observable of `pdf`. This
70// check is skipped when a range name was explicitly given to the fit,
71// because in that case out-of-range events are intentionally and
72// consistently dropped by RooFit::BatchModeDataHelpers::getDataSpans().
74 std::map<RooFit::Detail::DataKey, std::span<const double>> const &dataSpans)
75{
76 if (!pdf)
77 return;
78
79 if (auto const *simPdf = dynamic_cast<RooSimultaneous const *>(pdf)) {
80 // The per-channel pdfs coming out of RooSimultaneous::compileForNormSet()
81 // have their observables cloned and renamed with a "_<channel>_" prefix
82 // (and tagged with the "__obs__" attribute), so that the shared data map
83 // can hold independent columns for each channel. We look those up the
84 // same way, and strip the prefix again for a readable error message.
85 for (auto const &nameIdx : simPdf->indexCat()) {
86 RooAbsPdf *channelPdf = simPdf->getPdf(nameIdx.first);
87 if (!channelPdf)
88 continue;
89 const std::string prefix = "_" + nameIdx.first + "_";
90 std::unique_ptr<RooArgSet> vars{channelPdf->getVariables()};
91 std::unique_ptr<RooArgSet> obs{vars->selectByAttrib("__obs__", true)};
92 for (RooAbsArg *arg : *obs) {
93 auto *realVar = dynamic_cast<RooRealVar *>(arg);
94 if (!realVar)
95 continue;
97 if (it == dataSpans.end())
98 continue;
99 std::string obsName = realVar->GetName();
100 if (obsName.rfind(prefix, 0) == 0) {
101 obsName = obsName.substr(prefix.size());
102 }
103 checkObservableSpanInRange(*realVar, obsName, data.GetName(), it->second);
104 }
105 }
106 return;
107 }
108
109 RooArgSet obs;
110 pdf->getObservables(data.get(), obs);
111 for (RooAbsArg *arg : obs) {
112 auto *realVar = dynamic_cast<RooRealVar *>(arg);
113 if (!realVar)
114 continue;
116 if (it == dataSpans.end())
117 continue;
118 checkObservableSpanInRange(*realVar, realVar->GetName(), data.GetName(), it->second);
119 }
120}
121
122} // namespace
123
124namespace RooFit::Experimental {
125
126RooEvaluatorWrapper::RooEvaluatorWrapper(RooAbsReal &topNode, RooAbsData *data, bool useGPU,
127 std::string const &rangeName, RooAbsPdf const *pdf,
129 : RooAbsReal{"RooEvaluatorWrapper", "RooEvaluatorWrapper"},
130 _evaluator{std::make_unique<RooFit::Evaluator>(topNode, useGPU)},
131 _topNode("topNode", "top node", this, topNode, false, false),
132 _data{data},
133 _paramSet("paramSet", "Set of parameters", this),
134 _rangeName{rangeName},
135 _pdf{pdf},
136 _takeGlobalObservablesFromData{takeGlobalObservablesFromData}
137{
138 if (data) {
139 setData(*data, false);
140 }
141 _paramSet.add(_evaluator->getParameters());
142 for (auto const &item : _dataSpans) {
143 _paramSet.remove(*_paramSet.find(item.first->GetName()));
144 }
145}
146
147RooEvaluatorWrapper::RooEvaluatorWrapper(const RooEvaluatorWrapper &other, const char *name)
149 _evaluator{other._evaluator},
150 _topNode("topNode", this, other._topNode),
151 _data{other._data},
152 _paramSet("paramSet", "Set of parameters", this),
153 _rangeName{other._rangeName},
154 _pdf{other._pdf},
155 _takeGlobalObservablesFromData{other._takeGlobalObservablesFromData},
157{
158 _paramSet.add(other._paramSet);
159}
160
161RooEvaluatorWrapper::~RooEvaluatorWrapper() = default;
162
163bool RooEvaluatorWrapper::getParameters(const RooArgSet *observables, RooArgSet &outputSet,
164 bool stripDisconnected) const
165{
166 outputSet.add(_evaluator->getParameters());
167 if (observables) {
168 outputSet.remove(*observables, /*silent*/ false, /*matchByNameOnly*/ true);
169 }
170 // Exclude the data variables from the parameters which are not global observables
171 for (auto const &item : _dataSpans) {
172 if (_data->getGlobalObservables() && _data->getGlobalObservables()->find(item.first->GetName())) {
173 continue;
174 }
175 RooAbsArg *found = outputSet.find(item.first->GetName());
176 if (found) {
177 outputSet.remove(*found);
178 }
179 }
180 // If we take the global observables as data, we have to return these as
181 // parameters instead of the parameters in the model. Otherwise, the
182 // constant parameters in the fit result that are global observables will
183 // not have the right values.
184 if (_takeGlobalObservablesFromData && _data->getGlobalObservables()) {
185 outputSet.replace(*_data->getGlobalObservables());
186 }
187
188 // The disconnected parameters are stripped away in
189 // RooAbsArg::getParametersHook(), that is only called in the original
190 // RooAbsArg::getParameters() implementation. So he have to call it to
191 // identify disconnected parameters to remove.
192 if (stripDisconnected) {
194 _topNode->getParameters(observables, paramsStripped, true);
196 for (RooAbsArg *param : outputSet) {
197 if (!paramsStripped.find(param->GetName())) {
198 toRemove.add(*param);
199 }
200 }
201 outputSet.remove(toRemove, /*silent*/ false, /*matchByNameOnly*/ true);
202 }
203
204 return false;
205}
206
207/// @brief A wrapper class to store a C++ function of type 'double (*)(double*, double*)'.
208/// The parameters can be accessed as params[<relative position of param in paramSet>] in the function body.
209/// The observables can be accessed as obs[i + j], where i represents the observable position and j
210/// represents the data entry.
211class RooFuncWrapper {
212public:
214 std::string const &rangeName, bool skipZeroWeights);
215
216 bool hasGradient() const { return _hasGradient; }
217 bool hasHessian() const { return _hasHessian; }
218 void gradient(double *out) const
219 {
221 std::fill(out, out + _params.size(), 0.0);
222 _grad(_varBuffer.data(), _observables.data(), _xlArr.data(), out);
223 }
224 void hessian(double *out) const
225 {
227 std::fill(out, out + _params.size() * _params.size(), 0.0);
228 _hessian(_varBuffer.data(), _observables.data(), _xlArr.data(), out);
229 }
230
231 void createGradient();
232 void createHessian();
233
234 void writeDebugMacro(std::string const &) const;
235
236 std::vector<std::string> const &collectedFunctions() { return _collectedFunctions; }
237
238 double evaluate() const
239 {
241 return _func(_varBuffer.data(), _observables.data(), _xlArr.data());
242 }
243
244 void
245 loadData(RooAbsData const &data, RooSimultaneous const *simPdf, std::string const &rangeName, bool skipZeroWeights);
246
247private:
248 void updateGradientVarBuffer() const;
249
251
252 using Func = double (*)(double *, double const *, double const *);
253 using Grad = void (*)(double *, double const *, double const *, double *);
254 using Hessian = void (*)(double *, double const *, double const *, double *);
255
256 RooArgList _params;
257 std::string _funcName;
258 Func _func;
259 Grad _grad;
260 Hessian _hessian;
261 bool _hasGradient = false;
262 bool _hasHessian = false;
263 mutable std::vector<double> _varBuffer;
264 std::vector<double> _observables;
265 std::unordered_map<RooFit::Detail::DataKey, std::size_t> _obsInfos;
266 std::vector<double> _xlArr;
267 std::vector<std::string> _collectedFunctions;
268};
269
270namespace {
271
272void replaceAll(std::string &str, const std::string &from, const std::string &to)
273{
274 if (from.empty())
275 return;
276 size_t start_pos = 0;
277 while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
278 str.replace(start_pos, from.length(), to);
279 start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
280 }
281}
282
284{
287
288 std::unordered_set<RooFit::Detail::DataKey> dependsOnData;
289 for (RooAbsArg *arg : dataObs) {
290 dependsOnData.insert(arg);
291 }
292
293 for (RooAbsArg *arg : serverSet) {
294 if (arg->getAttribute("__obs__")) {
295 dependsOnData.insert(arg);
296 }
297 for (RooAbsArg *server : arg->servers()) {
298 if (server->isValueServer(*arg)) {
299 if (dependsOnData.find(server) != dependsOnData.end() && !arg->isReducerNode()) {
300 dependsOnData.insert(arg);
301 break;
302 }
303 }
304 }
305 }
306
307 return dependsOnData;
308}
309
310} // namespace
311
312RooFuncWrapper::RooFuncWrapper(RooAbsReal &obj, const RooAbsData *data, RooSimultaneous const *simPdf,
313 RooArgSet const &paramSet, std::string const &rangeName, bool skipZeroWeights)
314{
315 // Load the observables from the dataset
316 if (data) {
318 }
319
320 // Define the parameters
321 for (auto *param : paramSet) {
322 if (_obsInfos.find(param) == _obsInfos.end()) {
323 _params.add(*param);
324 }
325 }
326 _varBuffer.resize(_params.size());
327
328 // Figure out which part of the computation graph depends on data
329 std::unordered_set<RooFit::Detail::DataKey> dependsOnData;
330 if (data) {
331 dependsOnData = getDependsOnData(obj, *data->get());
332 }
333
334 // Set up the code generation context
336
337 // First update the result variable of params in the compute graph to in[<position>].
338 int idx = 0;
339 for (RooAbsArg *param : _params) {
340 ctx.addResult(param, "params[" + std::to_string(idx) + "]");
341 idx++;
342 }
343
344 for (auto const &item : _obsInfos) {
345 const char *obsName = item.first->GetName();
346 ctx.addResult(obsName, "obs");
347 ctx.addVecObs(obsName, item.second);
348 }
349
350 // Declare the function and create its derivative.
351 auto print = [](std::string const &msg) { oocoutI(nullptr, Fitting) << msg << std::endl; };
352 ROOT::Math::Util::TimingScope timingScope(print, "Function JIT time:");
353 _funcName = ctx.buildFunction(obj, dependsOnData);
354
355 // Make sure the codegen implementations are known to the interpreter
356 gInterpreter->Declare("#include <RooFit/CodegenImpl.h>\n");
357
358 if (!gInterpreter->Declare(ctx.collectedCode().c_str())) {
359 std::stringstream errorMsg;
360 std::string debugFileName = "_codegen_" + _funcName + ".cxx";
361 errorMsg << "Function " << _funcName << " could not be compiled. See above for details. Full code dumped to file "
362 << debugFileName << " for debugging";
363 {
364 std::ofstream outFile;
365 outFile.open(debugFileName.c_str());
366 outFile << ctx.collectedCode();
367 }
368 oocoutE(nullptr, InputArguments) << errorMsg.str() << std::endl;
369 throw std::runtime_error(errorMsg.str().c_str());
370 }
371
372 _func = reinterpret_cast<Func>(gInterpreter->ProcessLine((_funcName + ";").c_str()));
373
374 _xlArr = ctx.xlArr();
375 _collectedFunctions = ctx.collectedFunctions();
376}
377
378void RooFuncWrapper::loadData(RooAbsData const &data, RooSimultaneous const *simPdf, std::string const &rangeName,
379 bool skipZeroWeights)
380{
381 // Extract observables
382 std::stack<std::vector<double>> vectorBuffers; // for data loading
383 auto spans =
384 RooFit::BatchModeDataHelpers::getDataSpans(data, rangeName, simPdf, skipZeroWeights, false, vectorBuffers);
385
386 _observables.clear();
387 // The first elements contain the sizes of the packed observable arrays
388 std::size_t total = 0;
389 _observables.reserve(2 * spans.size());
390 std::size_t idx = 0;
391 for (auto const &item : spans) {
392 _obsInfos.emplace(item.first, idx);
393 _observables.push_back(total + 2 * spans.size());
394 _observables.push_back(item.second.size());
395 total += item.second.size();
396 idx += 1;
397 }
398 idx = 0;
399 for (auto const &item : spans) {
400 std::size_t n = item.second.size();
401 _observables.reserve(_observables.size() + n);
402 for (std::size_t i = 0; i < n; ++i) {
403 _observables.push_back(item.second[i]);
404 }
405 idx += n;
406 }
407}
408
409void RooFuncWrapper::createGradient()
410{
411#ifdef ROOFIT_CLAD
412 std::string gradName = _funcName + "_grad_0";
413 std::string requestName = _funcName + "_req";
414
415 // Calculate gradient
416 gInterpreter->Declare("#include <Math/CladDerivator.h>\n");
417 // disable clang-format for making the following code unreadable.
418 // clang-format off
419 std::stringstream requestFuncStrm;
420 requestFuncStrm << "#pragma clad ON\n"
421 "void " << requestName << "() {\n"
422 " clad::gradient(" << _funcName << ", \"params\");\n"
423 "}\n"
424 "#pragma clad OFF";
425 // clang-format on
426 auto print = [](std::string const &msg) { oocoutI(nullptr, Fitting) << msg << std::endl; };
427
428 bool cladSuccess = false;
429 {
430 ROOT::Math::Util::TimingScope timingScope(print, "Gradient generation time:");
431 cladSuccess = !gInterpreter->Declare(requestFuncStrm.str().c_str());
432 }
433 if (cladSuccess) {
434 std::stringstream errorMsg;
435 errorMsg << "Function could not be differentiated. See above for details.";
436 oocoutE(nullptr, InputArguments) << errorMsg.str() << std::endl;
437 throw std::runtime_error(errorMsg.str().c_str());
438 }
439
440 // Clad provides different overloads for the gradient, and we need to
441 // resolve to the one that we want. Without the static_cast, getting the
442 // function pointer would be ambiguous.
443 std::stringstream ss;
444 ROOT::Math::Util::TimingScope timingScope(print, "Gradient IR to machine code time:");
445 ss << "static_cast<void (*)(double *, double const *, double const *, double *)>(" << gradName << ");";
446 _grad = reinterpret_cast<Grad>(gInterpreter->ProcessLine(ss.str().c_str()));
447 _hasGradient = true;
448#else
449 _hasGradient = false;
450 std::stringstream errorMsg;
451 errorMsg << "Function could not be differentiated since ROOT was built without Clad support.";
452 oocoutE(nullptr, InputArguments) << errorMsg.str() << std::endl;
453 throw std::runtime_error(errorMsg.str().c_str());
454#endif
455}
456
457void RooFuncWrapper::createHessian()
458{
459#ifdef ROOFIT_CLAD
460 std::string hessianName = _funcName + "_hessian_0";
461 std::string requestName = _funcName + "_hessian_req";
462
463 // Calculate Hessian
464 gInterpreter->Declare("#include <Math/CladDerivator.h>\n");
465 // disable clang-format for making the following code unreadable.
466 // clang-format off
467 std::stringstream requestFuncStrm;
468 std::string paramsStr =
469 _params.size() == 1 ? "\"params[0]\"" : ("\"params[0:" + std::to_string(_params.size() - 1) + "]\"");
470 requestFuncStrm << "#pragma clad ON\n"
471 "void " << requestName << "() {\n"
472 " clad::hessian(" << _funcName << ", " << paramsStr << ");\n"
473 "}\n"
474 "#pragma clad OFF";
475 // clang-format on
476 auto print = [](std::string const &msg) { oocoutI(nullptr, Fitting) << msg << std::endl; };
477
478 bool cladSuccess = false;
479 {
480 ROOT::Math::Util::TimingScope timingScope(print, "Hessian generation time:");
481 cladSuccess = !gInterpreter->Declare(requestFuncStrm.str().c_str());
482 }
483 if (cladSuccess) {
484 std::stringstream errorMsg;
485 errorMsg << "Function could not be differentiated. See above for details.";
486 oocoutE(nullptr, InputArguments) << errorMsg.str() << std::endl;
487 throw std::runtime_error(errorMsg.str().c_str());
488 }
489
490 // Clad provides different overloads for the Hessian, and we need to
491 // resolve to the one that we want. Without the static_cast, getting the
492 // function pointer would be ambiguous.
493 std::stringstream ss;
494 ROOT::Math::Util::TimingScope timingScope(print, "Hessian IR to machine code time:");
495 ss << "static_cast<void (*)(double *, double const *, double const *, double *)>(" << hessianName << ");";
496 _hessian = reinterpret_cast<Hessian>(gInterpreter->ProcessLine(ss.str().c_str()));
497 _hasHessian = true;
498#else
499 _hasHessian = false;
500 std::stringstream errorMsg;
501 errorMsg << "Function could not be differentiated since ROOT was built without Clad support.";
502 oocoutE(nullptr, InputArguments) << errorMsg.str() << std::endl;
503 throw std::runtime_error(errorMsg.str().c_str());
504#endif
505}
506
507void RooFuncWrapper::updateGradientVarBuffer() const
508{
509 std::transform(_params.begin(), _params.end(), _varBuffer.begin(), [](RooAbsArg *obj) {
510 return obj->isCategory() ? static_cast<RooAbsCategory *>(obj)->getCurrentIndex()
511 : static_cast<RooAbsReal *>(obj)->getVal();
512 });
513}
514
515/// @brief Dumps a macro "filename.C" that can be used to test and debug the generated code and gradient.
516void RooFuncWrapper::writeDebugMacro(std::string const &filename) const
517{
518 std::stringstream allCode;
519 std::set<std::string> seenFunctions;
520
521 // Remove duplicated declared functions
522 for (std::string const &name : _collectedFunctions) {
523 if (seenFunctions.count(name) > 0) {
524 continue;
525 }
526 seenFunctions.insert(name);
527 std::unique_ptr<TInterpreterValue> v = gInterpreter->MakeInterpreterValue();
528 gInterpreter->Evaluate(name.c_str(), *v);
529 std::string s = v->ToString();
530 for (int i = 0; i < 2; ++i) {
531 s = s.erase(0, s.find("\n") + 1);
532 }
533 allCode << s << std::endl;
534 }
535
536 std::ofstream outFile;
537 std::string paramsStr =
538 _params.size() == 1 ? "\"params[0]\"" : ("\"params[0:" + std::to_string(_params.size() - 1) + "]\"");
539 outFile.open(filename + ".C");
540 outFile << R"(//auto-generated test macro
541#include <RooFit/Detail/MathFuncs.h>
542#include <Math/CladDerivator.h>
543
544//#define DO_HESSIAN
545
546)" << allCode.str()
547 << R"(
548#pragma clad ON
549void gradient_request() {
550 clad::gradient()"
551 << _funcName << R"(, "params");
552#ifdef DO_HESSIAN
553 clad::hessian()"
554 << _funcName << ", " << paramsStr << R"();
555#endif
556}
557#pragma clad OFF
558)";
559
561
562 auto writeVector = [&](std::string const &name, std::span<const double> vec) {
563 std::stringstream decl;
564 decl << "std::vector<double> " << name << " = {";
565 for (std::size_t i = 0; i < vec.size(); ++i) {
566 if (i % 10 == 0)
567 decl << "\n ";
568 decl << vec[i];
569 if (i < vec.size() - 1)
570 decl << ", ";
571 }
572 decl << "\n};\n";
573
574 std::string declStr = decl.str();
575
576 replaceAll(declStr, "inf", "std::numeric_limits<double>::infinity()");
577 replaceAll(declStr, "nan", "NAN");
578
579 outFile << declStr;
580 };
581
582 outFile << "// clang-format off\n" << std::endl;
583 writeVector("parametersVec", _varBuffer);
584 outFile << std::endl;
585 writeVector("observablesVec", _observables);
586 outFile << std::endl;
587 writeVector("auxConstantsVec", _xlArr);
588 outFile << std::endl;
589 outFile << "// clang-format on\n" << std::endl;
590
591 outFile << R"(
592// To run as a ROOT macro
593void )" << filename
594 << R"(()
595{
596 const std::size_t n = parametersVec.size();
597
598 std::vector<double> gradientVec(n);
599
600 auto func = [&](std::span<double> params) {
601 return )"
602 << _funcName << R"((params.data(), observablesVec.data(), auxConstantsVec.data());
603 };
604 auto grad = [&](std::span<double> params, std::span<double> out) {
605 return )"
606 << _funcName << R"(_grad_0(parametersVec.data(), observablesVec.data(), auxConstantsVec.data(),
607 out.data());
608 };
609
610 grad(parametersVec, gradientVec);
611
612 auto numDiff = [&](int i) {
613 const double eps = 1e-6;
614 std::vector<double> p{parametersVec};
615 p[i] = parametersVec[i] - eps;
616 double funcValDown = func(p);
617 p[i] = parametersVec[i] + eps;
618 double funcValUp = func(p);
619 return (funcValUp - funcValDown) / (2 * eps);
620 };
621
622 for (std::size_t i = 0; i < parametersVec.size(); ++i) {
623 std::cout << i << ":" << std::endl;
624 std::cout << " numr : " << numDiff(i) << std::endl;
625 std::cout << " clad : " << gradientVec[i] << std::endl;
626 }
627
628#ifdef DO_HESSIAN
629 std::cout << "\n";
630
631 auto hess = [&](std::span<double> params, std::span<double> out) {
632 return )"
633 << _funcName << R"(_hessian_0(params.data(), observablesVec.data(), auxConstantsVec.data(), out.data());
634 };
635
636 std::vector<double> hessianVec(n * n);
637 hess(parametersVec, hessianVec);
638
639 // ---------- Numerical Hessian ----------
640 // Uses central differences:
641 // diag: (f(x+ei)-2f(x)+f(x-ei))/eps^2
642 // offdiag: (f(++ ) - f(+-) - f(-+) + f(--)) / (4 eps^2)
643 auto numHess = [&](std::size_t i, std::size_t j) {
644 const double eps = 1e-5; // often needs to be a bit larger than grad eps
645 std::vector<double> p(parametersVec.begin(), parametersVec.end());
646
647 if (i == j) {
648 const double f0 = func(p);
649
650 p[i] = parametersVec[i] + eps;
651 const double fUp = func(p);
652
653 p[i] = parametersVec[i] - eps;
654 const double fDown = func(p);
655
656 return (fUp - 2.0 * f0 + fDown) / (eps * eps);
657 } else {
658 // f(x_i + eps, x_j + eps)
659 p[i] = parametersVec[i] + eps;
660 p[j] = parametersVec[j] + eps;
661 const double fPP = func(p);
662
663 // f(x_i + eps, x_j - eps)
664 p[i] = parametersVec[i] + eps;
665 p[j] = parametersVec[j] - eps;
666 const double fPM = func(p);
667
668 // f(x_i - eps, x_j + eps)
669 p[i] = parametersVec[i] - eps;
670 p[j] = parametersVec[j] + eps;
671 const double fMP = func(p);
672
673 // f(x_i - eps, x_j - eps)
674 p[i] = parametersVec[i] - eps;
675 p[j] = parametersVec[j] - eps;
676 const double fMM = func(p);
677
678 return (fPP - fPM - fMP + fMM) / (4.0 * eps * eps);
679 }
680 };
681
682 // Compute full numerical Hessian
683 std::vector<double> numHessianVec(n * n);
684 for (std::size_t i = 0; i < n; ++i) {
685 for (std::size_t j = 0; j < n; ++j) {
686 numHessianVec[i + n * j] = numHess(i, j); // keep same layout as your print
687 }
688 }
689
690 // ---------- Compare & print ----------
691 std::cout << "Hessian comparison (clad vs numeric vs diff):\n\n";
692
693 for (std::size_t i = 0; i < n; ++i) {
694 for (std::size_t j = 0; j < n; ++j) {
695 const std::size_t idx = i + n * j; // same indexing you used
696 const double cladH = hessianVec[idx];
697 const double numH = numHessianVec[idx];
698 const double diff = cladH - numH;
699
700 std::cout << "[" << i << "," << j << "] "
701 << "clad=" << cladH << " num=" << numH << " diff=" << diff << "\n";
702 }
703 }
704
705 std::cout << "\nRaw Clad Hessian matrix:\n";
706 for (std::size_t i = 0; i < n; ++i) {
707 for (std::size_t j = 0; j < n; ++j) {
708 std::cout << hessianVec[i + n * j] << " ";
709 }
710 std::cout << "\n";
711 }
712
713 std::cout << "\nRaw Numerical Hessian matrix:\n";
714 for (std::size_t i = 0; i < n; ++i) {
715 for (std::size_t j = 0; j < n; ++j) {
716 std::cout << numHessianVec[i + n * j] << " ";
717 }
718 std::cout << "\n";
719 }
720#endif
721}
722)";
723}
724
725double RooEvaluatorWrapper::evaluate() const
726{
728 return _funcWrapper->evaluate();
729
730 if (!_evaluator)
731 return 0.0;
732
733 _evaluator->setOffsetMode(hideOffset() ? RooFit::EvalContext::OffsetMode::WithoutOffset
734 : RooFit::EvalContext::OffsetMode::WithOffset);
735
736 return _evaluator->run()[0];
737}
738
739bool RooEvaluatorWrapper::setData(RooAbsData &data, bool /*cloneData*/)
740{
741 // To make things easier for RooFit, we only support resetting with
742 // datasets that have the same structure, e.g. the same columns and global
743 // observables. This is anyway the usecase: resetting same-structured data
744 // when iterating over toys.
745 constexpr auto errMsg = "Error in RooAbsReal::setData(): only resetting with same-structured data is supported.";
746
747 _data = &data;
748 bool isInitializing = _paramSet.empty();
749 const std::size_t oldSize = _dataSpans.size();
750
751 std::stack<std::vector<double>>{}.swap(_vectorBuffers);
752 const bool isChi2 = _topNode->getAttribute("Chi2EvaluationActive");
753 bool skipZeroWeights = !isChi2 && (!_pdf || !_pdf->getAttribute("BinnedLikelihoodActive"));
754 auto simPdf = dynamic_cast<RooSimultaneous const *>(_pdf);
755 _dataSpans = RooFit::BatchModeDataHelpers::getDataSpans(*_data, _rangeName, simPdf, skipZeroWeights,
756 _takeGlobalObservablesFromData, _vectorBuffers);
757 if (_rangeName.empty()) {
759 }
760 if (!isInitializing && _dataSpans.size() != oldSize) {
761 coutE(DataHandling) << errMsg << std::endl;
762 throw std::runtime_error(errMsg);
763 }
764 for (auto const &item : _dataSpans) {
765 const char *name = item.first->GetName();
766 _evaluator->setInput(name, item.second, false);
767 if (_paramSet.find(name)) {
768 coutE(DataHandling) << errMsg << std::endl;
769 throw std::runtime_error(errMsg);
770 }
771 }
772 if (_funcWrapper) {
773 _funcWrapper->loadData(*_data, simPdf, _rangeName, skipZeroWeights);
774 }
775 return true;
776}
777
778void RooEvaluatorWrapper::createFuncWrapper()
779{
780 // Get the parameters.
782 this->getParameters(_data ? _data->get() : nullptr, paramSet, /*sripDisconnectedParams=*/false);
783
784 const bool isChi2 = _topNode->getAttribute("Chi2EvaluationActive");
785 const bool skipZeroWeights = !isChi2 && (!_pdf || !_pdf->getAttribute("BinnedLikelihoodActive"));
786 _funcWrapper = std::make_unique<RooFuncWrapper>(*_topNode, _data, dynamic_cast<RooSimultaneous const *>(_pdf),
787 paramSet, _rangeName, skipZeroWeights);
788}
789
790void RooEvaluatorWrapper::generateGradient()
791{
792 if (!_funcWrapper)
794 if (!_funcWrapper->hasGradient())
795 _funcWrapper->createGradient();
796}
797
798void RooEvaluatorWrapper::generateHessian()
799{
800 if (!_funcWrapper)
802 if (!_funcWrapper->hasHessian())
803 _funcWrapper->createHessian();
804}
805
806void RooEvaluatorWrapper::setUseGeneratedFunctionCode(bool flag)
807{
811}
812
813void RooEvaluatorWrapper::gradient(double *out) const
814{
815 _funcWrapper->gradient(out);
816}
817
818void RooEvaluatorWrapper::hessian(double *out) const
819{
820 _funcWrapper->hessian(out);
821}
822
823bool RooEvaluatorWrapper::hasGradient() const
824{
825 return _funcWrapper && _funcWrapper->hasGradient();
826}
827
828bool RooEvaluatorWrapper::hasHessian() const
829{
830 return _funcWrapper && _funcWrapper->hasHessian();
831}
832
833void RooEvaluatorWrapper::writeDebugMacro(std::string const &filename) const
834{
835 if (_funcWrapper)
836 return _funcWrapper->writeDebugMacro(filename);
837}
838
839std::unique_ptr<ChangeOperModeRAII> RooEvaluatorWrapper::setOperModes(RooAbsArg::OperMode opMode)
840{
841 return _evaluator->setOperModes(opMode);
842}
843
844} // namespace RooFit::Experimental
845
846/// \endcond
#define oocoutE(o, a)
#define oocoutI(o, a)
#define coutE(a)
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
static unsigned int total
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 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 filename
char name[80]
Definition TGX11.cxx:148
#define hi
#define gInterpreter
const_iterator begin() const
const_iterator end() const
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:76
RooFit::OwningPtr< RooArgSet > getObservables(const RooArgSet &set, bool valueOnly=true) const
Given a set of possible observables, return the observables that this PDF depends on.
Abstract base class for binned and unbinned datasets.
Definition RooAbsData.h:56
Abstract interface for all probability density functions.
Definition RooAbsPdf.h:32
virtual double getMax(const char *name=nullptr) const
Get maximum of currently defined range.
virtual double getMin(const char *name=nullptr) const
Get minimum of currently defined range.
bool inRange(const char *name) const override
Check if current value is inside range with given name.
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
RooArgList is a container object that can hold multiple RooAbsArg objects.
Definition RooArgList.h:22
RooArgSet is a container object that can hold multiple RooAbsArg objects.
Definition RooArgSet.h:24
A class to maintain the context for squashing of RooFit models into code.
void addResult(RooAbsArg const *key, std::string const &value)
A function to save an expression that includes/depends on the result of the input node.
void addVecObs(const char *key, int idx)
Since the squashed code represents all observables as a single flattened array, it is important to ke...
std::string buildFunction(RooAbsArg const &arg, std::unordered_set< RooFit::Detail::DataKey > const &dependsOnData={})
Assemble and return the final code with the return expression and global statements.
std::vector< std::string > const & collectedFunctions()
std::vector< double > const & xlArr()
Variable that can be changed from the outside.
Definition RooRealVar.h:37
Facilitates simultaneous fitting of multiple PDFs to subsets of a given dataset.
const Int_t n
Definition legend1.C:16
void replaceAll(std::string &inOut, std::string_view what, std::string_view with)
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
Definition CodegenImpl.h:72
void getSortedComputationGraph(RooAbsArg const &func, RooArgSet &out)
void evaluate(typename Architecture_t::Tensor_t &A, EActivationFunction f)
Apply the given activation function to each value in the given tensor A.
Definition Functions.h:98