use std::fmt::Debug;
use std::sync::OnceLock;
use clap::{Arg, ArgAction, Command};
use num_format::{Locale, SystemLocale, ToFormattedString};
use prime_formula::common::{
ConfigMode, NON_ROOT_PRIMES, NUM_ROOTS, PRIME_PERIOD, PRIME_ROOTS, find_inv_prime_root_vec,
get_auto_mode, get_cyclic_composite_vec,
};
use prime_formula::{count_primes, get_primes};
#[cfg(feature = "constellations")]
use prime_formula::constellations::{
PrimeRootFilters, count_quadruplets, count_quintuplets, count_sextuplets, count_triplets,
count_twins, get_quadruplets, get_quintuplets, get_sextuplets, get_triplets, get_twins,
};
#[derive(Debug, Clone)]
struct Config {
print: bool,
quiet: bool,
page_size: u64,
locale: Locale,
mode: ConfigMode,
num_tests: u32,
}
const DEFAULT_PAGE: u64 = 1024 * 1024 * 1024;
static CONFIG: OnceLock<Config> = OnceLock::new();
fn init_config(
print: bool,
quiet: bool,
page_size: u64,
locale: Locale,
mode: ConfigMode,
num_tests: u32,
) {
CONFIG.get_or_init(|| Config {
print,
quiet,
page_size,
locale,
mode,
num_tests,
});
}
fn get_config() -> &'static Config {
CONFIG.get().expect("Config not initialized")
}
fn pretty_print<T: Debug>(items: &[T], elements_per_line: usize, separator: &str) {
assert!(
elements_per_line > 0,
"elements_per_line must be at least 1"
);
let chunks = items.chunks(elements_per_line);
for chunk in chunks {
let line = chunk
.iter()
.map(|item| format!("{:?}", item))
.collect::<Vec<_>>()
.join(separator);
eprintln!("{}", line);
}
}
#[cfg(feature = "constellations")]
fn info_constellations() {
let grouped_twins: Vec<(u8, u8)> = PRIME_ROOTS
.filter_twins()
.iter()
.map(|&root_idx| {
let i = root_idx as usize;
(PRIME_ROOTS[i as usize], PRIME_ROOTS[i as usize + 1])
}) .collect();
let grouped_triplets: Vec<(u8, u8, u8)> = PRIME_ROOTS
.filter_triplets()
.iter()
.map(|&root_idx| {
let i = root_idx as usize;
(
PRIME_ROOTS[i as usize],
PRIME_ROOTS[i as usize + 1],
PRIME_ROOTS[i as usize + 2],
)
}) .collect();
let grouped_quads: Vec<(u8, u8, u8, u8)> = PRIME_ROOTS
.filter_quadruplets()
.iter()
.map(|&root_idx| {
let i = root_idx as usize;
(
PRIME_ROOTS[i],
PRIME_ROOTS[i + 1],
PRIME_ROOTS[i + 2],
PRIME_ROOTS[i + 3],
)
}) .collect();
let grouped_quints: Vec<(u8, u8, u8, u8, u8)> = PRIME_ROOTS
.filter_quintuplets()
.iter()
.map(|&root_idx| {
let i = root_idx as usize;
(
PRIME_ROOTS[i],
PRIME_ROOTS[i + 1],
PRIME_ROOTS[i + 2],
PRIME_ROOTS[i + 3],
PRIME_ROOTS[i + 4],
)
}) .collect();
let grouped_sextuplets: Vec<(u8, u8, u8, u8, u8, u8)> = PRIME_ROOTS
.filter_sextuplets()
.iter()
.map(|&root_idx| {
let i = root_idx as usize;
(
PRIME_ROOTS[i],
PRIME_ROOTS[i + 1],
PRIME_ROOTS[i + 2],
PRIME_ROOTS[i + 3],
PRIME_ROOTS[i + 4],
PRIME_ROOTS[i + 5],
)
}) .collect();
eprintln!("Special Groups");
eprintln!("--------------");
eprintln!("[Twin roots] ({} pairs)", grouped_twins.len());
pretty_print(&grouped_twins, 5, " ");
eprintln!();
eprintln!("[Triplet roots] ({} triplets)", grouped_triplets.len());
pretty_print(&grouped_triplets, 4, " ");
eprintln!();
eprintln!("[Quadruplet roots] ({} quadruplets)", grouped_quads.len());
pretty_print(&grouped_quads, 4, " ");
eprintln!();
eprintln!("[Quintuplet roots] ({} quintuplets)", grouped_quints.len());
pretty_print(&grouped_quints, 2, " ");
eprintln!();
eprintln!(
"[Sextuplet roots] ({} sextuplets)",
grouped_sextuplets.len()
);
pretty_print(&grouped_sextuplets, 2, " ");
}
fn info() {
let primes_string = NON_ROOT_PRIMES
.iter()
.map(|n| n.to_string())
.collect::<Vec<_>>()
.join(" × ");
let primes_string2 = "2, 3, 5 & 7";
eprintln!("The Periodic Table of Prime Numbers");
eprintln!("===================================");
eprintln!();
eprintln!("A Rust implementation of novel prime number theory proposing:");
eprintln!(
" - All primes are descendants of {} fundamental roots",
NUM_ROOTS
);
eprintln!(
" - These roots derive from the foundational primes ({})",
primes_string2
);
eprintln!(
" - The system exhibits periodicity with cycle length {}",
PRIME_PERIOD
);
eprintln!(" - All primes (excl. fundamental roots) can be written in the form:");
eprintln!(" p = prime_root + 210 × cycle_num");
eprintln!();
eprintln!("Prime Constants");
eprintln!("---------------");
eprintln!(" - Foundation primes: {}", NON_ROOT_PRIMES.len());
eprintln!(
" - Cycle Period: {} ({} = {})",
PRIME_PERIOD, primes_string, PRIME_PERIOD
);
eprintln!(
" - Prime Roots: {} (Numbers {}-{} excluding multiples",
PRIME_PERIOD,
PRIME_ROOTS[0],
PRIME_ROOTS.last().unwrap()
);
eprintln!(" of {})", primes_string2);
eprintln!();
eprintln!("Prime Root Classification");
eprintln!("-------------------------");
eprintln!("[Non-root primes] (Foundation)");
pretty_print(&NON_ROOT_PRIMES, 10, ", ");
eprintln!();
eprintln!("[Prime Roots] (First Generation)");
pretty_print(&PRIME_ROOTS, 10, ", ");
eprintln!();
#[cfg(feature = "constellations")]
{
info_constellations();
}
}
fn dump_cyclic_composite_table() {
let inv_prime_root_table: Vec<Vec<u8>> = (0..NUM_ROOTS).map(find_inv_prime_root_vec).collect();
let cyclic_composite_table: Vec<Vec<u8>> = (0..NUM_ROOTS)
.map(|i| get_cyclic_composite_vec(i, &inv_prime_root_table[i]))
.collect();
eprintln!("Inverse Prime Root Table");
eprintln!("------------------------");
for (i, row) in inv_prime_root_table.iter().enumerate() {
eprintln!("{}: {:?}", PRIME_ROOTS[i], row);
}
eprintln!();
eprintln!("Cyclic Composite Table");
eprintln!("----------------------");
for (i, row) in cyclic_composite_table.iter().enumerate() {
eprintln!("{}: {:?}", PRIME_ROOTS[i], row);
}
}
fn get_prime_name(count: u8) -> &'static str {
match count {
2 => "twin primes",
3 => "prime triplets",
4 => "prime quadruplets",
5 => "prime quintuplets",
6 => "prime sextuplets",
_ => "primes",
}
}
fn display_batch(count: u8, num_primes: usize, start: u128, end: u128) {
let name = get_prime_name(count);
let locale = get_config().locale;
let print_mode = get_config().print;
if !get_config().quiet || !print_mode {
eprintln!(
"Found {} {} in range {} to {}",
num_primes.to_formatted_string(&locale),
name,
start.to_formatted_string(&locale),
end.to_formatted_string(&locale)
);
}
}
fn display_init(count: u8, start: u128, stop: u128) {
let page_size = get_config().page_size;
let name = get_prime_name(count);
let locale = get_config().locale;
let mode = get_config().mode;
if start == stop {
eprintln!(
"Finding {} from {} onwards:",
name,
start.to_formatted_string(&locale)
);
} else {
eprintln!(
"Finding {} in the range {} to {}:",
name,
start.to_formatted_string(&locale),
stop.to_formatted_string(&locale)
);
}
eprintln! {" - using {:?} mode", mode}
if get_config().print {
eprintln!(" - {} will be output to stdout", name);
} else {
eprintln!(" - {} will be counted", name);
}
if start == stop || (stop - start) > page_size as u128 {
eprintln!(
" - using page size {}",
page_size.to_formatted_string(&locale)
);
}
}
fn find_primes(start: u128, stop: u128, count: u8) {
let locale = get_config().locale;
let page_size = get_config().page_size as u128;
let print_mode = get_config().print;
if !get_config().quiet {
display_init(count, start, stop);
}
let sieve_mode = get_config().mode;
let quiet = get_config().quiet;
let mut b_start = start;
while b_start < stop || (start == stop) {
let x = b_start;
let y = if start == stop {
b_start + page_size - 1
} else {
(b_start + page_size - 1).min(stop)
};
if !quiet {
eprintln!(
"Checking range {} to {}..",
x.to_formatted_string(&locale),
y.to_formatted_string(&locale)
);
}
let num_primes;
if count > 1 {
#[cfg(feature = "constellations")]
{
num_primes = find_constellations(x, y, count);
}
#[cfg(not(feature = "constellations"))]
{
panic!("No constellation support!")
}
} else if print_mode {
let primes = get_primes(x, y, &sieve_mode);
println!("{:?}", primes);
num_primes = primes.len();
} else {
num_primes = count_primes(x, y, &sieve_mode);
}
display_batch(count, num_primes, x, y);
b_start = b_start.saturating_add(page_size);
}
}
#[cfg(feature = "constellations")]
fn find_constellations(x: u128, y: u128, count: u8) -> usize {
let mode = get_config().mode;
let print_mode = get_config().print;
let num_primes;
if count == 2 {
if print_mode {
let primes = get_twins(x, y, &mode);
println!("{:?}", primes);
num_primes = primes.len()
} else {
num_primes = count_twins(x, y, &mode);
}
} else if count == 3 {
if print_mode {
let primes = get_triplets(x, y, &mode);
println!("{:?}", primes);
num_primes = primes.len()
} else {
num_primes = count_triplets(x, y, &mode);
}
} else if count == 4 {
if print_mode {
let primes = get_quadruplets(x, y, &mode);
println!("{:?}", primes);
num_primes = primes.len()
} else {
num_primes = count_quadruplets(x, y, &mode);
}
} else if count == 5 {
if print_mode {
let primes = get_quintuplets(x, y, &mode);
println!("{:?}", primes);
num_primes = primes.len()
} else {
num_primes = count_quintuplets(x, y, &mode);
}
} else if count == 6 {
if print_mode {
let primes = get_sextuplets(x, y, &mode);
println!("{:?}", primes);
num_primes = primes.len()
} else {
num_primes = count_sextuplets(x, y, &mode);
}
} else {
panic!("Unexpected constellation size")
}
num_primes
}
fn get_system_locale(default: Locale) -> Locale {
let system_locale = SystemLocale::default().unwrap();
let raw_name = system_locale.name();
let cleaned_name = raw_name.split('.').next().unwrap_or(default.name());
let parts: Vec<&str> = cleaned_name.split('_').collect();
let mut candidates = Vec::new();
for i in (1..=parts.len()).rev() {
candidates.push(parts[0..i].join("-"));
}
candidates
.iter()
.find_map(|c| Locale::from_name(c).ok())
.unwrap_or(default)
}
fn parse_scientific(input: &str) -> Result<(u128, u32), String> {
let parts: Vec<&str> = input.splitn(2, 'e').collect();
if parts.len() != 2 {
return Err("Invalid scientific notation format".to_string());
}
let mut exponent: u32 = parts[1]
.parse()
.map_err(|_| format!("Invalid exponent: {}", parts[1]))?;
let base_parts: Vec<&str> = parts[0].splitn(2, '.').collect();
let base_int = base_parts[0]
.parse::<u8>()
.map_err(|_| format!("Invalid base: {}", base_parts[0]))?;
if base_int == 0 || base_int > 9 {
return Err(format!("Invalid base - must be 1-9: {}", base_int));
}
let mut base_fraction = if base_parts.len() == 2 {
base_parts[1]
.parse::<u128>()
.map_err(|_| format!("Invalid base fractional: {}", base_parts[1]))?
} else {
0u128
};
let mut base = base_int as u128;
if base_fraction > 0 {
let mut fractional_digits = base_parts[1].len() as u32;
if fractional_digits > exponent {
let diff = fractional_digits - exponent;
base_fraction += 10u128.pow(diff) / 2;
base_fraction /= 10u128.pow(diff);
fractional_digits -= diff;
}
base = base_int as u128 * 10u128.pow(fractional_digits) + base_fraction;
exponent -= fractional_digits;
}
Ok((base, exponent))
}
fn parse_human_number(s: &str, locale: &Locale) -> Result<u128, String> {
let cleaned = s
.replace(['_', ' '], "") .replace(locale.separator(), "")
.replace(locale.decimal(), ".")
.to_lowercase();
if cleaned.contains("e") {
let (base, exponent) = parse_scientific(&cleaned)
.map_err(|e| format!("Invalid scientific notation in '{s}': {e}"))?;
let value = base
.checked_mul(10u128.saturating_pow(exponent))
.ok_or_else(|| format!("Overflow calculating {base} * 10^{exponent}"))?;
Ok(value)
} else {
let parts: Vec<&str> = cleaned.splitn(2, '.').collect();
parts[0]
.parse::<u128>()
.map_err(|e| format!("Invalid number format in '{s}': {e}"))
}
}
fn main() {
let cmd = Command::new("primes-rs")
.version("0.1.0")
.about("Prime number generator using the Periodic Table of Primes theory")
.arg(
Arg::new("start")
.help("Start of range (supports 1e6, 10_000_000 formats)")
.default_value("0")
.index(1),
)
.arg(
Arg::new("stop")
.help("End of range (supports 1e6, 10_000_000 formats)")
.index(2),
)
.arg(
Arg::new("dist")
.short('d')
.long("dist")
.help("Set interval (supports 1e6, 10_000_000 formats)"),
)
.arg(
Arg::new("print")
.short('p')
.long("print")
.action(ArgAction::SetTrue)
.help("Print primes instead of counting"),
)
.arg(
Arg::new("quiet")
.short('q')
.long("quiet")
.action(ArgAction::SetTrue)
.help("Show minimal output"),
)
.arg(
Arg::new("page_size")
.long("page-size")
.help("Set page size in range 1 to N"),
)
.arg(
Arg::new("info")
.short('i')
.long("info")
.action(ArgAction::Count)
.help("Dump algorithm information (use -i for basic, -ii for more)"),
)
.arg(
Arg::new("constellation")
.short('c')
.long("constellation")
.value_parser(["1", "2", "3", "4", "5", "6"])
.default_value("1")
.help("Find or count twin, triple, quad or quint primes"),
);
let matches = cmd.get_matches();
let info_level = matches.get_count("info");
if info_level > 0 {
info();
if info_level >= 2 {
eprintln!();
dump_cyclic_composite_table();
}
std::process::exit(0);
}
let locale = get_system_locale(Locale::en);
let start = matches
.get_one::<String>("start")
.map(|s| parse_human_number(s, &locale))
.unwrap()
.expect("Start needs a default..");
let Ok(mut stop) = matches
.get_one::<String>("stop")
.map(|s| parse_human_number(s, &locale))
.unwrap_or(Ok(start))
else {
eprintln!("Stop cannot be parsed!");
std::process::exit(1);
};
let dist = matches
.get_one::<String>("dist")
.map(|s| parse_human_number(s, &locale))
.unwrap()
.unwrap_or(0);
if dist > 0 {
stop = start.saturating_add(dist)
}
if stop < start {
eprintln!("Stop cannot be less than start!");
std::process::exit(1);
}
if stop >= u128::MAX - (u128::MAX % 210) {
eprintln!("Stop exceeds u128 limit!");
std::process::exit(1);
}
let constellation_type = matches
.get_one::<String>("constellation")
.unwrap()
.parse::<u8>()
.unwrap();
#[cfg(not(feature = "constellations"))]
{
if constellation_type > 1 {
eprintln!("Please rebuild using --features constellations");
std::process::exit(1);
}
}
let print_mode = matches.get_flag("print");
let quiet = matches.get_flag("quiet");
let page_size = matches
.get_one::<String>("page_size")
.map(|s| s.parse::<u64>().unwrap())
.unwrap_or(DEFAULT_PAGE);
let mode = get_auto_mode(start, stop.max(start.saturating_add(page_size as u128)));
let num_tests = 8;
init_config(print_mode, quiet, page_size, locale, mode, num_tests);
find_primes(start, stop, constellation_type);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_scientific_notation() {
assert_eq!(parse_scientific("1e3"), Ok((1, 3)));
assert_eq!(parse_scientific("1e10"), Ok((1, 10)));
assert_eq!(parse_scientific("9e5"), Ok((9, 5)));
assert_eq!(parse_scientific("1.5e3"), Ok((15, 2))); assert_eq!(parse_scientific("2.718e6"), Ok((2718, 3))); assert_eq!(parse_scientific("3.000e4"), Ok((3, 4)));
assert_eq!(parse_scientific("1.000000000e9"), Ok((1, 9)));
assert_eq!(parse_scientific("9.9999999999e2"), Ok((1000, 0)));
}
#[test]
fn invalid_scientific_notation() {
assert!(parse_scientific("0.5e3").is_err()); assert!(parse_scientific("10.5e3").is_err());
assert!(parse_scientific("123e").is_err()); assert!(parse_scientific("1.2.3e4").is_err()); assert!(parse_scientific("1e2e3").is_err()); assert!(parse_scientific("1.5e").is_err()); assert!(parse_scientific("1.5e3.5").is_err());
assert!(parse_scientific("1a.5e3").is_err()); assert!(parse_scientific("1.5e3x").is_err()); }
#[test]
fn leading_zero_handling() {
assert!(parse_scientific("01.5e3").is_ok()); assert!(parse_scientific("1.05e3").is_ok()); assert!(parse_scientific("1.00e3").is_ok()); }
#[test]
fn test_parse_human_number() {
assert_eq!(parse_human_number("1e3", &Locale::en), Ok(1000));
assert_eq!(parse_human_number("14", &Locale::en), Ok(14));
assert_eq!(parse_human_number("1,200", &Locale::en), Ok(1200));
assert_eq!(parse_human_number("1,100.00", &Locale::en), Ok(1100));
assert_eq!(parse_human_number("1,000.50", &Locale::en), Ok(1000));
}
}