Mantid
Loading...
Searching...
No Matches
FABADAMinimizer.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
11#include "MantidAPI/IFunction.h"
15#include "MantidAPI/TableRow.h"
19
23
24#include "MantidHistogramData/LinearGenerator.h"
25
26#include "MantidKernel/Logger.h"
30
31#include <boost/math/special_functions/fpclassify.hpp>
32
33#include <cmath>
34#include <cstdio>
35#include <cstdlib>
36#include <ctime>
37#include <random>
38
40using namespace Mantid::API;
41
42namespace {
43
44std::string const DEFAULT_PDF_GROUP_NAME = "__PDF_Workspace";
45
46// static logger object
47Kernel::Logger g_log("FABADAMinimizer");
48// number of iterations when convergence isn't expected
49const size_t LOWER_CONVERGENCE_LIMIT = 350;
50// jump checking rate
51const size_t JUMP_CHECKING_RATE = 200;
52// low jump limit
53const double LOW_JUMP_LIMIT = 1e-25;
54// random number generator
55std::mt19937 rng;
56
57API::MatrixWorkspace_sptr createWorkspace(std::vector<double> const &xValues, std::vector<double> const &yValues,
58 int const numberOfSpectra,
59 std::vector<std::string> const &verticalAxisNames) {
60 auto createWorkspaceAlgorithm = AlgorithmManager::Instance().createUnmanaged("CreateWorkspace");
61 createWorkspaceAlgorithm->initialize();
62 createWorkspaceAlgorithm->setChild(true);
63 createWorkspaceAlgorithm->setLogging(false);
64 createWorkspaceAlgorithm->setProperty("DataX", xValues);
65 createWorkspaceAlgorithm->setProperty("DataY", yValues);
66 createWorkspaceAlgorithm->setProperty("NSpec", numberOfSpectra);
67 createWorkspaceAlgorithm->setProperty("VerticalAxisUnit", "Text");
68 createWorkspaceAlgorithm->setProperty("VerticalAxisValues", verticalAxisNames);
69 createWorkspaceAlgorithm->setProperty("OutputWorkspace", "__PDF");
70 createWorkspaceAlgorithm->execute();
71 return createWorkspaceAlgorithm->getProperty("OutputWorkspace");
72}
73
74} // namespace
75
76DECLARE_FUNCMINIMIZER(FABADAMinimizer, FABADA)
77
78
80 : m_counter(0), m_chainIterations(0), m_changes(), m_jump(), m_parameters(), m_chain(), m_chi2(0.),
81 m_converged(false), m_convPoint(0), m_parConverged(), m_criteria(), m_maxIter(0), m_parChanged(),
82 m_temperature(0.), m_counterGlobal(0), m_simAnnealingItStep(0), m_leftRefrPoints(0), m_tempStep(0.),
83 m_overexploration(false), m_nParams(0), m_numInactiveRegenerations(), m_changesOld() {
84 declareProperty("ChainLength", static_cast<size_t>(10000), "Length of the converged chain.");
85 declareProperty("StepsBetweenValues", 10,
86 "Steps done between chain points to avoid correlation"
87 " between them.");
88 declareProperty("ConvergenceCriteria", 0.01, "Variance in Cost Function for considering convergence reached.");
89 declareProperty("InnactiveConvergenceCriterion", static_cast<size_t>(5),
90 "Number of Innactive Regenerations to consider"
91 " a certain parameter to be converged");
92 declareProperty("JumpAcceptanceRate", 0.6666666, "Desired jumping acceptance rate");
93 // Simulated Annealing properties
94 declareProperty("SimAnnealingApplied", false,
95 "If minimization should be run with Simulated"
96 " Annealing or not");
97 declareProperty("MaximumTemperature", 10.0, "Simulated Annealing maximum temperature");
98 declareProperty("NumRefrigerationSteps", static_cast<size_t>(5), "Simulated Annealing number of temperature changes");
99 declareProperty("SimAnnealingIterations", static_cast<size_t>(10000),
100 "Number of iterations for the Simulated Annealig");
101 declareProperty("Overexploration", false,
102 "If Temperatures < 1 are desired for overexploration,"
103 " no error will jump for that (The temperature is"
104 " constant during the convergence period)."
105 " Useful to find the exact minimum.");
106 // Output Properties
107 declareProperty("PDF", DEFAULT_PDF_GROUP_NAME,
108 "Name for the output PDF workspace group. Default is " + DEFAULT_PDF_GROUP_NAME);
109 declareProperty("NumberBinsPDF", 20, "Number of bins used for the output PDFs");
110 declareProperty(std::make_unique<API::WorkspaceProperty<>>("Chains", "", Kernel::Direction::Output),
111 "The name to give the output workspace for the"
112 " complete chains.");
113 declareProperty(std::make_unique<API::WorkspaceProperty<>>("ConvergedChain", "", Kernel::Direction::Output,
115 "The name to give the output workspace for just the"
116 "converged chain");
117 declareProperty(std::make_unique<API::WorkspaceProperty<API::ITableWorkspace>>("CostFunctionTable", "",
119 "The name to give the output workspace");
120 declareProperty(
122 "The name to give the output workspace (Parameter values and errors)");
123
124 // To be implemented in the future
125 /*declareProperty(
126 std::make_unique<API::WorkspaceProperty<>>(
127 "Chi-squareLandscape", "", Kernel::Direction::Output),
128 "The name to give the output workspace containing the chi-square"
129 " landscape");*/
130}
131
137void FABADAMinimizer::initialize(API::ICostFunction_sptr function, size_t maxIterations) {
138
139 m_leastSquares = std::dynamic_pointer_cast<CostFunctions::CostFuncLeastSquares>(function);
140 if (!m_leastSquares) {
141 throw std::invalid_argument("FABADA works only with least squares."
142 " Different function was given.");
143 }
144
145 m_fitFunction = m_leastSquares->getFittingFunction();
146 m_counter = 0;
147 m_counterGlobal = 0;
148 m_converged = false;
149 m_maxIter = maxIterations;
150
151 // Initialize member variables related to fitting parameters, such as
152 // m_chains, m_jump, etc
154
155 // Initialize member variables related to simulated annealing, such as
156 // m_temperature, m_overexploration, etc
158
159 // Variable to calculate the total number of iterations required by the
160 // SimulatedAnnealing and the posterior chain plus the burn in required
161 // for the adaptation of the jump
162 size_t totalRequiredIterations = 350 + m_chainIterations;
164 totalRequiredIterations += m_simAnnealingItStep * m_leftRefrPoints;
165
166 // Throw error if there are not enough iterations
167 if (totalRequiredIterations >= maxIterations) {
168 throw std::length_error("Too few iterations to perform the"
169 " Simulated Annealing and/or the posterior chain plus"
170 " 350 iterations for the burn-in period. Increase"
171 " MaxIterations property");
172 }
173}
174
179bool FABADAMinimizer::iterate(size_t /*iteration*/) {
180
181 if (!m_leastSquares) {
182 throw std::runtime_error("Cost function isn't set up.");
183 }
184
185 size_t m = m_nParams;
186
187 // Just for the last iteration. For doing exactly the indicated
188 // number of iterations.
190 size_t t = getProperty("ChainLength");
191 m = t % m_nParams;
192 if (m == 0)
193 m = m_nParams;
194 }
195
196 // Do one iteration of FABADA's algorithm for each parameter.
197 for (size_t i = 0; i < m; i++) {
198
199 EigenVector newParameters = m_parameters;
200
201 if (!m_fitFunction->isFixed(i)) {
202 // Calculate the step from a Gaussian
203 double step = gaussianStep(m_jump[i]);
204
205 // Calculate the new value of the parameter
206 double newValue = m_parameters.get(i) + step;
207
208 // Checks if it is inside the boundary constrinctions.
209 // If not, changes it.
210 boundApplication(i, newValue, step);
211 // Obs: As well as checking whether the ties are not contradictory is
212 // too constly, if there are tied parameters that are bounded,
213 // checking that the boundedness is fulfilled for all the parameters
214 // is costly too (extremely costly if the relations get complex,
215 // which is plausible). Therefore it is not yet implemented and
216 // the user should be aware of that.
217
218 // Set the new value in order to calculate the new Chi square value
219 if (std::isnan(newValue))
220 throw std::runtime_error("Parameter value is NaN.");
221 newParameters.set(i, newValue);
222
223 // Update the new value through the IFunction
224 m_fitFunction->setParameter(i, newValue);
225
226 // First, it fulfills the other ties, finally the current parameter tie
227 // It notices m_leastSquares (the CostFuncLeastSquares) that we have
228 // modified the parameters
229 tieApplication(i, newParameters, newValue);
230 m_fitFunction->applyTies();
231 }
232
233 // To track "unmovable" parameters (=> cannot converge)
234 if (!m_parChanged[i] && newParameters.get(i) != m_parameters.get(i))
235 m_parChanged[i] = true;
236
237 // Calculate the new chi2 value
238 double newChi2 = m_leastSquares->val();
239 // Save the old one to check convergence later on
240 double oldChi2 = m_chi2;
241
242 // Given the new chi2, position, m_changes[parameterIndex] and chains are
243 // updated
244 algorithmDisplacement(i, newChi2, newParameters);
245
246 // Update the jump once each JUMP_CHECKING_RATE iterations
247 if (m_counter % JUMP_CHECKING_RATE == 150) // JUMP CHECKING RATE IS 200, BUT
248 // IS NOT CHECKED AT FIRST STEP, IT
249 // IS AT 150
250 {
251 jumpUpdate(i);
252 }
253
254 // Check if the Chi square value has converged for parameter i.
255 //(Obs: const int LOWER_CONVERGENCE_LIMIT = 350 := The iteration
256 // since it starts to check if convergence is reached)
257
258 // Take the unmovable parameters to be converged
259 if (m_leftRefrPoints == 0 && !m_parChanged[i] && m_counter > LOWER_CONVERGENCE_LIMIT)
260 m_parConverged[i] = true;
261
262 if (m_leftRefrPoints == 0 && !m_parConverged[i] && m_counter > LOWER_CONVERGENCE_LIMIT) {
263 if (oldChi2 != m_chi2) {
264 double chi2Quotient = fabs(m_chi2 - oldChi2) / oldChi2;
265 if (chi2Quotient < m_criteria[i]) {
266 m_parConverged[i] = true;
267 }
268 }
269 }
270 } // for i
271
272 // Update the counter, after finishing the iteration for each parameter
273 m_counter += 1;
274 m_counterGlobal += 1;
275
276 // Check if Chi square has converged for all the parameters
277 // if overexploring or Simulated Annealing completed
278 convergenceCheck(); // updates m_converged
279
280 // Check wheather it is refrigeration time or not (for Simulated Annealing)
283 }
284
285 // Evaluates if iterations should continue or not
286 return iterationContinuation();
287
288} // Iterate() end
289
291
296
297 // Creating the reduced chain (considering only one each
298 // "Steps between values" values)
299 size_t chainLength = getProperty("ChainLength");
300 int nSteps = getProperty("StepsBetweenValues");
301 if (nSteps <= 0) {
302 g_log.warning() << "StepsBetweenValues has a non valid value"
303 " (<= 0). Default one used"
304 " (StepsBetweenValues = 10).\n";
305 nSteps = 10;
306 }
307 auto convLength = size_t(double(chainLength) / double(nSteps));
308
309 // Reduced chain
310 std::vector<std::vector<double>> reducedConvergedChain;
311 // Declaring vectors for best values
312 std::vector<double> bestParameters(m_nParams);
313 std::vector<double> errorLeft(m_nParams);
314 std::vector<double> errorRight(m_nParams);
315
316 calculateConvChainAndBestParameters(convLength, nSteps, reducedConvergedChain, bestParameters, errorLeft, errorRight);
317
318 if (!getPropertyValue("Parameters").empty()) {
319 outputParameterTable(bestParameters, errorLeft, errorRight);
320 }
321
322 // Set the best parameter values
323 // Again, we need to modify the CostFunction through the IFunction
324
325 for (size_t j = 0; j < m_nParams; ++j) {
326 m_fitFunction->setParameter(j, bestParameters[j]);
327 }
328 // Convert type to setDirty the cost function
329 std::shared_ptr<MaleableCostFunction> leastSquaresMaleable =
330 std::static_pointer_cast<MaleableCostFunction>(m_leastSquares);
331 leastSquaresMaleable->setDirtyInherited();
332 // Convert back to base class
333 m_leastSquares = std::dynamic_pointer_cast<CostFunctions::CostFuncLeastSquares>(leastSquaresMaleable);
334
335 // If required, output the complete chain
336 if (!getPropertyValue("Chains").empty()) {
337 outputChains();
338 }
339
340 double mostPchi2 = outputPDF(convLength, reducedConvergedChain);
341
342 if (!getPropertyValue("ConvergedChain").empty()) {
343 outputConvergedChains(convLength, nSteps);
344 }
345
346 if (!getPropertyValue("CostFunctionTable").empty()) {
347 outputCostFunctionTable(convLength, mostPchi2);
348 }
349
350 // Set the best parameter values
351 // Not used (needed if we want to return the most probable values too,
352 // so saved as commented out)
353 /*for (size_t j = 0; j < m_nParams; ++j) {
354 m_leastSquares->setParameter(j, BestParameters[j]);
355 }*/
356}
357
363double FABADAMinimizer::gaussianStep(const double &jump) {
364 return Kernel::normal_distribution<double>(0.0, std::abs(jump))(rng);
365}
366
374void FABADAMinimizer::boundApplication(const size_t &parameterIndex, double &newValue, double &step) {
375 API::IConstraint *iConstraint = m_fitFunction->getConstraint(parameterIndex);
376 if (!iConstraint)
377 return;
378 auto const *bcon = dynamic_cast<Constraints::BoundaryConstraint *>(iConstraint);
379 if (!bcon)
380 return;
381
382 double lower = bcon->lower();
383 double upper = bcon->upper();
384 double delta = upper - lower;
385
386 // Lower
387 while (newValue < lower) {
388 if (std::abs(step) > delta) {
389 newValue = m_parameters.get(parameterIndex) + step / 10.0;
390 step = step / 10;
391 m_jump[parameterIndex] = m_jump[parameterIndex] / 10;
392 } else {
393 newValue = lower + std::abs(step) - (m_parameters.get(parameterIndex) - lower);
394 }
395 }
396 // Upper
397 while (newValue > upper) {
398 if (std::abs(step) > delta) {
399 newValue = m_parameters.get(parameterIndex) + step / 10.0;
400 step = step / 10;
401 m_jump[parameterIndex] = m_jump[parameterIndex] / 10;
402 } else {
403 newValue = upper - (std::abs(step) + m_parameters.get(parameterIndex) - upper);
404 }
405 }
406}
407
415void FABADAMinimizer::tieApplication(const size_t &parameterIndex, EigenVector &newParameters, double &newValue) {
416 // Fulfill the ties of the other parameters
417 for (size_t j = 0; j < m_nParams; ++j) {
418 if (j != parameterIndex) {
419 API::ParameterTie *tie = m_fitFunction->getTie(j);
420 if (tie) {
421 newValue = tie->eval();
422 if (boost::math::isnan(newValue)) { // maybe not needed
423 throw std::runtime_error("Parameter value is NaN.");
424 }
425 newParameters.set(j, newValue);
426 m_fitFunction->setParameter(j, newValue);
427 }
428 }
429 }
430 // After all the other variables, the current one is updated to the ties
431 API::ParameterTie *tie = m_fitFunction->getTie(parameterIndex);
432 if (tie) {
433 newValue = tie->eval();
434 if (boost::math::isnan(newValue)) { // maybe not needed
435 throw std::runtime_error("Parameter value is NaN.");
436 }
437 newParameters.set(parameterIndex, newValue);
438 m_fitFunction->setParameter(parameterIndex, newValue);
439 }
440
441 // Convert type to setDirty the cost function
442 //(to notify the CostFunction we have modified the IFunction)
443 std::shared_ptr<MaleableCostFunction> leastSquaresMaleable =
444 std::static_pointer_cast<MaleableCostFunction>(m_leastSquares);
445
446 leastSquaresMaleable->setDirtyInherited();
447
448 // Convert back to base class
449 m_leastSquares = std::dynamic_pointer_cast<CostFunctions::CostFuncLeastSquares>(leastSquaresMaleable);
450}
451
458void FABADAMinimizer::algorithmDisplacement(const size_t &parameterIndex, const double &chi2New,
459 const EigenVector &newParameters) {
460
461 // If new Chi square value is lower, jumping directly to new parameter
462 if (chi2New < m_chi2) {
463 for (size_t j = 0; j < m_nParams; j++) {
464 m_chain[j].emplace_back(newParameters.get(j));
465 }
466 m_chain[m_nParams].emplace_back(chi2New);
467 m_parameters = newParameters;
468 m_chi2 = chi2New;
469 m_changes[parameterIndex] += 1;
470 }
471
472 // If new Chi square value is higher, it depends on the probability
473 else {
474 // Calculate probability of change
475 double prob = exp((m_chi2 - chi2New) / (2.0 * m_temperature));
476
477 // Decide if changing or not
478 double p = std::uniform_real_distribution<double>(0.0, 1.0)(rng);
479 if (p <= prob) {
480 for (size_t j = 0; j < m_nParams; j++) {
481 m_chain[j].emplace_back(newParameters.get(j));
482 }
483 m_chain[m_nParams].emplace_back(chi2New);
484 m_parameters = newParameters;
485 m_chi2 = chi2New;
486 m_changes[parameterIndex] += 1;
487 } else {
488 for (size_t j = 0; j < m_nParams; j++) {
489 m_chain[j].emplace_back(m_parameters.get(j));
490 }
491 m_chain[m_nParams].emplace_back(m_chi2);
492 // Old parameters taken again
493 for (size_t j = 0; j < m_nParams; ++j) {
494 m_fitFunction->setParameter(j, m_parameters.get(j));
495 }
496 // Convert type to setDirty the cost function
497 //(to notify the CostFunction we have modified the FittingFunction)
498 std::shared_ptr<MaleableCostFunction> leastSquaresMaleable =
499 std::static_pointer_cast<MaleableCostFunction>(m_leastSquares);
500
501 leastSquaresMaleable->setDirtyInherited();
502
503 // Convert back to base class
504 m_leastSquares = std::dynamic_pointer_cast<CostFunctions::CostFuncLeastSquares>(leastSquaresMaleable);
505 }
506 }
507}
508
513void FABADAMinimizer::jumpUpdate(const size_t &parameterIndex) {
514 const double jumpAR = getProperty("JumpAcceptanceRate");
515 double newJump;
516
517 if (m_leftRefrPoints == 0 && m_changes[parameterIndex] == m_changesOld[parameterIndex])
518 ++m_numInactiveRegenerations[parameterIndex];
519 else
520 m_changesOld[parameterIndex] = m_changes[parameterIndex];
521
522 if (m_changes[parameterIndex] == 0) {
523 newJump = m_jump[parameterIndex] / JUMP_CHECKING_RATE;
524 // JUST FOR THE CASE THERE HAS NOT BEEN ANY CHANGE
525 //(treated as if only one acceptance).
526 } else {
527 m_numInactiveRegenerations[parameterIndex] = 0;
528 double f = m_changes[parameterIndex] / double(m_counter);
529
530 //*ALTERNATIVE CODE
531 //*Current acceptance rate evaluated
532 //*double f = m_changes[parameterIndex] / double(JUMP_CHECKING_RATE);
533 //*Obs: should be quicker to explore, but less stable (maybe not ergodic)
534
535 newJump = m_jump[parameterIndex] * f / jumpAR;
536
537 //*ALTERNATIVE CODE
538 //*Reset the m_changes value to get the information
539 //*for the current jump, not the whole history (maybe not ergodic)
540 //*m_changes[parameterIndex] = 0;
541 }
542
543 m_jump[parameterIndex] = newJump;
544
545 // Check if the new jump is too small. It means that it has been a wrong
546 // convergence.
547 if (std::abs(m_jump[parameterIndex]) < LOW_JUMP_LIMIT) {
548 g_log.warning() << "Wrong convergence might be reached for parameter " +
549 m_fitFunction->parameterName(parameterIndex) +
550 ". Try to set a proper initial value for this parameter\n";
551 }
552}
553
559 size_t innactConvCriterion = getProperty("InnactiveConvergenceCriterion");
560
561 if (m_leftRefrPoints == 0 && m_counter > LOWER_CONVERGENCE_LIMIT && !m_converged) {
562 size_t t = 0;
563 bool ImmobilityConv = false;
564 for (size_t i = 0; i < m_nParams; i++) {
565 if (m_parConverged[i]) {
566 t += 1;
567 } else if (m_numInactiveRegenerations[i] >= innactConvCriterion) {
568 ++t;
569 ImmobilityConv = true;
570 }
571 }
572 // If all parameters have converged (usually or through observed
573 // immobility):
574 // It sets up both the counter and the changes' vector to 0, in order to
575 // consider only the data of the converged part of the chain, when updating
576 // the jump.
577 if (t == m_nParams) {
578 m_converged = true;
579
580 if (ImmobilityConv)
581 g_log.warning() << "Convergence detected through immobility."
582 " It might be a bad convergence.\n";
583
585 m_counter = 0;
586 for (size_t i = 0; i < m_nParams; ++i) {
587 m_changes[i] = 0;
588 }
589
590 // If done with a different temperature, the error would be
591 // wrongly obtained (because the temperature modifies the
592 // chi-square landscape)
593 // Although keeping ergodicity, more iterations will be needed
594 // because a wrong step is initially used.
595 m_temperature = 1.0;
596 }
597
598 // All parameters should converge at the same iteration
599 else {
600 // The not converged parameters can be identified at the last iteration
602 for (size_t i = 0; i < m_nParams; ++i)
603 m_parConverged[i] = false;
604 }
605 }
606}
607
612 // Update jump to separate different temperatures
613 for (size_t i = 0; i < m_nParams; ++i)
614 jumpUpdate(i);
615
616 // Resetting variables for next temperature
617 //(independent jump calculation for different temperatures)
618 m_counter = 0;
619 for (size_t j = 0; j < m_nParams; ++j) {
620 m_changes[j] = 0;
621 }
622 // Simulated Annealing variables updated
624 // To avoid numerical error accumulation
625 if (m_leftRefrPoints == 0)
626 m_temperature = 1.0;
627 else
629}
630
631/* @return :: true if iteration must continue, false otherwise.
632 *
633 */
635
636 // If still through Simulated Annealing
637 if (m_leftRefrPoints != 0)
638 return true;
639
640 if (!m_converged) {
641
642 // If there is not convergence continue the iterations.
644 return true;
645 }
646 // If there is not convergence, but it has been made
647 // convergenceMaxIterations iterations, stop and throw the error.
648 else {
649 std::string failed = "";
650 for (size_t i = 0; i < m_nParams; ++i) {
651 if (!m_parConverged[i]) {
652 failed = failed + m_fitFunction->parameterName(i) + ", ";
653 }
654 }
655 failed.replace(failed.end() - 2, failed.end(), ".");
656 throw std::runtime_error("Convegence NOT reached after " + std::to_string(m_maxIter - m_chainIterations) +
657 " iterations.\n Try to set better initial values for parameters: " + failed +
658 " Or increase the maximum number of iterations "
659 "(MaxIterations property).");
660 }
661 } else {
662 // If convergence has been reached, continue until we complete the chain
663 // length. Otherwise, stop interations.
665 }
666 // can we even get here? -> Nope (we should not, so we do not want it to
667 // continue)
668 return false;
669}
670
676
677 size_t chainLength = m_chain[0].size();
679 API::WorkspaceFactory::Instance().create("Workspace2D", m_nParams + 1, chainLength, chainLength);
680
681 // Do one iteration for each parameter plus one for Chi square.
682 for (size_t j = 0; j < m_nParams + 1; ++j) {
683 auto &X = wsC->mutableX(j);
684 auto &Y = wsC->mutableY(j);
685 for (size_t k = 0; k < chainLength; ++k) {
686 X[k] = double(k);
687 Y[k] = m_chain[j][k];
688 }
689 }
690
691 // Set and name the workspace for the complete chain
692 setProperty("Chains", wsC);
693}
694
701void FABADAMinimizer::outputConvergedChains(size_t convLength, int nSteps) {
702
703 // Create the workspace for the converged part of the chain.
705 if (convLength > 0) {
706 wsConv = API::WorkspaceFactory::Instance().create("Workspace2D", m_nParams + 1, convLength, convLength);
707 } else {
708 g_log.warning() << "Empty converged chain, empty Workspace returned.";
709 wsConv = API::WorkspaceFactory::Instance().create("Workspace2D", m_nParams + 1, 1, 1);
710 }
711
712 // Do one iteration for each parameter plus one for Chi square.
713 for (size_t j = 0; j < m_nParams + 1; ++j) {
714 std::vector<double>::const_iterator first = m_chain[j].begin() + m_convPoint;
715 std::vector<double>::const_iterator last = m_chain[j].end();
716 std::vector<double> convChain(first, last);
717 auto &X = wsConv->mutableX(j);
718 auto &Y = wsConv->mutableY(j);
719 for (size_t k = 0; k < convLength; ++k) {
720 X[k] = double(k);
721 Y[k] = convChain[nSteps * k];
722 }
723 }
724
725 // Set and name the workspace for the converged part of the chain.
726 setProperty("ConvergedChain", wsConv);
727}
728
735void FABADAMinimizer::outputCostFunctionTable(size_t convLength, double mostProbableChi2) {
736 // Create the workspace for the Chi square values.
737 API::ITableWorkspace_sptr wsChi2 = API::WorkspaceFactory::Instance().createTable("TableWorkspace");
738 wsChi2->addColumn("double", "Chi2 Minimum");
739 wsChi2->addColumn("double", "Most Probable Chi2");
740 wsChi2->addColumn("double", "reduced Chi2 Minimum");
741 wsChi2->addColumn("double", "Most Probable reduced Chi2");
742
743 // Obtain the quantity of the initial data.
744 API::FunctionDomain_sptr domain = m_leastSquares->getDomain();
745 size_t dataSize = domain->size();
746
747 // Calculate the value for the reduced Chi square.
748 double minimumChi2Red = m_chi2 / (double(dataSize - m_nParams)); // For de minimum value.
749 double mostProbableChi2Red;
750 if (convLength > 0)
751 mostProbableChi2Red = mostProbableChi2 / (double(dataSize - m_nParams));
752 else {
753 g_log.warning() << "Most probable chi square not calculated. -1 is returned.\n"
754 << "Most probable reduced chi square not calculated. -1 is "
755 "returned.\n";
756 mostProbableChi2Red = -1;
757 }
758
759 // Add the information to the workspace and name it.
760 API::TableRow row = wsChi2->appendRow();
761 row << m_chi2 << mostProbableChi2 << minimumChi2Red << mostProbableChi2Red;
762 setProperty("CostFunctionTable", wsChi2);
763}
764
771double FABADAMinimizer::outputPDF(std::size_t const &convLength, std::vector<std::vector<double>> &reducedChain) {
772
773 // To store the most probable chi square value
774 double mostPchi2;
775
776 // Create the workspace for the Probability Density Functions
777 int pdfLength = getProperty("NumberBinsPDF"); // histogram length for the PDF output workspace
778 if (pdfLength <= 0) {
779 g_log.warning() << "Non valid Number of bins for the PDF (<= 0)."
780 " Default value (20 bins) taken\n";
781 pdfLength = 20;
782 }
783
784 // Calculate the cost function Probability Density Function
785 if (convLength > 0) {
786 std::sort(reducedChain[m_nParams].begin(), reducedChain[m_nParams].end());
787 std::vector<double> xValues((m_nParams + 1) * (pdfLength + 1));
788 std::vector<double> yValues((m_nParams + 1) * pdfLength);
789 std::vector<double> PDFYAxis(pdfLength, 0);
790 double const start = reducedChain[m_nParams][0];
791 double const bin = (reducedChain[m_nParams][convLength - 1] - start) / double(pdfLength);
792
793 mostPchi2 = getMostProbableChiSquared(convLength, reducedChain, pdfLength, xValues, yValues, PDFYAxis, start, bin);
794
795 outputPDF(xValues, yValues, reducedChain, convLength, pdfLength);
796
797 } // if convLength > 0
798 else {
799 g_log.warning() << "No points to create PDF. Empty Workspace returned.\n";
800 mostPchi2 = -1;
801 }
802
803 return mostPchi2;
804}
805
806void FABADAMinimizer::outputPDF(std::vector<double> &xValues, std::vector<double> &yValues,
807 std::vector<std::vector<double>> const &reducedChain, std::size_t const &convLength,
808 int const &pdfLength) {
809 setParameterXAndYValuesForPDF(xValues, yValues, reducedChain, convLength, pdfLength);
810 auto parameterNames = m_fitFunction->getParameterNames();
811 parameterNames.emplace_back("Chi Squared");
812 auto const workspace = createWorkspace(xValues, yValues, int(m_nParams) + 1, parameterNames);
813
814 const auto pdfName = getPropertyValue("PDF");
815 if (AnalysisDataService::Instance().doesExist(pdfName)) {
816 auto groupPDF = AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(pdfName);
817 groupPDF->addWorkspace(workspace);
818 AnalysisDataService::Instance().addOrReplace(pdfName, groupPDF);
819 } else {
820 auto groupPDF = std::make_shared<WorkspaceGroup>();
821 groupPDF->addWorkspace(workspace);
822 AnalysisDataService::Instance().addOrReplace(pdfName, groupPDF);
823 }
824}
825
826double FABADAMinimizer::getMostProbableChiSquared(std::size_t const &convLength,
827 std::vector<std::vector<double>> const &reducedChain,
828 int const &pdfLength, std::vector<double> &xValues,
829 std::vector<double> &yValues, std::vector<double> &PDFYAxis,
830 double const &start, double const &bin) {
831 std::size_t step = 0;
832 std::size_t const chiXStartPos = m_nParams * (pdfLength + 1);
833 std::size_t const chiYStartPos = m_nParams * pdfLength;
834
835 xValues[chiXStartPos] = start;
836 for (std::size_t i = 1; i < static_cast<std::size_t>(pdfLength) + 1; i++) {
837 double binEnd = start + double(i) * bin;
838 xValues[chiXStartPos + i] = binEnd;
839 while (step < convLength && reducedChain[m_nParams][step] <= binEnd) {
840 PDFYAxis[i - 1] += 1;
841 ++step;
842 }
843 // Divided by convLength * bin to normalize
844 yValues[chiYStartPos + i - 1] = PDFYAxis[i - 1] / (double(convLength) * bin);
845 }
846
847 auto indexMostProbableChi2 = std::max_element(PDFYAxis.begin(), PDFYAxis.end());
848
849 return xValues[indexMostProbableChi2 - PDFYAxis.begin()] + (bin / 2.0);
850}
851
852void FABADAMinimizer::setParameterXAndYValuesForPDF(std::vector<double> &xValues, std::vector<double> &yValues,
853 std::vector<std::vector<double>> const &reducedChain,
854 std::size_t const &convLength, int const &pdfLength) {
855 for (std::size_t j = 0; j < m_nParams; ++j) {
856
857 // Calculate the Probability Density Function
858 std::vector<double> PDFYAxis(pdfLength, 0);
859 double start = reducedChain[j][0];
860 double bin = (reducedChain[j][convLength - 1] - start) / double(pdfLength);
861 std::size_t step = 0;
862 std::size_t const startXPos = j * (pdfLength + 1);
863 std::size_t const startYPos = j * pdfLength;
864
865 xValues[startXPos] = start;
866 for (std::size_t i = 1; i < static_cast<std::size_t>(pdfLength) + 1; i++) {
867 double binEnd = start + double(i) * bin;
868 xValues[startXPos + i] = binEnd;
869 while (step < convLength && reducedChain[j][step] <= binEnd) {
870 PDFYAxis[i - 1] += 1;
871 ++step;
872 }
873 yValues[startYPos + i - 1] = PDFYAxis[i - 1] / (double(convLength) * bin);
874 }
875 }
876}
877
886void FABADAMinimizer::outputParameterTable(const std::vector<double> &bestParameters,
887 const std::vector<double> &errorLeft,
888 const std::vector<double> &errorRight) {
889
890 // Create the workspace for the parameters' value and errors.
891 API::ITableWorkspace_sptr wsPdfE = API::WorkspaceFactory::Instance().createTable("TableWorkspace");
892 wsPdfE->addColumn("str", "Name");
893 wsPdfE->addColumn("double", "Value");
894 wsPdfE->addColumn("double", "Left's error");
895 wsPdfE->addColumn("double", "Right's error");
896
897 for (size_t j = 0; j < m_nParams; ++j) {
898 API::TableRow row = wsPdfE->appendRow();
899 row << m_fitFunction->parameterName(j) << bestParameters[j] << errorLeft[j] << errorRight[j];
900 }
901 // Set and name the Parameter Errors workspace.
902 setProperty("Parameters", wsPdfE);
903}
904
919 std::vector<std::vector<double>> &reducedChain,
920 std::vector<double> &bestParameters,
921 std::vector<double> &errorLeft,
922 std::vector<double> &errorRight) {
923
924 // In case of reduced chain
925 if (convLength > 0) {
926 // Write first element of the reduced chain
927 for (size_t e = 0; e <= m_nParams; ++e) {
928 std::vector<double> v;
929 v.emplace_back(m_chain[e][m_convPoint]);
930 reducedChain.emplace_back(std::move(v));
931 }
932
933 // Calculate the reducedConvergedChain for the cost fuction.
934 for (size_t k = 1; k < convLength; ++k) {
935 reducedChain[m_nParams].emplace_back(m_chain[m_nParams][nSteps * k + m_convPoint]);
936 }
937
938 // Calculate the position of the minimum Chi square value
939 auto positionMinChi2 = std::min_element(reducedChain[m_nParams].begin(), reducedChain[m_nParams].end());
940 m_chi2 = *positionMinChi2;
941
942 // Calculate the parameter value and the errors
943 for (size_t j = 0; j < m_nParams; ++j) {
944 // Obs: Starts at 1 (0 already added)
945 for (size_t k = 1; k < convLength; ++k) {
946 reducedChain[j].emplace_back(m_chain[j][m_convPoint + nSteps * k]);
947 }
948 // best fit parameters taken
949 bestParameters[j] = reducedChain[j][positionMinChi2 - reducedChain[m_nParams].begin()];
950 std::sort(reducedChain[j].begin(), reducedChain[j].end());
951 auto posBestPar = std::find(reducedChain[j].begin(), reducedChain[j].end(), bestParameters[j]);
952 double varLeft = 0, varRight = 0;
953 for (auto k = reducedChain[j].begin(); k < reducedChain[j].end(); k += 2) {
954 if (k < posBestPar)
955 varLeft += (*k - bestParameters[j]) * (*k - bestParameters[j]);
956 else if (k > posBestPar)
957 varRight += (*k - bestParameters[j]) * (*k - bestParameters[j]);
958 }
959 if (posBestPar != reducedChain[j].begin())
960 varLeft /= double(posBestPar - reducedChain[j].begin());
961 if (posBestPar != reducedChain[j].end() - 1)
962 varRight /= double(reducedChain[j].end() - posBestPar - 1);
963
964 errorLeft[j] = -sqrt(varLeft);
965 errorRight[j] = sqrt(varRight);
966 }
967 } // End if there is converged chain
968
969 // If the converged chain is empty
970 else {
971 g_log.warning() << "There is no converged chain."
972 " Thus the parameters' errors are not"
973 " computed.\n";
974 for (size_t k = 0; k < m_nParams; ++k) {
975 bestParameters[k] = *(m_chain[k].end() - 1);
976 }
977 }
978}
979
984 // The "real" parametersare got (not the active ones)
985 m_nParams = m_fitFunction->nParams();
986 if (m_nParams == 0) {
987 throw std::invalid_argument("Function has 0 fitting parameters.");
988 }
989 // The initial parameters are saved
990 if (m_parameters.size() != m_nParams) {
992 }
993
994 size_t n = getProperty("ChainLength");
995 m_chainIterations = size_t(ceil(double(n) / double(m_nParams)));
996
997 // Save parameter constraints
998 for (size_t i = 0; i < m_nParams; ++i) {
999
1000 double param = m_fitFunction->getParameter(i);
1001 m_parameters.set(i, param);
1002
1003 API::IConstraint *iConstraint = m_fitFunction->getConstraint(i);
1004 if (iConstraint) {
1005 auto const *bcon = dynamic_cast<Constraints::BoundaryConstraint *>(iConstraint);
1006 if (bcon) {
1007 if (bcon->hasLower()) {
1008 if (param < bcon->lower())
1009 m_parameters.set(i, bcon->lower());
1010 }
1011 if (bcon->hasUpper()) {
1012 if (param > bcon->upper())
1013 m_parameters.set(i, bcon->upper());
1014 }
1015 }
1016 }
1017
1018 // Initialize chains
1019 m_chain.emplace_back(std::vector<double>(1, param));
1020 // Initilize jump parameters
1021 m_jump.emplace_back(param != 0.0 ? std::abs(param / 10) : 0.01);
1022 }
1023 m_chi2 = m_leastSquares->val();
1024 m_chain.emplace_back(std::vector<double>(1, m_chi2));
1025 m_parChanged = std::vector<bool>(m_nParams, false);
1026 m_changes = std::vector<int>(m_nParams, 0);
1028 m_numInactiveRegenerations = std::vector<size_t>(m_nParams, 0);
1029 m_parConverged = std::vector<bool>(m_nParams, false);
1030 m_criteria = std::vector<double>(m_nParams, getProperty("ConvergenceCriteria"));
1031}
1032
1037
1038 // Obs: Simulated Annealing with maximum temperature = 1.0, 1step,
1039 // could be used to increase the "burn-in" period before beginning to
1040 // check for convergence (Not the ideal way -> Think something)
1041 if (getProperty("SimAnnealingApplied")) {
1042
1043 m_temperature = getProperty("MaximumTemperature");
1044 if (m_temperature == 0.0) {
1045 g_log.warning() << "MaximumTemperature not a valid temperature"
1046 " (T = 0). Default (T = 10.0) taken.\n";
1047 m_temperature = 10.0;
1048 }
1049 if (m_temperature < 0) {
1050 g_log.warning() << "MaximumTemperature not a temperature"
1051 " (< 0), absolute value taken\n";
1053 }
1054 m_overexploration = getProperty("Overexploration");
1055 if (!m_overexploration && m_temperature < 1) {
1057 g_log.warning() << "MaximumTemperature reduces proper"
1058 " exploration (0 < T < 1), product inverse taken ("
1059 << m_temperature << ")\n";
1060 }
1061 if (m_overexploration && m_temperature > 1) {
1062 m_overexploration = false;
1063 g_log.warning() << "Overexploration wrong temperature. Not"
1064 " overexploring. Applying usual Simulated Annealing.\n";
1065 }
1066 // Obs: The result is truncated to not have more iterations than
1067 // the chosen by the user and for all temperatures have the same
1068 // number of iterations
1069 m_leftRefrPoints = getProperty("NumRefrigerationSteps");
1070 if (m_leftRefrPoints == 0) {
1071 g_log.warning() << "Wrong value for the number of refrigeration"
1072 " points (== 0). Therefore, default value (5 points) taken.\n";
1073 m_leftRefrPoints = 5;
1074 }
1075
1076 m_simAnnealingItStep = getProperty("SimAnnealingIterations");
1078
1079 m_tempStep = pow(m_temperature, 1.0 / double(m_leftRefrPoints));
1080
1081 // m_simAnnealingItStep stores the number of iterations per step
1082 // 50 for pseudo-continuous temperature decrease
1083 // without hindering the fitting algorithm itself
1085 g_log.warning() << "SimAnnealingIterations/NumRefrigerationSteps too small"
1086 " (< 50 it). Simulated Annealing not applied\n";
1087 m_leftRefrPoints = 0;
1088 m_temperature = 1.0;
1089 }
1090
1091 // During Overexploration, the temperature will not be changed
1093 m_leftRefrPoints = 0;
1094 } else {
1095 m_temperature = 1.0;
1096 m_leftRefrPoints = 0;
1097 }
1098}
1099
1100} // namespace Mantid::CurveFitting::FuncMinimisers
#define DECLARE_FUNCMINIMIZER(classname, username)
Macro for declaring a new type of minimizers to be used with the FuncMinimizerFactory.
IPeaksWorkspace_sptr workspace
#define fabs(x)
Definition Matrix.cpp:22
double lower
lower and upper bounds on the multiplier, if known
double upper
An interface to a constraint.
Definition IConstraint.h:26
Ties fitting parameters.
double eval(bool setParameterValue=true)
Evaluate the expression.
TableRow represents a row in a TableWorkspace.
Definition TableRow.h:39
Class to hold a set of workspaces.
void addWorkspace(const Workspace_sptr &workspace)
Adds a workspace to the group.
A property class for workspaces.
A boundary constraint is designed to be used to set either upper or lower (or both) boundaries on a s...
const double & lower() const
Return the lower bound value.
A wrapper around Eigen::Vector.
Definition EigenVector.h:33
void set(const size_t i, const double value)
Set an element.
double get(const size_t i) const
Get an element.
void resize(const size_t n)
Resize the vector.
size_t size() const
Size of the vector.
FABADA : Implements the FABADA Algorithm, based on a Adaptive Metropolis Algorithm extended with Gibb...
bool iterate(size_t iter) override
Do one iteration.
size_t m_nParams
Number of parameters of the FittingFunction (not necessarily the CostFunction)
size_t m_counter
The number of iterations done (restarted at each phase).
double gaussianStep(const double &jump)
Returns the step from a Gaussian given sigma = Jump.
std::shared_ptr< CostFunctions::CostFuncLeastSquares > m_leastSquares
Pointer to the cost function. Must be the least squares.
std::vector< int > m_changes
The number of changes done on each parameter.
double m_tempStep
Temperature step between different Simulated Annealing phases.
std::vector< double > m_jump
The jump for each parameter.
void tieApplication(const size_t &parameterIndex, EigenVector &newParameters, double &newValue)
Applied to the other parameters first and sequentially, finally to the current one.
double outputPDF(std::size_t const &convLength, std::vector< std::vector< double > > &reducedChain)
Output PDF.
void jumpUpdate(const size_t &parameterIndex)
Updates the ParameterIndex-th parameter jump if appropriate.
void boundApplication(const size_t &parameterIndex, double &newValue, double &step)
Public methods only for testing purposes.
void initSimulatedAnnealing()
Initialize member variables related to simulated annealing.
double m_temperature
Simulated Annealing temperature.
double m_chi2
The chi square result of previous iteration;.
void finalize() override
Finalize minimization, eg store additional outputs.
void simAnnealingRefrigeration()
Refrigerates the system if appropriate.
size_t m_counterGlobal
The global number of iterations done.
std::vector< double > m_criteria
Convergence criteria for each parameter.
std::vector< size_t > m_numInactiveRegenerations
Number of consecutive regenerations without changes.
void outputParameterTable(const std::vector< double > &bestParameters, const std::vector< double > &errorsLeft, const std::vector< double > &errorsRight)
Output parameter table.
size_t m_simAnnealingItStep
Number of iterations between Simulated Annealing refrigeration points.
void algorithmDisplacement(const size_t &parameterIndex, const double &chi2New, const EigenVector &newParameters)
Given the new chi2, next position is calculated and updated.
std::vector< std::vector< double > > m_chain
Markov chain.
void outputCostFunctionTable(size_t convLength, double mostProbableChi2)
Output cost function.
void initialize(API::ICostFunction_sptr function, size_t maxIterations) override
Initialize minimizer, i.e. pass a function to minimize.
size_t m_convPoint
The point when convergence has been reached.
std::vector< bool > m_parChanged
Bool that idicates if a varible has changed at some self iteration.
double getMostProbableChiSquared(std::size_t const &convLength, std::vector< std::vector< double > > const &reducedChain, int const &pdfLength, std::vector< double > &xValues, std::vector< double > &yValues, std::vector< double > &PDFYAxis, double const &start, double const &bin)
Finds the most probable Chi Squared value.
void setParameterXAndYValuesForPDF(std::vector< double > &xValues, std::vector< double > &yValues, std::vector< std::vector< double > > const &reducedChain, std::size_t const &convLength, int const &pdfLength)
Computes the X and Y for the Parameter PDF's.
bool iterationContinuation()
Decides wheather iteration must continue or not.
std::vector< bool > m_parConverged
Convergence of each parameter.
double costFunctionVal() override
Return current value of the cost function.
std::vector< int > m_changesOld
To track convergence through immobility.
size_t m_leftRefrPoints
The number of refrigeration points left.
size_t m_chainIterations
The number of chain iterations.
API::IFunction_sptr m_fitFunction
Pointer to the Fitting Function (IFunction) inside the cost function.
bool m_converged
Boolean that indicates global convergence.
void calculateConvChainAndBestParameters(size_t convLength, int nSteps, std::vector< std::vector< double > > &reducedChain, std::vector< double > &bestParameters, std::vector< double > &errorLeft, std::vector< double > &errorRight)
Calculated converged chain and parameters.
void initChainsAndParameters()
Initialize member variables related to fitting parameters.
void convergenceCheck()
Check for convergence (including Overexploration convergence), updates m_converged.
void outputConvergedChains(size_t convLength, int nSteps)
Output converged chains.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition Logger.h:51
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
std::shared_ptr< ITableWorkspace > ITableWorkspace_sptr
shared pointer to Mantid::API::ITableWorkspace
std::shared_ptr< T > createWorkspace(InitArgs... args)
Kernel::Logger g_log("ExperimentInfo")
static logger object
std::shared_ptr< ICostFunction > ICostFunction_sptr
define a shared pointer to a cost function
std::shared_ptr< FunctionDomain > FunctionDomain_sptr
typedef for a shared pointer
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Output
An output workspace.
Definition Property.h:54