use crate::scalar::{Rational, Valued};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewtonPolygon {
vertices: Vec<(usize, i128)>,
zero_root_mult: usize,
}
fn lower_hull(points: &[(usize, i128)]) -> Vec<(usize, i128)> {
if points.len() <= 1 {
return points.to_vec();
}
let mut hull = vec![points[0]];
let mut cur = 0usize;
while cur + 1 < points.len() {
let (cx, cy) = points[cur];
let mut best = cur + 1;
for j in (cur + 1)..points.len() {
let (jx, jy) = points[j];
let (bx, by) = points[best];
let (dxj, dxb) = ((jx - cx) as i128, (bx - cx) as i128);
let (lhs, rhs) = ((jy - cy) * dxb, (by - cy) * dxj);
if lhs < rhs || (lhs == rhs && jx > bx) {
best = j;
}
}
hull.push(points[best]);
cur = best;
}
hull
}
impl NewtonPolygon {
pub fn from_coeffs<K: Valued>(coeffs: &[K]) -> Option<NewtonPolygon> {
let points: Vec<(usize, i128)> = coeffs
.iter()
.enumerate()
.filter_map(|(i, c)| c.valuation().map(|v| (i, v)))
.collect();
let zero_root_mult = points.first()?.0; Some(NewtonPolygon {
vertices: lower_hull(&points),
zero_root_mult,
})
}
pub fn vertices(&self) -> &[(usize, i128)] {
&self.vertices
}
pub fn degree(&self) -> usize {
self.vertices.last().map_or(0, |&(x, _)| x)
}
pub fn zero_root_multiplicity(&self) -> usize {
self.zero_root_mult
}
pub fn slopes(&self) -> Vec<(Rational, u128)> {
self.vertices
.windows(2)
.map(|w| {
let ((x0, y0), (x1, y1)) = (w[0], w[1]);
(Rational::new(y1 - y0, (x1 - x0) as i128), (x1 - x0) as u128)
})
.collect()
}
pub fn root_valuations(&self) -> Vec<(Rational, u128)> {
self.vertices
.windows(2)
.map(|w| {
let ((x0, y0), (x1, y1)) = (w[0], w[1]);
(Rational::new(y0 - y1, (x1 - x0) as i128), (x1 - x0) as u128)
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::{Fp, Laurent, Poly, Qp, Ramified, Scalar};
fn rat(n: i128, d: i128) -> Rational {
Rational::new(n, d)
}
type Q5 = Qp<5, 8>;
fn qpoly(coeffs: &[i128]) -> Vec<Q5> {
coeffs.iter().map(|&n| Q5::from_int(n)).collect()
}
#[test]
fn eisenstein_single_slope() {
let np = NewtonPolygon::from_coeffs(&qpoly(&[-5, 0, 0, 1])).unwrap();
assert_eq!(np.root_valuations(), vec![(rat(1, 3), 3)]);
assert_eq!(np.degree(), 3);
assert_eq!(np.zero_root_multiplicity(), 0);
assert_eq!(Ramified::<Qp<5, 8>, 3>::pi().valuation(), Some(1));
}
#[test]
fn sqrt_p_slope_half() {
let np = NewtonPolygon::from_coeffs(&qpoly(&[-5, 0, 1])).unwrap();
assert_eq!(np.root_valuations(), vec![(rat(1, 2), 2)]);
assert_eq!(Q5::from_int(5).is_square(), Some(false));
}
#[test]
fn dumas_additivity() {
let f = Poly::new(qpoly(&[-5, 1])); let g = Poly::new(qpoly(&[-1, 1])); let fg = f.mul(&g);
let np = NewtonPolygon::from_coeffs(fg.coeffs()).unwrap();
assert_eq!(np.root_valuations(), vec![(rat(1, 1), 1), (rat(0, 1), 1)]);
let h = Poly::new(qpoly(&[-5, 0, 1])).mul(&g);
let nph = NewtonPolygon::from_coeffs(h.coeffs()).unwrap();
assert_eq!(nph.root_valuations(), vec![(rat(1, 2), 2), (rat(0, 1), 1)]);
}
#[test]
fn flat_polygon_iff_unit_roots() {
let np = NewtonPolygon::from_coeffs(&qpoly(&[2, 3, 1])).unwrap();
assert_eq!(np.root_valuations(), vec![(rat(0, 1), 2)]);
assert_eq!(Q5::from_int(2).valuation(), Some(0)); assert!(np.slopes().iter().all(|(s, _)| *s == Rational::zero()));
let np2 = NewtonPolygon::from_coeffs(&qpoly(&[5, 3, 1])).unwrap();
assert_ne!(np2.root_valuations(), vec![(rat(0, 1), 2)]);
assert_eq!(np2.root_valuations(), vec![(rat(1, 1), 1), (rat(0, 1), 1)]);
}
#[test]
fn negative_slope_for_pole_root() {
let coeffs = vec![Q5::from_p_power(-1).neg(), Q5::one()]; let np = NewtonPolygon::from_coeffs(&coeffs).unwrap();
assert_eq!(np.root_valuations(), vec![(rat(-1, 1), 1)]);
}
#[test]
fn zero_roots_are_tracked() {
let np = NewtonPolygon::from_coeffs(&qpoly(&[0, 0, -1, 1])).unwrap();
assert_eq!(np.zero_root_multiplicity(), 2);
assert_eq!(np.root_valuations(), vec![(rat(0, 1), 1)]);
}
#[test]
fn laurent_leg_is_exact() {
type L = Laurent<Fp<7>, 8>;
let t = L::t();
let minus_t = t.neg();
let coeffs = vec![minus_t, L::zero(), L::one()]; let np = NewtonPolygon::from_coeffs(&coeffs).unwrap();
assert_eq!(np.root_valuations(), vec![(rat(1, 2), 2)]);
}
#[test]
fn zero_polynomial_is_none() {
assert!(NewtonPolygon::from_coeffs::<Q5>(&[]).is_none());
assert!(NewtonPolygon::from_coeffs(&qpoly(&[0, 0, 0])).is_none());
}
}