Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 30 additions & 0 deletions projects/campaigns/factorial-25k-digits.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[project]
name = "factorial-25k-digit-primes"
description = "Search for factorial primes n!±1 with 25K+ digits"
objective = "record"
form = "factorial"
tags = ["large-prime", "25k-digits", "factorial"]

[target]
target_digits = 25000
range_start = 7000
range_end = 12000

[strategy]
auto_strategy = false

[[strategy.phases]]
name = "factorial-sweep"
description = "n=7000..12000 (n! gives ~23K-40K digits)"
search_params = { search_type = "factorial", start = 7000, end = 12000 }
block_size = 100
completion = "all_blocks_done"

[infrastructure]
min_ram_gb = 16
min_cores = 8
preferred_tools = ["pfgw"]

[budget]
max_cost_usd = 100.0
cloud_rate_usd_per_core_hour = 0.04
49 changes: 49 additions & 0 deletions projects/campaigns/kbn-25k-digits.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[project]
name = "kbn-25k-digit-primes"
description = "Search for 25K+ digit primes of the form k*2^n+/-1"
objective = "record"
form = "kbn"
tags = ["large-prime", "25k-digits", "proth", "riesel"]

[target]
target_digits = 25000
range_start = 83000
range_end = 100000

[strategy]
auto_strategy = false

[[strategy.phases]]
name = "k3-base2-sweep"
description = "k=3, base=2, n=83000..100000 (25K-30K digits)"
search_params = { search_type = "kbn", k = 3, base = 2, min_n = 83000, max_n = 100000 }
block_size = 500
completion = "all_blocks_done"

[[strategy.phases]]
name = "k5-base2-sweep"
description = "k=5, base=2, n=83000..100000"
search_params = { search_type = "kbn", k = 5, base = 2, min_n = 83000, max_n = 100000 }
block_size = 500
completion = "all_blocks_done"

[[strategy.phases]]
name = "k7-base2-sweep"
description = "k=7, base=2, n=83000..100000"
search_params = { search_type = "kbn", k = 7, base = 2, min_n = 83000, max_n = 100000 }
block_size = 500
completion = "all_blocks_done"

[infrastructure]
min_ram_gb = 16
min_cores = 8
preferred_tools = ["prst", "pfgw"]

[budget]
max_cost_usd = 200.0
cost_alert_threshold_usd = 100.0
cloud_rate_usd_per_core_hour = 0.04

[workers]
min_workers = 4
max_workers = 4
28 changes: 28 additions & 0 deletions projects/campaigns/palindromic-25k-digits.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[project]
name = "palindromic-25k-digit-primes"
description = "Search for palindromic primes with 25K+ digits"
objective = "record"
form = "palindromic"
tags = ["large-prime", "25k-digits", "palindromic"]

[target]
target_digits = 25000

[strategy]
auto_strategy = false

[[strategy.phases]]
name = "palindromic-sweep"
description = "25001-digit base-10 palindromes (odd digit count)"
search_params = { search_type = "palindromic", base = 10, min_digits = 25001, max_digits = 25001 }
block_size = 2
completion = "all_blocks_done"

[infrastructure]
min_ram_gb = 32
min_cores = 8
preferred_tools = ["pfgw"]

[budget]
max_cost_usd = 500.0
cloud_rate_usd_per_core_hour = 0.04
57 changes: 36 additions & 21 deletions src/ecm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,11 @@ fn random_curve_and_point(n: &Integer, seed: u64) -> Option<(EdwardsCurve, Edwar
// -x² + y² = 1 + d·x²·y² => d = (-x² + y² - 1)/(x²·y²)
let numerator = {
let val = (Integer::from(&y2) - Integer::from(&x2) - 1u32) % n;
if val < 0 { val + n } else { val }
if val < 0 {
val + n
} else {
val
}
};
let denominator = Integer::from(&x2 * &y2) % n;

Expand Down Expand Up @@ -243,12 +247,7 @@ fn random_curve_and_point(n: &Integer, seed: u64) -> Option<(EdwardsCurve, Edwar
/// multiplies the point by q^e. On Edwards curves, the identity is
/// (0:1:0:1), so when the point reaches the identity mod a prime factor p,
/// X ≡ 0 mod p (not Z ≡ 0). We check gcd(X, n) to find factors.
fn ecm_stage1(
point: &EdwardsPoint,
curve_d: &Integer,
n: &Integer,
b1: u64,
) -> Option<Integer> {
fn ecm_stage1(point: &EdwardsPoint, curve_d: &Integer, n: &Integer, b1: u64) -> Option<Integer> {
let primes = crate::sieve::generate_primes(b1);
let mut current = point.clone();

Expand Down Expand Up @@ -350,13 +349,7 @@ fn ecm_stage2(
fn ecm_one_curve(n: &Integer, b1: u64, b2: u64, curve_seed: u64) -> Option<Integer> {
let (curve, point) = random_curve_and_point(n, curve_seed)?;

// Stage 1
if let Some(factor) = ecm_stage1(&point, &curve.d, n, b1) {
return Some(factor);
}

// Recompute the point after Stage 1 for Stage 2
// (We need to re-run Stage 1 to get the accumulated point)
// Stage 1: compute accumulated point P * lcm(1..B1) and check for factors
let primes = crate::sieve::generate_primes(b1);
let mut accumulated = point.clone();
for &q in &primes {
Expand All @@ -367,6 +360,23 @@ fn ecm_one_curve(n: &Integer, b1: u64, b2: u64, curve_seed: u64) -> Option<Integ
accumulated = scalar_mul(&Integer::from(pk), &accumulated, &curve.d, n);
}

// Check for factor from Stage 1 (X coordinate reveals identity mod p)
let x_mod = Integer::from(&accumulated.x % n);
if x_mod != 0u32 {
let g = x_mod.gcd(n);
if g > 1u32 && &g < n {
return Some(g);
}
}
// Also check T coordinate
let t_mod = Integer::from(&accumulated.t % n);
if t_mod != 0u32 {
let g = t_mod.gcd(n);
if g > 1u32 && &g < n {
return Some(g);
}
}

// Stage 2
ecm_stage2(&accumulated, &curve.d, n, b1, b2)
}
Expand All @@ -391,8 +401,10 @@ pub fn adaptive_ecm_filter(n: &Integer, num_curves: u32) -> bool {
(50_000u64, 5_000_000u64, num_curves.min(5))
} else if bits < 80_000 {
(200_000u64, 20_000_000u64, num_curves.min(10))
} else {
} else if bits < 120_000 {
(1_000_000u64, 100_000_000u64, num_curves.min(20))
} else {
(3_000_000u64, 300_000_000u64, num_curves.min(30)) // 36K+ digits
};

for seed in 0..curves {
Expand Down Expand Up @@ -436,7 +448,10 @@ mod tests {
}
}
}
assert!(found, "ECM should find a factor of 41*10007 within 500 curves");
assert!(
found,
"ECM should find a factor of 41*10007 within 500 curves"
);
}

/// ECM returns no factor for primes.
Expand All @@ -446,10 +461,7 @@ mod tests {
for seed in 1..10u64 {
if let Some((curve, point)) = random_curve_and_point(&p, seed) {
let result = ecm_stage1(&point, &curve.d, &p, 100);
assert!(
result.is_none(),
"ECM should not find factors of primes"
);
assert!(result.is_none(), "ECM should not find factors of primes");
}
}
}
Expand All @@ -458,7 +470,10 @@ mod tests {
#[test]
fn ecm_adaptive_skips_small() {
let n = Integer::from(41u32 * 10007);
assert!(!adaptive_ecm_filter(&n, 10), "ECM should skip small candidates");
assert!(
!adaptive_ecm_filter(&n, 10),
"ECM should skip small candidates"
);
}

/// Edwards point identity check.
Expand Down
8 changes: 8 additions & 0 deletions src/factorial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ pub fn search(
if crate::p1::adaptive_p1_filter(&plus) {
return (IsPrime::No, None);
}
// ECM pre-filter — catches composites with smooth curve order
if crate::ecm::adaptive_ecm_filter(&plus, 20) {
return (IsPrime::No, None);
}
// Try PFGW acceleration for large candidates
if let Some(pfgw_result) =
pfgw::try_test(&format!("{}!+1", n), &plus, pfgw::PfgwMode::NMinus1Proof)
Expand Down Expand Up @@ -274,6 +278,10 @@ pub fn search(
if crate::p1::adaptive_p1_filter(&minus) {
return (IsPrime::No, None);
}
// ECM pre-filter — catches composites with smooth curve order
if crate::ecm::adaptive_ecm_filter(&minus, 20) {
return (IsPrime::No, None);
}
// Try PFGW acceleration for large candidates
if let Some(pfgw_result) =
pfgw::try_test(&format!("{}!-1", n), &minus, pfgw::PfgwMode::NPlus1Proof)
Expand Down
6 changes: 6 additions & 0 deletions src/kbn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,12 @@ pub(crate) fn test_prime(
return (IsPrime::No, "", None);
}

// ECM composite pre-filter for large candidates — catches ~1-3% more composites
// where the curve order is smooth even when p-1 is not (complementary to P-1).
if crate::ecm::adaptive_ecm_filter(candidate, 20) {
return (IsPrime::No, "", None);
}

// Try GWNUM direct FFI for large candidates (when --features gwnum is enabled)
#[cfg(feature = "gwnum")]
{
Expand Down
11 changes: 10 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

pub mod agent;
pub mod ai_engine;
pub mod batch_gcd;
pub mod carol_kynea;
pub mod certificate;
pub mod checkpoint;
Expand All @@ -50,6 +51,7 @@ pub mod cullen_woodall;
pub mod dashboard;
pub mod db;
pub mod deploy;
pub mod ecm;
pub mod events;
pub mod factorial;
pub mod fleet;
Expand Down Expand Up @@ -213,8 +215,15 @@ fn poly_pow_mod(exp: &Integer, coeff_b: &Integer, coeff_c: &Integer, n: &Integer
result = poly_sqr(&result, coeff_b, coeff_c, n);
if exp.get_bit(i) {
// Multiply by x: [r0, r1] * [0, 1] = [-r1*c, r0 + r1*b]
let new_r0 = (Integer::from(n) - Integer::from(&result[1] * coeff_c) % n) % n;
// Compute -r1*c mod n = n - (r1*c mod n), with care for the zero case
let r1c_mod_n = Integer::from(&result[1] * coeff_c) % n;
let new_r0 = if r1c_mod_n == 0u32 {
Integer::from(0u32)
} else {
Integer::from(n - &r1c_mod_n)
};
let new_r1 = (Integer::from(&result[0]) + Integer::from(&result[1] * coeff_b)) % n;
let new_r1 = if new_r1 < 0 { new_r1 + n } else { new_r1 };
result = [new_r0, new_r1];
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/p1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,10 @@ pub fn adaptive_p1_filter(n: &Integer) -> bool {
(100_000u64, 10_000_000u64)
} else if bits < 50_000 {
(500_000u64, 50_000_000u64)
} else if bits < 100_000 {
(2_000_000u64, 200_000_000u64) // 25K+ digits (~83K bits)
} else {
(1_000_000u64, 100_000_000u64)
(5_000_000u64, 500_000_000u64) // 30K+ digits (~100K bits)
};

p1_factor(n, b1, Some(b2)).is_some()
Expand Down
22 changes: 21 additions & 1 deletion src/palindromic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,22 @@ pub fn search(
digit_count, base, lead_digit
);

// Only candidates surviving the digit filter need primality testing.
// Batch GCD pre-filter: eliminate candidates sharing a factor with the
// primorial (product of small primes) via Bernstein product/remainder trees.
// O(n log²n) vs O(n·π(L)) for sequential trial division.
let batch: Vec<Integer> = if batch.len() >= 100 {
let composites = crate::batch_gcd::batch_has_small_factor(&batch, sieve_limit);
batch
.into_iter()
.zip(composites.iter())
.filter(|(_, &is_composite)| !is_composite)
.map(|(c, _)| c)
.collect()
} else {
batch
};

// Only candidates surviving the digit + batch GCD filters need primality testing.
// Try PFGW first for large candidates (50-100x faster), fall back to GMP MR.
let found_primes: Vec<_> = batch
.into_par_iter()
Expand Down Expand Up @@ -360,6 +375,11 @@ pub fn search(
return None;
}

// ECM pre-filter — catches composites with smooth curve order
if crate::ecm::adaptive_ecm_filter(&num, 20) {
return None;
}

// GMP Miller-Rabin fallback — defer to_string_radix until prime is found
let r = mr_screened_test(&num, mr_rounds);
if r != IsPrime::No {
Expand Down
8 changes: 8 additions & 0 deletions src/primorial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ pub fn search(
if crate::p1::adaptive_p1_filter(&plus) {
return (IsPrime::No, None);
}
// ECM pre-filter — catches composites with smooth curve order
if crate::ecm::adaptive_ecm_filter(&plus, 20) {
return (IsPrime::No, None);
}
if let Some(pfgw_result) =
pfgw::try_test(&format!("{}#+1", p), &plus, pfgw::PfgwMode::NMinus1Proof)
{
Expand Down Expand Up @@ -294,6 +298,10 @@ pub fn search(
if crate::p1::adaptive_p1_filter(&minus) {
return (IsPrime::No, None);
}
// ECM pre-filter — catches composites with smooth curve order
if crate::ecm::adaptive_ecm_filter(&minus, 20) {
return (IsPrime::No, None);
}
if let Some(pfgw_result) =
pfgw::try_test(&format!("{}#-1", p), &minus, pfgw::PfgwMode::NPlus1Proof)
{
Expand Down
Loading
Loading