pub(super) mod asnf;
mod global;
pub use global::{artin_schreier_form_places, global_is_pe, is_isotropic_global_char2};
use crate::forms::{artin_schreier_symbol_at, FiniteChar2Field, FunctionFieldPlace};
use crate::scalar::{Poly, RationalFunction, Scalar};
use std::collections::BTreeMap;
use asnf::{asnf, kmul, laurent, local_is_pe, local_is_square, merge_psi, valuation};
#[derive(Debug, Clone, PartialEq)]
pub struct Char2QuadForm<S: FiniteChar2Field> {
blocks: Vec<(RationalFunction<S>, RationalFunction<S>)>,
singular: Vec<RationalFunction<S>>,
}
impl<S: FiniteChar2Field> Char2QuadForm<S> {
pub fn from_blocks(blocks: Vec<(RationalFunction<S>, RationalFunction<S>)>) -> Self {
Self {
blocks,
singular: Vec::new(),
}
}
pub fn new(
blocks: Vec<(RationalFunction<S>, RationalFunction<S>)>,
singular: Vec<RationalFunction<S>>,
) -> Self {
debug_assert!(
singular.iter().all(|c| !c.is_zero()),
"Char2QuadForm::new: singular entries must be nonzero (c_j ≠ 0)"
);
Self { blocks, singular }
}
pub fn blocks(&self) -> &[(RationalFunction<S>, RationalFunction<S>)] {
&self.blocks
}
pub fn singular(&self) -> &[RationalFunction<S>] {
&self.singular
}
pub fn rank(&self) -> usize {
2 * self.blocks.len() + self.singular.len()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Char2LocalDecomp<S: FiniteChar2Field> {
pub phi0: u128,
pub psi: BTreeMap<usize, Poly<S>>,
pub phi1: u128,
}
impl<S: FiniteChar2Field> Char2LocalDecomp<S> {
pub fn display(&self) -> String {
self.to_string()
}
}
impl<S: FiniteChar2Field> std::fmt::Display for Char2LocalDecomp<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Char2LocalDecomp(F_{}): phi0={}, psi={{",
S::field_order(),
self.phi0
)?;
for (i, (pole, poly)) in self.psi.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{pole}: {poly}")?;
}
write!(f, "}}, phi1={}", self.phi1)
}
}
fn block_contribution<S: FiniteChar2Field>(
a: &RationalFunction<S>,
b: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> (u128, u128, BTreeMap<usize, Poly<S>>) {
let va = valuation(a, place).expect("a ≠ 0");
let vb = valuation(b, place).expect("b ≠ 0");
let a_hi = std::cmp::max(0, -vb);
let b_hi = std::cmp::max(0, -va);
let acoeffs = laurent(a, place, va, a_hi);
let bcoeffs = laurent(b, place, vb, b_hi);
let plo = va + vb;
let mut c_ev: BTreeMap<i128, Poly<S>> = BTreeMap::new();
let mut c_odd: BTreeMap<i128, Poly<S>> = BTreeMap::new();
let mut n = plo;
while n <= 0 {
let mut sev = Poly::<S>::zero();
let mut sod = Poly::<S>::zero();
let i_lo = std::cmp::max(va, n - b_hi);
let i_hi = std::cmp::min(a_hi, n - vb);
let mut i = i_lo;
while i <= i_hi {
let ai = &acoeffs[(i - va) as usize];
if !ai.is_zero() {
let bj = &bcoeffs[(n - i - vb) as usize];
if !bj.is_zero() {
let prod = kmul(ai, bj, place);
if i & 1 == 0 {
sev = sev.add(&prod);
} else {
sod = sod.add(&prod);
}
}
}
i += 1;
}
if !sev.is_zero() {
c_ev.insert(n, sev);
}
if !sod.is_zero() {
c_odd.insert(n, sod);
}
n += 1;
}
let (e0, r0) = asnf(&c_ev, plo, place);
let (e1, r1) = asnf(&c_odd, plo, place);
let mut psi = r0;
for (k, v) in r1 {
merge_psi(&mut psi, k, v);
}
(e0, e1, psi)
}
pub fn springer_decompose_local_char2<S: FiniteChar2Field>(
form: &Char2QuadForm<S>,
place: &FunctionFieldPlace<S>,
) -> Char2LocalDecomp<S> {
let mut phi0 = 0u128;
let mut phi1 = 0u128;
let mut psi: BTreeMap<usize, Poly<S>> = BTreeMap::new();
for (a, b) in &form.blocks {
if a.is_zero() || b.is_zero() {
continue; }
let (e0, e1, part) = block_contribution(a, b, place);
phi0 ^= e0;
phi1 ^= e1;
for (k, v) in part {
merge_psi(&mut psi, k, v);
}
}
Char2LocalDecomp { phi0, psi, phi1 }
}
fn binary_is_hyperbolic<S: FiniteChar2Field>(
a: &RationalFunction<S>,
b: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> bool {
if a.is_zero() || b.is_zero() {
return true;
}
local_is_pe(&a.mul(b), place)
}
fn nonsingular_anisotropic_dim<S: FiniteChar2Field>(
blocks: &[(RationalFunction<S>, RationalFunction<S>)],
place: &FunctionFieldPlace<S>,
) -> usize {
let form = Char2QuadForm::from_blocks(blocks.to_vec());
let d = springer_decompose_local_char2(&form, place);
if d.phi0 == 0 && d.phi1 == 0 && d.psi.is_empty() {
0
} else if d.phi0 == 1 && d.phi1 == 1 && d.psi.is_empty() {
4
} else {
2
}
}
fn singular_anisotropic_dim<S: FiniteChar2Field>(
singular: &[RationalFunction<S>],
place: &FunctionFieldPlace<S>,
) -> usize {
singular_square_representatives(singular, place).len()
}
fn singular_square_representatives<S: FiniteChar2Field>(
singular: &[RationalFunction<S>],
place: &FunctionFieldPlace<S>,
) -> Vec<RationalFunction<S>> {
let mut reps = Vec::new();
for c in singular.iter().filter(|c| !c.is_zero()) {
if reps.is_empty() {
reps.push(c.clone());
} else if reps.len() == 1 {
let ratio = c.mul(&reps[0].inv().expect("nonzero representative inverts"));
if !local_is_square(&ratio, place) {
reps.push(c.clone());
}
} else {
break;
}
}
reps
}
fn semisingular_clifford_at<S: FiniteChar2Field>(
blocks: &[(RationalFunction<S>, RationalFunction<S>)],
c: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> u128 {
let mut inv = 0u128;
for (a, b) in blocks {
if a.is_zero() || b.is_zero() {
continue;
}
let d = a.mul(b);
if local_is_pe(&d, place) {
continue;
}
let lambda = c.mul(&a.inv().expect("a ≠ 0"));
inv ^= artin_schreier_symbol_at(&d, &lambda, place);
}
inv
}
fn semisingular_anisotropic_dim<S: FiniteChar2Field>(
blocks: &[(RationalFunction<S>, RationalFunction<S>)],
c: &RationalFunction<S>,
place: &FunctionFieldPlace<S>,
) -> usize {
if semisingular_clifford_at(blocks, c, place) == 0 {
1
} else {
3
}
}
pub fn local_anisotropic_dim_char2<S: FiniteChar2Field>(
form: &Char2QuadForm<S>,
place: &FunctionFieldPlace<S>,
) -> Option<usize> {
let bl = &form.blocks;
let nb = bl.len();
let singular = singular_square_representatives(&form.singular, place);
let ns = singular.len();
let rank = 2 * nb + ns;
if rank == 0 {
return Some(0);
}
if nb == 0 {
return Some(singular_anisotropic_dim(&form.singular, place));
}
let reduced_blocks: Vec<_> = bl
.iter()
.filter(|(a, b)| !binary_is_hyperbolic(a, b, place))
.cloned()
.collect();
if reduced_blocks.len() != nb {
let reduced = Char2QuadForm::new(reduced_blocks, singular);
return local_anisotropic_dim_char2(&reduced, place);
}
if ns == 0 {
return Some(nonsingular_anisotropic_dim(bl, place));
}
if ns == 2 {
return Some(2);
}
if ns == 1 {
return Some(semisingular_anisotropic_dim(bl, &singular[0], place));
}
unreachable!("K_v has K_v²-dimension two")
}
pub fn local_is_isotropic_char2<S: FiniteChar2Field>(
form: &Char2QuadForm<S>,
place: &FunctionFieldPlace<S>,
) -> Option<bool> {
let rank = form.rank();
if rank == 0 {
return Some(false);
}
if rank >= 5 {
return Some(true);
}
local_anisotropic_dim_char2(form, place).map(|d| d < rank)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::Scalar;
use crate::scalar::{Fp, Fpn};
type F2 = Fp<2>;
type R2 = RationalFunction<F2>;
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(),
)
}
fn p2(c: &[i128]) -> Poly<F2> {
Poly::new(c.iter().map(|&n| F2::from_int(n)).collect())
}
fn place_t() -> FunctionFieldPlace<F2> {
FunctionFieldPlace::Finite(p2(&[0, 1]))
}
fn k2(n: i128) -> Poly<F2> {
Poly::constant(F2::from_int(n))
}
fn decomp(blocks: &[(R2, R2)]) -> Char2LocalDecomp<F2> {
springer_decompose_local_char2(&Char2QuadForm::from_blocks(blocks.to_vec()), &place_t())
}
#[test]
fn display_render_pin() {
let d = decomp(&[(r2(&[1], &[1]), r2(&[1], &[0, 0, 1]))]);
assert_eq!(
d.to_string(),
"Char2LocalDecomp(F_2): phi0=0, psi={1: 1}, phi1=0"
);
assert_eq!(d.display(), d.to_string());
let trivial: Char2LocalDecomp<F2> = Char2LocalDecomp {
phi0: 0,
psi: BTreeMap::new(),
phi1: 0,
};
assert_eq!(
trivial.to_string(),
"Char2LocalDecomp(F_2): phi0=0, psi={}, phi1=0"
);
}
#[test]
fn aj_oracle_perp_killed_by_p() {
let d = decomp(&[(r2(&[1], &[1]), r2(&[1, 1], &[0, 0, 1]))]); assert_eq!(d.phi0, 0);
assert_eq!(d.phi1, 0);
assert!(d.psi.is_empty());
}
#[test]
fn aj_oracle_wild_pole() {
let d = decomp(&[(r2(&[1], &[1]), r2(&[1], &[0, 0, 1]))]); assert_eq!(d.phi0, 0);
assert_eq!(d.phi1, 0);
assert_eq!(d.psi, BTreeMap::from([(1usize, k2(1))]));
}
#[test]
fn aj_oracle_residue_bit() {
let d = decomp(&[(r2(&[1, 1], &[1]), r2(&[1], &[1]))]);
assert_eq!(d.phi0, 1);
assert_eq!(d.phi1, 0);
assert!(d.psi.is_empty());
}
#[test]
fn aj_oracle_split_across_phi1_and_psi() {
let d = decomp(&[(r2(&[1, 1], &[1]), r2(&[1], &[0, 1]))]); assert_eq!(d.phi0, 0);
assert_eq!(d.phi1, 1);
assert_eq!(d.psi, BTreeMap::from([(1usize, k2(1))]));
}
#[test]
fn aj_oracle_sum_of_two_residue_bits_cancels() {
let one = r2(&[1], &[1]);
let single = decomp(&[(one.clone(), one.clone())]);
assert_eq!((single.phi0, single.phi1, single.psi.len()), (1, 0, 0));
let d = decomp(&[(one.clone(), one.clone()), (one.clone(), one.clone())]);
assert_eq!(d.phi0, 0);
assert_eq!(d.phi1, 0);
assert!(d.psi.is_empty());
}
#[test]
fn aj_oracle_anisotropic_u4() {
let d = decomp(&[
(r2(&[1], &[1]), r2(&[1], &[1])),
(r2(&[0, 1], &[1]), r2(&[1], &[0, 1])),
]);
assert_eq!(d.phi0, 1);
assert_eq!(d.phi1, 1);
assert!(d.psi.is_empty());
}
#[test]
fn aj_oracle_residue_plus_wild() {
let d = decomp(&[
(r2(&[1], &[1]), r2(&[1], &[0, 1])),
(r2(&[1], &[1]), r2(&[1], &[1])),
]);
assert_eq!(d.phi0, 1);
assert_eq!(d.phi1, 0);
assert_eq!(d.psi, BTreeMap::from([(1usize, k2(1))]));
}
fn form(blocks: &[(R2, R2)], singular: &[R2]) -> Char2QuadForm<F2> {
Char2QuadForm::new(blocks.to_vec(), singular.to_vec())
}
fn anis(blocks: &[(R2, R2)], singular: &[R2]) -> Option<usize> {
local_anisotropic_dim_char2(&form(blocks, singular), &place_t())
}
fn iso(blocks: &[(R2, R2)], singular: &[R2]) -> Option<bool> {
local_is_isotropic_char2(&form(blocks, singular), &place_t())
}
#[test]
fn iso_rank2() {
assert_eq!(
anis(&[(r2(&[1], &[1]), r2(&[1], &[0, 0, 1]))], &[]),
Some(2)
);
assert_eq!(
iso(&[(r2(&[1], &[1]), r2(&[1], &[0, 0, 1]))], &[]),
Some(false)
);
assert_eq!(
anis(&[(r2(&[1], &[1]), r2(&[1, 1], &[0, 0, 1]))], &[]),
Some(0)
);
assert_eq!(
iso(&[(r2(&[1], &[1]), r2(&[1, 1], &[0, 0, 1]))], &[]),
Some(true)
);
}
#[test]
fn iso_rank3() {
assert_eq!(
anis(&[(r2(&[1], &[1]), r2(&[1], &[0, 1]))], &[r2(&[1], &[1])]),
Some(1)
);
assert_eq!(
anis(&[(r2(&[1], &[1]), r2(&[1], &[1]))], &[r2(&[0, 1], &[1])]),
Some(3)
);
assert_eq!(
iso(&[(r2(&[1], &[1]), r2(&[1], &[1]))], &[r2(&[0, 1], &[1])]),
Some(false)
);
}
#[test]
fn pure_singular_local_dimension_reads_square_classes() {
let one = r2(&[1], &[1]);
let t = r2(&[0, 1], &[1]);
let t2 = r2(&[0, 0, 1], &[1]);
let one_plus_t = r2(&[1, 1], &[1]);
assert_eq!(anis(&[], &[one.clone(), t.clone()]), Some(2));
assert_eq!(iso(&[], &[one.clone(), t.clone()]), Some(false));
assert_eq!(anis(&[], &[one.clone(), one_plus_t.clone()]), Some(2));
assert_eq!(iso(&[], &[one.clone(), one_plus_t]), Some(false));
assert_eq!(anis(&[], &[one.clone(), t2.clone()]), Some(1));
assert_eq!(iso(&[], &[one.clone(), t2.clone()]), Some(true));
assert_eq!(anis(&[], &[one, t, t2]), Some(2));
assert_eq!(
iso(
&[],
&[r2(&[1], &[1]), r2(&[0, 1], &[1]), r2(&[0, 0, 1], &[1])]
),
Some(true)
);
}
#[test]
fn mixed_singular_tail_collapses_to_local_square_classes() {
let one = r2(&[1], &[1]);
let t = r2(&[0, 1], &[1]);
let t2 = r2(&[0, 0, 1], &[1]);
let one_plus_t = r2(&[1, 1], &[1]);
let block = [(one.clone(), one.clone())];
assert_eq!(
anis(&block, &[one.clone(), t2.clone()]),
anis(&block, std::slice::from_ref(&one))
);
assert_eq!(anis(&block, &[one.clone(), t.clone()]), Some(2));
assert_eq!(iso(&block, &[one.clone(), t]), Some(true));
assert_eq!(anis(&block, &[one.clone(), one_plus_t.clone()]), Some(2));
assert_eq!(iso(&block, &[one, one_plus_t]), Some(true));
}
#[test]
fn iso_rank4() {
let one = r2(&[1], &[1]);
let t = r2(&[0, 1], &[1]);
let inv_t = r2(&[1], &[0, 1]);
assert_eq!(
anis(
&[(one.clone(), inv_t.clone()), (one.clone(), one.clone())],
&[]
),
Some(2)
);
assert_eq!(
anis(
&[(one.clone(), one.clone()), (one.clone(), one.clone())],
&[]
),
Some(0)
);
assert_eq!(
anis(
&[(one.clone(), one.clone()), (t.clone(), inv_t.clone())],
&[]
),
Some(4)
);
assert_eq!(
iso(
&[(one.clone(), one.clone()), (t.clone(), inv_t.clone())],
&[]
),
Some(false)
);
}
#[test]
fn rank_ge_5_is_isotropic() {
let one = r2(&[1], &[1]);
assert_eq!(
iso(
&[(one.clone(), one.clone()), (one.clone(), one.clone())],
std::slice::from_ref(&one)
),
Some(true)
);
}
#[test]
fn high_rank_anisotropic_dim_strips_explicit_hyperbolic_blocks() {
let zero = R2::zero();
let one = r2(&[1], &[1]);
let t = r2(&[0, 1], &[1]);
let inv_t = r2(&[1], &[0, 1]);
assert_eq!(
anis(
&[
(zero.clone(), one.clone()),
(one.clone(), one.clone()),
(t.clone(), inv_t.clone())
],
&[]
),
Some(4)
);
assert_eq!(
anis(
&[
(zero.clone(), one.clone()),
(one.clone(), zero.clone()),
(one.clone(), one.clone())
],
&[]
),
Some(2)
);
}
#[test]
fn high_rank_nonsingular_dimension_uses_aj_kernel() {
let one = r2(&[1], &[1]);
let t = r2(&[0, 1], &[1]);
let inv_t = r2(&[1], &[0, 1]);
assert_eq!(
anis(
&[
(one.clone(), one.clone()),
(one.clone(), one.clone()),
(one.clone(), one.clone())
],
&[]
),
Some(2)
);
assert_eq!(
anis(
&[
(one.clone(), one.clone()),
(t.clone(), inv_t.clone()),
(one.clone(), one.clone())
],
&[]
),
Some(2)
);
assert_eq!(
anis(
&[
(one.clone(), one.clone()),
(t.clone(), inv_t.clone()),
(one.clone(), one.clone())
],
&[one.clone(), t.clone()]
),
Some(2)
);
}
#[test]
fn high_rank_one_class_singular_tail_uses_clifford_invariant() {
let one = r2(&[1], &[1]);
let t = r2(&[0, 1], &[1]);
let inv_t = r2(&[1], &[0, 1]);
assert_eq!(
anis(
&[(one.clone(), one.clone()), (one.clone(), one.clone())],
std::slice::from_ref(&one)
),
Some(1)
);
assert_eq!(
anis(
&[(one.clone(), one.clone()), (t.clone(), inv_t.clone())],
std::slice::from_ref(&one)
),
Some(3)
);
assert_eq!(
anis(
&[
(one.clone(), one.clone()),
(t.clone(), inv_t.clone()),
(one.clone(), one.clone())
],
std::slice::from_ref(&one)
),
Some(3)
);
assert_eq!(
anis(
&[
(one.clone(), one.clone()),
(one.clone(), one.clone()),
(one.clone(), one.clone())
],
std::slice::from_ref(&one)
),
Some(1)
);
}
#[test]
fn decomposition_agrees_with_isotropy_on_rank4() {
let one = r2(&[1], &[1]);
let t = r2(&[0, 1], &[1]);
let inv_t = r2(&[1], &[0, 1]);
for (blocks, expect_trivial) in [
(
vec![(one.clone(), one.clone()), (one.clone(), one.clone())],
true,
),
(
vec![(one.clone(), one.clone()), (t.clone(), inv_t.clone())],
false,
),
] {
let d = springer_decompose_local_char2(
&Char2QuadForm::from_blocks(blocks.clone()),
&place_t(),
);
let trivial = d.phi0 == 0 && d.phi1 == 0 && d.psi.is_empty();
assert_eq!(trivial, expect_trivial);
let dim = local_anisotropic_dim_char2(&Char2QuadForm::from_blocks(blocks), &place_t());
assert_eq!(trivial, dim == Some(0));
}
}
#[test]
fn aj_oracle_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 place =
FunctionFieldPlace::Finite(Poly::new(vec![F4::from_index(0), F4::from_index(1)])); let alpha = rf(vec![2], vec![1]); let blocks = vec![
(rf(vec![1], vec![1]), alpha.clone()), (rf(vec![0, 1], vec![1]), rf(vec![2], vec![0, 1])), ];
let d = springer_decompose_local_char2(&Char2QuadForm::from_blocks(blocks.clone()), &place);
assert_eq!(d.phi0, 1);
assert_eq!(d.phi1, 1);
assert!(d.psi.is_empty());
assert_eq!(
local_anisotropic_dim_char2(&Char2QuadForm::from_blocks(blocks), &place),
Some(4)
);
let h = springer_decompose_local_char2(
&Char2QuadForm::from_blocks(vec![(rf(vec![1], vec![1]), rf(vec![1], vec![1]))]),
&place,
);
assert_eq!((h.phi0, h.phi1, h.psi.len()), (0, 0, 0));
}
#[test]
fn decomposition_at_a_degree_two_place() {
let p = FunctionFieldPlace::Finite(p2(&[1, 1, 1])); let blocks = vec![(r2(&[1], &[1]), r2(&[1], &[1, 1, 1]))]; let d = springer_decompose_local_char2(&Char2QuadForm::from_blocks(blocks.clone()), &p);
assert_eq!(d.phi0, 0);
assert_eq!(d.phi1, 0);
assert_eq!(d.psi, BTreeMap::from([(1usize, Poly::<F2>::one())]));
assert_eq!(
local_anisotropic_dim_char2(&Char2QuadForm::from_blocks(blocks), &p),
Some(2)
);
}
#[test]
fn degree_two_place_keeps_hensel_carries_in_asnf() {
let p_poly = p2(&[1, 1, 1]);
let place = FunctionFieldPlace::Finite(p_poly.clone());
let p_sq = p_poly.mul(&p_poly);
let wp = R2::new(p2(&[0, 1, 0, 1]).coeffs().to_vec(), p_sq.coeffs().to_vec());
assert!(asnf::local_is_pe(&wp, &place));
let form = Char2QuadForm::from_blocks(vec![(r2(&[1], &[1]), wp)]);
let d = springer_decompose_local_char2(&form, &place);
assert_eq!((d.phi0, d.phi1), (0, 0));
assert!(d.psi.is_empty());
assert_eq!(local_anisotropic_dim_char2(&form, &place), Some(0));
assert_eq!(local_is_isotropic_char2(&form, &place), Some(true));
}
fn gi(blocks: &[(R2, R2)], singular: &[R2]) -> Option<bool> {
is_isotropic_global_char2(&form(blocks, singular))
}
#[test]
fn global_pe_direct() {
assert!(global_is_pe(&r2(&[0, 1, 1], &[1]))); assert!(global_is_pe(&r2(&[1, 1], &[0, 0, 1]))); assert!(global_is_pe(&R2::zero())); assert!(!global_is_pe(&r2(&[0, 1], &[1]))); assert!(!global_is_pe(&r2(&[1], &[0, 1]))); assert!(!global_is_pe(&r2(&[1], &[1]))); }
#[test]
fn global_rank2_pe_obstruction() {
let one = r2(&[1], &[1]);
assert_eq!(gi(&[(one.clone(), r2(&[0, 1, 1], &[1]))], &[]), Some(true));
assert_eq!(gi(&[(one.clone(), one.clone())], &[]), Some(false));
assert_eq!(gi(&[(one.clone(), r2(&[0, 1], &[1]))], &[]), Some(false));
}
#[test]
fn global_rank3() {
let one = r2(&[1], &[1]);
let t = r2(&[0, 1], &[1]);
assert_eq!(
gi(&[(one.clone(), t.clone())], std::slice::from_ref(&one)),
Some(true)
);
assert_eq!(
gi(&[(one.clone(), one.clone())], std::slice::from_ref(&t)),
Some(false)
);
}
#[test]
fn global_rank4() {
let one = r2(&[1], &[1]);
let t = r2(&[0, 1], &[1]);
let inv_t = r2(&[1], &[0, 1]);
assert_eq!(
gi(&[(one.clone(), t.clone()), (one.clone(), t.clone())], &[]),
Some(true)
);
assert_eq!(
gi(&[(one.clone(), one.clone()), (t.clone(), t.clone())], &[]),
Some(true)
);
assert_eq!(
gi(
&[(one.clone(), one.clone()), (t.clone(), inv_t.clone())],
&[]
),
Some(false)
);
}
#[test]
fn global_rank_ge_5_is_isotropic() {
let one = r2(&[1], &[1]);
let t = r2(&[0, 1], &[1]);
let inv_t = r2(&[1], &[0, 1]);
assert_eq!(
gi(
&[(one.clone(), one.clone()), (t.clone(), inv_t.clone())],
std::slice::from_ref(&one)
),
Some(true)
);
}
#[test]
fn global_totally_singular() {
let one = r2(&[1], &[1]);
let t = r2(&[0, 1], &[1]);
let t2 = r2(&[0, 0, 1], &[1]);
assert_eq!(gi(&[], &[one.clone(), t.clone()]), Some(false));
assert_eq!(gi(&[], &[one.clone(), t2.clone()]), Some(true));
assert_eq!(gi(&[], &[one.clone(), t.clone(), t2.clone()]), Some(true));
assert_eq!(
gi(&[(one.clone(), one.clone())], &[one.clone(), t.clone()]),
Some(true)
);
}
#[test]
fn global_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 one = rf(vec![1], vec![1]);
let alpha = rf(vec![2], vec![1]); assert_eq!(
is_isotropic_global_char2(&Char2QuadForm::from_blocks(vec![(
one.clone(),
one.clone()
)])),
Some(true)
);
assert_eq!(
is_isotropic_global_char2(&Char2QuadForm::from_blocks(vec![(
one.clone(),
alpha.clone()
)])),
Some(false)
);
}
}