Mantid
Loading...
Searching...
No Matches
ProfileChiSquared1D.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2021 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 +
9#include "MantidAPI/Column.h"
10#include "MantidAPI/IFunction.h"
17
18#include <boost/math/distributions/chi_squared.hpp>
19#include <utility>
20
21namespace {
22// The maximum difference of chi squared to search for
23// 10.8276 covers 99.9% of the distrubition
24constexpr double MAXCHISQUAREDIFFERENCE = 10.8276;
25
31double getDiff(const Mantid::API::IFunction &fun, size_t nParams, const Mantid::API::FunctionDomain &domain,
32 Mantid::API::FunctionValues &values, double chi0) {
33 double chiSquared = 0.0;
34 double chiSquaredWeighted = 0.0;
35 double dof = 0;
36 Mantid::CurveFitting::Algorithms::CalculateChiSquared::calcChiSquared(fun, nParams, domain, values, chiSquared,
37 chiSquaredWeighted, dof);
38 return chiSquaredWeighted - chi0;
39}
40
41} // namespace
42
44
45DECLARE_ALGORITHM(ProfileChiSquared1D)
46
47using namespace Mantid::API;
50class ChiSlice {
51public:
61 ChiSlice(IFunction_sptr inputFunction, int fixedParameterIndex, API::MatrixWorkspace_sptr inputWS, int workspaceIndex,
62 const API::FunctionDomain &domain, API::FunctionValues &values, double chi0,
63 std::vector<int> &freeParameters)
64 : m_fixedParameterIndex(fixedParameterIndex), m_domain(domain), m_values(values), m_chi0(chi0),
65 m_fitalg(AlgorithmFactory::Instance().create("Fit", -1)), m_function(std::move(inputFunction)),
66 m_ws(std::move(inputWS)), m_workspaceIndex(workspaceIndex), m_freeParameters(freeParameters) {
67 // create a fitting algorithm based on least squares (which is the default)
68 m_fitalg->setChild(true);
69 }
74 double operator()(double p) {
75 m_fitalg->initialize();
76 m_fitalg->setProperty("Function", m_function);
77 m_fitalg->setProperty("InputWorkspace", m_ws);
78 m_fitalg->setProperty("WorkspaceIndex", m_workspaceIndex);
79 IFunction_sptr function = m_fitalg->getProperty("Function");
80 std::vector<double> originalParamValues(function->nParams());
81 for (auto ip = 0u; ip < function->nParams(); ++ip) {
82 originalParamValues[ip] = function->getParameter(ip);
83 }
84 function->setParameter(m_fixedParameterIndex, originalParamValues[m_fixedParameterIndex] + p);
85 function->fix(m_fixedParameterIndex);
86
87 // re run the fit to minimze the unfixed parameters
88 m_fitalg->execute();
89 // find change in chi 2
90 // num free parameters is the number of global free parameters - the 1 we've
91 // just fixed
92 int numFreeParameters = static_cast<int>(m_freeParameters.size() - 1);
93 double res = getDiff(*function, numFreeParameters, m_domain, m_values, m_chi0);
94 // reset fit to original values
95 for (auto ip = 0u; ip < function->nParams(); ++ip) {
96 function->setParameter(ip, originalParamValues[ip]);
97 }
98 function->unfix(m_fixedParameterIndex);
99 return res;
100 }
101
107 Functions::ChebfunBase_sptr makeApprox(double lBound, double rBound, std::vector<double> &P, std::vector<double> &A) {
108
109 auto base = Functions::ChebfunBase::bestFitAnyTolerance(lBound, rBound, *this, P, A, 1.0, 1e-4, 129);
110 if (!base) {
111 base = std::make_shared<Functions::ChebfunBase>(10, lBound, rBound, 1e-4);
112 P = base->fit(*this);
113 A = base->calcA(P);
114 }
115 return base;
116 }
117
121 double findBound(double shift) {
122 double bound0 = 0;
123 double diff0 = (*this)(0);
124 double bound = shift;
125 bool canDecrease = true;
126 for (size_t i = 0; i < 100; ++i) {
127 double diff = (*this)(bound);
128
129 bool isIncreasing = fabs(bound) > fabs(bound0) && diff > diff0;
130 if (canDecrease) {
131 if (isIncreasing)
132 canDecrease = false;
133 } else {
134 if (!isIncreasing) {
135 bound = bound0;
136 break;
137 }
138 }
139
140 bound0 = bound;
141 diff0 = diff;
142
143 if (diff > MAXCHISQUAREDIFFERENCE - 1) {
144 if (diff < MAXCHISQUAREDIFFERENCE) {
145 break;
146 }
147 // diff is too large
148 bound *= 0.75;
149 } else {
150 // diff is too small
151 bound *= 2;
152 }
153 }
154 return bound;
155 }
156
157private:
158 // Fixed parameter index
165 double m_chi0;
166 // fitting algorithm
168 // Input workspace and function
172 // Vector of free parameter indices
173 std::vector<int> m_freeParameters;
174}; // namespace Algorithms
175
177ProfileChiSquared1D::ProfileChiSquared1D() : IFittingAlgorithm() {}
178
179const std::string ProfileChiSquared1D::name() const { return "ProfileChiSquared1D"; }
180
181int ProfileChiSquared1D::version() const { return 1; }
182
183const std::string ProfileChiSquared1D::summary() const {
184 return "Profiles chi squared about its minimum to obtain parameter errors "
185 "for the input function.";
186}
187
188void ProfileChiSquared1D::initConcrete() { declareProperty("Output", "", "A base name for output workspaces."); }
189
190void ProfileChiSquared1D::execConcrete() {
191 // Number of fiting parameters
192 auto nParams = m_function->nParams();
193 // Create an output table for displaying slices of the chi squared and
194 // the probabilitydensity function
195 auto pdfTable = API::WorkspaceFactory::Instance().createTable();
196
197 // Sigma confidence levels, could be an input but for now look for 1 sigma
198 // (68%) 2 sigma (95) and 3(99%) error bounds chi2 disturbution has 1 degree
199 // of freedom if we are changing 1 parameter at a time
200 boost::math::chi_squared chi2Dist(1);
201 std::array<double, 3> sigmas = {1, 2, 3};
202 std::array<double, 3> qvalues;
203 for (size_t i = 0; i < sigmas.size(); i++) {
204 double pvalue = std::erf(sigmas[i] / sqrt(2));
205 // find chi2 quanitile for given p value
206 qvalues[i] = boost::math::quantile(chi2Dist, pvalue);
207 }
208
209 // Find number of free parameter, should be >= 2
210 std::vector<int> freeParameters;
211 for (size_t ip = 0; ip < nParams; ++ip) {
212 if (m_function->isActive(ip)) {
213 freeParameters.push_back(static_cast<int>(ip));
214 }
215 }
216
217 if (freeParameters.size() < 2) {
218 throw std::invalid_argument("Function must have 2 or more free parameters");
219 }
220
221 std::string baseName = getProperty("Output");
222 Workspace_sptr ws = getProperty("InputWorkspace");
223 int workspaceIndex = getProperty("WorkspaceIndex");
224 MatrixWorkspace_sptr inputws = std::dynamic_pointer_cast<MatrixWorkspace>(ws);
225 if (baseName.empty()) {
226 baseName = "ProfileChiSquared1D";
227 }
229 "The name of the TableWorkspace in which to store the "
230 "pdfs of fit parameters");
231 setPropertyValue("PDFs", baseName + "_pdf");
232 setProperty("PDFs", pdfTable);
233
234 // Create an output table for displaying the parameter errors.
235 auto errorsTable = API::WorkspaceFactory::Instance().createTable();
236 auto nameColumn = errorsTable->addColumn("str", "Parameter");
237 auto valueColumn = errorsTable->addColumn("double", "Value");
238 auto minValueColumn = errorsTable->addColumn("double", "Value at Min");
239 errorsTable->addColumn("double", "Left Error (1-sigma)");
240 errorsTable->addColumn("double", "Right Error (1-sigma)");
241 errorsTable->addColumn("double", "Left Error (2-sigma)");
242 errorsTable->addColumn("double", "Right Error (2-sigma )");
243 errorsTable->addColumn("double", "Left Error (3-sigma)");
244 errorsTable->addColumn("double", "Right Error (3-sigma )");
245 auto quadraticErrColumn = errorsTable->addColumn("double", "Quadratic Error (1-sigma)");
246 errorsTable->setRowCount(freeParameters.size());
249 "The name of the TableWorkspace in which to store the "
250 "values and errors of fit parameters");
251 setPropertyValue("Errors", baseName + "_errors");
252 setProperty("Errors", errorsTable);
253
254 // Calculate initial values
255 double chiSquared = 0.0;
256 double chiSquaredWeighted = 0.0;
257 double dof = 0;
260 m_domainCreator->createDomain(domain, values);
261 CalculateChiSquared::calcChiSquared(*m_function, nParams, *domain, *values, chiSquared, chiSquaredWeighted, dof);
262 // Value of chi squared for current parameters in m_function
263 double chi0 = chiSquaredWeighted;
264
265 // Number of points in lines for plotting
266 size_t n = 100;
267 pdfTable->setRowCount(n);
268 const double fac = 1e-4;
269
270 for (auto p = 0u; p < freeParameters.size(); ++p) {
271 int row = p;
272 int ip = freeParameters[p];
273 // Add columns for the parameter to the pdf table.
274 auto parName = m_function->parameterName(ip);
275 nameColumn->read(row, parName);
276 // Parameter values
277 auto col1 = pdfTable->addColumn("double", parName);
278 col1->setPlotType(1);
279 // Chi squared values
280 auto col2 = pdfTable->addColumn("double", parName + "_chi2");
281 col2->setPlotType(2);
282 // PDF values
283 auto col3 = pdfTable->addColumn("double", parName + "_pdf");
284 col3->setPlotType(2);
285
286 double par0 = m_function->getParameter(ip);
287 double shift = fabs(par0 * fac);
288 if (shift == 0.0) {
289 shift = fac;
290 }
291
292 // Make a slice along this parameter
293 ChiSlice slice(m_function, ip, inputws, workspaceIndex, *domain, *values, chi0, freeParameters);
294
295 // Find the bounds withn which the PDF is significantly above zero.
296 // The bounds are defined relative to par0:
297 // par0 + lBound is the lowest value of the parameter (lBound <= 0)
298 // par0 + rBound is the highest value of the parameter (rBound >= 0)
299 double lBound = slice.findBound(-shift);
300 double rBound = slice.findBound(shift);
301
302 // Approximate the slice with a polynomial.
303 // P is a vector of values of the polynomial at special points.
304 // A is a vector of Chebyshev expansion coefficients.
305 // The polynomial is defined on interval [lBound, rBound]
306 // The value of the polynomial at 0 == chi squared at par0
307 std::vector<double> P, A;
308 auto base = slice.makeApprox(lBound, rBound, P, A);
309
310 // Write n slice points into the output table.
311 double dp = (rBound - lBound) / static_cast<double>(n);
312 for (size_t i = 0; i < n; ++i) {
313 double par = lBound + dp * static_cast<double>(i);
314 double chi = base->eval(par, P);
315 col1->fromDouble(i, par0 + par);
316 col2->fromDouble(i, chi);
317 }
318
319 // Check if par0 is a minimum point of the chi squared
320 std::vector<double> AD;
321 // Calculate the derivative polynomial.
322 // AD are the Chebyshev expansion of the derivative.
323 base->derivative(A, AD);
324 // Find the roots of the derivative polynomial
325 std::vector<double> minima = base->roots(AD);
326 if (minima.empty()) {
327 minima.emplace_back(par0);
328 }
329
330 // If only 1 extremum is found assume (without checking) that it's a
331 // minimum.
332 // If there are more than 1, find the one with the smallest chi^2.
333 double chiMin = std::numeric_limits<double>::max();
334 double parMin = par0;
335 for (double minimum : minima) {
336 double value = base->eval(minimum, P);
337 if (value < chiMin) {
338 chiMin = value;
339 parMin = minimum;
340 }
341 }
342 // Get intersection of curve and line of constant q value to get confidence
343 // interval on parameter ip
344 valueColumn->fromDouble(row, par0);
345 minValueColumn->fromDouble(row, par0 + parMin);
346 for (size_t i = 0; i < qvalues.size(); i++) {
347 auto [rootsMin, rootsMax] = getChiSquaredRoots(base, A, qvalues[i], rBound, lBound);
348 errorsTable->getColumn(3 + 2 * i)->fromDouble(row, rootsMin - parMin);
349 errorsTable->getColumn(4 + 2 * i)->fromDouble(row, rootsMax - parMin);
350 }
351
352 // Output the PDF
353 for (size_t i = 0; i < n; ++i) {
354 double chi = col2->toDouble(i);
355 col3->fromDouble(i, exp(-chi + chiMin));
356 }
357 // reset parameter values back to original value
358 m_function->setParameter(ip, par0);
359 }
360
361 // Square roots of the diagonals of the covariance matrix give
362 // the standard deviations in the quadratic approximation of the chi^2.
364 for (size_t i = 0; i < freeParameters.size(); ++i) {
365 int ip = freeParameters[i];
366 quadraticErrColumn->fromDouble(i, sqrt(V.get(ip, ip)));
367 }
368}
369
370EigenMatrix ProfileChiSquared1D::getCovarianceMatrix() {
373 auto nParams = m_function->nParams();
374 m_domainCreator->createDomain(domain, values);
376 EigenJacobian J(*m_function, values->size());
377 m_function->functionDeriv(*domain, J);
379 // Calculate the hessian at the current point.
380 EigenMatrix H;
381 H.resize(nParams, nParams);
382 for (size_t i = 0; i < nParams; ++i) {
383 for (size_t j = i; j < nParams; ++j) {
384 double h = 0.0;
385 for (size_t k = 0; k < values->size(); ++k) {
386 double w = values->getFitWeight(k);
387 h += J.get(k, i) * J.get(k, j) * w * w;
388 }
389 H.set(i, j, h);
390 if (i != j) {
391 H.set(j, i, h);
392 }
393 }
394 }
395 // Covariance matrix is inverse of hessian
396 EigenMatrix V(H);
397 V.invert();
398 return V;
399}
400
402void ProfileChiSquared1D::unfixParameters() {
403 for (size_t i = 0; i < m_function->nParams(); ++i) {
404 if (!m_function->isActive(i)) {
405 m_function->unfix(i);
406 m_fixedParameters.emplace_back(i);
407 }
408 }
409}
410
412void ProfileChiSquared1D::refixParameters() {
413 for (auto &fixedParameter : m_fixedParameters) {
414 m_function->fix(fixedParameter);
415 }
416 m_fixedParameters.clear();
417}
418
419std::tuple<double, double> ProfileChiSquared1D::getChiSquaredRoots(const Functions::ChebfunBase_sptr &approximation,
420 std::vector<double> &coeffs, double qvalue,
421 double rBound, double lBound) const {
422 // Points of intersections with line chi^2 = 1 give an estimate of
423 // the standard deviation of this parameter if it's uncorrelated with the
424 // others.
425 // Cache original value of A0
426 auto Aold = coeffs[0];
427 // Now find roots of curve when quantile is subtracted
428 coeffs[0] = Aold - qvalue;
429 std::vector<double> roots = approximation->roots(coeffs);
430 std::sort(roots.begin(), roots.end());
431 if (roots.empty()) {
432 // Something went wrong; use the whole interval.
433 roots.resize(2);
434 roots[0] = lBound;
435 roots[1] = rBound;
436 } else if (roots.size() == 1) {
437 // Only one root found; use a bound for the other root.
438 if (roots.front() < 0) {
439 roots.emplace_back(rBound);
440 } else {
441 roots.insert(roots.begin(), lBound);
442 }
443 } else if (roots.size() > 2) {
444 // More than 2 roots; use the smallest and the biggest
445 auto smallest = roots.front();
446 auto biggest = roots.back();
447 roots.resize(2);
448 roots[0] = smallest;
449 roots[1] = biggest;
450 }
451 coeffs[0] = Aold;
452 return {roots[0], roots[1]};
453}
454
455} // namespace Mantid::CurveFitting::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition: Algorithm.h:576
double value
The value of the point.
Definition: FitMW.cpp:51
#define fabs(x)
Definition: Matrix.cpp:22
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
Definition: Algorithm.cpp:1913
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
void setPropertyValue(const std::string &name, const std::string &value) override
Set the value of a property by string N.B.
Definition: Algorithm.cpp:1975
Base class that represents the domain of a function.
A class to store values calculated by a function.
This is an interface to a fitting function - a semi-abstarct class.
Definition: IFunction.h:163
A property class for workspaces.
static void calcChiSquared(const API::IFunction &fun, size_t nParams, const API::FunctionDomain &domain, API::FunctionValues &values, double &chiSquared, double &chiSquaredWeighted, double &dof)
Caclculate the chi squared, weighted chi squared and the number of degrees of freedom.
Helper class to calculate the chi squared along a direction in the parameter space.
Functions::ChebfunBase_sptr makeApprox(double lBound, double rBound, std::vector< double > &P, std::vector< double > &A)
Make an approximation for this slice on an interval.
API::FunctionValues & m_values
The values.
ChiSlice(IFunction_sptr inputFunction, int fixedParameterIndex, API::MatrixWorkspace_sptr inputWS, int workspaceIndex, const API::FunctionDomain &domain, API::FunctionValues &values, double chi0, std::vector< int > &freeParameters)
Constructor.
double findBound(double shift)
Fiind a displacement in the parameter space from the initial point to a point where the PDF drops sig...
const API::FunctionDomain & m_domain
The domain.
double m_chi0
The chi squared at the minimum.
double operator()(double p)
Calculate the value of chi squared along the chosen direction at a distance from the minimum point.
void unfixParameters()
Temporary unfix any fixed parameters.
std::tuple< double, double > getChiSquaredRoots(const Functions::ChebfunBase_sptr &approximation, std::vector< double > &coeffs, double qvalue, double rBound, double lBound) const
void refixParameters()
Restore the "fixed" status of previously unfixed paramters.
std::vector< size_t > m_fixedParameters
Cache indices of fixed parameters.
Two implementations of Jacobian.
Definition: EigenJacobian.h:23
double get(size_t iY, size_t iP) override
overwrite base method
Definition: EigenJacobian.h:74
A wrapper around Eigen::Matrix.
Definition: EigenMatrix.h:33
double get(size_t i, size_t j) const
Get an element.
void invert()
Invert this matrix.
static std::shared_ptr< ChebfunBase > bestFitAnyTolerance(double start, double end, FunctionType f, std::vector< double > &p, std::vector< double > &a, double maxA=0.0, double tolerance=0.0, size_t maxSize=0)
Find best fit with highest possible tolerance (to be used with noisy data).
Definition: ChebfunBase.h:178
A base class for fitting algorithms.
std::shared_ptr< API::IDomainCreator > m_domainCreator
Pointer to a domain creator.
std::shared_ptr< API::IFunction > m_function
Pointer to the fitting function.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
Manage the lifetime of a class intended to be a singleton.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
std::shared_ptr< FunctionValues > FunctionValues_sptr
typedef for a shared pointer
std::shared_ptr< IAlgorithm > IAlgorithm_sptr
shared pointer to Mantid::API::IAlgorithm
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
Definition: Workspace_fwd.h:20
std::shared_ptr< IFunction > IFunction_sptr
shared pointer to the function base class
Definition: IFunction.h:732
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::shared_ptr< ChebfunBase > ChebfunBase_sptr
Definition: ChebfunBase.h:174
std::unique_ptr< T > create(const P &parent, const IndexArg &indexArg, const HistArg &histArg)
This is the create() method that all the other create() methods call.
STL namespace.
@ Output
An output workspace.
Definition: Property.h:54