use crate::forms::{is_square_finite, FiniteOddField};
use crate::scalar::{Poly, Rational, RationalFunction, Scalar};
#[derive(Debug, Clone, PartialEq)]
pub enum FunctionFieldPlace<S: Scalar> {
Infinite,
Finite(Poly<S>),
}
impl<S: Scalar> Eq for FunctionFieldPlace<S> {}
impl<S: Scalar> std::fmt::Display for FunctionFieldPlace<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FunctionFieldPlace::Infinite => f.write_str("∞"),
FunctionFieldPlace::Finite(pi) => write!(f, "{pi}"),
}
}
}
pub fn monic_irreducible_factors<S: FiniteOddField>(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 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)
}
pub(crate) fn try_kappa_order<S: FiniteOddField>(place: &FunctionFieldPlace<S>) -> Option<u128> {
let q = S::field_order();
match place {
FunctionFieldPlace::Finite(pi) => {
let deg = pi
.degree()
.expect("an irreducible has degree ≥ 1")
.try_into()
.ok()?;
let order = q.checked_pow(deg)?;
debug_assert!(
monic_irreducible_factors(pi) == vec![pi.clone()],
"FunctionFieldPlace::Finite must carry a monic irreducible polynomial \
(q^deg(π) is a field order only when π is prime in F_q[t])"
);
Some(order)
}
FunctionFieldPlace::Infinite => Some(q),
}
}
pub fn try_valuation_at_ff<S: FiniteOddField>(
a: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> Option<i128> {
if a.is_zero() {
return None;
}
Some(match place {
FunctionFieldPlace::Finite(pi) => {
debug_assert!(
monic_irreducible_factors(pi) == vec![pi.clone()],
"FunctionFieldPlace::Finite must carry a monic irreducible polynomial \
(the π-adic multiplicity is a discrete valuation only when π is prime in F_q[t])"
);
let (mn, _) = strip_factor(a.num().clone(), pi);
let (md, _) = strip_factor(a.den().clone(), pi);
mn - md
}
FunctionFieldPlace::Infinite => {
let dn = a.num().degree().expect("nonzero numerator") as i128;
let dd = a.den().degree().expect("monic nonzero denominator") as i128;
dd - dn }
})
}
pub(crate) fn try_residue_unit_at<S: FiniteOddField>(
a: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> Option<Poly<S>> {
if a.is_zero() {
return None;
}
match place {
FunctionFieldPlace::Finite(pi) => {
let (_, num_s) = strip_factor(a.num().clone(), pi);
let (_, den_s) = strip_factor(a.den().clone(), pi);
let num_mod = num_s.rem(pi);
let den_mod = den_s.rem(pi);
let den_inv = den_mod.pow_mod(try_kappa_order(place)?.checked_sub(2)?, pi);
Some(num_mod.mul_mod(&den_inv, pi))
}
FunctionFieldPlace::Infinite => {
let ln = *a.num().leading().expect("nonzero numerator");
let ld = *a.den().leading().expect("monic nonzero denominator");
Some(Poly::constant(ln.mul(&ld.inv()?)))
}
}
}
pub(crate) fn try_chi_kappa<S: FiniteOddField>(
unit: &Poly<S>,
place: &FunctionFieldPlace<S>,
) -> Option<i128> {
match place {
FunctionFieldPlace::Finite(pi) => {
let e = (try_kappa_order(place)?.checked_sub(1)?) / 2;
Some(if unit.pow_mod(e, pi) == Poly::one() {
1
} else {
-1 })
}
FunctionFieldPlace::Infinite => Some(if is_square_finite::<S>(unit.coeff(0)) {
1
} else {
-1
}),
}
}
pub fn try_is_local_square_ff<S: FiniteOddField>(
a: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> Option<bool> {
if a.is_zero() {
return Some(false);
}
Some(
try_valuation_at_ff(a, place)?.rem_euclid(2) == 0
&& try_chi_kappa(&try_residue_unit_at(a, place)?, place)? == 1,
)
}
pub fn try_hilbert_symbol_ff<S: FiniteOddField>(
a: &RationalFunction<S>,
b: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> Option<i128> {
if a.is_zero() || b.is_zero() {
return None;
}
let al = try_valuation_at_ff(a, place)?;
let be = try_valuation_at_ff(b, place)?;
let ca = try_chi_kappa(&try_residue_unit_at(a, place)?, place)?;
let cb = try_chi_kappa(&try_residue_unit_at(b, place)?, place)?;
let chi_neg1 = if try_kappa_order(place)? % 4 == 1 {
1
} else {
-1
};
Some(crate::forms::tame_hilbert_symbol(al, be, ca, cb, chi_neg1))
}
pub fn try_relevant_places_ff<S: FiniteOddField>(
entries: &[RationalFunction<S>],
) -> Option<Vec<FunctionFieldPlace<S>>> {
if entries.iter().any(|a| a.is_zero()) {
return None;
}
let mut polys: Vec<Poly<S>> = Vec::new();
for a in entries {
let factors = monic_irreducible_factors(a.num())
.into_iter()
.chain(monic_irreducible_factors(a.den()));
for pi in factors {
if !polys.contains(&pi) {
polys.push(pi);
}
}
}
let mut places: Vec<FunctionFieldPlace<S>> =
polys.into_iter().map(FunctionFieldPlace::Finite).collect();
places.push(FunctionFieldPlace::Infinite);
Some(places)
}
pub fn try_hasse_at_place_ff<S: FiniteOddField>(
entries: &[RationalFunction<S>],
place: &FunctionFieldPlace<S>,
) -> Option<i128> {
let mut h = 1i128;
for i in 0..entries.len() {
for j in (i + 1)..entries.len() {
h *= try_hilbert_symbol_ff(&entries[i], &entries[j], place)?;
}
}
Some(h)
}
pub fn try_hilbert_reciprocity_product_ff<S: FiniteOddField>(
a: &RationalFunction<S>,
b: &RationalFunction<S>,
) -> Option<i128> {
<RationalFunction<S> as crate::forms::GlobalField>::try_reciprocity_product(a, b)
}
fn neg_one<S: FiniteOddField>() -> RationalFunction<S> {
RationalFunction::from_base(S::one().neg())
}
fn disc<S: FiniteOddField>(entries: &[RationalFunction<S>]) -> RationalFunction<S> {
let mut d = RationalFunction::one();
for e in entries {
d = d.mul(e);
}
d
}
pub(crate) fn is_global_square_ff<S: FiniteOddField>(x: &RationalFunction<S>) -> bool {
if x.is_zero() {
return false;
}
let f = x.num().mul(x.den());
let lead = *f.leading().expect("nonzero square-class representative");
if !is_square_finite::<S>(lead) {
return false;
}
let mut g = f.make_monic();
for pi in monic_irreducible_factors(&f) {
let (mult, rest) = strip_factor(g, &pi);
if mult.rem_euclid(2) != 0 {
return false;
}
g = rest;
}
true
}
pub fn try_is_isotropic_at_place_ff<S: FiniteOddField>(
entries: &[RationalFunction<S>],
place: &FunctionFieldPlace<S>,
) -> Option<bool> {
if entries.iter().any(|a| a.is_zero()) {
return Some(true);
}
Some(match entries.len() {
0 | 1 => false,
2 => try_is_local_square_ff(&entries[0].mul(&entries[1]).neg(), place)?,
3 => {
let d = disc(entries);
try_hilbert_symbol_ff(&neg_one(), &d.neg(), place)?
== try_hasse_at_place_ff(entries, place)?
}
4 => {
let d = disc(entries);
!try_is_local_square_ff(&d, place)?
|| try_hasse_at_place_ff(entries, place)?
== try_hilbert_symbol_ff(&neg_one::<S>(), &neg_one::<S>(), place)?
}
_ => true,
})
}
pub fn try_is_isotropic_ff<S: FiniteOddField>(entries: &[RationalFunction<S>]) -> Option<bool> {
<RationalFunction<S> as crate::forms::GlobalField>::try_is_isotropic_global(entries)
}
#[derive(Debug, Clone)]
pub struct FFAdelicIsotropy<S: FiniteOddField> {
pub local: Vec<(FunctionFieldPlace<S>, bool)>,
}
impl<S: FiniteOddField> FFAdelicIsotropy<S> {
pub fn is_global(&self) -> bool {
self.local.iter().all(|(_, iso)| *iso)
}
pub fn display(&self) -> String {
self.to_string()
}
}
impl<S: FiniteOddField> std::fmt::Display for FFAdelicIsotropy<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "FFAdelicIsotropy(local=[")?;
for (i, (place, iso)) in self.local.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{place}={iso}")?;
}
write!(f, "], is_global={})", self.is_global())
}
}
pub fn try_isotropy_over_ff_adeles<S: FiniteOddField>(
entries: &[RationalFunction<S>],
) -> Option<FFAdelicIsotropy<S>> {
if entries.iter().any(|a| a.is_zero()) {
return Some(FFAdelicIsotropy { local: Vec::new() });
}
let mut local = Vec::new();
for pl in try_relevant_places_ff(entries)? {
let iso = try_is_isotropic_at_place_ff(entries, &pl)?;
local.push((pl, iso));
}
Some(FFAdelicIsotropy { local })
}
pub fn try_ramified_places_ff<S: FiniteOddField>(
a: &RationalFunction<S>,
b: &RationalFunction<S>,
) -> Option<Vec<FunctionFieldPlace<S>>> {
<RationalFunction<S> as crate::forms::GlobalField>::try_ramified_places(a, b)
}
fn frac_mod_one_ratio(m: i128, n: i128) -> Option<Rational> {
Rational::try_new(m.rem_euclid(n), n)
}
fn finite_order<S: FiniteOddField>(x: S) -> Option<u128> {
if x.is_zero() {
return None;
}
let group = S::field_order().checked_sub(1)?;
let mut cur = S::one();
for k in 1..=group {
cur = cur.mul(&x);
if cur == S::one() {
return Some(k);
}
}
None
}
fn constant_field_primitive<S: FiniteOddField>() -> Option<S> {
let group = S::field_order().checked_sub(1)?;
for i in 1..S::field_order() {
let g = S::from_index(i);
if finite_order(g) == Some(group) {
return Some(g);
}
}
None
}
fn kappa_mul<S: FiniteOddField>(
a: &Poly<S>,
b: &Poly<S>,
place: &FunctionFieldPlace<S>,
) -> Poly<S> {
match place {
FunctionFieldPlace::Finite(pi) => a.mul_mod(b, pi),
FunctionFieldPlace::Infinite => Poly::constant(a.coeff(0).mul(&b.coeff(0))),
}
}
fn kappa_pow<S: FiniteOddField>(
base: &Poly<S>,
mut e: u128,
place: &FunctionFieldPlace<S>,
) -> Poly<S> {
let mut acc = Poly::one();
let mut b = base.clone();
while e > 0 {
if e & 1 == 1 {
acc = kappa_mul(&acc, &b, place);
}
e >>= 1;
if e > 0 {
b = kappa_mul(&b, &b, place);
}
}
acc
}
fn kappa_pow_signed<S: FiniteOddField>(
base: &Poly<S>,
e: i128,
place: &FunctionFieldPlace<S>,
) -> Option<Poly<S>> {
if e >= 0 {
Some(kappa_pow(base, e as u128, place))
} else {
let inv = kappa_pow(base, try_kappa_order(place)?.checked_sub(2)?, place);
Some(kappa_pow(&inv, e.unsigned_abs(), place))
}
}
fn tame_symbol_raw_ff<S: FiniteOddField>(
a: &RationalFunction<S>,
b: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> Option<Poly<S>> {
if a.is_zero() || b.is_zero() {
return None;
}
let alpha = try_valuation_at_ff(a, place)?;
let beta = try_valuation_at_ff(b, place)?;
let mut raw = if alpha.rem_euclid(2) == 1 && beta.rem_euclid(2) == 1 {
Poly::constant(S::one().neg())
} else {
Poly::one()
};
raw = kappa_mul(
&raw,
&kappa_pow_signed(&try_residue_unit_at(a, place)?, beta, place)?,
place,
);
raw = kappa_mul(
&raw,
&kappa_pow_signed(&try_residue_unit_at(b, place)?, -alpha, place)?,
place,
);
Some(raw)
}
fn kappa_log_with_order<S: FiniteOddField>(
base: &Poly<S>,
x: &Poly<S>,
order: u128,
place: &FunctionFieldPlace<S>,
) -> Option<u128> {
let mut cur = Poly::one();
for e in 0..order {
if cur == *x {
return Some(e);
}
cur = kappa_mul(&cur, base, place);
}
None
}
pub fn try_tame_symbol_exponent_ff<S: FiniteOddField>(
n: u128,
a: &RationalFunction<S>,
b: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> Option<u128> {
if n == 0 {
return None;
}
let constant_group = S::field_order().checked_sub(1)?;
if constant_group % n != 0 {
return None;
}
if n == 1 {
return Some(0);
}
let raw = tame_symbol_raw_ff(a, b, place)?;
let residue_group = try_kappa_order(place)?.checked_sub(1)?;
let value = kappa_pow(&raw, residue_group.checked_div(n)?, place);
let zeta = constant_field_primitive::<S>()?.pow(constant_group.checked_div(n)?);
kappa_log_with_order(&Poly::constant(zeta), &value, n, place)
}
pub fn try_tame_symbol_invariant_ff<S: FiniteOddField>(
n: u128,
a: &RationalFunction<S>,
b: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> Option<Rational> {
let e = i128::try_from(try_tame_symbol_exponent_ff(n, a, b, place)?).ok()?;
let ni = i128::try_from(n).ok()?;
frac_mod_one_ratio(e, ni)
}
pub fn tame_symbol_invariants_ff<S: FiniteOddField>(
n: u128,
a: &RationalFunction<S>,
b: &RationalFunction<S>,
) -> Option<Vec<(FunctionFieldPlace<S>, Rational)>> {
if a.is_zero() || b.is_zero() || n == 0 {
return None;
}
if S::field_order().checked_sub(1)? % n != 0 {
return None;
}
let mut out = Vec::new();
for place in try_relevant_places_ff(&[a.clone(), b.clone()])? {
let inv = try_tame_symbol_invariant_ff(n, a, b, &place)?;
if !inv.is_zero() {
out.push((place, inv));
}
}
Some(out)
}
pub fn tame_symbol_invariant_sum_ff<S: FiniteOddField>(
n: u128,
a: &RationalFunction<S>,
b: &RationalFunction<S>,
) -> Option<Rational> {
let invs = tame_symbol_invariants_ff(n, a, b)?;
let sum = invs
.into_iter()
.fold(Rational::from_int(0), |acc, (_, inv)| acc.add(&inv));
frac_mod_one_ratio(sum.numer(), sum.denom())
}
pub fn constant_extension_invariants<S: FiniteOddField>(
n: u128,
a: &RationalFunction<S>,
) -> Option<Vec<(FunctionFieldPlace<S>, Rational)>> {
if a.is_zero() || n == 0 {
return None;
}
let ni = i128::try_from(n).ok()?;
let mut out = Vec::new();
for place in try_relevant_places_ff(std::slice::from_ref(a))? {
let v = try_valuation_at_ff(a, &place)?;
let deg = match &place {
FunctionFieldPlace::Finite(pi) => i128::try_from(pi.degree()?).ok()?,
FunctionFieldPlace::Infinite => 1,
};
let inv = frac_mod_one_ratio(deg.checked_mul(v)?, ni)?;
if !inv.is_zero() {
out.push((place, inv));
}
}
Some(out)
}
pub fn constant_extension_invariant_sum<S: FiniteOddField>(
n: u128,
a: &RationalFunction<S>,
) -> Option<Rational> {
let invs = constant_extension_invariants(n, a)?;
let sum = invs
.into_iter()
.fold(Rational::from_int(0), |acc, (_, inv)| acc.add(&inv));
frac_mod_one_ratio(sum.numer(), sum.denom())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::Fp;
type F = RationalFunction<Fp<5>>;
type PolyF = Poly<Fp<5>>;
fn rf(num: &[i128], den: &[i128]) -> F {
RationalFunction::new(
num.iter().map(|&n| Fp::<5>::from_int(n)).collect(),
den.iter().map(|&n| Fp::<5>::from_int(n)).collect(),
)
}
fn poly(c: &[i128]) -> PolyF {
Poly::new(c.iter().map(|&n| Fp::<5>::from_int(n)).collect())
}
#[test]
fn function_field_place_display_render_pin() {
assert_eq!(
FunctionFieldPlace::<Fp<5>>::Finite(poly(&[0, 1])).to_string(),
"t"
);
assert_eq!(
FunctionFieldPlace::<Fp<5>>::Finite(poly(&[1, 1])).to_string(),
"t + 1"
);
assert_eq!(FunctionFieldPlace::<Fp<5>>::Infinite.to_string(), "∞");
}
#[test]
fn ff_adelic_isotropy_display_render_pin() {
let iso = FFAdelicIsotropy::<Fp<5>> {
local: vec![
(FunctionFieldPlace::Finite(poly(&[0, 1])), true),
(FunctionFieldPlace::Infinite, false),
],
};
assert_eq!(
iso.to_string(),
"FFAdelicIsotropy(local=[t=true, ∞=false], is_global=false)"
);
assert_eq!(iso.display(), iso.to_string());
}
#[test]
fn factors_into_monic_irreducibles() {
let fs = monic_irreducible_factors(&poly(&[4, 0, 1]));
assert_eq!(fs.len(), 2);
assert!(fs.contains(&poly(&[4, 1]))); assert!(fs.contains(&poly(&[1, 1]))); let irr = monic_irreducible_factors(&poly(&[2, 0, 1]));
assert_eq!(irr, vec![poly(&[2, 0, 1])]);
let sq = monic_irreducible_factors(&poly(&[1, 2, 1])); assert_eq!(sq, vec![poly(&[1, 1])]);
}
#[test]
#[should_panic(expected = "monic irreducible polynomial")]
fn finite_place_precondition_rejects_reducible_polynomial() {
let reducible = FunctionFieldPlace::<Fp<5>>::Finite(poly(&[4, 0, 1]));
let _ = try_kappa_order(&reducible);
}
#[test]
fn valuations_at_places() {
let a = rf(&[0, 1], &[1, 1]);
assert_eq!(
try_valuation_at_ff(&a, &FunctionFieldPlace::Finite(poly(&[0, 1]))),
Some(1)
); assert_eq!(
try_valuation_at_ff(&a, &FunctionFieldPlace::Finite(poly(&[1, 1]))),
Some(-1)
); assert_eq!(
try_valuation_at_ff(&a, &FunctionFieldPlace::Infinite),
Some(0)
); assert_eq!(
try_valuation_at_ff(&rf(&[1], &[0, 0, 1]), &FunctionFieldPlace::Infinite),
Some(2)
);
}
#[test]
fn residue_field_order_overflow_returns_none() {
let pi = Poly::<Fp<5>>::monomial(56, Fp::<5>::one()).add(&Poly::one());
assert_eq!(try_kappa_order(&FunctionFieldPlace::Finite(pi)), None);
}
#[test]
fn hilbert_symbol_is_symmetric_and_steinberg() {
let samples = [
rf(&[0, 1], &[1]),
rf(&[2], &[1]),
rf(&[1, 1], &[1]),
rf(&[0, 1], &[1, 1]),
];
let places = [
FunctionFieldPlace::Infinite,
FunctionFieldPlace::Finite(poly(&[0, 1])), FunctionFieldPlace::Finite(poly(&[1, 1])), FunctionFieldPlace::Finite(poly(&[2, 0, 1])), ];
for a in &samples {
for b in &samples {
for pl in &places {
assert_eq!(
try_hilbert_symbol_ff(a, b, pl),
try_hilbert_symbol_ff(b, a, pl),
"symmetry"
);
}
let neg_a = a.mul(&F::from_base(Fp::<5>::from_int(-1)));
for pl in &places {
assert_eq!(
try_hilbert_symbol_ff(a, &neg_a, pl),
Some(1),
"(a,−a)_v = 1"
);
}
}
}
}
#[test]
fn reciprocity_holds_small() {
let samples = [
rf(&[0, 1], &[1]), rf(&[1, 1], &[1]), rf(&[2], &[1]), rf(&[0, 1], &[1, 1]), rf(&[2, 0, 1], &[1]), ];
for a in &samples {
for b in &samples {
assert_eq!(
try_hilbert_reciprocity_product_ff(a, b),
Some(1),
"reciprocity failed at a={a:?} b={b:?}"
);
}
}
}
#[test]
fn quaternion_ramifies_at_an_even_number_of_places() {
let a = rf(&[0, 1], &[1]); let b = rf(&[2], &[1]); let ram = try_ramified_places_ff(&a, &b).unwrap();
assert_eq!(ram.len(), 2, "even number of ramified places");
assert!(ram.contains(&FunctionFieldPlace::Finite(poly(&[0, 1])))); assert!(ram.contains(&FunctionFieldPlace::Infinite)); assert!(try_ramified_places_ff(&a, &rf(&[4], &[1]))
.unwrap()
.is_empty()); }
#[test]
fn hasse_minkowski_global_matches_adelic() {
let forms: [Vec<F>; 4] = [
vec![rf(&[1], &[1]), rf(&[1], &[1]), rf(&[4], &[1])], vec![rf(&[1], &[1]), rf(&[0, 1], &[1]), rf(&[0, 4], &[1])], vec![
rf(&[1], &[1]),
rf(&[0, 4], &[1]),
rf(&[3], &[1]),
rf(&[0, 2], &[1]),
],
vec![
rf(&[1], &[1]),
rf(&[0, 4], &[1]),
rf(&[3], &[1]),
rf(&[0, 2], &[1]),
rf(&[1], &[1]),
],
];
let expected = [true, true, false, true];
for (form, &exp) in forms.iter().zip(&expected) {
assert_eq!(
try_is_isotropic_ff(form),
Some(exp),
"global isotropy of {form:?}"
);
assert_eq!(
try_isotropy_over_ff_adeles(form).unwrap().is_global(),
exp,
"adelic isotropy of {form:?}"
);
}
}
#[test]
fn cross_checks_springer_laurent_at_a_linear_place() {
use crate::clifford::Metric;
use crate::forms::springer_decompose_laurent;
use crate::scalar::Laurent;
type L5 = Laurent<Fp<5>, 4>;
let lc = |cs: &[i128], v: i128| {
L5::from_coeffs(cs.iter().map(|&n| Fp::<5>::from_int(n)).collect(), v)
};
let pi = poly(&[4, 1]); let place = FunctionFieldPlace::Finite(pi.clone());
let ff = [
rf(&[2], &[1]), rf(&[4, 1], &[1]), rf(&[1, 0, 1], &[1]), ];
let laurent = Metric::diagonal(vec![lc(&[2], 0), lc(&[1], 1), lc(&[2, 2, 1], 0)]);
let decomp = springer_decompose_laurent(&laurent).unwrap();
for layer in &decomp.graded {
let at: Vec<&F> = ff
.iter()
.filter(|e| try_valuation_at_ff(e, &place) == Some(layer.valuation))
.collect();
assert_eq!(at.len(), layer.dim, "dim at valuation {}", layer.valuation);
let disc_sq = at.iter().fold(true, |acc, e| {
acc == (try_chi_kappa(&try_residue_unit_at(e, &place).unwrap(), &place) == Some(1))
});
assert_eq!(
disc_sq, layer.disc_is_square,
"disc class at valuation {}",
layer.valuation
);
}
}
#[test]
fn tame_symbol_quadratic_slice_matches_hilbert_symbol() {
let samples = [
rf(&[0, 1], &[1]), rf(&[2], &[1]), rf(&[1, 1], &[1]), rf(&[0, 1], &[1, 1]), ];
let places = [
FunctionFieldPlace::Infinite,
FunctionFieldPlace::Finite(poly(&[0, 1])),
FunctionFieldPlace::Finite(poly(&[1, 1])),
FunctionFieldPlace::Finite(poly(&[2, 0, 1])),
];
for a in &samples {
for b in &samples {
for place in &places {
let exp = try_tame_symbol_exponent_ff(2, a, b, place).unwrap();
let hilb = try_hilbert_symbol_ff(a, b, place).unwrap();
assert_eq!(
exp,
if hilb == 1 { 0 } else { 1 },
"quadratic tame slice at {place:?}"
);
}
}
}
}
#[test]
fn tame_symbol_degree_four_convention_and_reciprocity() {
let t = rf(&[0, 1], &[1]);
let two = rf(&[2], &[1]); let at_t = FunctionFieldPlace::Finite(poly(&[0, 1]));
assert_eq!(try_tame_symbol_exponent_ff(4, &two, &t, &at_t), Some(1));
assert_eq!(
try_tame_symbol_invariant_ff(4, &two, &t, &at_t),
Some(Rational::try_new(1, 4).unwrap())
);
assert_eq!(
try_tame_symbol_exponent_ff(4, &t, &two, &at_t),
Some(3),
"a^v(b)/b^v(a) makes the swapped symbol inverse"
);
assert_eq!(
try_tame_symbol_invariant_ff(4, &t, &two, &at_t),
Some(Rational::try_new(3, 4).unwrap())
);
let invs = tame_symbol_invariants_ff(4, &t, &two).unwrap();
assert_eq!(invs.len(), 2, "finite t-place plus infinity");
assert!(invs.contains(&(
FunctionFieldPlace::Finite(poly(&[0, 1])),
Rational::try_new(3, 4).unwrap()
)));
assert!(invs.contains(&(
FunctionFieldPlace::Infinite,
Rational::try_new(1, 4).unwrap()
)));
assert_eq!(
tame_symbol_invariant_sum_ff(4, &t, &two),
Some(Rational::zero())
);
assert_eq!(
tame_symbol_invariants_ff(3, &t, &two),
None,
"3 is tame at some residue extensions but μ_3 is not in F_5"
);
assert_eq!(tame_symbol_invariants_ff(4, &F::zero(), &two), None);
}
#[test]
fn constant_extension_reciprocity_full_strength() {
let samples = [
rf(&[0, 1], &[1]), rf(&[1, 1], &[1]), rf(&[0, 1], &[1, 1]), rf(&[2, 0, 1], &[1]), rf(&[0, 0, 1], &[2, 1]), ];
for n in [2u128, 3, 4, 5] {
for a in &samples {
assert_eq!(
constant_extension_invariant_sum(n, a),
Some(Rational::from_int(0)),
"reciprocity n={n} a={a:?}"
);
let mut div_deg = 0i128;
for place in try_relevant_places_ff(std::slice::from_ref(a)).unwrap() {
let v = try_valuation_at_ff(a, &place).unwrap();
let deg = match &place {
FunctionFieldPlace::Finite(pi) => pi.degree().unwrap() as i128,
FunctionFieldPlace::Infinite => 1,
};
div_deg += deg * v;
}
assert_eq!(div_deg, 0, "deg(div a)=0 for a={a:?}");
}
}
}
#[test]
fn constant_extension_image_is_full_and_good_places_split() {
assert_eq!(
constant_extension_invariants(3, &rf(&[2], &[1])),
Some(vec![])
);
let invs = constant_extension_invariants(3, &rf(&[0, 1], &[1])).unwrap();
let at_t = invs
.iter()
.find(|(pl, _)| *pl == FunctionFieldPlace::Finite(poly(&[0, 1])))
.map(|(_, r)| r.clone());
assert_eq!(at_t, Some(Rational::try_new(1, 3).unwrap()));
let invs_b = constant_extension_invariants(3, &rf(&[2, 0, 1], &[1])).unwrap();
let at_b = invs_b
.iter()
.find(|(pl, _)| *pl == FunctionFieldPlace::Finite(poly(&[2, 0, 1])))
.map(|(_, r)| r.clone());
assert_eq!(at_b, Some(Rational::try_new(2, 3).unwrap()));
assert_eq!(
constant_extension_invariants(1, &rf(&[0, 1], &[1])),
Some(vec![])
);
assert_eq!(constant_extension_invariants(0, &rf(&[0, 1], &[1])), None);
assert_eq!(constant_extension_invariants(3, &rf(&[0], &[1])), None);
}
#[test]
fn rank_two_is_a_global_square_condition() {
assert_eq!(
try_is_isotropic_ff(&[rf(&[1], &[1]), rf(&[0, 0, 4], &[1])]),
Some(true)
);
assert_eq!(
try_is_isotropic_ff(&[rf(&[1], &[1]), rf(&[0, 4], &[1])]),
Some(false)
);
assert_eq!(
try_is_isotropic_ff(&[rf(&[1], &[1]), rf(&[3], &[1])]),
Some(false)
);
assert_eq!(
try_is_isotropic_ff(&[rf(&[2], &[1]), rf(&[2], &[1])]),
Some(true)
); }
}