tandem rng tandem-sycl GitHub

API

Use

#include <tandem/sycl.hpp>

sycl::queue q;
tandem::Rng rng(42);                        // 128-bit seed as two halves, default K = 32
double *x = sycl::malloc_device<double>(n, q);
tandem::fill(q, x, n, rng);                 // the spec's Float64 fill
double u = rng.drand();                     // continues the stream after the fill

sycl::buffer<std::complex<float>, 2> z(sycl::range<2>(64, 64));
tandem::fill(q, z, rng);                    // real then imaginary part per value

uint32_t *die = sycl::malloc_device<uint32_t>(n, q);
float *g = sycl::malloc_device<float>(n, q);
auto e1 = tandem::fill_below(q, die, n, rng, 6u);   // uniform on [0, 6)
auto e2 = tandem::fill_normal(q, g, n, rng);        // standard normals
double *t = sycl::malloc_device<double>(n, q);
auto e3 = tandem::fill_exponential(q, t, n, rng);   // standard exponentials

const double w[] = {1, 2, 3, 4};
std::vector<uint64_t> cut(4);
std::vector<uint32_t> alias(4);
tandem::ChoiceTable table;
tandem::choice_build(table, w, 4, cut.data(), alias.data());   // on the host, no draws
uint64_t *dcut = sycl::malloc_device<uint64_t>(4, q);
uint32_t *dalias = sycl::malloc_device<uint32_t>(4, q);
q.copy(cut.data(), dcut, 4);
q.copy(alias.data(), dalias, 4).wait();
uint32_t *k = sycl::malloc_device<uint32_t>(n, q);
tandem::fill_choice(q, k, n, rng, {table.capacity, dcut, dalias, table.m});  // i with prob. w[i] / 10

float *y = sycl::malloc_device<float>(m, q);
q.parallel_for(sycl::range<1>(m), {e1, e2, e3}, [=](sycl::id<1> i) {
    tandem::Rng r = rng.split(i);           // one generator per work item, from the key alone
    y[i] = r.frand() + r.frand();
}).wait();

Reference

Every fill returns a sycl::event and does not wait. The USM forms take an optional std::vector<sycl::event> of dependencies as their last argument. The buffer forms take the buffer's dependencies from the SYCL runtime. Positions move as in tandem-cuda: an empty fill or double fill_normal aligns the position to the type's width, and an empty fill_below, float fill_normal or fill_exponential consumes no draws and leaves the position alone, and an empty fill_choice aligns it to 64 bits. A fill whose end align(p, w) + w n reaches 2^64 throws std::length_error before it launches, writes or moves the position.

Rng draws

bit(), urand(), urand64(), frand(), drand() the specification's Bool, UInt32, UInt64, Float32 and Float64 draws
at_urand(i), at_urand64(i), at_frand(i), at_drand(i) element i of the fill that would start here, without advancing
urand(range), urand64(range), rand(start, end), rand64(start, end), frand(range), drand(start, end), ... bounded draws, uniform by Lemire's multiply and reject
below(range) a bounded draw whose width comes from the range: 32 bits for range <= 2^32, else 64
normal(), normal(mean, sd) the ziggurat of one UInt64 draw
normalf() the cos half of a Box-Muller step from two Float32 draws
normal2(), normalf2() two ziggurat draws, or both halves of the float step, as a pair z0, z1
exponential(), exponentialf() -log(1 - u) of one Float64 or Float32 draw
choice(table) the weighted choice of one UInt64 draw, element 0 of a choice fill
split(i), sub(purpose), fork(children, n) child generators as the specification defines them
key(), position(), set_position(p), chunk_length() transport form; set_position returns false and changes nothing for p at or above 2^63

Signed integers hold the two's complement of the unsigned draw of the same width. A complex value takes two draws, the real and then the imaginary component.