use super::function_field::FunctionFieldPlace;
use crate::forms::{artin_schreier_class_finite, FiniteChar2Field};
use crate::scalar::{Poly, RationalFunction, Scalar};
fn dpoly<S: Scalar>(p: &Poly<S>) -> Poly<S> {
let cs = p.coeffs();
if cs.len() <= 1 {
return Poly::zero();
}
let mut out = vec![S::zero(); cs.len() - 1];
for (i, c) in cs.iter().enumerate().skip(1) {
if i & 1 == 1 {
out[i - 1] = c.clone();
}
}
Poly::new(out)
}
pub(crate) fn strip_factor<S: Scalar>(mut p: Poly<S>, pi: &Poly<S>) -> (i128, Poly<S>) {
let mut mult = 0i128;
if p.is_zero() {
return (0, p);
}
loop {
let (quot, rem) = p.divrem(pi);
if rem.is_zero() {
p = quot;
mult += 1;
} else {
break;
}
}
(mult, p)
}
fn egcd<S: Scalar>(a: &Poly<S>, b: &Poly<S>) -> (Poly<S>, Poly<S>, Poly<S>) {
let (mut old_r, mut r) = (a.clone(), b.clone());
let (mut old_s, mut s) = (Poly::one(), Poly::zero());
let (mut old_t, mut t) = (Poly::zero(), Poly::one());
while !r.is_zero() {
let (q, _) = old_r.divrem(&r);
let nr = old_r.sub(&q.mul(&r));
old_r = std::mem::replace(&mut r, nr);
let ns = old_s.sub(&q.mul(&s));
old_s = std::mem::replace(&mut s, ns);
let nt = old_t.sub(&q.mul(&t));
old_t = std::mem::replace(&mut t, nt);
}
(old_r, old_s, old_t)
}
pub(crate) fn inverse_mod<S: Scalar>(e: &Poly<S>, m: &Poly<S>) -> Poly<S> {
let (g, x, _) = egcd(e, m);
let unit = g
.coeff(0)
.inv()
.expect("inverse_mod needs gcd(e, m) = 1 (a unit)");
x.scale(&unit).rem(m)
}
pub(crate) fn char2_monic_irreducible_factors<S: FiniteChar2Field>(f: &Poly<S>) -> Vec<Poly<S>> {
crate::forms::poly_factor::monic_irreducible_factor_support(
f,
S::characteristic_prime(),
S::field_order(),
S::from_index,
)
}
fn ps_mul<S: Scalar>(a: &[Poly<S>], b: &[Poly<S>], prec: usize, p: &Poly<S>) -> Vec<Poly<S>> {
let mut out = vec![Poly::<S>::zero(); prec];
for (i, ai) in a.iter().enumerate().take(prec) {
if ai.is_zero() {
continue;
}
for (j, bj) in b.iter().enumerate().take(prec - i) {
out[i + j] = out[i + j].add(&ai.mul_mod(bj, p));
}
}
out
}
fn ps_inv<S: FiniteChar2Field>(a: &[Poly<S>], prec: usize, p: &Poly<S>) -> Vec<Poly<S>> {
let mut b = vec![Poly::<S>::zero(); prec];
let a0_inv = kappa_inv(&a[0], p);
b[0] = a0_inv.clone();
for k in 1..prec {
let mut acc = Poly::<S>::zero();
for i in 1..=k {
let ai = a.get(i).cloned().unwrap_or_else(Poly::zero);
if !ai.is_zero() {
acc = acc.add(&ai.mul_mod(&b[k - i], p));
}
}
b[k] = acc.mul_mod(&a0_inv, p);
}
b
}
pub(crate) fn ps_eval_poly<S: Scalar>(
poly: &Poly<S>,
t: &[Poly<S>],
prec: usize,
p: &Poly<S>,
) -> Vec<Poly<S>> {
let mut acc = vec![Poly::<S>::zero(); prec];
for c in poly.coeffs().iter().rev() {
acc = ps_mul(&acc, t, prec, p);
acc[0] = acc[0].add(&Poly::constant(c.clone())).rem(p);
}
acc
}
fn kappa_inv<S: FiniteChar2Field>(z: &Poly<S>, p: &Poly<S>) -> Poly<S> {
let d = p.degree().expect("a place modulus has degree ≥ 1") as u128;
let order = S::field_order().pow(
d.try_into()
.expect("place degree fits the platform exponent type"),
);
z.pow_mod(order - 2, p)
}
pub(crate) fn hensel_series<S: FiniteChar2Field>(p: &Poly<S>, prec: usize) -> Vec<Poly<S>> {
let alpha = Poly::monomial(1, S::one()).rem(p); let mut t = vec![Poly::<S>::zero(); prec];
if prec == 0 {
return t;
}
t[0] = alpha;
let pp = dpoly(p);
let pa_inv = kappa_inv(&pp.rem(p), p); for k in 1..prec {
let pt = ps_eval_poly(p, &t, k + 1, p);
let target = if k == 1 { Poly::one() } else { Poly::zero() };
let ck = pt[k].clone();
t[k] = target.add(&ck).mul_mod(&pa_inv, p);
}
t
}
fn residue_finite<S: FiniteChar2Field>(num: &Poly<S>, den: &Poly<S>, p: &Poly<S>) -> Poly<S> {
let (m, e) = strip_factor(den.clone(), p);
if m == 0 {
return Poly::zero(); }
let m = usize::try_from(m).expect("pole order fits usize (indexes a power-series precision)");
let mut pm = Poly::one();
for _ in 0..m {
pm = pm.mul(p);
}
let e_inv = inverse_mod(&e, &pm);
let b = num.mul(&e_inv).rem(&pm); let t = hensel_series(p, m);
let bt = ps_eval_poly(&b, &t, m, p);
let ppt = ps_eval_poly(&dpoly(p), &t, m, p);
let ppt_inv = ps_inv(&ppt, m, p);
let val = ps_mul(&bt, &ppt_inv, m, p);
val[m - 1].clone()
}
fn residue_infinity<S: FiniteChar2Field>(num: &Poly<S>, den: &Poly<S>) -> S {
if num.is_zero() {
return S::zero();
}
let dn = num.degree().expect("nonzero numerator") as i128;
let dd = den.degree().expect("nonzero denominator") as i128;
let k = dn - dd + 1;
if k < 0 {
return S::zero();
}
let k = k as usize;
let rev = |p: &Poly<S>| {
let mut c = p.coeffs().to_vec();
c.reverse();
Poly::new(c)
};
let (nt, dt) = (rev(num), rev(den)); let prec = k + 1;
let d0_inv = dt.coeff(0).inv().expect("D̃(0) = lead(D) inverts");
let mut binv = vec![S::zero(); prec];
binv[0] = d0_inv;
for i in 1..prec {
let mut acc = S::zero();
for j in 1..=i {
acc = acc.add(&dt.coeff(j).mul(&binv[i - j]));
}
binv[i] = acc.mul(&d0_inv); }
let mut res = S::zero();
for i in 0..=k {
res = res.add(&nt.coeff(i).mul(&binv[k - i]));
}
res
}
pub(crate) fn trace_kappa_to_f2<S: FiniteChar2Field>(z: &Poly<S>, p: &Poly<S>) -> u128 {
let d = p.degree().expect("a place modulus has degree ≥ 1");
let q = S::field_order();
let mut term = z.rem(p);
let mut tr = term.clone();
for _ in 1..d {
term = term.pow_mod(q, p);
tr = tr.add(&term);
}
artin_schreier_class_finite(tr.rem(p).coeff(0))
}
fn dlog_differential<S: FiniteChar2Field>(
a: &RationalFunction<S>,
b: &RationalFunction<S>,
) -> Option<(Poly<S>, Poly<S>)> {
assert!(!b.is_zero(), "the Artin–Schreier symbol needs b ≠ 0");
if a.is_zero() {
return None;
}
let (an, ad) = (a.num(), a.den());
let (bn, bd) = (b.num(), b.den());
let dlog_num = dpoly(bn).mul(bd).add(&bn.mul(&dpoly(bd)));
let mut gnum = an.mul(&dlog_num);
let mut gden = ad.mul(bn).mul(bd);
let gg = gnum.gcd(&gden);
if gg.degree().unwrap_or(0) > 0 {
gnum = gnum.divrem(&gg).0;
gden = gden.divrem(&gg).0;
}
Some((gnum, gden))
}
pub fn artin_schreier_symbol_at<S: FiniteChar2Field>(
a: &RationalFunction<S>,
b: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> u128 {
let Some((gnum, gden)) = dlog_differential(a, b) else {
return 0; };
match place {
FunctionFieldPlace::Finite(pi) => trace_kappa_to_f2(&residue_finite(&gnum, &gden, pi), pi),
FunctionFieldPlace::Infinite => artin_schreier_class_finite(residue_infinity(&gnum, &gden)),
}
}
pub fn artin_schreier_symbol_places<S: FiniteChar2Field>(
a: &RationalFunction<S>,
b: &RationalFunction<S>,
) -> Vec<FunctionFieldPlace<S>> {
let mut places = vec![FunctionFieldPlace::Infinite];
if let Some((_, gden)) = dlog_differential(a, b) {
for pi in char2_monic_irreducible_factors(&gden) {
places.push(FunctionFieldPlace::Finite(pi));
}
}
places
}
pub fn artin_schreier_reciprocity_sum<S: FiniteChar2Field>(
a: &RationalFunction<S>,
b: &RationalFunction<S>,
) -> u128 {
artin_schreier_symbol_places(a, b)
.iter()
.fold(0u128, |acc, pl| acc ^ artin_schreier_symbol_at(a, b, pl))
}
pub fn artin_schreier_ramified_places<S: FiniteChar2Field>(
a: &RationalFunction<S>,
b: &RationalFunction<S>,
) -> Vec<FunctionFieldPlace<S>> {
artin_schreier_symbol_places(a, b)
.into_iter()
.filter(|pl| artin_schreier_symbol_at(a, b, pl) == 1)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::Scalar;
use crate::scalar::{Fp, Fpn};
type F2 = Fp<2>;
type R2 = RationalFunction<F2>;
fn p2(c: &[i128]) -> Poly<F2> {
Poly::new(c.iter().map(|&n| F2::from_int(n)).collect())
}
fn r2(num: &[i128], den: &[i128]) -> R2 {
RationalFunction::new(
num.iter().map(|&n| F2::from_int(n)).collect(),
den.iter().map(|&n| F2::from_int(n)).collect(),
)
}
#[test]
fn residue_oracles_at_a_degree_two_place() {
let p = p2(&[1, 1, 1]); let p2sq = p.mul(&p); let p3 = p2sq.mul(&p); let alpha = p2(&[0, 1]); let one = Poly::<F2>::one();
assert_eq!(residue_finite(&p2(&[0, 1]), &p2sq, &p), one);
assert_eq!(residue_finite(&one, &p3, &p), Poly::zero());
assert_eq!(residue_finite(&p2(&[0, 1]), &p3, &p), one);
assert_eq!(residue_finite(&one, &p, &p), one);
let t = hensel_series(&p, 3);
assert_eq!(t[0], alpha);
assert_eq!(t[1], one);
assert_eq!(t[2], one);
}
#[test]
fn symbol_oracle_a1_b_t() {
let (a, b) = (r2(&[1], &[1]), r2(&[0, 1], &[1]));
assert_eq!(
artin_schreier_symbol_at(&a, &b, &FunctionFieldPlace::Finite(p2(&[0, 1]))),
1
); assert_eq!(
artin_schreier_symbol_at(&a, &b, &FunctionFieldPlace::Infinite),
1
); assert_eq!(
artin_schreier_symbol_at(&a, &b, &FunctionFieldPlace::Finite(p2(&[1, 1]))),
0
); assert_eq!(artin_schreier_reciprocity_sum(&a, &b), 0);
assert_eq!(artin_schreier_ramified_places(&a, &b).len(), 2);
}
#[test]
fn symbol_oracle_a_recip_tp1_b_t() {
let (a, b) = (r2(&[1], &[1, 1]), r2(&[0, 1], &[1]));
assert_eq!(
artin_schreier_symbol_at(&a, &b, &FunctionFieldPlace::Finite(p2(&[0, 1]))),
1
); assert_eq!(
artin_schreier_symbol_at(&a, &b, &FunctionFieldPlace::Finite(p2(&[1, 1]))),
1
); assert_eq!(
artin_schreier_symbol_at(&a, &b, &FunctionFieldPlace::Infinite),
0
); assert_eq!(artin_schreier_reciprocity_sum(&a, &b), 0);
}
#[test]
fn symbol_oracle_a_recip_irreducible_b_t() {
let (a, b) = (r2(&[1], &[1, 1, 1]), r2(&[0, 1], &[1]));
assert_eq!(
artin_schreier_symbol_at(&a, &b, &FunctionFieldPlace::Finite(p2(&[0, 1]))),
1
); assert_eq!(
artin_schreier_symbol_at(&a, &b, &FunctionFieldPlace::Finite(p2(&[1, 1, 1]))),
1
); assert_eq!(
artin_schreier_symbol_at(&a, &b, &FunctionFieldPlace::Infinite),
0
); assert_eq!(artin_schreier_reciprocity_sum(&a, &b), 0);
}
#[test]
fn symbol_oracle_over_f4() {
type F4 = Fpn<2, 2>;
type R4 = RationalFunction<F4>;
let alpha = F4::from_coeffs(&[0, 1]);
let a = R4::from_base(alpha);
let b = R4::new(
vec![F4::constant(0), F4::constant(1)],
vec![F4::constant(1)],
); assert_eq!(
artin_schreier_symbol_at(
&a,
&b,
&FunctionFieldPlace::Finite(Poly::new(vec![F4::constant(0), F4::constant(1)]))
),
1
);
assert_eq!(
artin_schreier_symbol_at(&a, &b, &FunctionFieldPlace::Infinite),
1
);
assert_eq!(artin_schreier_reciprocity_sum(&a, &b), 0);
let one = R4::from_base(F4::constant(1));
assert_eq!(artin_schreier_reciprocity_sum(&one, &b), 0);
assert!(artin_schreier_ramified_places(&one, &b).is_empty());
}
#[test]
fn reciprocity_sweep_over_f2() {
let samples = [
r2(&[0, 1], &[1]), r2(&[1, 1], &[1]), r2(&[1, 0, 1, 1], &[1]), r2(&[0, 1], &[1, 1]), r2(&[1], &[0, 1]), r2(&[1, 1], &[0, 1, 1]), ];
for a in &samples {
for b in &samples {
assert_eq!(
artin_schreier_reciprocity_sum(a, b),
0,
"reciprocity Σ_v s_v(a,b) = 0 failed at a={a:?} b={b:?}"
);
assert_eq!(
artin_schreier_ramified_places(a, b).len() % 2,
0,
"ramified-place count must be even at a={a:?} b={b:?}"
);
}
}
}
#[test]
fn reciprocity_sweep_over_f4() {
type F4 = Fpn<2, 2>;
type R4 = RationalFunction<F4>;
let c = |n: u128| F4::from_index(n);
let rf = |num: Vec<u128>, den: Vec<u128>| -> R4 {
RationalFunction::new(
num.into_iter().map(c).collect(),
den.into_iter().map(c).collect(),
)
};
let samples = [
rf(vec![0, 1], vec![1]), rf(vec![1, 1], vec![1]), rf(vec![2, 1], vec![1]), rf(vec![0, 1], vec![1, 1]), rf(vec![2], vec![0, 1]), ];
for a in &samples {
for b in &samples {
assert_eq!(
artin_schreier_reciprocity_sum(a, b),
0,
"reciprocity at a={a:?} b={b:?}"
);
}
}
}
#[test]
fn symbol_relations() {
let places = [
FunctionFieldPlace::Infinite,
FunctionFieldPlace::Finite(p2(&[0, 1])), FunctionFieldPlace::Finite(p2(&[1, 1])), FunctionFieldPlace::Finite(p2(&[1, 1, 1])), ];
let samples = [r2(&[0, 1], &[1]), r2(&[1, 1], &[1]), r2(&[1], &[0, 1])];
for a in &samples {
for b in &samples {
for pl in &places {
let b2 = b.mul(b);
assert_eq!(artin_schreier_symbol_at(a, &b2, pl), 0, "s(a, b²) = 0");
assert_eq!(artin_schreier_symbol_at(a, a, pl), 0, "s(a, a) = 0");
let wp = a.mul(a).add(a); assert_eq!(artin_schreier_symbol_at(&wp, b, pl), 0, "s(℘(x), b) = 0");
}
for pl in &places {
let lhs = artin_schreier_symbol_at(&samples[0].add(&samples[1]), b, pl);
let rhs = artin_schreier_symbol_at(&samples[0], b, pl)
^ artin_schreier_symbol_at(&samples[1], b, pl);
assert_eq!(lhs, rhs, "additive in a");
}
}
}
}
}