BornAgain  1.19.79
Open-source research software to simulate and fit neutron and x-ray reflectometry and grazing-incidence small-angle scattering
IDistribution2DSampler.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file Sample/Correlations/IDistribution2DSampler.cpp
6 //! @brief Defines interface class IProfile1D, and children thereof
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 <random>
18 
19 namespace {
20 
21 double sigma_scale = 3.0;
22 size_t n_boxes = 256; // number of boxes for Ziggurat sampling
23 
24 struct ZigguratBox {
25  ZigguratBox(double x_min, double x_max, double y_max, double y_lower)
26  : m_x_min(x_min)
27  , m_x_max(x_max)
28  , m_y_max(y_max)
29  , m_y_lower(y_lower)
30  {
31  }
32 
33  double m_x_min; // left edge of the box
34  double m_x_max; // right edge of the box
35  // m_y_min is inherently 0 for every box and hence has not been defined
36  double m_y_max; // height of box
37  double m_y_lower; // minimum height of the box for which points below that height
38  // are located below the density function curve in the box
39 };
40 
41 std::pair<double, double> samplingZiggurat(double r, double x_func_max, double (*func_phi)(double))
42 {
43  // This sampling is based on vertical boxes instead of the conventional
44  // Ziggurat sampling that is done with horizontal boxes
45 
46  std::random_device rd; // random device class instance
47  std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()
48  std::uniform_real_distribution<double> uniformDist(0.0, 1.0);
49 
50  double box_width = (x_func_max + r) / n_boxes; // r = rightmost box's right-edge from x_func_max
51  std::vector<ZigguratBox> boxes;
52  std::vector<double> cum_area_vector;
53 
54  double x_min = 0, x_max = 0, y_max = 0, y_lower = 0, cum_area_box = 0;
55 
56  // Establising vectors of boxes and cumulative area (probability of each box) for Ziggurat
57  // sampling
58  for (size_t i = 0; i < n_boxes; ++i) {
59  if (i != 0)
60  x_min = x_max;
61 
62  x_max += box_width;
63 
64  if (x_func_max >= x_max) {
65  y_max = func_phi(x_max);
66  y_lower = func_phi(x_min);
67  } else if (x_func_max > x_min && x_func_max <= x_max) {
68  y_max = func_phi(x_func_max);
69  y_lower = std::min(func_phi(x_min), func_phi(x_max));
70  } else {
71  y_max = func_phi(x_min);
72  y_lower = func_phi(x_max);
73  }
74 
75  boxes.emplace_back(ZigguratBox(x_min, x_max, y_max, y_lower));
76 
77  cum_area_box += box_width * y_max;
78  cum_area_vector.emplace_back(cum_area_box);
79  }
80 
81  // Normalizing the cumulative area to 1
82  for (size_t i = 0; i < n_boxes; ++i)
83  cum_area_vector[i] = cum_area_vector[i] / cum_area_vector.back();
84 
85  // Sampling a phi value
86  double phi = 0;
87  bool solnFound(false);
88 
89  while (!solnFound) {
90  double random_cum_area = uniformDist(gen);
91  for (size_t i = 0; i < n_boxes; ++i) {
92  if (random_cum_area <= cum_area_vector[i]) {
93  double random_y = uniformDist(gen) * boxes[i].m_y_max;
94 
95  std::uniform_real_distribution<double> uniformDistAB(boxes[i].m_x_min,
96  boxes[i].m_x_max);
97  double phi_attempt = uniformDistAB(gen);
98 
99  if (random_y <= boxes[i].m_y_lower) {
100  phi = phi_attempt;
101  solnFound = true;
102  } else {
103  if (random_y <= func_phi(phi_attempt)) {
104  phi = phi_attempt;
105  solnFound = true;
106  }
107  }
108  break;
109  }
110  }
111  }
112 
113  // Sampling an alpha value
114  double alpha = M_TWOPI * uniformDist(gen);
115  return std::make_pair(phi, alpha);
116 }
117 
118 double func_phi_Cauchy(double phi)
119 {
120  // The independent "phi" density function of the 2D Cauchy distribution
121  return phi * std::exp(-phi);
122 }
123 
124 double func_phi_Cone(double phi)
125 {
126  // The independent "phi" density function of the 2D Cone distribution
127  return 6 * (1 - phi) * phi;
128 }
129 
130 } // namespace
131 
132 
134 
135 std::pair<double, double> Distribution2DCauchySampler::randomSample() const
136 {
137  // Use Ziggurat sampling instead of Inverse Transform Sampling (ITS requires numerical solver)
138 
139  double phi_max_Cauchy = 1.0;
140  // rightmost box's right-edge from phi_max_Cauchy for Ziggurat Sampling
141  double r = sigma_scale * std::sqrt(2); // standard dev of func_phi_Cauchy is sqrt(2)
142  std::pair<double, double> samples = samplingZiggurat(r, phi_max_Cauchy, func_phi_Cauchy);
143  return std::make_pair(m_omega_x * samples.first * std::cos(samples.second),
144  m_omega_y * samples.first * std::sin(samples.second));
145 }
146 
147 std::pair<double, double> Distribution2DGaussSampler::randomSample() const
148 {
149  std::random_device rd; // random device class instance
150  std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()
151  std::uniform_real_distribution<double> uniformDist(0.0, 1.0);
152 
153  double cdf_value_phi = uniformDist(gen);
154 
155  // Use ITS and solve for phi from the cdf of radial (phi) distribution
156  double phi = std::sqrt(-2 * std::log(1 - cdf_value_phi));
157  double alpha = M_TWOPI * uniformDist(gen);
158  return std::make_pair(m_omega_x * phi * std::cos(alpha), m_omega_y * phi * std::sin(alpha));
159 }
160 
161 std::pair<double, double> Distribution2DGateSampler::randomSample() const
162 {
163  std::random_device rd; // random device class instance
164  std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()
165  std::uniform_real_distribution<double> uniformDist(0.0, 1.0);
166 
167  double cdf_value_phi = uniformDist(gen);
168 
169  // Use ITS and solve for phi from the cdf of radial (phi) distribution
170  double phi = std::sqrt(cdf_value_phi);
171  double alpha = M_TWOPI * uniformDist(gen);
172  return std::make_pair(m_omega_x * phi * std::cos(alpha), m_omega_y * phi * std::sin(alpha));
173 }
174 
175 std::pair<double, double> Distribution2DConeSampler::randomSample() const
176 {
177  // Use Ziggurat sampling instead of Inverse Transform Sampling (ITS requires numerical solver)
178 
179  double phi_max_Cone = 0.5;
180  // rightmost box's right-edge from phi_max_Cone for Ziggurat Sampling
181  double r = 0.5;
182  std::pair<double, double> samples = samplingZiggurat(r, phi_max_Cone, func_phi_Cone);
183  return std::make_pair(m_omega_x * samples.first * std::cos(samples.second),
184  m_omega_y * samples.first * std::sin(samples.second));
185 }
Defines M_PI and some more mathematical constants.
#define M_TWOPI
Definition: Constants.h:54
Defines interface class IProfile1D, and children thereof.
std::pair< double, double > randomSample() const override
std::pair< double, double > randomSample() const override
std::pair< double, double > randomSample() const override
std::pair< double, double > randomSample() const override
virtual ~IDistribution2DSampler()