BornAgain  1.19.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 reflection and scattering
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 
16 #include "Base/Math/Constants.h"
20 #include <algorithm>
21 #include <cmath>
22 #include <limits>
23 #include <sstream>
24 
25 namespace {
26 bool DoubleEqual(double a, double b);
27 }
28 
29 // ************************************************************************************************
30 // class IDistribution1D
31 // ************************************************************************************************
32 
33 IDistribution1D::IDistribution1D(const NodeMeta& meta, const std::vector<double>& PValues)
34  : INode(meta, PValues)
35 {
36 }
37 
38 //! Returns equidistant samples, using intrinsic parameters, weighted with probabilityDensity().
39 
40 std::vector<ParameterSample> IDistribution1D::equidistantSamples(size_t nbr_samples,
41  double sigma_factor,
42  const RealLimits& limits) const
43 {
44  if (nbr_samples == 0)
45  throw std::runtime_error("IDistribution1D::generateSamples: "
46  "number of generated samples must be bigger than zero");
47  if (isDelta())
48  return {ParameterSample(getMean())};
49  return generateSamplesFromValues(equidistantPoints(nbr_samples, sigma_factor, limits));
50 }
51 
52 //! Returns equidistant samples from xmin to xmax, weighted with probabilityDensity().
53 
54 std::vector<ParameterSample>
55 IDistribution1D::equidistantSamplesInRange(size_t nbr_samples, double xmin, double xmax) const
56 {
57  if (nbr_samples == 0)
58  throw std::runtime_error("IDistribution1D::generateSamples: "
59  "number of generated samples must be bigger than zero");
60  if (isDelta())
61  return {ParameterSample(getMean())};
62  return generateSamplesFromValues(equidistantPointsInRange(nbr_samples, xmin, xmax));
63 }
64 
65 //! Returns equidistant interpolation points from xmin to xmax.
66 
67 std::vector<double> IDistribution1D::equidistantPointsInRange(size_t nbr_samples, double xmin,
68  double xmax) const
69 {
70  if (nbr_samples < 2 || DoubleEqual(xmin, xmax))
71  return {getMean()};
72  std::vector<double> result(nbr_samples);
73  for (size_t i = 0; i < nbr_samples; ++i)
74  result[i] = xmin + i * (xmax - xmin) / (nbr_samples - 1.0);
75  return result;
76 }
77 
78 void IDistribution1D::setUnits(const std::string& units)
79 {
80  for (auto* par : parameterPool()->parameters())
81  par->setUnit(units);
82 }
83 
84 void IDistribution1D::adjustMinMaxForLimits(double& xmin, double& xmax,
85  const RealLimits& limits) const
86 {
87  if (limits.hasLowerLimit() && xmin < limits.lowerLimit())
88  xmin = limits.lowerLimit();
89  if (limits.hasUpperLimit() && xmax > limits.upperLimit())
90  xmax = limits.upperLimit();
91  if (xmin > xmax) {
92  std::ostringstream ostr;
93  ostr << "IDistribution1D::adjustMinMaxForLimits() -> Error. Can't' adjust ";
94  ostr << "xmin:" << xmin << " xmax:" << xmax << " for given limits " << limits << std::endl;
95  throw std::runtime_error(ostr.str());
96  }
97 }
98 
99 //! Returns weighted samples from given interpolation points and probabilityDensity().
100 
101 std::vector<ParameterSample>
102 IDistribution1D::generateSamplesFromValues(const std::vector<double>& sample_values) const
103 {
104  std::vector<ParameterSample> result;
105  double norm_factor = 0.0;
106  for (double value : sample_values) {
107  double pdf = probabilityDensity(value);
108  result.push_back(ParameterSample(value, pdf));
109  norm_factor += pdf;
110  }
111  if (norm_factor <= 0.0)
112  throw std::runtime_error("IDistribution1D::generateSamples: "
113  "total probability must be bigger than zero");
114  for (ParameterSample& sample : result)
115  sample.weight /= norm_factor;
116  return result;
117 }
118 
119 // ************************************************************************************************
120 // class DistributionGate
121 // ************************************************************************************************
122 
123 DistributionGate::DistributionGate(const std::vector<double> P)
124  : IDistribution1D(
125  {"DistributionGate",
126  "class_tooltip",
127  {{"Min", "", "para_tooltip", -INF, +INF, 0}, {"Max", "", "para_tooltip", -INF, +INF, 0}}},
128  P)
129  , m_min(m_P[0])
130  , m_max(m_P[1])
131 {
132  if (m_max < m_min)
133  throw std::runtime_error("DistributionGate: max<min");
134 }
135 
136 DistributionGate::DistributionGate(double min, double max)
137  : DistributionGate(std::vector<double>{min, max})
138 {
139 }
140 
142 
144 {
145  if (x < m_min || x > m_max)
146  return 0.0;
147  if (DoubleEqual(m_min, m_max))
148  return 1.0;
149  return 1.0 / (m_max - m_min);
150 }
151 
152 std::vector<double> DistributionGate::equidistantPoints(size_t nbr_samples, double /*sigma_factor*/,
153  const RealLimits& limits) const
154 {
155  double xmin = m_min;
156  double xmax = m_max;
157  adjustMinMaxForLimits(xmin, xmax, limits);
158  return equidistantPointsInRange(nbr_samples, xmin, xmax);
159 }
160 
162 {
163  return DoubleEqual(m_min, m_max);
164 }
165 
166 // ************************************************************************************************
167 // class DistributionLorentz
168 // ************************************************************************************************
169 
170 DistributionLorentz::DistributionLorentz(const std::vector<double> P)
171  : IDistribution1D({"DistributionLorentz",
172  "class_tooltip",
173  {{"Mean", "", "para_tooltip", -INF, +INF, 0},
174  {"HWHM", "", "para_tooltip", -INF, +INF, 0}}},
175  P)
176  , m_mean(m_P[0])
177  , m_hwhm(m_P[1])
178 {
179  if (m_hwhm < 0.0)
180  throw std::runtime_error("DistributionLorentz: hwhm<0");
181 }
182 
184  : DistributionLorentz(std::vector<double>{mean, hwhm})
185 {
186 }
187 
189 
191 {
192  if (m_hwhm == 0.0)
193  return DoubleEqual(x, m_mean) ? 1.0 : 0.0;
194  return m_hwhm / (m_hwhm * m_hwhm + (x - m_mean) * (x - m_mean)) / M_PI;
195 }
196 
197 std::vector<double> DistributionLorentz::equidistantPoints(size_t nbr_samples, double sigma_factor,
198  const RealLimits& limits) const
199 {
200  if (sigma_factor <= 0.0)
201  sigma_factor = 2.0;
202  double xmin = m_mean - sigma_factor * m_hwhm;
203  double xmax = m_mean + sigma_factor * m_hwhm;
204  adjustMinMaxForLimits(xmin, xmax, limits);
205  return equidistantPointsInRange(nbr_samples, xmin, xmax);
206 }
207 
209 {
210  return m_hwhm == 0.0;
211 }
212 
213 // ************************************************************************************************
214 // class DistributionGaussian
215 // ************************************************************************************************
216 
217 DistributionGaussian::DistributionGaussian(const std::vector<double> P)
218  : IDistribution1D({"DistributionGaussian",
219  "class_tooltip",
220  {{"Mean", "", "para_tooltip", -INF, +INF, 0},
221  {"StdDev", "", "para_tooltip", -INF, +INF, 0}}},
222  P)
223  , m_mean(m_P[0])
224  , m_std_dev(m_P[1])
225 {
226  if (m_std_dev < 0.0)
227  throw std::runtime_error("DistributionGaussian: std_dev < 0");
228 }
229 
230 DistributionGaussian::DistributionGaussian(double mean, double std_dev)
231  : DistributionGaussian(std::vector<double>{mean, std_dev})
232 {
233 }
234 
236 
238 {
239  if (m_std_dev == 0.0)
240  return DoubleEqual(x, m_mean) ? 1.0 : 0.0;
241  double exponential = std::exp(-(x - m_mean) * (x - m_mean) / (2.0 * m_std_dev * m_std_dev));
242  return exponential / m_std_dev / std::sqrt(M_TWOPI);
243 }
244 
245 std::vector<double> DistributionGaussian::equidistantPoints(size_t nbr_samples, double sigma_factor,
246  const RealLimits& limits) const
247 {
248  if (sigma_factor <= 0.0)
249  sigma_factor = 2.0;
250  double xmin = m_mean - sigma_factor * m_std_dev;
251  double xmax = m_mean + sigma_factor * m_std_dev;
252  adjustMinMaxForLimits(xmin, xmax, limits);
253  return equidistantPointsInRange(nbr_samples, xmin, xmax);
254 }
255 
257 {
258  return m_std_dev == 0.0;
259 }
260 
261 // ************************************************************************************************
262 // class DistributionLogNormal
263 // ************************************************************************************************
264 
266  : IDistribution1D({"DistributionLogNormal",
267  "class_tooltip",
268  {{"Median", "", "para_tooltip", -INF, +INF, 0},
269  {"ScaleParameter", "", "para_tooltip", -INF, +INF, 0}}},
270  P)
271  , m_median(m_P[0])
272  , m_scale_param(m_P[1])
273 {
274  if (m_scale_param < 0.0)
275  throw std::runtime_error("DistributionLogNormal: scale_param < 0");
276  if (m_median <= 0.0)
277  throw std::runtime_error("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])
340  , m_sigma(m_P[1])
341 {
342  if (m_sigma < 0.0)
343  throw std::runtime_error("DistributionCosine: sigma<0");
344 }
345 
346 DistributionCosine::DistributionCosine(double mean, double sigma)
347  : DistributionCosine(std::vector<double>{mean, sigma})
348 {
349 }
350 
352 
354 {
355  if (m_sigma == 0.0)
356  return DoubleEqual(x, m_mean) ? 1.0 : 0.0;
357  if (std::abs(x - m_mean) > M_PI * m_sigma)
358  return 0.0;
359  return (1.0 + std::cos((x - m_mean) / m_sigma)) / (m_sigma * M_TWOPI);
360 }
361 
362 std::vector<double> DistributionCosine::equidistantPoints(size_t nbr_samples, double sigma_factor,
363  const RealLimits& limits) const
364 {
365  if (sigma_factor <= 0.0 || sigma_factor > 2.0)
366  sigma_factor = 2.0;
367  double xmin = m_mean - sigma_factor * m_sigma * M_PI_2;
368  double xmax = m_mean + sigma_factor * m_sigma * M_PI_2;
369  adjustMinMaxForLimits(xmin, xmax, limits);
370  return equidistantPointsInRange(nbr_samples, xmin, xmax);
371 }
372 
374 {
375  return m_sigma == 0.0;
376 }
377 
378 // ************************************************************************************************
379 // class DistributionTrapezoidal
380 // ************************************************************************************************
381 
383  : IDistribution1D({"DistributionTrapezoid",
384  "class_tooltip",
385  {{"Center", "", "para_tooltip", -INF, +INF, 0},
386  {"LeftWidth", "", "para_tooltip", -INF, +INF, 0},
387  {"MiddleWidth", "", "para_tooltip", -INF, +INF, 0},
388  {"RightWidth", "", "para_tooltip", -INF, +INF, 0}}},
389  P)
390  , m_center(m_P[0])
391  , m_left(m_P[1])
392  , m_middle(m_P[2])
393  , m_right(m_P[3])
394 {
395  if (m_left < 0.0)
396  throw std::runtime_error("DistributionTrapezoid: leftWidth < 0");
397  if (m_middle < 0.0)
398  throw std::runtime_error("DistributionTrapezoid: middleWidth < 0");
399  if (m_right < 0.0)
400  throw std::runtime_error("DistributionTrapezoid: rightWidth < 0");
401 }
402 
403 DistributionTrapezoid::DistributionTrapezoid(double center, double left, double middle,
404  double right)
405  : DistributionTrapezoid(std::vector<double>{center, left, middle, right})
406 {
407 }
408 
410 {
411  double height = 2.0 / (m_left + 2.0 * m_middle + m_right);
412  double min = m_center - m_middle / 2.0 - m_left;
413  if (x < min)
414  return 0.0;
415  if (x < min + m_left)
416  return (x - min) * height / m_left;
417  if (x < min + m_left + m_middle)
418  return height;
419  if (x < min + m_left + m_middle + m_right) {
420  return height - (x - min - m_left - m_middle) * height / m_right;
421  }
422  return 0.0;
423 }
424 
425 std::vector<double> DistributionTrapezoid::equidistantPoints(size_t nbr_samples, double,
426  const RealLimits& limits) const
427 {
428  double xmin = m_center - m_middle / 2.0 - m_left;
429  double xmax = xmin + m_left + m_middle + m_right;
430  adjustLimitsToNonZeroSamples(xmin, xmax, nbr_samples);
431  adjustMinMaxForLimits(xmin, xmax, limits);
432  return equidistantPointsInRange(nbr_samples, xmin, xmax);
433 }
434 
436 {
437  return (m_left + m_middle + m_right) == 0.0;
438 }
439 
441  size_t nbr_samples) const
442 {
443  if (nbr_samples <= 1)
444  return;
445  size_t N = nbr_samples;
446  if (m_left > 0.0)
447  ++N;
448  if (m_right > 0.0)
449  ++N;
450  if (N == nbr_samples)
451  return;
452  double step = (max - min) / (N - 1);
453  if (m_left > 0.0)
454  min += step;
455  if (m_right > 0.0)
456  max -= step;
457 }
458 
459 namespace {
460 bool DoubleEqual(double a, double b)
461 {
462  double eps = 10.0
463  * std::max(std::abs(a) * std::numeric_limits<double>::epsilon(),
464  std::numeric_limits<double>::min());
465  return std::abs(a - b) < eps;
466 }
467 } // namespace
Defines M_PI and some more mathematical constants.
#define M_TWOPI
Definition: Constants.h:54
#define M_PI_2
Definition: Constants.h:45
#define M_PI
Definition: Constants.h:44
Defines classes representing one-dimensional distributions.
const double INF
Definition: INode.h:25
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:88
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:34
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
ParameterPool * parameterPool() const
Returns pointer to the parameter pool.
RealParameter * parameter(const std::string &name) const
Returns parameter with given 'name'.
A parameter value with a weight, as obtained when sampling from a distribution.
Limits for a real fit parameter.
Definition: RealLimits.h:24
bool hasUpperLimit() const
if has upper limit
Definition: RealLimits.cpp:57
double upperLimit() const
Returns upper limit.
Definition: RealLimits.cpp:62
double lowerLimit() const
Returns lower limit.
Definition: RealLimits.cpp:40
bool hasLowerLimit() const
if has lower limit
Definition: RealLimits.cpp:35
RealParameter & setUnit(const std::string &name)
Definition: filesystem.h:81
Metadata of one model node.
Definition: INode.h:38