tandem rng tandem-kokkos GitHub

Tests

pixi run test                         # Serial and OpenMP
pixi run -e gcc test-gcc              # the same with GCC 14 on Linux
pixi run -e cuda test-cuda            # Serial, OpenMP and CUDA on a GPU host

Suite

tests/test_tandem.cpp runs every check on each enabled backend: Serial, OpenMP and CUDA. It checks every vector of the specification and every case of its conformance files (see below), hashes fills of every type with every kernel 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 that fills split at arbitrary points with host draws between them continue one stream, and checks mixed-width draws, random access, derived keys and fork positions. Bounded fills are checked against the contract in core.hpp written out on host generators and against the sequential urand(range) calls. Double normal fills must equal the scalar normal() calls bit for bit from random positions, counts and chunk lengths with every kernel and through fill_normal itself. Float normal fills are checked against the normalf2() calls: bit for bit on host backends, to 16 ulps plus 1e-6 on CUDA. Exponential fills must equal the scalar exponential() calls bit for bit with every kernel. Choice fills of every kernel must equal the choice() calls at random keys, chunk lengths, positions, lengths and output alignments. 1e7 draws of each normal and exponential fill have the moments and a Kolmogorov-Smirnov statistic of N(0, 1) and Exp(1). It also checks 8- and 16-bit, signed, Float16, half_t and complex fills against the u32 stream, and Views of rank 0 to 3 in both layouts. Every non-Serial backend must then write the bytes Serial writes.

Conformance files

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

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 on a uint64_t View, Kokkos::rand<Rng, uint64_t>::draw on CROSS_BELOW32[3], ranges 2^32 and 2^32 + 1, the signed and bounds draws; the u64 cases through their width-naming kind; check_range0_and_empty
n = 0 the seven n = 0 cases through the public fills, onto a sentinel; check_range0_and_empty for uniform fills
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

The fill cases run on every path: the public fill and detail::fill_kind with each kernel of the backend, group and chunk on host backends, tile and chunk on CUDA. Every fill writes into a View one element longer whose last element must keep its sentinel. Values are bit for bit, except CUDA Float32 normals, which take the case's tolerance. A Float32 normal fill cut at an odd element drops that piece's last sin half, so it cuts only between pairs. The Float32 normal dump hash holds for C's polynomials, which host fills take and CUDA's __sincosf does not, so that dump runs on host backends. hashes.json has no fill here for its UInt128 and Char streams. check_position_bounds checks the start bounds on the host and in kernels, and that a fill ending past 2^63 moves the generator there, so the next fill continues the stream. It runs uniform, bounded, normal and exponential fills whose end reaches exactly 2^64 on Views of 2^57 or 2^58 elements over one element of memory: each throws std::length_error before it writes, and the memory and the position stay unchanged.

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

CI