MaCh3  2.6.1
Reference Guide
ParameterHandlerBase.cpp
Go to the documentation of this file.
3 
4 #include <regex>
5 
6 // ********************************************
7 ParameterHandlerBase::ParameterHandlerBase(std::string name, std::string file, double threshold, int FirstPCA, int LastPCA)
8  : inputFile(file), pca(true) {
9 // ********************************************
10  MACH3LOG_DEBUG("Constructing instance of ParameterHandler");
11  doSpecialStepProposal = false;
12  // Not using adaptive by default
13  use_adaptive = false;
14  if (threshold < 0 || threshold >= 1) {
15  MACH3LOG_INFO("NOTE: {} {}", name, file);
16  MACH3LOG_INFO("Principal component analysis but given the threshold for the principal components to be less than 0, or greater than (or equal to) 1. This will not work");
17  MACH3LOG_INFO("Please specify a number between 0 and 1");
18  MACH3LOG_INFO("You specified: ");
19  MACH3LOG_INFO("Am instead calling the usual non-PCA constructor...");
20  pca = false;
21  }
22 
23  InitFromFile(name, file);
24 
25  // Call the innocent helper function
26  if (pca) ConstructPCA(threshold, FirstPCA, LastPCA);
27 }
28 
29 // ********************************************
30 //Destructor
32 // ********************************************
33  delete[] randParams;
34  delete[] corr_throw;
35 
36  if (covMatrix != nullptr) delete covMatrix;
37  if (invCovMatrix != nullptr) delete invCovMatrix;
38  if (throwMatrix != nullptr) delete throwMatrix;
39  for(int i = 0; i < _fNumPar; i++) {
40  delete[] throwMatrixCholDecomp[i];
41  }
42  delete[] throwMatrixCholDecomp;
43 }
44 
45 // ********************************************
46 void ParameterHandlerBase::ConstructPCA(const double eigen_threshold, int FirstPCAdpar, int LastPCAdpar) {
47 // ********************************************
48  if(AdaptiveHandler) {
49  MACH3LOG_ERROR("Adaption has been enabled and now trying to enable PCA. Right now both configuration don't work with each other");
50  throw MaCh3Exception(__FILE__ , __LINE__ );
51  }
52 
53  PCAObj = std::make_unique<PCAHandler>();
54  //Check whether first and last pcadpar are set and if not just PCA everything
55  if(FirstPCAdpar == -999 || LastPCAdpar == -999) {
56  if(FirstPCAdpar == -999 && LastPCAdpar == -999) {
57  FirstPCAdpar = 0;
58  LastPCAdpar = covMatrix->GetNrows()-1;
59  }
60  else{
61  MACH3LOG_ERROR("You must either leave FirstPCAdpar and LastPCAdpar at -999 or set them both to something");
62  throw MaCh3Exception(__FILE__ , __LINE__ );
63  }
64  }
65 
66  PCAObj->ConstructPCA(covMatrix, FirstPCAdpar, LastPCAdpar, eigen_threshold, _fNumPar);
67  PCAObj->SetupPointers(&_fCurrVal, &_fPropVal);
68  // Make a note that we have now done PCA
69  pca = true;
70 }
71 
72 // ********************************************
73 void ParameterHandlerBase::InitFromFile(const std::string& name, const std::string& file) {
74 // ********************************************
75  // Set the covariance matrix from input ROOT file (e.g. flux, ND280, NIWG)
76  TFile *infile = new TFile(file.c_str(), "READ");
77  if (infile->IsZombie()) {
78  MACH3LOG_ERROR("Could not open input covariance ROOT file {} !!!", file);
79  MACH3LOG_ERROR("Was about to retrieve matrix with name {}", name);
80  throw MaCh3Exception(__FILE__ , __LINE__ );
81  }
82 
83  TMatrixDSym *CovMat = static_cast<TMatrixDSym*>(infile->Get(name.c_str()));
84 
85  if (!CovMat) {
86  MACH3LOG_ERROR("Could not find covariance matrix name {} in file {}", name, file);
87  MACH3LOG_ERROR("Are you really sure {} exists in the file?", name);
88  throw MaCh3Exception(__FILE__ , __LINE__ );
89  }
90 
91  PrintLength = 35;
92 
93  const int nThreads = M3::GetNThreads();
94  //KS: set Random numbers for each thread so each thread has different seed
95  //or for one thread if without MULTITHREAD
96  random_number.reserve(nThreads);
97  for (int iThread = 0; iThread < nThreads; iThread++) {
98  random_number.emplace_back(std::make_unique<TRandom3>(0));
99  }
100  // Set the covariance matrix
101  _fNumPar = CovMat->GetNrows();
102 
104  SetName(name);
105  MakePosDef(CovMat);
106  SetCovMatrix(CovMat);
107 
108  infile->Close();
109 
110  MACH3LOG_INFO("Created covariance matrix named: {}", GetName());
111  MACH3LOG_INFO("from file: {}", file);
112  delete infile;
113 }
114 
115 // ********************************************
116 void ParameterHandlerBase::EnableSpecialProposal(const YAML::Node& param, const int Index){
117 // ********************************************
118  doSpecialStepProposal = true;
119 
120  bool CircEnabled = false;
121  std::pair<double, double> circular_bounds;
122 
123  bool FlipEnabled = false;
124  std::string flip_group;
125  double flip_point;
126 
127  if (param["CircularBounds"]) {
128  CircEnabled = true;
129  circular_bounds = Get<std::pair<double, double>>(param["CircularBounds"], __FILE__, __LINE__);
130  }
131 
132  if (param["FlipParameter"]) {
133  FlipEnabled = true;
134  // grab flip group if it exists, otherwise use the parameter name as the group
135  if (param["FlipGroup"]) {
136  flip_group = Get<std::string>(param["FlipGroup"], __FILE__, __LINE__);
137  } else {
138  flip_group = GetParFancyName(Index);
139  }
140  flip_point = Get<double>(param["FlipParameter"], __FILE__, __LINE__);
141  }
142 
143  if (!CircEnabled && !FlipEnabled) {
144  MACH3LOG_ERROR("None of Special Proposal were enabled even though param {}, has SpecialProposal entry in Yaml", GetParFancyName(Index));
145  throw MaCh3Exception(__FILE__, __LINE__);
146  }
147 
148  if (CircEnabled) {
149  CircularBoundsIndex.push_back(Index);
150  CircularBoundsValues.push_back(circular_bounds);
151  MACH3LOG_INFO("Enabling CircularBounds for parameter {} with range [{}, {}]",
152  GetParFancyName(Index),
153  circular_bounds.first,
154  circular_bounds.second);
155  // KS: Make sure circular bounds are within physical bounds. If we are outside of physics bound MCMC will never explore such phase space region
156  if (circular_bounds.first < _fLowBound.at(Index) || circular_bounds.second > _fUpBound.at(Index)) {
157  MACH3LOG_ERROR("Circular bounds [{}, {}] for parameter {} exceed physical bounds [{}, {}]",
158  circular_bounds.first, circular_bounds.second,
159  GetParFancyName(Index),
160  _fLowBound.at(Index), _fUpBound.at(Index));
161  throw MaCh3Exception(__FILE__, __LINE__);
162  }
163  // KS: Make sure CircularPrior is applied only to param with flat prior. Sadly doesn't work with Gaussian
164  if(GetFlatPrior(Index) == false) {
165  MACH3LOG_ERROR("Enabled CircularPrior for parameter {}, which has gaussian prior", GetParFancyName(Index));
166  MACH3LOG_ERROR("This is not supported, CircularPrior only works with flat prior");
167  MACH3LOG_ERROR("Change FlatPrior in Parameter config to true");
168  throw MaCh3Exception(__FILE__, __LINE__);
169  }
170  }
171 
172  if (FlipEnabled) {
173  FlipGroup& group = FlipGroups[flip_group];
174  group.FlipParameterIndex.push_back(Index);
175  group.FlipParameterPoint.push_back(flip_point);
176 
177  MACH3LOG_INFO("Enabling Flipping for parameter {} in group {} with value {}",
178  GetParFancyName(Index),
179  flip_group,
180  flip_point);
181  }
182 
183  if (CircEnabled && FlipEnabled) {
184  if (flip_point < circular_bounds.first || flip_point > circular_bounds.second) {
185  MACH3LOG_ERROR("FlipParameter value {} for parameter {} is outside the CircularBounds [{}, {}]",
186  flip_point, GetParFancyName(Index), circular_bounds.first, circular_bounds.second);
187  throw MaCh3Exception(__FILE__, __LINE__);
188  }
189 
190  const double low = circular_bounds.first;
191  const double high = circular_bounds.second;
192 
193  // Sanity check: ensure flipping any x in [low, high] keeps the result in [low, high]
194  const double flipped_low = 2 * flip_point - low;
195  const double flipped_high = 2 * flip_point - high;
196  const double min_flip = std::min(flipped_low, flipped_high);
197  const double max_flip = std::max(flipped_low, flipped_high);
198 
199  if (min_flip < low || max_flip > high) {
200  MACH3LOG_ERROR("Flipping about point {} for parameter {} would leave circular bounds [{}, {}]",
201  flip_point, GetParFancyName(Index), low, high);
202  throw MaCh3Exception(__FILE__, __LINE__);
203  }
204  }
205 }
206 
207 // ********************************************
208 // Set the covariance matrix for this class
209 void ParameterHandlerBase::SetCovMatrix(TMatrixDSym *cov) {
210 // ********************************************
211  if (cov == nullptr) {
212  MACH3LOG_ERROR("Could not find covariance matrix you provided to {}", __func__ );
213  throw MaCh3Exception(__FILE__ , __LINE__ );
214  }
215  covMatrix = cov;
216 
217  invCovMatrix = static_cast<TMatrixDSym *>(cov->Clone());
218  invCovMatrix->Invert();
219  //KS: ROOT has bad memory management, using standard double means we can decrease most operation by factor 2 simply due to cache hits
220  for (int i = 0; i < _fNumPar; i++)
221  {
222  for (int j = 0; j < _fNumPar; ++j)
223  {
224  InvertCovMatrix[i][j] = (*invCovMatrix)(i,j);
225  }
226  }
227 
228  SetThrowMatrix(cov);
229 }
230 // ********************************************
231 void ParameterHandlerBase::ReserveMemory(const int SizeVec) {
232 // ********************************************
233  if (SizeVec <= 0) {
234  MACH3LOG_CRITICAL("Covariance matrix {} has {} entries!", GetName(), SizeVec);
235  throw MaCh3Exception(__FILE__ , __LINE__ );
236  }
237 
238  _fNames = std::vector<std::string>(SizeVec);
239  _fFancyNames = std::vector<std::string>(SizeVec);
240 
241  _fPreFitValue = std::vector<double>(SizeVec, 1.0);
242  _fError = std::vector<double>(SizeVec, 1.0);
243  _fCurrVal = std::vector<double>(SizeVec, 0.0);
244  _fPropVal = std::vector<M3::float_t>(SizeVec, 0.0);
245  _fLowBound = std::vector<double>(SizeVec, -999.99);
246  _fUpBound = std::vector<double>(SizeVec, 999.99);
247  _fFlatPrior = std::vector<bool>(SizeVec, false);
248  _fIndivStepScale = std::vector<double>(SizeVec, 1.0);
249 
250  corr_throw = new double[SizeVec];
251  // set random parameter vector (for correlated steps)
252  randParams = new double[SizeVec];
253 
254  // Set the defaults to true
255  for(int i = 0; i < SizeVec; i++) {
256  corr_throw[i] = 0.0;
257  randParams[i] = 0.0;
258  }
259 
260  InvertCovMatrix.resize(SizeVec, std::vector<double>(SizeVec, 0.0));
261  throwMatrixCholDecomp = new double*[SizeVec]();
262  // Set the defaults to true
263  for(int i = 0; i < SizeVec; i++) {
264  throwMatrixCholDecomp[i] = new double[SizeVec]();
265  for (int j = 0; j < SizeVec; j++) {
266  throwMatrixCholDecomp[i][j] = 0.;
267  }
268  }
269 
270  _fGlobalStepScale = 1.0;
271 }
272 
273 // ********************************************
274 // Set all the covariance matrix parameters to a user-defined value
275 // Might want to split this
276 void ParameterHandlerBase::SetPar(const int i , const double val) {
277 // ********************************************
278  MACH3LOG_DEBUG("Over-riding {}: _fPropVal ({}), _fCurrVal ({}), _fPreFitValue ({}) to ({})",
279  GetParFancyName(i), _fPropVal[i], _fCurrVal[i], _fPreFitValue[i], val);
280 
281  _fPropVal[i] = static_cast<M3::float_t>(val);
282  _fCurrVal[i] = val;
283  _fPreFitValue[i] = val;
284 
285  // Transfer the parameter values to the PCA basis
286  if (pca) PCAObj->TransferToPCA();
287 }
288 
289 // ********************************************
290 std::vector<double> ParameterHandlerBase::GetProposed() const {
291 // ********************************************
292  std::vector<double> props(_fNumPar);
293  for (int i = 0; i < _fNumPar; ++i) props[i] = _fPropVal[i];
294  return props;
295 }
296 
297 // *************************************
298 // Throw the parameters according to the covariance matrix
299 // This shouldn't be used in MCMC code ase it can break Detailed Balance;
301 // *************************************
302  // First draw new randParams
303  Randomize();
304 
306  #pragma GCC diagnostic push
307  #pragma GCC diagnostic ignored "-Wuseless-cast"
308  // KS: We use PCA very rarely on top PCA functionality isn't implemented for this function.
309  // Use __builtin_expect to give compiler a hint which option is more likely, which should help
310  // with better optimisation. This isn't critical but more to have example
311  if (__builtin_expect(!pca, 1)) {
312  #ifdef MULTITHREAD
313  #pragma omp parallel for
314  #endif
315  for (int i = 0; i < _fNumPar; ++i) {
316  // Check if parameter is fixed first: if so don't randomly throw
317  if (IsParameterFixed(i)) continue;
318  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i] + corr_throw[i]);
319 
320  int throws = 0;
321  // Try again if we the initial parameter proposal falls outside of the range of the parameter
322  while (_fPropVal[i] > _fUpBound[i] || _fPropVal[i] < _fLowBound[i]) {
323  randParams[i] = random_number[M3::GetThreadIndex()]->Gaus(0, 1);
324  const double corr_throw_single = M3::MatrixVectorMultiSingle(throwMatrixCholDecomp, randParams, _fNumPar, i);
325  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i] + corr_throw_single);
326  if (throws > 10000)
327  {
328  //KS: Since we are multithreading there is danger that those messages
329  //will be all over the place, small price to pay for faster code
330  MACH3LOG_WARN("Tried {} times to throw parameter {} but failed", throws, i);
331  MACH3LOG_WARN("Matrix: {}", matrixName);
332  MACH3LOG_WARN("Param: {}", _fNames[i]);
333  MACH3LOG_WARN("Setting _fPropVal: {} to {}", _fPropVal[i], _fPreFitValue[i]);
334  MACH3LOG_WARN("I live at {}:{}", __FILE__, __LINE__);
335  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i]);
336  //throw MaCh3Exception(__FILE__ , __LINE__ );
337  }
338  throws++;
339  }
340  _fCurrVal[i] = _fPropVal[i];
341  }
342  }
343  else
344  {
345  PCAObj->ThrowParameters(random_number, throwMatrixCholDecomp,
348  } // end if pca
349  #pragma GCC diagnostic pop
350  // KS: At the end once we are happy with proposal do special proposal
352 }
353 
354 // *************************************
355 // Throw each parameter within their 1 sigma range
356 // Used to start the chain in different states
358 // *************************************
359  #pragma GCC diagnostic push
360  #pragma GCC diagnostic ignored "-Wuseless-cast"
361  // Have the 1 sigma for each parameter in each covariance class, sweet!
362  // Don't want to change the prior array because that's what determines our likelihood
363  // Want to change the _fPropVal, _fCurrVal, _fPreFitValue
364  // _fPreFitValue and the others will already be set
365  for (int i = 0; i < _fNumPar; ++i) {
366  // Check if parameter is fixed first: if so don't randomly throw
367  if (IsParameterFixed(i)) continue;
368  // Check that the sigma range is larger than the parameter range
369  // If not, throw in the valid parameter range instead
370  const double paramrange = _fUpBound[i] - _fLowBound[i];
371  const double sigma = sqrt((*covMatrix)(i,i));
372  double throwrange = sigma;
373  if (paramrange < sigma) throwrange = paramrange;
374 
375  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i] + random_number[0]->Gaus(0, 1)*throwrange);
376  // Try again if we the initial parameter proposal falls outside of the range of the parameter
377  int throws = 0;
378  while (_fPropVal[i] > _fUpBound[i] || _fPropVal[i] < _fLowBound[i]) {
379  if (throws > 1000) {
380  MACH3LOG_WARN("Tried {} times to throw parameter {} but failed", throws, i);
381  MACH3LOG_WARN("Matrix: {}", matrixName);
382  MACH3LOG_WARN("Param: {}", _fNames[i]);
383  throw MaCh3Exception(__FILE__ , __LINE__ );
384  }
385  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i] + random_number[0]->Gaus(0, 1)*throwrange);
386  throws++;
387  }
388  MACH3LOG_INFO("Setting current step in {} param {} = {} from {}", matrixName, i, _fPropVal[i], _fCurrVal[i]);
389  _fCurrVal[i] = _fPropVal[i];
390  }
391  #pragma GCC diagnostic pop
392  if (pca) PCAObj->TransferToPCA();
393 
394  // KS: At the end once we are happy with proposal do special proposal
396 }
397 
398 // *************************************
399 // Set a single parameter
400 void ParameterHandlerBase::SetSingleParameter(const int parNo, const double parVal) {
401 // *************************************
402  _fPropVal[parNo] = static_cast<M3::float_t>(parVal);
403  _fCurrVal[parNo] = parVal;
404  MACH3LOG_DEBUG("Setting {} (parameter {}) to {})", GetParFancyName(parNo), parNo, parVal);
405  if (pca) PCAObj->TransferToPCA();
406 }
407 
408 // ********************************************
409 void ParameterHandlerBase::SetParCurrProp(const int parNo, const double parVal) {
410 // ********************************************
411  _fPropVal[parNo] = static_cast<M3::float_t>(parVal);
412  _fCurrVal[parNo] = parVal;
413  MACH3LOG_DEBUG("Setting {} (parameter {}) to {})", GetParFancyName(parNo), parNo, parVal);
414  if (pca) PCAObj->TransferToPCA();
415 }
416 
417 // ************************************************
418 // Propose a step for the set of systematics parameters this covariance class holds
420 // ************************************************
421  // Make the random numbers for the step proposal
422  Randomize();
423  CorrelateSteps();
424 
425  // KS: According to Dr Wallace we update using previous not proposed step
426  // this way we do special proposal after adaptive after.
427  // This way we can shortcut and skip rest of proposal
428  if(!doSpecialStepProposal) return;
429 
431 }
432 
433 // ************************************************
435 // ************************************************
437 
438  // HW It should now automatically set dcp to be with [-pi, pi]
439  for (size_t i = 0; i < CircularBoundsIndex.size(); ++i) {
440  const int index = CircularBoundsIndex[i];
441  if(!IsParameterFixed(index))
442  CircularParBounds(index, CircularBoundsValues[i].first, CircularBoundsValues[i].second);
443  }
444 
445  // // Okay now we've done the standard steps, we can add in our nice flips hierarchy flip first
446  for (const auto& [group_name, group] : FlipGroups) {
447  FlipParameterGroup(group_name);
448  }
449 }
450 
451 // ************************************************
452 // "Randomize" the parameters in the covariance class for the proposed step
453 // Used the proposal kernel and the current parameter value to set proposed step
454 // Also get a new random number for the randParams
456 // ************************************************
457  if (!pca) {
458  //KS: By multithreading here we gain at least factor 2 with 8 threads with ND only fit
459  #ifdef MULTITHREAD
460  #pragma omp parallel for
461  #endif
462  for (int i = 0; i < _fNumPar; ++i) {
463  // If parameter isn't fixed
464  if (!IsParameterFixed(i) > 0.0) {
465  randParams[i] = random_number[M3::GetThreadIndex()]->Gaus(0, 1);
466  // If parameter IS fixed
467  } else {
468  randParams[i] = 0.0;
469  }
470  } // end for
471  // If we're in the PCA basis we instead throw parameters there (only _fNumParPCA parameter)
472  } else {
473  // Scale the random parameters by the sqrt of eigen values for the throw
474  #ifdef MULTITHREAD
475  #pragma omp parallel for
476  #endif
477  for (int i = 0; i < PCAObj->GetNumberPCAedParameters(); ++i)
478  {
479  // If parameter IS fixed or out of bounds
480  if (PCAObj->IsParameterFixedPCA(i)) {
481  randParams[i] = 0.0;
482  } else {
483  randParams[i] = random_number[M3::GetThreadIndex()]->Gaus(0,1);
484  }
485  }
486  }
487 }
488 
489 // ************************************************
490 // Correlate the steps by setting the proposed step of a parameter to its current value + some correlated throw
492 // ************************************************
493  //KS: Using custom function compared to ROOT one with 8 threads we have almost factor 2 performance increase, by replacing TMatrix with just double we increase it even more
495 
496  // If not doing PCA
497  if (!pca) {
498  #ifdef MULTITHREAD
499  #pragma omp parallel for
500  #endif
501  for (int i = 0; i < _fNumPar; ++i) {
502  if (!IsParameterFixed(i) > 0.) {
503  #pragma GCC diagnostic push
504  #pragma GCC diagnostic ignored "-Wuseless-cast"
506  #pragma GCC diagnostic pop
507  }
508  }
509  // If doing PCA throw uncorrelated in PCA basis (orthogonal basis by definition)
510  } else {
512  }
513 }
514 // ********************************************
515 // Update so that current step becomes the previously proposed step
517 // ********************************************
518  if (!pca) {
519  #ifdef MULTITHREAD
520  #pragma omp parallel for
521  #endif
522  for (int i = 0; i < _fNumPar; ++i) {
523  // Update state so that current state is proposed state
524  _fCurrVal[i] = _fPropVal[i];
525  }
526  } else {
527  PCAObj->AcceptStep();
528  }
529 
530  if (AdaptiveHandler) {
531  AdaptiveHandler->IncrementAcceptedSteps();
532  }
533 }
534 
535 #pragma GCC diagnostic push
536 #pragma GCC diagnostic ignored "-Wuseless-cast"
537 // *************************************
538 //HW: This method is a tad hacky but modular arithmetic gives me a headache.
539 void ParameterHandlerBase::CircularParBounds(const int index, const double LowBound, const double UpBound) {
540 // *************************************
541  if(_fPropVal[index] > UpBound) {
542  _fPropVal[index] = static_cast<M3::float_t>(LowBound + std::fmod(_fPropVal[index] - UpBound, UpBound - LowBound));
543  } else if (_fPropVal[index] < LowBound) {
544  _fPropVal[index] = static_cast<M3::float_t>(UpBound - std::fmod(LowBound - _fPropVal[index], UpBound - LowBound));
545  }
546 }
547 
548 // *************************************
549 void ParameterHandlerBase::FlipParameterGroup(const std::string group) {
550 // *************************************
551  if(random_number[0]->Uniform() < 0.5) {
552  for (size_t i = 0; i < FlipGroups[group].FlipParameterIndex.size(); ++i) {
553  const int index = FlipGroups[group].FlipParameterIndex[i];
554  if(!IsParameterFixed(index)) {
555  const double flip_point = FlipGroups[group].FlipParameterPoint[i];
556  _fPropVal[index] = static_cast<M3::float_t>(2 * flip_point - _fPropVal[index]);
557  }
558  }
559  }
560 }
561 
562 
563 #pragma GCC diagnostic pop
564 // ********************************************
565 // Function to print the prior values
567 // ********************************************
568  MACH3LOG_INFO("Prior values for {} ParameterHandler:", GetName());
569  for (int i = 0; i < _fNumPar; i++) {
570  MACH3LOG_INFO(" {} {} ", GetParFancyName(i), GetParPreFit(i));
571  }
572 }
573 
574 // ********************************************
575 // Function to print the prior, current and proposed values
577 // ********************************************
578  MACH3LOG_INFO("Printing parameters for {}", GetName());
579  // Dump out the PCA parameters too
580  if (pca) {
581  PCAObj->Print();
582  }
583  MACH3LOG_INFO("{:<30} {:<10} {:<10} {:<10}", "Name", "Prior", "Current", "Proposed");
584  for (int i = 0; i < _fNumPar; ++i) {
585  MACH3LOG_INFO("{:<30} {:<10.2f} {:<10.2f} {:<10.2f}", GetParFancyName(i), _fPreFitValue[i], _fCurrVal[i], _fPropVal[i]);
586  }
587 }
588 
589 // ********************************************
590 // Get the likelihood in the case where we want to include priors on the parameters
591 // _fFlatPrior stores if we want to evaluate the likelihood for the given parameter
592 // true = don't evaluate likelihood (so run without a prior)
593 // false = evaluate likelihood (so run with a prior)
595 // ********************************************
596  double logL = 0.0;
597  #ifdef MULTITHREAD
598  #pragma omp parallel for reduction(+:logL)
599  #endif
600  for(int i = 0; i < _fNumPar; ++i) {
601  if(_fFlatPrior[i]){
602  //HW: Flat prior, no need to calculate anything
603  continue;
604  }
605  // KS: Precalculate Diff once per "i" without doing this for every "j"
606  const double Diff = _fPropVal[i] - _fPreFitValue[i];
607  #ifdef MULTITHREAD
608  #pragma omp simd
609  #endif
610  for (int j = 0; j <= i; ++j) {
611  if (!_fFlatPrior[j]) {
612  //KS: Since matrix is symmetric we can calculate non diagonal elements only once and multiply by 2, can bring up to factor speed decrease.
613  double scale = (i != j) ? 1. : 0.5;
614  logL += scale * Diff * (_fPropVal[j] - _fPreFitValue[j])*InvertCovMatrix[i][j];
615  }
616  }
617  }
618  return logL;
619 }
620 
621 // ********************************************
623 // ********************************************
624  int NOutside = 0;
625  for (int i = 0; i < _fNumPar; ++i) {
626  // KS: Count how many parameters are outside bounds using branchless logic
627  // faster by at least factor two
628  // Do not multithread even with 5k params no gains
629  NOutside += (_fPropVal[i] > _fUpBound[i]) | (_fPropVal[i] < _fLowBound[i]);
630  }
631  return NOutside;
632 }
633 
634 // ********************************************
636 // ********************************************
637  // Default behaviour is to reject negative values + do std llh calculation
638  const int NOutside = CheckBounds();
639 
640  if(NOutside > 0) return NOutside*M3::_LARGE_LOGL_;
641 
642  return CalcLikelihood();
643 }
644 
645 // ********************************************
646 // Sets the proposed parameters to the prior values
647 void ParameterHandlerBase::SetParameters(const std::vector<double>& pars) {
648 // ********************************************
649  #pragma GCC diagnostic push
650  #pragma GCC diagnostic ignored "-Wuseless-cast"
651  // If empty, set the proposed to prior
652  if (pars.empty()) {
653  // For xsec this means setting to the prior (because prior is the prior)
654  for (int i = 0; i < _fNumPar; i++) {
655  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i]);
656  }
657  // If not empty, set the parameters to the specified
658  } else {
659  if (pars.size() != static_cast<size_t>(_fNumPar)) {
660  MACH3LOG_ERROR("Parameter arrays of incompatible size! Not changing parameters! {} has size {} but was expecting {}", matrixName, pars.size(), _fNumPar);
661  throw MaCh3Exception(__FILE__ , __LINE__ );
662  }
663  int parsSize = int(pars.size());
664  for (int i = 0; i < parsSize; i++) {
665  //Make sure that you are actually passing a number to set the parameter to
666  if(std::isnan(pars[i])) {
667  MACH3LOG_ERROR("Trying to set parameter value to a nan for parameter {} in matrix {}. This will not go well!", GetParName(i), matrixName);
668  throw MaCh3Exception(__FILE__ , __LINE__ );
669  } else {
670  _fPropVal[i] = static_cast<M3::float_t>(pars[i]);
671  }
672  }
673  }
674  // And if pca make the transfer
675  if (pca) {
676  PCAObj->TransferToPCA();
677  PCAObj->TransferToParam();
678  }
679  #pragma GCC diagnostic pop
680 }
681 
682 // ********************************************
683 void ParameterHandlerBase::SetBranches(TTree &tree, bool SaveProposal) {
684 // ********************************************
685  // loop over parameters and set a branch
686  for (int i = 0; i < _fNumPar; ++i) {
687  tree.Branch(_fNames[i].c_str(), &_fCurrVal[i], Form("%s/D", _fNames[i].c_str()));
688  }
689  // When running PCA, also save PCA parameters
690  if (pca) {
691  PCAObj->SetBranches(tree, SaveProposal, _fNames);
692  }
693  if(SaveProposal)
694  {
695  // loop over parameters and set a branch
696  for (int i = 0; i < _fNumPar; ++i) {
697  tree.Branch(Form("%s_Prop", _fNames[i].c_str()), &_fPropVal[i], Form("%s_Prop/D", _fNames[i].c_str()));
698  }
699  }
700  if(use_adaptive && AdaptiveHandler->GetUseRobbinsMonro()){
701  tree.Branch(Form("GlobalStepScale_%s", GetName().c_str()), &_fGlobalStepScale, Form("GlobalStepScale_%s/D", GetName().c_str()));
702  }
703 }
704 
705 // ********************************************
706 void ParameterHandlerBase::SetStepScale(const double scale, const bool verbose) {
707 // ********************************************
708  if(scale <= 0) {
709  MACH3LOG_ERROR("You are trying so set StepScale to 0 or negative this will not work");
710  throw MaCh3Exception(__FILE__ , __LINE__ );
711  }
712 
713  if(verbose){
714  MACH3LOG_INFO("{} setStepScale() = {}", GetName(), scale);
715  const double SuggestedScale = 2.38/std::sqrt(_fNumPar);
716  if(std::fabs(scale - SuggestedScale)/SuggestedScale > 1) {
717  MACH3LOG_WARN("Defined Global StepScale is {}, while suggested suggested {}", scale, SuggestedScale);
718  }
719  }
720  _fGlobalStepScale = scale;
721 }
722 
723 // ********************************************
724 int ParameterHandlerBase::GetParIndex(const std::string& name) const {
725 // ********************************************
726  int Index = M3::_BAD_INT_;
727  for (int i = 0; i <_fNumPar; ++i) {
728  if(name == _fFancyNames[i]) {
729  Index = i;
730  break;
731  }
732  }
733  return Index;
734 }
735 
736 // ********************************************
738 // ********************************************
739  // Check if the parameter is fixed and if not, toggle fix it
740  for (int i = 0; i < _fNumPar; ++i)
742 }
743 
744 // ********************************************
746 // ********************************************
747  // Check if the parameter is fixed and if not, toggle fix it
749 }
750 
751 // ********************************************
752 void ParameterHandlerBase::SetFixParameter(const std::string& name) {
753 // ********************************************
754  // Check if the parameter is fixed and if not, toggle fix it
755  if(!IsParameterFixed(name)) ToggleFixParameter(name);
756 }
757 
758 // ********************************************
760 // ********************************************
761  // Check if the parameter is fixed and if not, toggle fix it
762  for (int i = 0; i < _fNumPar; ++i)
764 }
765 
766 // ********************************************
768 // ********************************************
769  // Check if the parameter is fixed and if not, toggle fix it
771 }
772 
773 // ********************************************
774 void ParameterHandlerBase::SetFreeParameter(const std::string& name) {
775 // ********************************************
776  // Check if the parameter is fixed and if not, toggle fix it
777  if(IsParameterFixed(name)) ToggleFixParameter(name);
778 }
779 
780 // ********************************************
782 // ********************************************
783  if(!pca) {
784  if (i > _fNumPar) {
785  MACH3LOG_ERROR("Can't {} for parameter {} because size of covariance ={}", __func__, i, _fNumPar);
786  MACH3LOG_ERROR("Fix this in your config file please!");
787  throw MaCh3Exception(__FILE__ , __LINE__ );
788  } else {
789  _fError[i] *= -1.0;
790  if(IsParameterFixed(i)) MACH3LOG_INFO("Setting {}(parameter {}) to fixed at {}", GetParFancyName(i), i, _fCurrVal[i]);
791  else MACH3LOG_INFO("Setting {}(parameter {}) free", GetParFancyName(i), i);
792  }
793  if( (_fCurrVal[i] > _fUpBound[i] || _fCurrVal[i] < _fLowBound[i]) && IsParameterFixed(i) ) {
794  MACH3LOG_ERROR("Parameter {} (index {}) is fixed at {}, which is outside of its bounds [{}, {}]", GetParFancyName(i), i, _fCurrVal[i], _fLowBound[i], _fUpBound[i]);
795  throw MaCh3Exception(__FILE__ , __LINE__ );
796  }
797  } else {
798  PCAObj->ToggleFixParameter(i, _fNames);
799  }
800 }
801 
802 // ********************************************
803 void ParameterHandlerBase::ToggleFixParameter(const std::string& name) {
804 // ********************************************
805  const int Index = GetParIndex(name);
806  if(Index != M3::_BAD_INT_) {
807  ToggleFixParameter(Index);
808  return;
809  }
810  MACH3LOG_WARN("I couldn't find parameter with name {}, therefore will not fix it", name);
811 }
812 
813 // ********************************************
814 bool ParameterHandlerBase::IsParameterFixed(const std::string& name) const {
815 // ********************************************
816  const int Index = GetParIndex(name);
817  if(Index != M3::_BAD_INT_) {
818  return IsParameterFixed(Index);
819  }
820 
821  MACH3LOG_WARN("I couldn't find parameter with name {}, therefore don't know if it fixed", name);
822  return false;
823 }
824 
825 // ********************************************
826 void ParameterHandlerBase::SetFlatPrior(const int i, const bool eL) {
827 // ********************************************
828  if (i > _fNumPar) {
829  MACH3LOG_INFO("Can't {} for Cov={}/Param={} because size of Covariance = {}", __func__, GetName(), i, _fNumPar);
830  MACH3LOG_ERROR("Fix this in your config file please!");
831  throw MaCh3Exception(__FILE__ , __LINE__ );
832  } else {
833  if(eL){
834  MACH3LOG_INFO("Setting {} (parameter {}) to flat prior", GetParName(i), i);
835  }
836  else{
837  // HW :: This is useful
838  MACH3LOG_INFO("Setting {} (parameter {}) to non-flat prior", GetParName(i), i);
839  }
840  _fFlatPrior[i] = eL;
841  }
842 }
843 
844 // ********************************************
845 void ParameterHandlerBase::SetIndivStepScale(const std::vector<double>& stepscale) {
846 // ********************************************
847  if (static_cast<int>(stepscale.size()) != _fNumPar)
848  {
849  MACH3LOG_WARN("Stepscale vector not equal to number of parameters. Quitting..");
850  MACH3LOG_WARN("Size of argument vector: {}", stepscale.size());
851  MACH3LOG_WARN("Expected size: {}", _fNumPar);
852  return;
853  }
854 
855  for (int iParam = 0 ; iParam < _fNumPar; iParam++) {
856  _fIndivStepScale[iParam] = stepscale[iParam];
857  }
859 }
860 
861 // ********************************************
863 // ********************************************
864  MACH3LOG_INFO("============================================================");
865  MACH3LOG_INFO("{:<{}} | {:<11}", "Parameter:", PrintLength, "Step scale:");
866  for (int iParam = 0; iParam < _fNumPar; iParam++) {
867  MACH3LOG_INFO("{:<{}} | {:<11}", _fFancyNames[iParam].c_str(), PrintLength, _fIndivStepScale[iParam]);
868  }
869  MACH3LOG_INFO("============================================================");
870 }
871 
872 // ********************************************
873 //Makes sure that matrix is positive-definite by adding a small number to on-diagonal elements
874 void ParameterHandlerBase::MakePosDef(TMatrixDSym *cov, bool verbose) {
875 // ********************************************
876  if(cov == nullptr){
877  cov = &*covMatrix;
878  MACH3LOG_WARN("Passed nullptr to cov matrix in {}", matrixName);
879  }
880 
881  int n_attempts = M3::MakeMatrixPosDef(cov);
882 
883  if(n_attempts > 0 && verbose) {
884  MACH3LOG_WARN("Covariance matrix {} was not positive-definite, made it positive-definite after {} attempts", matrixName, n_attempts);
885  }
886 }
887 
888 // ********************************************
890 // ********************************************
891  std::vector<double> stepScales(_fNumPar, 1.0);
892  _fGlobalStepScale = 1.0;
893  SetIndivStepScale(stepScales);
894 }
895 
896 // ********************************************
898 // ********************************************
899  if (!param_skip_adapt_flags.size()) {
900  MACH3LOG_ERROR("Parameter skip adapt flags not set, cannot set individual step scales for skipped parameters.");
901  throw MaCh3Exception(__FILE__ , __LINE__ );
902  }
903  // HH: Cancel the effect of global step scale change for parameters that are not adapting
904  for (int i = 0; i <_fNumPar; i++) {
905  if (param_skip_adapt_flags[i]) {
907  }
908  }
909  MACH3LOG_DEBUG("Updating individual step scales for non-adapting parameters to cancel global step scale change.");
910  MACH3LOG_DEBUG("Global step scale initial: {}, current: {}", _fGlobalStepScaleInitial, _fGlobalStepScale);
911 }
912 
913 // ********************************************
914 // HW: Code for throwing from separate throw matrix, needs to be set after init to ensure pos-def
915 void ParameterHandlerBase::SetThrowMatrix(const TMatrixDSym *cov) {
916 // ********************************************
917  if (cov == nullptr) {
918  MACH3LOG_ERROR("Could not find covariance matrix you provided to {}", __func__);
919  throw MaCh3Exception(__FILE__ , __LINE__ );
920  }
921 
922  if (covMatrix->GetNrows() != cov->GetNrows()) {
923  MACH3LOG_ERROR("Matrix given for throw Matrix is not the same size as the covariance matrix stored in object!");
924  MACH3LOG_ERROR("Stored covariance matrix size: {}", covMatrix->GetNrows());
925  MACH3LOG_ERROR("Given matrix size: {}", cov->GetNrows());
926  throw MaCh3Exception(__FILE__ , __LINE__ );
927  }
928 
929  throwMatrix = static_cast<TMatrixDSym*>(cov->Clone());
930  if(use_adaptive && AdaptiveHandler->AdaptionUpdate()) MakeClosestPosDef(throwMatrix);
931  else {
932  // HW: Prevent spam from adaptive handler
933  bool verbose = AdaptiveHandler ? AdaptiveHandler->GetTotalSteps() < 2 : true;
934  MakePosDef(throwMatrix, verbose);
935  }
936 
937  auto throwMatrix_CholDecomp = M3::GetCholeskyDecomposedMatrix(*throwMatrix, matrixName);
938 
939  //KS: ROOT has bad memory management, using standard double means we can decrease most operation by factor 2 simply due to cache hits
940  #ifdef MULTITHREAD
941  #pragma omp parallel for collapse(2)
942  #endif
943  for (int i = 0; i < _fNumPar; ++i)
944  {
945  for (int j = 0; j < _fNumPar; ++j)
946  {
947  throwMatrixCholDecomp[i][j] = throwMatrix_CholDecomp[i][j];
948  }
949  }
950 }
951 
952 // ********************************************
953 void ParameterHandlerBase::SetSubThrowMatrix(int first_index, int last_index,
954  TMatrixDSym const &subcov) {
955 // ********************************************
956  if ((last_index - first_index) >= subcov.GetNrows()) {
957  MACH3LOG_ERROR("Trying to SetSubThrowMatrix into range: ({},{}) with a "
958  "submatrix with only {} rows {}",
959  first_index, last_index, subcov.GetNrows(), __func__);
960  throw MaCh3Exception(__FILE__, __LINE__);
961  }
962 
963  TMatrixDSym *current_ThrowMatrix =
964  static_cast<TMatrixDSym *>(throwMatrix->Clone());
965  for (int i = first_index; i <= last_index; ++i) {
966  for (int j = first_index; j <= last_index; ++j) {
967  current_ThrowMatrix->operator()(i, j) =
968  subcov(i - first_index, j - first_index);
969  }
970  }
971 
972  SetThrowMatrix(current_ThrowMatrix);
973  delete current_ThrowMatrix;
974 }
975 
976 // ********************************************
978 // ********************************************
979  delete throwMatrix;
980  throwMatrix = nullptr;
981  SetThrowMatrix(cov);
982 }
983 
984 
985 // ********************************************
987 // ********************************************
988  for (const auto& [_, group] : FlipGroups) {
989  for (size_t i = 0; i < group.FlipParameterIndex.size(); ++i) {
990  const int index = group.FlipParameterIndex[i];
991  if(!param_skip_adapt_flags[index]) {
992  MACH3LOG_ERROR("You enabled adaption for parameter which has enabled flipping ({})", _fFancyNames[index]);
993  MACH3LOG_ERROR("Right now flipping and adapting doesn't work very well");
994  MACH3LOG_ERROR("Please skip adaption for param {}, using ParametersToSkip option in config", _fFancyNames[index]);
995  throw MaCh3Exception(__FILE__, __LINE__);
996  }
997  }
998  }
999 
1000  // HH: Loop over correlations to check if any skipped parameter is correlated with adapted one
1001  // We don't want to change one parameter while keeping the other fixed as this would
1002  // lead to weird penalty terms in the prior after adapting
1003  double max_correlation = 0.01; // Define a threshold for significant correlation above which we throw an error
1004  for (int i = 0; i < _fNumPar; ++i) {
1005  for (int j = 0; j <= i; ++j) {
1006  // The symmetry should have been checked during the Init phase
1008  double corr = (*covMatrix)(i,j)/std::sqrt((*covMatrix)(i,i)*(*covMatrix)(j,j));
1009  if(std::fabs(corr) > max_correlation) {
1010  MACH3LOG_ERROR("Correlation between skipped parameter {} ({}) and non-skipped parameter {} ({}) is {:.6e}, above the allowed threshold of {:.6e}.",
1011  i, _fFancyNames[i], j, _fFancyNames[j], corr, max_correlation);
1012  throw MaCh3Exception(__FILE__, __LINE__);
1013  }
1014  }
1015  }
1016  }
1017 }
1018 
1019 // ********************************************
1020 // HW : Here be adaption
1021 void ParameterHandlerBase::InitialiseAdaption(const YAML::Node& adapt_manager) {
1022 // ********************************************
1023  if(PCAObj){
1024  MACH3LOG_ERROR("PCA has been enabled and now trying to enable Adaption. Right now both configuration don't work with each other");
1025  throw MaCh3Exception(__FILE__ , __LINE__ );
1026  }
1027  if(AdaptiveHandler){
1028  MACH3LOG_ERROR("Adaptive Handler has already been initialise can't do it again so skipping.");
1029  return;
1030  }
1031  AdaptiveHandler = std::make_unique<AdaptiveMCMCHandler>();
1032 
1033  // HH: Backing up _fIndivStepScale and _fGlobalStepScale before adaption
1036 
1037  // HH: adding these here because they will be used to set the individual step scales for non-adapting parameters
1038  auto params_to_skip = GetFromManager<std::vector<std::string>>(adapt_manager["AdaptionOptions"]["Covariance"][matrixName]["ParametersToSkip"], {}, __FILE__ , __LINE__);
1039  // Build a list of skip flags
1040  param_skip_adapt_flags.resize(_fNumPar, false);
1041  for (int i = 0; i <_fNumPar; ++i) {
1043  }
1044 
1045  // Now we read the general settings [these SHOULD be common across all matrices!]
1046  bool success = AdaptiveHandler->InitFromConfig(adapt_manager, matrixName,
1049  );
1050  if (success) {
1051  // Ensure there is no misconfiguration in adaption config
1052  SanitizeAdaption();
1053  AdaptiveHandler->Print();
1054  } else {
1055  MACH3LOG_INFO("Not using adaptive MCMC for {}. Checking external matrix options...", matrixName);
1056  }
1057 
1058  // HH: Adjusting the external matrix reading logic such that you can not do adaptive
1059  // and still read an external matrix
1060  // Logic:
1061  // if read external matrix:
1062  // set throw matrix regardless of adaptive or not
1063  // else:
1064  // if adaptive:
1065  // create new adaptive matrix from scratch
1066  // else:
1067  // do nothing
1068  if(GetFromManager<bool>(adapt_manager["AdaptionOptions"]["Covariance"][matrixName]["UseExternalMatrix"], false, __FILE__ , __LINE__)) {
1069  // Finally, we accept that we want to read the matrix from a file!
1070  auto external_file_name = GetFromManager<std::string>(adapt_manager["AdaptionOptions"]["Covariance"][matrixName]["ExternalMatrixFileName"], "", __FILE__ , __LINE__);
1071  auto external_matrix_name = GetFromManager<std::string>(adapt_manager["AdaptionOptions"]["Covariance"][matrixName]["ExternalMatrixName"], "", __FILE__ , __LINE__);
1072  auto external_mean_name = GetFromManager<std::string>(adapt_manager["AdaptionOptions"]["Covariance"][matrixName]["ExternalMeansName"], "", __FILE__ , __LINE__);
1073 
1074  AdaptiveHandler->SetThrowMatrixFromFile(external_file_name, external_matrix_name, external_mean_name, use_adaptive);
1075  SetThrowMatrix(AdaptiveHandler->GetAdaptiveCovariance());
1076 
1078  // HH: Set individual step scales for non-adapting parameters to the default individual step scales
1079  // global step scale should be 1 so no need to adjust for that
1081 
1082  MACH3LOG_INFO("Successfully Set External Throw Matrix Stored in {}", external_file_name);
1083  } else {
1084  MACH3LOG_INFO("Not using external matrix for {}", matrixName);
1085  if (!success) return; // Not adaptive either so nothing to do
1086  MACH3LOG_INFO("Initialising adaption from scratch");
1087  // If we don't have a covariance matrix to start from for adaptive tune we need to make one!
1088  use_adaptive = true;
1089  AdaptiveHandler->CheckMatrixValidityForAdaption(GetCovMatrix());
1090  AdaptiveHandler->CreateNewAdaptiveCovariance();
1091  return;
1092  }
1093 }
1094 
1095 // ********************************************
1096 // Truely adaptive MCMC!
1098 // ********************************************
1099  // Updates adaptive matrix
1100  // First we update the total means
1101 
1102  // Skip this if we're at a large number of steps
1103  if(AdaptiveHandler->SkipAdaption()) {
1104  AdaptiveHandler->IncrementNSteps();
1105  return;
1106  }
1107 
1109  if(AdaptiveHandler->GetUseRobbinsMonro()){
1110  bool verbose=false;
1111  #ifdef MACH3_DEBUG
1112  verbose=true;
1113  #endif
1114  AdaptiveHandler->UpdateRobbinsMonroScale();
1115  SetStepScale(AdaptiveHandler->GetAdaptionScale(), verbose);
1117  }
1118 
1119  // Call main adaption function
1120  AdaptiveHandler->UpdateAdaptiveCovariance();
1121 
1122  // Set scales to 1 * optimal scale
1123  if(AdaptiveHandler->IndivStepScaleAdapt()) {
1125  SetStepScale(AdaptiveHandler->GetAdaptionScale());
1127  }
1128 
1129  if(AdaptiveHandler->UpdateMatrixAdapt()) {
1130  TMatrixDSym* update_matrix = static_cast<TMatrixDSym*>(AdaptiveHandler->GetAdaptiveCovariance()->Clone());
1131  UpdateThrowMatrix(update_matrix); //Now we update and continue!
1132  //Also Save the adaptive to file
1133  AdaptiveHandler->SaveAdaptiveToFile(AdaptiveHandler->GetOutFileName(), GetName());
1134  }
1135 
1136  AdaptiveHandler->IncrementNSteps();
1137 }
1138 
1139 // ********************************************
1140 //HW: Finds closest possible positive definite matrix in Frobenius Norm ||.||_frob
1141 // Where ||X||_frob=sqrt[sum_ij(x_ij^2)] (basically just turns an n,n matrix into vector in n^2 space
1142 // then does Euclidean norm)
1144 // ********************************************
1145  // Want to get cov' = (cov_sym+cov_polar)/2
1146  // cov_sym=(cov+cov^T)/2
1147  // cov_polar-> SVD cov to cov=USV^T then cov_polar=VSV^T
1148 
1149  //Get frob norm of cov
1150  // Double_t cov_norm=cov->E2Norm();
1151 
1152  TMatrixDSym* cov_trans = cov;
1153  cov_trans->T();
1154  TMatrixDSym cov_sym = 0.5*(*cov+*cov_trans); //If cov is symmetric does nothing, otherwise just ensures symmetry
1155 
1156  //Do SVD to get polar form
1157  TDecompSVD cov_sym_svd=TDecompSVD(cov_sym);
1158  if(!cov_sym_svd.Decompose()){
1159  MACH3LOG_WARN("Cannot do SVD on input matrix, trying MakePosDef() first!");
1160  MakePosDef(&cov_sym);
1161  }
1162 
1163  TMatrixD cov_sym_v = cov_sym_svd.GetV();
1164  TMatrixD cov_sym_vt = cov_sym_v;
1165  cov_sym_vt.T();
1166  //SVD returns as vector (grrr) so need to get into matrix form for multiplying!
1167  TVectorD cov_sym_sigvect = cov_sym_svd.GetSig();
1168 
1169  const Int_t nCols = cov_sym_v.GetNcols(); //square so only need rows hence lack of cols
1170  TMatrixDSym cov_sym_sig(nCols);
1171  TMatrixDDiag cov_sym_sig_diag(cov_sym_sig);
1172  cov_sym_sig_diag=cov_sym_sigvect;
1173 
1174  //Can finally get H=VSV
1175  TMatrixDSym cov_sym_polar = cov_sym_sig.SimilarityT(cov_sym_vt);//V*S*V^T (this took forver to find!)
1176 
1177  //Now we can construct closest approximater Ahat=0.5*(B+H)
1178  TMatrixDSym cov_closest_approx = 0.5*(cov_sym+cov_sym_polar);//Not fully sure why this is even needed since symmetric B -> U=V
1179  //Get norm of transformed
1180  // Double_t approx_norm=cov_closest_approx.E2Norm();
1181  //MACH3LOG_INFO("Initial Norm: {:.6f} | Norm after transformation: {:.6f} | Ratio: {:.6f}", cov_norm, approx_norm, cov_norm / approx_norm);
1182 
1183  *cov = cov_closest_approx;
1184  //Now can just add a makeposdef!
1185  MakePosDef(cov);
1186 }
1187 
1188 // ********************************************
1189 // KS: Convert covariance matrix to correlation matrix and return TH2D which can be used for fancy plotting
1191 // ********************************************
1192  TH2D* hMatrix = new TH2D(GetName().c_str(), GetName().c_str(), _fNumPar, 0.0, _fNumPar, _fNumPar, 0.0, _fNumPar);
1193  hMatrix->SetDirectory(nullptr);
1194  for(int i = 0; i < _fNumPar; i++)
1195  {
1196  hMatrix->SetBinContent(i+1, i+1, 1.);
1197  hMatrix->GetXaxis()->SetBinLabel(i+1, GetParFancyName(i).c_str());
1198  hMatrix->GetYaxis()->SetBinLabel(i+1, GetParFancyName(i).c_str());
1199  }
1200 
1201  #ifdef MULTITHREAD
1202  #pragma omp parallel for
1203  #endif
1204  for(int i = 0; i < _fNumPar; i++)
1205  {
1206  for(int j = 0; j <= i; j++)
1207  {
1208  const double Corr = (*covMatrix)(i,j) / ( GetDiagonalError(i) * GetDiagonalError(j));
1209  hMatrix->SetBinContent(i+1, j+1, Corr);
1210  hMatrix->SetBinContent(j+1, i+1, Corr);
1211  }
1212  }
1213  return hMatrix;
1214 }
1215 
1216 // ********************************************
1217 // KS: After step scale, prefit etc. value were modified save this modified config.
1219 // ********************************************
1220  if (!_fYAMLDoc)
1221  {
1222  MACH3LOG_CRITICAL("Yaml node hasn't been initialised for matrix {}, something is not right", matrixName);
1223  MACH3LOG_CRITICAL("I am not throwing error but should be investigated");
1224  return;
1225  }
1226 
1227  YAML::Node copyNode = _fYAMLDoc;
1228  int i = 0;
1229 
1230  for (YAML::Node param : copyNode["Systematics"])
1231  {
1232  //KS: Feel free to update it, if you need updated prefit value etc
1233  param["Systematic"]["StepScale"]["MCMC"] = M3::Utils::FormatDouble(_fIndivStepScale[i], 4);
1234  i++;
1235  }
1236  // Save the modified node to a file
1237  std::ofstream fout("Modified_Matrix.yaml");
1238  fout << copyNode;
1239  fout.close();
1240 }
1241 
1242 // ********************************************
1243 // Set proposed parameter values vector to be base on tune values
1244 void ParameterHandlerBase::SetTune(const std::string& TuneName) {
1245 // ********************************************
1246  if(Tunes == nullptr) {
1247  MACH3LOG_ERROR("Tunes haven't been initialised, which are being loaded from YAML, have you used some deprecated constructor");
1248  throw MaCh3Exception(__FILE__, __LINE__);
1249  }
1250  auto Values = Tunes->GetTune(TuneName);
1251 
1252  SetParameters(Values);
1253 }
1254 
1255 // *************************************
1257  std::vector<double>& BranchValues,
1258  std::vector<std::string>& BranchNames,
1259  const std::vector<std::string>& FancyNames) {
1260 // *************************************
1261  BranchValues.resize(GetNumParams());
1262  BranchNames.resize(GetNumParams());
1263 
1264  // if fancy names are passed match ONLY them
1265  // this allow to perform studies where one perform ND fits and pass it to FD fits
1266  // which usually have more params like osc...
1267  if(FancyNames.size() != 0){
1268  for (int i = 0; i < GetNumParams(); ++i) {
1269  BranchNames[i] = GetParName(i);
1270  // by default set current step
1271  BranchValues[i] = _fPropVal[i];
1272  bool matched = false;
1273  for (size_t iPar = 0; iPar < FancyNames.size(); ++iPar) {
1274  if(GetParFancyName(i) == FancyNames[iPar]) {
1275  MACH3LOG_DEBUG("Matched name {} in config", FancyNames[iPar]);
1276  PosteriorFile->SetBranchStatus(BranchNames[i].c_str(), true);
1277  PosteriorFile->SetBranchAddress(BranchNames[i].c_str(), &BranchValues[i]);
1278  matched = true;
1279  break;
1280  }
1281  }
1282  if(!matched) {
1283  MACH3LOG_WARN("Didn't match param {} is this what you want?", GetParFancyName(i));
1284  }
1285  }
1286  } else {
1287  // simply loop over params and match them
1288  for (int i = 0; i < GetNumParams(); ++i) {
1289  BranchNames[i] = GetParName(i);
1290  if (!PosteriorFile->GetBranch(BranchNames[i].c_str())) {
1291  MACH3LOG_ERROR("Branch '{}' does not exist in the TTree!", BranchNames[i]);
1292  throw MaCh3Exception(__FILE__, __LINE__);
1293  }
1294  PosteriorFile->SetBranchStatus(BranchNames[i].c_str(), true);
1295  PosteriorFile->SetBranchAddress(BranchNames[i].c_str(), &BranchValues[i]);
1296  }
1297  }
1298 }
#define _noexcept_
KS: noexcept can help with performance but is terrible for debugging, this is meant to help easy way ...
Definition: Core.h:96
#define MACH3LOG_CRITICAL
Definition: MaCh3Logger.h:38
#define MACH3LOG_DEBUG
Definition: MaCh3Logger.h:34
#define MACH3LOG_ERROR
Definition: MaCh3Logger.h:37
#define MACH3LOG_INFO
Definition: MaCh3Logger.h:35
#define MACH3LOG_WARN
Definition: MaCh3Logger.h:36
Custom exception class used throughout MaCh3.
std::vector< int > CircularBoundsIndex
Indices of parameters with circular bounds.
int GetNumParams() const
Get total number of parameters.
std::vector< bool > param_skip_adapt_flags
Flags telling if parameter should be skipped during adaption.
void ToggleFixParameter(const int i)
Toggle fixing parameter at prior values.
bool use_adaptive
Are we using AMCMC?
void SetFixAllParameters()
Set all parameters to be fixed at prior values.
virtual void ProposeStep()
Generate a new proposed state.
TH2D * GetCorrelationMatrix() const
KS: Convert covariance matrix to correlation matrix and return TH2D which can be used for fancy plott...
void SanitizeAdaption() const
Perform sanity check to ensure adaption isn't misbehaving before fit starts.
double * randParams
Random number taken from gaussian around prior error used for corr_throw.
void SetName(const std::string &name)
Set matrix name.
std::map< std::string, FlipGroup > FlipGroups
Map of flip groups, where the key is the group name and the value is a FlipGroup struct.
double ** throwMatrixCholDecomp
Throw matrix that is being used in the fit, much faster as TMatrixDSym cache miss.
void SetStepScale(const double scale, const bool verbose=true)
Set global step scale for covariance object.
virtual ~ParameterHandlerBase()
Destructor.
TMatrixDSym * invCovMatrix
The inverse covariance matrix.
void SetCovMatrix(TMatrixDSym *cov)
Set covariance matrix.
std::string GetParName(const int i) const
Get name of parameter.
std::vector< double > GetProposed() const
Get vector of all proposed parameter values.
void SetFlatPrior(const int i, const bool eL)
Set if parameter should have flat prior or not.
void AcceptStep() _noexcept_
Accepted this step.
virtual double GetLikelihood()
Return CalcLikelihood if some params were thrown out of boundary return LARGE_LOGL
std::unique_ptr< AdaptiveMCMCHandler > AdaptiveHandler
Struct containing information about adaption.
void FlipParameterGroup(std::string group)
With a 50% chance, flip all parameters in a group around their respective flip points.
std::vector< M3::float_t > _fPropVal
Proposed value of the parameter.
std::unique_ptr< ParameterTunes > Tunes
Struct containing information about adaption.
void InitialiseAdaption(const YAML::Node &adapt_manager)
Initialise adaptive MCMC.
TMatrixDSym * throwMatrix
Matrix which we use for step proposal before Cholesky decomposition (not actually used for step propo...
double _fGlobalStepScale
Global step scale applied to all params in this class.
int GetParIndex(const std::string &name) const
Get index based on name.
void ReserveMemory(const int size)
Initialise vectors with parameters information.
std::vector< std::string > _fFancyNames
Fancy name for example rather than param_0 it is MAQE, useful for human reading.
bool doSpecialStepProposal
Check if any of special step proposal were enabled.
std::vector< bool > _fFlatPrior
Whether to apply flat prior or not.
void ThrowParameters()
Throw the parameters according to the covariance matrix. This shouldn't be used in MCMC code ase it c...
double _fGlobalStepScaleInitial
Backup of _fGlobalStepScale for parameters which are skipped during adaption.
std::string matrixName
Name of cov matrix.
void MakeClosestPosDef(TMatrixDSym *cov)
HW: Finds closest possible positive definite matrix in Frobenius Norm ||.||_frob Where ||X||_frob=sqr...
void RandomConfiguration()
Randomly throw the parameters in their 1 sigma range.
TMatrixDSym * GetCovMatrix() const
Return covariance matrix.
void UpdateAdaptiveCovariance()
Method to update adaptive MCMC .
std::string GetParFancyName(const int i) const
Get fancy name of the Parameter.
std::vector< double > _fError
Prior error on the parameter.
void SetFreeAllParameters()
Set all parameters to be treated as free.
void SetFixParameter(const int i)
Set parameter to be fixed at prior value.
void SetParameters(const std::vector< double > &pars={})
Set parameter values using vector, it has to have same size as covariance class.
YAML::Node _fYAMLDoc
Stores config describing systematics.
void SetThrowMatrix(const TMatrixDSym *cov)
Use new throw matrix, used in adaptive MCMC.
bool IsParameterFixed(const int i) const
Is parameter fixed or not.
void MatchMaCh3OutputBranches(TTree *PosteriorFile, std::vector< double > &BranchValues, std::vector< std::string > &BranchNames, const std::vector< std::string > &FancyNames={})
Matches branches in a TTree to parameters in a systematic handler.
void UpdateThrowMatrix(TMatrixDSym *cov)
Replaces old throw matrix with new one.
void CircularParBounds(const int i, const double LowBound, const double UpBound)
HW :: This method is a tad hacky but modular arithmetic gives me a headache.
void SetBranches(TTree &tree, const bool SaveProposal=false)
set branches for output file
void SaveUpdatedMatrixConfig()
KS: After step scale, prefit etc. value were modified save this modified config.
void InitFromFile(const std::string &name, const std::string &file)
Initialisation of the class using matrix from root file.
void ResetIndivStepScale()
Adaptive Step Tuning Stuff.
std::unique_ptr< PCAHandler > PCAObj
Struct containing information about PCA.
int _fNumPar
Number of systematic parameters.
double CalcLikelihood() const _noexcept_
Calc penalty term based on inverted covariance matrix.
void ConstructPCA(const double eigen_threshold, int FirstPCAdpar, int LastPCAdpar)
CW: Calculate eigen values, prepare transition matrices and remove param based on defined threshold.
std::vector< double > _fIndivStepScaleInitial
Backup of _fIndivStepScale for parameters which are skipped during adaption.
std::vector< double > _fLowBound
Lowest physical bound, parameter will not be able to go beyond it.
std::vector< double > _fCurrVal
Current value of the parameter.
std::vector< std::vector< double > > InvertCovMatrix
KS: Same as above but much faster as TMatrixDSym cache miss.
TMatrixDSym * covMatrix
The covariance matrix.
std::vector< double > _fPreFitValue
Parameter value dictated by the prior model. Based on it penalty term is calculated.
void Randomize() _noexcept_
"Randomize" the parameters in the covariance class for the proposed step. Used the proposal kernel an...
void PrintIndivStepScale() const
Print step scale for each parameter.
std::vector< std::string > _fNames
ETA _fNames is set automatically in the covariance class to be something like param_i,...
std::vector< std::pair< double, double > > CircularBoundsValues
Circular bounds for each parameter (lower, upper)
void EnableSpecialProposal(const YAML::Node &param, const int Index)
Enable special proposal.
double * corr_throw
Result of multiplication of Cholesky matrix and randParams.
bool GetFlatPrior(const int i) const
Get if param has flat prior or not.
std::string GetName() const
Get name of covariance.
double GetParPreFit(const int i) const
Get prior parameter value.
void SetParCurrProp(const int i, const double val)
Set current parameter value.
void SetFreeParameter(const int i)
Set parameter to be treated as free.
std::vector< double > _fUpBound
Upper physical bound, parameter will not be able to go beyond it.
void SetSingleParameter(const int parNo, const double parVal)
Set value of single param to a given value.
void SetPar(const int i, const double val)
Set all the covariance matrix parameters to a user-defined value.
void SetIndivStepScale(const int ParameterIndex, const double StepScale)
DB Function to set fIndivStepScale from a vector (Can be used from execs and inside covariance constr...
void SetIndivStepScaleForSkippedAdaptParams()
Set individual step scale for parameters which are skipped during adaption to initial values.
double GetDiagonalError(const int i) const
Get diagonal error for ith parameter.
ParameterHandlerBase()=default
void SetSubThrowMatrix(int first_index, int last_index, TMatrixDSym const &subcov)
void SetTune(const std::string &TuneName)
KS: Set proposed parameter values vector to be base on tune values, for example set proposed values t...
bool pca
perform PCA or not
std::vector< double > _fIndivStepScale
Individual step scale used by MCMC algorithm.
std::vector< std::unique_ptr< TRandom3 > > random_number
KS: Set Random numbers for each thread so each thread has different seed.
void PrintPreFitValues() const
Print prior value for every parameter.
void SpecialStepProposal()
Perform Special Step Proposal.
void PrintPreFitCurrPropValues() const
Print prior, current and proposed value for each parameter.
int PrintLength
KS: This is used when printing parameters, sometimes we have super long parameters name,...
void CorrelateSteps() _noexcept_
Use Cholesky throw matrix for better step proposal.
void MakePosDef(TMatrixDSym *cov=nullptr, bool verbose=true)
Make matrix positive definite by adding small values to diagonal, necessary for inverting matrix.
int CheckBounds() const _noexcept_
Check if parameters were proposed outside physical boundary.
std::string FormatDouble(const double value, const int precision)
Convert double into string for precision, useful for playing with yaml if you don't want to have in c...
constexpr static const double _LARGE_LOGL_
Large Likelihood is used it parameter go out of physical boundary, this indicates in MCMC that such s...
Definition: Core.h:80
void MatrixVectorMulti(double *_restrict_ VecMulti, double **_restrict_ matrix, const double *_restrict_ vector, const int n)
KS: Custom function to perform multiplication of matrix and vector with multithreading.
double float_t
Definition: Core.h:37
int GetThreadIndex()
thread index inside parallel loop
Definition: Monitor.h:86
double MatrixVectorMultiSingle(double **_restrict_ matrix, const double *_restrict_ vector, const int Length, const int i)
KS: Custom function to perform multiplication of matrix and single element which is thread safe.
int MakeMatrixPosDef(TMatrixDSym *cov)
Makes sure that matrix is positive-definite by adding a small number to on-diagonal elements.
constexpr static const int _BAD_INT_
Default value used for int initialisation.
Definition: Core.h:55
int GetNThreads()
number of threads which we need for example for TRandom3
Definition: Monitor.cpp:372
bool CaseInsensitiveMatchAny(std::string Text, const std::vector< std::string > &Patterns)
Matches a string against a simple wildcard Pattern using regex. Is not case sensitive.
std::vector< std::vector< double > > GetCholeskyDecomposedMatrix(const TMatrixDSym &matrix, const std::string &matrixName)
Computes Cholesky decomposition of a symmetric positive definite matrix using custom function which c...
Struct to hold information about a group of parameters that flip together at the same time.
std::vector< int > FlipParameterIndex
Indices of parameters with flip symmetry.
std::vector< double > FlipParameterPoint
Central points around which parameters are flipped.