-
Notifications
You must be signed in to change notification settings - Fork 519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Randomized Quasi-Monte Carlo Sampling in The Random Ray Method #3268
base: develop
Are you sure you want to change the base?
Changes from all commits
fa6f28a
d967bb1
0d14f44
08abdc6
528bb72
44cc548
808cb6c
25ac209
31518dc
5083584
e868aea
2f5de56
2d7cf35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,9 @@ | |
#include "openmc/search.h" | ||
#include "openmc/settings.h" | ||
#include "openmc/simulation.h" | ||
|
||
#include "openmc/distribution_spatial.h" | ||
#include "openmc/random_dist.h" | ||
#include "openmc/source.h" | ||
|
||
namespace openmc { | ||
|
@@ -174,6 +177,65 @@ float exponentialG2(float tau) | |
return num / den; | ||
} | ||
|
||
// Implementation of the Fisher-Yates shuffle algorithm. | ||
// Algorithm adapted from: | ||
// https://en.cppreference.com/w/cpp/algorithm/random_shuffle#Version_3 | ||
void fisher_yates_shuffle(vector<int>& arr, uint64_t* seed) | ||
{ | ||
// Loop over the array from the last element down to the second | ||
for (size_t i = arr.size() - 1; i > 0; --i) { | ||
// Generate a random index in the range [0, i] | ||
size_t j = uniform_int_distribution(0, i, seed); | ||
// Swap arr[i] with arr[j] | ||
std::swap(arr[i], arr[j]); | ||
} | ||
} | ||
|
||
// Function to generate randomized Halton sequence samples | ||
// | ||
// Algorithm adapted from: | ||
// A. B. Owen. A randomized halton algorithm in r. Arxiv, 6 2017. | ||
// URL https://arxiv.org/abs/1706.02808 | ||
vector<vector<float>> rhalton(int N, int dim, uint64_t* seed, int64_t skip = 0) | ||
{ | ||
int b; | ||
double b2r; | ||
vector<double> ans(N); | ||
vector<int> ind(N); | ||
vector<int> primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; | ||
vector<vector<float>> halton(N, vector<float>(dim, 0.0)); | ||
|
||
std::iota(ind.begin(), ind.end(), skip); | ||
|
||
for (int D = 0; D < dim; ++D) { | ||
b = primes[D]; | ||
b2r = 1.0 / b; | ||
vector<int> res(ind); | ||
std::fill(ans.begin(), ans.end(), 0.0); | ||
|
||
while ((1.0 - b2r) < 1.0) { | ||
vector<int> dig(N); | ||
// randomaly permute a sequence from skip to skip+N | ||
vector<int> perm(b); | ||
std::iota(perm.begin(), perm.end(), 0); | ||
fisher_yates_shuffle(perm, seed); | ||
|
||
for (int i = 0; i < N; ++i) { | ||
dig[i] = res[i] % b; | ||
ans[i] += perm[dig[i]] * b2r; | ||
res[i] = (res[i] - dig[i]) / b; | ||
} | ||
b2r /= b; | ||
} | ||
|
||
for (int i = 0; i < N; ++i) { | ||
halton[i][D] = ans[i]; | ||
} | ||
} | ||
|
||
return halton; | ||
} | ||
|
||
//============================================================================== | ||
// RandomRay implementation | ||
//============================================================================== | ||
|
@@ -183,6 +245,7 @@ double RandomRay::distance_inactive_; | |
double RandomRay::distance_active_; | ||
unique_ptr<Source> RandomRay::ray_source_; | ||
RandomRaySourceShape RandomRay::source_shape_ {RandomRaySourceShape::FLAT}; | ||
RandomRaySampleMethod RandomRay::sample_method_ {RandomRaySampleMethod::PRNG}; | ||
|
||
RandomRay::RandomRay() | ||
: angular_flux_(data::mg.num_energy_groups_), | ||
|
@@ -509,14 +572,19 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) | |
// set identifier for particle | ||
id() = simulation::work_index[mpi::rank] + ray_id; | ||
|
||
// set random number seed | ||
int64_t particle_seed = | ||
(simulation::current_batch - 1) * settings::n_particles + id(); | ||
init_particle_seeds(particle_seed, seeds()); | ||
stream() = STREAM_TRACKING; | ||
// generate source site using sample method | ||
SourceSite site; | ||
switch (sample_method_) { | ||
case RandomRaySampleMethod::PRNG: | ||
site = sample_prng(); | ||
break; | ||
case RandomRaySampleMethod::HALTON: | ||
site = sample_halton(); | ||
break; | ||
default: | ||
fatal_error("Unknown sample method for random ray transport."); | ||
} | ||
|
||
// Sample from ray source distribution | ||
SourceSite site {ray_source_->sample(current_seed())}; | ||
site.E = lower_bound_index( | ||
data::mg.rev_energy_bins_.begin(), data::mg.rev_energy_bins_.end(), site.E); | ||
site.E = negroups_ - site.E - 1.; | ||
|
@@ -545,4 +613,53 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) | |
} | ||
} | ||
|
||
SourceSite RandomRay::sample_prng() | ||
{ | ||
// set random number seed | ||
int64_t particle_seed = | ||
(simulation::current_batch - 1) * settings::n_particles + id(); | ||
init_particle_seeds(particle_seed, seeds()); | ||
stream() = STREAM_TRACKING; | ||
|
||
// Sample from ray source distribution | ||
SourceSite site {ray_source_->sample(current_seed())}; | ||
|
||
return site; | ||
} | ||
|
||
SourceSite RandomRay::sample_halton() | ||
{ | ||
SourceSite site; | ||
|
||
// Set random number seed | ||
int64_t batch_seed = (simulation::current_batch - 1) * settings::n_particles; | ||
int64_t skip = id(); | ||
init_particle_seeds(batch_seed, seeds()); | ||
stream() = STREAM_TRACKING; | ||
Comment on lines
+634
to
+638
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a similar manner to my comment regarding simplification vs. leaving abstract for the |
||
|
||
// Calculate next samples in LDS | ||
vector<vector<float>> samples = rhalton(1, 5, current_seed(), skip = skip); | ||
|
||
// Get spatial box of ray_source_ | ||
SpatialBox* sb = dynamic_cast<SpatialBox*>( | ||
dynamic_cast<IndependentSource*>(RandomRay::ray_source_.get())->space()); | ||
|
||
// Sample spatial distribution | ||
Position xi {samples[0][0], samples[0][1], samples[0][2]}; | ||
Position shift {1e-9, 1e-9, 1e-9}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wherever possible, "magic numbers" in OpenMC are defined in // Used for surface current tallies
constexpr double TINY_BIT {1e-8};
// User for precision in geometry
constexpr double FP_PRECISION {1e-14};
constexpr double FP_REL_PRECISION {1e-5};
constexpr double FP_COINCIDENT {1e-12}; |
||
site.r = (sb->lower_left() + shift) + | ||
xi * ((sb->upper_right() - shift) - (sb->lower_left() + shift)); | ||
|
||
// Sample Polar cosine and azimuthal angles | ||
float mu = 2.0 * samples[0][3] - 1.0; | ||
float azi = 2.0 * PI * samples[0][4]; | ||
// Convert to Cartesian coordinates | ||
float c = std::sqrt(1.0 - mu * mu); | ||
site.u.x = mu; | ||
site.u.y = std::cos(azi) * c; | ||
site.u.z = std::sin(azi) * c; | ||
|
||
return site; | ||
} | ||
|
||
} // namespace openmc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that the particle positions and directions are stored in FP64, would it make sense to allow the sampling to also be 64-bit?
This is a nice and high-level/abstract implementation of the sampling scheme, though the "abstractness" is a double edged sword as it requires a significant amount of dynamic memory allocation with all the usage of
vector
. My guess is that this causes most of the added expense in the sampling scheme as compared to PRNG sampling. With that in mind, as the function is only ever called in OpenMC as:we could in theory make a simpler and faster function that only generates a single 5 dimensional sample, thus eliminating the need for several vectors (or replacement with statically sized
std::array
). However, it's possible other areas in OpenMC may want to use Halton sampling in the future, in which case perhaps it is worthwhile to keep things abstract? At the very least, it seems like we can limit things to a single sample at a time (eliminating theN
argument and thus several dynamically sized vectors)?