use crate::scalar::Scalar;
use std::fmt;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Laurent<S: Scalar, const K: usize> {
unit: Vec<S>,
val: i128,
}
impl<S: Scalar, const K: usize> Laurent<S, K> {
pub fn assert_supported_params() {
assert!(K > 0, "Laurent<S,K> needs positive precision K, got K={K}");
}
pub fn precision() -> usize {
Self::assert_supported_params();
K
}
fn normalized(coeffs: Vec<S>, val: i128) -> Self {
Self::assert_supported_params();
let lead = coeffs.iter().position(|c| !c.is_zero());
let Some(lead) = lead else {
return Laurent {
unit: Vec::new(),
val: 0,
};
};
let mut unit: Vec<S> = coeffs[lead..].to_vec();
unit.truncate(K);
while unit.last().map(|c| c.is_zero()).unwrap_or(false) {
unit.pop();
}
Laurent {
unit,
val: val + lead as i128,
}
}
pub fn from_coeffs(coeffs: Vec<S>, val: i128) -> Self {
Self::normalized(coeffs, val)
}
pub fn from_base(s: S) -> Self {
Self::normalized(vec![s], 0)
}
pub fn t() -> Self {
Self::assert_supported_params();
Laurent {
unit: vec![S::one()],
val: 1,
}
}
pub fn from_t_power(v: i128) -> Self {
Self::assert_supported_params();
Laurent {
unit: vec![S::one()],
val: v,
}
}
pub fn valuation(&self) -> Option<i128> {
if self.unit.is_empty() {
None
} else {
Some(self.val)
}
}
pub fn leading_coeff(&self) -> Option<S> {
self.unit.first().cloned()
}
pub fn is_integral(&self) -> bool {
self.valuation().is_none_or(|v| v >= 0)
}
pub fn unit_coeffs(&self) -> &[S] {
&self.unit
}
pub fn coeff(&self, exp: i128) -> S {
if self.unit.is_empty() {
return S::zero();
}
let i = exp - self.val;
if i < 0 || i >= self.unit.len() as i128 {
S::zero()
} else {
self.unit[i as usize].clone()
}
}
}
impl<S: Scalar, const K: usize> fmt::Display for Laurent<S, K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.unit.is_empty() {
return write!(f, "0 (S((t)))");
}
let mut first = true;
for (i, c) in self.unit.iter().enumerate() {
if c.is_zero() {
continue;
}
if !first {
write!(f, " + ")?;
}
first = false;
let e = self.val + i as i128;
match e {
0 => write!(f, "{c}")?,
1 => write!(f, "{c}·t")?,
_ => write!(f, "{c}·t^{e}")?,
}
}
write!(f, " + O(t^{})", self.val + self.unit.len() as i128)
}
}
impl<S: Scalar, const K: usize> fmt::Debug for Laurent<S, K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl<S: Scalar, const K: usize> Scalar for Laurent<S, K> {
fn zero() -> Self {
Self::assert_supported_params();
Laurent {
unit: Vec::new(),
val: 0,
}
}
fn one() -> Self {
Self::assert_supported_params();
Laurent {
unit: vec![S::one()],
val: 0,
}
}
fn add(&self, rhs: &Self) -> Self {
Self::assert_supported_params();
if self.unit.is_empty() {
return rhs.clone();
}
if rhs.unit.is_empty() {
return self.clone();
}
let (lo, hi) = if self.val <= rhs.val {
(self, rhs)
} else {
(rhs, self)
};
let d_i128 = hi.val - lo.val;
if d_i128 >= K as i128 {
let mut coeffs = vec![S::zero(); lo.unit.len().min(K)];
for (i, c) in lo.unit.iter().take(K).enumerate() {
coeffs[i] = c.clone();
}
return Self::normalized(coeffs, lo.val);
}
let d = d_i128 as usize; let len = lo.unit.len().max(d + hi.unit.len()).min(K);
let mut coeffs = vec![S::zero(); len];
for (i, c) in lo.unit.iter().enumerate() {
if i < len {
coeffs[i] = coeffs[i].add(c);
}
}
for (j, c) in hi.unit.iter().enumerate() {
let i = j + d;
if i < len {
coeffs[i] = coeffs[i].add(c);
}
}
Self::normalized(coeffs, lo.val)
}
fn neg(&self) -> Self {
Self::assert_supported_params();
Laurent {
unit: self.unit.iter().map(|c| c.neg()).collect(),
val: self.val,
}
}
fn mul(&self, rhs: &Self) -> Self {
Self::assert_supported_params();
if self.unit.is_empty() || rhs.unit.is_empty() {
return Self::zero();
}
let len = (self.unit.len() + rhs.unit.len() - 1).min(K);
let mut coeffs = vec![S::zero(); len];
for (i, a) in self.unit.iter().enumerate() {
if i >= len {
break;
}
for (j, b) in rhs.unit.iter().enumerate() {
let k = i + j;
if k >= len {
break;
}
coeffs[k] = coeffs[k].add(&a.mul(b));
}
}
Self::normalized(coeffs, self.val + rhs.val)
}
fn characteristic() -> u128 {
Self::assert_supported_params();
S::characteristic()
}
fn inv(&self) -> Option<Self> {
Self::assert_supported_params();
let u0 = self.unit.first()?;
let u0inv = u0.inv()?;
let mut w = vec![S::zero(); K];
w[0] = u0inv.clone();
for n in 1..K {
let mut acc = S::zero();
for i in 1..=n {
if i < self.unit.len() {
acc = acc.add(&self.unit[i].mul(&w[n - i]));
}
}
w[n] = u0inv.mul(&acc).neg();
}
Some(Self::normalized(w, -self.val))
}
fn is_zero(&self) -> bool {
Self::assert_supported_params();
self.unit.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::{Fpn, Nimber, Rational};
type L = Laurent<Rational, 6>;
fn r(n: i128) -> Rational {
Rational::from_int(n)
}
fn lc(coeffs: &[i128], val: i128) -> L {
Laurent::from_coeffs(coeffs.iter().map(|&n| r(n)).collect(), val)
}
#[test]
fn ring_basics_hold() {
let x = lc(&[1, 2, 3], 0); assert_eq!(x.add(&L::zero()), x);
assert_eq!(x.mul(&L::one()), x);
assert_eq!(x.add(&x.neg()), L::zero());
let a = lc(&[1, 1], 0);
let b = lc(&[1, -1], 0);
assert_eq!(a.mul(&b), lc(&[1, 0, -1], 0));
}
#[test]
fn valuations_add_under_multiplication() {
let a = lc(&[2, 1], 1); let b = lc(&[3], -2); assert_eq!(a.valuation(), Some(1));
assert_eq!(b.valuation(), Some(-2));
assert_eq!(a.mul(&b).valuation(), Some(-1));
assert_eq!(a.mul(&L::from_t_power(-1)).valuation(), Some(0));
}
#[test]
fn inverse_is_the_neumann_series() {
let one_minus_t = lc(&[1, -1], 0);
let inv = one_minus_t.inv().unwrap();
assert_eq!(inv, lc(&[1, 1, 1, 1, 1, 1], 0));
assert_eq!(one_minus_t.mul(&inv), L::one());
}
#[test]
fn every_nonzero_inverts_when_base_is_a_field() {
let t = L::t();
let ti = t.inv().unwrap();
assert_eq!(ti, L::from_t_power(-1));
assert_eq!(t.mul(&ti), L::one());
assert_eq!(L::zero().inv(), None);
for lead in 1..4 {
for v in -2i128..=2 {
let x = lc(&[lead, 1, 2], v);
let xi = x.inv().expect("nonzero must invert over a field");
assert_eq!(x.mul(&xi), L::one(), "x·x⁻¹ ≠ 1 for {x:?}");
}
}
}
#[test]
fn equal_characteristic_local_field_over_finite_base() {
type F8t = Laurent<Fpn<2, 3>, 4>; assert_eq!(F8t::characteristic(), 2);
type F9t = Laurent<Fpn<3, 2>, 4>; assert_eq!(F9t::characteristic(), 3);
let x = F8t::t();
assert_eq!(x.mul(&x.inv().unwrap()), F8t::one());
}
#[test]
fn relative_precision_cancellation_is_intended() {
let one = L::one();
let far = L::from_t_power(6);
assert_eq!(one.add(&far), one);
assert_eq!(one.add(&L::from_t_power(2)), lc(&[1, 0, 1], 0));
}
#[test]
fn characteristic_two_base_threads_neg_through_scalar() {
type Ln = Laurent<Nimber, 4>;
let x = Laurent::<Nimber, 4>::from_coeffs(vec![Nimber(3), Nimber(5)], 0);
assert_eq!(x.add(&x), Ln::zero());
assert_eq!(x.neg(), x);
}
#[test]
fn zero_precision_is_rejected() {
type L0 = Laurent<Rational, 0>;
assert!(std::panic::catch_unwind(L0::one).is_err());
assert!(std::panic::catch_unwind(L0::t).is_err());
assert!(std::panic::catch_unwind(|| L0::from_coeffs(vec![Rational::one()], 0)).is_err());
}
#[test]
fn m2_huge_valuation_gap_does_not_panic_or_corrupt() {
let one = L::one();
let far = L::from_t_power(i128::MAX);
assert_eq!(one.add(&far), one, "huge gap: hi term must vanish");
assert_eq!(far.add(&one), one, "symmetric");
assert_eq!(one.coeff(i128::MAX), Rational::zero());
let at_k = L::from_t_power(6);
assert_eq!(one.add(&at_k), one, "gap == K: hi vanishes too");
}
}