MooseRandomPerturbation
Overview
MooseRandomPerturbation generates a keyed pseudo-random permutation of the integers [0, n) using a balanced Feistel network. Given the same seed and n, the mapping is fully deterministic and bijective: every input in [0, n) maps to a unique output in [0, n), and different seeds produce statistically independent permutations. This makes it useful for shuffling a fixed-size index set (e.g., sample or row indices) reproducibly without materializing the full permutation in memory.
* Generates a keyed pseudo-random permutation of the integers [0, n) using a
* balanced Feistel network. Given the same seed and n, the mapping is fully
* deterministic and bijective: every input in [0, n) maps to a unique output
* in [0, n). Different seeds produce statistically independent permutations.
*
* Because n need not be a power of two, the Feistel network operates on a
* padded domain of size 2^(2*half_bits) >= n and uses cycle-walking: if the
* raw output falls outside [0, n) it is re-applied until a valid index is
* reached. This guarantees termination because the padded permutation is
* itself a bijection and n > 0 ensures at least one valid output exists.
*
* The permutation is also invertible via invert(), which runs the Feistel
* rounds in reverse order.
*/
(framework/include/utils/MooseRandomPerturbation.h)An instance is constructed with a seed, the domain size n, and the number of Feistel rounds to apply (more rounds improve mixing at the cost of throughput):
MooseRandomPerturbation(uint64_t seed, unsigned int n, unsigned int rounds = 8);
The permute and invert methods then map an index to its permuted value and back, such that invert(permute(x)) == x for every x in [0, n):
uint32_t permute(uint32_t x) const;
uint32_t invert(uint32_t y) const;
For example, LatinHypercube constructs one MooseRandomPerturbation per column to shuffle that column's row assignment:
_shufflers.push_back(std::make_unique<MooseRandomPerturbation>(seed, getNumberOfRows()));
(modules/stochastic_tools/src/samplers/LatinHypercubeSampler.C)Recovering from a checkpoint
Because the permutation is fully determined by the seed, n, and round count, dataStore/dataLoad specializations serialize only those three values rather than any per-index mapping, allowing a MooseRandomPerturbation (typically held via std::unique_ptr) to be exactly reconstructed on recover:
dataStore(std::ostream & stream, MooseRandomPerturbation & v, void * context)
{
uint64_t seed = (static_cast<uint64_t>(v._k1) << 32) | v._k0;
dataStore(stream, seed, context);
dataStore(stream, v._n, context);
dataStore(stream, v._rounds, context);
}
template <>
inline void
dataStore(std::ostream & stream, std::unique_ptr<MooseRandomPerturbation> & v, void * context)
{
dataStore(stream, *v, context);
}
template <>
inline void
dataLoad(std::istream & stream, std::unique_ptr<MooseRandomPerturbation> & v, void * context)
{
uint64_t seed;
unsigned int n, rounds;
dataLoad(stream, seed, context);
dataLoad(stream, n, context);
dataLoad(stream, rounds, context);
v = std::make_unique<MooseRandomPerturbation>(seed, n, rounds);
(framework/include/utils/MooseRandomPerturbation.h)