Mantid
Loading...
Searching...
No Matches
GramCharlierComptonProfile.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 +
7//------------------------------------------------------------------------------------------------
8// Includes
9//------------------------------------------------------------------------------------------------
12#include "MantidKernel/Spline.h"
13
14#include <gsl/gsl_errno.h>
15#include <gsl/gsl_sf_gamma.h> // for factorial
16
17#include <boost/math/special_functions/hermite.hpp>
18
19#include <cmath>
20#include <sstream>
21
23
24using namespace CurveFitting;
25// Register into factory
26DECLARE_FUNCTION(GramCharlierComptonProfile)
27
28namespace {
30const char *WIDTH_PARAM = "Width";
31const char *HERMITE_PREFIX = "C_";
32const char *KFSE_NAME = "FSECoeff";
33const char *HERMITE_C_NAME = "HermiteCoeffs";
34const int NFINE_Y = 1000;
35
43double trapzf(const std::vector<double> &xv, const std::vector<double> &yv) {
44 const double stepsize = xv[1] - xv[0];
45 const size_t endpoint = xv.size() - 1;
46 double area = std::accumulate(yv.cbegin() + 1, yv.cbegin() + endpoint, 0.0);
47 area = stepsize / 2 * (yv[0] + 2 * area + yv[endpoint]); // final step
48 return area;
49}
50
51// Cannot put these inside massProfile definition as you can't use local types
52// as template
53// arguments on some compilers
54
55// massProfile needs to sort 1 vector but keep another in order. Use a struct of
56// combined points
57// and a custom comparator
58struct Point {
59 Point(double yvalue = 0.0, double qvalue = 0.0) : y(yvalue), q(qvalue) {}
60 double y;
61 double q;
62};
63
64struct InY {
66 bool operator()(Point const &a, Point const &b) { return a.y < b.y; }
67};
69} // namespace
70
72 : ComptonProfile(), m_hermite(), m_yfine(), m_qfine(), m_voigt(), m_voigtProfile(), m_userFixedFSE(false) {}
73
77std::string GramCharlierComptonProfile::name() const { return "GramCharlierComptonProfile"; }
78
80 // Base class ones
82 declareParameter(WIDTH_PARAM, 1.0, "Gaussian width parameter");
83 declareParameter(KFSE_NAME, 1.0, "FSE coefficient k");
84 // Other parameters depend on the Hermite attribute...
85}
86
88 // Base class ones
90 declareAttribute(HERMITE_C_NAME,
91 IFunction::Attribute("")); // space-separated string 1/0
92 // indicating which coefficients
93 // are active
94}
95
101 if (name == HERMITE_C_NAME)
102 setHermiteCoefficients(value.asString());
104}
105
112 if (coeffs.empty()) {
113 throw std::invalid_argument("GramCharlierComptonProfile - Hermite polynomial string is empty!");
114 }
115 m_hermite.clear();
116 m_hermite.reserve(3); // Maximum guess
117 std::istringstream is(coeffs);
118 while (!is.eof()) {
119 short value;
120 is >> value;
121 if (!is) {
122 throw std::invalid_argument("NCSCountRate - Error reading int from hermite coefficient string: " + coeffs);
123 }
124 m_hermite.emplace_back(value);
125 }
127}
128
135 // Gram-Chelier parameters are the even coefficents of the Hermite
136 // polynomials, i.e
137 // setting hermite coefficients to "1 0 1" uses coefficents C_0,C_4 and C_2 is
138 // skipped.
139 for (size_t i = 0; i < m_hermite.size(); ++i) {
140 if (m_hermite[i] > 0) {
141 std::ostringstream os;
142 os << HERMITE_PREFIX << 2 * i;
143 this->declareParameter(os.str(), 1.0, "Hermite polynomial coefficent");
144 }
145 }
146}
147
149 assert(!m_hermite.empty());
150
151 std::vector<size_t> indices;
152 indices.reserve(m_hermite.size() + 1);
153 for (size_t i = 0; i < m_hermite.size(); ++i) {
154 if (m_hermite[i] > 0) {
155 std::ostringstream os;
156 os << HERMITE_PREFIX << 2 * i; // refactor to have method that produces the name
157 indices.emplace_back(this->parameterIndex(os.str()));
158 }
159 }
160 // Include Kfse if it is not fixed
161 const size_t kIndex = this->parameterIndex(KFSE_NAME);
162 if (isActive(kIndex)) {
163 indices.emplace_back(kIndex);
164 }
165
166 return indices;
167}
168
179 const HistogramData::HistogramE &errors) const {
180 std::vector<double> profile(NFINE_Y, 0.0);
181 const size_t nData(ySpace().size());
182 std::vector<double> result(nData, 0.0);
183
184 // If the FSE term is fixed by user then it's contribution is convoluted with
185 // Voigt and summed with the first column
186 // otherwise it gets a column of it's own at the end.
187 // Either way it needs to be computed, so do this first
188
189 std::vector<double> fse(NFINE_Y, 0.0);
190 std::vector<double> convolvedFSE(nData, 0.0);
191 addFSETerm(fse);
192 convoluteVoigt(convolvedFSE.data(), nData, fse);
193
194 size_t col(0);
195 for (unsigned int i = 0; i < m_hermite.size(); ++i) {
196 if (m_hermite[i] == 0)
197 continue;
198
199 const unsigned int npoly = 2 * i;
200 addMassProfile(profile.data(), npoly);
201 convoluteVoigt(result.data(), nData, profile);
202 if (i == 0 && m_userFixedFSE) {
203 std::transform(result.begin(), result.end(), convolvedFSE.begin(), result.begin(), std::plus<double>());
204 }
205 std::transform(result.begin(), result.end(), errors.begin(), result.begin(), std::divides<double>());
206 cmatrix.setColumn(start + col, result);
207
208 std::fill_n(profile.begin(), NFINE_Y, 0.0);
209 std::fill_n(result.begin(), nData, 0.0);
210 ++col;
211 }
212
213 if (!m_userFixedFSE) // Extra column for He3
214 {
215 std::transform(convolvedFSE.begin(), convolvedFSE.end(), errors.begin(), convolvedFSE.begin(),
216 std::divides<double>());
217 cmatrix.setColumn(start + col, convolvedFSE);
218 ++col;
219 }
220 return col;
221}
222
231void GramCharlierComptonProfile::massProfile(double *result, const size_t nData) const {
232 UNUSED_ARG(nData);
233
234 using namespace Mantid::Kernel;
235
236 // Hermite expansion (only even terms) + FSE term
237 const size_t nhermite(m_hermite.size());
238
239 // Sum over polynomials for each y
240 std::vector<double> sumH(NFINE_Y);
241 for (unsigned int i = 0; i < nhermite; ++i) {
242 if (m_hermite[i] == 0)
243 continue;
244 const unsigned int npoly = 2 * i; // Only even ones
245 addMassProfile(sumH.data(), npoly);
246 }
247 addFSETerm(sumH);
248 convoluteVoigt(result, nData, sumH);
249}
250
260void GramCharlierComptonProfile::addMassProfile(double *result, const unsigned int npoly) const {
261
262 using namespace Mantid::Kernel;
263
264 const double amp(1.0), wg(getParameter(WIDTH_PARAM));
265 const double ampNorm = amp / (std::sqrt(2.0 * M_PI) * wg);
266
267 std::ostringstream os;
268 os << HERMITE_PREFIX << npoly;
269 const double hermiteCoeff = getParameter(os.str());
270 const double factorial = gsl_sf_fact(npoly / 2);
271 // Intel compiler doesn't overload pow for unsigned types
272 const double denom = std::pow(2.0, static_cast<int>(npoly)) * factorial;
273
274 for (int j = 0; j < NFINE_Y; ++j) {
275 const double y = m_yfine[j] / M_SQRT2 / wg;
276 const double hermiteI = boost::math::hermite(npoly, y);
277 result[j] += ampNorm * std::exp(-y * y) * hermiteI * hermiteCoeff / denom;
278 }
279}
280
285void GramCharlierComptonProfile::addFSETerm(std::vector<double> &lhs) const {
286 assert(static_cast<size_t>(NFINE_Y) == lhs.size());
287
288 using namespace Mantid::Kernel;
289
290 const double amp(1.0), wg(getParameter(WIDTH_PARAM));
291 const double ampNorm = amp / (std::sqrt(2.0 * M_PI) * wg);
292
293 double kfse(getParameter(KFSE_NAME));
294 if (m_userFixedFSE)
295 kfse *= getParameter("C_0");
296
297 if (m_yfine.empty() || m_qfine.empty()) {
298 throw std::runtime_error("The Y values or Q values have not been set");
299 }
300
301 for (int j = 0; j < NFINE_Y; ++j) {
302 const double y = m_yfine[j] / M_SQRT2 / wg;
303 const double he3 = boost::math::hermite(3, y);
304 lhs[j] += ampNorm * std::exp(-y * y) * he3 * (kfse / m_qfine[j]);
305 }
306}
307
314void GramCharlierComptonProfile::convoluteVoigt(double *result, const size_t nData,
315 const std::vector<double> &profile) const {
316 const auto &modq = modQ();
317 const auto &ei = e0();
318
319 // Now convolute with the Voigt function (pre-calculated in setWorkspace as
320 // its expensive)
321 for (size_t i = 0; i < nData; ++i) {
322 const std::vector<double> &voigt = m_voigt[i];
323 // Multiply voigt with polynomial sum and put result in voigt to save using
324 // another vector
325 std::transform(voigt.begin(), voigt.end(), profile.begin(), m_voigtProfile.begin(), std::multiplies<double>());
326 const double prefactor = std::pow(ei[i], 0.1) * mass() / modq[i];
327 result[i] = prefactor * trapzf(m_yfine, m_voigtProfile);
328 }
329}
330
338void GramCharlierComptonProfile::setMatrixWorkspace(std::shared_ptr<const API::MatrixWorkspace> workspace, size_t wi,
339 double startX, double endX) {
341 endX); // Do base-class calculation first
342}
343
348void GramCharlierComptonProfile::cacheYSpaceValues(const HistogramData::Points &tseconds,
349 const Algorithms::DetectorParams &detpar) {
351 detpar); // base-class calculations
352
353 // Is FSE fixed at the moment?
354 // The ComptonScatteringCountRate fixes it but we still need to know if the
355 // user wanted it fixed
356 m_userFixedFSE = !this->isActive(this->parameterIndex(KFSE_NAME));
357
358 const auto &yspace = ySpace();
359 const auto &modq = modQ();
360
361 // massProfile is calculated over a large range of Y, constructed by
362 // interpolation
363 // This is done over an interpolated range between ymin & ymax and y and hence
364 // q must be sorted
365 const size_t ncoarseY(yspace.size());
366 std::vector<Point> points(ncoarseY);
367 for (size_t i = 0; i < ncoarseY; ++i) {
368 points[i] = Point(yspace[i], modq[i]);
369 }
370 std::sort(points.begin(), points.end(), InY());
371 // Separate back into vectors as GSL requires them separate
372 std::vector<double> sortedy(ncoarseY), sortedq(ncoarseY);
373 for (size_t i = 0; i < ncoarseY; ++i) {
374 const auto &p = points[i];
375 sortedy[i] = p.y;
376 sortedq[i] = p.q;
377 }
378
379 // Generate a more-finely grained y axis and interpolate Q values
380 m_yfine.resize(NFINE_Y);
381 m_qfine.resize(NFINE_Y);
382 const double miny(sortedy.front()), maxy(sortedy.back());
383 const double step = (maxy - miny) / static_cast<double>((NFINE_Y - 1));
384 for (int i = 0; i < NFINE_Y - 1; ++i) {
385 const double xi = miny + step * i;
386 m_yfine[i] = xi;
387 }
388 // Final value to ensure it ends at maxy
389 m_yfine.back() = maxy;
390 // Interpolate q values at fine y grid using linear spline
392
393 // Cache voigt function over yfine
394 std::vector<double> minusYFine(NFINE_Y);
395 using std::placeholders::_1;
396 std::transform(m_yfine.begin(), m_yfine.end(), minusYFine.begin(), std::bind(std::multiplies<double>(), _1, -1.0));
397 std::vector<double> ym(NFINE_Y); // Holds result of (y[i] - yfine) for each original y
398 m_voigt.resize(ncoarseY);
399
400 for (size_t i = 0; i < ncoarseY; ++i) {
401 std::vector<double> &voigt = m_voigt[i];
402 voigt.resize(NFINE_Y);
403
404 const double yi = yspace[i];
405 std::transform(minusYFine.begin(), minusYFine.end(), ym.begin(),
406 std::bind(std::plus<double>(), _1, yi)); // yfine is actually -yfine
407 m_resolutionFunction->voigtApprox(voigt, ym, 0, 1.0);
408 }
409
410 m_voigtProfile.resize(NFINE_Y); // Value holder for later to avoid repeated
411 // memory allocations when creating a new
412 // vector
413}
414
415} // namespace Mantid::CurveFitting::Functions
std::string name
Definition Run.cpp:60
double value
The value of the point.
Definition FitMW.cpp:51
#define DECLARE_FUNCTION(classname)
Macro for declaring a new type of function to be used with the FunctionFactory.
IPeaksWorkspace_sptr workspace
#define UNUSED_ARG(x)
Function arguments are sometimes unused in certain implmentations but are required for documentation ...
Definition System.h:48
Attribute is a non-fitting parameter.
Definition IFunction.h:285
bool isActive(size_t i) const
Check if an active parameter i is actually active.
void declareAttribute(const std::string &name, const API::IFunction::Attribute &defaultValue)
Declare a single attribute.
virtual void declareAttributes()
Override to declare function attributes.
Definition IFunction.h:681
virtual void setAttribute(const std::string &name, const Attribute &)
Set a value to attribute attName.
size_t parameterIndex(const std::string &name) const override
Returns the index of parameter name.
void declareParameter(const std::string &name, double initValue=0, const std::string &description="") override
Declare a new parameter.
double getParameter(size_t i) const override
Get i-th parameter.
This class serves as a base-class for ComptonProfile type functions.
const std::vector< double > & modQ() const
Access Q values cache.
void declareParameters() override
Declare parameters that will never participate in the fit.
std::shared_ptr< VesuvioResolution > m_resolutionFunction
Vesuvio resolution function.
void setMatrixWorkspace(std::shared_ptr< const API::MatrixWorkspace > workspace, size_t wsIndex, double startX, double endX) override
Cache a copy of the workspace pointer and pull out the parameters.
const std::vector< double > & ySpace() const
Access y-values cache.
const std::vector< double > & e0() const
Access e0 values.
void cacheYSpaceValues(const HistogramData::Points &tseconds, const Algorithms::DetectorParams &detpar, const ResolutionParams &respar)
Pre-calculate the Y-space values with specified resolution parameters.
void declareParameters() override
Declare the function parameters.
std::string name() const override
A string identifier for this function.
void addMassProfile(double *result, const unsigned int npoly) const
Compute the contribution to mass profile nth Hermite polynomial coefficient.
std::vector< double > m_qfine
Interpolated Q values over a finer Y range.
void declareAttributes() override
Declare non-parameter attributes.
std::vector< double > m_yfine
Y values over a finer range.
void massProfile(double *result, const size_t nData) const override
Compute the sum for all Hermite polynomial coefficents.
void declareGramCharlierParameters()
Declare the Gram-Charlier (Hermite) coefficients.
std::vector< double > m_voigtProfile
Holds the result Voigt multiplied by the profile function for the extended Y space range.
void setHermiteCoefficients(const std::string &coeffs)
Parse the active hermite polynomial coefficents.
bool m_userFixedFSE
Flag to hold whether the FSE parameter is fixed by the user.
void setAttribute(const std::string &name, const Attribute &value) override
Set an attribute value (and possibly cache its value)
void addFSETerm(std::vector< double > &lhs) const
Add FSE term based on current parameter setting.
std::vector< short > m_hermite
The active hermite coefficents.
std::vector< std::vector< double > > m_voigt
Holds the value of the Voigt function for each coarse y-space point as this is an expensive calculati...
void convoluteVoigt(double *result, const size_t nData, const std::vector< double > &profile) const
Convolute with resolution.
void setMatrixWorkspace(std::shared_ptr< const API::MatrixWorkspace > workspace, size_t wi, double startX, double endX) override
Called by the framework when a workspace is set.
size_t fillConstraintMatrix(Kernel::DblMatrix &cmatrix, const size_t start, const HistogramData::HistogramE &errors) const override
Fill in the columns of the matrix for this mass.
std::vector< size_t > intensityParameterIndices() const override
Returns the indices of the intensity parameters.
void cacheYSpaceValues(const HistogramData::Points &tseconds, const Algorithms::DetectorParams &detpar) override
Pre-calculate the Y-space values.
static std::vector< Y > getSplinedYValues(std::span< X const > newX, std::span< X const > x, std::span< Y const > y)
Definition Spline.h:98
void setColumn(const size_t nCol, const std::vector< T > &newCol)
Definition Matrix.cpp:675
Simple data structure to store nominal detector values It avoids some functions taking a huge number ...