use std::collections::HashMap;
use std::hash::Hash;
use num_bigint::BigInt;
use num_integer::Integer;
use num_rational::BigRational;
use num_traits::{One, Signed, ToPrimitive, Zero};
use crate::modp::{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)
}
}
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> {
let (mut r0, mut r1) = (self.modulus.clone(), x.mod_floor(self.modulus));
let (mut s0, mut s1) = (BigInt::zero(), BigInt::one());
while r1 > self.bound {
let q = &r0 / &r1;
let r = &r0 - &q * &r1;
r0 = std::mem::replace(&mut r1, r);
let s = &s0 - &q * &s1;
s0 = std::mem::replace(&mut s1, s);
}
(!s1.is_zero() && s1.abs() <= self.bound && r1.gcd(&s1).is_one())
.then(|| BigRational::new(r1, s1))
}
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);
}
}