|
|
//Renata Kopecna
#include <fitter.hh>
#include <paths.hh>
#include <event.hh>
#include <parameters.hh>
#include <pdf.hh>
#include <plotter.hh>
#include <options.hh>
#include <helpers.hh>
#include <funcs.hh>
#include <design.hh>
#include <constants.hh>
#include <TH1D.h>
#include <TMatrixD.h>
#include <TMatrixDSym.h>
#include <TRandom3.h>
#include <TVector.h>
#include <TDecompChol.h>
#include <spdlog.h>
#include <fstream>
#include <iostream>
#include <complex>
#include <assert.h>
#include <thread>
#include <mutex>
//Log of the covariance matrix status
void printCovMatrixStatus(int status, bool squared){ std::string hesse = "Hesse"; if (squared) hesse = "Squared Hesse"; if (status==0) spdlog::warn(hesse + " returns 0: Hesse not calculated."); else if (status==1) spdlog::warn(hesse + " returns 1: Approximation only."); else if (status==2) spdlog::warn(hesse + " returns 2: Full, forced pos def"); else if (status==3) spdlog::info(hesse + " returns 3: ALL GOOD"); else spdlog::warn(hesse + " returns {0:d} -> SOMETHING VERY WRONG", status); return; }
//Migrad status log so one doesn't have to look it up all the time
void printMigradStatus(int status){ if (status==0) spdlog::info("MIGRAD returns 0: ALL GOOD"); else if (status==1) spdlog::warn("MIGRAD returns 1: Blank command, ignored."); else if (status==2) spdlog::warn("MIGRAD returns 2: Command line unreadable, ignored."); else if (status==3) spdlog::warn("MIGRAD returns 3: Unknown command, ignored"); else if (status==4) spdlog::warn("MIGRAD returns 4: Abnormal termination, MIGRAD not converged or something."); else if (status==9) spdlog::warn("MIGRAD returns 9: Reserved."); else if (status==10) spdlog::warn("MIGRAD returns 10: End command."); else if (status==11) spdlog::warn("MIGRAD returns 11: Exit/Stop command."); else if (status==12) spdlog::warn("MIGRAD returns 12: Return command."); else spdlog::warn("MIGRAD returns {0:d} -> SOMETHING VERY WRONG", status); return; }
//set plotter
void fcnc::fitter::set_plotters(plotter* plot) { std::vector<plotter* > p; p.push_back(plot); plots = p; return; }
///set plotters
void fcnc::fitter::set_plotters(std::vector<plotter*> plotters) { plots = plotters; return; } ///define common parameters for all pdfs
void fcnc::fitter::set_common_parameters(std::vector<std::string> common_pars) { common_params = common_pars; return; } ///is the parameter with name one of the common parameters
bool fcnc::fitter::is_common(std::string name) { for (unsigned int i=0; i<common_params.size(); i++){ if (common_params.at(i) == name) return true; } return false; } ///is this the first occurence of the parameter?
bool fcnc::fitter::is_first(std::string name, unsigned int idx_i, unsigned int idx_j) { for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ if (name == params.at(i)->get_parameter(j)->get_name()){ if (i==idx_i && j==idx_j) return true; else return false; } } } spdlog::critical("[FATAL]\t\tThe parameter '"+name+"' was not found!"); assert(0); return true; }
///returns the nth minuit index for parameter with name
int fcnc::fitter::nth_minuit_index(std::string name, unsigned int n) { assert(n); assert(n <= params.size()); unsigned int start_param = 0; for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ if (name == params.at(i)->get_parameter(j)->get_name()){ if (n == 1) return start_param+j; else n--; } } start_param += minuit_one_fitter->params.at(i)->nparameters(); } return -1; }
///returns the first minuit index for parameter with name
int fcnc::fitter::first_minuit_index(std::string name) { return nth_minuit_index(name, 1); }
///returns wether this is an analysis with blinded parameters
bool fcnc::fitter::is_blind() { for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ if (params.at(i)->get_parameter(j)->is_blind()){ return true; } } } return false; }
//WHAT THE FUCK IS THIS DOING HERE?
int fcnc::fitter::fit(pdf* probs, parameters* parms, std::vector<event>* ev, std::string index){ std::vector<pdf *> the_probs; the_probs.push_back(probs);
std::vector<parameters *> the_params; the_params.push_back(parms);
std::vector<std::vector<event>* > the_events; the_events.push_back(ev);
return fit(the_probs, the_params, the_events, index); }
void fcnc::fitter::init(pdf* probs, parameters* parms, std::vector<event>* ev, std::string index){ std::vector<pdf *> the_probs; the_probs.push_back(probs);
std::vector<parameters *> the_params; the_params.push_back(parms);
std::vector<std::vector<event>* > the_events; the_events.push_back(ev);
return init(the_probs, the_params, the_events, index); }
void fcnc::fitter::minuit_one_fcn(Int_t &npar, Double_t *grad, Double_t &lh, Double_t *parameters, Int_t iflag){ //set the parameter sets according to minuits current parameter values stored in the params pointer
//do not forget to set common parameters correctly. these have no directly corresponding minuit index, use first_index instead
unsigned int start_param = 0; for (unsigned int i = 0; i < minuit_one_fitter->params.size(); i++){ for (unsigned int j = 0; j < minuit_one_fitter->params.at(i)->nparameters(); j++){ unsigned int index = start_param + j; std::string par_name = minuit_one_fitter->params.at(i)->get_parameter(j)->get_name(); if (minuit_one_fitter->is_common(par_name)) minuit_one_fitter->params.at(i)->get_parameter(j)->set_value(parameters[minuit_one_fitter->first_minuit_index(par_name)]); else minuit_one_fitter->params.at(i)->get_parameter(j)->set_value(parameters[index]); } start_param += minuit_one_fitter->params.at(i)->nparameters(); } lh = minuit_one_fitter->likelihood(); return; }
fcnc::fitter::fitter(options* o): opts(o) { minuit_one = new TMinuit(1000);//params->nparameters());
minuit_one->SetFCN(&(minuit_one_fcn)); //this makes minuit use highest precision
int errorcode; double strategy_list[1];// = {2.0}
strategy_list[0] = opts->minuit_strategy; minuit_one->mnexcm("SET STR", strategy_list, 1, errorcode); minuit_one->SetMaxIterations(40000); //need to set this value to 0.5 if you do not use -2*ln(LH)
minuit_one->SetErrorDef(1.0);//default
spdlog::info("Machine precision: {0:f}", minuit_one->fEpsmac); //8.88178e-16
//verbose minuit
//minuit_one->SetPrintLevel(o->print_level);
//minuit_one->SetPrintLevel(-1);
empirical_constant = 0.0; }
fcnc::fitter::~fitter(){ delete minuit_one; }
void fcnc::fitter::init(std::vector<pdf*> probs, std::vector<parameters*> parms, std::vector< std::vector<event> * > ev, std::string index){ square_weights = false; //set all important pointers and initialize
minuit_one_fitter = this; //this could be different for every fit (feldman-cousins)
int errorcode; double strategy_list[1];// = {2.0}
strategy_list[0] = opts->minuit_strategy; minuit_one->mnexcm("SET STR", strategy_list, 1, errorcode);
//prob = probability;
pdfs = probs; params = parms; events = ev;
reset_param_values(); //this was originally before the reset... put here to catch reset of swave
for (unsigned int i=0; i<pdfs.size(); i++) pdfs.at(i)->init(params.at(i));
//update cached efficiencies in three situations
//1. q2 fixed and regular acceptance
//2. unfolded fit to determine the weight=1/eff
//3. per event norm
//cache fis
if (opts->cache_fis){ spdlog::info("Caching fis"); for (unsigned int i = 0; i < pdfs.size(); i++) pdfs.at(i)->update_cached_integrated_fis(params.at(i)); } for (unsigned int i = 0; i < pdfs.size(); i++){ parameter* effq2 = params.at(i)->get_parameter("eff_q2"); bool q2fixed = false; if (effq2) q2fixed = (effq2->get_step_size() == 0.0); if ((q2fixed && opts->use_angular_acc) || opts->weighted_fit || opts->use_event_norm){ pdfs.at(i)->update_cached_efficiencies(params.at(i), events.at(i)); } } //per event xis
if (opts->use_event_norm){ for (unsigned int i = 0; i < pdfs.size(); i++){ pdfs.at(i)->update_cached_xis(params.at(i), events.at(i)); } }
}
int fcnc::fitter::fit(std::vector<pdf*> probs, std::vector<parameters*> parms, std::vector< std::vector<event> * > ev, std::string index){ //can only use either squared hesse or minos
if(opts->minos_errors && opts->squared_hesse){ spdlog::critical("Cannot use both MINOS and squared HESSE for uncertainty deterimination. Choose one!"); assert(0); } square_weights = false; //set all important pointers and initialize
minuit_one_fitter = this; //this could be different for every fit (feldman-cousins)
int errorcode; double strategy_list[1];// = {2.0}
strategy_list[0] = opts->minuit_strategy; minuit_one->mnexcm("SET STR", strategy_list, 1, errorcode);
//prob = probability;
pdfs = probs; params = parms; events = ev;
//make sure to reduce output for blind analysis
if (is_blind()) minuit_one->SetPrintLevel(-1); else if (spdlog_debug()) minuit_one->SetPrintLevel(0); else minuit_one->SetPrintLevel(-1);
reset_param_values(); //this was originally before the reset... put here to catch reset of swave
for (unsigned int i=0; i<pdfs.size(); i++){ pdfs.at(i)->init(params.at(i)); }
//update cached efficiencies in three situations
//1. q2 fixed and regular acceptance
//2. unfolded fit to determine the weight=1/eff
//3. per event norm
//cache fis
if (opts->cache_fis){ spdlog::info("Caching fis"); for (unsigned int i = 0; i < pdfs.size(); i++){ pdfs.at(i)->update_cached_integrated_fis(params.at(i)); } } for (unsigned int i = 0; i < pdfs.size(); i++){ parameter* effq2 = params.at(i)->get_parameter("eff_q2"); bool q2fixed = false; if (effq2) q2fixed = (effq2->get_step_size() == 0.0); if ((q2fixed && opts->use_angular_acc) || opts->weighted_fit || opts->use_event_norm){ pdfs.at(i)->update_cached_efficiencies(params.at(i), events.at(i)); } } //per event xis
if (opts->use_event_norm){ for (unsigned int i = 0; i < pdfs.size(); i++){ pdfs.at(i)->update_cached_xis(params.at(i), events.at(i)); } }
//get empirical factor
empirical_constant = 0.0; if (opts->shift_lh){ unsigned int the_size=0; for (unsigned int i=0; i<ev.size(); i++) the_size += ev.at(i)->size(); empirical_constant = likelihood()/the_size; }
spdlog::debug("Determined empirical constant of: {0:f}", empirical_constant);
int result = 0; unsigned int varied_params = 0; //determine the number of varied parameters
//count only parameters that are not common among the PDFs!
for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ if (params.at(i)->get_parameter(j)->get_step_size() != 0.0 && !is_common(params.at(i)->get_parameter(j)->get_name())){ varied_params++; spdlog::debug("[PDF{0:d}]\tPAR='"+params.at(i)->get_parameter(j)->get_name()+"'\trange=[{1:f} - {2:f}]", i+1, params.at(i)->get_parameter(j)->get_min(), params.at(i)->get_parameter(j)->get_max()); } } }
spdlog::debug("Count number of varied parameters: {0:d} PDF(s)", params.size()); spdlog::debug("Number of varied, non-common parameters: {0:d}", varied_params); //assert(!(varied_params % params.size()));
//add the parameters to the counter, that are common AND varied
for (unsigned int i = 0; i < common_params.size(); i++){ bool is_varied = false; for (unsigned int j = 0; j < params.size(); j++){ for (unsigned int k = 0; k < params.at(j)->nparameters(); k++){ if (common_params.at(i) == params.at(j)->get_parameter(k)->get_name() && params.at(j)->get_parameter(k)->get_step_size() != 0.0){ is_varied = true; spdlog::debug("[COMMON]\tPAR='"+params.at(j)->get_parameter(k)->get_name()+"'\trange=[{0:f} - {1:f}]", params.at(j)->get_parameter(k)->get_min(), params.at(j)->get_parameter(k)->get_max()); break; } } if(is_varied)break; } if (is_varied){ varied_params++; } }
spdlog::info("Including common parameters, number of varied parameters is: {0:d}", varied_params);
//open texFile
std::ofstream myFile; if (index != ""){ open_Latex_noteFile(latex_fitterFile(index), myFile); }
//Use Markov Chain Monte Carlo //TODO when bored, move this to a separate function
if (opts->use_mcmc){ unsigned int nlength = 10000; //just run a single chain
std::vector<TVectorD> chain;//(nlength);
for (unsigned int i=0; i<nlength; i++){ chain.push_back(TVectorD(varied_params)); } TVectorD current(varied_params); //fill in starting values
//TMatrixDSym S1(nparams);
//S1.IdentityMatrix();
//S1 *= 0.1;
TMatrixDSym SNminusone(varied_params); SNminusone.UnitMatrix(); //SNminusone *= 0.1; //might be more appropriate for the problem
unsigned int idx = 0; for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ if (params.at(i)->get_parameter(j)->get_step_size() != 0.0 && !is_common(params.at(i)->get_parameter(j)->get_name())){ current[idx] = params.at(i)->get_parameter(j)->get_value(); SNminusone(idx, idx) = params.at(i)->get_parameter(j)->get_step_size();//this is actually quite clever
idx++; } } } spdlog::debug("nrows {0:d}\tvaried_params {1:d}", current.GetNrows(), varied_params); for(int i=0; i<current.GetNrows(); i++) spdlog::debug("{0:d}", current[i]);
TVectorD last(current);
double alphastar = 0.234; double last_llh = likelihood(); double minimum_llh = last_llh; TVectorD minimum(current);
TMatrixDSym SN(SNminusone); TRandom3 * rnd = new TRandom3(); spdlog::info("Starting Markov chain"); //produce markov chain of the desired length
for (unsigned int i=0; i<nlength; i++){ if ((i*100)%nlength == 0) std::cout << i*100/double(nlength) << "%" << std::endl; //should optionally repeat this, until values are in parameter range
TVectorD WN(varied_params); bool finished = false; while (!finished){//repeat until we are inside the boundaries
//get random vector
for (int j=0; j<WN.GetNrows(); j++){ WN[j] = rnd->Gaus(0.0, 1.0);//could also use students t distribution
} //shift depends on SNminusone
TVectorD SW(SNminusone*WN); current = last + SW; finished = true; //this will disallow parameters outside the allowed range
const bool constrain_parameters = true; if (constrain_parameters){ idx = 0; for (unsigned int j = 0; j < params.size(); j++){ for (unsigned int k = 0; k < params.at(j)->nparameters(); k++){ if (params.at(j)->get_parameter(k)->get_step_size() != 0.0 && !is_common(params.at(j)->get_parameter(k)->get_name())){ if (current[idx] > params.at(j)->get_parameter(k)->get_max() || current[idx] < params.at(j)->get_parameter(k)->get_min()){ finished = false; } idx++; } } } } } spdlog::debug("event {0:d}", i); //convert index back to parameter* and set to current point
idx = 0; for (unsigned int j = 0; j < params.size(); j++){ for (unsigned int k = 0; k < params.at(j)->nparameters(); k++){ if (params.at(j)->get_parameter(k)->get_step_size() != 0.0 && !is_common(params.at(j)->get_parameter(k)->get_name())){ params.at(j)->get_parameter(k)->set_value(current[idx]); idx++; } } } for (unsigned int j=0; j<pdfs.size(); j++){//check if this is needed!
pdfs.at(j)->init(params.at(j)); } //compute likelihood of current point
double current_llh = likelihood(); double r = rnd->Rndm(); double alpha = std::min(1.0, exp(0.5*(last_llh-current_llh)));//llhs are in fact Sum_i -2*log(P_i)
//debug output
if (spdlog_debug()){ for (int i=0; i<current.GetNrows(); i++) std::cout << current[i] << " "; std::cout << std::endl; } //check if current point is accepted
spdlog::debug("accept event?"); if (r < alpha){ chain.at(i) = current; last = current; last_llh = current_llh; } else{ chain.at(i) = last; //last stays last
} //debugging output
spdlog::debug("current_llh {0:f}\t last_llh {1:f}" + std::string((r<alpha) ? " accepted " : " rejected "), current_llh, last_llh); //check wether a new likelihood minimum is found and set if this is the case
if (current_llh < minimum_llh){ minimum = current; minimum_llh = current_llh; } //update SN
TMatrixDSym SNminusoneT(SNminusone); SNminusoneT.T(); double etan = std::min(1.0, varied_params*pow(double(i), -2.0/3.0)); TMatrixDSym WNWNT(varied_params); WNWNT.Zero(); for (int row = 0; row < WNWNT.GetNrows(); row++){ for (int col = 0; col < WNWNT.GetNcols(); col++){ WNWNT[row][col] = WN[row]*WN[col]/WN.Norm2Sqr(); } } //TMatrixDSym SNSNT(identity + WNWNT*etan*(alpha-alphastar));
TMatrixDSym SNSNT(varied_params); SNSNT.UnitMatrix(); SNSNT += WNWNT*etan*(alpha-alphastar); SNSNT = SNSNT.Similarity(SNminusone); TDecompChol chol(SNSNT); bool success = chol.Decompose(); assert(success); TMatrixD SNT = chol.GetU(); TMatrixD SN(SNT); SN.T(); for (int row = 0; row < SN.GetNrows(); row++){ for (int col = 0; col < SN.GetNcols(); col++){ SNminusone(row,col) = SN(row,col); } } } //best spot found
spdlog::info("lowest point found at llh of {0:f}\n", minimum_llh); if (spdlog_info()){ for (int i=0; i<minimum.GetNrows(); i++) std::cout << minimum[i] << " "; std::cout << std::endl; } //mean
spdlog::info("mean point at"); TVectorD mean(varied_params); mean.Zero(); for (unsigned int i=0; i<nlength; i++){ TVectorD v(chain.at(i)); v *= 1.0/double(nlength); mean += v; } if (spdlog_info()){ for (int i=0; i<mean.GetNrows(); i++) std::cout << mean[i] << std::endl; std::cout << std::endl; } //rms
spdlog::info("rms is "); TVectorD rms(varied_params); rms.Zero(); for (unsigned int i=0; i<nlength; i++){ TVectorD v(chain.at(i)-mean); v.Sqr(); v *= 1.0/double(nlength); rms += v; } rms.Sqrt(); if (spdlog_info()){ for (int i=0; i<rms.GetNrows(); i++) std::cout << rms[i] << " "; std::cout << std::endl; } //get covariance matrix estimate from matrix SN
spdlog::debug("matrix SNminousone"); if (spdlog_debug()){ for (int row = 0; row < SNminusone.GetNrows(); row++){ for (int col = 0; col < SNminusone.GetNcols(); col++) std::cout << std::setw(8) << SNminusone(row,col) << " "; std::cout << std::endl; } } TMatrixD SNminusoneT(SNminusone); SNminusoneT.T(); TMatrixD cov = SNminusone*SNminusoneT; spdlog::debug("matrix cov"); if (spdlog_debug()){ for (int row = 0; row < cov.GetNrows(); row++){ for (int col = 0; col < cov.GetNcols(); col++) std::cout << std::setw(8) << cov(row,col) << " "; std::cout << std::endl; } } spdlog::debug("errors from matrix cov");
if (spdlog_debug()){ for (int row = 0; row < cov.GetNrows(); row++)std::cout << sqrt(cov(row,row)) << " "; std::cout << std::endl; } //proper estimate of best point and uncertainties from projections
unsigned int nbins = 1000; TH1D* histos[varied_params]; idx = 0; for (unsigned int j = 0; j < params.size(); j++){ for (unsigned int k = 0; k < params.at(j)->nparameters(); k++){ if (params.at(j)->get_parameter(k)->get_step_size() != 0.0 && !is_common(params.at(j)->get_parameter(k)->get_name())){ std::ostringstream hname; hname << "hist" << idx; histos[idx] = new TH1D(hname.str().c_str(), ";;", nbins, params.at(j)->get_parameter(k)->get_min(), params.at(j)->get_parameter(k)->get_max()); idx++; } } } //loop over chain and fill histos
for (unsigned int i=0; i<nlength; i++){ TVectorD v(chain.at(i)); for (int row = 0; row < v.GetNrows(); row++) histos[row]->Fill(v[row]); } if (true){//smooth all histos
const unsigned int ntimes = 1; for (unsigned int i=0; i<varied_params; i++) histos[i]->Smooth(ntimes); } if (spdlog_debug()){ //If in debug mode, just save the histos for now
for (unsigned int i=0; i<varied_params; i++){ plotAndSave(histos[i],"c"+std::to_string(i),std::string(histos[i]->GetName()),"eps"); } } //get maximum from histos (for now no smoothing) and add up highes bins until 1 sigma
//probably want to add from highest bin, no?
TVectorD max(varied_params); TVectorD eup(varied_params); TVectorD edown(varied_params); for (unsigned int i=0; i<varied_params; i++){ int maxbin = histos[i]->GetMaximumBin(); double x = histos[i]->GetBinCenter(maxbin); double left = histos[i]->GetBinLowEdge(maxbin); double right = histos[i]->GetBinLowEdge(maxbin+1); double sum = histos[i]->GetBinContent(maxbin); histos[i]->SetBinContent(maxbin, 0.0); bool finished = sum/nlength > ONE_SIGMA; while (!finished){ maxbin = histos[i]->GetMaximumBin(); double nx = histos[i]->GetBinCenter(maxbin); sum += histos[i]->GetBinContent(maxbin); histos[i]->SetBinContent(maxbin, 0.0); if (nx > right) right = histos[i]->GetBinLowEdge(maxbin+1); if (nx < left) left = histos[i]->GetBinLowEdge(maxbin); finished = sum/nlength > ONE_SIGMA; } max[i] = x; eup[i] = right-x; edown[i] = left-x; delete histos[i]; } //output of the newly determined mean and errors
if (spdlog_info()){ spdlog::info("max"); for (int i=0; i<max.GetNrows(); i++) std::cout << max[i] << " "; std::cout << std::endl; spdlog::info("eup"); for (int i=0; i<eup.GetNrows(); i++) std::cout << eup[i] << " "; std::cout << std::endl; spdlog::info("edown"); for (int i=0; i<edown.GetNrows(); i++) std::cout << edown[i] << " "; std::cout << std::endl; } idx = 0; for (unsigned int j = 0; j < params.size(); j++){ for (unsigned int k = 0; k < params.at(j)->nparameters(); k++){ if (params.at(j)->get_parameter(k)->get_step_size() != 0.0 && !is_common(params.at(j)->get_parameter(k)->get_name())){ params.at(j)->get_parameter(k)->set_value(max[idx]); params.at(j)->get_parameter(k)->set_error(0.5*(fabs(eup[idx])+fabs(edown[idx]))); params.at(j)->get_parameter(k)->set_error_up(eup[idx]); params.at(j)->get_parameter(k)->set_error_down(edown[idx]); //params.at(j)->get_parameter(k)->set_value(mean[idx]);
//params.at(j)->get_parameter(k)->set_error(sqrt(cov(idx,idx)));
idx++; } } } //MCMC always returns status OK
return 300;//this means all ok
} //When using no Markov Chain MC: Nominal fit procedure
else{ //TODO when bored, move this to a separate function
spdlog::debug("Starting Fit Procedure"); int errorcode; //double eps[1] = {1.0e-10};
//minuit_one->mnexcm("SET EPS", eps, 1, errorcode);
//double strategy_list[2] = {0,0.1};
double migrad_options[2] = {MIG_MAX_CALLS, MIG_TOLERANCE}; //Maxcalls and tolerance
// The default tolerance is 0.1, and the minimization will stop when the estimated vertical distance to the minimum (EDM) is less than 0.001*[tolerance]*UP(see SET ERR)
if (opts->simplex_prerun){ spdlog::info("Running Simplex"); minuit_one->mnexcm("SIM", migrad_options, 2, errorcode); } minuit_one->mnexcm("MIG", migrad_options, 2, errorcode); result = errorcode; printMigradStatus(result); spdlog::warn("EDM: {0:f}", minuit_one->fEDM); if (opts->hesse_postrun){ spdlog::info("Starting hesse"); minuit_one->mnhess(); if (spdlog_debug()) minuit_one->mnmatu(1); //Print the correlation matrix, they are annoying so turned off for now
} int nfree, ntot, status_cov; double m_fcn, m_edm, up; minuit_one->mnstat(m_fcn, m_edm, up, nfree, ntot, status_cov); //status_cov 0=not calculated, 1=approximation, 2=full but forced pos. def., 3=full accurate
result += 100*status_cov; if (opts->hesse_postrun){ //Hesse calculates the 2nd derivative of the likelihood profile to assign the correct systematics
printCovMatrixStatus(status_cov,false); } double tmp_cov[varied_params * varied_params]; //spdlog::info("nvaried params = " << varied_params);;
minuit_one->mnemat(tmp_cov, varied_params); if (opts->weighted_fit && opts->asymptotic){//perform correction according to 1911.01303 Eq. 18
spdlog::debug("Starting an assymptotic treatment."); assert(!(opts->squared_hesse));//TODOCL i think hesse_postrun is fine (and will lead to better estimate for the weighted Hessian)
//weighted hesse matrix is available as tmp_cov
//need to iterate over all events
//need to evaluate first derivatives at central values
//events in different pdfs are independently distributed
//just should count every event separately
TMatrixDSym V(varied_params, tmp_cov);//weighted covariance matrix
TMatrixDSym C(varied_params);//, 0.0);//tmp_cov_sq);
for (unsigned int k=0; k<varied_params; k++){ for (unsigned int l=0; l<varied_params; l++){ C(k,l) = 0.0; } } //save best fit values
spdlog::debug("Saving best fit values."); std::vector<std::string> param_names; std::vector<double> param_values; std::vector<double> eps(varied_params, 1.0e-5); for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ if (params.at(i)->get_parameter(j)->get_step_size() != 0.0){ //if the parameter is common, only add name once
if (!is_common(params.at(i)->get_parameter(j)->get_name()) || is_first(params.at(i)->get_parameter(j)->get_name(), i, j)){ double v,e; std::string par_name = params.at(i)->get_parameter(j)->get_name(); unsigned int idx = first_minuit_index(par_name); minuit_one->GetParameter(idx, v, e); param_names.push_back(par_name); param_values.push_back(v); spdlog::debug(par_name+": {0:f}", v); } } } } //the optimized version varies the parameter in the outer loop and performs the update_cached_normalisation only once for + and -
//loop over data samples
spdlog::debug("Looping over data samples.");
std::vector<double> lh_extended_nsig(events.size(),0.0); std::vector<double> lh_extended_nbkg(events.size(),0.0); std::vector<double> lh_extended_nsigbar(events.size(),0.0); std::vector<double> lh_extended_nbkgbar(events.size(),0.0); for (unsigned int i = 0; i<events.size(); i++){ //save derivative for every event, every param
if (i%10==0) spdlog::trace("Event {0:d}",i); std::vector<std::vector<double> > derivs; //first idx varied param, second index event
for (unsigned int k=0; k<varied_params; k++){ //only want to do this for params.at(i), other data samples use the other param set
fcnc::parameter* varied = params.at(i)->get_parameter(param_names.at(k)); if (!varied){ std::vector<double> derivs_k(events.at(i)->size(),0.0); derivs.push_back(derivs_k); continue; } //set varied parameter + everywhere
varied->set_value(param_values.at(k)+eps.at(k)); //cache integrals
pdfs.at(i)->update_cached_normalization(params.at(i)); if (opts->use_event_norm) pdfs.at(i)->update_cached_normalization(params.at(i), events.at(i));
//TODOCL
spdlog::debug("Getting likelihood."); likelihood();//this is supposed to only get the extended term, for the + varied parameters, should be fast enough //This doesn't do anything!
//loop over lh_plus events
std::vector<double> loglh_plus_k(events.at(i)->size(),0.0); for (unsigned int j = 0; j < events.at(i)->size(); j++){ //for every event do finite differences
const fcnc::event meas = events.at(i)->at(j); double probability = 0.0; probability = pdfs.at(i)->prob(params.at(i), meas); if (probability > 0.0) loglh_plus_k.at(j) = -TMath::Log(probability); //TODOCL
if (meas.cp_conjugate){ //TODOCL CHECK!! //LEONS COMMENT meas.cp_conjugate corresponds to n_sig !!!!!!!
loglh_plus_k.at(j) += -TMath::Log(lh_extended_nsig.at(i)+lh_extended_nbkg.at(i)); } else{ loglh_plus_k.at(j) += -TMath::Log(lh_extended_nsigbar.at(i)+lh_extended_nbkgbar.at(i)); } }
//set varied parameter - everywhere
varied->set_value(param_values.at(k)-eps.at(k)); //cache integrals
pdfs.at(i)->update_cached_normalization(params.at(i)); if (opts->use_event_norm) pdfs.at(i)->update_cached_normalization(params.at(i), events.at(i));
//TODOCL
likelihood();//this is supposed to only get the extended term, for the - varied parameters, should be fast enough
//loop over lh_minus events
std::vector<double> loglh_minus_k(events.at(i)->size(),0.0); // for (unsigned int j = 0; j < events.at(i)->size(); j++){
// //for every event do finite differences
// const fcnc::event meas = events.at(i)->at(j);
// double probability = 0.0;
// probability = pdfs.at(i)->prob(params.at(i), meas);
// if (probability > 0.0) loglh_minus_k.at(j) = -TMath::Log(probability);
// //TODOCL
// if (meas.cp_conjugate){ //TODOCL CHECK!! //LEONS COMMENT meas.cp_conjugate corresponds to n_sig !!!!!!!
// loglh_minus_k.at(j) += -TMath::Log(lh_extended_nsig.at(i)+lh_extended_nbkg.at(i));
// }
// else{
// loglh_minus_k.at(j) += -TMath::Log(lh_extended_nsigbar.at(i)+lh_extended_nbkgbar.at(i));
// }
// }
//calculate derivatives for all events
spdlog::debug("Calculating derivatives for all events"); std::vector<double> derivs_k(events.at(i)->size(),0.0); for (unsigned int j = 0; j < events.at(i)->size(); j++){ derivs_k.at(j) = (loglh_plus_k.at(j)-loglh_minus_k.at(j))/(2.0*eps.at(k)); } spdlog::trace("Derivatives:+ "+convert_vector_to_string(derivs_k)); derivs.push_back(derivs_k);
//reset varied parameter everywhere
varied->set_value(param_values.at(k)); //cache integrals
pdfs.at(i)->update_cached_normalization(params.at(i)); if (opts->use_event_norm){ pdfs.at(i)->update_cached_normalization(params.at(i), events.at(i)); }
}
//afterwards determine derivative via finite differences
spdlog::debug("Calculating derivatives for finite differences"); //and save matrix of squared first derivatives
for (unsigned int j = 0; j < events.at(i)->size(); j++){ //for every event do finite differences
const fcnc::event meas = events.at(i)->at(j); for (unsigned int k=0; k<varied_params; k++){ for (unsigned int l=0; l<varied_params; l++){ C(k,l) += meas.weight*meas.weight*derivs.at(k).at(j)*derivs.at(l).at(j); } } } }
TMatrixD VCV(V,TMatrixD::kMult,TMatrixD(C,TMatrixD::kMult,V)); TMatrixDSym VCVsym(varied_params); spdlog::debug("printing corrected covariance matrix"); for (unsigned int i=0; i<varied_params; i++){ for (unsigned int j=0; j<varied_params; j++) { if (spdlog_debug()) std::cout << std::scientific << std::setw(9) << std::setprecision(2) << VCV(i,j) << " "; VCVsym(i,j) = 0.5*(VCV(i,j)+VCV(j,i));//no check for now
tmp_cov[varied_params*j + i] = VCVsym(i,j);//save corrected covariance matrix
} if (spdlog_debug()) std::cout << std::endl; } }
if (opts->weighted_fit && opts->squared_hesse){ square_weights = true; spdlog::info("Starting hesse with squared weights"); minuit_one->mnhess(); //check that this went fine
double m_sq_fcn, m_sq_edm, sq_up; int status_sq_cov; minuit_one->mnstat(m_sq_fcn, m_sq_edm, sq_up, nfree, ntot, status_sq_cov); printCovMatrixStatus(status_sq_cov,true); //change status to invalied if squared weighted fit failed
if (status_cov != status_sq_cov){ result -= 100*status_cov; result += 100*((status_cov < status_sq_cov) ? status_cov : status_sq_cov); } // minuit_one->mnmatu(1);//this also sets something internal in minuit! getparameter seems to get its error from here!
double tmp_cov_sq[varied_params * varied_params]; minuit_one->mnemat(tmp_cov_sq, varied_params); //lets use root classes for the matrix inversion
TMatrixDSym V(varied_params, tmp_cov);//tmp_cov
TMatrixDSym C(varied_params, tmp_cov_sq); double det=0.0; C.Invert(&det); TMatrixD VCV(V,TMatrixD::kMult,TMatrixD(C,TMatrixD::kMult,V)); TMatrixDSym VCVsym(varied_params); spdlog::info("printing corrected covariance matrix"); for (unsigned int i=0; i<varied_params; i++){ for (unsigned int j=0; j<varied_params; j++){ if (spdlog_debug()){ std::cout << std::scientific << std::setw(9) << std::setprecision(2) << VCV(i,j) << " "; } VCVsym(i,j) = 0.5*(VCV(i,j)+VCV(j,i));//no check for now
tmp_cov[varied_params*j + i] = VCVsym(i,j);//save corrected covariance matrix
} if (spdlog_debug()) std::cout << std::endl; } square_weights = false; }
//to get the correct latex names
std::vector<std::string> varied_names; for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ if (params.at(i)->get_parameter(j)->get_step_size() != 0.0){//if the parameter is common, only add name once
if (!is_common(params.at(i)->get_parameter(j)->get_name()) || is_first(params.at(i)->get_parameter(j)->get_name(), i, j)){ varied_names.push_back(params.at(i)->get_parameter(j)->get_description()); } } } }
//printing error matrix
if (spdlog_debug()){ spdlog::debug("printing error matrix"); for (unsigned int x = 0; x < varied_params; x++) std::cout << "\tvar" << x << " "; std::cout << std::endl; for (unsigned int y = 0; y < varied_params; y++){ std::cout << "var" << y; for (unsigned int x = 0; x < varied_params; x++){ std::cout << " " << std::scientific << std::setw(9) << std::setprecision(2) << tmp_cov[varied_params*y + x]; } std::cout << std::endl; } } //calculating correlation matrix from error matrix
double cor[varied_params*varied_params]; for (unsigned int x = 0; x < varied_params; x++){ for (unsigned int y = 0; y < varied_params; y++){ cor[varied_params*y + x] = tmp_cov[varied_params*y + x] /(sqrt(fabs(tmp_cov[varied_params*x + x]))*sqrt(fabs(tmp_cov[varied_params*y + y]))); } } //printing correlation matrix
if (index != ""){ spdlog::info("printing correlation matrix"); myFile << "Run: " << opts->run << std::endl; myFile << "\\begin{tabular}{c"; for (unsigned int x = 0; x < varied_params; x++){ myFile << "c"; } myFile << "} \\hline" << std::endl; myFile << " "; for (unsigned int x = 0; x < varied_params; x++){ myFile << " & $" << varied_names.at(x) << "$"; } myFile << "\\\\ \\hline" << std::endl;
bool upper_half = true; for (unsigned int y = 0; y < varied_params; y++){ myFile << std::setw(20) << "$" << varied_names.at(y) << "$"; for (unsigned int x = 0; x < varied_params; x++){ if (x >= y || !upper_half){ if (fabs(cor[varied_params*y + x]) >= 0.005){ myFile << " & " << std::fixed << std::setw(5) << std::setprecision(2) << cor[varied_params*y + x]; } else myFile << " & " << std::setw(5) << "-"; } else myFile << " & " << std::setw(5) << " "; } myFile << " \\\\ " << std::endl; } myFile <<"\\hline \\end{tabular}" << std::endl; }
//saves correlations for all parameters
for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j=0; j < params.at(i)->nparameters(); j++){ if (params.at(i)->get_parameter(j)->get_step_size() != 0.0){ params.at(i)->get_parameter(j)->correlations.clear(); unsigned int apary = 0; for (unsigned int y=0; y < varied_params; y++){ if (varied_names.at(y) == params.at(i)->get_parameter(j)->get_description()){ apary = y; break; } } for (unsigned int x=0; x < varied_params; x++) params.at(i)->get_parameter(j)->correlations.push_back(cor[varied_params*apary + x]); } } }
//minos error analysis
if (opts->minos_errors && (result%100) == 0){//migrad is successfull
spdlog::info("Starting Minos error analysis"); std::vector<int> indices; for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ if (params.at(i)->get_parameter(j)->get_step_size() != 0.0 && params.at(i)->get_parameter(j)->get_minos()){ std::string par_name = params.at(i)->get_parameter(j)->get_name(); unsigned int idx = 0; if(is_common(par_name) || opts->use_individual_param_names) idx = first_minuit_index(par_name); else idx = nth_minuit_index(par_name, i+1); indices.push_back(idx); } } } if (indices.size() > 0){//run minos just for subset of parameters
spdlog::info("MINOS SUBSET"); int errorcode; /*
double migrad_options[indices.size()+1]; migrad_options[0] = 100000000.0; for (unsigned int i = 0; i < indices.size(); i++) //migrad_options[i+1] = indices.at(i);
migrad_options[i+1] = indices.at(i)+1; minuit_one->mnexcm("MINO", migrad_options, indices.size()+1, errorcode); */ for (unsigned int i = 0; i < indices.size(); i++){ double migrad_options[2]; migrad_options[0] = 100000000.0; migrad_options[1] = indices.at(i)+1; minuit_one->mnexcm("MINO", migrad_options, 2, errorcode); } } else minuit_one->mnmnos();//do minos error analysis on all parameters
//write MINOS errors to parameters
for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ double e_up, e_down, parab, gcc; if (params.at(i)->get_parameter(j)->get_step_size() != 0.0){ std::string par_name = params.at(i)->get_parameter(j)->get_name(); unsigned int idx = 0; if(is_common(par_name) || opts->use_individual_param_names) idx = first_minuit_index(par_name); else idx = nth_minuit_index(par_name, i+1); minuit_one->mnerrs(idx, e_up, e_down, parab, gcc); params.at(i)->get_parameter(j)->set_error_up(e_up); params.at(i)->get_parameter(j)->set_error_down(e_down); } } } }
//finally extract fitted parameters from minuit
for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ double v,e; std::string par_name = params.at(i)->get_parameter(j)->get_name(); unsigned int idx; if(is_common(par_name) || opts->use_individual_param_names) idx = first_minuit_index(par_name); else idx = nth_minuit_index(par_name, i + 1);
spdlog::trace("[PDF{0:d}]\tPAR{1:d}:\t{2:s}\tTMinuit_idx: {3:d}", i, j, par_name, idx);
minuit_one->GetParameter(idx, v, e); params.at(i)->get_parameter(j)->set_value(v); params.at(i)->get_parameter(j)->set_error(e);
if (opts->weighted_fit){ //this method is approximate for parameters with limited range!
int iidx = minuit_one->fNiofex[idx] - 1; if (iidx>=0){ e = sqrt(fabs(tmp_cov[iidx*varied_params+iidx])); params.at(i)->get_parameter(j)->set_error(e); } } } }
if (spdlog_trace()){ for (unsigned int i = 0; i < params.size(); i++){ params.at(i)->print_parameters(); } }
if (opts->project){ for (unsigned int i = 0; i<plots.size(); i++) plots.at(i)->plot_data(pdfs.at(i), params.at(i), events.at(i), index); } spdlog::info("Fit procedure finished"); }
//Close Latex file
if (index != "") myFile.close();
return result; }
void fcnc::fitter::define_parameter(int i, const parameter & p){ if (p.get_unlimited()){ minuit_one->DefineParameter(i, p.get_mn_name(), p(), p.get_step_size(), 0.0, 0.0); } else{ minuit_one->DefineParameter(i, p.get_mn_name(), p(), p.get_step_size(), p.get_min(), p.get_max()); } }
void fcnc::fitter::reset_param_values(){ spdlog::debug("Resetting parameter values"); minuit_one->mncler();
unsigned int start_param = 0; for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ if (opts->reset_start){ params.at(i)->get_parameter(j)->reset_start(); } std::string par_name = params.at(i)->get_parameter(j)->get_name(); if (!is_common(par_name) || is_first(par_name, i, j)){ define_parameter(start_param+j, *(params.at(i)->get_parameter(j))); } } start_param += params.at(i)->nparameters(); } }
double fcnc::fitter::likelihood(){ lh_tot = 0.0; //this is called on every parameter change
//this can never hurt, also needed in case of weighted/unfolded fit
for (unsigned int i = 0; i < pdfs.size(); i++){ pdfs.at(i)->update_cached_normalization(params.at(i));//should do this, but should buffer f1, update f1 on fit command
if(opts->use_event_norm){ pdfs.at(i)->update_cached_normalization(params.at(i), events.at(i)); } }
//threading the likelihood determination on multiple CPU cores
if(opts->ncores > 1){ std::vector<std::thread*> threads; for(unsigned int i = 0; i < opts->ncores; i++){ std::thread* t = new std::thread(std::bind( &fitter::likelihood_thread, this, i)); threads.push_back(t); } for(unsigned int i = 0; i < opts->ncores; i++) threads[i]->join(); for(unsigned int i = 0; i < opts->ncores; i++) delete threads[i]; } else{ likelihood_thread(0); }
if (spdlog_trace()){ unsigned int start_param = 0; for(unsigned int i = 0; i < params.size(); i++){ for(unsigned int j = 0; j < params.at(i)->nparameters(); j++){ if(params.at(i)->get_parameter(j)->get_step_size() != 0.0){ std::string par_name = params.at(i)->get_parameter(j)->get_name(); if(!is_common(par_name) || is_first(par_name, i, j)){ if(!params.at(i)->get_parameter(j)->is_blind()) std::cout << par_name << ":" << params.at(i)->get_parameter(j)->get_value() << " "; else std::cout << par_name << ":" << "XXX" << " "; } } } start_param += params.at(i)->nparameters(); } std::cout << std::endl; }
//there is an additional term for parameters which have been measured previously
for (unsigned int i = 0; i < params.size(); i++){ for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){ const parameter* param = params.at(i)->get_parameter(j); if (param->get_gaussian_constraint()){ if (!is_common(param->get_name()) || is_first(param->get_name(), i, j)){ double mean = param->get_previous_measurement(); double x = param->get_value(); double dup = fabs(param->get_previous_error_high()); double ddown = fabs(param->get_previous_error_low()); double sigma = (x>mean ? dup : ddown); //spdlog::trace("mean {0:f}\tx {1:f}\tdup {2:f}\tddown {3:f}\tsigma {4:f}",mean,x,dup,ddown,sigma);
if (x>mean+dup) sigma = dup; else if (x<mean-ddown) sigma = ddown; else sigma = ddown + (dup-ddown)*(x-(mean-ddown))/(dup+ddown); lh_tot += (-2.0*(-(x-mean)*(x-mean)/(2.0*sigma*sigma))); } } } } if (opts->chisquaredstudy){ double chi2 = 0.0; for (unsigned int i = 0; i < pdfs.size(); i++) chi2 += pdfs.at(i)->chi2(params.at(i)); lh_tot += chi2; } if (opts->extended_ml){ double lh_ext = 0.0; for(unsigned int i = 0; i < params.size(); i++){ double nobs = 0.0; parameter* pnsig = params.at(i)->get_parameter("n_sig"); parameter* pnbkg = params.at(i)->get_parameter("n_bkg"); assert(pnsig && pnbkg); double nsig = pnsig->get_value(); double nbkg = pnbkg->get_value(); if(opts->weighted_fit){ for(unsigned int j = 0; j<events.at(i)->size(); j++){ nobs += events.at(i)->at(j).weight; } } else{ nobs = events.at(i)->size(); } double prob = TMath::Poisson(nsig+nbkg, nobs); if(prob == 0.0) prob = 1e-100; lh_ext += -2.0*TMath::Log( prob ); if (std::isinf(lh_ext) || std::isnan(lh_ext)){ std::cout << "[PDF" << i << "]\t\tlh_ext = " << lh_ext << "\t prob = " << prob << "\t -2log(prob) = " << -2.0*TMath::Log( prob ) << std::endl; std::cout << "[PDF" << i << "]\t\tnsig = " << nsig << "\t n_bkg = " << nbkg << "\t nobs = " << nobs << std::endl; assert(0); } } if (std::isinf(lh_ext)){ spdlog::error("{0:0.16f}", lh_ext); spdlog::error("lh (w/o lh_ext) = {0:0.16f}", lh_tot); assert(0); } lh_tot += lh_ext; }
if (std::isinf(lh_tot || std::isnan(lh_tot))){ spdlog::error("lh = {0:0.16f}", lh_tot); assert(0); } if (spdlog_trace()) std::cout << "[debug]\t" << std::setprecision(16) << "lh = " << lh_tot << "\r" << std::flush; return lh_tot; }
void fcnc::fitter::likelihood_thread(int thread_id){
double probability = 0.0; event meas; long double result = 0.0;
for (unsigned int i = 0; i<events.size(); i++){ unsigned int min, max; unsigned int evts_per_thread = events.at(i)->size()/opts->ncores; //is too small for last vector!
min = evts_per_thread * thread_id; max = evts_per_thread * (thread_id+1); int last_thread = opts->ncores - 1; if (thread_id == last_thread){//last thread needs to take all posibly remaining events
max = events.at(i)->size(); } std::vector<long double> vec; vec.reserve(max - min); for (unsigned int j = min; j < max; j++){ meas = events.at(i)->at(j); probability = pdfs.at(i)->prob(params.at(i), meas); if (probability < 0.0 || std::isnan(probability) || std::isinf(probability)){ std::lock_guard<std::mutex> lock(fitter_mutex); spdlog::info("Event probability is {0:d}", probability); print_event(meas); params.at(i)->print_parameters(); assert(0); } if (probability == 0.0){ probability = 1.0;//ignore event
} if (probability != 0.0){ if (opts->weighted_fit){ if (square_weights) vec.push_back(-2.0 * meas.weight * meas.weight * TMath::Log(probability) - empirical_constant);//todo: does the empirical constant pose a problem here?
else vec.push_back(-2.0 * meas.weight * TMath::Log(probability) - empirical_constant); } else vec.push_back(-2.0 * TMath::Log(probability) - empirical_constant); } else if (!std::isnan(probability)){ spdlog::error("Event probability is {0:d}", probability); print_event(meas); assert(0); } else{//is inf?
vec.push_back(8.0);//is inf?//use something like prob max? log(p_min)
} } result += add_results<std::vector<long double>::iterator>(vec.begin(), vec.end()); } if (opts->ncores > 1){ std::lock_guard<std::mutex> lock(fitter_mutex); lh_tot += result; } else{ lh_tot += result; } }
//boost::mutex fitter::fitter_mutex;
std::mutex fcnc::fitter::fitter_mutex;
fcnc::fitter* fcnc::fitter::minuit_one_fitter;
|