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