MaCh3  2.8.0
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  // Set the covariance matrix
92  _fNumPar = CovMat->GetNrows();
93 
95  SetName(name);
96  MakePosDef(CovMat);
97  SetCovMatrix(CovMat);
98 
99  infile->Close();
100 
101  MACH3LOG_INFO("Created covariance matrix named: {}", GetName());
102  MACH3LOG_INFO("from file: {}", file);
103  delete infile;
104 }
105 
106 // ********************************************
107 void ParameterHandlerBase::EnableSpecialProposal(const YAML::Node& param, const int Index){
108 // ********************************************
109  doSpecialStepProposal = true;
110 
111  bool CircEnabled = false;
112  std::pair<double, double> circular_bounds;
113 
114  bool FlipEnabled = false;
115  std::string flip_group;
116  double flip_point;
117 
118  bool FunctionalFlipEnabled = false;
119  YAML::Node functional_flip;
120 
121  if (param["CircularBounds"]) {
122  CircEnabled = true;
123  circular_bounds = Get<std::pair<double, double>>(param["CircularBounds"], __FILE__, __LINE__);
124  }
125 
126  if (param["FlipParameter"]) {
127  FlipEnabled = true;
128  // grab flip group if it exists, otherwise use the parameter name as the group
129  if (param["FlipGroup"]) {
130  flip_group = Get<std::string>(param["FlipGroup"], __FILE__, __LINE__);
131  } else {
132  flip_group = GetParFancyName(Index);
133  }
134  flip_point = Get<double>(param["FlipParameter"], __FILE__, __LINE__);
135  }
136 
137  if (param["FunctionalFlip"]) {
138  FunctionalFlipEnabled = true;
139  // grab functional flip details if needed
140  if (param["FlipGroup"]) {
141  flip_group = Get<std::string>(param["FlipGroup"], __FILE__, __LINE__);
142  } else {
143  flip_group = GetParFancyName(Index);
144  }
145  functional_flip = param["FunctionalFlip"];
146  }
147 
148  if (!CircEnabled && !FlipEnabled && !FunctionalFlipEnabled) {
149  MACH3LOG_ERROR("None of Special Proposal were enabled even though param {}, has SpecialProposal entry in Yaml", GetParFancyName(Index));
150  throw MaCh3Exception(__FILE__, __LINE__);
151  }
152 
153 
154  if (FlipEnabled && FunctionalFlipEnabled) {
155  MACH3LOG_ERROR("Parameter {} enables both FlipParameter and FunctionalFlip; choose exactly one flip type", GetParFancyName(Index));
156  throw MaCh3Exception(__FILE__, __LINE__);
157  }
158 
159  if (CircEnabled) {
160  CircularBoundsIndex.push_back(Index);
161  CircularBoundsValues.push_back(circular_bounds);
162  MACH3LOG_INFO("Enabling CircularBounds for parameter {} with range [{}, {}]",
163  GetParFancyName(Index),
164  circular_bounds.first,
165  circular_bounds.second);
166  // KS: Make sure circular bounds are within physical bounds. If we are outside of physics bound MCMC will never explore such phase space region
167  if (circular_bounds.first < _fLowBound.at(Index) || circular_bounds.second > _fUpBound.at(Index)) {
168  MACH3LOG_ERROR("Circular bounds [{}, {}] for parameter {} exceed physical bounds [{}, {}]",
169  circular_bounds.first, circular_bounds.second,
170  GetParFancyName(Index),
171  _fLowBound.at(Index), _fUpBound.at(Index));
172  throw MaCh3Exception(__FILE__, __LINE__);
173  }
174  // KS: Make sure CircularPrior is applied only to param with flat prior. Sadly doesn't work with Gaussian
175  if(GetFlatPrior(Index) == false) {
176  MACH3LOG_ERROR("Enabled CircularPrior for parameter {}, which has gaussian prior", GetParFancyName(Index));
177  MACH3LOG_ERROR("This is not supported, CircularPrior only works with flat prior");
178  MACH3LOG_ERROR("Change FlatPrior in Parameter config to true");
179  throw MaCh3Exception(__FILE__, __LINE__);
180  }
181  }
182 
183  if (FlipEnabled) {
184  FlipGroup& group = FlipGroups[flip_group];
185  group.FlipParameterIndex.push_back(Index);
186  group.FlipParameterPoint.push_back(flip_point);
187 
188  MACH3LOG_INFO("Enabling Flipping for parameter {} in group {} with value {}",
189  GetParFancyName(Index),
190  flip_group,
191  flip_point);
192  }
193 
194  if (FunctionalFlipEnabled) {
195  FlipGroups[flip_group];
196  QueueFunctionalFlip(functional_flip, Index, flip_group); // leave the resolution of the functional flips until after all the params have been loaded in
197 
198  MACH3LOG_INFO("Enabling Functional Flipping for parameter {} in group {} with config {}",
199  GetParFancyName(Index),
200  flip_group,
201  YAML::Dump(functional_flip));
202  }
203 
204  if (CircEnabled && FlipEnabled) {
205  if (flip_point < circular_bounds.first || flip_point > circular_bounds.second) {
206  MACH3LOG_ERROR("FlipParameter value {} for parameter {} is outside the CircularBounds [{}, {}]",
207  flip_point, GetParFancyName(Index), circular_bounds.first, circular_bounds.second);
208  throw MaCh3Exception(__FILE__, __LINE__);
209  }
210 
211  const double low = circular_bounds.first;
212  const double high = circular_bounds.second;
213 
214  // Sanity check: ensure flipping any x in [low, high] keeps the result in [low, high]
215  const double flipped_low = 2 * flip_point - low;
216  const double flipped_high = 2 * flip_point - high;
217  const double min_flip = std::min(flipped_low, flipped_high);
218  const double max_flip = std::max(flipped_low, flipped_high);
219 
220  if (min_flip < low || max_flip > high) {
221  MACH3LOG_ERROR("Flipping about point {} for parameter {} would leave circular bounds [{}, {}]",
222  flip_point, GetParFancyName(Index), low, high);
223  throw MaCh3Exception(__FILE__, __LINE__);
224  }
225  }
226 }
227 
228 // ********************************************
229 void ParameterHandlerBase::QueueFunctionalFlip(const YAML::Node& param, const int index, const std::string& group_name) {
230 // ********************************************
231  PendingFunctionalFlipProposal pending_flip;
232  pending_flip.target_index = index;
233  pending_flip.group_name = group_name;
234  pending_flip.config = YAML::Clone(param);
235  PendingFunctionalFlipParameters.push_back(std::move(pending_flip));
236 }
237 
238 // ********************************************
240 // ********************************************
241  for (const auto& pending_flip : PendingFunctionalFlipParameters) {
242  AddFunctionalFlip(pending_flip.config, pending_flip.target_index, pending_flip.group_name);
243  }
244 
246 }
247 
248 // ********************************************
249 void ParameterHandlerBase::AddFunctionalFlip(const YAML::Node& param, const int index, const std::string& group_name) {
250 // ********************************************
251  YAML::Node functional_flip = YAML::Clone(param);
252  const YAML::Node formula = functional_flip["Formula"];
253  const YAML::Node parameters = functional_flip["Parameters"];
254 
255  if (!formula) {
256  MACH3LOG_ERROR("FunctionalFlip for parameter {} is missing Formula", GetParFancyName(index));
257  throw MaCh3Exception(__FILE__, __LINE__);
258  }
259  if (!parameters) {
260  MACH3LOG_ERROR("FunctionalFlip for parameter {} is missing Parameters list", GetParFancyName(index));
261  throw MaCh3Exception(__FILE__, __LINE__);
262  }
263 
265  flip.target_index = index;
266  flip.formula = Get<std::string>(formula, __FILE__, __LINE__);
267  flip.argument_names = Get<std::vector<std::string>>(parameters, __FILE__, __LINE__);
268 
269  for (const auto& name : flip.argument_names) {
270  const int argument_index = GetParIndex(name);
271  if (argument_index == M3::_BAD_INT_) {
272  MACH3LOG_ERROR("FunctionalFlip for parameter {} refers to unknown parameter {}",
273  GetParFancyName(index), name);
274  throw MaCh3Exception(__FILE__, __LINE__);
275  }
276  flip.argument_indices.push_back(argument_index);
277  }
278 
279  const std::string flip_name = fmt::format("{}_{}_functional_flip_{}",
280  matrixName,
281  group_name,
282  FlipGroups[group_name].FunctionalFlipParameters.size());
283  flip.evaluator = std::make_unique<TF1>(flip_name.c_str(), flip.formula.c_str(), 0.0, 1.0);
284 
285  if (!flip.evaluator || flip.evaluator->IsZombie()) {
286  MACH3LOG_ERROR("Failed to parse FunctionalFlip formula for parameter {}: {}",
287  GetParFancyName(index), flip.formula);
288  throw MaCh3Exception(__FILE__, __LINE__);
289  }
290 
291  if (flip.evaluator->GetNpar() != static_cast<int>(flip.argument_names.size())) {
292  MACH3LOG_ERROR("FunctionalFlip formula for parameter {} expects {} auxiliary parameters, but {} names were provided",
293  GetParFancyName(index), flip.evaluator->GetNpar(), flip.argument_names.size());
294  MACH3LOG_ERROR("Use x for the target parameter and [0], [1], ... for the listed Parameters entries");
295  throw MaCh3Exception(__FILE__, __LINE__);
296  }
297 
298  MACH3LOG_INFO("Enabling FunctionalFlip for parameter {} with formula '{}'",
299  GetParFancyName(index), flip.formula);
300  FlipGroups[group_name].FunctionalFlipParameters.push_back(std::move(flip));
301 }
302 
303 // ********************************************
304 // Set the covariance matrix for this class
305 void ParameterHandlerBase::SetCovMatrix(TMatrixDSym *cov) {
306 // ********************************************
307  if (cov == nullptr) {
308  MACH3LOG_ERROR("Could not find covariance matrix you provided to {}", __func__ );
309  throw MaCh3Exception(__FILE__ , __LINE__ );
310  }
311  covMatrix = cov;
312 
313  invCovMatrix = static_cast<TMatrixDSym *>(cov->Clone());
314  invCovMatrix->Invert();
315  //KS: ROOT has bad memory management, using standard double means we can decrease most operation by factor 2 simply due to cache hits
316  for (int i = 0; i < _fNumPar; i++)
317  {
318  for (int j = 0; j < _fNumPar; ++j)
319  {
320  InvertCovMatrix[i][j] = (*invCovMatrix)(i,j);
321  }
322  }
323 
324  SetThrowMatrix(cov);
325 }
326 // ********************************************
327 void ParameterHandlerBase::ReserveMemory(const int SizeVec) {
328 // ********************************************
329  if (SizeVec <= 0) {
330  MACH3LOG_CRITICAL("Covariance matrix {} has {} entries!", GetName(), SizeVec);
331  throw MaCh3Exception(__FILE__ , __LINE__ );
332  }
333 
334  _fNames = std::vector<std::string>(SizeVec);
335  _fFancyNames = std::vector<std::string>(SizeVec);
336  _fPreFitValue = std::vector<double>(SizeVec, 1.0);
337  _fError = std::vector<double>(SizeVec, 1.0);
338  _fCurrVal = std::vector<double>(SizeVec, 0.0);
339  _fPropVal = std::vector<M3::float_t>(SizeVec, 0.0);
340  _fLowBound = std::vector<double>(SizeVec, -999.99);
341  _fUpBound = std::vector<double>(SizeVec, 999.99);
342  _fFlatPrior = std::vector<bool>(SizeVec, false);
343  _fIndivStepScale = std::vector<double>(SizeVec, 1.0);
344 
345  corr_throw = new double[SizeVec];
346  // set random parameter vector (for correlated steps)
347  randParams = new double[SizeVec];
348 
349  // Set the defaults to true
350  for(int i = 0; i < SizeVec; i++) {
351  corr_throw[i] = 0.0;
352  randParams[i] = 0.0;
353  }
354 
355  InvertCovMatrix.resize(SizeVec, std::vector<double>(SizeVec, 0.0));
356  throwMatrixCholDecomp = new double*[SizeVec]();
357  // Set the defaults to true
358  for(int i = 0; i < SizeVec; i++) {
359  throwMatrixCholDecomp[i] = new double[SizeVec]();
360  for (int j = 0; j < SizeVec; j++) {
361  throwMatrixCholDecomp[i][j] = 0.;
362  }
363  }
364 
365  _fGlobalStepScale = 1.0;
366 }
367 
368 // ********************************************
369 // Set all the covariance matrix parameters to a user-defined value
370 // Might want to split this
371 void ParameterHandlerBase::SetPar(const int i , const double val) {
372 // ********************************************
373  MACH3LOG_DEBUG("Over-riding {}: _fPropVal ({}), _fCurrVal ({}), _fPreFitValue ({}) to ({})",
374  GetParFancyName(i), _fPropVal[i], _fCurrVal[i], _fPreFitValue[i], val);
375 
376  _fPropVal[i] = static_cast<M3::float_t>(val);
377  _fCurrVal[i] = val;
378  _fPreFitValue[i] = val;
379 
380  // Transfer the parameter values to the PCA basis
381  if (pca) PCAObj->TransferToPCA();
382 }
383 
384 // ********************************************
385 std::vector<double> ParameterHandlerBase::GetProposed() const {
386 // ********************************************
387  std::vector<double> props(_fNumPar);
388  for (int i = 0; i < _fNumPar; ++i) props[i] = _fPropVal[i];
389  return props;
390 }
391 
392 // *************************************
393 // Throw the parameters according to the covariance matrix
394 // This shouldn't be used in MCMC code ase it can break Detailed Balance;
396 // *************************************
397  // First draw new randParams
398  Randomize();
399 
401  #pragma GCC diagnostic push
402  #pragma GCC diagnostic ignored "-Wuseless-cast"
403  // KS: We use PCA very rarely on top PCA functionality isn't implemented for this function.
404  // Use __builtin_expect to give compiler a hint which option is more likely, which should help
405  // with better optimisation. This isn't critical but more to have example
406  if (__builtin_expect(!pca, 1)) {
407  #ifdef MULTITHREAD
408  #pragma omp parallel for
409  #endif
410  for (int i = 0; i < _fNumPar; ++i) {
411  // Check if parameter is fixed first: if so don't randomly throw
412  if (IsParameterFixed(i)) continue;
413  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i] + corr_throw[i]);
414 
415  int throws = 0;
416  // Try again if we the initial parameter proposal falls outside of the range of the parameter
417  while (_fPropVal[i] > _fUpBound[i] || _fPropVal[i] < _fLowBound[i]) {
418  randParams[i] = M3::rand::Gaus(0, 1);
419  const double corr_throw_single = M3::MatrixVectorMultiSingle(throwMatrixCholDecomp, randParams, _fNumPar, i);
420  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i] + corr_throw_single);
421  if (throws > 10000)
422  {
423  //KS: Since we are multithreading there is danger that those messages
424  //will be all over the place, small price to pay for faster code
425  MACH3LOG_WARN("Tried {} times to throw parameter {} but failed", throws, i);
426  MACH3LOG_WARN("Matrix: {}", matrixName);
427  MACH3LOG_WARN("Param: {}", _fNames[i]);
428  MACH3LOG_WARN("Setting _fPropVal: {} to {}", _fPropVal[i], _fPreFitValue[i]);
429  MACH3LOG_WARN("I live at {}:{}", __FILE__, __LINE__);
430  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i]);
431  //throw MaCh3Exception(__FILE__ , __LINE__ );
432  }
433  throws++;
434  }
435  _fCurrVal[i] = _fPropVal[i];
436  }
437  }
438  else
439  {
440  PCAObj->ThrowParameters(throwMatrixCholDecomp,
443  } // end if pca
444  #pragma GCC diagnostic pop
445  // KS: At the end once we are happy with proposal do special proposal
447 }
448 
449 // *************************************
450 // Throw each parameter within their 1 sigma range
451 // Used to start the chain in different states
453 // *************************************
454  #pragma GCC diagnostic push
455  #pragma GCC diagnostic ignored "-Wuseless-cast"
456  // Have the 1 sigma for each parameter in each covariance class, sweet!
457  // Don't want to change the prior array because that's what determines our likelihood
458  // Want to change the _fPropVal, _fCurrVal, _fPreFitValue
459  // _fPreFitValue and the others will already be set
460  for (int i = 0; i < _fNumPar; ++i) {
461  // Check if parameter is fixed first: if so don't randomly throw
462  if (IsParameterFixed(i)) continue;
463  // Check that the sigma range is larger than the parameter range
464  // If not, throw in the valid parameter range instead
465  const double paramrange = _fUpBound[i] - _fLowBound[i];
466  const double sigma = sqrt((*covMatrix)(i,i));
467  double throwrange = sigma;
468  if (paramrange < sigma) throwrange = paramrange;
469 
470  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i] + M3::rand::Gaus(0, 1)*throwrange);
471  // Try again if we the initial parameter proposal falls outside of the range of the parameter
472  int throws = 0;
473  while (_fPropVal[i] > _fUpBound[i] || _fPropVal[i] < _fLowBound[i]) {
474  if (throws > 1000) {
475  MACH3LOG_WARN("Tried {} times to throw parameter {} but failed", throws, i);
476  MACH3LOG_WARN("Matrix: {}", matrixName);
477  MACH3LOG_WARN("Param: {}", _fNames[i]);
478  throw MaCh3Exception(__FILE__ , __LINE__ );
479  }
480  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i] + M3::rand::Gaus(0, 1)*throwrange);
481  throws++;
482  }
483  MACH3LOG_INFO("Setting current step in {} param {} = {} from {}", matrixName, i, _fPropVal[i], _fCurrVal[i]);
484  _fCurrVal[i] = _fPropVal[i];
485  }
486  #pragma GCC diagnostic pop
487  if (pca) PCAObj->TransferToPCA();
488 
489  // KS: At the end once we are happy with proposal do special proposal
491 }
492 
493 // *************************************
494 // Set a single parameter
495 void ParameterHandlerBase::SetSingleParameter(const int parNo, const double parVal) {
496 // *************************************
497  _fPropVal[parNo] = static_cast<M3::float_t>(parVal);
498  _fCurrVal[parNo] = parVal;
499  MACH3LOG_DEBUG("Setting {} (parameter {}) to {})", GetParFancyName(parNo), parNo, parVal);
500  if (pca) PCAObj->TransferToPCA();
501 }
502 
503 // ********************************************
504 void ParameterHandlerBase::SetParCurrProp(const int parNo, const double parVal) {
505 // ********************************************
506  _fPropVal[parNo] = static_cast<M3::float_t>(parVal);
507  _fCurrVal[parNo] = parVal;
508  MACH3LOG_DEBUG("Setting {} (parameter {}) to {})", GetParFancyName(parNo), parNo, parVal);
509  if (pca) PCAObj->TransferToPCA();
510 }
511 
512 // ************************************************
513 // Propose a step for the set of systematics parameters this covariance class holds
515 // ************************************************
516  // Make the random numbers for the step proposal
517  Randomize();
518  CorrelateSteps();
519 
520  // KS: According to Dr Wallace we update using previous not proposed step
521  // this way we do special proposal after adaptive after.
522  // This way we can shortcut and skip rest of proposal
523  if(!doSpecialStepProposal) return;
524 
526 }
527 
528 // ************************************************
530 // ************************************************
532 
533  // HW It should now automatically set dcp to be with [-pi, pi]
534  for (size_t i = 0; i < CircularBoundsIndex.size(); ++i) {
535  const int index = CircularBoundsIndex[i];
536  if(!IsParameterFixed(index))
537  CircularParBounds(index, CircularBoundsValues[i].first, CircularBoundsValues[i].second);
538  }
539 
540  // // Okay now we've done the standard steps, we can add in our nice flips hierarchy flip first
541  for (const auto& [group_name, group] : FlipGroups) {
542  FlipParameterGroup(group_name);
543  }
544 }
545 
546 // ************************************************
547 // "Randomize" the parameters in the covariance class for the proposed step
548 // Used the proposal kernel and the current parameter value to set proposed step
549 // Also get a new random number for the randParams
551 // ************************************************
552  if (!pca) {
553  //KS: By multithreading here we gain at least factor 2 with 8 threads with ND only fit
554  #ifdef MULTITHREAD
555  #pragma omp parallel for
556  #endif
557  for (int i = 0; i < _fNumPar; ++i) {
558  // If parameter isn't fixed
559  if (!IsParameterFixed(i) > 0.0) {
560  randParams[i] = M3::rand::Gaus(0, 1);
561  // If parameter IS fixed
562  } else {
563  randParams[i] = 0.0;
564  }
565  } // end for
566  // If we're in the PCA basis we instead throw parameters there (only _fNumParPCA parameter)
567  } else {
568  // Scale the random parameters by the sqrt of eigen values for the throw
569  #ifdef MULTITHREAD
570  #pragma omp parallel for
571  #endif
572  for (int i = 0; i < PCAObj->GetNumberPCAedParameters(); ++i)
573  {
574  // If parameter IS fixed or out of bounds
575  if (PCAObj->IsParameterFixedPCA(i)) {
576  randParams[i] = 0.0;
577  } else {
578  randParams[i] = M3::rand::Gaus(0, 1);
579  }
580  }
581  }
582 }
583 
584 // ************************************************
585 // Correlate the steps by setting the proposed step of a parameter to its current value + some correlated throw
587 // ************************************************
588  //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
590 
591  // If not doing PCA
592  if (!pca) {
593  #ifdef MULTITHREAD
594  #pragma omp parallel for
595  #endif
596  for (int i = 0; i < _fNumPar; ++i) {
597  if (!IsParameterFixed(i) > 0.) {
598  #pragma GCC diagnostic push
599  #pragma GCC diagnostic ignored "-Wuseless-cast"
601  #pragma GCC diagnostic pop
602  }
603  }
604  // If doing PCA throw uncorrelated in PCA basis (orthogonal basis by definition)
605  } else {
607  }
608 }
609 // ********************************************
610 // Update so that current step becomes the previously proposed step
612 // ********************************************
613  if (!pca) {
614  #ifdef MULTITHREAD
615  #pragma omp parallel for
616  #endif
617  for (int i = 0; i < _fNumPar; ++i) {
618  // Update state so that current state is proposed state
619  _fCurrVal[i] = _fPropVal[i];
620  }
621  } else {
622  PCAObj->AcceptStep();
623  }
624 
625  if (AdaptiveHandler) {
626  AdaptiveHandler->IncrementAcceptedSteps();
627  }
628 }
629 
630 #pragma GCC diagnostic push
631 #pragma GCC diagnostic ignored "-Wuseless-cast"
632 // *************************************
633 //HW: This method is a tad hacky but modular arithmetic gives me a headache.
634 void ParameterHandlerBase::CircularParBounds(const int index, const double LowBound, const double UpBound) {
635 // *************************************
636  if(_fPropVal[index] > UpBound) {
637  _fPropVal[index] = static_cast<M3::float_t>(LowBound + std::fmod(_fPropVal[index] - UpBound, UpBound - LowBound));
638  } else if (_fPropVal[index] < LowBound) {
639  _fPropVal[index] = static_cast<M3::float_t>(UpBound - std::fmod(LowBound - _fPropVal[index], UpBound - LowBound));
640  }
641 }
642 
643 // *************************************
644 void ParameterHandlerBase::FlipParameterGroup(const std::string& group) {
645 // *************************************
646  const FlipGroup& flip_group = FlipGroups.at(group);
647  // All standard and functional flip groups use the same 50% gate
648  // The exact value doesn't matter: studies with different probabilities showed
649  // it doesn't help exploration even for very unlikely hypotheses, because once
650  // the chain flips to a state, it stays there for a very long time regardless.
651  if (M3::rand::Uniform() >= 0.5) {
652  return;
653  }
654 
655  // store the proposed values before doing any flipping, so that the functional flip can see the unflipped values for any standard flips
656  std::vector<double> proposed_values(_fNumPar, 0.0);
657  for (int i = 0; i < _fNumPar; ++i) {
658  proposed_values[i] = _fPropVal[i];
659  }
660 
661  // perform standard flips first
662  for (size_t i = 0; i < flip_group.FlipParameterIndex.size(); ++i) {
663  const int index = flip_group.FlipParameterIndex[i];
664  if (!IsParameterFixed(index)) {
665  const double flip_point = flip_group.FlipParameterPoint[i];
666  _fPropVal[index] = static_cast<M3::float_t>(2 * flip_point - proposed_values[index]);
667  }
668  }
669 
670  // perform functional flips next
671  for (const auto& functional_flip : flip_group.FunctionalFlipParameters) {
672  if (IsParameterFixed(functional_flip.target_index)) {
673  continue;
674  }
675 
676  _fPropVal[functional_flip.target_index] = EvaluateFunctionalFlip(functional_flip, proposed_values);
677  }
678 }
679 
680 
681 // *************************************
683  const std::vector<double>& proposed_values) const {
684 // *************************************
685  std::vector<double> parameter_values(flip.argument_indices.size(), 0.0);
686  for (size_t i = 0; i < flip.argument_indices.size(); ++i) {
687  parameter_values[i] = proposed_values[flip.argument_indices[i]];
688  }
689 
690  const double target_value = proposed_values[flip.target_index];
691  const double flipped_value = flip.evaluator->EvalPar(&target_value, parameter_values.data());
692  return static_cast<M3::float_t>(flipped_value);
693 }
694 #pragma GCC diagnostic pop
695 // ********************************************
696 // Function to print the prior values
698 // ********************************************
699  MACH3LOG_INFO("Prior values for {} ParameterHandler:", GetName());
700 
701  M3::Utils::TablePrinter table({{"Parameter", 10, 40, M3::Utils::Alignment::Left},
702  {"Prior", 10, 15, M3::Utils::Alignment::Right},});
703 
704  for (int i = 0; i < _fNumPar; ++i) {
705  table.AddRow(GetParFancyName(i), GetParPreFit(i));
706  }
707 
708  for (const auto& line : table.Render()) {
709  MACH3LOG_INFO("{}", line);
710  }
711 }
712 
713 // ********************************************
714 // Function to print the prior, current and proposed values
716 // ********************************************
717  MACH3LOG_INFO("Printing parameters for {}", GetName());
718  // Dump out the PCA parameters too
719  if (pca) {
720  PCAObj->Print();
721  }
722  M3::Utils::TablePrinter table({{"Name", 10, 40, M3::Utils::Alignment::Left},
723  {"Prior", 10, 15, M3::Utils::Alignment::Right},
724  {"Current", 10, 15, M3::Utils::Alignment::Right},
725  {"Proposed", 10, 15, M3::Utils::Alignment::Right},});
726 
727  for (int i = 0; i < _fNumPar; ++i) {
728  table.AddRow(GetParFancyName(i),
729  M3::Utils::Cell(_fPreFitValue[i], "{:.2f}"),
730  M3::Utils::Cell(_fCurrVal[i], "{:.2f}"),
731  M3::Utils::Cell(_fPropVal[i], "{:.2f}"));
732  }
733 
734  for (const auto& line : table.Render()) {
735  MACH3LOG_INFO("{}", line);
736  }
737 }
738 
739 // ********************************************
740 // Get the likelihood in the case where we want to include priors on the parameters
741 // _fFlatPrior stores if we want to evaluate the likelihood for the given parameter
742 // true = don't evaluate likelihood (so run without a prior)
743 // false = evaluate likelihood (so run with a prior)
745 // ********************************************
746  double logL = 0.0;
747  #ifdef MULTITHREAD
748  #pragma omp parallel for reduction(+:logL)
749  #endif
750  for(int i = 0; i < _fNumPar; ++i) {
751  if(_fFlatPrior[i]){
752  //HW: Flat prior, no need to calculate anything
753  continue;
754  }
755  // KS: Precalculate Diff once per "i" without doing this for every "j"
756  const double Diff = _fPropVal[i] - _fPreFitValue[i];
757  #ifdef MULTITHREAD
758  #pragma omp simd
759  #endif
760  for (int j = 0; j <= i; ++j) {
761  if (!_fFlatPrior[j]) {
762  //KS: Since matrix is symmetric we can calculate non diagonal elements only once and multiply by 2, can bring up to factor speed decrease.
763  double scale = (i != j) ? 1. : 0.5;
764  logL += scale * Diff * (_fPropVal[j] - _fPreFitValue[j])*InvertCovMatrix[i][j];
765  }
766  }
767  }
768  return logL;
769 }
770 
771 // ********************************************
773 // ********************************************
774  int NOutside = 0;
775  for (int i = 0; i < _fNumPar; ++i) {
776  // KS: Count how many parameters are outside bounds using branchless logic
777  // faster by at least factor two
778  // Do not multithread even with 5k params no gains
779  NOutside += (_fPropVal[i] > _fUpBound[i]) | (_fPropVal[i] < _fLowBound[i]);
780  }
781  return NOutside;
782 }
783 
784 // ********************************************
786 // ********************************************
787  // Default behaviour is to reject negative values + do std llh calculation
788  const int NOutside = CheckBounds();
789 
790  if(NOutside > 0) return NOutside*M3::_LARGE_LOGL_;
791 
792  return CalcLikelihood();
793 }
794 
795 // ********************************************
796 // Sets the proposed parameters to the prior values
797 void ParameterHandlerBase::SetParameters(const std::vector<double>& pars) {
798 // ********************************************
799  #pragma GCC diagnostic push
800  #pragma GCC diagnostic ignored "-Wuseless-cast"
801  // If empty, set the proposed to prior
802  if (pars.empty()) {
803  for (int i = 0; i < _fNumPar; i++) {
804  _fPropVal[i] = static_cast<M3::float_t>(_fPreFitValue[i]);
805  }
806  // If not empty, set the parameters to the specified
807  } else {
808  if (pars.size() != static_cast<size_t>(_fNumPar)) {
809  MACH3LOG_ERROR("Parameter arrays of incompatible size! Not changing parameters! {} has size {} but was expecting {}", matrixName, pars.size(), _fNumPar);
810  throw MaCh3Exception(__FILE__ , __LINE__ );
811  }
812  int parsSize = int(pars.size());
813  for (int i = 0; i < parsSize; i++) {
814  //Make sure that you are actually passing a number to set the parameter to
815  if(std::isnan(pars[i])) {
816  MACH3LOG_ERROR("Trying to set parameter value to a nan for parameter {} in matrix {}. This will not go well!", GetParName(i), matrixName);
817  throw MaCh3Exception(__FILE__ , __LINE__ );
818  } else {
819  _fPropVal[i] = static_cast<M3::float_t>(pars[i]);
820  }
821  }
822  }
823  // And if pca make the transfer
824  if (pca) {
825  PCAObj->TransferToPCA();
826  PCAObj->TransferToParam();
827  }
828  #pragma GCC diagnostic pop
829 }
830 
831 // ********************************************
832 void ParameterHandlerBase::SetBranches(TTree &tree, bool SaveProposal) {
833 // ********************************************
834  // loop over parameters and set a branch
835  for (int i = 0; i < _fNumPar; ++i) {
836  tree.Branch(_fNames[i].c_str(), &_fCurrVal[i], Form("%s/D", _fNames[i].c_str()));
837  }
838  // When running PCA, also save PCA parameters
839  if (pca) {
840  PCAObj->SetBranches(tree, SaveProposal, _fNames);
841  }
842  if(SaveProposal)
843  {
844  // loop over parameters and set a branch
845  for (int i = 0; i < _fNumPar; ++i) {
846  tree.Branch(Form("%s_Prop", _fNames[i].c_str()), &_fPropVal[i], Form("%s_Prop/D", _fNames[i].c_str()));
847  }
848  }
849  if(use_adaptive && AdaptiveHandler->GetUseRobbinsMonro()){
850  tree.Branch(Form("GlobalStepScale_%s", GetName().c_str()), &_fGlobalStepScale, Form("GlobalStepScale_%s/D", GetName().c_str()));
851  }
852 }
853 
854 // ********************************************
855 void ParameterHandlerBase::SetStepScale(const double scale, const bool verbose) {
856 // ********************************************
857  if(scale <= 0) {
858  MACH3LOG_ERROR("You are trying so set StepScale to 0 or negative this will not work");
859  throw MaCh3Exception(__FILE__ , __LINE__ );
860  }
861 
862  if(verbose){
863  MACH3LOG_INFO("{} setStepScale() = {}", GetName(), scale);
864  const double SuggestedScale = 2.38/std::sqrt(_fNumPar);
865  if(std::fabs(scale - SuggestedScale)/SuggestedScale > 1) {
866  MACH3LOG_WARN("Defined Global StepScale is {}, while suggested suggested {}", scale, SuggestedScale);
867  }
868  }
869  _fGlobalStepScale = scale;
870 }
871 
872 // ********************************************
873 int ParameterHandlerBase::GetParIndex(const std::string& name) const {
874 // ********************************************
875  int Index = M3::_BAD_INT_;
876  for (int i = 0; i <_fNumPar; ++i) {
877  if(name == _fFancyNames[i]) {
878  Index = i;
879  break;
880  }
881  }
882  return Index;
883 }
884 
885 // ********************************************
887 // ********************************************
888  // Check if the parameter is fixed and if not, toggle fix it
889  for (int i = 0; i < _fNumPar; ++i)
891 }
892 
893 // ********************************************
895 // ********************************************
896  // Check if the parameter is fixed and if not, toggle fix it
898 }
899 
900 // ********************************************
901 void ParameterHandlerBase::SetFixParameter(const std::string& name) {
902 // ********************************************
903  // Check if the parameter is fixed and if not, toggle fix it
904  if(!IsParameterFixed(name)) ToggleFixParameter(name);
905 }
906 
907 // ********************************************
909 // ********************************************
910  // Check if the parameter is fixed and if not, toggle fix it
911  for (int i = 0; i < _fNumPar; ++i)
913 }
914 
915 // ********************************************
917 // ********************************************
918  // Check if the parameter is fixed and if not, toggle fix it
920 }
921 
922 // ********************************************
923 void ParameterHandlerBase::SetFreeParameter(const std::string& name) {
924 // ********************************************
925  // Check if the parameter is fixed and if not, toggle fix it
926  if(IsParameterFixed(name)) ToggleFixParameter(name);
927 }
928 
929 // ********************************************
931 // ********************************************
932  if(!pca) {
933  if (i > _fNumPar) {
934  MACH3LOG_ERROR("Can't {} for parameter {} because size of covariance ={}", __func__, i, _fNumPar);
935  MACH3LOG_ERROR("Fix this in your config file please!");
936  throw MaCh3Exception(__FILE__ , __LINE__ );
937  } else {
938  _fError[i] *= -1.0;
939  if(IsParameterFixed(i)) MACH3LOG_INFO("Setting {}(parameter {}) to fixed at {}", GetParFancyName(i), i, _fCurrVal[i]);
940  else MACH3LOG_INFO("Setting {}(parameter {}) free", GetParFancyName(i), i);
941  }
942  if( (_fCurrVal[i] > _fUpBound[i] || _fCurrVal[i] < _fLowBound[i]) && IsParameterFixed(i) ) {
943  MACH3LOG_ERROR("Parameter {} (index {}) is fixed at {}, which is outside of its bounds [{}, {}]", GetParFancyName(i), i, _fCurrVal[i], _fLowBound[i], _fUpBound[i]);
944  throw MaCh3Exception(__FILE__ , __LINE__ );
945  }
946  } else {
947  PCAObj->ToggleFixParameter(i, _fNames);
948  }
949 }
950 
951 // ********************************************
952 void ParameterHandlerBase::ToggleFixParameter(const std::string& name) {
953 // ********************************************
954  const int Index = GetParIndex(name);
955  if(Index != M3::_BAD_INT_) {
956  ToggleFixParameter(Index);
957  return;
958  }
959  MACH3LOG_WARN("I couldn't find parameter with name {}, therefore will not fix it", name);
960 }
961 
962 // ********************************************
963 bool ParameterHandlerBase::IsParameterFixed(const std::string& name) const {
964 // ********************************************
965  const int Index = GetParIndex(name);
966  if(Index != M3::_BAD_INT_) {
967  return IsParameterFixed(Index);
968  }
969 
970  MACH3LOG_WARN("I couldn't find parameter with name {}, therefore don't know if it fixed", name);
971  return false;
972 }
973 
974 // ********************************************
975 void ParameterHandlerBase::SetFlatPrior(const int i, const bool eL) {
976 // ********************************************
977  if (i > _fNumPar) {
978  MACH3LOG_INFO("Can't {} for Cov={}/Param={} because size of Covariance = {}", __func__, GetName(), i, _fNumPar);
979  MACH3LOG_ERROR("Fix this in your config file please!");
980  throw MaCh3Exception(__FILE__ , __LINE__ );
981  } else {
982  if(eL){
983  MACH3LOG_INFO("Setting {} (parameter {}) to flat prior", GetParName(i), i);
984  }
985  else{
986  // HW :: This is useful
987  MACH3LOG_INFO("Setting {} (parameter {}) to non-flat prior", GetParName(i), i);
988  }
989  _fFlatPrior[i] = eL;
990  }
991 }
992 
993 // ********************************************
994 void ParameterHandlerBase::SetIndivStepScale(const std::vector<double>& stepscale) {
995 // ********************************************
996  if (static_cast<int>(stepscale.size()) != _fNumPar)
997  {
998  MACH3LOG_WARN("Stepscale vector not equal to number of parameters. Quitting..");
999  MACH3LOG_WARN("Size of argument vector: {}", stepscale.size());
1000  MACH3LOG_WARN("Expected size: {}", _fNumPar);
1001  return;
1002  }
1003 
1004  for (int iParam = 0 ; iParam < _fNumPar; iParam++) {
1005  _fIndivStepScale[iParam] = stepscale[iParam];
1006  }
1008 }
1009 
1010 // ********************************************
1012 // ********************************************
1013  M3::Utils::TablePrinter table({{"Parameter", 10, 40, M3::Utils::Alignment::Left},
1014  {"Step scale", 10, 15, M3::Utils::Alignment::Right},});
1015 
1016  for (int iParam = 0; iParam < _fNumPar; ++iParam) {
1017  table.AddRow(_fFancyNames[iParam], _fIndivStepScale[iParam]);
1018  }
1019 
1020  for (const auto& line : table.Render()) {
1021  MACH3LOG_INFO("{}", line);
1022  }
1023 }
1024 
1025 // ********************************************
1026 //Makes sure that matrix is positive-definite by adding a small number to on-diagonal elements
1027 void ParameterHandlerBase::MakePosDef(TMatrixDSym *cov, bool verbose) {
1028 // ********************************************
1029  if(cov == nullptr){
1030  cov = &*covMatrix;
1031  MACH3LOG_WARN("Passed nullptr to cov matrix in {}", matrixName);
1032  }
1033 
1034  int n_attempts = M3::MakeMatrixPosDef(cov);
1035 
1036  if(n_attempts > 0 && verbose) {
1037  MACH3LOG_WARN("Covariance matrix {} was not positive-definite, made it positive-definite after {} attempts", matrixName, n_attempts);
1038  }
1039 }
1040 
1041 // ********************************************
1043 // ********************************************
1044  std::vector<double> stepScales(_fNumPar, 1.0);
1045  _fGlobalStepScale = 1.0;
1046  SetIndivStepScale(stepScales);
1047 }
1048 
1049 // ********************************************
1051 // ********************************************
1052  if (!param_skip_adapt_flags.size()) {
1053  MACH3LOG_ERROR("Parameter skip adapt flags not set, cannot set individual step scales for skipped parameters.");
1054  throw MaCh3Exception(__FILE__ , __LINE__ );
1055  }
1056  // HH: Cancel the effect of global step scale change for parameters that are not adapting
1057  for (int i = 0; i <_fNumPar; i++) {
1058  if (param_skip_adapt_flags[i]) {
1060  }
1061  }
1062  MACH3LOG_DEBUG("Updating individual step scales for non-adapting parameters to cancel global step scale change.");
1063  MACH3LOG_DEBUG("Global step scale initial: {}, current: {}", _fGlobalStepScaleInitial, _fGlobalStepScale);
1064 }
1065 
1066 // ********************************************
1067 // HW: Code for throwing from separate throw matrix, needs to be set after init to ensure pos-def
1068 void ParameterHandlerBase::SetThrowMatrix(const TMatrixDSym *cov) {
1069 // ********************************************
1070  if (cov == nullptr) {
1071  MACH3LOG_ERROR("Could not find covariance matrix you provided to {}", __func__);
1072  throw MaCh3Exception(__FILE__ , __LINE__ );
1073  }
1074 
1075  if (covMatrix->GetNrows() != cov->GetNrows()) {
1076  MACH3LOG_ERROR("Matrix given for throw Matrix is not the same size as the covariance matrix stored in object!");
1077  MACH3LOG_ERROR("Stored covariance matrix size: {}", covMatrix->GetNrows());
1078  MACH3LOG_ERROR("Given matrix size: {}", cov->GetNrows());
1079  throw MaCh3Exception(__FILE__ , __LINE__ );
1080  }
1081 
1082  throwMatrix = static_cast<TMatrixDSym*>(cov->Clone());
1083  if(use_adaptive && AdaptiveHandler->AdaptionUpdate()) MakeClosestPosDef(throwMatrix);
1084  else {
1085  // HW: Prevent spam from adaptive handler
1086  bool verbose = AdaptiveHandler ? AdaptiveHandler->GetTotalSteps() < 2 : true;
1087  MakePosDef(throwMatrix, verbose);
1088  }
1089 
1090  auto throwMatrix_CholDecomp = M3::GetCholeskyDecomposedMatrix(*throwMatrix, matrixName);
1091 
1092  //KS: ROOT has bad memory management, using standard double means we can decrease most operation by factor 2 simply due to cache hits
1093  #ifdef MULTITHREAD
1094  #pragma omp parallel for collapse(2)
1095  #endif
1096  for (int i = 0; i < _fNumPar; ++i)
1097  {
1098  for (int j = 0; j < _fNumPar; ++j)
1099  {
1100  throwMatrixCholDecomp[i][j] = throwMatrix_CholDecomp[i][j];
1101  }
1102  }
1103 }
1104 
1105 // ********************************************
1106 void ParameterHandlerBase::SetSubThrowMatrix(int first_index, int last_index,
1107  TMatrixDSym const &subcov) {
1108 // ********************************************
1109  if ((last_index - first_index) >= subcov.GetNrows()) {
1110  MACH3LOG_ERROR("Trying to SetSubThrowMatrix into range: ({},{}) with a "
1111  "submatrix with only {} rows {}",
1112  first_index, last_index, subcov.GetNrows(), __func__);
1113  throw MaCh3Exception(__FILE__, __LINE__);
1114  }
1115 
1116  TMatrixDSym *current_ThrowMatrix =
1117  static_cast<TMatrixDSym *>(throwMatrix->Clone());
1118  for (int i = first_index; i <= last_index; ++i) {
1119  for (int j = first_index; j <= last_index; ++j) {
1120  current_ThrowMatrix->operator()(i, j) =
1121  subcov(i - first_index, j - first_index);
1122  }
1123  }
1124 
1125  SetThrowMatrix(current_ThrowMatrix);
1126  delete current_ThrowMatrix;
1127 }
1128 
1129 // ********************************************
1131 // ********************************************
1132  delete throwMatrix;
1133  throwMatrix = nullptr;
1134  SetThrowMatrix(cov);
1135 }
1136 
1137 // ********************************************
1139 // ********************************************
1140  for (const auto& [_, group] : FlipGroups) {
1141  for (size_t i = 0; i < group.FlipParameterIndex.size(); ++i) {
1142  const int index = group.FlipParameterIndex[i];
1143  if(!param_skip_adapt_flags[index]) {
1144  MACH3LOG_ERROR("You enabled adaption for parameter which has enabled flipping ({})", _fFancyNames[index]);
1145  MACH3LOG_ERROR("Right now flipping and adapting doesn't work very well");
1146  MACH3LOG_ERROR("Please skip adaption for param {}, using ParametersToSkip option in config", _fFancyNames[index]);
1147  throw MaCh3Exception(__FILE__, __LINE__);
1148  }
1149  }
1150  }
1151 
1152  // HH: Loop over correlations to check if any skipped parameter is correlated with adapted one
1153  // We don't want to change one parameter while keeping the other fixed as this would
1154  // lead to weird penalty terms in the prior after adapting
1155  double max_correlation = 0.01; // Define a threshold for significant correlation above which we throw an error
1156  for (int i = 0; i < _fNumPar; ++i) {
1157  for (int j = 0; j <= i; ++j) {
1158  // The symmetry should have been checked during the Init phase
1160  double corr = (*covMatrix)(i,j)/std::sqrt((*covMatrix)(i,i)*(*covMatrix)(j,j));
1161  if(std::fabs(corr) > max_correlation) {
1162  MACH3LOG_ERROR("Correlation between skipped parameter {} ({}) and non-skipped parameter {} ({}) is {:.6e}, above the allowed threshold of {:.6e}.",
1163  i, _fFancyNames[i], j, _fFancyNames[j], corr, max_correlation);
1164  throw MaCh3Exception(__FILE__, __LINE__);
1165  }
1166  }
1167  }
1168  }
1169 }
1170 
1171 // ********************************************
1172 // HW : Here be adaption
1173 void ParameterHandlerBase::InitialiseAdaption(const YAML::Node& adapt_manager) {
1174 // ********************************************
1175  if(PCAObj){
1176  MACH3LOG_ERROR("PCA has been enabled and now trying to enable Adaption. Right now both configuration don't work with each other");
1177  throw MaCh3Exception(__FILE__ , __LINE__ );
1178  }
1179  if(AdaptiveHandler){
1180  MACH3LOG_ERROR("Adaptive Handler has already been initialise can't do it again so skipping.");
1181  return;
1182  }
1183  AdaptiveHandler = std::make_unique<AdaptiveMCMCHandler>();
1184 
1185  // HH: Backing up _fIndivStepScale and _fGlobalStepScale before adaption
1188 
1189  // HH: adding these here because they will be used to set the individual step scales for non-adapting parameters
1190  auto params_to_skip = GetFromManager<std::vector<std::string>>(adapt_manager["AdaptionOptions"]["Covariance"][matrixName]["ParametersToSkip"], {}, __FILE__ , __LINE__);
1191  // Build a list of skip flags
1192  param_skip_adapt_flags.resize(_fNumPar, false);
1193  for (int i = 0; i <_fNumPar; ++i) {
1195  }
1196 
1197  // Now we read the general settings [these SHOULD be common across all matrices!]
1198  bool success = AdaptiveHandler->InitFromConfig(adapt_manager, matrixName,
1201  );
1202  if (success) {
1203  // Ensure there is no misconfiguration in adaption config
1204  SanitizeAdaption();
1205  AdaptiveHandler->Print();
1206  } else {
1207  MACH3LOG_INFO("Not using adaptive MCMC for {}. Checking external matrix options...", matrixName);
1208  }
1209 
1210  // HH: Adjusting the external matrix reading logic such that you can not do adaptive
1211  // and still read an external matrix
1212  // Logic:
1213  // if read external matrix:
1214  // set throw matrix regardless of adaptive or not
1215  // else:
1216  // if adaptive:
1217  // create new adaptive matrix from scratch
1218  // else:
1219  // do nothing
1220  if(GetFromManager<bool>(adapt_manager["AdaptionOptions"]["Covariance"][matrixName]["UseExternalMatrix"], false, __FILE__ , __LINE__)) {
1221  // Finally, we accept that we want to read the matrix from a file!
1222  auto external_file_name = GetFromManager<std::string>(adapt_manager["AdaptionOptions"]["Covariance"][matrixName]["ExternalMatrixFileName"], "", __FILE__ , __LINE__);
1223  auto external_matrix_name = GetFromManager<std::string>(adapt_manager["AdaptionOptions"]["Covariance"][matrixName]["ExternalMatrixName"], "", __FILE__ , __LINE__);
1224  auto external_mean_name = GetFromManager<std::string>(adapt_manager["AdaptionOptions"]["Covariance"][matrixName]["ExternalMeansName"], "", __FILE__ , __LINE__);
1225 
1226  AdaptiveHandler->SetThrowMatrixFromFile(external_file_name, external_matrix_name, external_mean_name, use_adaptive);
1227  SetThrowMatrix(AdaptiveHandler->GetAdaptiveCovariance());
1228 
1230  // HH: Set individual step scales for non-adapting parameters to the default individual step scales
1231  // global step scale should be 1 so no need to adjust for that
1233 
1234  MACH3LOG_INFO("Successfully Set External Throw Matrix Stored in {}", external_file_name);
1235  } else {
1236  MACH3LOG_INFO("Not using external matrix for {}", matrixName);
1237  if (!success) return; // Not adaptive either so nothing to do
1238  MACH3LOG_INFO("Initialising adaption from scratch");
1239  // If we don't have a covariance matrix to start from for adaptive tune we need to make one!
1240  use_adaptive = true;
1241  AdaptiveHandler->CheckMatrixValidityForAdaption(GetCovMatrix());
1242  AdaptiveHandler->CreateNewAdaptiveCovariance();
1243  return;
1244  }
1245 }
1246 
1247 // ********************************************
1248 // Truely adaptive MCMC!
1250 // ********************************************
1251  // Updates adaptive matrix
1252  // First we update the total means
1253 
1254  // Skip this if we're at a large number of steps
1255  if(AdaptiveHandler->SkipAdaption()) {
1256  AdaptiveHandler->IncrementNSteps();
1257  return;
1258  }
1259 
1261  if(AdaptiveHandler->GetStartRobbinsMonro()){
1262  bool verbose=false;
1263  #ifdef MACH3_DEBUG
1264  verbose=true;
1265  #endif
1266  AdaptiveHandler->UpdateRobbinsMonroScale();
1267  SetStepScale(AdaptiveHandler->GetAdaptionScale(), verbose);
1269  }
1270 
1271  // Call main adaption function
1272  AdaptiveHandler->UpdateAdaptiveCovariance();
1273 
1274  // Set scales to 1 * optimal scale
1275  if(AdaptiveHandler->IndivStepScaleAdapt()) {
1277  SetStepScale(AdaptiveHandler->GetAdaptionScale());
1279  }
1280 
1281  if(AdaptiveHandler->UpdateMatrixAdapt()) {
1282  TMatrixDSym* update_matrix = static_cast<TMatrixDSym*>(AdaptiveHandler->GetAdaptiveCovariance()->Clone());
1283  UpdateThrowMatrix(update_matrix); //Now we update and continue!
1284  //Also Save the adaptive to file
1285  AdaptiveHandler->SaveAdaptiveToFile(AdaptiveHandler->GetOutFileName(), GetName());
1286  }
1287 
1288  AdaptiveHandler->IncrementNSteps();
1289 }
1290 
1291 // ********************************************
1292 //HW: Finds closest possible positive definite matrix in Frobenius Norm ||.||_frob
1293 // Where ||X||_frob=sqrt[sum_ij(x_ij^2)] (basically just turns an n,n matrix into vector in n^2 space
1294 // then does Euclidean norm)
1296 // ********************************************
1297  // Want to get cov' = (cov_sym+cov_polar)/2
1298  // cov_sym=(cov+cov^T)/2
1299  // cov_polar-> SVD cov to cov=USV^T then cov_polar=VSV^T
1300 
1301  //Get frob norm of cov
1302  // Double_t cov_norm=cov->E2Norm();
1303 
1304  TMatrixDSym* cov_trans = cov;
1305  cov_trans->T();
1306  TMatrixDSym cov_sym = 0.5*(*cov+*cov_trans); //If cov is symmetric does nothing, otherwise just ensures symmetry
1307 
1308  //Do SVD to get polar form
1309  TDecompSVD cov_sym_svd=TDecompSVD(cov_sym);
1310  if(!cov_sym_svd.Decompose()){
1311  MACH3LOG_WARN("Cannot do SVD on input matrix, trying MakePosDef() first!");
1312  MakePosDef(&cov_sym);
1313  }
1314 
1315  TMatrixD cov_sym_v = cov_sym_svd.GetV();
1316  TMatrixD cov_sym_vt = cov_sym_v;
1317  cov_sym_vt.T();
1318  //SVD returns as vector (grrr) so need to get into matrix form for multiplying!
1319  TVectorD cov_sym_sigvect = cov_sym_svd.GetSig();
1320 
1321  const Int_t nCols = cov_sym_v.GetNcols(); //square so only need rows hence lack of cols
1322  TMatrixDSym cov_sym_sig(nCols);
1323  TMatrixDDiag cov_sym_sig_diag(cov_sym_sig);
1324  cov_sym_sig_diag=cov_sym_sigvect;
1325 
1326  //Can finally get H=VSV
1327  TMatrixDSym cov_sym_polar = cov_sym_sig.SimilarityT(cov_sym_vt);//V*S*V^T (this took forver to find!)
1328 
1329  //Now we can construct closest approximater Ahat=0.5*(B+H)
1330  TMatrixDSym cov_closest_approx = 0.5*(cov_sym+cov_sym_polar);//Not fully sure why this is even needed since symmetric B -> U=V
1331  //Get norm of transformed
1332  // Double_t approx_norm=cov_closest_approx.E2Norm();
1333  //MACH3LOG_INFO("Initial Norm: {:.6f} | Norm after transformation: {:.6f} | Ratio: {:.6f}", cov_norm, approx_norm, cov_norm / approx_norm);
1334 
1335  *cov = cov_closest_approx;
1336  //Now can just add a makeposdef!
1337  MakePosDef(cov);
1338 }
1339 
1340 // ********************************************
1341 // KS: Convert covariance matrix to correlation matrix and return TH2D which can be used for fancy plotting
1343 // ********************************************
1344  TH2D* hMatrix = new TH2D(GetName().c_str(), GetName().c_str(), _fNumPar, 0.0, _fNumPar, _fNumPar, 0.0, _fNumPar);
1345  hMatrix->SetDirectory(nullptr);
1346  for(int i = 0; i < _fNumPar; i++)
1347  {
1348  hMatrix->SetBinContent(i+1, i+1, 1.);
1349  hMatrix->GetXaxis()->SetBinLabel(i+1, GetParFancyName(i).c_str());
1350  hMatrix->GetYaxis()->SetBinLabel(i+1, GetParFancyName(i).c_str());
1351  }
1352 
1353  #ifdef MULTITHREAD
1354  #pragma omp parallel for
1355  #endif
1356  for(int i = 0; i < _fNumPar; i++)
1357  {
1358  for(int j = 0; j <= i; j++)
1359  {
1360  const double Corr = (*covMatrix)(i,j) / ( GetDiagonalError(i) * GetDiagonalError(j));
1361  hMatrix->SetBinContent(i+1, j+1, Corr);
1362  hMatrix->SetBinContent(j+1, i+1, Corr);
1363  }
1364  }
1365  return hMatrix;
1366 }
1367 
1368 // ********************************************
1369 // KS: After step scale, prefit etc. value were modified save this modified config.
1371 // ********************************************
1372  if (!_fYAMLDoc)
1373  {
1374  MACH3LOG_CRITICAL("Yaml node hasn't been initialised for matrix {}, something is not right", matrixName);
1375  MACH3LOG_CRITICAL("I am not throwing error but should be investigated");
1376  return;
1377  }
1378 
1379  YAML::Node copyNode = _fYAMLDoc;
1380  int i = 0;
1381 
1382  for (YAML::Node param : copyNode["Systematics"])
1383  {
1384  //KS: Feel free to update it, if you need updated prefit value etc
1385  param["Systematic"]["StepScale"] = M3::Utils::FormatDouble(_fIndivStepScale[i], 4);
1386  i++;
1387  }
1388  // Save the modified node to a file
1389  std::ofstream fout("Modified_Matrix.yaml");
1390  fout << copyNode;
1391  fout.close();
1392 }
1393 
1394 // ********************************************
1395 // Set proposed parameter values vector to be base on tune values
1396 void ParameterHandlerBase::SetTune(const std::string& TuneName) {
1397 // ********************************************
1398  if(Tunes == nullptr) {
1399  MACH3LOG_ERROR("Tunes haven't been initialised, which are being loaded from YAML, have you used some deprecated constructor");
1400  throw MaCh3Exception(__FILE__, __LINE__);
1401  }
1402  auto Values = Tunes->GetTune(TuneName);
1403 
1404  SetParameters(Values);
1405 }
1406 
1407 // *************************************
1409  std::vector<double>& BranchValues,
1410  std::vector<std::string>& BranchNames,
1411  const std::vector<std::string>& FancyNames) {
1412 // *************************************
1413  BranchValues.resize(GetNumParams());
1414  BranchNames.resize(GetNumParams());
1415 
1416  // if fancy names are passed match ONLY them
1417  // this allow to perform studies where one perform ND fits and pass it to FD fits
1418  // which usually have more params like osc...
1419  if(FancyNames.size() != 0){
1420  for (int i = 0; i < GetNumParams(); ++i) {
1421  BranchNames[i] = GetParName(i);
1422  // by default set current step
1423  BranchValues[i] = _fPropVal[i];
1424  bool matched = false;
1425  for (size_t iPar = 0; iPar < FancyNames.size(); ++iPar) {
1426  if(GetParFancyName(i) == FancyNames[iPar]) {
1427  MACH3LOG_DEBUG("Matched name {} in config", FancyNames[iPar]);
1428  PosteriorFile->SetBranchStatus(BranchNames[iPar].c_str(), true);
1429  PosteriorFile->SetBranchAddress(BranchNames[iPar].c_str(), &BranchValues[i]);
1430  matched = true;
1431  break;
1432  }
1433  }
1434  if(!matched) {
1435  MACH3LOG_WARN("Didn't match param {}, is this what you want?", GetParFancyName(i));
1436  }
1437  }
1438  } else {
1439  // simply loop over params and match them
1440  for (int i = 0; i < GetNumParams(); ++i) {
1441  BranchNames[i] = GetParName(i);
1442  if (!PosteriorFile->GetBranch(BranchNames[i].c_str())) {
1443  MACH3LOG_WARN("Branch '{}' does not exist in the TTree, is this what you want?", BranchNames[i]);
1444  continue;
1445  }
1446  PosteriorFile->SetBranchStatus(BranchNames[i].c_str(), true);
1447  PosteriorFile->SetBranchAddress(BranchNames[i].c_str(), &BranchValues[i]);
1448  }
1449  }
1450 }
#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
Creates fancy formatted text tables for terminal output.
Definition: TablePrinter.h:50
void AddRow(Args &&... args)
Add a row to the table.
Definition: TablePrinter.h:57
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.
void AddFunctionalFlip(const YAML::Node &param, const int index, const std::string &group_name)
Parse and register a functional flip defined in YAML.
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 ResolveFunctionalFlips()
Resolve queued functional flips after parameter names are known.
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 QueueFunctionalFlip(const YAML::Node &param, const int index, const std::string &group_name)
Queue a functional flip until all parameters have been loaded.
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.
void FlipParameterGroup(const std::string &group)
With a 50% chance, flip all parameters in a group around their respective flip points.
int GetParIndex(const std::string &name) const
Get index based on name.
void ReserveMemory(const int size)
Initialise vectors with parameters information.
M3::float_t EvaluateFunctionalFlip(const FunctionalFlipProposal &flip, const std::vector< double > &proposed_values) const
Evaluate a formula-driven flip for a target parameter.
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< PendingFunctionalFlipProposal > PendingFunctionalFlipParameters
Functional flips waiting for full parameter-name registration.
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.
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.
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.
static std::string Cell(T &&value, std::string_view format)
Format a value using a runtime fmt format string.
Definition: TablePrinter.h:44
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...
@ Right
Align text to the right.
@ Left
Align text to the left.
double Uniform(const double min=0.0, const double max=1.0)
Generate a uniform random number in [min, max).
Definition: Random.h:116
double Gaus(const double mean=0.0, const double sigma=1.0)
Generate a Gaussian random number.
Definition: Random.h:137
std::unique_ptr< ObjectType > Clone(const ObjectType *obj, const std::string &name="")
KS: Creates a copy of a ROOT-like object and wraps it in a smart pointer.
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
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
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.
std::vector< FunctionalFlipProposal > FunctionalFlipParameters
Formula-driven flips that should be applied when this group flips.
Configuration for a formula-based functional flip.
std::vector< int > argument_indices
std::vector< std::string > argument_names
std::unique_ptr< TF1 > evaluator
Configuration for a pending functional flip awaiting all parameters to be initialised.