use std::collections::HashMap;
use std::hash::Hash;
use std::sync::atomic::{AtomicBool, Ordering};
use num_bigint::{BigInt, BigUint};
use num_integer::Integer;
use num_rational::BigRational;
use num_traits::{One, Signed, ToPrimitive, Zero};
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use crate::lehmer;
use crate::modp::{add, inv, mul, sub, try_inv};
pub fn residue(x: &BigInt, p: u64) -> u64 {
x.mod_floor(&BigInt::from(p))
.to_u64()
.expect("residue below p")
}
pub fn reduce(q: &BigRational, p: u64) -> Option<u64> {
let s = residue(q.denom(), p);
(s != 0).then(|| mul(residue(q.numer(), p), inv(s, p), p))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CrtError {
LengthMismatch { expected: usize, actual: usize },
InvalidModulus,
NonCoprimeModuli,
UnreducedResidue,
}
impl std::fmt::Display for CrtError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LengthMismatch { expected, actual } => {
write!(
f,
"CRT vector length mismatch: expected {expected}, got {actual}"
)
}
Self::InvalidModulus => f.write_str("CRT requires m > 0 and p > 1"),
Self::NonCoprimeModuli => f.write_str("CRT moduli must be coprime"),
Self::UnreducedResidue => f.write_str("CRT image residues must be less than p"),
}
}
}
impl std::error::Error for CrtError {}
pub fn try_garner(
xs: &mut [BigInt],
m: &mut BigInt,
values: &[u64],
p: u64,
) -> Result<(), CrtError> {
if xs.len() != values.len() {
return Err(CrtError::LengthMismatch {
expected: xs.len(),
actual: values.len(),
});
}
if !m.is_positive() || p < 2 {
return Err(CrtError::InvalidModulus);
}
if values.iter().any(|&v| v >= p) {
return Err(CrtError::UnreducedResidue);
}
let minv = try_inv(residue(m, p), p).ok_or(CrtError::NonCoprimeModuli)?;
for (x, &v) in xs.iter_mut().zip(values) {
*x += &*m * mul(sub(v, residue(x, p), p), minv, p);
}
*m *= p;
Ok(())
}
pub fn garner(xs: &mut [BigInt], m: &mut BigInt, values: &[u64], p: u64) {
try_garner(xs, m, values, p).expect("invalid Garner update");
}
#[derive(Clone, Debug)]
pub struct CrtAccumulator {
residues: Vec<BigInt>,
modulus: BigInt,
image_count: usize,
}
impl CrtAccumulator {
pub fn new(len: usize) -> Self {
Self {
residues: vec![BigInt::zero(); len],
modulus: BigInt::one(),
image_count: 0,
}
}
pub fn add(&mut self, p: u64, values: &[u64]) -> Result<(), CrtError> {
try_garner(&mut self.residues, &mut self.modulus, values, p)?;
self.image_count += 1;
Ok(())
}
pub fn residues(&self) -> &[BigInt] {
&self.residues
}
pub const fn modulus(&self) -> &BigInt {
&self.modulus
}
pub const fn image_count(&self) -> usize {
self.image_count
}
pub fn reconstruct(&self) -> Option<Vec<BigRational>> {
WangContext::new(&self.modulus)?.reconstruct_many(&self.residues)
}
}
#[derive(Clone, Debug)]
pub struct MixedRadixAccumulator {
len: usize,
primes: Vec<u64>,
digits: Vec<Vec<u32>>,
}
impl MixedRadixAccumulator {
const ADD_CHUNK: usize = 4096;
const RECONSTRUCT_CHUNK: usize = 256;
const PROBES: usize = 64;
pub const fn new(len: usize) -> Self {
Self {
len,
primes: Vec::new(),
digits: Vec::new(),
}
}
pub fn add(&mut self, p: u64, values: &[u64]) -> Result<(), CrtError> {
if values.len() != self.len {
return Err(CrtError::LengthMismatch {
expected: self.len,
actual: values.len(),
});
}
if !(2..1 << 32).contains(&p) {
return Err(CrtError::InvalidModulus);
}
if values.iter().any(|&v| v >= p) {
return Err(CrtError::UnreducedResidue);
}
let mut weights = Vec::with_capacity(self.primes.len());
let mut m = 1;
for &q in &self.primes {
weights.push(m as u32);
m = mul(m, q % p, p);
}
let minv = try_inv(m, p).ok_or(CrtError::NonCoprimeModuli)?;
let max = self.primes.iter().copied().fold(p, u64::max);
let lazy = u128::from(max).pow(2) * (self.primes.len() as u128 + 1) < 1 << 64;
let digits = &self.digits;
let fill = |(c, out): (usize, &mut [u32])| {
let span = c * Self::ADD_CHUNK..c * Self::ADD_CHUNK + out.len();
let mut acc = vec![0u64; out.len()];
for (row, &w) in digits.iter().zip(&weights) {
let row = &row[span.clone()];
if lazy {
for (a, &t) in acc.iter_mut().zip(row) {
*a += u64::from(t) * u64::from(w);
}
} else {
for (a, &t) in acc.iter_mut().zip(row) {
*a = add(*a, mul(u64::from(t), u64::from(w), p), p);
}
}
}
for ((o, &v), a) in out.iter_mut().zip(&values[span]).zip(acc) {
*o = mul(sub(v, a % p, p), minv, p) as u32;
}
};
let mut next = vec![0u32; self.len];
#[cfg(feature = "parallel")]
next.par_chunks_mut(Self::ADD_CHUNK)
.enumerate()
.for_each(fill);
#[cfg(not(feature = "parallel"))]
next.chunks_mut(Self::ADD_CHUNK).enumerate().for_each(fill);
self.digits.push(next);
self.primes.push(p);
Ok(())
}
pub fn primes(&self) -> &[u64] {
&self.primes
}
pub const fn image_count(&self) -> usize {
self.primes.len()
}
pub fn modulus(&self) -> BigInt {
self.primes.iter().product::<BigUint>().into()
}
pub fn remap(&mut self, positions: &[Option<usize>]) {
for row in &mut self.digits {
*row = positions.iter().map(|i| i.map_or(0, |i| row[i])).collect();
}
self.len = positions.len();
}
fn radices(&self) -> Vec<(u64, std::ops::Range<usize>)> {
let mut radices: Vec<(u64, std::ops::Range<usize>)> = Vec::new();
for (j, &p) in self.primes.iter().enumerate() {
match radices.last_mut() {
Some((r, js)) if r.checked_mul(p).is_some() => {
*r *= p;
js.end = j + 1;
}
_ => radices.push((p, j..j + 1)),
}
}
radices
}
fn value(&self, radices: &[(u64, std::ops::Range<usize>)], i: usize) -> BigInt {
let mut x = BigUint::zero();
for (r, js) in radices.iter().rev() {
let d = js
.clone()
.rev()
.fold(0, |d, j| d * self.primes[j] + u64::from(self.digits[j][i]));
x *= *r;
x += d;
}
x.into()
}
pub fn residues(&self) -> Vec<BigInt> {
let radices = self.radices();
(0..self.len).map(|i| self.value(&radices, i)).collect()
}
pub fn reconstruct(&self) -> Option<Vec<BigRational>> {
if self.primes.is_empty() {
return None;
}
let radices = self.radices();
let modulus = self.modulus();
let context = WangContext::new(&modulus)?;
let n = self.len;
let probes = Self::PROBES.min(n);
for i in 0..probes {
let index = i * (n - 1) / probes.saturating_sub(1).max(1);
context.reconstruct(&self.value(&radices, index))?;
}
let failed = AtomicBool::new(false);
let run = |c: usize| -> Option<Vec<BigRational>> {
let mut d = BigInt::one();
let start = c * Self::RECONSTRUCT_CHUNK;
(start..n.min(start + Self::RECONSTRUCT_CHUNK))
.map(|i| {
if failed.load(Ordering::Relaxed) {
return None;
}
let x = self.value(&radices, i);
let y = symmetric(&(&x * &d), &modulus);
if y.magnitude() <= context.bound.magnitude() {
return Some(lehmer::fraction(y, d.magnitude()));
}
let Some(q) = context.reconstruct(&x) else {
failed.store(true, Ordering::Relaxed);
return None;
};
let g = lehmer::gcd(d.magnitude(), q.denom().magnitude());
let lcm = &d / BigInt::from(g) * q.denom();
d = if lcm <= context.bound {
lcm
} else {
q.denom().clone()
};
Some(q)
})
.collect()
};
let chunks = 0..n.div_ceil(Self::RECONSTRUCT_CHUNK);
#[cfg(feature = "parallel")]
let parts = chunks.into_par_iter().map(run).collect::<Option<Vec<_>>>();
#[cfg(not(feature = "parallel"))]
let parts = chunks.map(run).collect::<Option<Vec<_>>>();
parts.map(|v| v.concat())
}
}
pub fn crt(residues: &[(u64, u64)]) -> (BigInt, BigInt) {
let (mut x, mut m) = ([BigInt::zero()], BigInt::one());
for &(v, p) in residues {
garner(&mut x, &mut m, &[v], p);
}
let [x] = x;
(x, m)
}
pub fn wang(x: &BigInt, m: &BigInt) -> Option<BigRational> {
WangContext::new(m)?.reconstruct(x)
}
#[derive(Debug)]
pub struct WangContext<'a> {
modulus: &'a BigInt,
bound: BigInt,
}
impl<'a> WangContext<'a> {
pub fn new(modulus: &'a BigInt) -> Option<Self> {
(modulus > &BigInt::one()).then(|| Self {
modulus,
bound: (modulus / 2u32).sqrt(),
})
}
pub fn reconstruct(&self, x: &BigInt) -> Option<BigRational> {
lehmer::wang(x, self.modulus, &self.bound)
}
pub fn reconstruct_many(&self, xs: &[BigInt]) -> Option<Vec<BigRational>> {
xs.iter().map(|x| self.reconstruct(x)).collect()
}
}
pub fn symmetric(x: &BigInt, m: &BigInt) -> BigInt {
let r = x.mod_floor(m);
if &r * 2u32 > *m { r - m } else { r }
}
pub fn reconstruct(
mut image: impl FnMut(u64) -> Option<Vec<u64>>,
primes: impl IntoIterator<Item = u64>,
) -> Option<Vec<BigRational>> {
reconstruct_keyed(|p| image(p).map(|v| ((), v)), primes).map(|((), q)| q)
}
pub fn reconstruct_keyed<K: Ord>(
mut image: impl FnMut(u64) -> Option<(K, Vec<u64>)>,
primes: impl IntoIterator<Item = u64>,
) -> Option<(K, Vec<BigRational>)> {
let mut acc: Option<(K, Vec<BigInt>, BigInt)> = None;
let mut candidate: Option<Vec<BigRational>> = None;
for p in primes {
let Some((key, values)) = image(p) else {
continue;
};
match &mut acc {
Some((best, ..)) if key < *best => continue,
Some((best, xs, m)) if key == *best => {
let agrees = |q: &Vec<BigRational>| {
q.len() == values.len()
&& q.iter().zip(&values).all(|(r, &v)| reduce(r, p) == Some(v))
};
if candidate.as_ref().is_some_and(agrees) {
return acc.map(|(k, ..)| k).zip(candidate);
}
garner(xs, m, &values, p);
}
_ => acc = Some((key, values.iter().map(|&v| v.into()).collect(), p.into())),
}
candidate = acc
.as_ref()
.and_then(|(_, xs, m)| WangContext::new(m)?.reconstruct_many(xs));
}
None
}
pub fn reconstruct_voted<K: Clone + Eq + Hash>(
mut images: impl FnMut(&[u64]) -> Vec<Option<(K, Vec<u64>)>>,
primes: impl IntoIterator<Item = u64>,
) -> Option<(K, Vec<BigRational>)> {
let mut primes = primes.into_iter();
let mut groups: HashMap<K, (Vec<BigInt>, BigInt, usize)> = HashMap::new();
let mut candidate: Option<(K, Vec<BigRational>)> = None;
let mut batch = 1;
loop {
let ps: Vec<u64> = primes.by_ref().take(batch).collect();
if ps.is_empty() {
return None;
}
let batch_images = images(&ps);
assert_eq!(
batch_images.len(),
ps.len(),
"image callback must return one entry per prime"
);
for (&p, image) in ps.iter().zip(batch_images) {
let Some((key, values)) = image else {
continue;
};
if let Some((k, q)) = &candidate
&& *k == key
&& q.len() == values.len()
&& q.iter().zip(&values).all(|(r, &v)| reduce(r, p) == Some(v))
{
return candidate;
}
if let Some((xs, m, n)) = groups.get_mut(&key) {
garner(xs, m, &values, p);
*n += 1;
} else {
let xs = values.iter().map(|&v| v.into()).collect();
groups.insert(key, (xs, p.into(), 1));
}
}
candidate = groups
.iter()
.max_by_key(|(_, g)| g.2)
.and_then(|(k, (xs, m, _))| {
let context = WangContext::new(m)?;
if let Some(x) = xs.last() {
context.reconstruct(x)?;
}
let q = context.reconstruct_many(xs)?;
Some((k.clone(), q))
});
batch = (batch * 2).min(64);
}
}