use super::error::LatticeGeometryError;
use rug::ops::DivRounding;
use rug::{Integer, Rational};
pub(crate) struct Found<'a> {
pub(crate) x: &'a [Integer],
pub(crate) norm: Rational,
}
pub(crate) struct Enumerator {
mu: Vec<Vec<Rational>>,
b: Vec<Rational>,
b_split: Vec<(Integer, Integer)>,
budget: u64,
nodes: u64,
}
impl Enumerator {
pub(crate) fn new(mu: Vec<Vec<Rational>>, b: Vec<Rational>, budget: u64) -> Self {
let b_split = b
.iter()
.map(|v| (Integer::from(v.numer()), Integer::from(v.denom())))
.collect();
Self {
mu,
b,
b_split,
budget,
nodes: 0,
}
}
pub(crate) fn enumerate<F>(
&mut self,
bound: &Rational,
center: Option<&[Rational]>,
half: bool,
visit: &mut F,
) -> Result<(), LatticeGeometryError>
where
F: FnMut(Found<'_>),
{
let m = self.b.len();
if m == 0 || *bound < 0 {
return Ok(());
}
self.nodes = 0;
let mut x = vec![Integer::new(); m];
let mut u: Vec<Rational> = match center {
Some(t) => t.iter().map(|v| -v.clone()).collect(),
None => vec![Rational::new(); m],
};
self.rec(
m - 1,
bound.clone(),
Rational::new(),
&mut x,
&mut u,
center,
half,
visit,
)
}
#[allow(clippy::too_many_arguments)]
fn rec<F>(
&mut self,
level: usize,
rem: Rational,
used: Rational,
x: &mut Vec<Integer>,
u: &mut Vec<Rational>,
center: Option<&[Rational]>,
leading_free: bool,
visit: &mut F,
) -> Result<(), LatticeGeometryError>
where
F: FnMut(Found<'_>),
{
self.nodes += 1;
if self.nodes > self.budget {
return Err(LatticeGeometryError::EnumerationBudget {
budget: self.budget,
});
}
let (lo, hi) = coordinate_range(&self.b_split[level], &u[level], &rem);
let lo = if leading_free {
lo.max(Integer::new())
} else {
lo
};
let mut xi = lo;
while xi <= hi {
let shifted = Rational::from(&xi) + &u[level];
let term = Rational::from(&shifted * &shifted) * &self.b[level];
if term <= rem {
x[level] = xi.clone();
if level == 0 {
let q = Rational::from(&used + &term);
visit(Found { x, norm: q });
} else {
let next_rem = rem.clone() - &term;
let next_used = Rational::from(&used + &term);
let delta_i = match center {
Some(t) => Rational::from(&xi) - &t[level],
None => Rational::from(&xi),
};
for (uj, m) in u.iter_mut().zip(self.mu[level].iter()).take(level) {
*uj += Rational::from(m * &delta_i);
}
let still_free = leading_free && xi == 0;
self.rec(
level - 1,
next_rem,
next_used,
x,
u,
center,
still_free,
visit,
)?;
for (uj, m) in u.iter_mut().zip(self.mu[level].iter()).take(level) {
*uj -= Rational::from(m * &delta_i);
}
}
}
xi += 1;
}
x[level] = Integer::new();
Ok(())
}
pub(crate) fn babai_bound(&self, center: &[Rational]) -> Rational {
let m = self.b.len();
let mut x = vec![Integer::new(); m];
let mut u: Vec<Rational> = center.iter().map(|v| -v.clone()).collect();
for level in (0..m).rev() {
let xi = super::quadform::round_rational(&-u[level].clone());
x[level] = xi.clone();
let delta_i = Rational::from(&xi) - ¢er[level];
for (uj, m) in u.iter_mut().zip(self.mu[level].iter()).take(level) {
*uj += Rational::from(m * &delta_i);
}
}
self.exact_norm(&x, Some(center))
}
fn exact_norm(&self, x: &[Integer], center: Option<&[Rational]>) -> Rational {
let m = self.b.len();
let mut acc = Rational::new();
for j in 0..m {
let mut s = match center {
Some(t) => Rational::from(&x[j]) - &t[j],
None => Rational::from(&x[j]),
};
for i in (j + 1)..m {
let di = match center {
Some(t) => Rational::from(&x[i]) - &t[i],
None => Rational::from(&x[i]),
};
s += Rational::from(&self.mu[i][j] * &di);
}
acc += Rational::from(&s * &s) * &self.b[j];
}
acc
}
}
fn coordinate_range(b: &(Integer, Integer), u: &Rational, rem: &Rational) -> (Integer, Integer) {
let empty = (Integer::from(1), Integer::new());
if *rem < 0 {
return empty;
}
let (bp, bq) = b;
debug_assert!(*bp > 0 && *bq > 0);
let a = Integer::from(u.numer());
let d = Integer::from(u.denom()); let num = Integer::from(rem.numer()) * bq * &d * &d;
let den = Integer::from(rem.denom()) * bp;
let floor = num.div_floor(den);
if floor < 0 {
return empty;
}
let root = floor.sqrt(); let lo = (Integer::from(-&root) - &a).div_ceil(d.clone());
let hi = (root - &a).div_floor(d);
(lo, hi)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn coordinate_range_is_exact_on_the_boundary() {
let one = (Integer::from(1), Integer::from(1));
let two = (Integer::from(2), Integer::from(1));
let nine = (Integer::from(9), Integer::from(1));
let (lo, hi) = coordinate_range(&one, &Rational::new(), &Rational::from(4));
assert_eq!((lo, hi), (Integer::from(-2), Integer::from(2)));
let (lo, hi) = coordinate_range(&two, &Rational::from((1, 2)), &Rational::from(2));
assert_eq!((lo, hi), (Integer::from(-1), Integer::from(0)));
let (lo, hi) = coordinate_range(&two, &Rational::new(), &Rational::from((7, 4)));
assert_eq!((lo, hi), (Integer::from(0), Integer::from(0)));
let (lo, hi) = coordinate_range(&nine, &Rational::from((1, 2)), &Rational::from(1));
assert!(lo > hi);
}
#[test]
fn accumulated_norm_matches_recomputation() {
let forms: Vec<Vec<Vec<Rational>>> = vec![
vec![
vec![Rational::from(2), Rational::from(-1)],
vec![Rational::from(-1), Rational::from(2)],
],
vec![
vec![Rational::from(5), Rational::from(3)],
vec![Rational::from(3), Rational::from(4)],
],
vec![
vec![Rational::from(1), Rational::new(), Rational::new()],
vec![Rational::new(), Rational::from(2), Rational::from(1)],
vec![Rational::new(), Rational::from(1), Rational::from(3)],
],
];
let centers: Vec<Option<Vec<Rational>>> = vec![
None,
Some(vec![Rational::from((1, 3)), Rational::from((1, 2))]),
];
for g in &forms {
let (mu, b) = crate::lattice::quadform::gram_schmidt(g).unwrap();
for c in ¢ers {
if let Some(t) = c {
if t.len() != g.len() {
continue;
}
}
let mut e = Enumerator::new(mu.clone(), b.clone(), 1_000_000);
let reference = Enumerator::new(mu.clone(), b.clone(), 1);
let mut checked = 0usize;
e.enumerate(&Rational::from(12), c.as_deref(), false, &mut |found| {
let want = reference.exact_norm(found.x, c.as_deref());
assert_eq!(found.norm, want, "x = {:?}", found.x);
checked += 1;
})
.unwrap();
assert!(checked > 0);
}
}
}
#[test]
fn budget_is_a_refusal_not_a_truncation() {
let mu = vec![vec![Rational::new(); 3]; 3];
let b = vec![Rational::from(1), Rational::from(1), Rational::from(1)];
let mut e = Enumerator::new(mu, b, 3);
let mut seen = 0usize;
let err = e.enumerate(&Rational::from(100), None, false, &mut |_| seen += 1);
assert!(matches!(
err,
Err(LatticeGeometryError::EnumerationBudget { .. })
));
}
}