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, pslq, pslq_with};
#[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))
}
pub fn pslq(xs: &[Float], precision: u64) -> Option<Vec<Int>> {
let n = xs.len();
let detect_bits = precision - precision / 4;
let max_iters = (precision as usize + 20) * n.max(1) * n.max(1);
pslq_with(xs, precision, max_iters, detect_bits)
}
pub fn pslq_with(
xs: &[Float],
precision: u64,
max_iters: usize,
detect_bits: u64,
) -> Option<Vec<Int>> {
let n = xs.len();
if n == 0 {
return None;
}
if xs.iter().any(|v| !v.is_finite()) {
return None;
}
if n == 1 {
return xs[0].is_zero().then(|| alloc::vec![Int::ONE]);
}
let m = RoundingMode::Nearest;
let wp = precision + 64;
let x: Vec<Float> = xs.iter().map(|v| v.round(wp, m)).collect();
let mut s = alloc::vec![Float::zero(wp); n];
let mut acc = Float::zero(wp);
for k in (0..n).rev() {
acc = acc.add(&x[k].mul(&x[k], wp, m), wp, m);
s[k] = acc.sqrt(wp, m);
}
if s[0].is_zero() {
let mut a = alloc::vec![Int::ZERO; n];
a[0] = Int::ONE;
return Some(a);
}
let one = Float::from_int(&Int::ONE, wp, m);
let t0 = one.div(&s[0], wp, m);
let mut y: Vec<Float> = x.iter().map(|xi| t0.mul(xi, wp, m)).collect();
for sk in &mut s {
*sk = t0.mul(sk, wp, m);
}
let mut h = alloc::vec![alloc::vec![Float::zero(wp); n - 1]; n];
for j in 0..n - 1 {
h[j][j] = s[j + 1].div(&s[j], wp, m);
let sj = s[j].mul(&s[j + 1], wp, m);
for i in j + 1..n {
h[i][j] = y[i].mul(&y[j], wp, m).div(&sj, wp, m).neg();
}
}
let mut a_mat = alloc::vec![alloc::vec![Int::ZERO; n]; n];
let mut b_mat = alloc::vec![alloc::vec![Int::ZERO; n]; n];
for i in 0..n {
a_mat[i][i] = Int::ONE;
b_mat[i][i] = Int::ONE;
}
let gamma = Float::from_int(&Int::from_i64(2), wp, m).div(
&Float::from_int(&Int::from_i64(3), wp, m).sqrt(wp, m),
wp,
m,
);
let mut gpow = Vec::with_capacity(n - 1);
let mut g = gamma.clone();
for _ in 0..n - 1 {
gpow.push(g.clone());
g = g.mul(&gamma, wp, m);
}
let coeff_bits = detect_bits / (2 * (n as u64 - 1));
let eps = Float::from_rational(
&Rational::new(
Int::ONE,
Int::ONE.mul_2k(detect_bits.min(u32::MAX as u64) as u32),
),
wp,
m,
);
hermite_reduce(n, wp, m, &mut y, &mut h, &mut a_mat, &mut b_mat);
for _ in 0..max_iters {
let mut sel = 0;
let mut best = gpow[0].mul(&h[0][0].abs(), wp, m);
for k in 1..n - 1 {
let v = gpow[k].mul(&h[k][k].abs(), wp, m);
if v > best {
best = v;
sel = k;
}
}
y.swap(sel, sel + 1);
h.swap(sel, sel + 1);
a_mat.swap(sel, sel + 1);
for row in &mut b_mat {
row.swap(sel, sel + 1);
}
if sel < n - 2 {
let hmm = h[sel][sel].clone();
let hmm1 = h[sel][sel + 1].clone();
let t = hmm
.mul(&hmm, wp, m)
.add(&hmm1.mul(&hmm1, wp, m), wp, m)
.sqrt(wp, m);
let t1 = hmm.div(&t, wp, m);
let t2 = hmm1.div(&t, wp, m);
for row in h.iter_mut().take(n).skip(sel) {
let t3 = row[sel].clone();
let t4 = row[sel + 1].clone();
row[sel] = t1.mul(&t3, wp, m).add(&t2.mul(&t4, wp, m), wp, m);
row[sel + 1] = t1.mul(&t4, wp, m).sub(&t2.mul(&t3, wp, m), wp, m);
}
}
hermite_reduce(n, wp, m, &mut y, &mut h, &mut a_mat, &mut b_mat);
let mut kmin = 0;
let mut ymin = y[0].abs();
for (k, yk) in y.iter().enumerate().skip(1) {
let ak = yk.abs();
if ak < ymin {
ymin = ak;
kmin = k;
}
}
if ymin < eps {
let rel: Vec<Int> = (0..n).map(|j| b_mat[j][kmin].clone()).collect();
if rel.iter().any(|c| u64::from(c.bit_len()) > coeff_bits) {
return None;
}
if rel.iter().any(|c| !c.is_zero()) {
return Some(normalize_sign(rel));
}
}
}
None
}
#[allow(clippy::too_many_arguments)]
fn hermite_reduce(
n: usize,
wp: u64,
m: RoundingMode,
y: &mut [Float],
h: &mut [Vec<Float>],
a: &mut [Vec<Int>],
b: &mut [Vec<Int>],
) {
for i in 1..n {
for j in (0..i).rev() {
let q = match h[i][j].div(&h[j][j], wp, m).round_to_int() {
Some(q) if !q.is_zero() => q,
_ => continue,
};
let qf = Float::from_int(&q, wp, m);
let dy = qf.mul(&y[i], wp, m);
y[j] = y[j].add(&dy, wp, m);
let hj = h[j].clone();
for k in 0..=j {
let d = qf.mul(&hj[k], wp, m);
h[i][k] = h[i][k].sub(&d, wp, m);
}
let aj = a[j].clone();
for k in 0..n {
a[i][k] = a[i][k].sub(&q.mul(&aj[k]));
}
for row in b.iter_mut() {
let bki = row[i].clone();
row[j] = row[j].add(&q.mul(&bki));
}
}
}
}
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
}
}