use crate::scalar::{Poly, RationalFunction, ResidueField, Scalar, Valued};
use std::fmt;
#[derive(Clone)]
pub struct Gauss<S: Valued> {
num: Poly<S>,
den: Poly<S>,
}
impl<S: Valued> Gauss<S> {
fn from_polys(num: Poly<S>, den: Poly<S>) -> Self {
assert!(!den.is_zero(), "Gauss: zero denominator");
if num.is_zero() {
return Gauss {
num: Poly::zero(),
den: Poly::one(),
};
}
let lead_inv = den
.leading()
.unwrap()
.inv()
.expect("a field's nonzero leading coefficient inverts");
Gauss {
num: num.scale(&lead_inv),
den: den.scale(&lead_inv),
}
}
pub fn new(num: Vec<S>, den: Vec<S>) -> Self {
Gauss::from_polys(Poly::new(num), Poly::new(den))
}
pub fn from_base(s: S) -> Self {
Gauss::from_polys(Poly::constant(s), Poly::one())
}
pub fn t() -> Self {
Gauss::from_polys(Poly::t(), Poly::one())
}
pub fn parts(&self) -> (&[S], &[S]) {
(self.num.coeffs(), self.den.coeffs())
}
pub fn valuation(&self) -> Option<i128> {
let vn = self.num.min_coeff_valuation()?; let vd = self
.den
.min_coeff_valuation()
.expect("denominator is nonzero");
Some(vn - vd)
}
pub fn is_integral(&self) -> bool {
self.valuation().is_none_or(|v| v >= 0)
}
}
impl<S: Valued> PartialEq for Gauss<S> {
fn eq(&self, other: &Self) -> bool {
self.num.mul(&other.den) == other.num.mul(&self.den)
}
}
impl<S: Valued> fmt::Display for Gauss<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn fmt_poly<S: Scalar>(p: &[S]) -> String {
if p.is_empty() {
return "0".to_string();
}
let mut parts = Vec::new();
for (i, c) in p.iter().enumerate() {
if c.is_zero() {
continue;
}
parts.push(match i {
0 => format!("{c}"),
1 => format!("({c})·t"),
_ => format!("({c})·t^{i}"),
});
}
parts.join(" + ")
}
if self.den == Poly::one() {
write!(f, "{}", fmt_poly(self.num.coeffs()))
} else {
write!(
f,
"[{}] / [{}]",
fmt_poly(self.num.coeffs()),
fmt_poly(self.den.coeffs())
)
}
}
}
impl<S: Valued> fmt::Debug for Gauss<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl<S: Valued> Scalar for Gauss<S> {
fn zero() -> Self {
Gauss {
num: Poly::zero(),
den: Poly::one(),
}
}
fn one() -> Self {
Gauss {
num: Poly::one(),
den: Poly::one(),
}
}
fn add(&self, rhs: &Self) -> Self {
let num = self.num.mul(&rhs.den).add(&rhs.num.mul(&self.den));
let den = self.den.mul(&rhs.den);
Gauss::from_polys(num, den)
}
fn neg(&self) -> Self {
Gauss {
num: self.num.neg(),
den: self.den.clone(),
}
}
fn mul(&self, rhs: &Self) -> Self {
Gauss::from_polys(self.num.mul(&rhs.num), self.den.mul(&rhs.den))
}
fn characteristic() -> u128 {
S::characteristic()
}
fn inv(&self) -> Option<Self> {
if self.num.is_zero() {
return None; }
Some(Gauss::from_polys(self.den.clone(), self.num.clone()))
}
fn is_zero(&self) -> bool {
self.num.is_zero()
}
}
impl<S: Valued> Valued for Gauss<S> {
fn valuation(&self) -> Option<i128> {
Gauss::valuation(self)
}
fn uniformizer() -> Self {
Gauss::from_base(S::uniformizer())
}
}
fn reduce_poly_at_min<S: ResidueField>(p: &Poly<S>, min_val: i128) -> Poly<S::Residue> {
Poly::new(
p.coeffs()
.iter()
.map(|c| match c.valuation() {
Some(v) if v == min_val => c
.residue_unit()
.expect("a nonzero coefficient has an angular component"),
_ => S::Residue::zero(),
})
.collect(),
)
}
fn gauss_angular_component<S: ResidueField>(x: &Gauss<S>) -> Option<RationalFunction<S::Residue>> {
let vn = x.num.min_coeff_valuation()?;
let vd = x.den.min_coeff_valuation().expect("denominator is nonzero");
let num = reduce_poly_at_min(&x.num, vn);
let den = reduce_poly_at_min(&x.den, vd);
Some(RationalFunction::new(
num.coeffs().to_vec(),
den.coeffs().to_vec(),
))
}
impl<S: ResidueField> ResidueField for Gauss<S> {
type Residue = RationalFunction<S::Residue>;
fn residue(&self) -> Option<Self::Residue> {
match self.valuation() {
None => Some(RationalFunction::zero()),
Some(v) if v < 0 => None,
Some(0) => gauss_angular_component(self),
Some(_) => Some(RationalFunction::zero()),
}
}
fn residue_unit(&self) -> Option<Self::Residue> {
gauss_angular_component(self)
}
fn teichmuller(residue: Self::Residue) -> Self {
let num = residue
.num()
.coeffs()
.iter()
.cloned()
.map(S::teichmuller)
.collect();
let den = residue
.den()
.coeffs()
.iter()
.cloned()
.map(S::teichmuller)
.collect();
Gauss::new(num, den)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::{Fp, Laurent, Qp};
type G = Gauss<Qp<3, 6>>;
fn c(n: i128) -> Qp<3, 6> {
Qp::from_int(n)
}
#[test]
fn t_is_a_unit_p_is_the_uniformizer() {
assert_eq!(G::t().valuation(), Some(0));
assert_eq!(<G as Valued>::uniformizer().valuation(), Some(1));
assert_eq!(G::from_base(c(3)).valuation(), Some(1)); assert_eq!(G::zero().valuation(), None);
}
#[test]
fn gauss_valuation_is_min_of_coefficients() {
let three_plus_t = G::new(vec![c(3), c(1)], vec![c(1)]);
assert_eq!(three_plus_t.valuation(), Some(0));
let x = G::new(vec![c(9), c(3)], vec![c(0), c(1)]);
assert_eq!(x.valuation(), Some(1));
let a = G::from_base(c(3)); let b = G::new(vec![c(1), c(1)], vec![c(1)]); assert_eq!(a.mul(&b).valuation(), Some(1));
}
#[test]
fn is_a_field_inv_total_on_nonzero() {
let samples = [
G::t(),
G::from_base(c(2)),
G::new(vec![c(1), c(1)], vec![c(1)]), G::new(vec![c(1)], vec![c(0), c(1)]), G::new(vec![c(2), c(0), c(1)], vec![c(1), c(1)]), ];
for x in &samples {
let xi = x.inv().expect("nonzero inverts in a field");
assert_eq!(x.mul(&xi), G::one(), "x·x⁻¹ ≠ 1 for {x:?}");
}
assert_eq!(G::zero().inv(), None);
}
#[test]
fn cross_multiplication_equality() {
let t_over_t = G::new(vec![c(0), c(1)], vec![c(0), c(1)]);
assert_eq!(t_over_t, G::one());
let two_t_over_two = G::new(vec![c(0), c(2)], vec![c(2)]);
assert_eq!(two_t_over_two, G::t());
assert_ne!(G::t(), G::one());
}
#[test]
fn ring_axioms_on_a_sample() {
let es = [
G::zero(),
G::one(),
G::t(),
G::from_base(c(2)),
G::new(vec![c(1), c(1)], vec![c(1)]), ];
for a in &es {
assert_eq!(a.add(&G::zero()), *a);
assert_eq!(a.add(&a.neg()), G::zero());
assert_eq!(a.mul(&G::one()), *a);
for b in &es {
assert_eq!(a.add(b), b.add(a));
assert_eq!(a.mul(b), b.mul(a));
for d in &es {
assert_eq!(a.add(b).add(d), a.add(&b.add(d)));
assert_eq!(a.mul(b).mul(d), a.mul(&b.mul(d)));
assert_eq!(a.mul(&b.add(d)), a.mul(b).add(&a.mul(d)));
}
}
}
}
#[test]
fn integrality_is_the_valuation_subring() {
assert!(G::t().is_integral()); assert!(G::from_base(c(3)).is_integral()); assert!(G::new(vec![c(1)], vec![c(0), c(1)]).is_integral());
assert!(!G::from_base(c(3)).inv().unwrap().is_integral());
}
#[test]
fn composes_over_a_laurent_base() {
type GL = Gauss<Laurent<Fp<5>, 6>>;
let t = GL::t();
let s = GL::from_base(Laurent::<Fp<5>, 6>::t()); assert_eq!(t.valuation(), Some(0)); assert_eq!(s.valuation(), Some(1)); let x = t.add(&s);
let xi = x.inv().expect("nonzero inverts");
assert_eq!(x.mul(&xi), GL::one());
}
#[test]
fn residue_field_is_rational_function_over_base_residue() {
type R = RationalFunction<Fp<3>>;
assert_eq!(G::t().residue(), Some(R::t()));
assert_eq!(G::from_base(c(3)).residue(), Some(R::zero()));
assert_eq!(
G::from_base(c(3)).residue_unit(),
Some(R::from_base(Fp::<3>::one()))
);
let x = G::new(vec![c(3), c(2)], vec![c(1)]);
assert_eq!(
x.residue(),
Some(RationalFunction::from_poly(Poly::monomial(
1,
Fp::<3>::from_int(2)
)))
);
}
#[test]
fn teichmuller_lifts_rational_function_residue() {
type R = RationalFunction<Fp<3>>;
let r = R::new(
vec![Fp::<3>::from_int(1), Fp::<3>::from_int(2)],
vec![Fp::<3>::from_int(1)],
);
let tau = <G as ResidueField>::teichmuller(r.clone());
assert_eq!(tau.residue(), Some(r));
}
#[test]
fn mixed_characteristic_gauss_lift_is_not_multiplicative_on_polynomials() {
type G5 = Gauss<Qp<5, 6>>;
type R = RationalFunction<Fp<5>>;
let r = R::new(
vec![Fp::<5>::from_int(1), Fp::<5>::from_int(1)],
vec![Fp::<5>::from_int(1)],
);
let s = R::new(
vec![Fp::<5>::from_int(1), Fp::<5>::from_int(2)],
vec![Fp::<5>::from_int(1)],
);
let rs = r.mul(&s);
let tau_rs = <G5 as ResidueField>::teichmuller(rs.clone());
let tau_product =
<G5 as ResidueField>::teichmuller(r).mul(&<G5 as ResidueField>::teichmuller(s));
assert_eq!(tau_rs.residue(), Some(rs.clone()));
assert_eq!(tau_product.residue(), Some(rs));
assert_ne!(tau_rs, tau_product);
}
}