tandem rng tandem-sycl GitHub

Tests

pixi run test                         # AdaptiveCpp 25.10, OpenMP backend on the host CPU
pixi run -e dpcpp test-dpcpp          # DPC++ 2026.1 with Intel's OpenCL CPU runtime, Linux
pixi run -e cuda test-cuda            # AdaptiveCpp 25.10 for CUDA 12.9, Linux with an NVIDIA GPU

Suite

tests/test_tandem.cpp runs every check on the default SYCL device: 3366 checks on the AdaptiveCpp CPU device, 3942 on the A100, which also runs the normal fills' shuffle variant. It checks every vector of the specification and every case of its conformance files (see below), hashes fills with both kernels and in-kernel scalar draws against the stream SHA-256 sums of hashes.json, compares fills against in-kernel draws at random keys, chunk lengths, positions, lengths and output alignments, checks fills of every type (bool, 8 to 64-bit, float, double) against the bits of the u32 stream, checks that fills split at arbitrary points with host draws between them continue one stream, and checks mixed-width draws, random access and derived keys in kernels. In-kernel bounded draws and double normals equal the host's, in-kernel float normals agree with them to the tolerance in design. Bounded fills are checked against Appendix A written out on host generators, against the sequential urand(range) calls. Double normal fills of both kernels must equal the scalar normal() calls bit for bit at random keys, chunk lengths, positions, lengths and alignments, with misses and tail values among them. A miss list too short for the fill must make the second kernel walk the fill and give the same values. Fills from every first draw modulo 16, at K = 1 and 32, on and 8 bytes off 16-byte addresses, must equal them too, with each way of passing values between lanes. A fill cut at an odd element, at its first miss and just after must equal the whole. Float normal fills of every kernel variant are checked against the scalar normalf2() calls to 16 ulps plus 1e-6, from random positions and counts and from starts at every word offset within a block and several lanes, with K from 1 to 64. Exponential fills of both kernels must equal the scalar exponential() calls bit for bit, and 10^7 samples must match the Exp(1) law in their first four moments and a Kolmogorov-Smirnov test. In-kernel exponentials equal the host's bit for bit. Signed, Float16, sycl::half and complex fills, buffers of rank 1 and 2, and the position after empty fills at odd positions are checked too.

Conformance files

tests/conformance/*.json are byte-identical copies of tandem-spec 2a4bd08 conformance/*.json, read by tests/conformance.hpp. Each item of the spec's conformance/CHECKLIST.md at the same commit has its test:

checklist section test
Fallback by global draw index check_fill_cases on fill_below.json and normal.json, check_shift on the _AT[4], _AT[6] and CROSS_NORMAL[1] pairs
Width from range check_width_from_range: every u32 case of fill_below.json through fill_below into uint64_t, by USM and a buffer, Rng::below on CROSS_BELOW32[3] on the host and in a kernel, ranges 2^32 and 2^32 + 1; the u64 cases through their width-naming kind; check_range0
n = 0 the seven n = 0 cases through the public fills, onto a sentinel
Odd n the CROSS_NORMAL32 cases, values and end
Pair rule for Float32 Box-Muller CROSS_NORMALF, check_shift on CROSS_NORMAL32[1] and [2], check_scalars for normalf()
Weighted choice test_choice: every table of choice.json (whole where pinned, else capacity), every case, check_shift on CROSS_CHOICE[1], check_scalars for choice(), the n = 0 case, rejected weights; m = 1 is the choice single case
Cut fill check_fill_cases cuts every case of the four fill files at 1, 7, 20, 21 and n − 1, Float32 normals at 2, 8, 20 and the largest even element below n; check_scalars
Block and 2^63 position boundaries test_streams, test_dumps, check_complex_straddle, check_random_access, check_position_bounds, which runs every item: Rng holds any 64-bit position and a fill takes any 64-bit length. Uniform, bool, bounded, normal, exponential and choice fills that end exactly at 2^64 throw std::length_error and leave the memory and the position unchanged

The fill cases run on every kernel path: chunk and tile, and for float normals the group kernel with local memory and with shuffles. The Float32 normal dump hash holds for C's polynomials, which the host's normalf2() takes, so that dump runs on the host. hashes.json has no fill here for its UInt128 and Char streams. Choice fills of both kernels also equal the choice() calls at random keys, chunk lengths, positions, lengths and output alignments, and a rank 2 buffer equals the USM fill.

tests/vectors.hpp is generated from the spec repository's vectors.json by tools/gen_vectors.py.

CI

CI runs the tests on the CPU device with AdaptiveCpp on Linux and macOS and with DPC++ on Linux, all with -Wall -Wextra -Werror, and fails when the vector header differs from upstream, a conformance copy differs from tandem-spec 2a4bd08, or the submodule pin leaves tandem-cuda's main. The suite also passes on an NVIDIA A100 with the cuda environment.