diff --git a/math/mathcore/inc/Fit/FitResult.h b/math/mathcore/inc/Fit/FitResult.h index 1a4df7a125848..896bdc36ca55f 100644 --- a/math/mathcore/inc/Fit/FitResult.h +++ b/math/mathcore/inc/Fit/FitResult.h @@ -22,18 +22,15 @@ #include #include -namespace ROOT { +namespace ROOT::Math { +class Minimizer; +} - namespace Math { - class Minimizer; - } - - - namespace Fit { +namespace ROOT::Fit { - class FitConfig; - class FitData; - class BinData; +class FitConfig; +class FitData; +class BinData; //___________________________________________________________________________________ /** @@ -53,7 +50,7 @@ class FitResult { /** Default constructor for an empty (non valid) fit result */ - FitResult (); + FitResult() = default; /** Constructor from a fit-config for a dummy fit @@ -63,9 +60,6 @@ class FitResult { // default copy constructor and assignment can be used - /** - Destructor - */ virtual ~FitResult () {} @@ -340,22 +334,22 @@ class FitResult { friend class Fitter; - bool fValid; ///< flag for indicating valid fit - bool fNormalized; ///< flag for indicating is errors are normalized - unsigned int fNFree; ///< number of fit free parameters (total parameters are in size of parameter vector) - unsigned int fNdf; ///< number of degree of freedom - unsigned int fNCalls; ///< number of function calls - int fStatus; ///< minimizer status code - int fCovStatus; ///< covariance matrix status code - double fVal; ///< minimum function value - double fEdm; ///< expected distance from minimum - double fChi2; ///< fit chi2 value (different than fval in case of chi2 fits) + bool fValid = false; ///< flag for indicating valid fit + bool fNormalized = false; ///< flag for indicating is errors are normalized + unsigned int fNFree = 0; ///< number of fit free parameters (total parameters are in size of parameter vector) + unsigned int fNdf = 0; ///< number of degree of freedom + unsigned int fNCalls = 0; ///< number of function calls + int fStatus = -1; ///< minimizer status code + int fCovStatus = 0; ///< covariance matrix status code + double fVal = 0; ///< minimum function value + double fEdm = -1; ///< expected distance from minimum + double fChi2 = -1; ///< fit chi2 value (different than fval in case of chi2 fits) std::shared_ptr fMinimizer; /// fObjFunc; /// fFitFunc; /// fFitData; /// fFixedParams; ///< list of fixed parameters - std::map fBoundParams; ///< list of limited parameters + std::vector fFixedParams; ///< if parameters are fixed + std::vector fBoundParams; ///< if parameters are limited std::vector > fParamBounds; ///< parameter bounds std::vector fParams; ///< parameter values. Size is total number of parameters std::vector fErrors; ///< errors @@ -367,13 +361,6 @@ class FitResult { }; - - } // end namespace Fit - -} // end namespace ROOT - - - - +} // namespace ROOT::Fit #endif /* ROOT_Fit_FitResult */ diff --git a/math/mathcore/inc/Fit/Fitter.h b/math/mathcore/inc/Fit/Fitter.h index 31021f75e39a3..59889413badbc 100644 --- a/math/mathcore/inc/Fit/Fitter.h +++ b/math/mathcore/inc/Fit/Fitter.h @@ -8,8 +8,6 @@ * * **********************************************************************/ -// Header file for class Fitter - #ifndef ROOT_Fit_Fitter #define ROOT_Fit_Fitter @@ -25,34 +23,35 @@ Classes used for fitting (regression analysis) and estimation of parameter value */ #include "Fit/BinData.h" -#include "Fit/UnBinData.h" #include "Fit/FitConfig.h" -#include "ROOT/EExecutionPolicy.hxx" #include "Fit/FitResult.h" +#include "Fit/UnBinData.h" #include "Math/IParamFunction.h" -#include +#include "Math/WrappedFunction.h" +#include "ROOT/EExecutionPolicy.hxx" -namespace ROOT { +#include +namespace ROOT::Math { - namespace Math { - class Minimizer; +class Minimizer; - // should maybe put this in a FitMethodFunctionfwd file - template class BasicFitMethodFunction; +// should maybe put this in a FitMethodFunctionfwd file +template +class BasicFitMethodFunction; - // define the normal and gradient function - typedef BasicFitMethodFunction FitMethodFunction; - typedef BasicFitMethodFunction FitMethodGradFunction; +// define the normal and gradient function +typedef BasicFitMethodFunction FitMethodFunction; +typedef BasicFitMethodFunction FitMethodGradFunction; - } +} // namespace ROOT::Math /** Namespace for the fitting classes @ingroup Fit */ - namespace Fit { +namespace ROOT::Fit { /** @defgroup FitMain User Fitting classes @@ -265,7 +264,9 @@ class Fitter { For the options see documentation for following methods FitFCN(IMultiGenFunction & fcn,..) */ template - bool FitFCN(unsigned int npar, Function & fcn, const double * params = nullptr, unsigned int dataSize = 0, int fitType = 0); + bool FitFCN(unsigned int npar, Function & fcn, const double * params = nullptr, unsigned int dataSize = 0, int fitType = 0) { + return DoSetFCN(false, ROOT::Math::WrappedMultiFunction{fcn, npar}, params, dataSize, fitType) ? FitFCN() : false; + } /** Set a generic FCN function as a C++ callable object implementing @@ -274,7 +275,9 @@ class Fitter { For the options see documentation for following methods FitFCN(IMultiGenFunction & fcn,..) */ template - bool SetFCN(unsigned int npar, Function & fcn, const double * params = nullptr, unsigned int dataSize = 0, int fitType = 0); + bool SetFCN(unsigned int npar, Function & fcn, const double * params = nullptr, unsigned int dataSize = 0, int fitType = 0) { + return DoSetFCN(false, ROOT::Math::WrappedMultiFunction{fcn, npar}, params, dataSize, fitType); + } /** Fit using the given FCN function represented by a multi-dimensional function interface @@ -527,14 +530,20 @@ class Fitter { /// Set the input data for the fit (Copying the given data object) template void SetData(const Data & data) { - auto dataClone = std::make_shared(data); - SetData(dataClone); + SetData(std::make_shared(data)); } /// internal functions to get data set and model function from FCN /// useful for fits done with customized FCN classes template - bool GetDataFromFCN(); + bool GetDataFromFCN() { + if (const ObjFuncType *objfunc = dynamic_cast(ObjFunction())) { + fFunc = objfunc->ModelFunctionPtr(); + fData = objfunc->DataPtr(); + return true; + } + return false; + } /// Return pointer to the used objective function for fitting. /// If using an external function (e.g. given in SetFCN), return the cached pointer, @@ -542,55 +551,28 @@ class Fitter { const ROOT::Math::IBaseFunctionMultiDimTempl * ObjFunction() const { // need to specify here full return type since when using the typedef (IMultiGenFunction) // there is an error when using the class in Python (see issue #12391) - return (fExtObjFunction) ? fExtObjFunction : fObjFunction.get(); + return fExtObjFunction ? fExtObjFunction : fObjFunction.get(); } private: - bool fUseGradient = false; ///< flag to indicate if using gradient or not - - bool fBinFit = false; ///< flag to indicate if fit is binned - ///< in case of false the fit is unbinned or undefined) - ///< flag it is used to compute chi2 for binned likelihood fit - - int fFitType = 0; ///< type of fit (0 undefined, 1 least square, 2 likelihood, 3 binned likelihood) - - int fDataSize = 0; ///< size of data sets (need for Fumili or LM fitters) - - FitConfig fConfig; ///< fitter configuration (options and parameter settings) - - std::shared_ptr fFunc_v; /// fFunc; /// fResult; /// fMinimizer; /// fData; /// fObjFunction; /// fFunc_v; /// fFunc; /// fResult; /// fMinimizer; /// fData; /// fObjFunction; /// -bool Fitter::GetDataFromFCN() { - const ObjFuncType * objfunc = dynamic_cast(ObjFunction()); - if (objfunc) { - fFunc = objfunc->ModelFunctionPtr(); - fData = objfunc->DataPtr(); - return true; - } - else { - return false; - } -} - #ifdef R__HAS_VECCORE template void Fitter::SetFunction(const IModelFunction_v &func, bool useGradient) @@ -633,30 +615,6 @@ void Fitter::SetFunction(const IGradModelFunction_v &func, bool useGradient) } #endif - } // end namespace Fit - -} // end namespace ROOT - -// implementation of inline methods - - - -#include "Math/WrappedFunction.h" - -template -bool ROOT::Fit::Fitter::FitFCN(unsigned int npar, Function & f, const double * par, unsigned int datasize,int fitType) { - ROOT::Math::WrappedMultiFunction wf(f,npar); - if (!DoSetFCN(false, wf, par, datasize, fitType)) - return false; - return FitFCN(); -} -template -bool ROOT::Fit::Fitter::SetFCN(unsigned int npar, Function & f, const double * par, unsigned int datasize,int fitType) { - ROOT::Math::WrappedMultiFunction wf(f,npar); - return DoSetFCN(false, wf, par, datasize, fitType); -} - - - +} // namespace ROOT::Fit #endif /* ROOT_Fit_Fitter */ diff --git a/math/mathcore/inc/Fit/ParameterSettings.h b/math/mathcore/inc/Fit/ParameterSettings.h index c109e24d0053d..4875b135f0acf 100644 --- a/math/mathcore/inc/Fit/ParameterSettings.h +++ b/math/mathcore/inc/Fit/ParameterSettings.h @@ -8,17 +8,12 @@ * * **********************************************************************/ -// Header file for class ParameterSettings - #ifndef ROOT_Fit_ParameterSettings #define ROOT_Fit_ParameterSettings #include -namespace ROOT { - - namespace Fit { - +namespace ROOT::Fit { //___________________________________________________________________________________ /** @@ -155,12 +150,8 @@ class ParameterSettings { bool fHasUpperLimit = false; ///< flag to control upper parameter limit std::string fName; ///< parameter name - }; - } // end namespace Fit - -} // end namespace ROOT - +} // namespace ROOT::Fit #endif /* ROOT_Fit_ParameterSettings */ diff --git a/math/mathcore/src/FitResult.cxx b/math/mathcore/src/FitResult.cxx index 6ecc25329bcea..e9926df890c9b 100644 --- a/math/mathcore/src/FitResult.cxx +++ b/math/mathcore/src/FitResult.cxx @@ -8,62 +8,33 @@ * * **********************************************************************/ -// Implementation file for class FitResult - #include "Fit/FitResult.h" -#include "Fit/FitConfig.h" - #include "Fit/BinData.h" - -//#include "Fit/Chi2FCN.h" - -#include "Math/Minimizer.h" - +#include "Fit/FitConfig.h" +#include "Math/Error.h" #include "Math/IParamFunction.h" +#include "Math/Minimizer.h" #include "Math/OneDimFunctionAdapter.h" - #include "Math/ProbFuncMathCore.h" #include "Math/QuantFuncMathCore.h" - -#include "TMath.h" #include "Math/RichardsonDerivator.h" -#include "Math/Error.h" +#include "TMath.h" #include #include #include #include -namespace ROOT { - - namespace Fit { - +namespace ROOT::Fit { const int gInitialResultStatus = -99; // use this special convention to flag it when printing result -FitResult::FitResult() : - fValid(false), fNormalized(false), fNFree(0), fNdf(0), fNCalls(0), - fStatus(-1), fCovStatus(0), fVal(0), fEdm(-1), fChi2(-1) -{ - // Default constructor implementation. -} - FitResult::FitResult(const FitConfig & fconfig) : - fValid(false), - fNormalized(false), - fNFree(0), - fNdf(0), - fNCalls(0), fStatus(gInitialResultStatus), - fCovStatus(0), - fVal(0), - fEdm(-1), - fChi2(-1), - fFitFunc(nullptr), - fParams(std::vector( fconfig.NPar() ) ), - fErrors(std::vector( fconfig.NPar() ) ), - fParNames(std::vector ( fconfig.NPar() ) ) + fParams(fconfig.NPar()), + fErrors(fconfig.NPar()), + fParNames(fconfig.NPar()) { // create a Fit result from a fit config (i.e. with initial parameter values // and errors equal to step values @@ -79,19 +50,19 @@ FitResult::FitResult(const FitConfig & fconfig) : // get parameter values and errors (step sizes) unsigned int npar = fconfig.NPar(); + fParamBounds.resize(npar); + fFixedParams.resize(npar); + fBoundParams.resize(npar); for (unsigned int i = 0; i < npar; ++i ) { const ParameterSettings & par = fconfig.ParSettings(i); fParams[i] = par.Value(); fErrors[i] = par.StepSize(); fParNames[i] = par.Name(); - if (par.IsFixed() ) fFixedParams[i] = true; - else fNFree++; - if (par.IsBound() ) { - double lower = (par.HasLowerLimit()) ? par.LowerLimit() : - std::numeric_limits::infinity() ; - double upper = (par.HasUpperLimit()) ? par.UpperLimit() : std::numeric_limits::infinity() ; - fBoundParams[i] = fParamBounds.size(); - fParamBounds.push_back(std::make_pair(lower,upper)); - } + fFixedParams[i] = par.IsFixed(); + if (!par.IsFixed() ) fNFree++; + double lower = par.HasLowerLimit() ? par.LowerLimit() : - std::numeric_limits::infinity() ; + double upper = par.HasUpperLimit() ? par.UpperLimit() : std::numeric_limits::infinity() ; + fParamBounds.emplace_back(lower,upper); } std::cout << "create fit result from config - nfree " << fNFree << std::endl; } @@ -129,7 +100,7 @@ void FitResult::FillResult(const std::shared_ptr & min, c // case minimizer does not provide minimum values (it failed) take from configuration fParams.resize(npar); for (unsigned int i = 0; i < npar; ++i ) { - fParams[i] = ( fconfig.ParSettings(i).Value() ); + fParams[i] = fconfig.ParSettings(i).Value(); } } @@ -155,17 +126,17 @@ void FitResult::FillResult(const std::shared_ptr & min, c // check for fixed or limited parameters unsigned int nfree = 0; - if (!fParamBounds.empty()) fParamBounds.clear(); + fParamBounds.resize(npar); + fFixedParams.resize(npar); + fBoundParams.resize(npar); for (unsigned int ipar = 0; ipar < npar; ++ipar) { const ParameterSettings & par = fconfig.ParSettings(ipar); - if (par.IsFixed() ) fFixedParams[ipar] = true; - else nfree++; - if (par.IsBound() ) { - double lower = (par.HasLowerLimit()) ? par.LowerLimit() : - std::numeric_limits::infinity() ; - double upper = (par.HasUpperLimit()) ? par.UpperLimit() : std::numeric_limits::infinity() ; - fBoundParams[ipar] = fParamBounds.size(); - fParamBounds.push_back(std::make_pair(lower,upper)); - } + fFixedParams[ipar] = par.IsFixed(); + fBoundParams[ipar] = par.IsBound(); + if (!par.IsFixed() ) nfree++; + double lower = par.HasLowerLimit() ? par.LowerLimit() : - std::numeric_limits::infinity() ; + double upper = par.HasUpperLimit() ? par.UpperLimit() : std::numeric_limits::infinity() ; + fParamBounds.emplace_back(lower,upper); } // check if nfree (from FitConfig) and fNFree (from minimizer) are consistent if (nfree != fNFree ) { @@ -309,23 +280,22 @@ double FitResult::Prob() const { bool FitResult::HasMinosError(unsigned int i) const { // query if the parameter i has the Minos error - std::map >::const_iterator itr = fMinosErrors.find(i); - return (itr != fMinosErrors.end() ); + return fMinosErrors.find(i) != fMinosErrors.end(); } double FitResult::LowerError(unsigned int i) const { // return lower Minos error for parameter i // return the parabolic error if Minos error has not been calculated for the parameter i - std::map >::const_iterator itr = fMinosErrors.find(i); - return ( itr != fMinosErrors.end() ) ? itr->second.first : Error(i) ; + auto itr = fMinosErrors.find(i); + return itr != fMinosErrors.end() ? itr->second.first : Error(i); } double FitResult::UpperError(unsigned int i) const { // return upper Minos error for parameter i // return the parabolic error if Minos error has not been calculated for the parameter i - std::map >::const_iterator itr = fMinosErrors.find(i); - return ( itr != fMinosErrors.end() ) ? itr->second.second : Error(i) ; + auto itr = fMinosErrors.find(i); + return itr != fMinosErrors.end() ? itr->second.second : Error(i); } void FitResult::SetMinosError(unsigned int i, double elow, double eup) { @@ -337,31 +307,31 @@ int FitResult::Index(const std::string & name) const { // find index for given parameter name if (! fFitFunc) return -1; unsigned int npar = fParams.size(); - for (unsigned int i = 0; i < npar; ++i) + for (unsigned int i = 0; i < npar; ++i) { if ( fFitFunc->ParameterName(i) == name) return i; + } return -1; // case name is not found } -bool FitResult::IsParameterBound(unsigned int ipar) const { - return fBoundParams.find(ipar) != fBoundParams.end(); +bool FitResult::IsParameterBound(unsigned int ipar) const +{ + return ipar < fBoundParams.size() ? fBoundParams[ipar] : false; } -bool FitResult::IsParameterFixed(unsigned int ipar) const { - return fFixedParams.find(ipar) != fFixedParams.end(); +bool FitResult::IsParameterFixed(unsigned int ipar) const +{ + return ipar < fFixedParams.size() ? fFixedParams[ipar] : false; } -bool FitResult::ParameterBounds(unsigned int ipar, double & lower, double & upper) const { - std::map::const_iterator itr = fBoundParams.find(ipar); - if (itr == fBoundParams.end() ) { - lower = -std::numeric_limits::infinity(); - upper = std::numeric_limits::infinity(); - return false; +bool FitResult::ParameterBounds(unsigned int ipar, double &lower, double &upper) const +{ + constexpr double inf = std::numeric_limits::infinity(); + if (ipar < fParamBounds.size()) { + lower = fParamBounds[ipar].first; + upper = fParamBounds[ipar].second; } - assert(itr->second < fParamBounds.size() ); - lower = fParamBounds[itr->second].first; - upper = fParamBounds[itr->second].second; - return true; + return lower != -inf || upper != inf; } std::string FitResult::ParName(unsigned int ipar) const { @@ -596,23 +566,6 @@ std::vector FitResult::GetConfidenceIntervals(double cl, bool norm ) con return result; } -// const BinData * GetFitBinData() const { -// // return a pointer to the binned data used in the fit -// // works only for chi2 or binned likelihood fits -// // thus when the objective function stored is a Chi2Func or a PoissonLikelihood -// ROOT::Math::IMultiGenFunction * f = fObjFunc->get(); -// Chi2Function * chi2func = dynamic_cast(f); -// if (chi2func) return &(chi2func->Data()); -// PoissonLLFunction * pllfunc = dynamic_cast(f); -// if (pllfunc) return &(pllfunc->Data()); -// Chi2GradFunction * chi2gradfunc = dynamic_cast(f); -// if (chi2gradfunc) return &(chi2gradfunc->Data()); -// PoissonLLGradFunction * pllgradfunc = dynamic_cast(f); -// if (pllgradfunc) return &(pllgradfunc->Data()); -// MATH_WARN_MSG("FitResult::GetFitBinData","Cannot return fit bin data set if objective function is not of a known type"); -// return nullptr; -// } - const BinData * FitResult::FittedBinData() const { return dynamic_cast ( fFitData.get() ); } @@ -668,6 +621,4 @@ bool FitResult::Contour(unsigned int ipar, unsigned int jpar, unsigned int &npoi return ret; } - } // end namespace Fit - -} // end namespace ROOT +} // namespace ROOT::Fit diff --git a/math/mathcore/src/Fitter.cxx b/math/mathcore/src/Fitter.cxx index d7634f544871b..3accd6d73bfad 100644 --- a/math/mathcore/src/Fitter.cxx +++ b/math/mathcore/src/Fitter.cxx @@ -8,9 +8,6 @@ * * **********************************************************************/ -// Implementation file for class Fitter - - #include "Fit/Fitter.h" #include "Fit/Chi2FCN.h" #include "Fit/PoissonLikelihoodFCN.h" @@ -32,20 +29,13 @@ #include "Math/MultiDimParamFunctionAdapter.h" -// #include "TMatrixDSym.h" -// for debugging -//#include "TMatrixD.h" -// #include - -namespace ROOT { - - namespace Fit { +namespace ROOT::Fit { // use a static variable to get default minimizer options for error def // to see if user has changed it later on. If it has not been changed we set // for the likelihood method an error def of 0.5 // t.b.d : multiply likelihood by 2 so have same error def definition as chi2 - double gDefaultErrorDef = ROOT::Math::MinimizerOptions::DefaultErrorDef(); +double gDefaultErrorDef = ROOT::Math::MinimizerOptions::DefaultErrorDef(); Fitter::Fitter(const std::shared_ptr & result) : @@ -61,7 +51,7 @@ void Fitter::SetFunction(const IModelFunction & func, bool useGradient) fUseGradient = useGradient; if (fUseGradient) { - const IGradModelFunction * gradFunc = dynamic_cast(&func); + auto *gradFunc = dynamic_cast(&func); if (gradFunc) { SetFunction(*gradFunc, true); return; @@ -75,7 +65,7 @@ void Fitter::SetFunction(const IModelFunction & func, bool useGradient) // set the fit model function (clone the given one and keep a copy ) //std::cout << "set a non-grad function" << std::endl; - fFunc = std::shared_ptr(dynamic_cast(func.Clone() ) ); + fFunc = std::unique_ptr(dynamic_cast(func.Clone() ) ); assert(fFunc); // creates the parameter settings @@ -87,7 +77,7 @@ void Fitter::SetFunction(const IModel1DFunction & func, bool useGradient) { fUseGradient = useGradient; if (fUseGradient) { - const IGradModel1DFunction * gradFunc = dynamic_cast(&func); + auto *gradFunc = dynamic_cast(&func); if (gradFunc) { SetFunction(*gradFunc, true); return; @@ -112,7 +102,7 @@ void Fitter::SetFunction(const IGradModelFunction & func, bool useGradient) fUseGradient = useGradient; //std::cout << "set a grad function" << std::endl; // set the fit model function (clone the given one and keep a copy ) - fFunc = std::shared_ptr( dynamic_cast ( func.Clone() ) ); + fFunc = std::unique_ptr( dynamic_cast ( func.Clone() ) ); assert(fFunc); // creates the parameter settings @@ -621,7 +611,7 @@ bool Fitter::CalculateMinosErrors() { const std::vector & ipars = fConfig.MinosParams(); - unsigned int n = (!ipars.empty()) ? ipars.size() : fResult->Parameters().size(); + unsigned int n = !ipars.empty() ? ipars.size() : fResult->Parameters().size(); bool ok = false; int iparNewMin = 0; @@ -712,7 +702,7 @@ bool Fitter::DoInitMinimizer() { // in case of gradient function one needs to downcast the pointer if (fUseGradient) { - const ROOT::Math::IMultiGradFunction * gradfcn = dynamic_cast (objFunction ); + auto* gradfcn = dynamic_cast (objFunction ); if (!gradfcn) { MATH_ERROR_MSG("Fitter::DoInitMinimizer","wrong type of function - it does not provide gradient"); return false; @@ -720,8 +710,7 @@ bool Fitter::DoInitMinimizer() { fMinimizer->SetFunction( *gradfcn); // set also Hessian if available if (Config().MinimizerType() == "Minuit2") { - const ROOT::Math::FitMethodGradFunction *fitGradFcn = - dynamic_cast(gradfcn); + auto *fitGradFcn = dynamic_cast(gradfcn); if (fitGradFcn && fitGradFcn->HasHessian()) { auto hessFcn = [=](std::span x, double *hess) { unsigned int ndim = x.size(); @@ -835,8 +824,7 @@ bool Fitter::DoMinimization(std::unique_ptr objFunc, const ROOT::Mat fFitType = objFunc->Type(); fExtObjFunction = nullptr; fObjFunction = std::move(objFunc); - if (!DoInitMinimizer()) return false; - return DoMinimization(chi2func); + return DoInitMinimizer() ? DoMinimization(chi2func): false; } template bool Fitter::DoWeightMinimization(std::unique_ptr objFunc, const ROOT::Math::IMultiGenFunction * chi2func) { @@ -864,19 +852,16 @@ void Fitter::DoUpdateFitConfig() { } } -int Fitter::GetNCallsFromFCN() { +int Fitter::GetNCallsFromFCN() +{ // retrieve ncalls from the fit method functions // this function is called when minimizer does not provide a way of returning the number of function calls - int ncalls = 0; if (!fUseGradient) { - const ROOT::Math::FitMethodFunction * fcn = dynamic_cast(fObjFunction.get()); - if (fcn) ncalls = fcn->NCalls(); - } - else { - const ROOT::Math::FitMethodGradFunction * fcn = dynamic_cast(fObjFunction.get()); - if (fcn) ncalls = fcn->NCalls(); - } - return ncalls; + if (auto *fcn = dynamic_cast(fObjFunction.get())) + return fcn->NCalls(); + } else if (auto *fcn = dynamic_cast(fObjFunction.get())) + return fcn->NCalls(); + return 0; } @@ -896,7 +881,7 @@ bool Fitter::ApplyWeightCorrection(const ROOT::Math::IMultiGenFunction & loglw2, unsigned int n = loglw2.NDim(); // correct errors for weight squared std::vector cov(n*n); - bool ret = fMinimizer->GetCovMatrix(&cov[0] ); + bool ret = fMinimizer->GetCovMatrix(cov.data()); if (!ret) { MATH_ERROR_MSG("Fitter::ApplyWeightCorrection","Previous fit has no valid Covariance matrix"); return false; @@ -932,7 +917,7 @@ bool Fitter::ApplyWeightCorrection(const ROOT::Math::IMultiGenFunction & loglw2, // get Hessian matrix from weight-square likelihood std::vector hes(n*n); - ret = fMinimizer->GetHessianMatrix(&hes[0] ); + ret = fMinimizer->GetHessianMatrix(hes.data()); if (!ret) { MATH_ERROR_MSG("Fitter::ApplyWeightCorrection","Error retrieving Hesse on weight2 likelihood - cannot compute errors"); return false; @@ -971,6 +956,4 @@ bool Fitter::ApplyWeightCorrection(const ROOT::Math::IMultiGenFunction & loglw2, return true; } - } // end namespace Fit - -} // end namespace ROOT +} // namespace ROOT::Fit diff --git a/math/mathcore/src/ParameterSettings.cxx b/math/mathcore/src/ParameterSettings.cxx index 311723f5890d0..d45a443608889 100644 --- a/math/mathcore/src/ParameterSettings.cxx +++ b/math/mathcore/src/ParameterSettings.cxx @@ -8,15 +8,11 @@ * * **********************************************************************/ -// Implementation file for class ParameterSettings - #include #include -namespace ROOT { - -namespace Fit { +namespace ROOT::Fit { /// set a double side limit, /// if low == up the parameter is fixed if low > up the limits are removed @@ -44,6 +40,4 @@ void ParameterSettings::SetLimits(double low, double up) fHasUpperLimit = true; } -} // end namespace Fit - -} // end namespace ROOT +} // namespace ROOT::Fit diff --git a/math/mathcore/test/CMakeLists.txt b/math/mathcore/test/CMakeLists.txt index 94e72e706df49..073a321a52539 100644 --- a/math/mathcore/test/CMakeLists.txt +++ b/math/mathcore/test/CMakeLists.txt @@ -100,3 +100,5 @@ ROOT_ADD_GTEST(testKNNDensity testKNNDensity.cxx LIBRARIES Core MathCore Hist) if(clad) ROOT_ADD_GTEST(CladDerivatorTests CladDerivatorTests.cxx LIBRARIES Core MathCore) endif() + +ROOT_ADD_GTEST(testFitter testFitter.cxx LIBRARIES Core MathCore) diff --git a/math/mathcore/test/testFitter.cxx b/math/mathcore/test/testFitter.cxx new file mode 100644 index 0000000000000..cbc07b3efac64 --- /dev/null +++ b/math/mathcore/test/testFitter.cxx @@ -0,0 +1,77 @@ +// Tests for the ROOT::Fit::Fitter + +#include +#include + +#include + +namespace { + +double gauss(double *x, double *p) +{ + double A = p[0]; + double mu = p[1]; + double sigma = p[2]; + double xx = x[0]; + return A * exp(-(xx - mu) * (xx - mu) / (2 * sigma * sigma)); +} + +} // namespace + +/// Check if we can fix and release parameters, and that this will be correctly +/// reflected in the FitResult. +/// Covers https://github.com/root-project/root/issues/20703 +TEST(Fitter, FitAndReleaseParams) +{ + // --- Create data --- + const int n = 50; + double x[n]; + double y[n]; + std::vector initVals{1.0, 2.0, 1.0}; + for (int i = 0; i < n; ++i) { + x[i] = i * 0.1; + y[i] = gauss(&x[i], initVals.data()); + } + + // --- Model functor --- + ROOT::Math::Functor f( + [&](const double *p) { + double chi2 = 0.0; + for (int i = 0; i < n; ++i) { + double yi = gauss(&x[i], (double *)p); + double diff = y[i] - yi; + chi2 += diff * diff; + } + return chi2; + }, + 3); + + ROOT::Fit::Fitter fitter; + + std::vector fixState{false, false, false}; + fitter.Config().SetParamsSettings(initVals.size(), initVals.data()); + + fitter.Config().ParSettings(0).SetName("A"); + fitter.Config().ParSettings(1).SetName("mu"); + fitter.Config().ParSettings(2).SetName("sigma"); + + // Repeatedly run fits using the SAME fitter + for (int iter = 0; iter < 6; ++iter) { + bool fix = (iter % 2 == 0); + int iparam = (iter / 2) % initVals.size(); + + fixState[iparam] = fix; + if (fix) { + fitter.Config().ParSettings(iparam).Fix(); + } else { + fitter.Config().ParSettings(iparam).Release(); + } + + fitter.FitFCN(f, nullptr, 3); + const ROOT::Fit::FitResult &res = fitter.Result(); + + for (unsigned int i = 0; i < initVals.size(); ++i) { + EXPECT_EQ(res.IsParameterFixed(i), fixState[i]); + } + } +}