BornAgain  1.18.0
Simulate and fit neutron and x-ray scattering at grazing incidence
Distributions.cpp
Go to the documentation of this file.
1 // ************************************************************************** //
2 //
3 // BornAgain: simulate and fit scattering at grazing incidence
4 //
5 //! @file Param/Distrib/Distributions.cpp
6 //! @brief Implements classes representing one-dimensional distributions.
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2018
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************** //
14 
17 #include "Base/Types/Exceptions.h"
21 #include <algorithm>
22 #include <cmath>
23 #include <limits>
24 #include <sstream>
25 
26 namespace
27 {
28 bool DoubleEqual(double a, double b);
29 }
30 
31 // ************************************************************************** //
32 // class IDistribution1D
33 // ************************************************************************** //
34 
35 IDistribution1D::IDistribution1D(const NodeMeta& meta, const std::vector<double>& PValues)
36  : INode(meta, PValues)
37 {
38 }
39 
40 //! Returns equidistant samples, using intrinsic parameters, weighted with probabilityDensity().
41 
42 std::vector<ParameterSample> IDistribution1D::equidistantSamples(size_t nbr_samples,
43  double sigma_factor,
44  const RealLimits& limits) const
45 {
46  if (nbr_samples == 0)
48  "IDistribution1D::generateSamples: "
49  "number of generated samples must be bigger than zero");
50  if (isDelta())
51  return {ParameterSample(getMean())};
52  return generateSamplesFromValues(equidistantPoints(nbr_samples, sigma_factor, limits));
53 }
54 
55 //! Returns equidistant samples from xmin to xmax, weighted with probabilityDensity().
56 
57 std::vector<ParameterSample>
58 IDistribution1D::equidistantSamplesInRange(size_t nbr_samples, double xmin, double xmax) const
59 {
60  if (nbr_samples == 0)
62  "IDistribution1D::generateSamples: "
63  "number of generated samples must be bigger than zero");
64  if (isDelta())
65  return {ParameterSample(getMean())};
66  return generateSamplesFromValues(equidistantPointsInRange(nbr_samples, xmin, xmax));
67 }
68 
69 //! Returns equidistant interpolation points from xmin to xmax.
70 
71 std::vector<double> IDistribution1D::equidistantPointsInRange(size_t nbr_samples, double xmin,
72  double xmax) const
73 {
74  if (nbr_samples < 2 || DoubleEqual(xmin, xmax))
75  return {getMean()};
76  std::vector<double> result(nbr_samples);
77  for (size_t i = 0; i < nbr_samples; ++i)
78  result[i] = xmin + i * (xmax - xmin) / (nbr_samples - 1.0);
79  return result;
80 }
81 
82 void IDistribution1D::setUnits(const std::string& units)
83 {
84  for (auto* par : parameterPool()->parameters())
85  par->setUnit(units);
86 }
87 
88 void IDistribution1D::adjustMinMaxForLimits(double& xmin, double& xmax,
89  const RealLimits& limits) const
90 {
91  if (limits.hasLowerLimit() && xmin < limits.lowerLimit())
92  xmin = limits.lowerLimit();
93  if (limits.hasUpperLimit() && xmax > limits.upperLimit())
94  xmax = limits.upperLimit();
95  if (xmin > xmax) {
96  std::ostringstream ostr;
97  ostr << "IDistribution1D::adjustMinMaxForLimits() -> Error. Can't' adjust ";
98  ostr << "xmin:" << xmin << " xmax:" << xmax << " for given limits " << limits << std::endl;
99  throw Exceptions::DomainErrorException(ostr.str());
100  }
101 }
102 
103 //! Returns weighted samples from given interpolation points and probabilityDensity().
104 
105 std::vector<ParameterSample>
106 IDistribution1D::generateSamplesFromValues(const std::vector<double>& sample_values) const
107 {
108  std::vector<ParameterSample> result;
109  double norm_factor = 0.0;
110  for (double value : sample_values) {
111  double pdf = probabilityDensity(value);
112  result.push_back(ParameterSample(value, pdf));
113  norm_factor += pdf;
114  }
115  if (norm_factor <= 0.0)
116  throw Exceptions::RuntimeErrorException("IDistribution1D::generateSamples: "
117  "total probability must be bigger than zero");
118  for (ParameterSample& sample : result)
119  sample.weight /= norm_factor;
120  return result;
121 }
122 
123 // ************************************************************************** //
124 // class DistributionGate
125 // ************************************************************************** //
126 
127 DistributionGate::DistributionGate(const std::vector<double> P)
128  : IDistribution1D(
129  {"DistributionGate",
130  "class_tooltip",
131  {{"Min", "", "para_tooltip", -INF, +INF, 0}, {"Max", "", "para_tooltip", -INF, +INF, 0}}},
132  P),
133  m_min(m_P[0]), m_max(m_P[1])
134 {
135  if (m_max < m_min)
136  throw Exceptions::ClassInitializationException("DistributionGate: max<min");
137 }
138 
139 DistributionGate::DistributionGate(double min, double max)
140  : DistributionGate(std::vector<double>{min, max})
141 {
142 }
143 
145 
147 {
148  if (x < m_min || x > m_max)
149  return 0.0;
150  if (DoubleEqual(m_min, m_max))
151  return 1.0;
152  return 1.0 / (m_max - m_min);
153 }
154 
155 std::vector<double> DistributionGate::equidistantPoints(size_t nbr_samples, double /*sigma_factor*/,
156  const RealLimits& limits) const
157 {
158  double xmin = m_min;
159  double xmax = m_max;
160  adjustMinMaxForLimits(xmin, xmax, limits);
161  return equidistantPointsInRange(nbr_samples, xmin, xmax);
162 }
163 
165 {
166  return DoubleEqual(m_min, m_max);
167 }
168 
169 // ************************************************************************** //
170 // class DistributionLorentz
171 // ************************************************************************** //
172 
173 DistributionLorentz::DistributionLorentz(const std::vector<double> P)
174  : IDistribution1D({"DistributionLorentz",
175  "class_tooltip",
176  {{"Mean", "", "para_tooltip", -INF, +INF, 0},
177  {"HWHM", "", "para_tooltip", -INF, +INF, 0}}},
178  P),
179  m_mean(m_P[0]), m_hwhm(m_P[1])
180 {
181  if (m_hwhm < 0.0)
182  throw Exceptions::ClassInitializationException("DistributionLorentz: hwhm<0");
183 }
184 
186  : DistributionLorentz(std::vector<double>{mean, hwhm})
187 {
188 }
189 
191 
193 {
194  if (m_hwhm == 0.0)
195  return DoubleEqual(x, m_mean) ? 1.0 : 0.0;
196  return m_hwhm / (m_hwhm * m_hwhm + (x - m_mean) * (x - m_mean)) / M_PI;
197 }
198 
199 std::vector<double> DistributionLorentz::equidistantPoints(size_t nbr_samples, double sigma_factor,
200  const RealLimits& limits) const
201 {
202  if (sigma_factor <= 0.0)
203  sigma_factor = 2.0;
204  double xmin = m_mean - sigma_factor * m_hwhm;
205  double xmax = m_mean + sigma_factor * m_hwhm;
206  adjustMinMaxForLimits(xmin, xmax, limits);
207  return equidistantPointsInRange(nbr_samples, xmin, xmax);
208 }
209 
211 {
212  return m_hwhm == 0.0;
213 }
214 
215 // ************************************************************************** //
216 // class DistributionGaussian
217 // ************************************************************************** //
218 
219 DistributionGaussian::DistributionGaussian(const std::vector<double> P)
220  : IDistribution1D({"DistributionGaussian",
221  "class_tooltip",
222  {{"Mean", "", "para_tooltip", -INF, +INF, 0},
223  {"StdDev", "", "para_tooltip", -INF, +INF, 0}}},
224  P),
225  m_mean(m_P[0]), m_std_dev(m_P[1])
226 {
227  if (m_std_dev < 0.0)
228  throw Exceptions::ClassInitializationException("DistributionGaussian: std_dev < 0");
229 }
230 
231 DistributionGaussian::DistributionGaussian(double mean, double std_dev)
232  : DistributionGaussian(std::vector<double>{mean, std_dev})
233 {
234 }
235 
237 
239 {
240  if (m_std_dev == 0.0)
241  return DoubleEqual(x, m_mean) ? 1.0 : 0.0;
242  double exponential = std::exp(-(x - m_mean) * (x - m_mean) / (2.0 * m_std_dev * m_std_dev));
243  return exponential / m_std_dev / std::sqrt(M_TWOPI);
244 }
245 
246 std::vector<double> DistributionGaussian::equidistantPoints(size_t nbr_samples, double sigma_factor,
247  const RealLimits& limits) const
248 {
249  if (sigma_factor <= 0.0)
250  sigma_factor = 2.0;
251  double xmin = m_mean - sigma_factor * m_std_dev;
252  double xmax = m_mean + sigma_factor * m_std_dev;
253  adjustMinMaxForLimits(xmin, xmax, limits);
254  return equidistantPointsInRange(nbr_samples, xmin, xmax);
255 }
256 
258 {
259  return m_std_dev == 0.0;
260 }
261 
262 // ************************************************************************** //
263 // class DistributionLogNormal
264 // ************************************************************************** //
265 
267  : IDistribution1D({"DistributionLogNormal",
268  "class_tooltip",
269  {{"Median", "", "para_tooltip", -INF, +INF, 0},
270  {"ScaleParameter", "", "para_tooltip", -INF, +INF, 0}}},
271  P),
272  m_median(m_P[0]), m_scale_param(m_P[1])
273 {
274  if (m_scale_param < 0.0)
275  throw Exceptions::ClassInitializationException("DistributionLogNormal: scale_param < 0");
276  if (m_median <= 0.0)
277  throw Exceptions::ClassInitializationException("DistributionLogNormal: median < 0");
278 }
279 
280 DistributionLogNormal::DistributionLogNormal(double median, double scale_param)
281  : DistributionLogNormal(std::vector<double>{median, scale_param})
282 {
283 }
284 
286 
288 {
289  if (m_scale_param == 0.0)
290  return DoubleEqual(x, m_median) ? 1.0 : 0.0;
291  double t = std::log(x / m_median) / m_scale_param;
292  return std::exp(-t * t / 2.0) / (x * m_scale_param * std::sqrt(M_TWOPI));
293 }
294 
296 {
297  double exponent = m_scale_param * m_scale_param / 2.0;
298  return m_median * std::exp(exponent);
299 }
300 
301 std::vector<double> DistributionLogNormal::equidistantPoints(size_t nbr_samples,
302  double sigma_factor,
303  const RealLimits& limits) const
304 {
305  if (nbr_samples < 2) {
306  std::vector<double> result;
307  result.push_back(m_median);
308  return result;
309  }
310  if (sigma_factor <= 0.0)
311  sigma_factor = 2.0;
312  double xmin = m_median * std::exp(-sigma_factor * m_scale_param);
313  double xmax = m_median * std::exp(sigma_factor * m_scale_param);
314  adjustMinMaxForLimits(xmin, xmax, limits);
315  return equidistantPointsInRange(nbr_samples, xmin, xmax);
316 }
317 
319 {
320  return m_scale_param == 0.0;
321 }
322 
323 void DistributionLogNormal::setUnits(const std::string& units)
324 {
325  parameter("Median")->setUnit(units);
326  // scale parameter remains unitless
327 }
328 
329 // ************************************************************************** //
330 // class DistributionCosine
331 // ************************************************************************** //
332 
333 DistributionCosine::DistributionCosine(const std::vector<double> P)
334  : IDistribution1D({"DistributionCosine",
335  "class_tooltip",
336  {{"Mean", "", "para_tooltip", -INF, +INF, 0},
337  {"Sigma", "", "para_tooltip", -INF, +INF, 0}}},
338  P),
339  m_mean(m_P[0]), m_sigma(m_P[1])
340 {
341  if (m_sigma < 0.0)
342  throw Exceptions::ClassInitializationException("DistributionCosine: sigma<0");
343 }
344 
345 DistributionCosine::DistributionCosine(double mean, double sigma)
346  : DistributionCosine(std::vector<double>{mean, sigma})
347 {
348 }
349 
351 
353 {
354  if (m_sigma == 0.0)
355  return DoubleEqual(x, m_mean) ? 1.0 : 0.0;
356  if (std::abs(x - m_mean) > M_PI * m_sigma)
357  return 0.0;
358  return (1.0 + std::cos((x - m_mean) / m_sigma)) / (m_sigma * M_TWOPI);
359 }
360 
361 std::vector<double> DistributionCosine::equidistantPoints(size_t nbr_samples, double sigma_factor,
362  const RealLimits& limits) const
363 {
364  if (sigma_factor <= 0.0 || sigma_factor > 2.0)
365  sigma_factor = 2.0;
366  double xmin = m_mean - sigma_factor * m_sigma * M_PI_2;
367  double xmax = m_mean + sigma_factor * m_sigma * M_PI_2;
368  adjustMinMaxForLimits(xmin, xmax, limits);
369  return equidistantPointsInRange(nbr_samples, xmin, xmax);
370 }
371 
373 {
374  return m_sigma == 0.0;
375 }
376 
377 // ************************************************************************** //
378 // class DistributionTrapezoidal
379 // ************************************************************************** //
380 
382  : IDistribution1D({"DistributionTrapezoid",
383  "class_tooltip",
384  {{"Center", "", "para_tooltip", -INF, +INF, 0},
385  {"LeftWidth", "", "para_tooltip", -INF, +INF, 0},
386  {"MiddleWidth", "", "para_tooltip", -INF, +INF, 0},
387  {"RightWidth", "", "para_tooltip", -INF, +INF, 0}}},
388  P),
389  m_center(m_P[0]), m_left(m_P[1]), m_middle(m_P[2]), m_right(m_P[3])
390 {
391  if (m_left < 0.0)
392  throw Exceptions::ClassInitializationException("DistributionTrapezoid: leftWidth < 0");
393  if (m_middle < 0.0)
394  throw Exceptions::ClassInitializationException("DistributionTrapezoid: middleWidth < 0");
395  if (m_right < 0.0)
396  throw Exceptions::ClassInitializationException("DistributionTrapezoid: rightWidth < 0");
397 }
398 
399 DistributionTrapezoid::DistributionTrapezoid(double center, double left, double middle,
400  double right)
401  : DistributionTrapezoid(std::vector<double>{center, left, middle, right})
402 {
403 }
404 
406 {
407  double height = 2.0 / (m_left + 2.0 * m_middle + m_right);
408  double min = m_center - m_middle / 2.0 - m_left;
409  if (x < min)
410  return 0.0;
411  if (x < min + m_left)
412  return (x - min) * height / m_left;
413  if (x < min + m_left + m_middle)
414  return height;
415  if (x < min + m_left + m_middle + m_right) {
416  return height - (x - min - m_left - m_middle) * height / m_right;
417  }
418  return 0.0;
419 }
420 
421 std::vector<double> DistributionTrapezoid::equidistantPoints(size_t nbr_samples, double,
422  const RealLimits& limits) const
423 {
424  double xmin = m_center - m_middle / 2.0 - m_left;
425  double xmax = xmin + m_left + m_middle + m_right;
426  adjustLimitsToNonZeroSamples(xmin, xmax, nbr_samples);
427  adjustMinMaxForLimits(xmin, xmax, limits);
428  return equidistantPointsInRange(nbr_samples, xmin, xmax);
429 }
430 
432 {
433  return (m_left + m_middle + m_right) == 0.0;
434 }
435 
437  size_t nbr_samples) const
438 {
439  if (nbr_samples <= 1)
440  return;
441  size_t N = nbr_samples;
442  if (m_left > 0.0)
443  ++N;
444  if (m_right > 0.0)
445  ++N;
446  if (N == nbr_samples)
447  return;
448  double step = (max - min) / (N - 1);
449  if (m_left > 0.0)
450  min += step;
451  if (m_right > 0.0)
452  max -= step;
453 }
454 
455 namespace
456 {
457 bool DoubleEqual(double a, double b)
458 {
459  double eps = 10.0
460  * std::max(std::abs(a) * std::numeric_limits<double>::epsilon(),
461  std::numeric_limits<double>::min());
462  return std::abs(a - b) < eps;
463 }
464 } // namespace
Defines classes representing one-dimensional distributions.
Defines many exception classes in namespace Exceptionss.
const double INF
Definition: INode.h:24
Defines M_PI and some more mathematical constants.
#define M_TWOPI
Definition: MathConstants.h:49
#define M_PI_2
Definition: MathConstants.h:40
#define M_PI
Definition: MathConstants.h:39
Defines class ParameterPool.
Defines class ParameterSample.
Defines class RealParameter.
Cosine distribution.
const double & m_sigma
double probabilityDensity(double x) const final
Returns the distribution-specific probability density for value x.
bool isDelta() const final
Returns true if the distribution is in the limit case of a Dirac delta distribution.
const double & m_mean
virtual std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const
generate list of sample values
Uniform distribution function with half width hwhm.
Definition: Distributions.h:86
const double & m_max
const double & m_min
bool isDelta() const final
Returns true if the distribution is in the limit case of a Dirac delta distribution.
virtual std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const
Returns list of sample values.
double probabilityDensity(double x) const final
Returns the distribution-specific probability density for value x.
Gaussian distribution with standard deviation std_dev.
bool isDelta() const final
Returns true if the distribution is in the limit case of a Dirac delta distribution.
double probabilityDensity(double x) const final
Returns the distribution-specific probability density for value x.
const double & m_std_dev
const double & m_mean
virtual std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const
generate list of sample values
Log-normal distribution.
double probabilityDensity(double x) const final
Returns the distribution-specific probability density for value x.
double getMean() const final
Returns the distribution-specific mean.
const double & m_scale_param
virtual void setUnits(const std::string &units)
Sets distribution units.
DistributionLogNormal()=delete
bool isDelta() const final
Returns true if the distribution is in the limit case of a Dirac delta distribution.
const double & m_median
virtual std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const
generate list of sample values
Lorentz distribution with half width hwhm.
bool isDelta() const final
Returns true if the distribution is in the limit case of a Dirac delta distribution.
virtual std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const
generate list of sample values
const double & m_mean
double probabilityDensity(double x) const final
Returns the distribution-specific probability density for value x.
const double & m_hwhm
Trapezoidal distribution.
bool isDelta() const final
Returns true if the distribution is in the limit case of a Dirac delta distribution.
double probabilityDensity(double x) const final
Returns the distribution-specific probability density for value x.
virtual std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const
generate list of sample values
const double & m_center
const double & m_left
const double & m_middle
const double & m_right
void adjustLimitsToNonZeroSamples(double &min, double &max, size_t nbr_samples) const
Interface for one-dimensional distributions.
Definition: Distributions.h:33
std::vector< ParameterSample > generateSamplesFromValues(const std::vector< double > &sample_values) const
Returns weighted samples from given interpolation points and probabilityDensity().
virtual double probabilityDensity(double x) const =0
Returns the distribution-specific probability density for value x.
virtual double getMean() const =0
Returns the distribution-specific mean.
std::vector< ParameterSample > equidistantSamplesInRange(size_t nbr_samples, double xmin, double xmax) const
Returns equidistant samples from xmin to xmax, weighted with probabilityDensity().
IDistribution1D(const NodeMeta &meta, const std::vector< double > &PValues)
void adjustMinMaxForLimits(double &xmin, double &xmax, const RealLimits &limits) const
modifies xmin and xmax if they are outside of limits
virtual std::vector< double > equidistantPointsInRange(size_t nbr_samples, double xmin, double xmax) const
Returns equidistant interpolation points from xmin to xmax.
virtual bool isDelta() const =0
Returns true if the distribution is in the limit case of a Dirac delta distribution.
virtual std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const =0
Returns equidistant interpolation points, with range computed in distribution-specific way from mean ...
virtual void setUnits(const std::string &units)
Sets distribution units.
std::vector< ParameterSample > equidistantSamples(size_t nbr_samples, double sigma_factor=0., const RealLimits &limits=RealLimits()) const
Returns equidistant samples, using intrinsic parameters, weighted with probabilityDensity().
Base class for tree-like structures containing parameterized objects.
Definition: INode.h:49
RealParameter * parameter(const std::string &name) const
Returns parameter with given 'name'.
ParameterPool * parameterPool() const
Returns pointer to the parameter pool.
A parameter value with a weight, as obtained when sampling from a distribution.
Limits for a real fit parameter.
Definition: RealLimits.h:25
bool hasUpperLimit() const
if has upper limit
Definition: RealLimits.cpp:55
double upperLimit() const
Returns upper limit.
Definition: RealLimits.cpp:60
double lowerLimit() const
Returns lower limit.
Definition: RealLimits.cpp:38
bool hasLowerLimit() const
if has lower limit
Definition: RealLimits.cpp:33
RealParameter & setUnit(const std::string &name)
bool DoubleEqual(double a, double b)
Metadata of one model node.
Definition: INode.h:37