use crate::scalar::{Rational, Scalar};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum DegenerateBehavior {
StopAtRadical,
RequireNonsingular,
}
pub(crate) fn rdiv(a: &Rational, b: &Rational) -> Rational {
a.mul(
&b.inv()
.expect("division by zero rational in exact lattice arithmetic"),
)
}
pub(crate) fn v_p_i128(mut x: i128, p: i128) -> i128 {
debug_assert!(x != 0);
let mut k = 0i128;
while x % p == 0 {
x /= p;
k += 1;
}
k
}
pub(crate) fn unit_part_i128(mut x: i128, p: i128) -> i128 {
while x % p == 0 {
x /= p;
}
x
}
pub(crate) fn rat_val(r: &Rational, p: i128) -> i128 {
debug_assert!(!r.is_zero());
v_p_i128(r.numer(), p) - v_p_i128(r.denom(), p)
}
pub(crate) fn odd_unit_residue(r: &Rational, p: i128) -> i128 {
let a = unit_part_i128(r.numer(), p).rem_euclid(p);
let b = unit_part_i128(r.denom(), p).rem_euclid(p);
(a * b).rem_euclid(p)
}
pub(crate) fn unit_mod8(r: &Rational) -> i128 {
let a = unit_part_i128(r.numer(), 2).rem_euclid(8);
let b = unit_part_i128(r.denom(), 2).rem_euclid(8);
(a * b).rem_euclid(8)
}
pub(crate) fn rational_mod_int(x: Rational, modulus: i128) -> Rational {
debug_assert!(modulus > 0);
let den = x.denom();
let mden = den
.checked_mul(modulus)
.expect("rational modulus exceeds i128");
Rational::new(x.numer().rem_euclid(mden), den)
}
pub(crate) fn rational_congruence_diagonal(
gram: &[Vec<i128>],
degenerate: DegenerateBehavior,
) -> Vec<Rational> {
let n = gram.len();
let mut a: Vec<Vec<Rational>> = gram
.iter()
.map(|row| row.iter().map(|&x| Rational::from_int(x)).collect())
.collect();
let mut active: Vec<usize> = (0..n).collect();
let mut out = Vec::with_capacity(n);
while !active.is_empty() {
if !active.iter().any(|&r| !a[r][r].is_zero()) {
let mut pair = None;
'find: for (ai, &r) in active.iter().enumerate() {
for &s in &active[ai + 1..] {
if !a[r][s].is_zero() {
pair = Some((r, s));
break 'find;
}
}
}
let Some((r, s)) = pair else {
match degenerate {
DegenerateBehavior::StopAtRadical => break,
DegenerateBehavior::RequireNonsingular => {
panic!("nondegenerate form has a nonzero entry")
}
}
};
for &c in &active {
a[r][c] = a[r][c].add(&a[s][c].clone());
}
for &rr in &active {
a[rr][r] = a[rr][r].add(&a[rr][s].clone());
}
}
let Some(i) = active.iter().copied().find(|&r| !a[r][r].is_zero()) else {
match degenerate {
DegenerateBehavior::StopAtRadical => break,
DegenerateBehavior::RequireNonsingular => panic!("a diagonal pivot now exists"),
}
};
let pivot = a[i][i].clone();
out.push(pivot.clone());
let rest: Vec<usize> = active.iter().copied().filter(|&r| r != i).collect();
for &r in &rest {
for &s in &rest {
let corr = rdiv(&a[r][i].mul(&a[i][s]), &pivot);
a[r][s] = a[r][s].sub(&corr);
}
}
active = rest;
}
out
}
pub(crate) fn signature_from_diagonal(diag: &[Rational]) -> (usize, usize) {
let (mut pos, mut neg) = (0usize, 0usize);
for d in diag {
match d.sign() {
std::cmp::Ordering::Greater => pos += 1,
std::cmp::Ordering::Less => neg += 1,
std::cmp::Ordering::Equal => {}
}
}
(pos, neg)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v_p_and_unit_part_agree_on_known_factorizations() {
assert_eq!(v_p_i128(12, 2), 2); assert_eq!(unit_part_i128(12, 2), 3);
assert_eq!(v_p_i128(-12, 2), 2);
assert_eq!(unit_part_i128(-12, 2), -3);
assert_eq!(v_p_i128(27, 3), 3); assert_eq!(unit_part_i128(27, 3), 1);
}
#[test]
fn rat_val_is_signed_valuation_difference() {
assert_eq!(rat_val(&Rational::new(12, 1), 2), 2);
assert_eq!(rat_val(&Rational::new(1, 12), 2), -2);
assert_eq!(rat_val(&Rational::new(3, 4), 2), -2);
}
#[test]
fn odd_unit_residue_and_unit_mod8_match_known_units() {
assert_eq!(
odd_unit_residue(&Rational::new(5, 7), 3),
(5i128 * 7).rem_euclid(3)
);
assert_eq!(unit_mod8(&Rational::new(3, 1)), 3);
assert_eq!(unit_mod8(&Rational::new(12, 1)), 3);
}
#[test]
fn rational_mod_int_reduces_into_the_half_open_interval() {
assert_eq!(
rational_mod_int(Rational::new(5, 2), 2),
Rational::new(1, 2)
);
assert_eq!(
rational_mod_int(Rational::new(-1, 2), 2),
Rational::new(3, 2)
);
assert_eq!(
rational_mod_int(Rational::new(4, 1), 2),
Rational::new(0, 1)
);
}
#[test]
fn rdiv_recovers_exact_quotient() {
assert_eq!(
rdiv(&Rational::new(6, 1), &Rational::new(3, 1)),
Rational::new(2, 1)
);
}
}