File indexing completed on 2026-04-07 08:08:31
0001 #include "CustomHistogramFit/ConstrainedFitter.h"
0002
0003 #include "Math/Minimizer.h"
0004 #include "Math/Functor.h"
0005 #include "Math/Factory.h"
0006
0007 #include <algorithm>
0008
0009 ConstrainedFitter::ConstrainedFitter(TH1* hist, std::vector<FitFunctions::FitModel> models)
0010 : hist_(hist) {
0011
0012 modelName_ = "";
0013 singleModelFuns_.resize(models.size());
0014 singleNParams_.resize(models.size());
0015 nParams_ = 0;
0016 int iparGlobal = 0;
0017 for (int im = 0; im < (int) models.size(); im++) {
0018 FitFunctions::FitModel model = models[im];
0019 FitFunctions::modelStructure modelInfo = FitFunctions::GetModel(model);
0020 modelName_ += (im > 0 ? " and " : "") + modelInfo.name;
0021 singleModelFuns_[im] = modelInfo.fun;
0022 singleNParams_[im] = modelInfo.nParams;
0023 nParams_ += modelInfo.nParams;
0024 int ipar = 0;
0025
0026 for (; ipar < modelInfo.nParams; ipar++, iparGlobal++) {
0027
0028
0029
0030
0031
0032 if (std::find(paramNames_.begin(), paramNames_.end(), modelInfo.paramNames[ipar]) == paramNames_.end()) {
0033 paramNames_.push_back(modelInfo.paramNames[ipar]);
0034 } else {
0035 paramNames_.push_back(modelInfo.paramNames[ipar] + "_2");
0036 }
0037
0038 }
0039
0040 }
0041
0042 CombinedModelFun_ = [this] (const double *x, const double *par) { return this->CombineModelFunctions(x, par); };
0043
0044
0045 initialParams_.resize(nParams_, 0.0);
0046 paramBounds_.resize(nParams_, {0.0, 0.0});
0047 paramStep_ = 0.001;
0048 std::cout << "combined model '" << modelName_ << "' " << nParams_ << " parameters\n";
0049
0050 fitFunction_ = new TF1("fitFunction", CombinedModelFun_, hist->GetXaxis()->GetXmin(), hist->GetXaxis()->GetXmax(), nParams_);
0051 for (int ipar = 0; ipar < nParams_; ipar++) fitFunction_->SetParName(ipar, paramNames_[ipar].c_str());
0052 }
0053
0054 ConstrainedFitter::~ConstrainedFitter()
0055 {
0056 delete fitFunction_;
0057 }
0058
0059 void ConstrainedFitter::SetInitialParams(const std::vector<double>& params) {
0060 if (params.size() != static_cast<size_t>(nParams_)) {
0061 std::cerr << "Error in ConstrainedFitter::SetInitialParams: Incorrect number of parameters provided! Expected "
0062 << nParams_ << " parameters for the model '" << modelName_ << "'. "
0063 << params.size() << " given instead." << std::endl;
0064 return;
0065 }
0066 initialParams_ = params;
0067
0068 for (int ipar = 0; ipar < nParams_; ipar++) {
0069 fitFunction_->SetParameter(ipar, initialParams_[ipar]);
0070 }
0071 }
0072
0073 void ConstrainedFitter::SetParamBounds(const std::vector<std::pair<double, double>>& bounds) {
0074 if (bounds.size() != static_cast<size_t>(nParams_)) {
0075 std::cerr << "Error in ConstrainedFitter::SetParamBounds: Incorrect number of bounds provided! Expected "
0076 << nParams_ << " parameters for the model '" << modelName_ << "'. "
0077 << bounds.size() << " given instead." << std::endl;
0078 return;
0079 }
0080 paramBounds_ = bounds;
0081 }
0082
0083 void ConstrainedFitter::PerformFit() {
0084 if (!hist_) {
0085 std::cerr << "Error in ConstrainedFitter::PerformFit: No histogram provided!" << std::endl;
0086 return;
0087 }
0088
0089 ROOT::Math::Minimizer *minimizer = ROOT::Math::Factory::CreateMinimizer("Minuit2", "Migrad");
0090
0091
0092
0093 std::function<double(double const*)> const Chi2Lambda = [this] (const double *params) { return this->Chi2Function(params); };
0094
0095 ROOT::Math::Functor functor(Chi2Lambda, nParams_);
0096 minimizer->SetFunction(functor);
0097
0098
0099
0100
0101
0102
0103 for (int i = 0; i < nParams_; i++) {
0104
0105 minimizer->SetLimitedVariable(i, paramNames_[i], initialParams_[i], paramStep_, paramBounds_[i].first, paramBounds_[i].second);
0106 }
0107
0108 bool success = minimizer->Minimize();
0109
0110 if (!success) {
0111 std::cout << "Minimization failed!" << std::endl;
0112 } else {
0113 std::cout << "Minimization successful!" << std::endl;
0114 }
0115 std::cout << "Final Chi2: " << Chi2Function(minimizer->X()) << std::endl;
0116
0117 const double *optimized_params = minimizer->X();
0118 const double *param_errors = minimizer->Errors();
0119
0120 for (int i = 0; i < nParams_; i++)
0121 {
0122 fitFunction_->SetParameter(i, optimized_params[i]);
0123 fitFunction_->SetParError(i, param_errors[i]);
0124 }
0125 }
0126
0127 void ConstrainedFitter::PrintResults() {
0128 if (!fitFunction_) {
0129 std::cerr << "Error: Fit function not initialized! Cannot print results" << std::endl;
0130 return;
0131 }
0132
0133 std::cout << "Fit Results:" << std::endl;
0134 for (int i = 0; i < nParams_; ++i) {
0135 double value, error;
0136 value = fitFunction_->GetParameter(i);
0137 error = fitFunction_->GetParError(i);
0138 std::cout << "Parameter " << i << ": " << value << " +- " << error << std::endl;
0139 }
0140 }
0141
0142 double ConstrainedFitter::CombineModelFunctions(const double* x, const double* par) {
0143 double sum = 0;
0144 int nPars = 0;
0145 int iparGlobal = 0;
0146 FitFunctions::modelFunction modelFun;
0147 for (int ifun = 0; ifun < (int) singleModelFuns_.size(); ifun++) {
0148
0149 modelFun = singleModelFuns_[ifun];
0150 nPars = singleNParams_[ifun];
0151 double *params = new double[nPars];
0152 int ipar = 0;
0153 for (; ipar < nPars; ipar++, iparGlobal++) {
0154 params[ipar] = par[iparGlobal];
0155 }
0156 sum += modelFun(x, params);
0157 delete params;
0158 }
0159 return sum;
0160 }
0161
0162 double ConstrainedFitter::Chi2Function(const double* par) {
0163 if (hist_ == nullptr) {
0164 std::cerr << "Error: Histogram not found in minimization function!" << std::endl;
0165 exit(1);
0166 }
0167 fitFunction_->SetParameters(par);
0168
0169 double chi2 = 0;
0170 for (int bin = 2; bin <= hist_->GetNbinsX(); ++bin) {
0171 double x = hist_->GetBinCenter(bin);
0172 double dx = hist_->GetBinWidth(bin);
0173 double y_data_error = hist_->GetBinError(bin);
0174 double y_data = hist_->GetBinContent(bin);
0175 double y_fit = fitFunction_->Eval(x);
0176
0177
0178 int nbWindows = fitRange_.size();
0179
0180 if (nbWindows >= 1) {
0181 bool inFitRange = false;
0182 for (int iw = 0; iw < nbWindows; iw++) {
0183 if ((fitRange_[iw].first < x) && (x < fitRange_[iw].second)) {
0184 inFitRange = true;
0185 break;
0186 }
0187 }
0188 if (!inFitRange) continue;
0189 }
0190
0191
0192
0193
0194
0195
0196
0197
0198
0199
0200 if (y_data == 0 || y_data_error == 0) continue;
0201
0202
0203
0204 if (std::isnan(y_fit) || std::isinf(y_fit))
0205 {
0206 std::cout << "NaN or Inf detected in fit evaluation at bin " << bin << std::endl;
0207 std::cout << "Parameters were the following:\n";
0208 for (int i = 0; i < nParams_; i++)
0209 {
0210 std::cout << "parameter " << i << ": " << par[i] << std::endl;
0211 }
0212
0213 }
0214
0215 double increment = std::pow((y_fit - y_data) / y_data_error, 2);
0216 if (std::isnan(increment) || std::isinf(increment))
0217 chi2 += 1e7;
0218 else
0219 chi2 += increment;
0220 }
0221 return chi2;
0222 }
0223