1use logicaffeine_base::numeric::{BigInt, Rational};
21
22fn zero() -> Rational {
23 Rational::zero()
24}
25
26fn rat(n: i64, d: i64) -> Rational {
27 Rational::new(BigInt::from_i64(n), BigInt::from_i64(d)).expect("nonzero denominator")
28}
29
30fn dot(a: &[Rational], b: &[Rational]) -> Rational {
32 a.iter().zip(b).fold(zero(), |s, (x, y)| s.add(&x.mul(y)))
33}
34
35fn gram_schmidt(b: &[Vec<Rational>]) -> (Vec<Vec<Rational>>, Vec<Vec<Rational>>) {
38 let n = b.len();
39 let mut bstar: Vec<Vec<Rational>> = Vec::with_capacity(n);
40 let mut mu = vec![vec![zero(); n]; n];
41 for i in 0..n {
42 let mut v = b[i].clone();
43 for j in 0..i {
44 let m = dot(&b[i], &bstar[j]).div(&dot(&bstar[j], &bstar[j])).expect("independent basis");
45 for d in 0..v.len() {
46 v[d] = v[d].sub(&m.mul(&bstar[j][d]));
47 }
48 mu[i][j] = m;
49 }
50 bstar.push(v);
51 }
52 (bstar, mu)
53}
54
55fn lll_core(mut b: Vec<Vec<Rational>>) -> Vec<Vec<Rational>> {
57 let n = b.len();
58 if n < 2 {
59 return b;
60 }
61 let delta = rat(3, 4);
62 let half = rat(1, 2);
63 let (mut bstar, mut mu) = gram_schmidt(&b);
64 let mut norm: Vec<Rational> = (0..n).map(|i| dot(&bstar[i], &bstar[i])).collect();
65
66 let mut k = 1;
67 while k < n {
68 for j in (0..k).rev() {
70 if mu[k][j].abs() > half {
71 let q = mu[k][j].round(); let qr = Rational::from_bigint(q);
73 for d in 0..b[k].len() {
74 b[k][d] = b[k][d].sub(&qr.mul(&b[j][d]));
75 }
76 mu[k][j] = mu[k][j].sub(&qr);
77 for l in 0..j {
78 mu[k][l] = mu[k][l].sub(&qr.mul(&mu[j][l]));
79 }
80 }
81 }
82 let rhs = delta.sub(&mu[k][k - 1].mul(&mu[k][k - 1])).mul(&norm[k - 1]);
84 if norm[k] >= rhs {
85 k += 1;
86 } else {
87 b.swap(k, k - 1);
88 let (bs, m) = gram_schmidt(&b);
89 bstar = bs;
90 mu = m;
91 norm = (0..n).map(|i| dot(&bstar[i], &bstar[i])).collect();
92 k = if k >= 2 { k - 1 } else { 1 };
93 }
94 }
95 b
96}
97
98pub fn lll_reduce(basis: &[Vec<i64>]) -> Vec<Vec<BigInt>> {
104 let b = basis.iter().map(|row| row.iter().map(|&x| Rational::from_i64(x)).collect()).collect();
105 lll_core(b).iter().map(|row| row.iter().map(|r| r.round()).collect()).collect()
106}
107
108fn bit_len(x: &BigInt) -> usize {
110 let (_, bytes) = x.to_le_bytes();
111 for (i, &byte) in bytes.iter().enumerate().rev() {
112 if byte != 0 {
113 return i * 8 + (8 - byte.leading_zeros() as usize);
114 }
115 }
116 0
117}
118
119fn gram_schmidt_f64(b: &[Vec<BigInt>]) -> (Vec<Vec<f64>>, Vec<f64>) {
122 let (n, dim) = (b.len(), b[0].len());
123 let maxbits = b.iter().flatten().map(bit_len).max().unwrap_or(0);
124 let shift = maxbits.saturating_sub(900);
125 let mut scale = BigInt::from_i64(1);
126 for _ in 0..shift {
127 scale = scale.mul(&BigInt::from_i64(2));
128 }
129 let cf: Vec<Vec<f64>> = b
130 .iter()
131 .map(|row| row.iter().map(|x| Rational::new(x.clone(), scale.clone()).map(|r| r.to_f64()).unwrap_or(0.0)).collect())
132 .collect();
133 let mut bstar = vec![vec![0f64; dim]; n];
134 let mut mu = vec![vec![0f64; n]; n];
135 for i in 0..n {
136 bstar[i].clone_from(&cf[i]);
137 for j in 0..i {
138 let dp: f64 = cf[i].iter().zip(&bstar[j]).map(|(a, c)| a * c).sum();
139 let nj: f64 = bstar[j].iter().map(|c| c * c).sum();
140 let m = if nj != 0.0 { dp / nj } else { 0.0 };
141 mu[i][j] = m;
142 for d in 0..dim {
143 bstar[i][d] -= m * bstar[j][d];
144 }
145 }
146 }
147 let norm = bstar.iter().map(|v| v.iter().map(|c| c * c).sum()).collect();
148 (mu, norm)
149}
150
151fn vdot(u: &[BigInt], v: &[BigInt]) -> BigInt {
152 u.iter().zip(v).fold(BigInt::zero(), |a, (x, y)| a.add(&x.mul(y)))
153}
154
155const FP_PREC: u64 = 2048;
163
164fn bit_length(x: &BigInt) -> u64 {
165 let (_, bytes) = x.to_le_bytes();
166 for (i, &b) in bytes.iter().enumerate().rev() {
167 if b != 0 {
168 return i as u64 * 8 + (8 - b.leading_zeros() as u64);
169 }
170 }
171 0
172}
173
174fn two_pow(k: u64) -> BigInt {
175 BigInt::from_i64(2).pow(k as u32)
176}
177
178fn shl(x: &BigInt, k: u64) -> BigInt {
179 if k == 0 {
180 x.clone()
181 } else {
182 x.mul(&two_pow(k))
183 }
184}
185
186fn shr(x: &BigInt, k: u64) -> BigInt {
187 if k == 0 {
188 x.clone()
189 } else {
190 x.div_rem(&two_pow(k)).expect("nonzero").0
191 }
192}
193
194#[derive(Clone)]
195struct BigFloat {
196 m: BigInt,
197 e: i64,
198}
199
200impl BigFloat {
201 fn normalized(m: BigInt, e: i64) -> Self {
202 if m.is_zero() {
203 return Self { m, e: 0 };
204 }
205 let bl = bit_length(&m);
206 if bl > FP_PREC {
207 let s = bl - FP_PREC;
208 Self { m: shr(&m, s), e: e + s as i64 }
209 } else {
210 Self { m, e }
211 }
212 }
213 fn zero() -> Self {
214 Self { m: BigInt::zero(), e: 0 }
215 }
216 fn from_bigint(n: &BigInt) -> Self {
217 Self::normalized(n.clone(), 0)
218 }
219 fn from_i64(n: i64) -> Self {
220 Self::from_bigint(&BigInt::from_i64(n))
221 }
222 fn add(&self, o: &Self) -> Self {
223 if self.m.is_zero() {
224 return o.clone();
225 }
226 if o.m.is_zero() {
227 return self.clone();
228 }
229 let mag_s = self.e + bit_length(&self.m) as i64;
232 let mag_o = o.e + bit_length(&o.m) as i64;
233 if mag_s > mag_o + FP_PREC as i64 + 8 {
234 return self.clone();
235 }
236 if mag_o > mag_s + FP_PREC as i64 + 8 {
237 return o.clone();
238 }
239 let e = self.e.min(o.e);
240 let ma = shl(&self.m, (self.e - e) as u64);
241 let mb = shl(&o.m, (o.e - e) as u64);
242 Self::normalized(ma.add(&mb), e)
243 }
244 fn neg(&self) -> Self {
245 Self { m: self.m.negated(), e: self.e }
246 }
247 fn sub(&self, o: &Self) -> Self {
248 self.add(&o.neg())
249 }
250 fn mul(&self, o: &Self) -> Self {
251 Self::normalized(self.m.mul(&o.m), self.e + o.e)
252 }
253 fn div(&self, o: &Self) -> Self {
254 let num = shl(&self.m, FP_PREC);
255 Self::normalized(num.div_rem(&o.m).expect("nonzero div").0, self.e - o.e - FP_PREC as i64)
256 }
257 fn abs(&self) -> Self {
258 Self { m: self.m.abs(), e: self.e }
259 }
260 fn cmp(&self, o: &Self) -> std::cmp::Ordering {
261 let d = self.sub(o).m;
262 if d.is_zero() {
263 std::cmp::Ordering::Equal
264 } else if d.is_negative() {
265 std::cmp::Ordering::Less
266 } else {
267 std::cmp::Ordering::Greater
268 }
269 }
270 fn round(&self) -> BigInt {
272 if self.e >= 0 {
273 shl(&self.m, self.e as u64)
274 } else {
275 let k = (-self.e) as u64;
276 let half = two_pow(k - 1);
277 let adj = if self.m.is_negative() { self.m.sub(&half) } else { self.m.add(&half) };
278 adj.div_rem(&two_pow(k)).expect("nonzero").0
279 }
280 }
281}
282
283fn dot_bf(u: &[BigFloat], v: &[BigFloat]) -> BigFloat {
284 u.iter().zip(v).fold(BigFloat::zero(), |a, (x, y)| a.add(&x.mul(y)))
285}
286
287fn gram_schmidt_bf(b: &[Vec<BigInt>]) -> (Vec<Vec<BigFloat>>, Vec<BigFloat>) {
288 let (n, dim) = (b.len(), b[0].len());
289 let cf: Vec<Vec<BigFloat>> = b.iter().map(|r| r.iter().map(BigFloat::from_bigint).collect()).collect();
290 let mut bstar = vec![vec![BigFloat::zero(); dim]; n];
291 let mut mu = vec![vec![BigFloat::zero(); n]; n];
292 for i in 0..n {
293 bstar[i].clone_from(&cf[i]);
294 for j in 0..i {
295 let m = dot_bf(&cf[i], &bstar[j]).div(&dot_bf(&bstar[j], &bstar[j]));
296 for d in 0..dim {
297 bstar[i][d] = bstar[i][d].sub(&m.mul(&bstar[j][d]));
298 }
299 mu[i][j] = m;
300 }
301 }
302 let norm = bstar.iter().map(|v| dot_bf(v, v)).collect();
303 (mu, norm)
304}
305
306fn swap_gso(k: usize, n: usize, b: &mut [Vec<BigInt>], mu: &mut [Vec<BigFloat>], norm: &mut [BigFloat]) {
309 b.swap(k, k - 1);
310 let nu = mu[k][k - 1].clone();
311 let db = norm[k].add(&nu.mul(&nu).mul(&norm[k - 1])); let new_mu = nu.mul(&norm[k - 1]).div(&db);
313 let new_norm_k = norm[k - 1].mul(&norm[k]).div(&db);
314 {
315 let (lower, upper) = mu.split_at_mut(k);
316 let (row_km1, row_k) = (&mut lower[k - 1], &mut upper[0]);
317 for j in 0..(k - 1) {
318 std::mem::swap(&mut row_km1[j], &mut row_k[j]);
319 }
320 }
321 mu[k][k - 1] = new_mu.clone();
322 norm[k] = new_norm_k;
323 norm[k - 1] = db;
324 for i in (k + 1)..n {
325 let t = mu[i][k].clone();
326 mu[i][k] = mu[i][k - 1].sub(&nu.mul(&t));
327 mu[i][k - 1] = t.add(&new_mu.mul(&mu[i][k]));
328 }
329}
330
331pub fn lll_reduce_bigint_fp(basis: &[Vec<BigInt>]) -> Vec<Vec<BigInt>> {
337 let n = basis.len();
338 if n < 2 {
339 return basis.to_vec();
340 }
341 let dim = basis[0].len();
342 let mut b = basis.to_vec();
343 let delta = BigFloat::from_i64(3).div(&BigFloat::from_i64(4));
344 let half = BigFloat::from_i64(1).div(&BigFloat::from_i64(2));
345 let (mut mu, mut norm) = gram_schmidt_bf(&b);
346 let max_bits = b.iter().flatten().map(bit_length).max().unwrap_or(1) as usize;
347 let cap = (max_bits + 16) * n * n * 4;
348 let mut k = 1;
349 let mut guard = 0usize;
350 while k < n && guard < cap {
351 guard += 1;
352 for j in (0..k).rev() {
353 if mu[k][j].abs().cmp(&half) == std::cmp::Ordering::Greater {
354 let q = mu[k][j].round();
355 if !q.is_zero() {
356 let qf = BigFloat::from_bigint(&q);
357 for d in 0..dim {
358 b[k][d] = b[k][d].sub(&q.mul(&b[j][d]));
359 }
360 mu[k][j] = mu[k][j].sub(&qf);
361 for l in 0..j {
362 mu[k][l] = mu[k][l].sub(&qf.mul(&mu[j][l]));
363 }
364 }
365 }
366 }
367 let rhs = delta.sub(&mu[k][k - 1].mul(&mu[k][k - 1])).mul(&norm[k - 1]);
368 if norm[k].cmp(&rhs) != std::cmp::Ordering::Less {
369 k += 1;
370 } else {
371 swap_gso(k, n, &mut b, &mut mu, &mut norm);
372 k = if k >= 2 { k - 1 } else { 1 };
373 }
374 }
375 b
376}
377
378fn round_div(a: &BigInt, b: &BigInt) -> BigInt {
380 let (q, r) = a.div_rem(b).expect("nonzero");
381 if BigInt::from_i64(2).mul(&r.abs()) > *b {
382 if a.is_negative() {
383 q.sub(&BigInt::from_i64(1))
384 } else {
385 q.add(&BigInt::from_i64(1))
386 }
387 } else {
388 q
389 }
390}
391
392fn redi(k: usize, l: usize, b: &mut [Vec<BigInt>], d: &[BigInt], lam: &mut [Vec<BigInt>]) {
393 if BigInt::from_i64(2).mul(&lam[k][l].abs()) <= d[l] {
394 return;
395 }
396 let q = round_div(&lam[k][l], &d[l]);
397 let bl = b[l].clone();
398 for c in 0..bl.len() {
399 b[k][c] = b[k][c].sub(&q.mul(&bl[c]));
400 }
401 lam[k][l] = lam[k][l].sub(&q.mul(&d[l]));
402 let laml = lam[l].clone();
403 for i in 1..l {
404 lam[k][i] = lam[k][i].sub(&q.mul(&laml[i]));
405 }
406}
407
408fn swapi(k: usize, n: usize, b: &mut [Vec<BigInt>], d: &mut [BigInt], lam: &mut [Vec<BigInt>]) {
409 b.swap(k, k - 1);
410 for j in 1..=k.saturating_sub(2) {
411 let t = lam[k][j].clone();
412 lam[k][j] = lam[k - 1][j].clone();
413 lam[k - 1][j] = t;
414 }
415 let lambda = lam[k][k - 1].clone();
416 let bval = d[k - 2].mul(&d[k]).add(&lambda.mul(&lambda)).div_rem(&d[k - 1]).expect("exact").0;
417 for i in (k + 1)..=n {
418 let t = lam[i][k].clone();
419 let new_ik = d[k].mul(&lam[i][k - 1]).sub(&lambda.mul(&t)).div_rem(&d[k - 1]).expect("exact").0;
420 let new_ik1 = bval.mul(&t).add(&lambda.mul(&new_ik)).div_rem(&d[k]).expect("exact").0;
421 lam[i][k] = new_ik;
422 lam[i][k - 1] = new_ik1;
423 }
424 d[k - 1] = bval;
425}
426
427pub fn lll_reduce_bigint_exact(basis: &[Vec<BigInt>]) -> Vec<Vec<BigInt>> {
433 let n = basis.len();
434 if n < 2 {
435 return basis.to_vec();
436 }
437 let mut b: Vec<Vec<BigInt>> = vec![Vec::new()]; b.extend(basis.iter().cloned());
439 let mut d = vec![BigInt::zero(); n + 2];
440 d[0] = BigInt::from_i64(1);
441 let mut lam = vec![vec![BigInt::zero(); n + 2]; n + 2];
442
443 d[1] = vdot(&b[1], &b[1]);
444 let mut k = 2;
445 let mut kmax = 1;
446 while k <= n {
447 if k > kmax {
448 kmax = k;
449 for j in 1..=k {
450 let mut u = vdot(&b[k], &b[j]);
451 for i in 1..j {
452 u = d[i].mul(&u).sub(&lam[k][i].mul(&lam[j][i])).div_rem(&d[i - 1]).expect("exact").0;
453 }
454 if j < k {
455 lam[k][j] = u;
456 } else {
457 d[k] = u;
458 }
459 }
460 }
461 redi(k, k - 1, &mut b, &d, &mut lam);
462 let lhs = BigInt::from_i64(4).mul(&d[k]).mul(&d[k - 2]);
463 let rhs = BigInt::from_i64(3)
464 .mul(&d[k - 1].mul(&d[k - 1]))
465 .sub(&BigInt::from_i64(4).mul(&lam[k][k - 1].mul(&lam[k][k - 1])));
466 if lhs < rhs {
467 swapi(k, n, &mut b, &mut d, &mut lam);
468 k = if k - 1 >= 2 { k - 1 } else { 2 };
469 } else {
470 for l in (1..=k.saturating_sub(2)).rev() {
471 redi(k, l, &mut b, &d, &mut lam);
472 }
473 k += 1;
474 }
475 }
476 b.into_iter().skip(1).collect()
477}
478
479pub fn lll_reduce_bigint(basis: &[Vec<BigInt>]) -> Vec<Vec<BigInt>> {
485 let n = basis.len();
486 if n < 2 {
487 return basis.to_vec();
488 }
489 let dim = basis[0].len();
490 let mut b = basis.to_vec();
491 let (mut mu, mut norm) = gram_schmidt_f64(&b);
492 let mut k = 1;
493 let mut guard = 0usize;
494 let cap = 2000 * n * n; while k < n && guard < cap {
496 guard += 1;
497 for j in (0..k).rev() {
498 if mu[k][j].abs() > 0.5 {
499 let q = mu[k][j].round();
500 if q != 0.0 {
501 let qb = BigInt::parse_decimal(&format!("{q:.0}")).unwrap_or_else(BigInt::zero);
502 for d in 0..dim {
503 b[k][d] = b[k][d].sub(&qb.mul(&b[j][d]));
504 }
505 mu[k][j] -= q;
506 for l in 0..j {
507 mu[k][l] -= q * mu[j][l];
508 }
509 }
510 }
511 }
512 if norm[k] >= (0.75 - mu[k][k - 1] * mu[k][k - 1]) * norm[k - 1] {
513 k += 1;
514 } else {
515 b.swap(k, k - 1);
516 let (m, nn) = gram_schmidt_f64(&b);
517 mu = m;
518 norm = nn;
519 k = if k >= 2 { k - 1 } else { 1 };
520 }
521 }
522 b
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 fn as_i64(v: &[Vec<BigInt>]) -> Vec<Vec<i64>> {
530 v.iter().map(|row| row.iter().map(|x| x.to_i64().expect("small entry")).collect()).collect()
531 }
532
533 fn norm_sq(row: &[i64]) -> i64 {
534 row.iter().map(|&x| x * x).sum()
535 }
536
537 #[test]
538 fn lll_reduces_a_skewed_basis_to_the_standard_one() {
539 let reduced = as_i64(&lll_reduce(&[vec![1, 0], vec![10, 1]]));
541 assert_eq!(reduced, vec![vec![1, 0], vec![0, 1]], "LLL recovers the standard basis");
542 }
543
544 #[test]
545 fn lll_finds_the_shortest_vector_of_a_flat_lattice() {
546 let reduced = as_i64(&lll_reduce(&[vec![15, 23], vec![11, 17]]));
549 let shortest = reduced.iter().map(|r| norm_sq(r)).min().unwrap();
550 assert_eq!(shortest, 2, "LLL finds the shortest vector, norm² = 2");
551 assert!(reduced.iter().any(|r| r == &[1, 1] || r == &[-1, -1]), "and it is ±[1,1]");
552 }
553
554 #[test]
555 fn exact_integer_lll_matches_the_known_reductions() {
556 let big = |rows: &[&[i64]]| -> Vec<Vec<BigInt>> {
557 rows.iter().map(|r| r.iter().map(|&x| BigInt::from_i64(x)).collect()).collect()
558 };
559 let out = as_i64(&lll_reduce_bigint_exact(&big(&[&[1, 0], &[10, 1]])));
560 assert_eq!(out, vec![vec![1, 0], vec![0, 1]], "exact LLL recovers the standard basis");
561
562 let out = as_i64(&lll_reduce_bigint_exact(&big(&[&[15, 23], &[11, 17]])));
563 let shortest = out.iter().map(|r| norm_sq(r)).min().unwrap();
564 assert_eq!(shortest, 2, "exact LLL finds the shortest vector, norm² = 2");
565 }
566
567 #[test]
568 fn l2_fplll_matches_the_known_reductions() {
569 let big = |rows: &[&[i64]]| -> Vec<Vec<BigInt>> {
570 rows.iter().map(|r| r.iter().map(|&x| BigInt::from_i64(x)).collect()).collect()
571 };
572 let out = as_i64(&lll_reduce_bigint_fp(&big(&[&[1, 0], &[10, 1]])));
573 assert_eq!(out, vec![vec![1, 0], vec![0, 1]], "L² recovers the standard basis");
574
575 let out = as_i64(&lll_reduce_bigint_fp(&big(&[&[15, 23], &[11, 17]])));
576 let shortest = out.iter().map(|r| norm_sq(r)).min().unwrap();
577 assert_eq!(shortest, 2, "L² finds the shortest vector, norm² = 2");
578 }
579
580 #[test]
581 fn lll_output_is_size_reduced() {
582 let reduced = lll_reduce(&[vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 10]]);
584 let b: Vec<Vec<Rational>> =
585 reduced.iter().map(|row| row.iter().map(|x| Rational::from_bigint(x.clone())).collect()).collect();
586 let (_, mu) = gram_schmidt(&b);
587 for i in 0..b.len() {
588 for j in 0..i {
589 assert!(mu[i][j].abs() <= rat(1, 2), "size-reduced: |μ[{i}][{j}]| ≤ ½");
590 }
591 }
592 }
593}