tandem rng tandem-fortran GitHub

Tests

pixi run test                       # fpm test
pixi run -e cuda test-cuda          # device fills, on a GPU host
make -f cuda/Makefile test-device   # the kernel module, with nvfortran
make -f target/Makefile test        # gfortran -fopenmp, target regions on the CPU
pixi run -e mpi check-mpi           # 1, 2 and 4 ranks print the hash of a serial run

Suite

test/test_vectors.f90 checks every vector of the specification. test/test_stream.f90 compares fills, scalar draws and random access with the reference stream dumps in test/data. It also checks fills that start inside a row at eleven offsets against one whole fill, alignment after draws of mixed widths, fills of any rank and strided sections, and the elemental forms of at_*, split and sub.

test/test_conformance.f90 reads the specification's conformance fixtures and shows each section of conformance/CHECKLIST.md at tandem-spec b31af72, all bit for bit:

section check
Fallback by global draw index every case of fill_below.json and normal.json, and the shift of CROSS_BELOW32_AT[4], CROSS_BELOW64_AT[6] and CROSS_NORMAL[1] by one element
Width from range the integer kind names the width: int32 and int64 range 1000 fills match CROSS_BELOW32[3] and CROSS_BELOW64[3], and range 0 returns 0 and takes one draw
n = 0 the seven empty cases, end position only
Odd n CROSS_NORMAL32[0] to [4], end align(start, 32) + 64 ceil(n / 2)
Pair rule CROSS_NORMALF, its first 33 values against CROSS_NORMAL32[1], the scalar cos half and its two draws, the shift of CROSS_NORMAL32[2] by one pair
Weighted choice the tables of the vectors.json cases, every case of choice.json, the shift of CROSS_CHOICE[1], scalar draws, m = 1, and weights that build no table
Cut fill every fill case cut at elements 1, 7, 20, 21 and n - 1, Float32 normals at 2, 8, 20 and the largest even element below n, and n scalar draws for the bounded, Float64 normal, exponential and choice cases
Block and 2^63 boundaries the SHA-256 of every stream of hashes.json from the fills, the FNV-1a of every long output, complex draws across a block, set_position at 2^63 - 1, 2^63 and 2^64 - 1 as int64 bit patterns, a 64-bit draw at 2^63 - 1, and advance_to to an end past 2^63

The checklist's last item, a fill whose end reaches 2^64, is skipped. int64 positions express it, but tandem.c, which the module binds, does not refuse such a fill. The module instead refuses starts at or past 2^63 in set_position, so a fill end reaches 2^64 only after 2^63 bits of draws. tandem_from_key and advance_to stay unchecked, as tandem_from_key of tandem-c and Rng::advance_to of tandem-cuda are.

test/test_sampling.f90 checks that real64 normal fills equal the next_normal64 calls and real32 fills the pairs from an unaligned start, that an empty real64 fill aligns the position, and that rank 3 bounded, normal and choice fills equal rank 1 fills. Bounded fills of 1000 elements cut at arbitrary elements equal the whole fill at an unaligned start, rejected draws included, and the OpenMP target and CUDA tests check the same cut.

cuda/test_cuda.f90 checks device fills against the vectors and the dumps, and against CPU fills of the same generator for K from 1 to 256, six start positions, five lengths and four output offsets, which runs both kernels of tandem.cuh and its aligned and unaligned stores. It runs the narrow, complex, bounded and normal fills against CPU fills of the same generator at several chunk lengths, starts, lengths and output offsets, real64 normals bit for bit at lengths that take one kernel and two. Exponential and choice fills equal CPU fills bit for bit at three chunk lengths, three starts and several lengths. Every case of fill_below.json, normal.json, exponential.json and choice.json runs on the device, whole and cut, with the second piece right after the first in device memory: bit for bit with the end position, except that real32 normals match to the fixtures' tolerance. The device fills of ten stream types match the SHA-256 of hashes.json, and the real64 normal and the exponential fills of 10^6 elements from five starts match its FNV-1a. It also interleaves CPU draws and device fills. cuda/test_device.cuf runs the kernel module on the GPU against the vectors and against the C library's scalar draws, at four chunk lengths, three keys and ten start positions, with mixed widths, splits and subs, and checks level 1 fills, choice included, into CUDA Fortran device arrays. It also compares device below and normals with the host at bounds that reject a quarter of the draws, device at_* with the host's, and fork children with the host's. 10^6 device real64 normals from each of the five starts of tandem-c's tests/test_normal_bits.c, about 21000 misses and 270 tail values, must equal the C fill bit for bit, and so must the host compilation of the same procedures.

Fixtures

test/vectors.f90 is generated from the spec repository's vectors.json by tools/gen_vectors.py. The reference stream dumps in test/data are copied from tandem-c, and tools/sync_c.sh refreshes them from the pinned commit. test/conformance/*.json are byte-identical copies of tandem-spec 2a4bd08 conformance/, read at run time by test/conformance.f90, which also holds the SHA-256 and FNV-1a of hashes.json. The OpenMP target test runs every case of fill_below.json and the stream hashes of its four types. cuda/tandem_zig_tables.f90 comes from the spec's ziggurat tables through tools/gen_zig_tables.py.

CI