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 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", true, "If the PDF's should be calculated or not.");
108 declareProperty("NumberBinsPDF", 20, "Number of bins used for the output PDFs");
109 declareProperty(std::make_unique<API::WorkspaceProperty<>>("Chains", "", Kernel::Direction::Output),
110 "The name to give the output workspace for the"
111 " complete chains.");
112 declareProperty(std::make_unique<API::WorkspaceProperty<>>("ConvergedChain", "", Kernel::Direction::Output,
114 "The name to give the output workspace for just the"
115 "converged chain");
116 declareProperty(std::make_unique<API::WorkspaceProperty<API::ITableWorkspace>>("CostFunctionTable", "",
118 "The name to give the output workspace");
119 declareProperty(
121 "The name to give the output workspace (Parameter values and errors)");
122
123 // To be implemented in the future
124 /*declareProperty(
125 std::make_unique<API::WorkspaceProperty<>>(
126 "Chi-squareLandscape", "", Kernel::Direction::Output),
127 "The name to give the output workspace containing the chi-square"
128 " landscape");*/
129}
130
136void FABADAMinimizer::initialize(API::ICostFunction_sptr function, size_t maxIterations) {
137
138 m_leastSquares = std::dynamic_pointer_cast<CostFunctions::CostFuncLeastSquares>(function);
139 if (!m_leastSquares) {
140 throw std::invalid_argument("FABADA works only with least squares."
141 " Different function was given.");
142 }
143
144 m_fitFunction = m_leastSquares->getFittingFunction();
145 m_counter = 0;
146 m_counterGlobal = 0;
147 m_converged = false;
148 m_maxIter = maxIterations;
149
150 // Initialize member variables related to fitting parameters, such as
151 // m_chains, m_jump, etc
153
154 // Initialize member variables related to simulated annealing, such as
155 // m_temperature, m_overexploration, etc
157
158 // Variable to calculate the total number of iterations required by the
159 // SimulatedAnnealing and the posterior chain plus the burn in required
160 // for the adaptation of the jump
161 size_t totalRequiredIterations = 350 + m_chainIterations;
163 totalRequiredIterations += m_simAnnealingItStep * m_leftRefrPoints;
164
165 // Throw error if there are not enough iterations
166 if (totalRequiredIterations >= maxIterations) {
167 throw std::length_error("Too few iterations to perform the"
168 " Simulated Annealing and/or the posterior chain plus"
169 " 350 iterations for the burn-in period. Increase"
170 " MaxIterations property");
171 }
172}
173
178bool FABADAMinimizer::iterate(size_t /*iteration*/) {
179
180 if (!m_leastSquares) {
181 throw std::runtime_error("Cost function isn't set up.");
182 }
183
184 size_t m = m_nParams;
185
186 // Just for the last iteration. For doing exactly the indicated
187 // number of iterations.
189 size_t t = getProperty("ChainLength");
190 m = t % m_nParams;
191 if (m == 0)
192 m = m_nParams;
193 }
194
195 // Do one iteration of FABADA's algorithm for each parameter.
196 for (size_t i = 0; i < m; i++) {
197
198 EigenVector newParameters = m_parameters;
199
200 if (!m_fitFunction->isFixed(i)) {
201 // Calculate the step from a Gaussian
202 double step = gaussianStep(m_jump[i]);
203
204 // Calculate the new value of the parameter
205 double newValue = m_parameters.get(i) + step;
206
207 // Checks if it is inside the boundary constrinctions.
208 // If not, changes it.
209 boundApplication(i, newValue, step);
210 // Obs: As well as checking whether the ties are not contradictory is
211 // too constly, if there are tied parameters that are bounded,
212 // checking that the boundedness is fulfilled for all the parameters
213 // is costly too (extremely costly if the relations get complex,
214 // which is plausible). Therefore it is not yet implemented and
215 // the user should be aware of that.
216
217 // Set the new value in order to calculate the new Chi square value
218 if (std::isnan(newValue))
219 throw std::runtime_error("Parameter value is NaN.");
220 newParameters.set(i, newValue);
221
222 // Update the new value through the IFunction
223 m_fitFunction->setParameter(i, newValue);
224
225 // First, it fulfills the other ties, finally the current parameter tie
226 // It notices m_leastSquares (the CostFuncLeastSquares) that we have
227 // modified the parameters
228 tieApplication(i, newParameters, newValue);
229 m_fitFunction->applyTies();
230 }
231
232 // To track "unmovable" parameters (=> cannot converge)
233 if (!m_parChanged[i] && newParameters.get(i) != m_parameters.get(i))
234 m_parChanged[i] = true;
235
236 // Calculate the new chi2 value
237 double newChi2 = m_leastSquares->val();
238 // Save the old one to check convergence later on
239 double oldChi2 = m_chi2;
240
241 // Given the new chi2, position, m_changes[parameterIndex] and chains are
242 // updated
243 algorithmDisplacement(i, newChi2, newParameters);
244
245 // Update the jump once each JUMP_CHECKING_RATE iterations
246 if (m_counter % JUMP_CHECKING_RATE == 150) // JUMP CHECKING RATE IS 200, BUT
247 // IS NOT CHECKED AT FIRST STEP, IT
248 // IS AT 150
249 {
250 jumpUpdate(i);
251 }
252
253 // Check if the Chi square value has converged for parameter i.
254 //(Obs: const int LOWER_CONVERGENCE_LIMIT = 350 := The iteration
255 // since it starts to check if convergence is reached)
256
257 // Take the unmovable parameters to be converged
258 if (m_leftRefrPoints == 0 && !m_parChanged[i] && m_counter > LOWER_CONVERGENCE_LIMIT)
259 m_parConverged[i] = true;
260
261 if (m_leftRefrPoints == 0 && !m_parConverged[i] && m_counter > LOWER_CONVERGENCE_LIMIT) {
262 if (oldChi2 != m_chi2) {
263 double chi2Quotient = fabs(m_chi2 - oldChi2) / oldChi2;
264 if (chi2Quotient < m_criteria[i]) {
265 m_parConverged[i] = true;
266 }
267 }
268 }
269 } // for i
270
271 // Update the counter, after finishing the iteration for each parameter
272 m_counter += 1;
273 m_counterGlobal += 1;
274
275 // Check if Chi square has converged for all the parameters
276 // if overexploring or Simulated Annealing completed
277 convergenceCheck(); // updates m_converged
278
279 // Check wheather it is refrigeration time or not (for Simulated Annealing)
282 }
283
284 // Evaluates if iterations should continue or not
285 return iterationContinuation();
286
287} // Iterate() end
288
290
295
296 // Creating the reduced chain (considering only one each
297 // "Steps between values" values)
298 size_t chainLength = getProperty("ChainLength");
299 int nSteps = getProperty("StepsBetweenValues");
300 if (nSteps <= 0) {
301 g_log.warning() << "StepsBetweenValues has a non valid value"
302 " (<= 0). Default one used"
303 " (StepsBetweenValues = 10).\n";
304 nSteps = 10;
305 }
306 auto convLength = size_t(double(chainLength) / double(nSteps));
307
308 // Reduced chain
309 std::vector<std::vector<double>> reducedConvergedChain;
310 // Declaring vectors for best values
311 std::vector<double> bestParameters(m_nParams);
312 std::vector<double> errorLeft(m_nParams);
313 std::vector<double> errorRight(m_nParams);
314
315 calculateConvChainAndBestParameters(convLength, nSteps, reducedConvergedChain, bestParameters, errorLeft, errorRight);
316
317 if (!getPropertyValue("Parameters").empty()) {
318 outputParameterTable(bestParameters, errorLeft, errorRight);
319 }
320
321 // Set the best parameter values
322 // Again, we need to modify the CostFunction through the IFunction
323
324 for (size_t j = 0; j < m_nParams; ++j) {
325 m_fitFunction->setParameter(j, bestParameters[j]);
326 }
327 // Convert type to setDirty the cost function
328 std::shared_ptr<MaleableCostFunction> leastSquaresMaleable =
329 std::static_pointer_cast<MaleableCostFunction>(m_leastSquares);
330 leastSquaresMaleable->setDirtyInherited();
331 // Convert back to base class
332 m_leastSquares = std::dynamic_pointer_cast<CostFunctions::CostFuncLeastSquares>(leastSquaresMaleable);
333
334 // If required, output the complete chain
335 if (!getPropertyValue("Chains").empty()) {
336 outputChains();
337 }
338
339 double mostPchi2 = outputPDF(convLength, reducedConvergedChain);
340
341 if (!getPropertyValue("ConvergedChain").empty()) {
342 outputConvergedChains(convLength, nSteps);
343 }
344
345 if (!getPropertyValue("CostFunctionTable").empty()) {
346 outputCostFunctionTable(convLength, mostPchi2);
347 }
348
349 // Set the best parameter values
350 // Not used (needed if we want to return the most probable values too,
351 // so saved as commented out)
352 /*for (size_t j = 0; j < m_nParams; ++j) {
353 m_leastSquares->setParameter(j, BestParameters[j]);
354 }*/
355}
356
362double FABADAMinimizer::gaussianStep(const double &jump) {
363 return Kernel::normal_distribution<double>(0.0, std::abs(jump))(rng);
364}
365
373void FABADAMinimizer::boundApplication(const size_t &parameterIndex, double &newValue, double &step) {
374 API::IConstraint *iConstraint = m_fitFunction->getConstraint(parameterIndex);
375 if (!iConstraint)
376 return;
377 auto *bcon = dynamic_cast<Constraints::BoundaryConstraint *>(iConstraint);
378 if (!bcon)
379 return;
380
381 double lower = bcon->lower();
382 double upper = bcon->upper();
383 double delta = upper - lower;
384
385 // Lower
386 while (newValue < lower) {
387 if (std::abs(step) > delta) {
388 newValue = m_parameters.get(parameterIndex) + step / 10.0;
389 step = step / 10;
390 m_jump[parameterIndex] = m_jump[parameterIndex] / 10;
391 } else {
392 newValue = lower + std::abs(step) - (m_parameters.get(parameterIndex) - lower);
393 }
394 }
395 // Upper
396 while (newValue > upper) {
397 if (std::abs(step) > delta) {
398 newValue = m_parameters.get(parameterIndex) + step / 10.0;
399 step = step / 10;
400 m_jump[parameterIndex] = m_jump[parameterIndex] / 10;
401 } else {
402 newValue = upper - (std::abs(step) + m_parameters.get(parameterIndex) - upper);
403 }
404 }
405}
406
414void FABADAMinimizer::tieApplication(const size_t &parameterIndex, EigenVector &newParameters, double &newValue) {
415 // Fulfill the ties of the other parameters
416 for (size_t j = 0; j < m_nParams; ++j) {
417 if (j != parameterIndex) {
418 API::ParameterTie *tie = m_fitFunction->getTie(j);
419 if (tie) {
420 newValue = tie->eval();
421 if (boost::math::isnan(newValue)) { // maybe not needed
422 throw std::runtime_error("Parameter value is NaN.");
423 }
424 newParameters.set(j, newValue);
425 m_fitFunction->setParameter(j, newValue);
426 }
427 }
428 }
429 // After all the other variables, the current one is updated to the ties
430 API::ParameterTie *tie = m_fitFunction->getTie(parameterIndex);
431 if (tie) {
432 newValue = tie->eval();
433 if (boost::math::isnan(newValue)) { // maybe not needed
434 throw std::runtime_error("Parameter value is NaN.");
435 }
436 newParameters.set(parameterIndex, newValue);
437 m_fitFunction->setParameter(parameterIndex, newValue);
438 }
439
440 // Convert type to setDirty the cost function
441 //(to notify the CostFunction we have modified the IFunction)
442 std::shared_ptr<MaleableCostFunction> leastSquaresMaleable =
443 std::static_pointer_cast<MaleableCostFunction>(m_leastSquares);
444
445 leastSquaresMaleable->setDirtyInherited();
446
447 // Convert back to base class
448 m_leastSquares = std::dynamic_pointer_cast<CostFunctions::CostFuncLeastSquares>(leastSquaresMaleable);
449}
450
457void FABADAMinimizer::algorithmDisplacement(const size_t &parameterIndex, const double &chi2New,
458 const EigenVector &newParameters) {
459
460 // If new Chi square value is lower, jumping directly to new parameter
461 if (chi2New < m_chi2) {
462 for (size_t j = 0; j < m_nParams; j++) {
463 m_chain[j].emplace_back(newParameters.get(j));
464 }
465 m_chain[m_nParams].emplace_back(chi2New);
466 m_parameters = newParameters;
467 m_chi2 = chi2New;
468 m_changes[parameterIndex] += 1;
469 }
470
471 // If new Chi square value is higher, it depends on the probability
472 else {
473 // Calculate probability of change
474 double prob = exp((m_chi2 - chi2New) / (2.0 * m_temperature));
475
476 // Decide if changing or not
477 double p = std::uniform_real_distribution<double>(0.0, 1.0)(rng);
478 if (p <= prob) {
479 for (size_t j = 0; j < m_nParams; j++) {
480 m_chain[j].emplace_back(newParameters.get(j));
481 }
482 m_chain[m_nParams].emplace_back(chi2New);
483 m_parameters = newParameters;
484 m_chi2 = chi2New;
485 m_changes[parameterIndex] += 1;
486 } else {
487 for (size_t j = 0; j < m_nParams; j++) {
488 m_chain[j].emplace_back(m_parameters.get(j));
489 }
490 m_chain[m_nParams].emplace_back(m_chi2);
491 // Old parameters taken again
492 for (size_t j = 0; j < m_nParams; ++j) {
493 m_fitFunction->setParameter(j, m_parameters.get(j));
494 }
495 // Convert type to setDirty the cost function
496 //(to notify the CostFunction we have modified the FittingFunction)
497 std::shared_ptr<MaleableCostFunction> leastSquaresMaleable =
498 std::static_pointer_cast<MaleableCostFunction>(m_leastSquares);
499
500 leastSquaresMaleable->setDirtyInherited();
501
502 // Convert back to base class
503 m_leastSquares = std::dynamic_pointer_cast<CostFunctions::CostFuncLeastSquares>(leastSquaresMaleable);
504 }
505 }
506}
507
512void FABADAMinimizer::jumpUpdate(const size_t &parameterIndex) {
513 const double jumpAR = getProperty("JumpAcceptanceRate");
514 double newJump;
515
516 if (m_leftRefrPoints == 0 && m_changes[parameterIndex] == m_changesOld[parameterIndex])
517 ++m_numInactiveRegenerations[parameterIndex];
518 else
519 m_changesOld[parameterIndex] = m_changes[parameterIndex];
520
521 if (m_changes[parameterIndex] == 0) {
522 newJump = m_jump[parameterIndex] / JUMP_CHECKING_RATE;
523 // JUST FOR THE CASE THERE HAS NOT BEEN ANY CHANGE
524 //(treated as if only one acceptance).
525 } else {
526 m_numInactiveRegenerations[parameterIndex] = 0;
527 double f = m_changes[parameterIndex] / double(m_counter);
528
529 //*ALTERNATIVE CODE
530 //*Current acceptance rate evaluated
531 //*double f = m_changes[parameterIndex] / double(JUMP_CHECKING_RATE);
532 //*Obs: should be quicker to explore, but less stable (maybe not ergodic)
533
534 newJump = m_jump[parameterIndex] * f / jumpAR;
535
536 //*ALTERNATIVE CODE
537 //*Reset the m_changes value to get the information
538 //*for the current jump, not the whole history (maybe not ergodic)
539 //*m_changes[parameterIndex] = 0;
540 }
541
542 m_jump[parameterIndex] = newJump;
543
544 // Check if the new jump is too small. It means that it has been a wrong
545 // convergence.
546 if (std::abs(m_jump[parameterIndex]) < LOW_JUMP_LIMIT) {
547 g_log.warning() << "Wrong convergence might be reached for parameter " +
548 m_fitFunction->parameterName(parameterIndex) +
549 ". Try to set a proper initial value for this parameter\n";
550 }
551}
552
558 size_t innactConvCriterion = getProperty("InnactiveConvergenceCriterion");
559
560 if (m_leftRefrPoints == 0 && m_counter > LOWER_CONVERGENCE_LIMIT && !m_converged) {
561 size_t t = 0;
562 bool ImmobilityConv = false;
563 for (size_t i = 0; i < m_nParams; i++) {
564 if (m_parConverged[i]) {
565 t += 1;
566 } else if (m_numInactiveRegenerations[i] >= innactConvCriterion) {
567 ++t;
568 ImmobilityConv = true;
569 }
570 }
571 // If all parameters have converged (usually or through observed
572 // immobility):
573 // It sets up both the counter and the changes' vector to 0, in order to
574 // consider only the data of the converged part of the chain, when updating
575 // the jump.
576 if (t == m_nParams) {
577 m_converged = true;
578
579 if (ImmobilityConv)
580 g_log.warning() << "Convergence detected through immobility."
581 " It might be a bad convergence.\n";
582
584 m_counter = 0;
585 for (size_t i = 0; i < m_nParams; ++i) {
586 m_changes[i] = 0;
587 }
588
589 // If done with a different temperature, the error would be
590 // wrongly obtained (because the temperature modifies the
591 // chi-square landscape)
592 // Although keeping ergodicity, more iterations will be needed
593 // because a wrong step is initially used.
594 m_temperature = 1.0;
595 }
596
597 // All parameters should converge at the same iteration
598 else {
599 // The not converged parameters can be identified at the last iteration
601 for (size_t i = 0; i < m_nParams; ++i)
602 m_parConverged[i] = false;
603 }
604 }
605}
606
611 // Update jump to separate different temperatures
612 for (size_t i = 0; i < m_nParams; ++i)
613 jumpUpdate(i);
614
615 // Resetting variables for next temperature
616 //(independent jump calculation for different temperatures)
617 m_counter = 0;
618 for (size_t j = 0; j < m_nParams; ++j) {
619 m_changes[j] = 0;
620 }
621 // Simulated Annealing variables updated
623 // To avoid numerical error accumulation
624 if (m_leftRefrPoints == 0)
625 m_temperature = 1.0;
626 else
628}
629
630/* @return :: true if iteration must continue, false otherwise.
631 *
632 */
634
635 // If still through Simulated Annealing
636 if (m_leftRefrPoints != 0)
637 return true;
638
639 if (!m_converged) {
640
641 // If there is not convergence continue the iterations.
643 return true;
644 }
645 // If there is not convergence, but it has been made
646 // convergenceMaxIterations iterations, stop and throw the error.
647 else {
648 std::string failed = "";
649 for (size_t i = 0; i < m_nParams; ++i) {
650 if (!m_parConverged[i]) {
651 failed = failed + m_fitFunction->parameterName(i) + ", ";
652 }
653 }
654 failed.replace(failed.end() - 2, failed.end(), ".");
655 throw std::runtime_error("Convegence NOT reached after " + std::to_string(m_maxIter - m_chainIterations) +
656 " iterations.\n Try to set better initial values for parameters: " + failed +
657 " Or increase the maximum number of iterations "
658 "(MaxIterations property).");
659 }
660 } else {
661 // If convergence has been reached, continue until we complete the chain
662 // length. Otherwise, stop interations.
664 }
665 // can we even get here? -> Nope (we should not, so we do not want it to
666 // continue)
667 return false;
668}
669
675
676 size_t chainLength = m_chain[0].size();
678 API::WorkspaceFactory::Instance().create("Workspace2D", m_nParams + 1, chainLength, chainLength);
679
680 // Do one iteration for each parameter plus one for Chi square.
681 for (size_t j = 0; j < m_nParams + 1; ++j) {
682 auto &X = wsC->mutableX(j);
683 auto &Y = wsC->mutableY(j);
684 for (size_t k = 0; k < chainLength; ++k) {
685 X[k] = double(k);
686 Y[k] = m_chain[j][k];
687 }
688 }
689
690 // Set and name the workspace for the complete chain
691 setProperty("Chains", wsC);
692}
693
700void FABADAMinimizer::outputConvergedChains(size_t convLength, int nSteps) {
701
702 // Create the workspace for the converged part of the chain.
704 if (convLength > 0) {
705 wsConv = API::WorkspaceFactory::Instance().create("Workspace2D", m_nParams + 1, convLength, convLength);
706 } else {
707 g_log.warning() << "Empty converged chain, empty Workspace returned.";
708 wsConv = API::WorkspaceFactory::Instance().create("Workspace2D", m_nParams + 1, 1, 1);
709 }
710
711 // Do one iteration for each parameter plus one for Chi square.
712 for (size_t j = 0; j < m_nParams + 1; ++j) {
713 std::vector<double>::const_iterator first = m_chain[j].begin() + m_convPoint;
714 std::vector<double>::const_iterator last = m_chain[j].end();
715 std::vector<double> convChain(first, last);
716 auto &X = wsConv->mutableX(j);
717 auto &Y = wsConv->mutableY(j);
718 for (size_t k = 0; k < convLength; ++k) {
719 X[k] = double(k);
720 Y[k] = convChain[nSteps * k];
721 }
722 }
723
724 // Set and name the workspace for the converged part of the chain.
725 setProperty("ConvergedChain", wsConv);
726}
727
734void FABADAMinimizer::outputCostFunctionTable(size_t convLength, double mostProbableChi2) {
735 // Create the workspace for the Chi square values.
736 API::ITableWorkspace_sptr wsChi2 = API::WorkspaceFactory::Instance().createTable("TableWorkspace");
737 wsChi2->addColumn("double", "Chi2 Minimum");
738 wsChi2->addColumn("double", "Most Probable Chi2");
739 wsChi2->addColumn("double", "reduced Chi2 Minimum");
740 wsChi2->addColumn("double", "Most Probable reduced Chi2");
741
742 // Obtain the quantity of the initial data.
743 API::FunctionDomain_sptr domain = m_leastSquares->getDomain();
744 size_t dataSize = domain->size();
745
746 // Calculate the value for the reduced Chi square.
747 double minimumChi2Red = m_chi2 / (double(dataSize - m_nParams)); // For de minimum value.
748 double mostProbableChi2Red;
749 if (convLength > 0)
750 mostProbableChi2Red = mostProbableChi2 / (double(dataSize - m_nParams));
751 else {
752 g_log.warning() << "Most probable chi square not calculated. -1 is returned.\n"
753 << "Most probable reduced chi square not calculated. -1 is "
754 "returned.\n";
755 mostProbableChi2Red = -1;
756 }
757
758 // Add the information to the workspace and name it.
759 API::TableRow row = wsChi2->appendRow();
760 row << m_chi2 << mostProbableChi2 << minimumChi2Red << mostProbableChi2Red;
761 setProperty("CostFunctionTable", wsChi2);
762}
763
770double FABADAMinimizer::outputPDF(std::size_t const &convLength, std::vector<std::vector<double>> &reducedChain) {
771
772 // To store the most probable chi square value
773 double mostPchi2;
774
775 // Create the workspace for the Probability Density Functions
776 int pdfLength = getProperty("NumberBinsPDF"); // histogram length for the PDF output workspace
777 if (pdfLength <= 0) {
778 g_log.warning() << "Non valid Number of bins for the PDF (<= 0)."
779 " Default value (20 bins) taken\n";
780 pdfLength = 20;
781 }
782
783 // Calculate the cost function Probability Density Function
784 if (convLength > 0) {
785 std::sort(reducedChain[m_nParams].begin(), reducedChain[m_nParams].end());
786 std::vector<double> xValues((m_nParams + 1) * (pdfLength + 1));
787 std::vector<double> yValues((m_nParams + 1) * pdfLength);
788 std::vector<double> PDFYAxis(pdfLength, 0);
789 double const start = reducedChain[m_nParams][0];
790 double const bin = (reducedChain[m_nParams][convLength - 1] - start) / double(pdfLength);
791
792 mostPchi2 = getMostProbableChiSquared(convLength, reducedChain, pdfLength, xValues, yValues, PDFYAxis, start, bin);
793
794 if (getProperty("PDF"))
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>> &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 if (AnalysisDataService::Instance().doesExist(PDF_GROUP_NAME)) {
815 auto groupPDF = AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(PDF_GROUP_NAME);
816 groupPDF->addWorkspace(workspace);
817 AnalysisDataService::Instance().addOrReplace(PDF_GROUP_NAME, groupPDF);
818 } else {
819 auto groupPDF = std::make_shared<WorkspaceGroup>();
820 groupPDF->addWorkspace(workspace);
821 AnalysisDataService::Instance().addOrReplace(PDF_GROUP_NAME, groupPDF);
822 }
823}
824
825double FABADAMinimizer::getMostProbableChiSquared(std::size_t const &convLength,
826 std::vector<std::vector<double>> &reducedChain, int const &pdfLength,
827 std::vector<double> &xValues, std::vector<double> &yValues,
828 std::vector<double> &PDFYAxis, double const &start,
829 double const &bin) {
830 std::size_t step = 0;
831 std::size_t const chiXStartPos = m_nParams * (pdfLength + 1);
832 std::size_t const chiYStartPos = m_nParams * pdfLength;
833
834 xValues[chiXStartPos] = start;
835 for (std::size_t i = 1; i < static_cast<std::size_t>(pdfLength) + 1; i++) {
836 double binEnd = start + double(i) * bin;
837 xValues[chiXStartPos + i] = binEnd;
838 while (step < convLength && reducedChain[m_nParams][step] <= binEnd) {
839 PDFYAxis[i - 1] += 1;
840 ++step;
841 }
842 // Divided by convLength * bin to normalize
843 yValues[chiYStartPos + i - 1] = PDFYAxis[i - 1] / (double(convLength) * bin);
844 }
845
846 auto indexMostProbableChi2 = std::max_element(PDFYAxis.begin(), PDFYAxis.end());
847
848 return xValues[indexMostProbableChi2 - PDFYAxis.begin()] + (bin / 2.0);
849}
850
851void FABADAMinimizer::setParameterXAndYValuesForPDF(std::vector<double> &xValues, std::vector<double> &yValues,
852 std::vector<std::vector<double>> &reducedChain,
853 std::size_t const &convLength, int const &pdfLength) {
854 for (std::size_t j = 0; j < m_nParams; ++j) {
855
856 // Calculate the Probability Density Function
857 std::vector<double> PDFYAxis(pdfLength, 0);
858 double start = reducedChain[j][0];
859 double bin = (reducedChain[j][convLength - 1] - start) / double(pdfLength);
860 std::size_t step = 0;
861 std::size_t const startXPos = j * (pdfLength + 1);
862 std::size_t const startYPos = j * pdfLength;
863
864 xValues[startXPos] = start;
865 for (std::size_t i = 1; i < static_cast<std::size_t>(pdfLength) + 1; i++) {
866 double binEnd = start + double(i) * bin;
867 xValues[startXPos + i] = binEnd;
868 while (step < convLength && reducedChain[j][step] <= binEnd) {
869 PDFYAxis[i - 1] += 1;
870 ++step;
871 }
872 yValues[startYPos + i - 1] = PDFYAxis[i - 1] / (double(convLength) * bin);
873 }
874 }
875}
876
885void FABADAMinimizer::outputParameterTable(const std::vector<double> &bestParameters,
886 const std::vector<double> &errorLeft,
887 const std::vector<double> &errorRight) {
888
889 // Create the workspace for the parameters' value and errors.
890 API::ITableWorkspace_sptr wsPdfE = API::WorkspaceFactory::Instance().createTable("TableWorkspace");
891 wsPdfE->addColumn("str", "Name");
892 wsPdfE->addColumn("double", "Value");
893 wsPdfE->addColumn("double", "Left's error");
894 wsPdfE->addColumn("double", "Right's error");
895
896 for (size_t j = 0; j < m_nParams; ++j) {
897 API::TableRow row = wsPdfE->appendRow();
898 row << m_fitFunction->parameterName(j) << bestParameters[j] << errorLeft[j] << errorRight[j];
899 }
900 // Set and name the Parameter Errors workspace.
901 setProperty("Parameters", wsPdfE);
902}
903
918 std::vector<std::vector<double>> &reducedChain,
919 std::vector<double> &bestParameters,
920 std::vector<double> &errorLeft,
921 std::vector<double> &errorRight) {
922
923 // In case of reduced chain
924 if (convLength > 0) {
925 // Write first element of the reduced chain
926 for (size_t e = 0; e <= m_nParams; ++e) {
927 std::vector<double> v;
928 v.emplace_back(m_chain[e][m_convPoint]);
929 reducedChain.emplace_back(std::move(v));
930 }
931
932 // Calculate the reducedConvergedChain for the cost fuction.
933 for (size_t k = 1; k < convLength; ++k) {
934 reducedChain[m_nParams].emplace_back(m_chain[m_nParams][nSteps * k + m_convPoint]);
935 }
936
937 // Calculate the position of the minimum Chi square value
938 auto positionMinChi2 = std::min_element(reducedChain[m_nParams].begin(), reducedChain[m_nParams].end());
939 m_chi2 = *positionMinChi2;
940
941 // Calculate the parameter value and the errors
942 for (size_t j = 0; j < m_nParams; ++j) {
943 // Obs: Starts at 1 (0 already added)
944 for (size_t k = 1; k < convLength; ++k) {
945 reducedChain[j].emplace_back(m_chain[j][m_convPoint + nSteps * k]);
946 }
947 // best fit parameters taken
948 bestParameters[j] = reducedChain[j][positionMinChi2 - reducedChain[m_nParams].begin()];
949 std::sort(reducedChain[j].begin(), reducedChain[j].end());
950 auto posBestPar = std::find(reducedChain[j].begin(), reducedChain[j].end(), bestParameters[j]);
951 double varLeft = 0, varRight = 0;
952 for (auto k = reducedChain[j].begin(); k < reducedChain[j].end(); k += 2) {
953 if (k < posBestPar)
954 varLeft += (*k - bestParameters[j]) * (*k - bestParameters[j]);
955 else if (k > posBestPar)
956 varRight += (*k - bestParameters[j]) * (*k - bestParameters[j]);
957 }
958 if (posBestPar != reducedChain[j].begin())
959 varLeft /= double(posBestPar - reducedChain[j].begin());
960 if (posBestPar != reducedChain[j].end() - 1)
961 varRight /= double(reducedChain[j].end() - posBestPar - 1);
962
963 errorLeft[j] = -sqrt(varLeft);
964 errorRight[j] = sqrt(varRight);
965 }
966 } // End if there is converged chain
967
968 // If the converged chain is empty
969 else {
970 g_log.warning() << "There is no converged chain."
971 " Thus the parameters' errors are not"
972 " computed.\n";
973 for (size_t k = 0; k < m_nParams; ++k) {
974 bestParameters[k] = *(m_chain[k].end() - 1);
975 }
976 }
977}
978
983 // The "real" parametersare got (not the active ones)
984 m_nParams = m_fitFunction->nParams();
985 if (m_nParams == 0) {
986 throw std::invalid_argument("Function has 0 fitting parameters.");
987 }
988 // The initial parameters are saved
989 if (m_parameters.size() != m_nParams) {
991 }
992
993 size_t n = getProperty("ChainLength");
994 m_chainIterations = size_t(ceil(double(n) / double(m_nParams)));
995
996 // Save parameter constraints
997 for (size_t i = 0; i < m_nParams; ++i) {
998
999 double param = m_fitFunction->getParameter(i);
1000 m_parameters.set(i, param);
1001
1002 API::IConstraint *iConstraint = m_fitFunction->getConstraint(i);
1003 if (iConstraint) {
1004 auto *bcon = dynamic_cast<Constraints::BoundaryConstraint *>(iConstraint);
1005 if (bcon) {
1006 if (bcon->hasLower()) {
1007 if (param < bcon->lower())
1008 m_parameters.set(i, bcon->lower());
1009 }
1010 if (bcon->hasUpper()) {
1011 if (param > bcon->upper())
1012 m_parameters.set(i, bcon->upper());
1013 }
1014 }
1015 }
1016
1017 // Initialize chains
1018 m_chain.emplace_back(std::vector<double>(1, param));
1019 // Initilize jump parameters
1020 m_jump.emplace_back(param != 0.0 ? std::abs(param / 10) : 0.01);
1021 }
1022 m_chi2 = m_leastSquares->val();
1023 m_chain.emplace_back(std::vector<double>(1, m_chi2));
1024 m_parChanged = std::vector<bool>(m_nParams, false);
1025 m_changes = std::vector<int>(m_nParams, 0);
1027 m_numInactiveRegenerations = std::vector<size_t>(m_nParams, 0);
1028 m_parConverged = std::vector<bool>(m_nParams, false);
1029 m_criteria = std::vector<double>(m_nParams, getProperty("ConvergenceCriteria"));
1030}
1031
1036
1037 // Obs: Simulated Annealing with maximum temperature = 1.0, 1step,
1038 // could be used to increase the "burn-in" period before beginning to
1039 // check for convergence (Not the ideal way -> Think something)
1040 if (getProperty("SimAnnealingApplied")) {
1041
1042 m_temperature = getProperty("MaximumTemperature");
1043 if (m_temperature == 0.0) {
1044 g_log.warning() << "MaximumTemperature not a valid temperature"
1045 " (T = 0). Default (T = 10.0) taken.\n";
1046 m_temperature = 10.0;
1047 }
1048 if (m_temperature < 0) {
1049 g_log.warning() << "MaximumTemperature not a temperature"
1050 " (< 0), absolute value taken\n";
1052 }
1053 m_overexploration = getProperty("Overexploration");
1054 if (!m_overexploration && m_temperature < 1) {
1056 g_log.warning() << "MaximumTemperature reduces proper"
1057 " exploration (0 < T < 1), product inverse taken ("
1058 << m_temperature << ")\n";
1059 }
1060 if (m_overexploration && m_temperature > 1) {
1061 m_overexploration = false;
1062 g_log.warning() << "Overexploration wrong temperature. Not"
1063 " overexploring. Applying usual Simulated Annealing.\n";
1064 }
1065 // Obs: The result is truncated to not have more iterations than
1066 // the chosen by the user and for all temperatures have the same
1067 // number of iterations
1068 m_leftRefrPoints = getProperty("NumRefrigerationSteps");
1069 if (m_leftRefrPoints == 0) {
1070 g_log.warning() << "Wrong value for the number of refrigeration"
1071 " points (== 0). Therefore, default value (5 points) taken.\n";
1072 m_leftRefrPoints = 5;
1073 }
1074
1075 m_simAnnealingItStep = getProperty("SimAnnealingIterations");
1077
1078 m_tempStep = pow(m_temperature, 1.0 / double(m_leftRefrPoints));
1079
1080 // m_simAnnealingItStep stores the number of iterations per step
1081 // 50 for pseudo-continuous temperature decrease
1082 // without hindering the fitting algorithm itself
1084 g_log.warning() << "SimAnnealingIterations/NumRefrigerationSteps too small"
1085 " (< 50 it). Simulated Annealing not applied\n";
1086 m_leftRefrPoints = 0;
1087 m_temperature = 1.0;
1088 }
1089
1090 // During Overexploration, the temperature will not be changed
1092 m_leftRefrPoints = 0;
1093 } else {
1094 m_temperature = 1.0;
1095 m_leftRefrPoints = 0;
1096 }
1097}
1098
1099} // 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
Definition: IndexPeaks.cpp:114
#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.
Definition: ParameterTie.h:35
virtual 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...
A wrapper around Eigen::Vector.
Definition: EigenVector.h:27
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.
Definition: EigenVector.cpp:96
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 setParameterXAndYValuesForPDF(std::vector< double > &xValues, std::vector< double > &yValues, std::vector< std::vector< double > > &reducedChain, std::size_t const &convLength, int const &pdfLength)
Computes the X and Y for the Parameter PDF's.
void boundApplication(const size_t &parameterIndex, double &newValue, double &step)
Public methods only for testing purposes.
size_t m_maxIter
Maximum number of iterations.
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.
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.
double getMostProbableChiSquared(std::size_t const &convLength, std::vector< std::vector< double > > &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 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:52
void warning(const std::string &msg)
Logs at warning level.
Definition: Logger.cpp:86
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
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
Definition: ICostFunction.h:60
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