use alloc::vec;
use alloc::vec::Vec;
use core::cmp::Ordering;
use crate::int::Int;
use crate::rational::Rational;
pub fn lll_reduce(basis: &[Vec<Int>]) -> Vec<Vec<Int>> {
lll_reduce_delta(basis, &Rational::new(Int::from_i64(3), Int::from_i64(4)))
}
pub fn lll_reduce_delta(basis: &[Vec<Int>], delta: &Rational) -> Vec<Vec<Int>> {
let n = basis.len();
if n <= 1 {
return basis.to_vec();
}
let dim = basis[0].len();
assert!(
basis.iter().all(|v| v.len() == dim),
"lll_reduce: all basis vectors must share the same dimension"
);
let mut b: Vec<Vec<Int>> = basis.to_vec();
let (mut mu, mut bstar_norm) = match gram_schmidt(&b) {
Some(gs) => gs,
None => return basis.to_vec(), };
let half = Rational::new(Int::ONE, Int::from_i64(2));
let mut k = 1;
while k < n {
size_reduce(&mut b, &mut mu, k, k - 1, &half);
let mk = mu[k][k - 1].clone();
let bound = Rational::mul(
&Rational::sub(delta, &Rational::mul(&mk, &mk)),
&bstar_norm[k - 1],
);
if bstar_norm[k].cmp(&bound) != Ordering::Less {
for l in (0..k - 1).rev() {
size_reduce(&mut b, &mut mu, k, l, &half);
}
k += 1;
} else {
swap_step(&mut b, &mut mu, &mut bstar_norm, k, n);
k = if k > 1 { k - 1 } else { 1 };
}
}
b
}
fn gram_schmidt(b: &[Vec<Int>]) -> Option<(Vec<Vec<Rational>>, Vec<Rational>)> {
let n = b.len();
let dim = b[0].len();
let mut mu = vec![vec![Rational::ZERO; n]; n];
let mut norm = vec![Rational::ZERO; n];
let mut bstar: Vec<Vec<Rational>> = Vec::with_capacity(n);
for i in 0..n {
let mut bi: Vec<Rational> = b[i]
.iter()
.map(|x| Rational::from_integer(x.clone()))
.collect();
for j in 0..i {
let dot = dot_ir(&b[i], &bstar[j]);
mu[i][j] = Rational::div(&dot, &norm[j]);
for t in 0..dim {
bi[t] = Rational::sub(&bi[t], &Rational::mul(&mu[i][j], &bstar[j][t]));
}
}
norm[i] = dot_rr(&bi, &bi);
if norm[i].numerator().is_zero() {
return None; }
bstar.push(bi);
}
Some((mu, norm))
}
fn size_reduce(b: &mut [Vec<Int>], mu: &mut [Vec<Rational>], k: usize, l: usize, half: &Rational) {
if mu[k][l].abs().cmp(half) != Ordering::Greater {
return;
}
let q = round_to_int(&mu[k][l]);
if q.is_zero() {
return;
}
let dim = b[k].len();
let bl = b[l].clone();
for t in 0..dim {
b[k][t] = b[k][t].sub(&q.mul(&bl[t]));
}
let qr = Rational::from_integer(q);
let mul = mu[l].clone();
for j in 0..l {
mu[k][j] = Rational::sub(&mu[k][j], &Rational::mul(&qr, &mul[j]));
}
mu[k][l] = Rational::sub(&mu[k][l], &qr);
}
#[allow(clippy::needless_range_loop)] fn swap_step(
b: &mut [Vec<Int>],
mu: &mut [Vec<Rational>],
norm: &mut [Rational],
k: usize,
n: usize,
) {
let mu_old = mu[k][k - 1].clone();
b.swap(k, k - 1);
for j in 0..k - 1 {
let tmp = mu[k][j].clone();
mu[k][j] = mu[k - 1][j].clone();
mu[k - 1][j] = tmp;
}
let bnew = Rational::add(
&norm[k],
&Rational::mul(&Rational::mul(&mu_old, &mu_old), &norm[k - 1]),
);
mu[k][k - 1] = Rational::div(&Rational::mul(&mu_old, &norm[k - 1]), &bnew);
norm[k] = Rational::div(&Rational::mul(&norm[k - 1], &norm[k]), &bnew);
norm[k - 1] = bnew;
let mk = mu[k][k - 1].clone();
for i in k + 1..n {
let t = mu[i][k].clone();
mu[i][k] = Rational::sub(&mu[i][k - 1], &Rational::mul(&mu_old, &t));
mu[i][k - 1] = Rational::add(&t, &Rational::mul(&mk, &mu[i][k]));
}
}
fn round_to_int(r: &Rational) -> Int {
let two = Int::from_i64(2);
let num2 = r.numerator().mul(&two);
let den = r.denominator();
num2.add(den).div_floor(&den.mul(&two))
}
fn dot_ir(a: &[Int], b: &[Rational]) -> Rational {
let mut acc = Rational::ZERO;
for (x, y) in a.iter().zip(b) {
acc.addmul(&Rational::from_integer(x.clone()), y);
}
acc
}
fn dot_rr(a: &[Rational], b: &[Rational]) -> Rational {
let mut acc = Rational::ZERO;
for (x, y) in a.iter().zip(b) {
acc.addmul(x, y);
}
acc
}
#[cfg(feature = "float")]
pub use relations::{find_integer_relation, minimal_polynomial};
#[cfg(feature = "float")]
mod relations {
use super::{Int, Rational, Vec, lll_reduce, round_to_int};
use crate::float::{Float, RoundingMode};
pub fn find_integer_relation(xs: &[Float], scale_bits: u64) -> Option<Vec<Int>> {
let n = xs.len();
if n == 0 {
return None;
}
if n == 1 {
return xs[0].is_zero().then(|| alloc::vec![Int::ONE]);
}
let pow2 = Rational::from_integer(Int::ONE.mul_2k(scale_bits as u32));
let mut basis: Vec<Vec<Int>> = Vec::with_capacity(n);
for (i, x) in xs.iter().enumerate() {
let r = x.to_rational()?; let mut row = alloc::vec![Int::ZERO; n + 1];
row[i] = Int::ONE;
row[n] = round_to_int(&Rational::mul(&r, &pow2));
basis.push(row);
}
let reduced = lll_reduce(&basis);
let short = &reduced[0];
let cand = &short[..n];
if cand.iter().all(Int::is_zero) {
return None;
}
let threshold_bits = scale_bits / (2 * n as u64);
if short
.iter()
.any(|e| u64::from(e.bit_len()) > threshold_bits)
{
return None;
}
Some(normalize_sign(cand.to_vec()))
}
pub fn minimal_polynomial(
alpha: &Float,
max_degree: usize,
scale_bits: u64,
) -> Option<Vec<Int>> {
let prec = alpha.precision();
let m = RoundingMode::Nearest;
let mut powers = Vec::with_capacity(max_degree + 1);
powers.push(Float::from_int(&Int::ONE, prec, m));
for _ in 1..=max_degree {
powers.push(powers.last().unwrap().mul(alpha, prec, m));
}
(1..=max_degree).find_map(|d| find_integer_relation(&powers[..=d], scale_bits))
}
fn normalize_sign(mut v: Vec<Int>) -> Vec<Int> {
if let Some(first) = v.iter().find(|c| !c.is_zero())
&& first.is_negative()
{
for c in &mut v {
*c = c.neg();
}
}
v
}
}