{"id":51290191,"url":"https://github.com/vigna/modlin-rs","last_synced_at":"2026-06-30T09:32:00.985Z","repository":{"id":366648721,"uuid":"1271062667","full_name":"vigna/modlin-rs","owner":"vigna","description":"Modular rank and linear-complexity tests for pseudorandom number generators","archived":false,"fork":false,"pushed_at":"2026-06-22T17:49:08.000Z","size":143,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-22T19:25:22.114Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vigna.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-Apache-2.0","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-16T09:49:03.000Z","updated_at":"2026-06-22T17:49:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vigna/modlin-rs","commit_stats":null,"previous_names":["vigna/modlin-rs"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/vigna/modlin-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vigna%2Fmodlin-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vigna%2Fmodlin-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vigna%2Fmodlin-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vigna%2Fmodlin-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vigna","download_url":"https://codeload.github.com/vigna/modlin-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vigna%2Fmodlin-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34961544,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-30T02:00:05.919Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-30T09:32:00.188Z","updated_at":"2026-06-30T09:32:00.978Z","avatar_url":"https://github.com/vigna.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Modular rank and linear-complexity tests for pseudorandom number generators\n\nThis crate implements two empirical tests for pseudorandom number generators\n(PRNGs), the _modular rank test_ and the _modular linear-complexity test_. These\ntests are generalizations of the standard binary-rank and (binary)\nlinear-complexity tests from 𝐅₂ to an arbitrary finite field.\n\nWhile the _binary_ rank and linear-complexity tests are widely used in the\nliterature (e.g., [dieharder], the [NIST suite], [TestU01], [PractRand]\nimplement them), they can only detect linearity in generators that are linear\nover 𝐅₂. The modular rank and linear-complexity tests can find bias in\ngenerators that are linear over any finite field. This includes linear\ncongruential generators, single multiple-recursive recurrences, and matrix\ngenerators such as [MIXMAX]; indeed, implementing tests detecting the\nstatistical defects of [MIXMAX] was the original motivation for this crate.\n\nAs in the binary case, the two tests are different measures of the linear\ncomplexity of the output of the generator. The rank test measure it by the rank\nof a matrix of outputs, while the linear-complexity test measures it directly\nfrom the output stream using the Berlekamp–Massey algorithm. The first is more\nexpensive, but robust, as it can find bias even in presence of moderate\nscrambling, whereas the second is cheaper but fragile, as it requires the output\nto obey a single linear recurrence of low degree.\n\n## Testing linearity over 𝐅*ₚ*\n\nBoth tests are implemented for fields 𝐅*ₚ* of prime order _p_, with _p_ \u003c\n2⁶³. A generator that is linear over 𝐅*ₚ* has a finite, usually small linear\ncomplexity _L_; a generator with no such structure does not. For a generator\nthat emits _b_ values per step from a _k_-dimensional state _L_ ≤ _b_ · _k_; for\nexample, [MIXMAX]-_N_ emits _N_ − 1 values per step, giving _L_ = _N_(_N_ − 1).\n\nThe _modular rank test_ reads *n*² successive outputs into an _n_ × _n_ matrix\nover 𝐅*ₚ*. Every length-_n_ window of an _L_-linear stream is fixed by the\nrecurrence, so all the rows lie in a subspace of dimension at most _L_, and the\nmatrix has rank at most _L_. A side _n_ \u003e _L_ then forces the matrix to be _rank\ndeficient_ (in practice, the deficiency already appears at far smaller sides). A\ngenerator with no linear structure instead yields full-rank matrices: a uniform\nrandom _n_ × _n_ matrix over 𝐅*ₚ* has corank (i.e., side minus rank) _d_\nwith probability ≈ *p*⁻ᵈ², so it is singular only with probability ≈ 1/_p_. The\ntest reports, for each matrix, its own _p_-value.\n\nThe _modular linear-complexity test_ reads _L_ off the stream directly, with the\nBerlekamp–Massey algorithm, which returns the order of the shortest linear\nrecurrence a sequence obeys. A length-_n_ stream from an _L_-linear generator\nhas complexity _L_ once _n_ passes about 2*L*, far below the expected ⌈_n_/2⌉.\nAlso in this case the test reports, for each sequence, its own _p_-value.\n\nNote that for large fields deviation from the typical case is astronomically rare,\nso a single anomalous matrix or sequence already has a per-sample\n_p_-value very close to 0.\n\n## Build and run\n\nThe generator is selected at build time via a Cargo feature. Exactly one of\n`-R`/`-L` selects the test. For example, we can find bias in the 17-dimensional\n[MIXMAX] generator from [CERN's ROOT], for which _p_ = 2⁶¹ − 1, using a 500×500\nmatrix in milliseconds:\n\n```text\ncargo run -r -F mixmax17 -- -R 500 -p 2305843009213693951 -S 1\nGenerator: MIXMAX (TRandomMixMax17, N=17)\nSeed: 0x0000000000000001\nRunning a modular rank test using 1.907 MiB of RAM: 1 500×500 matrix over the field of size 2305843009213693951\n2026-06-21 21:15:42.580 6ms INFO [ThreadId(1)] modlin - Generating matrix entries...\n2026-06-21 21:15:42.581 7ms INFO [ThreadId(1)] modlin - Completed.\n2026-06-21 21:15:42.581 8ms INFO [ThreadId(1)] modlin - Elapsed: 1ms [250,000 outputs, 223955739.18 outputs/s, 4.47 ns/output]; res/vir/avail/free/total mem 8.96MB/420.73GB/32.78GB/1.93GB/68.72GB\n2026-06-21 21:15:42.581 8ms INFO [ThreadId(1)] modlin - Matrix 1/1: ranking (blocked Gaussian elimination over Fₚ)...\n2026-06-21 21:15:42.590 17ms INFO [ThreadId(1)] modlin - Completed.\n2026-06-21 21:15:42.590 17ms INFO [ThreadId(1)] modlin - Elapsed: 8ms [500 columns, 55700.74 columns/s, 17.95 μs/column]; res/vir/avail/free/total mem 9.91MB/420.79GB/32.78GB/1.93GB/68.72GB\nMatrix 1/1\tcorank=432\tp=1e-307\n```\n\nThe corank should be zero, and the _p_-value should be 1, for a generator with\nno linear dependencies.\n\nFinding bias using the Berlekamp–Massey algorithm to measure the linear complexity of a\nsequence of 1000 elements is even faster:\n\n```text\ncargo run -r -F mixmax17 -- -L 1000 -p 2305843009213693951 -S 1\nGenerator: MIXMAX (TRandomMixMax17, N=17)\nSeed: 0x0000000000000001\nRunning a modular linear-complexity test using 0.031 MiB of RAM: 1 sequence of length 1000 over the field of size 2305843009213693951\n2026-06-21 21:15:50.127 7ms INFO [ThreadId(1)] modlin - Sequence 1/1: Berlekamp–Massey over Fₚ...\n2026-06-21 21:15:50.128 8ms INFO [ThreadId(1)] modlin - Completed.\n2026-06-21 21:15:50.129 8ms INFO [ThreadId(1)] modlin - Elapsed: 1ms [1,000 steps, 854335.75 steps/s, 1.17 μs/step]; res/vir/avail/free/total mem 7.03MB/420.73GB/32.76GB/1.94GB/68.72GB\nSequence 1/1\tlinear complexity=272\tp=1e-307\n```\n\nThe linear complexity here should be approximately 500, and the _p_-value should\nbe 1, for a generator with no linear dependencies.\n\nThe same test finds bias on the largest provided [MIXMAX] generator in [CERN's\nROOT] (256-dimensional) in less than a minute:\n\n```text\ncargo run -r -F mixmax256 -- -L 200000 -p 2305843009213693951 -S 1\nGenerator: MIXMAX (TRandomMixMax256, N=256, skip=2)\nSeed: 0x0000000000000001\nRunning a modular linear-complexity test using 6.104 MiB of RAM: 1 sequence of length 200000 over the field of size 2305843009213693951\n2026-06-21 21:16:06.814 9ms INFO [ThreadId(1)] modlin - Sequence 1/1: Berlekamp–Massey over Fₚ...\n2026-06-21 21:16:16.814 10s9ms INFO [ThreadId(1)] modlin - 89,274 steps, 10s, 8927.06 steps/s, 112.02 μs/step; 44.64% done, 12s to end; res/vir/avail/free/total mem 13.43MB/420.73GB/32.80GB/1.91GB/68.72GB\n[...]\n2026-06-21 21:16:40.334 33s529ms INFO [ThreadId(1)] modlin - Completed.\n2026-06-21 21:16:40.335 33s529ms INFO [ThreadId(1)] modlin - Elapsed: 33s [200,000 steps, 5966.55 steps/s, 167.60 μs/step]; res/vir/avail/free/total mem 13.52MB/420.74GB/32.80GB/1.91GB/68.72GB\nSequence 1/1\tlinear complexity=65280\tp=1e-307\n```\n\nRunning a modular rank test capable of detecting bias on the same generator\nrequires instead a couple of hours, albeit the time depends on the amount of\ncores, as the Gaussian elimination is parallelized. You can customize the amount\nof parallism with the environment variable `RAYON_NUM_THREADS`.\n\nRunning the same test on [`xoroshiro128++`], a generator without linear\ndependencies will find no bias for any _p_:\n\n```text\ncargo run -r -F xoroshiro128pp -- -R 1000 -p 2\nGenerator: xoroshiro128++\nSeed: 0x0000000000000000\nRunning a modular rank test using 7.629 MiB of RAM: 1 1000×1000 matrix over the field of size 2\n2026-06-21 21:15:50.337 8ms INFO [ThreadId(1)] modlin - Generating matrix entries...\n2026-06-21 21:15:50.346 17ms INFO [ThreadId(1)] modlin - Completed.\n2026-06-21 21:15:50.346 17ms INFO [ThreadId(1)] modlin - Elapsed: 8ms [1,000,000 outputs, 118677923.19 outputs/s, 8.43 ns/output]; res/vir/avail/free/total mem 14.88MB/420.73GB/32.76GB/1.93GB/68.72GB\n2026-06-21 21:15:50.346 17ms INFO [ThreadId(1)] modlin - Matrix 1/1: ranking (blocked Gaussian elimination over Fₚ)...\n2026-06-21 21:15:50.454 125ms INFO [ThreadId(1)] modlin - Completed.\n2026-06-21 21:15:50.455 125ms INFO [ThreadId(1)] modlin - Elapsed: 108ms [1,000 columns, 9252.37 columns/s, 108.08 μs/column]; res/vir/avail/free/total mem 16.30MB/420.79GB/32.76GB/1.93GB/68.72GB\nMatrix 1/1\tcorank=0\tp=1\n\nGenerator: xoroshiro128++\nSeed: 0x0000000000000000\nRunning a modular linear-complexity test using 0.031 MiB of RAM: 1 sequence of length 1000 over the field of size 2\n2026-06-21 21:15:50.668 6ms INFO [ThreadId(1)] modlin - Sequence 1/1: Berlekamp–Massey over Fₚ...\n2026-06-21 21:15:50.669 8ms INFO [ThreadId(1)] modlin - Completed.\n2026-06-21 21:15:50.669 8ms INFO [ThreadId(1)] modlin - Elapsed: 1ms [1,000 steps, 798057.85 steps/s, 1.25 μs/step]; res/vir/avail/free/total mem 6.86MB/420.73GB/32.76GB/1.93GB/68.72GB\nSequence 1/1\tlinear complexity=503\tp=0.9947916666666666\n\ncargo run -r -F xoroshiro128pp -- -R 1000 -p 2305843009213693951\nGenerator: xoroshiro128++\nSeed: 0x0000000000000000\nRunning a modular rank test using 7.629 MiB of RAM: 1 1000×1000 matrix over the field of size 2305843009213693951\n2026-06-21 21:15:59.086 9ms INFO [ThreadId(1)] modlin - Generating matrix entries...\n2026-06-21 21:15:59.089 12ms INFO [ThreadId(1)] modlin - Completed.\n2026-06-21 21:15:59.089 12ms INFO [ThreadId(1)] modlin - Elapsed: 3ms [1,000,000 outputs, 305429031.58 outputs/s, 3.27 ns/output]; res/vir/avail/free/total mem 14.22MB/420.59GB/32.74GB/1.93GB/68.72GB\n2026-06-21 21:15:59.089 12ms INFO [ThreadId(1)] modlin - Matrix 1/1: ranking (blocked Gaussian elimination over Fₚ)...\n2026-06-21 21:15:59.225 149ms INFO [ThreadId(1)] modlin - Completed.\n2026-06-21 21:15:59.226 149ms INFO [ThreadId(1)] modlin - Elapsed: 136ms [1,000 columns, 7331.94 columns/s, 136.39 μs/column]; res/vir/avail/free/total mem 15.58MB/420.63GB/32.74GB/1.93GB/68.72GB\nMatrix 1/1\tcorank=0\tp=1\n\ncargo run -r -F xoroshiro128pp -- -L 10000 -p 2305843009213693951\nGenerator: xoroshiro128++\nSeed: 0x0000000000000000\nRunning a modular linear-complexity test using 0.305 MiB of RAM: 1 sequence of length 10000 over the field of size 2305843009213693951\n2026-06-21 21:15:59.449 9ms INFO [ThreadId(1)] modlin - Sequence 1/1: Berlekamp–Massey over Fₚ...\n2026-06-21 21:15:59.579 138ms INFO [ThreadId(1)] modlin - Completed.\n2026-06-21 21:15:59.579 138ms INFO [ThreadId(1)] modlin - Elapsed: 129ms [10,000 steps, 77110.19 steps/s, 12.97 μs/step]; res/vir/avail/free/total mem 8.01MB/420.88GB/32.78GB/1.97GB/68.72GB\nSequence 1/1\tlinear complexity=5000\tp=1\n```\n\nNote that _p_-values are _one-sided_: a _p_-value near 0 thus flags an anomaly,\nwhereas a _p_-value near 1 is just the generic case—not a failure.\n\nYou can also repeat the test multiple times: the test is simply run again on\ndisjoint, contiguous stretches of the orbit, printing one _p_-value per matrix\n(or sequence), and you can decide how to combine them (e.g., a simple Bonferroni\ncorrection, or a full-scale χ² test). For large _p_, however, the probability of\na deficient matrix under the randomness hypothesis is so low that a single test\nis normally enough to rule out the randomness hypothesis (as above).\n\nThere is some progress logging during the computation, and you can adjust the\nfrequency with a command-line option or the logging level using the `RUST_LOG`\nenvironment variable.\n\n## Adding your own generator\n\nTo add a new generator, add a feature in `Cargo.toml` and a corresponding\nimplementation in the [`prng`] module.\n\n[dieharder]: https://webhome.phy.duke.edu/~rgb/General/dieharder.php\n[NIST suite]: https://csrc.nist.gov/projects/random-bit-generation/documentation-and-software\n[TestU01]: https://dl.acm.org/doi/10.1145/1268776.1268777\n[PractRand]: https://pracrand.sourceforge.net/\n[MIXMAX]: https://doi.org/10.1016/j.cpc.2015.06.003\n[`prng`]: https://docs.rs/modlin/latest/modlin/prng/index.html\n[CERN's ROOT]: https://root.cern/doc/v628/classROOT_1_1Math_1_1MixMaxEngine.html\n[`xoroshiro128++`]: https://prng.di.unimi.it/xoroshiro128plusplus.c\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvigna%2Fmodlin-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvigna%2Fmodlin-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvigna%2Fmodlin-rs/lists"}