@@ -128,15 +128,22 @@ class RooFuncWrapper {
128128 RooFuncWrapper (RooAbsReal &obj, const RooAbsData *data, RooSimultaneous const *simPdf, RooArgSet const ¶mSet);
129129
130130 bool hasGradient () const { return _hasGradient; }
131+ bool hasHessian () const { return _hasHessian; }
131132 void gradient (double *out) const
132133 {
133134 updateGradientVarBuffer ();
134135 std::fill (out, out + _params.size (), 0.0 );
135-
136- _grad (_gradientVarBuffer.data (), _observables.data (), _xlArr.data (), out);
136+ _grad (_varBuffer.data (), _observables.data (), _xlArr.data (), out);
137+ }
138+ void hessian (double *out) const
139+ {
140+ updateGradientVarBuffer ();
141+ std::fill (out, out + _params.size () * _params.size (), 0.0 );
142+ _hessian (_varBuffer.data (), _observables.data (), _xlArr.data (), out);
137143 }
138144
139145 void createGradient ();
146+ void createHessian ();
140147
141148 void writeDebugMacro (std::string const &) const ;
142149
@@ -145,7 +152,7 @@ class RooFuncWrapper {
145152 double evaluate () const
146153 {
147154 updateGradientVarBuffer ();
148- return _func (_gradientVarBuffer .data (), _observables.data (), _xlArr.data ());
155+ return _func (_varBuffer .data (), _observables.data (), _xlArr.data ());
149156 }
150157
151158 void loadData (RooAbsData const &data, RooSimultaneous const *simPdf);
@@ -157,13 +164,16 @@ class RooFuncWrapper {
157164
158165 using Func = double (*)(double *, double const *, double const *);
159166 using Grad = void (*)(double *, double const *, double const *, double *);
167+ using Hessian = void (*)(double *, double const *, double const *, double *);
160168
161169 RooArgList _params;
162170 std::string _funcName;
163171 Func _func;
164172 Grad _grad;
173+ Hessian _hessian;
165174 bool _hasGradient = false ;
166- mutable std::vector<double > _gradientVarBuffer;
175+ bool _hasHessian = false ;
176+ mutable std::vector<double > _varBuffer;
167177 std::vector<double > _observables;
168178 std::unordered_map<RooFit::Detail::DataKey, std::size_t > _obsInfos;
169179 std::vector<double > _xlArr;
@@ -226,7 +236,7 @@ RooFuncWrapper::RooFuncWrapper(RooAbsReal &obj, const RooAbsData *data, RooSimul
226236 _params.add (*param);
227237 }
228238 }
229- _gradientVarBuffer .resize (_params.size ());
239+ _varBuffer .resize (_params.size ());
230240
231241 // Figure out which part of the computation graph depends on data
232242 std::unordered_set<RooFit::Detail::DataKey> dependsOnData;
@@ -355,9 +365,59 @@ void RooFuncWrapper::createGradient()
355365#endif
356366}
357367
368+ void RooFuncWrapper::createHessian ()
369+ {
370+ #ifdef ROOFIT_CLAD
371+ std::string hessianName = _funcName + " _hessian_0" ;
372+ std::string requestName = _funcName + " _hessian_req" ;
373+
374+ // Calculate Hessian
375+ gInterpreter ->Declare (" #include <Math/CladDerivator.h>\n " );
376+ // disable clang-format for making the following code unreadable.
377+ // clang-format off
378+ std::stringstream requestFuncStrm;
379+ std::string paramsStr =
380+ _params.size () == 1 ? " \" params[0]\" " : (" \" params[0:" + std::to_string (_params.size () - 1 ) + " ]\" " );
381+ requestFuncStrm << " #pragma clad ON\n "
382+ " void " << requestName << " () {\n "
383+ " clad::hessian(" << _funcName << " , " << paramsStr << " );\n "
384+ " }\n "
385+ " #pragma clad OFF" ;
386+ // clang-format on
387+ auto print = [](std::string const &msg) { oocoutI (nullptr , Fitting) << msg << std::endl; };
388+
389+ bool cladSuccess = false ;
390+ {
391+ ROOT ::Math::Util::TimingScope timingScope (print, " Hessian generation time:" );
392+ cladSuccess = !gInterpreter ->Declare (requestFuncStrm.str ().c_str ());
393+ }
394+ if (cladSuccess) {
395+ std::stringstream errorMsg;
396+ errorMsg << " Function could not be differentiated. See above for details." ;
397+ oocoutE (nullptr , InputArguments) << errorMsg.str () << std::endl;
398+ throw std::runtime_error (errorMsg.str ().c_str ());
399+ }
400+
401+ // Clad provides different overloads for the Hessian, and we need to
402+ // resolve to the one that we want. Without the static_cast, getting the
403+ // function pointer would be ambiguous.
404+ std::stringstream ss;
405+ ROOT ::Math::Util::TimingScope timingScope (print, " Hessian IR to machine code time:" );
406+ ss << " static_cast<void (*)(double *, double const *, double const *, double *)>(" << hessianName << " );" ;
407+ _hessian = reinterpret_cast <Hessian>(gInterpreter ->ProcessLine (ss.str ().c_str ()));
408+ _hasHessian = true ;
409+ #else
410+ _hasHessian = false ;
411+ std::stringstream errorMsg;
412+ errorMsg << " Function could not be differentiated since ROOT was built without Clad support." ;
413+ oocoutE (nullptr , InputArguments) << errorMsg.str () << std::endl;
414+ throw std::runtime_error (errorMsg.str ().c_str ());
415+ #endif
416+ }
417+
358418void RooFuncWrapper::updateGradientVarBuffer () const
359419{
360- std::transform (_params.begin (), _params.end (), _gradientVarBuffer .begin (), [](RooAbsArg *obj) {
420+ std::transform (_params.begin (), _params.end (), _varBuffer .begin (), [](RooAbsArg *obj) {
361421 return obj->isCategory () ? static_cast <RooAbsCategory *>(obj)->getCurrentIndex ()
362422 : static_cast <RooAbsReal *>(obj)->getVal ();
363423 });
@@ -385,17 +445,25 @@ void RooFuncWrapper::writeDebugMacro(std::string const &filename) const
385445 }
386446
387447 std::ofstream outFile;
448+ std::string paramsStr =
449+ _params.size () == 1 ? " \" params[0]\" " : (" \" params[0:" + std::to_string (_params.size () - 1 ) + " ]\" " );
388450 outFile.open (filename + " .C" );
389451 outFile << R"( //auto-generated test macro
390452#include <RooFit/Detail/MathFuncs.h>
391453#include <Math/CladDerivator.h>
392454
455+ //#define DO_HESSIAN
456+
393457)" << allCode.str ()
394458 << R"(
395459#pragma clad ON
396460void gradient_request() {
397461 clad::gradient()"
398462 << _funcName << R"( , "params");
463+ #ifdef DO_HESSIAN
464+ clad::hessian()"
465+ << _funcName << " , " << paramsStr << R"( );
466+ #endif
399467}
400468#pragma clad OFF
401469)" ;
@@ -423,7 +491,7 @@ void gradient_request() {
423491 };
424492
425493 outFile << " // clang-format off\n " << std::endl;
426- writeVector (" parametersVec" , _gradientVarBuffer );
494+ writeVector (" parametersVec" , _varBuffer );
427495 outFile << std::endl;
428496 writeVector (" observablesVec" , _observables);
429497 outFile << std::endl;
@@ -436,7 +504,9 @@ void gradient_request() {
436504void )" << filename
437505 << R"( ()
438506{
439- std::vector<double> gradientVec(parametersVec.size());
507+ const std::size_t n = parametersVec.size();
508+
509+ std::vector<double> gradientVec(n);
440510
441511 auto func = [&](std::span<double> params) {
442512 return )"
@@ -465,6 +535,100 @@ void )" << filename
465535 std::cout << " numr : " << numDiff(i) << std::endl;
466536 std::cout << " clad : " << gradientVec[i] << std::endl;
467537 }
538+
539+ #ifdef DO_HESSIAN
540+ std::cout << "\n";
541+
542+ auto hess = [&](std::span<double> params, std::span<double> out) {
543+ return )"
544+ << _funcName << R"( _hessian_0(params.data(), observablesVec.data(), auxConstantsVec.data(), out.data());
545+ };
546+
547+ std::vector<double> hessianVec(n * n);
548+ hess(parametersVec, hessianVec);
549+
550+ // ---------- Numerical Hessian ----------
551+ // Uses central differences:
552+ // diag: (f(x+ei)-2f(x)+f(x-ei))/eps^2
553+ // offdiag: (f(++ ) - f(+-) - f(-+) + f(--)) / (4 eps^2)
554+ auto numHess = [&](std::size_t i, std::size_t j) {
555+ const double eps = 1e-5; // often needs to be a bit larger than grad eps
556+ std::vector<double> p(parametersVec.begin(), parametersVec.end());
557+
558+ if (i == j) {
559+ const double f0 = func(p);
560+
561+ p[i] = parametersVec[i] + eps;
562+ const double fUp = func(p);
563+
564+ p[i] = parametersVec[i] - eps;
565+ const double fDown = func(p);
566+
567+ return (fUp - 2.0 * f0 + fDown) / (eps * eps);
568+ } else {
569+ // f(x_i + eps, x_j + eps)
570+ p[i] = parametersVec[i] + eps;
571+ p[j] = parametersVec[j] + eps;
572+ const double fPP = func(p);
573+
574+ // f(x_i + eps, x_j - eps)
575+ p[i] = parametersVec[i] + eps;
576+ p[j] = parametersVec[j] - eps;
577+ const double fPM = func(p);
578+
579+ // f(x_i - eps, x_j + eps)
580+ p[i] = parametersVec[i] - eps;
581+ p[j] = parametersVec[j] + eps;
582+ const double fMP = func(p);
583+
584+ // f(x_i - eps, x_j - eps)
585+ p[i] = parametersVec[i] - eps;
586+ p[j] = parametersVec[j] - eps;
587+ const double fMM = func(p);
588+
589+ return (fPP - fPM - fMP + fMM) / (4.0 * eps * eps);
590+ }
591+ };
592+
593+ // Compute full numerical Hessian
594+ std::vector<double> numHessianVec(n * n);
595+ for (std::size_t i = 0; i < n; ++i) {
596+ for (std::size_t j = 0; j < n; ++j) {
597+ numHessianVec[i + n * j] = numHess(i, j); // keep same layout as your print
598+ }
599+ }
600+
601+ // ---------- Compare & print ----------
602+ std::cout << "Hessian comparison (clad vs numeric vs diff):\n\n";
603+
604+ for (std::size_t i = 0; i < n; ++i) {
605+ for (std::size_t j = 0; j < n; ++j) {
606+ const std::size_t idx = i + n * j; // same indexing you used
607+ const double cladH = hessianVec[idx];
608+ const double numH = numHessianVec[idx];
609+ const double diff = cladH - numH;
610+
611+ std::cout << "[" << i << "," << j << "] "
612+ << "clad=" << cladH << " num=" << numH << " diff=" << diff << "\n";
613+ }
614+ }
615+
616+ std::cout << "\nRaw Clad Hessian matrix:\n";
617+ for (std::size_t i = 0; i < n; ++i) {
618+ for (std::size_t j = 0; j < n; ++j) {
619+ std::cout << hessianVec[i + n * j] << " ";
620+ }
621+ std::cout << "\n";
622+ }
623+
624+ std::cout << "\nRaw Numerical Hessian matrix:\n";
625+ for (std::size_t i = 0; i < n; ++i) {
626+ for (std::size_t j = 0; j < n; ++j) {
627+ std::cout << numHessianVec[i + n * j] << " ";
628+ }
629+ std::cout << "\n";
630+ }
631+ #endif
468632}
469633)" ;
470634}
@@ -532,7 +696,16 @@ void RooEvaluatorWrapper::generateGradient()
532696{
533697 if (!_funcWrapper)
534698 createFuncWrapper ();
535- _funcWrapper->createGradient ();
699+ if (!_funcWrapper->hasGradient ())
700+ _funcWrapper->createGradient ();
701+ }
702+
703+ void RooEvaluatorWrapper::generateHessian ()
704+ {
705+ if (!_funcWrapper)
706+ createFuncWrapper ();
707+ if (!_funcWrapper->hasHessian ())
708+ _funcWrapper->createHessian ();
536709}
537710
538711void RooEvaluatorWrapper::setUseGeneratedFunctionCode (bool flag)
@@ -547,11 +720,19 @@ void RooEvaluatorWrapper::gradient(double *out) const
547720 _funcWrapper->gradient (out);
548721}
549722
723+ void RooEvaluatorWrapper::hessian (double *out) const
724+ {
725+ _funcWrapper->hessian (out);
726+ }
727+
550728bool RooEvaluatorWrapper::hasGradient () const
551729{
552- if (!_funcWrapper)
553- return false ;
554- return _funcWrapper->hasGradient ();
730+ return _funcWrapper && _funcWrapper->hasGradient ();
731+ }
732+
733+ bool RooEvaluatorWrapper::hasHessian () const
734+ {
735+ return _funcWrapper && _funcWrapper->hasHessian ();
555736}
556737
557738void RooEvaluatorWrapper::writeDebugMacro (std::string const &filename) const
0 commit comments