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