Mantid
Loading...
Searching...
No Matches
CostFuncPoisson.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2019 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 +
7//----------------------------------------------------------------------
8// Includes
9//----------------------------------------------------------------------
16#include "MantidKernel/Logger.h"
18
19#include <cmath>
20#include <limits>
21
22using namespace Mantid::API;
23
24namespace {
25// predicted < 0 is forbidden as it causes inf cost
26constexpr double absoluteCutOff = 0.0;
27constexpr double effectiveCutOff = 0.0001;
28constexpr double epsilon = 1e-10;
29
30double calculatePoissonLoss(double observedCounts, double predicted) {
31 double retVal = (predicted - observedCounts);
32 retVal += observedCounts * (log(observedCounts) - log(predicted));
33 return retVal;
34}
35
36} // namespace
37
39
40namespace PoissonLossLM {
41template MANTID_CURVEFITTING_DLL int sgn<double>(double val);
42
43double calculatePoissonResidualLM(double observedCounts, double predicted) {
44 observedCounts = std::max(absoluteCutOff, observedCounts);
45 double retVal = (predicted - observedCounts);
46 if (predicted <= absoluteCutOff) {
47 return std::numeric_limits<double>::max();
48 }
49 // at observed = 0 the Poisson function reduces to predicted - observed
50 if (observedCounts != 0) {
51 if (const auto x = retVal / observedCounts; std::abs(x) < effectiveCutOff) {
52 // taylor around small
53 return observedCounts * (x * x) * (0.5 - (x / 3.0) + (x * x) / 4.0);
54 }
55
56 retVal += observedCounts * (log(observedCounts) - log(predicted));
57 }
58 return retVal;
59}
60double calculateJacobianScaleFactor(double observedCounts, double predicted) {
61 if (predicted <= absoluteCutOff) {
62 return std::numeric_limits<double>::max();
63 }
64 const double delta = predicted - observedCounts;
65 const double signDelta = sgn(delta);
66 // If observed is zero
67 if (observedCounts == absoluteCutOff) {
68 return predicted > epsilon ? signDelta / std::sqrt(2.0 * predicted) : signDelta / epsilon;
69 }
70 // taylor
71 if (const auto x = delta / observedCounts; std::abs(x) < effectiveCutOff) {
72 return (1.0 / std::sqrt(observedCounts)) * (1.0 - (2.0 / 3.0) * x + (7.0 / 12.0) * (x * x));
73 }
74
75 return signDelta * (1 - observedCounts / predicted) * 1 /
76 std::sqrt(2 * calculatePoissonResidualLM(observedCounts, predicted));
77}
78
79// calculate loss for the LM minimizer as needed for gsl minimizer, taking the root of the residual.
80double calculatePoissonLossLM(double observedCounts, double predicted) {
81 return sgn(predicted - observedCounts) * std::sqrt(2 * calculatePoissonResidualLM(observedCounts, predicted));
82}
83
84} // namespace PoissonLossLM
85
86DECLARE_COSTFUNCTION(CostFuncPoisson, Poisson)
87//----------------------------------------------------------------------------------------------
91
99 m_function->function(*domain, *values);
100 size_t ny = values->size();
101
102 double retVal = 0.0;
103
104 for (size_t i = 0; i < ny; i++) {
105 const double predicted = values->getCalculated(i);
106
107 if (predicted <= absoluteCutOff) {
108 retVal = std::numeric_limits<double>::infinity();
109 break;
110 }
111
112 const double observed = values->getFitData(i);
113 if (predicted <= effectiveCutOff) {
114 retVal += (effectiveCutOff - predicted) / predicted;
115 } else if (observed == 0.0) {
116 // at observed = 0 the Poisson function reduces to simply adding predicted
117 retVal += predicted;
118 } else {
119 retVal += calculatePoissonLoss(observed, predicted);
120 }
121 }
122
124 m_value += 2.0 * retVal;
125}
126
137 API::FunctionValues_sptr values, bool evalDeriv, bool evalHessian) const {
138 const size_t numParams = nParams();
139
140 if (evalDeriv) {
141 m_der.resize(numParams);
142 m_der.zero();
143 calculateDerivative(*function, *domain, *values);
144 }
145
146 if (evalHessian) {
147 m_hessian.resize(numParams, numParams);
148 m_hessian.zero();
149 calculateHessian(*function, *domain, *values);
150 }
151}
152
154 FunctionValues &values) const {
155 const size_t numParams = function.nParams();
156 const size_t numDataPoints = domain.size();
157
158 Jacobian jacobian(numDataPoints, numParams);
159 function.function(domain, values);
160 function.functionDeriv(domain, jacobian);
161
162 size_t activeParamIndex = 0;
163 double costVal = 0.0;
164
165 for (size_t paramIndex = 0; paramIndex < numParams; ++paramIndex) {
166 if (!function.isActive(paramIndex))
167 continue;
168
169 double determinant = 0.0;
170 for (size_t i = 0; i < numDataPoints; ++i) {
171 double calc = values.getCalculated(i);
172 double obs = values.getFitData(i);
173
174 if (calc <= absoluteCutOff) {
175 if (activeParamIndex == 0) {
176 costVal += std::numeric_limits<double>::infinity();
177 }
178 determinant += std::numeric_limits<double>::infinity();
179 continue;
180 }
181
182 if (calc <= effectiveCutOff) {
183 if (activeParamIndex == 0) {
184 costVal += (effectiveCutOff - calc) / (calc - absoluteCutOff);
185 }
186 double tmp = calc - absoluteCutOff;
187 determinant += jacobian.get(i, paramIndex) * (absoluteCutOff - effectiveCutOff) / (tmp * tmp);
188
189 } else if (obs == 0.0) {
190 if (activeParamIndex == 0) {
191 costVal += calc;
192 }
193 determinant += jacobian.get(i, paramIndex);
194
195 } else {
196 if (activeParamIndex == 0) {
197 costVal += calculatePoissonLoss(obs, calc);
198 }
199 determinant += jacobian.get(i, paramIndex) * (1.0 - obs / calc);
200 }
201 }
202 PARALLEL_CRITICAL(der_set) {
203 double der = m_der.get(activeParamIndex);
204 m_der.set(activeParamIndex, der + determinant);
205 }
206 ++activeParamIndex;
207 }
208
210 m_value += 2.0 * costVal;
211}
212
214 const API::FunctionValues &values) const {
215 size_t numParams = function.nParams(); // number of parameters
216 size_t numDataPoints = domain.size(); // number of data points
217
218 Jacobian jacobian(numDataPoints, numParams);
219 function.functionDeriv(domain, jacobian);
220
221 size_t activeParamFirstIndex = 0; // The params are split into two halves and iterated through
222 for (size_t paramIndex = 0; paramIndex < numParams; ++paramIndex) {
223
224 if (!function.isActive(paramIndex))
225 continue;
226 size_t activeParamSecondIndex = 0; // The counterpart index
227 double parameter = function.getParameter(paramIndex);
228
229 double scalingFactor = 1e-4;
230 if (parameter != 0.0) {
231 scalingFactor *= parameter;
232 }
233
234 function.setParameter(paramIndex, parameter + scalingFactor);
235 Jacobian jacobian2(numDataPoints, numParams);
236 function.functionDeriv(domain, jacobian2);
237 function.setParameter(paramIndex, parameter);
238
239 for (size_t j = 0; j <= paramIndex; ++j) // over ~ half of parameters
240 {
241 if (!function.isActive(j))
242 continue;
243 double d = 0.0;
244 for (size_t k = 0; k < numDataPoints; ++k) // over fitting data
245 {
246 double d2 = (jacobian2.get(k, j) - jacobian.get(k, j)) / scalingFactor;
247
248 double calc = values.getCalculated(k);
249 double obs = values.getFitData(k);
250 if (calc <= absoluteCutOff) {
251 d += std::numeric_limits<double>::infinity();
252 } else {
253 if (calc <= effectiveCutOff) {
254 double constrainedCalc = calc - absoluteCutOff;
255 d += d2 * (absoluteCutOff - effectiveCutOff) / (constrainedCalc * constrainedCalc);
256 d += jacobian.get(k, paramIndex) * jacobian.get(k, j) * (effectiveCutOff - absoluteCutOff) * 2 /
257 (constrainedCalc * constrainedCalc * constrainedCalc);
258 } else if (obs == 0.0) {
259 d += d2;
260 } else {
261 d += d2 * (1.0 - obs / calc);
262 d += jacobian.get(k, paramIndex) * jacobian.get(k, j) * obs / (calc * calc);
263 }
264 }
265 }
266 PARALLEL_CRITICAL(hessian_set) {
267 double h = m_hessian.get(activeParamFirstIndex, activeParamSecondIndex) + d;
268 m_hessian.set(activeParamFirstIndex, activeParamSecondIndex, h);
269 if (activeParamFirstIndex != activeParamSecondIndex) {
270 m_hessian.set(activeParamSecondIndex, activeParamFirstIndex, h);
271 }
272 }
273 ++activeParamSecondIndex;
274 }
275 ++activeParamFirstIndex;
276 }
277}
278
279} // namespace Mantid::CurveFitting::CostFunctions
gsl_vector * tmp
#define DECLARE_COSTFUNCTION(classname, username)
Macro for declaring a new type of cost functions to be used with the CostFunctionFactory.
#define PARALLEL_CRITICAL(name)
#define PARALLEL_ATOMIC
Base class that represents the domain of a function.
virtual size_t size() const =0
Return the number of points in the domain.
A class to store values calculated by a function.
double getFitData(size_t i) const
Get a fitting data value.
double getCalculated(size_t i) const
Get i-th calculated value.
This is an interface to a fitting function - a semi-abstarct class.
Definition IFunction.h:166
virtual void functionDeriv(const FunctionDomain &domain, Jacobian &jacobian)
Derivatives of function with respect to active parameters.
virtual size_t nParams() const =0
Total number of parameters.
bool isActive(size_t i) const
Check if an active parameter i is actually active.
virtual double getParameter(size_t i) const =0
Get i-th parameter.
virtual void setParameter(size_t, const double &value, bool explicitlySet=true)=0
Set i-th parameter.
virtual void function(const FunctionDomain &domain, FunctionValues &values) const =0
Evaluates the function for all arguments in the domain.
Represents the Jacobian in IFitFunction::functionDeriv.
Definition Jacobian.h:22
virtual double get(size_t iY, size_t iP)=0
Get the value to a Jacobian matrix element.
A semi-abstract class for a cost function for fitting functions.
size_t nParams() const override
Number of parameters.
API::IFunction_sptr m_function
Shared pointer to the fitting function.
CostFuncPoisson : Implements a cost function for fitting applications using a Poisson measure.
void calculateHessian(API::IFunction &function, const API::FunctionDomain &domain, const API::FunctionValues &values) const
Calculates the Hessian matrix for the addValDerivHessian method.
void addValDerivHessian(API::IFunction_sptr function, API::FunctionDomain_sptr domain, API::FunctionValues_sptr values, bool evalDeriv=true, bool evalHessian=true) const override
Update the cost function, derivatives and hessian by adding values calculated on a domain.
void addVal(API::FunctionDomain_sptr domain, API::FunctionValues_sptr values) const override
Add a contribution to the cost function value from the fitting function evaluated on a particular dom...
void calculateDerivative(API::IFunction &function, const API::FunctionDomain &domain, API::FunctionValues &values) const
Calculates the derivative for the addValDerivHessian method.
void set(size_t i, size_t j, double value)
Set an element.
double get(size_t i, size_t j) const
Get an element.
void zero()
Set all elements to zero.
void resize(const size_t nx, const size_t ny)
Resize the matrix.
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.
std::shared_ptr< FunctionValues > FunctionValues_sptr
typedef for a shared pointer
std::shared_ptr< IFunction > IFunction_sptr
shared pointer to the function base class
Definition IFunction.h:748
std::shared_ptr< FunctionDomain > FunctionDomain_sptr
typedef for a shared pointer
template MANTID_CURVEFITTING_DLL int sgn< double >(double val)
double calculatePoissonResidualLM(double observedCounts, double predicted)
double MANTID_CURVEFITTING_DLL calculateJacobianScaleFactor(double observedCounts, double predicted)
double MANTID_CURVEFITTING_DLL calculatePoissonLossLM(double observedCounts, double predicted)