use crate::forms::FiniteOddField;
use crate::scalar::{Rational, RationalFunction, Scalar};
pub trait GlobalField: Scalar {
type Place: Clone + std::fmt::Debug + PartialEq;
fn try_relevant_places(entries: &[Self]) -> Option<Vec<Self::Place>>;
fn try_hilbert_symbol_at(a: &Self, b: &Self, place: &Self::Place) -> Option<i128>;
fn try_is_local_square(x: &Self, place: &Self::Place) -> Option<bool>;
fn try_is_global_square(x: &Self) -> Option<bool>;
fn try_is_isotropic_at_place(entries: &[Self], place: &Self::Place) -> Option<bool>;
fn try_hasse_at_place(entries: &[Self], place: &Self::Place) -> Option<i128> {
let mut h = 1i128;
for i in 0..entries.len() {
for j in (i + 1)..entries.len() {
h *= Self::try_hilbert_symbol_at(&entries[i], &entries[j], place)?;
}
}
Some(h)
}
fn try_reciprocity_product(a: &Self, b: &Self) -> Option<i128> {
if a.is_zero() || b.is_zero() {
return None;
}
let pair = [a.clone(), b.clone()];
let mut prod = 1i128;
for pl in Self::try_relevant_places(&pair)? {
prod *= Self::try_hilbert_symbol_at(a, b, &pl)?;
}
Some(prod)
}
fn try_ramified_places(a: &Self, b: &Self) -> Option<Vec<Self::Place>> {
if a.is_zero() || b.is_zero() {
return None;
}
let pair = [a.clone(), b.clone()];
let mut ramified = Vec::new();
for pl in Self::try_relevant_places(&pair)? {
if Self::try_hilbert_symbol_at(a, b, &pl)? == -1 {
ramified.push(pl);
}
}
Some(ramified)
}
fn try_is_isotropic_global(entries: &[Self]) -> Option<bool> {
if entries.iter().any(|e| e.is_zero()) {
return Some(true);
}
Some(match entries.len() {
0 | 1 => false,
2 => Self::try_is_global_square(&entries[0].mul(&entries[1]).neg())?,
_ => {
for pl in Self::try_relevant_places(entries)? {
if !Self::try_is_isotropic_at_place(entries, &pl)? {
return Some(false);
}
}
true
}
})
}
}
fn try_rat_square_class(q: &Rational) -> Option<i128> {
q.numer().checked_mul(q.denom())
}
impl GlobalField for Rational {
type Place = crate::forms::Place;
fn try_relevant_places(entries: &[Self]) -> Option<Vec<Self::Place>> {
use crate::forms::{relevant_primes, Place};
if entries.iter().any(|x| x.is_zero()) {
return None;
}
let classes: Vec<i128> = entries
.iter()
.map(try_rat_square_class)
.collect::<Option<Vec<_>>>()?;
let mut places = vec![Place::Real];
places.extend(relevant_primes(&classes).into_iter().map(Place::Prime));
Some(places)
}
fn try_hilbert_symbol_at(a: &Self, b: &Self, place: &Self::Place) -> Option<i128> {
crate::forms::try_hilbert_symbol_at(
try_rat_square_class(a)?,
try_rat_square_class(b)?,
*place,
)
}
fn try_is_local_square(x: &Self, place: &Self::Place) -> Option<bool> {
use crate::forms::{try_is_square_qp, Place};
if x.is_zero() {
return Some(false);
}
let c = try_rat_square_class(x)?;
match place {
Place::Real => Some(c >= 0),
Place::Prime(p) => try_is_square_qp(c, *p),
}
}
fn try_is_global_square(x: &Self) -> Option<bool> {
if x.is_zero() {
return Some(false);
}
Some(crate::forms::is_perfect_square(try_rat_square_class(x)?))
}
fn try_is_isotropic_at_place(entries: &[Self], place: &Self::Place) -> Option<bool> {
use crate::forms::{try_is_isotropic_at_p, Place};
if entries.iter().any(|e| e.is_zero()) {
return Some(true);
}
match place {
Place::Real => {
let classes: Vec<i128> = entries
.iter()
.map(try_rat_square_class)
.collect::<Option<Vec<_>>>()?;
Some(classes.iter().any(|&c| c > 0) && classes.iter().any(|&c| c < 0))
}
Place::Prime(p) => {
let nz: Vec<i128> = entries
.iter()
.map(try_rat_square_class)
.collect::<Option<Vec<_>>>()?;
try_is_isotropic_at_p(&nz, *p)
}
}
}
}
impl<S: FiniteOddField> GlobalField for RationalFunction<S> {
type Place = crate::forms::FunctionFieldPlace<S>;
fn try_relevant_places(entries: &[Self]) -> Option<Vec<Self::Place>> {
crate::forms::try_relevant_places_ff(entries)
}
fn try_hilbert_symbol_at(a: &Self, b: &Self, place: &Self::Place) -> Option<i128> {
crate::forms::try_hilbert_symbol_ff(a, b, place)
}
fn try_is_local_square(x: &Self, place: &Self::Place) -> Option<bool> {
crate::forms::try_is_local_square_ff(x, place)
}
fn try_is_global_square(x: &Self) -> Option<bool> {
Some(crate::forms::is_global_square_ff(x))
}
fn try_is_isotropic_at_place(entries: &[Self], place: &Self::Place) -> Option<bool> {
crate::forms::try_is_isotropic_at_place_ff(entries, place)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::Fp;
fn reciprocity_and_even_ramification<G: GlobalField>(samples: &[G]) {
for a in samples {
for b in samples {
assert_eq!(
G::try_reciprocity_product(a, b),
Some(1),
"reciprocity ∏_v (a,b)_v = +1 failed at a={a:?} b={b:?}"
);
assert_eq!(
G::try_ramified_places(a, b).unwrap().len() % 2,
0,
"ramified place count must be even at a={a:?} b={b:?}"
);
}
}
}
#[test]
fn reciprocity_over_q() {
let samples: Vec<Rational> = [-3, -1, 1, 2, 3, 5, 6]
.iter()
.map(|&n| Rational::from_int(n))
.collect();
reciprocity_and_even_ramification(&samples);
}
#[test]
fn reciprocity_over_function_field() {
type F = RationalFunction<Fp<5>>;
let 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(),
)
};
let samples = [
rf(&[0, 1], &[1]), rf(&[1, 1], &[1]), rf(&[2], &[1]), rf(&[0, 1], &[1, 1]), rf(&[2, 0, 1], &[1]), ];
reciprocity_and_even_ramification(&samples);
}
#[test]
fn global_isotropy_matches_q_field_facade() {
use crate::forms::try_is_isotropic_q;
let forms: &[&[i128]] = &[
&[1, 1, 1],
&[1, 1, -1],
&[1, 1, -3],
&[1, 1, -2],
&[1, 1, 1, 1],
&[1, 1, 1, -1],
&[1, 1, 1, 1, -1],
&[1, 1, 1, 1, 1],
&[2, 3, -1],
&[1, -2, -5],
&[1, -1],
&[2, -8],
&[1, -2],
];
for f in forms {
let rats: Vec<Rational> = f.iter().map(|&n| Rational::from_int(n)).collect();
assert_eq!(
Rational::try_is_isotropic_global(&rats),
try_is_isotropic_q(f),
"generic vs Q-specific isotropy disagree on {f:?}"
);
}
}
#[test]
fn global_isotropy_matches_function_field_facade() {
use crate::forms::try_is_isotropic_ff;
type F = RationalFunction<Fp<5>>;
let 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(),
)
};
let forms: Vec<Vec<F>> = vec![
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])], vec![rf(&[1], &[1]), rf(&[0, 0, 4], &[1])], ];
for f in &forms {
assert_eq!(
RationalFunction::try_is_isotropic_global(f),
try_is_isotropic_ff(f),
"generic vs function-field-specific isotropy disagree on {f:?}"
);
}
}
}