// @(#)root/mathmore:$Id$ // Author: L. Moneta Wed Sep 6 09:52:26 2006 /********************************************************************** * * * Copyright (c) 2006 LCG ROOT Math Team, CERN/PH-SFT * * * * * **********************************************************************/ // Header file for class WrappedTFunction #ifndef ROOT_Math_WrappedMultiTF1 #define ROOT_Math_WrappedMultiTF1 #include "Math/IParamFunction.h" #include "TF1.h" #include #include #include namespace ROOT { namespace Math { namespace Internal { double DerivPrecision(double eps); TF1 *CopyTF1Ptr(const TF1 *funcToCopy); }; /** Class to Wrap a ROOT Function class (like TF1) in a IParamMultiFunction interface of multi-dimensions to be used in the ROOT::Math numerical algorithm. This wrapper class does not own the TF1 pointer, so it assumes it exists during the wrapper lifetime. The class copy the TF1 pointer only when it owns it. The class from ROOT version 6.03 does not contain anymore a copy of the parameters. The parameters are stored in the TF1 class. @ingroup CppFunctions */ //LM note: are there any issues when cloning the class for the parameters that are not copied anymore ?? template class WrappedMultiTF1Templ: virtual public ROOT::Math::IParametricGradFunctionMultiDimTempl { public: typedef ROOT::Math::IParametricGradFunctionMultiDimTempl BaseParamFunc; typedef typename ROOT::Math::IParametricFunctionMultiDimTempl::BaseFunc BaseFunc; /** constructor from a function pointer to a TF1 If dim = 0 dimension is taken from TF1::GetNdim(). In case of multi-dimensional function created using directly TF1 object the dimension returned by TF1::GetNdim is always 1. The user must then pass the correct value of dim */ WrappedMultiTF1Templ(TF1 &f, unsigned int dim = 0); /** Destructor (no operations). Function pointer is not owned */ ~WrappedMultiTF1Templ() override { if (fOwnFunc && fFunc) delete fFunc; } /** Copy constructor */ WrappedMultiTF1Templ(const WrappedMultiTF1Templ &rhs); /** Assignment operator */ WrappedMultiTF1Templ &operator = (const WrappedMultiTF1Templ &rhs); /** @name interface inherited from IParamFunction */ /** Clone the wrapper but not the original function */ IMultiGenFunctionTempl *Clone() const override { return new WrappedMultiTF1Templ(*this); } /** Retrieve the dimension of the function */ unsigned int NDim() const override { return fDim; } /// get the parameter values (return values from TF1) const double *Parameters() const override { //return (fParams.size() > 0) ? &fParams.front() : 0; return fFunc->GetParameters(); } /// set parameter values (only the cached one in this class,leave unchanges those of TF1) void SetParameters(const double *p) override { //std::copy(p,p+fParams.size(),fParams.begin()); fFunc->SetParameters(p); } /// return number of parameters unsigned int NPar() const override { // return fParams.size(); return fFunc->GetNpar(); } /// return parameter name (from TF1) std::string ParameterName(unsigned int i) const override { return std::string(fFunc->GetParName(i)); } // evaluate the derivative of the function with respect to the parameters void ParameterGradient(const T *x, const double *par, T *grad) const override; // evaluate the hessian of the function with respect to the parameters bool ParameterHessian(const T *x, const double *par, T *h) const override; // return capability of computing parameter Hessian bool HasParameterHessian() const override; // evaluate the 2nd derivatives of the function with respect to the parameters bool ParameterG2(const T *, const double *, T *) const override { return false; // not yet implemented } /// precision value used for calculating the derivative step-size /// h = eps * |x|. The default is 0.001, give a smaller in case function changes rapidly static void SetDerivPrecision(double eps); /// get precision value used for calculating the derivative step-size static double GetDerivPrecision(); /// method to retrieve the internal function pointer const TF1 *GetFunction() const { return fFunc; } /// method to set a new function pointer and copy it inside. /// By calling this method the class manages now the passed TF1 pointer void SetAndCopyFunction(const TF1 *f = nullptr); private: /// evaluate function passing coordinates x and vector of parameters T DoEvalPar(const T *x, const double *p) const override { return fFunc->EvalPar(x, p); } /// evaluate function using the cached parameter values (of TF1) /// re-implement for better efficiency T DoEvalVec(const T *x) const { return fFunc->EvalPar(x, 0); } /// evaluate function using the cached parameter values (of TF1) /// re-implement for better efficiency T DoEval(const T *x) const override { // no need to call InitArg for interpreted functions (done in ctor) //const double * p = (fParams.size() > 0) ? &fParams.front() : 0; return fFunc->EvalPar(x, nullptr); } /// evaluate the partial derivative with respect to the parameter T DoParameterDerivative(const T *x, const double *p, unsigned int ipar) const override; bool fLinear; // flag for linear functions bool fPolynomial; // flag for polynomial functions bool fOwnFunc; // flag to indicate we own the TF1 function pointer TF1 *fFunc; // pointer to ROOT function unsigned int fDim; // cached value of dimension //std::vector fParams; // cached vector with parameter values }; /** * Auxiliar class to bypass the (provisional) lack of vectorization in TFormula::EvalPar. * * WrappedMultiTF1Templ::DoParameterDerivation calls TFormula::EvalPar in the case of a general linear function * built with TFormula using ++; as EvalPar is not vectorized, in order to generalize DoParameterDerivative with * a general type T, we use this auxiliar class to branch the code in compile time with the double * specialization (that can call EvalPar) and the general implementation (that throws an error in the case of * general linear function). */ template struct GeneralLinearFunctionDerivation { static T DoParameterDerivative(const WrappedMultiTF1Templ *, const T *, unsigned int) { Error("DoParameterDerivative", "The vectorized implementation of DoParameterDerivative does not support" "general linear functions built in TFormula with ++"); return TMath::SignalingNaN(); } }; template <> struct GeneralLinearFunctionDerivation { static double DoParameterDerivative(const WrappedMultiTF1Templ *wrappedFunc, const double *x, unsigned int ipar) { const TFormula *df = dynamic_cast(wrappedFunc->GetFunction()->GetLinearPart(ipar)); assert(df != nullptr); return (const_cast(df))->EvalPar(x); // derivatives should not depend on parameters since // function is linear } }; // implementations for WrappedMultiTF1Templ template WrappedMultiTF1Templ::WrappedMultiTF1Templ(TF1 &f, unsigned int dim) : fLinear(false), fPolynomial(false), fOwnFunc(false), fFunc(&f), fDim(dim) //fParams(f.GetParameters(),f.GetParameters()+f.GetNpar()) { // constructor of WrappedMultiTF1Templ // pass a dimension if dimension specified in TF1 does not correspond to real dimension // for example in case of multi-dimensional TF1 objects defined as TF1 (i.e. for functions with dims > 3 ) if (fDim == 0) fDim = fFunc->GetNdim(); // check that in case function is linear the linear terms are not zero // function is linear when is a TFormula created with "++" // hyperplane are not yet existing in TFormula if (fFunc->IsLinear()) { int ip = 0; fLinear = true; while (fLinear && ip < fFunc->GetNpar()) { fLinear &= (fFunc->GetLinearPart(ip) != nullptr); ip++; } } // distinguish case of polynomial functions and linear functions if (fDim == 1 && fFunc->GetNumber() >= 300 && fFunc->GetNumber() < 310) { fLinear = true; fPolynomial = true; } } template WrappedMultiTF1Templ::WrappedMultiTF1Templ(const WrappedMultiTF1Templ &rhs) : BaseParamFunc(rhs), fLinear(rhs.fLinear), fPolynomial(rhs.fPolynomial), fOwnFunc(rhs.fOwnFunc), fFunc(rhs.fFunc), fDim(rhs.fDim) //fParams(rhs.fParams) { // copy constructor if (fOwnFunc) SetAndCopyFunction(rhs.fFunc); } template WrappedMultiTF1Templ &WrappedMultiTF1Templ::operator= (const WrappedMultiTF1Templ &rhs) { // Assignment operator if (this == &rhs) return *this; // time saving self-test fLinear = rhs.fLinear; fPolynomial = rhs.fPolynomial; fOwnFunc = rhs.fOwnFunc; fDim = rhs.fDim; //fParams = rhs.fParams; return *this; } template void WrappedMultiTF1Templ::ParameterGradient(const T *x, const double *par, T *grad) const { // evaluate the gradient of the function with respect to the parameters //IMPORTANT NOTE: TF1::GradientPar returns 0 for fixed parameters to avoid computing useless derivatives // BUT the TLinearFitter wants to have the derivatives also for fixed parameters. // so in case of fLinear (or fPolynomial) a non-zero value will be returned for fixed parameters if (!fLinear) { // need to set parameter values fFunc->SetParameters(par); // no need to call InitArgs (it is called in TF1::GradientPar) double prec = this->GetDerivPrecision(); fFunc->GradientPar(x, grad, prec); } else { // case of linear functions unsigned int np = NPar(); for (unsigned int i = 0; i < np; ++i) grad[i] = DoParameterDerivative(x, par, i); } } // struct for dealing of generic Hessian computation, since it is available only in TFormula template struct GeneralHessianCalc { static bool Hessian(TF1 *, const T *, const double *, T *) { Error("Hessian", "The vectorized implementation of ParameterHessian is not supported"); return false; } static bool IsAvailable(TF1 * ) { return false;} }; template <> struct GeneralHessianCalc { static bool Hessian(TF1 * func, const double *x, const double * par, double * h) { // compute Hessian if TF1 is a formula based unsigned int np = func->GetNpar(); auto formula = func->GetFormula(); if (!formula) return false; std::vector h2(np*np); func->SetParameters(par); formula->HessianPar(x,h2); for (unsigned int i = 0; i < np; i++) { for (unsigned int j = 0; j <= i; j++) { unsigned int ih = j + i *(i+1)/2; // formula for j <= i unsigned int im = i*np + j; h[ih] = h2[im]; } } return true; } static bool IsAvailable(TF1 * func) { auto formula = func->GetFormula(); if (!formula) return false; return formula->GenerateHessianPar(); } }; template bool WrappedMultiTF1Templ::ParameterHessian(const T *x, const double *par, T *h) const { if (fLinear) { std::fill(h, h + NPar()*(NPar()+1)/2, 0.0); return true; } return GeneralHessianCalc::Hessian(fFunc, x, par, h); } template bool WrappedMultiTF1Templ::HasParameterHessian() const { return GeneralHessianCalc::IsAvailable(fFunc); } template T WrappedMultiTF1Templ::DoParameterDerivative(const T *x, const double *p, unsigned int ipar) const { // evaluate the derivative of the function with respect to parameter ipar // see note above concerning the fixed parameters if (!fLinear) { fFunc->SetParameters(p); double prec = this->GetDerivPrecision(); return fFunc->GradientPar(ipar, x, prec); } if (fPolynomial) { // case of polynomial function (no parameter dependency) (case for dim = 1) assert(fDim == 1); if (ipar == 0) return 1.0; #ifdef R__HAS_VECCORE return vecCore::math::Pow(x[0], static_cast(ipar)); #else return std::pow(x[0], static_cast(ipar)); #endif } else { // case of general linear function (built in TFormula with ++ ) return GeneralLinearFunctionDerivation::DoParameterDerivative(this, x, ipar); } } template void WrappedMultiTF1Templ::SetDerivPrecision(double eps) { ::ROOT::Math::Internal::DerivPrecision(eps); } template double WrappedMultiTF1Templ::GetDerivPrecision() { return ::ROOT::Math::Internal::DerivPrecision(-1); } template void WrappedMultiTF1Templ::SetAndCopyFunction(const TF1 *f) { const TF1 *funcToCopy = (f) ? f : fFunc; fFunc = ::ROOT::Math::Internal::CopyTF1Ptr(funcToCopy); fOwnFunc = true; } using WrappedMultiTF1 = WrappedMultiTF1Templ; } // end namespace Math } // end namespace ROOT #endif /* ROOT_Fit_WrappedMultiTF1 */