Skip to content

Draft impl of JP06 pseudo-uniform sieve #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 48 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5c7b5f4
Add a "rayon" feature that enables a parallelized prime generation me…
dvdplm Oct 21, 2024
52024e3
Clippy
dvdplm Oct 21, 2024
f0e5edf
Drop the threadpool when recursing
dvdplm Nov 4, 2024
e4b1adb
Add parallel safe prime generation
dvdplm Nov 5, 2024
7491a06
Let users pick the thread count
dvdplm Nov 5, 2024
ca8b74d
Fix tests
dvdplm Nov 5, 2024
9dac4e9
Document available features and provide an example
dvdplm Nov 5, 2024
be85f48
No need for num_cpus
dvdplm Nov 5, 2024
2be5919
CHANGELOG entry
dvdplm Nov 5, 2024
5929045
Fix doctest in README
dvdplm Nov 5, 2024
2934030
Criterion benches for the rayon feature
dvdplm Nov 5, 2024
61a860b
Add parallel versions of generate_prime and generate_safe_prime
dvdplm Nov 5, 2024
d994c51
Rename the feature to "multicore" and other review feedback
dvdplm Nov 6, 2024
0e2e7e6
Missed feature gated NonZeroU32s
dvdplm Nov 7, 2024
b273519
Merge branch 'master' into dp-rayon-prime-gen
dvdplm Nov 7, 2024
70cda7d
working draft
dvdplm Nov 11, 2024
09d04b8
working draft
dvdplm Nov 11, 2024
c11db6a
draft of JP06 pseudo-uniform prime generation
dvdplm Nov 11, 2024
ce62bf5
Merge branch 'master' into dp-uniform-sieve
dvdplm Nov 11, 2024
db21ed3
Minor cleanup
dvdplm Nov 11, 2024
7f5697c
Add test-log crate for easy tracing setup in tests
dvdplm Nov 13, 2024
b05bf23
Add benches for uniform prime gen to compare with "presets"
dvdplm Nov 13, 2024
c5735a5
Let tests use std
dvdplm Nov 13, 2024
23d3cb2
Proper bounds check (but why is random_bits so much faster?)
dvdplm Nov 13, 2024
a4b4546
Use a bigger l for U128 (4)
dvdplm Nov 13, 2024
ddc4730
Let users pass the CSPRNG
dvdplm Nov 13, 2024
82aa804
Add back generate_prime + some docs + fix tests
dvdplm Nov 13, 2024
3ec85d4
Statistical test of prime distribution (as an example)
dvdplm Nov 19, 2024
7c649d3
cleanup
dvdplm Nov 19, 2024
a24faf8
Bench the full suite of bigint sizes
dvdplm Nov 19, 2024
51dfb62
cleanup
dvdplm Nov 19, 2024
f73a993
Use faster bounded random number trick for unit generation too.
dvdplm Nov 19, 2024
975b7ff
Cleanup
dvdplm Nov 19, 2024
15beef5
Add stats helpers to own module
dvdplm Nov 22, 2024
da9ec5e
Move rand dep to dev-deps
dvdplm Nov 22, 2024
01066f9
Add distribution quality tests
dvdplm Nov 22, 2024
3c59100
Better name
dvdplm Nov 22, 2024
4215eba
Finish tweaking the constants
dvdplm Nov 25, 2024
c5d0a99
Clippy: don't use a mod.rs
dvdplm Nov 25, 2024
f99e049
Clippy suggested
dvdplm Nov 25, 2024
321e7fd
Cleanup and docs
dvdplm Nov 25, 2024
40fa8f1
Cleanup
dvdplm Nov 25, 2024
55deec8
Move UniformSieve to hazmat
dvdplm Nov 25, 2024
0cf32e8
whitespace
dvdplm Nov 25, 2024
5ce9bf9
Fix wasm build
dvdplm Nov 25, 2024
66b37ae
Fix tests
dvdplm Nov 25, 2024
a0059d0
Don't assume OsRng is available
dvdplm Nov 25, 2024
95ac315
Add TODO about making calculate constants const
dvdplm Dec 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ openssl = { version = "0.10.39", optional = true, features = ["vendored"] }
rug = { version = "1.26", default-features = false, features = ["integer"], optional = true }
glass_pumpkin = { version = "1", optional = true }
rayon = { version = "1", optional = true }
tracing = { version = "0.1", default-features = false }

[dev-dependencies]
rand = "0.8"
# need `crypto-bigint` with `alloc` to test `BoxedUint`
crypto-bigint = { version = "0.6.0-rc.6", default-features = false, features = ["alloc"] }
rand_chacha = "0.3"
Expand All @@ -30,6 +32,8 @@ num-integer = "0.1"
proptest = "1"
num-prime = "0.4.3"
num_cpus = "1.16"
test-log = { version = "0.2.16", default-features = false, features = ["trace", "color"] }
statrs = "0.17.1"

[features]
default = ["default-rng"]
Expand All @@ -49,3 +53,10 @@ rustdoc-args = ["--cfg", "docsrs"]
[[bench]]
name = "bench"
harness = false

[profile.bench]
lto = true
codegen-units = 1

[[example]]
name = "distribution"
47 changes: 46 additions & 1 deletion benches/bench.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::num::NonZero;

use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use crypto_bigint::{nlimbs, BoxedUint, Integer, Odd, RandomBits, Uint, U1024, U128, U256};
use crypto_bigint::{nlimbs, BoxedUint, Integer, Odd, RandomBits, Uint, U1024, U128, U2048, U256, U512};
use rand_chacha::ChaCha8Rng;
use rand_core::{CryptoRngCore, OsRng, SeedableRng};

Expand Down Expand Up @@ -39,6 +39,35 @@ fn make_presieved_num<const L: usize>(rng: &mut impl CryptoRngCore) -> Odd<Uint<
Odd::new(sieve.next().unwrap()).unwrap()
}

fn bench_uniform_sieve(c: &mut Criterion) {
use crypto_primes::hazmat::UniformSieve;
let mut group = c.benchmark_group("Uniform sieve");

let mut rng = make_rng();
group.bench_function("(U128) Random prime", |b| {
b.iter(|| U128::generate_prime_with_rng(&mut rng));
});

let mut rng = make_rng();
group.bench_function("(U256) Random prime", |b| {
b.iter(|| U256::generate_prime_with_rng(&mut rng));
});

let mut rng = make_rng();
group.bench_function("(U512) Random prime", |b| {
b.iter(|| U256::generate_prime_with_rng(&mut rng));
});

let mut rng = make_rng();
group.bench_function("(U1024) Random prime", |b| {
b.iter(|| U1024::generate_prime_with_rng(&mut rng));
});

let mut rng = make_rng();
group.bench_function("(U2048) Random prime", |b| {
b.iter(|| U2048::generate_prime_with_rng(&mut rng));
});
}
fn bench_sieve(c: &mut Criterion) {
let mut group = c.benchmark_group("Sieve");

Expand Down Expand Up @@ -227,11 +256,26 @@ fn bench_presets(c: &mut Criterion) {
b.iter(|| generate_prime_with_rng::<U128>(&mut rng, 128))
});

let mut rng = make_rng();
group.bench_function("(U256) Random prime", |b| {
b.iter(|| generate_prime_with_rng::<U256>(&mut rng, 128))
});

let mut rng = make_rng();
group.bench_function("(U512) Random prime", |b| {
b.iter(|| generate_prime_with_rng::<U512>(&mut rng, 128))
});

let mut rng = make_rng();
group.bench_function("(U1024) Random prime", |b| {
b.iter(|| generate_prime_with_rng::<U1024>(&mut rng, 1024))
});

let mut rng = make_rng();
group.bench_function("(U2048) Random prime", |b| {
b.iter(|| generate_prime_with_rng::<U2048>(&mut rng, 1024))
});

let mut rng = make_rng();
group.bench_function("(U128) Random safe prime", |b| {
b.iter(|| generate_safe_prime_with_rng::<U128>(&mut rng, 128))
Expand Down Expand Up @@ -523,6 +567,7 @@ fn bench_glass_pumpkin(_c: &mut Criterion) {}

criterion_group!(
benches,
bench_uniform_sieve,
bench_sieve,
bench_miller_rabin,
bench_lucas,
Expand Down
90 changes: 90 additions & 0 deletions examples/distribution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use crypto_bigint::{FixedInteger, NonZero, RandomBits, RandomMod};
use crypto_primes::hazmat::UniformSieve;
use statrs::distribution::{ChiSquared, ContinuousCDF};

/// Chi-squared statistical test for some collection of primes.

fn main() {
let sample_count = 5000;
let num_intervals = 20;
type T = crypto_bigint::U512;

let end = T::MAX;
let (counts, elapsed) = distribution::<T>(sample_count, num_intervals, end);
let expected_count = sample_count as f64 / num_intervals as f64;

// Chi-Square test for uniformity
let (chi_square_stat, p_value) = chi_square_test(&counts, expected_count);

println!("Prime Counts in Each Interval: {:?}", counts);
println!("Collected {sample_count} primes in {elapsed:?}");
println!("Expected Count per Interval: {:.2}", expected_count);
println!("Chi-Square Statistic: {:.2}", chi_square_stat);
println!("P-Value: {:.4}", p_value);

if p_value < 0.05 {
println!("The prime distribution significantly deviates from uniformity.");
} else {
println!("The prime distribution is close to uniform.");
}
}

// Divide interval and count primes in each subinterval. Returns stats and the time elapsed.
fn distribution<T>(num_primes: usize, intervals: usize, end: T) -> (Vec<u64>, std::time::Duration)
where
T: FixedInteger + RandomBits + RandomMod,
T: UniformSieve<T>,
{
let now = std::time::Instant::now();
let primes: Vec<T> = collect_primes(num_primes);
let elapsed = now.elapsed();
println!("primes={:?}", &primes[0..5]);
let start = T::ZERO;
assert!(end > start);
let interval_width = (end - start).div(NonZero::new(T::from(intervals as u64)).unwrap());
let interval_width_nz = NonZero::new(interval_width).unwrap();
println!("intervals_width={interval_width:?}");
// Initialize counters for each interval
let mut counts = vec![0; intervals];

// Count primes in each interval
for &prime in &primes {
let interval = (prime - start).div(interval_width_nz).as_ref()[0].0 as usize;
if interval < intervals {
counts[interval] += 1;
} else {
println!("interval={interval} is outside the intervals count {intervals}");
}
}

(counts, elapsed)
}

// Collect primes
fn collect_primes<T>(num_primes: usize) -> Vec<T>
where
T: FixedInteger + RandomBits + RandomMod,
T: UniformSieve<T>,
{
(0..num_primes).fold(Vec::with_capacity(num_primes), |mut acc, _| {
acc.push(T::generate_prime());
acc
})
}

// Calculate Chi-Square statistic and p-value
fn chi_square_test(counts: &[u64], expected: f64) -> (f64, f64) {
let chi_square_stat: f64 = counts
.iter()
.map(|&count| {
let diff = count as f64 - expected;
diff * diff / expected
})
.sum();

// Calculate p-value
let chi_square_dist = ChiSquared::new((counts.len() - 1) as f64).unwrap();
let p_value = 1.0 - chi_square_dist.cdf(chi_square_stat);

(chi_square_stat, p_value)
}
5 changes: 4 additions & 1 deletion src/hazmat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ mod gcd;
mod jacobi;
mod lucas;
mod miller_rabin;
mod precomputed;
pub(crate) mod precomputed;
#[cfg(test)]
pub(crate) mod primes;
#[cfg(test)]
pub(crate) mod pseudoprimes;
mod sieve;
mod uniform_sieve;

pub use lucas::{lucas_test, AStarBase, BruteForceBase, LucasBase, LucasCheck, SelfridgeBase};
pub use miller_rabin::MillerRabin;

pub use sieve::{random_odd_integer, Sieve};
pub use uniform_sieve::UniformSieve;

/// Possible results of various primality tests.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down
2 changes: 1 addition & 1 deletion src/hazmat/gcd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub(crate) fn gcd_vartime<T: Integer>(n: &T, m: NonZero<Word>) -> Word {
// As GCD is commutative `gcd(n, m) = gcd(m, n)` those identities still apply if the operands are swapped.
//
// [1]: https://en.wikipedia.org/wiki/Binary_GCD_algorithm
fn binary_gcd(mut n: Word, mut m: Word) -> Word {
pub(crate) fn binary_gcd(mut n: Word, mut m: Word) -> Word {
// Using identities 2 and 3:
// gcd(2ⁱn, 2ʲm) = 2ᵏ gcd(n, m) with n, m odd and k = min(i, j)
// 2ᵏ is the greatest power of two that divides both 2ⁱn and 2ʲm
Expand Down
Loading