use clap::Parser;
use crate::scan::Mode;
#[derive(Parser, Debug, Clone)]
#[command(
author,
version,
about = "Runs a test for Hamming-weight dependencies; use RAYON_NUM_THREADS to customize the number of threads.",
next_line_help = true,
max_term_width = 100
)]
pub struct Args {
#[arg(allow_negative_numbers = true, value_parser = parse_f64)]
pub n: Option<f64>,
#[arg(short = 'w', long, default_value_t = 64)]
pub word_bits: usize,
#[arg(long, default_value_t = 64)]
pub prng_bits: usize,
#[arg(short = 'k', long, default_value_t = 8)]
pub dim: usize,
#[arg(short = 'c', long)]
pub categories: Option<usize>,
#[arg(short = 't', long)]
pub transitions: bool,
#[arg(long)]
pub progress: bool,
#[arg(long, value_parser = parse_f64)]
pub low_pv: Option<f64>,
#[arg(long, value_parser = parse_f64)]
pub max_batch_size: Option<f64>,
#[arg(short = 'S', long, default_value_t = 0, value_parser = parse_u64)]
pub seed: u64,
#[arg(short = 'P', long)]
pub parallel: bool,
}
impl Args {
pub fn mode(&self) -> Mode {
Mode::new(self.word_bits, self.prng_bits).expect("arguments must be validated")
}
pub fn numcats(&self) -> usize {
self.categories.unwrap_or(self.dim / 2 + 1)
}
pub fn bytes(&self) -> i64 {
self.n.map_or(-1, |n| n as i64)
}
pub fn progress(&self) -> bool {
self.progress || self.bytes() <= 0
}
pub fn low_pv(&self) -> f64 {
self.low_pv.unwrap_or(f64::MIN_POSITIVE)
}
pub fn max_batch_size(&self) -> Option<u64> {
self.max_batch_size.map(|b| b as u64)
}
pub fn validate(&self) {
if ![32, 64, 128].contains(&self.word_bits) {
Self::die(&format!(
"the word size ({}) must be 32, 64, or 128",
self.word_bits
));
}
if ![32, 64].contains(&self.prng_bits) {
Self::die(&format!(
"the number of output bits ({}) must be 32 or 64",
self.prng_bits
));
}
if Mode::new(self.word_bits, self.prng_bits).is_none() {
Self::die(&format!(
"{}-bit words are not supported with {}-bit outputs",
self.word_bits, self.prng_bits
));
}
if !(1..=19).contains(&self.dim) {
Self::die(&format!("k ({}) must be between 1 and 19", self.dim));
}
if !(1..=self.dim).contains(&self.numcats()) {
Self::die(&format!(
"the number of categories ({}) must be between 1 and k ({})",
self.numcats(),
self.dim
));
}
if let Some(b) = self.max_batch_size {
if !(b >= 2.0 && b < 2f64.powi(63) && b.fract() == 0.0 && b % 2.0 == 0.0) {
Self::die(&format!(
"the maximum batch size ({b}) must be a positive even integer"
));
}
}
}
pub fn die(msg: &str) -> ! {
use clap::CommandFactory;
Args::command()
.error(clap::error::ErrorKind::ValueValidation, msg)
.exit()
}
pub fn parallel_cpus(&self) -> Option<usize> {
if self.parallel {
Some(rayon::current_num_threads())
} else {
None
}
}
}
fn parse_f64(value: &str) -> Result<f64, String> {
value
.trim()
.parse::<f64>()
.map_err(|e| format!("invalid number {value:?}: {e}"))
}
fn parse_u64(value: &str) -> Result<u64, String> {
let trimmed = value.trim();
let (radix, digits) = match trimmed.get(..2) {
Some("0x") | Some("0X") => (16, &trimmed[2..]),
Some("0o") | Some("0O") => (8, &trimmed[2..]),
Some("0b") | Some("0B") => (2, &trimmed[2..]),
_ => (10, trimmed),
};
let digits: String = digits.chars().filter(|&c| c != '_').collect();
if digits.is_empty() {
return Err(format!("invalid integer: {value:?}"));
}
u64::from_str_radix(&digits, radix).map_err(|e| format!("invalid integer {value:?}: {e}"))
}