use crate::scalar::{ResidueField, Scalar, Valued};
use std::fmt;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Ramified<S: Valued, const E: usize> {
coeffs: Vec<S>,
}
impl<S: Valued, const E: usize> Ramified<S, E> {
pub fn assert_supported_params() {
assert!(
E >= 2,
"Ramified<S,E> needs E >= 2 to be a proper extension, got E={E}"
);
}
pub fn new(mut coeffs: Vec<S>) -> Self {
Self::assert_supported_params();
coeffs.resize(E, S::zero());
Ramified { coeffs }
}
pub fn from_base(s: S) -> Self {
Self::assert_supported_params();
let mut coeffs = vec![S::zero(); E];
if E > 0 {
coeffs[0] = s;
}
Ramified { coeffs }
}
pub fn pi() -> Self {
Self::assert_supported_params();
let mut coeffs = vec![S::zero(); E];
coeffs[1] = S::one();
Ramified { coeffs }
}
fn pi_basis(k: usize) -> Self {
Self::assert_supported_params();
let mut coeffs = vec![S::zero(); E];
coeffs[k] = S::one();
Ramified { coeffs }
}
pub fn valuation(&self) -> Option<i128> {
Self::assert_supported_params();
let mut best: Option<i128> = None;
for (i, a) in self.coeffs.iter().enumerate() {
if let Some(v) = a.valuation() {
let val = E as i128 * v + i as i128;
best = Some(best.map_or(val, |b| b.min(val)));
}
}
best
}
fn leading_component(&self) -> Option<&S> {
let target = self.valuation()?;
self.coeffs.iter().enumerate().find_map(|(i, a)| {
a.valuation()
.filter(|v| E as i128 * *v + i as i128 == target)
.map(|_| a)
})
}
pub fn is_integral(&self) -> bool {
Self::assert_supported_params();
self.valuation().is_none_or(|v| v >= 0)
}
pub fn components(&self) -> &[S] {
Self::assert_supported_params();
&self.coeffs
}
}
impl<S: Valued, const E: usize> fmt::Display for Ramified<S, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_zero() {
return write!(f, "0 (π^{E}=ϖ)");
}
let mut first = true;
for (i, c) in self.coeffs.iter().enumerate() {
if c.is_zero() {
continue;
}
if !first {
write!(f, " + ")?;
}
first = false;
match i {
0 => write!(f, "{c}")?,
1 => write!(f, "({c})·π")?,
_ => write!(f, "({c})·π^{i}")?,
}
}
Ok(())
}
}
impl<S: Valued, const E: usize> fmt::Debug for Ramified<S, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl<S: Valued, const E: usize> Scalar for Ramified<S, E> {
fn zero() -> Self {
Self::assert_supported_params();
Ramified {
coeffs: vec![S::zero(); E],
}
}
fn one() -> Self {
Self::assert_supported_params();
let mut coeffs = vec![S::zero(); E];
if E > 0 {
coeffs[0] = S::one();
}
Ramified { coeffs }
}
fn add(&self, rhs: &Self) -> Self {
Self::assert_supported_params();
Ramified {
coeffs: self
.coeffs
.iter()
.zip(&rhs.coeffs)
.map(|(a, b)| a.add(b))
.collect(),
}
}
fn neg(&self) -> Self {
Self::assert_supported_params();
Ramified {
coeffs: self.coeffs.iter().map(|a| a.neg()).collect(),
}
}
fn mul(&self, rhs: &Self) -> Self {
Self::assert_supported_params();
let w = S::uniformizer();
let mut out = vec![S::zero(); E];
for (i, a) in self.coeffs.iter().enumerate() {
if a.is_zero() {
continue;
}
for (j, b) in rhs.coeffs.iter().enumerate() {
if b.is_zero() {
continue;
}
let prod = a.mul(b);
let k = i + j;
if k < E {
out[k] = out[k].add(&prod);
} else {
out[k - E] = out[k - E].add(&prod.mul(&w));
}
}
}
Ramified { coeffs: out }
}
fn characteristic() -> u128 {
Self::assert_supported_params();
S::characteristic()
}
fn inv(&self) -> Option<Self> {
Self::assert_supported_params();
if self.is_zero() {
return None;
}
if E == 2 {
let a = &self.coeffs[0];
let b = &self.coeffs[1];
let w = S::uniformizer();
let norm = a.mul(a).sub(&w.mul(&b.mul(b)));
let ninv = norm.inv()?;
return Some(Ramified {
coeffs: vec![a.mul(&ninv), b.neg().mul(&ninv)],
});
}
let mut m = vec![vec![S::zero(); E]; E];
for col in 0..E {
let prod = self.mul(&Self::pi_basis(col));
for row in 0..E {
m[row][col] = prod.coeffs[row].clone();
}
}
let mut e0 = vec![S::zero(); E];
e0[0] = S::one();
let c = crate::linalg::field::solve(m, e0)?;
Some(Ramified { coeffs: c })
}
fn is_zero(&self) -> bool {
Self::assert_supported_params();
self.coeffs.iter().all(|a| a.is_zero())
}
}
impl<S: Valued, const E: usize> Valued for Ramified<S, E> {
fn valuation(&self) -> Option<i128> {
Ramified::valuation(self)
}
fn uniformizer() -> Self {
Ramified::pi()
}
}
impl<S: ResidueField, const E: usize> ResidueField for Ramified<S, E> {
type Residue = S::Residue;
fn residue(&self) -> Option<Self::Residue> {
match self.valuation() {
None => Some(S::Residue::zero()),
Some(v) if v < 0 => None,
Some(0) => self.residue_unit(),
Some(_) => Some(S::Residue::zero()),
}
}
fn residue_unit(&self) -> Option<Self::Residue> {
self.leading_component()
.and_then(|component| component.residue_unit())
}
fn teichmuller(residue: Self::Residue) -> Self {
Ramified::from_base(S::teichmuller(residue))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::{Fp, Laurent, Qp};
type E2 = Ramified<Qp<3, 6>, 2>;
type E3 = Ramified<Qp<2, 8>, 3>;
fn q3(n: i128) -> Qp<3, 6> {
Qp::from_int(n)
}
#[test]
fn pi_power_e_is_the_uniformizer() {
let pi = E2::pi();
assert_eq!(pi.mul(&pi), E2::from_base(q3(3)));
assert_eq!(pi.mul(&pi).mul(&pi), E2::new(vec![q3(0), q3(3)]));
}
#[test]
fn ramified_valuation_is_exact_and_unique() {
assert_eq!(E2::pi().valuation(), Some(1));
assert_eq!(E2::from_base(q3(3)).valuation(), Some(2));
assert_eq!(E2::new(vec![q3(1), q3(1)]).valuation(), Some(0));
assert_eq!(E2::new(vec![q3(3), q3(1)]).valuation(), Some(1));
assert_eq!(E2::zero().valuation(), None);
}
#[test]
fn valuation_is_additive_under_multiplication() {
let a = E2::new(vec![q3(1), q3(1)]); let b = E2::pi(); assert_eq!(a.mul(&b).valuation(), Some(1));
let three = E2::from_base(q3(3)); assert_eq!(b.mul(&three).valuation(), Some(3));
}
#[test]
fn e2_inverse_round_trips_via_norm() {
for a in 1..5i128 {
for b in 0..5i128 {
let x = E2::new(vec![q3(a), q3(b)]);
let xi = x.inv().expect("nonzero inverts in a field");
assert_eq!(x.mul(&xi), E2::one(), "x·x⁻¹ ≠ 1 for {x:?}");
}
}
assert_eq!(E2::zero().inv(), None);
let pinv = E2::pi().inv().unwrap();
assert_eq!(E2::pi().mul(&pinv), E2::one());
assert_eq!(pinv.valuation(), Some(-1));
}
#[test]
fn e3_inverse_round_trips_via_matrix_solve() {
fn q2(n: i128) -> Qp<2, 8> {
Qp::from_int(n)
}
let pi = E3::pi();
assert_eq!(pi.mul(&pi).mul(&pi), E3::from_base(q2(2)));
const K: i128 = 8;
for a in [1i128, 3] {
for b in 0..3i128 {
for c in 0..3i128 {
let x = E3::new(vec![q2(a), q2(b), q2(c)]);
assert_eq!(x.valuation(), Some(0), "expected a unit: {x:?}");
let xi = x.inv().expect("a unit inverts");
let residual = x.mul(&xi).sub(&E3::one());
assert!(
residual.is_zero() || residual.valuation().unwrap() >= K,
"x·x⁻¹ not ≈ 1 to precision for {x:?}: residual {residual:?}"
);
}
}
}
}
#[test]
fn ring_of_integers_is_the_valuation_subring() {
assert!(E2::pi().is_integral()); assert!(E2::one().is_integral());
assert!(!E2::pi().inv().unwrap().is_integral()); }
#[test]
fn valued_trait_uses_pi_as_uniformizer() {
assert_eq!(<E2 as Valued>::uniformizer(), E2::pi());
assert_eq!(<E2 as Valued>::uniformizer().valuation(), Some(1));
}
#[test]
fn residue_field_is_the_base_residue() {
assert_eq!(E2::pi().residue(), Some(Fp::<3>::zero()));
assert_eq!(E2::pi().residue_unit(), Some(Fp::<3>::one()));
assert_eq!(E2::from_base(q3(2)).residue(), Some(Fp::<3>::from_u128(2)));
assert_eq!(E2::from_base(q3(3)).residue(), Some(Fp::<3>::zero()));
assert_eq!(E2::from_base(q3(3)).residue_unit(), Some(Fp::<3>::one()));
}
#[test]
fn teichmuller_lifts_base_residue_through_ramification() {
let r = Fp::<3>::from_u128(2);
let tau = <E2 as ResidueField>::teichmuller(r);
assert_eq!(tau.residue(), Some(r));
assert_eq!(tau.valuation(), Some(0));
}
#[test]
fn characteristic_is_inherited_from_the_base() {
assert_eq!(E2::characteristic(), 0); type EW = Ramified<Laurent<Fp<2>, 6>, 2>;
assert_eq!(EW::characteristic(), 2); }
#[test]
fn wild_ramification_is_still_a_field() {
type EW = Ramified<Laurent<Fp<2>, 8>, 2>;
let t = Laurent::<Fp<2>, 8>::t();
let pi = EW::pi();
assert_eq!(pi.mul(&pi), EW::from_base(t.clone()));
for a in 0..2u128 {
for b in 0..2u128 {
if a == 0 && b == 0 {
continue;
}
let c0 = Laurent::from_base(Fp::<2>::from_int(a as i128)).add(&t);
let c1 = Laurent::from_base(Fp::<2>::from_int(b as i128));
let x = EW::new(vec![c0, c1]);
let xi = x.inv().expect("nonzero inverts: wild extension is a field");
assert_eq!(x.mul(&xi), EW::one(), "x·x⁻¹ ≠ 1 for {x:?}");
}
}
let pinv = pi.inv().expect("π inverts");
assert_eq!(pi.mul(&pinv), EW::one());
}
#[test]
fn invalid_parameters_are_rejected() {
assert!(std::panic::catch_unwind(Ramified::<Qp<3, 6>, 1>::one).is_err());
assert!(std::panic::catch_unwind(Ramified::<Qp<3, 6>, 0>::one).is_err());
}
}