BornAgain  1.19.79
Open-source research software to simulate and fit neutron and x-ray reflectometry and grazing-incidence small-angle scattering
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"
17 #include "Base/Py/PyFmt.h"
19 #include <algorithm>
20 #include <cmath>
21 #include <limits>
22 #include <map>
23 #include <sstream>
24 
25 namespace {
26 
27 bool DoubleEqual(double a, double b);
28 
29 } // namespace
30 
31 
32 // ************************************************************************************************
33 // class IDistribution1D
34 // ************************************************************************************************
35 
36 IDistribution1D::IDistribution1D(const std::vector<double>& PValues)
37  : INode(PValues)
38 {
39 }
40 
41 //! Returns equidistant samples, using intrinsic parameters, weighted with probabilityDensity().
42 
43 std::vector<ParameterSample> IDistribution1D::equidistantSamples(size_t nbr_samples,
44  double sigma_factor,
45  const RealLimits& limits) const
46 {
47  if (nbr_samples == 0)
48  throw std::runtime_error("IDistribution1D::generateSamples: "
49  "number of generated samples must be bigger than zero");
50  if (isDelta())
51  return {ParameterSample(mean())};
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)
61  throw std::runtime_error("IDistribution1D::generateSamples: "
62  "number of generated samples must be bigger than zero");
63  if (isDelta())
64  return {ParameterSample(mean())};
65  return generateSamplesFromValues(equidistantPointsInRange(nbr_samples, xmin, xmax));
66 }
67 
68 //! Returns equidistant interpolation points from xmin to xmax.
69 
70 std::vector<double> IDistribution1D::equidistantPointsInRange(size_t nbr_samples, double xmin,
71  double xmax) const
72 {
73  if (nbr_samples < 2 || DoubleEqual(xmin, xmax))
74  return {mean()};
75  std::vector<double> result(nbr_samples);
76  for (size_t i = 0; i < nbr_samples; ++i)
77  result[i] = xmin + i * (xmax - xmin) / (nbr_samples - 1.0);
78  return result;
79 }
80 
81 void IDistribution1D::adjustMinMaxForLimits(double& xmin, double& xmax,
82  const RealLimits& limits) const
83 {
84  if (limits.hasLowerLimit() && xmin < limits.lowerLimit())
85  xmin = limits.lowerLimit();
86  if (limits.hasUpperLimit() && xmax > limits.upperLimit())
87  xmax = limits.upperLimit();
88  if (xmin > xmax) {
89  std::ostringstream ostr;
90  ostr << "IDistribution1D::adjustMinMaxForLimits() -> Error. Can't' adjust ";
91  ostr << "xmin:" << xmin << " xmax:" << xmax << " for given limits " << limits << std::endl;
92  throw std::runtime_error(ostr.str());
93  }
94 }
95 
96 //! Returns weighted samples from given interpolation points and probabilityDensity().
97 
98 std::vector<ParameterSample>
99 IDistribution1D::generateSamplesFromValues(const std::vector<double>& sample_values) const
100 {
101  std::vector<ParameterSample> result;
102  double norm_factor = 0.0;
103  for (double value : sample_values) {
104  double pdf = probabilityDensity(value);
105  result.emplace_back(value, pdf);
106  norm_factor += pdf;
107  }
108  if (norm_factor <= 0.0)
109  throw std::runtime_error("IDistribution1D::generateSamples: "
110  "total probability must be bigger than zero");
111  for (ParameterSample& sample : result)
112  sample.weight /= norm_factor;
113  return result;
114 }
115 
116 // ************************************************************************************************
117 // class DistributionGate
118 // ************************************************************************************************
119 
120 DistributionGate::DistributionGate(const std::vector<double> P)
121  : IDistribution1D(P)
122  , m_min(m_P[0])
123  , m_max(m_P[1])
124 {
125  checkNodeArgs();
126  if (m_max < m_min)
127  throw std::runtime_error("DistributionGate: max<min");
128 }
129 
130 DistributionGate::DistributionGate(double min, double max)
131  : DistributionGate(std::vector<double>{min, max})
132 {
133 }
134 
136  : DistributionGate(0., 1.)
137 {
138 }
139 
141 {
142  if (x < m_min || x > m_max)
143  return 0.0;
144  if (DoubleEqual(m_min, m_max))
145  return 1.0;
146  return 1.0 / (m_max - m_min);
147 }
148 
149 std::vector<double> DistributionGate::equidistantPoints(size_t nbr_samples, double /*sigma_factor*/,
150  const RealLimits& limits) const
151 {
152  double xmin = m_min;
153  double xmax = m_max;
154  adjustMinMaxForLimits(xmin, xmax, limits);
155  return equidistantPointsInRange(nbr_samples, xmin, xmax);
156 }
157 
159 {
160  return DoubleEqual(m_min, m_max);
161 }
162 
163 std::string DistributionGate::pythonConstructor(const std::string& units) const
164 {
165  return Py::Fmt::printFunction(className(), m_min, units, m_max, units);
166 }
167 
168 // ************************************************************************************************
169 // class DistributionLorentz
170 // ************************************************************************************************
171 
172 DistributionLorentz::DistributionLorentz(const std::vector<double> P)
173  : IDistribution1D(P)
174  , m_mean(m_P[0])
175  , m_hwhm(m_P[1])
176 {
177  checkNodeArgs();
178  if (m_hwhm < 0.0)
179  throw std::runtime_error("DistributionLorentz: hwhm<0");
180 }
181 
183  : DistributionLorentz(std::vector<double>{mean, hwhm})
184 {
185 }
186 
188  : DistributionLorentz(0., 1.)
189 {
190 }
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 std::string DistributionLorentz::pythonConstructor(const std::string& units) const
216 {
217  return Py::Fmt::printFunction(className(), m_mean, units, m_hwhm, units);
218 }
219 
220 // ************************************************************************************************
221 // class DistributionGaussian
222 // ************************************************************************************************
223 
224 DistributionGaussian::DistributionGaussian(const std::vector<double> P)
225  : IDistribution1D(P)
226  , m_mean(m_P[0])
227  , m_std_dev(m_P[1])
228 {
229  checkNodeArgs();
230  if (m_std_dev < 0.0)
231  throw std::runtime_error("DistributionGaussian: std_dev < 0");
232 }
233 
234 DistributionGaussian::DistributionGaussian(double mean, double std_dev)
235  : DistributionGaussian(std::vector<double>{mean, std_dev})
236 {
237 }
238 
240  : DistributionGaussian(0., 1.)
241 {
242 }
243 
245 {
246  if (m_std_dev == 0.0)
247  return DoubleEqual(x, m_mean) ? 1.0 : 0.0;
248  double exponential = std::exp(-(x - m_mean) * (x - m_mean) / (2.0 * m_std_dev * m_std_dev));
249  return exponential / m_std_dev / std::sqrt(M_TWOPI);
250 }
251 
252 std::vector<double> DistributionGaussian::equidistantPoints(size_t nbr_samples, double sigma_factor,
253  const RealLimits& limits) const
254 {
255  if (sigma_factor <= 0.0)
256  sigma_factor = 2.0;
257  double xmin = m_mean - sigma_factor * m_std_dev;
258  double xmax = m_mean + sigma_factor * m_std_dev;
259  adjustMinMaxForLimits(xmin, xmax, limits);
260  return equidistantPointsInRange(nbr_samples, xmin, xmax);
261 }
262 
264 {
265  return m_std_dev == 0.0;
266 }
267 
268 std::string DistributionGaussian::pythonConstructor(const std::string& units) const
269 {
270  return Py::Fmt::printFunction(className(), m_mean, units, m_std_dev, units);
271 }
272 
273 // ************************************************************************************************
274 // class DistributionLogNormal
275 // ************************************************************************************************
276 
278  : IDistribution1D(P)
279  , m_median(m_P[0])
280  , m_scale_param(m_P[1])
281 {
282  checkNodeArgs();
283  if (m_scale_param < 0.0)
284  throw std::runtime_error("DistributionLogNormal: scale_param < 0");
285  if (m_median <= 0.0)
286  throw std::runtime_error("DistributionLogNormal: median < 0");
287 }
288 
289 DistributionLogNormal::DistributionLogNormal(double median, double scale_param)
290  : DistributionLogNormal(std::vector<double>{median, scale_param})
291 {
292 }
293 
295 {
296  if (m_scale_param == 0.0)
297  return DoubleEqual(x, m_median) ? 1.0 : 0.0;
298  double t = std::log(x / m_median) / m_scale_param;
299  return std::exp(-t * t / 2.0) / (x * m_scale_param * std::sqrt(M_TWOPI));
300 }
301 
303 {
304  double exponent = m_scale_param * m_scale_param / 2.0;
305  return m_median * std::exp(exponent);
306 }
307 
308 std::vector<double> DistributionLogNormal::equidistantPoints(size_t nbr_samples,
309  double sigma_factor,
310  const RealLimits& limits) const
311 {
312  if (nbr_samples < 2) {
313  std::vector<double> result;
314  result.push_back(m_median);
315  return result;
316  }
317  if (sigma_factor <= 0.0)
318  sigma_factor = 2.0;
319  double xmin = m_median * std::exp(-sigma_factor * m_scale_param);
320  double xmax = m_median * std::exp(sigma_factor * m_scale_param);
321  adjustMinMaxForLimits(xmin, xmax, limits);
322  return equidistantPointsInRange(nbr_samples, xmin, xmax);
323 }
324 
326 {
327  return m_scale_param == 0.0;
328 }
329 
330 std::string DistributionLogNormal::pythonConstructor(const std::string& units) const
331 {
332  // scale parameter remains unitless
334 }
335 
336 // ************************************************************************************************
337 // class DistributionCosine
338 // ************************************************************************************************
339 
340 DistributionCosine::DistributionCosine(const std::vector<double> P)
341  : IDistribution1D(P)
342  , m_mean(m_P[0])
343  , m_sigma(m_P[1])
344 {
345  checkNodeArgs();
346  if (m_sigma < 0.0)
347  throw std::runtime_error("DistributionCosine: sigma<0");
348 }
349 
350 DistributionCosine::DistributionCosine(double mean, double sigma)
351  : DistributionCosine(std::vector<double>{mean, sigma})
352 {
353 }
354 
356  : DistributionCosine(0., 1.)
357 {
358 }
359 
361 {
362  if (m_sigma == 0.0)
363  return DoubleEqual(x, m_mean) ? 1.0 : 0.0;
364  if (std::abs(x - m_mean) > M_PI * m_sigma)
365  return 0.0;
366  return (1.0 + std::cos((x - m_mean) / m_sigma)) / (m_sigma * M_TWOPI);
367 }
368 
369 std::vector<double> DistributionCosine::equidistantPoints(size_t nbr_samples, double sigma_factor,
370  const RealLimits& limits) const
371 {
372  if (sigma_factor <= 0.0 || sigma_factor > 2.0)
373  sigma_factor = 2.0;
374  double xmin = m_mean - sigma_factor * m_sigma * M_PI_2;
375  double xmax = m_mean + sigma_factor * m_sigma * M_PI_2;
376  adjustMinMaxForLimits(xmin, xmax, limits);
377  return equidistantPointsInRange(nbr_samples, xmin, xmax);
378 }
379 
381 {
382  return m_sigma == 0.0;
383 }
384 
385 std::string DistributionCosine::pythonConstructor(const std::string& units) const
386 {
387  return Py::Fmt::printFunction(className(), m_mean, units, m_sigma, units);
388 }
389 
390 // ************************************************************************************************
391 // class DistributionTrapezoidal
392 // ************************************************************************************************
393 
395  : IDistribution1D(P)
396  , m_center(m_P[0])
397  , m_left(m_P[1])
398  , m_middle(m_P[2])
399  , m_right(m_P[3])
400 {
401  checkNodeArgs();
402  if (m_left < 0.0)
403  throw std::runtime_error("DistributionTrapezoid: leftWidth < 0");
404  if (m_middle < 0.0)
405  throw std::runtime_error("DistributionTrapezoid: middleWidth < 0");
406  if (m_right < 0.0)
407  throw std::runtime_error("DistributionTrapezoid: rightWidth < 0");
408 }
409 
410 DistributionTrapezoid::DistributionTrapezoid(double center, double left, double middle,
411  double right)
412  : DistributionTrapezoid(std::vector<double>{center, left, middle, right})
413 {
414 }
415 
417  : DistributionTrapezoid(0., 0., 1., 0.)
418 {
419 }
420 
422 {
423  double height = 2.0 / (m_left + 2.0 * m_middle + m_right);
424  double min = m_center - m_middle / 2.0 - m_left;
425  if (x < min)
426  return 0.0;
427  if (x < min + m_left)
428  return (x - min) * height / m_left;
429  if (x < min + m_left + m_middle)
430  return height;
431  if (x < min + m_left + m_middle + m_right)
432  return height - (x - min - m_left - m_middle) * height / m_right;
433  return 0.0;
434 }
435 
436 std::vector<double> DistributionTrapezoid::equidistantPoints(size_t nbr_samples, double,
437  const RealLimits& limits) const
438 {
439  double xmin = m_center - m_middle / 2.0 - m_left;
440  double xmax = xmin + m_left + m_middle + m_right;
441  adjustLimitsToNonZeroSamples(xmin, xmax, nbr_samples);
442  adjustMinMaxForLimits(xmin, xmax, limits);
443  return equidistantPointsInRange(nbr_samples, xmin, xmax);
444 }
445 
447 {
448  return (m_left + m_middle + m_right) == 0.0;
449 }
450 
451 std::string DistributionTrapezoid::pythonConstructor(const std::string& units) const
452 {
453  return Py::Fmt::printFunction(
454  className(), {{m_center, units}, {m_left, units}, {m_middle, units}, {m_right, units}});
455 }
456 
458  size_t nbr_samples) const
459 {
460  if (nbr_samples <= 1)
461  return;
462  size_t N = nbr_samples;
463  if (m_left > 0.0)
464  ++N;
465  if (m_right > 0.0)
466  ++N;
467  if (N == nbr_samples)
468  return;
469  double step = (max - min) / (N - 1);
470  if (m_left > 0.0)
471  min += step;
472  if (m_right > 0.0)
473  max -= step;
474 }
475 
476 namespace {
477 
478 bool DoubleEqual(double a, double b)
479 {
480  double eps = 10.0
481  * std::max(std::abs(a) * std::numeric_limits<double>::epsilon(),
482  std::numeric_limits<double>::min());
483  return std::abs(a - b) < eps;
484 }
485 
486 } // 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.
Defines class ParameterSample.
Defines namespace pyfmt.
Cosine distribution.
double sigma() const
double mean() const override
Returns the distribution-specific mean.
std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const override
generate list of sample values
std::string className() const final
Returns the class name, to be hard-coded in each leaf class that inherits from INode.
const double & m_sigma
bool isDelta() const override
Returns true if the distribution is in the limit case of a Dirac delta distribution.
double probabilityDensity(double x) const override
Returns the distribution-specific probability density for value x.
const double & m_mean
std::string pythonConstructor(const std::string &units) const override
Prints distribution with constructor parameters in given units. ba.DistributionGaussian(2....
Uniform distribution function with half width hwhm.
Definition: Distributions.h:90
std::string pythonConstructor(const std::string &units) const override
Prints distribution with constructor parameters in given units. ba.DistributionGaussian(2....
const double & m_max
double max() const
double probabilityDensity(double x) const override
Returns the distribution-specific probability density for value x.
std::string className() const final
Returns the class name, to be hard-coded in each leaf class that inherits from INode.
Definition: Distributions.h:97
std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const override
Returns list of sample values.
const double & m_min
bool isDelta() const override
Returns true if the distribution is in the limit case of a Dirac delta distribution.
double min() const
Gaussian distribution with standard deviation std_dev.
double probabilityDensity(double x) const override
Returns the distribution-specific probability density for value x.
const double & m_std_dev
std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const override
generate list of sample values
std::string className() const final
Returns the class name, to be hard-coded in each leaf class that inherits from INode.
double mean() const override
Returns the distribution-specific mean.
const double & m_mean
bool isDelta() const override
Returns true if the distribution is in the limit case of a Dirac delta distribution.
std::string pythonConstructor(const std::string &units) const override
Prints distribution with constructor parameters in given units. ba.DistributionGaussian(2....
Log-normal distribution.
double mean() const override
Returns the distribution-specific mean.
const double & m_scale_param
std::string pythonConstructor(const std::string &units) const override
Prints distribution with constructor parameters in given units. ba.DistributionGaussian(2....
bool isDelta() const override
Returns true if the distribution is in the limit case of a Dirac delta distribution.
DistributionLogNormal(std::vector< double > P)
std::string className() const final
Returns the class name, to be hard-coded in each leaf class that inherits from INode.
std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const override
generate list of sample values
const double & m_median
double probabilityDensity(double x) const override
Returns the distribution-specific probability density for value x.
Lorentz distribution with half width hwhm.
bool isDelta() const override
Returns true if the distribution is in the limit case of a Dirac delta distribution.
double hwhm() const
std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const override
generate list of sample values
const double & m_mean
double mean() const override
Returns the distribution-specific mean.
std::string className() const final
Returns the class name, to be hard-coded in each leaf class that inherits from INode.
double probabilityDensity(double x) const override
Returns the distribution-specific probability density for value x.
std::string pythonConstructor(const std::string &units) const override
Prints distribution with constructor parameters in given units. ba.DistributionGaussian(2....
const double & m_hwhm
Trapezoidal distribution.
std::vector< double > equidistantPoints(size_t nbr_samples, double sigma_factor, const RealLimits &limits=RealLimits()) const override
generate list of sample values
double probabilityDensity(double x) const override
Returns the distribution-specific probability density for value x.
std::string pythonConstructor(const std::string &units) const override
Prints distribution with constructor parameters in given units. ba.DistributionGaussian(2....
const double & m_center
const double & m_left
std::string className() const final
Returns the class name, to be hard-coded in each leaf class that inherits from INode.
const double & m_middle
const double & m_right
void adjustLimitsToNonZeroSamples(double &min, double &max, size_t nbr_samples) const
bool isDelta() const override
Returns true if the distribution is in the limit case of a Dirac delta distribution.
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.
std::vector< ParameterSample > equidistantSamplesInRange(size_t nbr_samples, double xmin, double xmax) const
Returns equidistant samples from xmin to xmax, weighted with probabilityDensity().
virtual double mean() const =0
Returns the distribution-specific mean.
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.
IDistribution1D(const std::vector< double > &PValues)
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 ...
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:40
void checkNodeArgs() const
Raises exception if a parameter value is invalid.
Definition: INode.cpp:27
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:66
double upperLimit() const
Returns upper limit.
Definition: RealLimits.cpp:71
double lowerLimit() const
Returns lower limit.
Definition: RealLimits.cpp:49
bool hasLowerLimit() const
if has lower limit
Definition: RealLimits.cpp:44
#define N
Definition: mixmax.h:31
std::string printFunction(const std::string &name, const std::vector< std::pair< double, std::string >> &arguments)
Print a function in the form "<name>(<arguments>)". arguments will be processed by printArguments(),...
Definition: PyFmt.cpp:168