use crate::scalar::Scalar;
#[derive(Debug, Clone, PartialEq)]
pub struct SymplecticForm<S: Scalar> {
gram: Vec<Vec<S>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SymplecticInvariants {
pub rank: usize,
pub radical_dim: usize,
}
impl SymplecticInvariants {
pub fn planes(&self) -> usize {
self.rank / 2
}
pub fn display(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for SymplecticInvariants {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"SymplecticInvariants(rank={}, radical_dim={}, planes={})",
self.rank,
self.radical_dim,
self.planes()
)
}
}
impl<S: Scalar> SymplecticForm<S> {
pub fn from_gram(gram: Vec<Vec<S>>) -> Option<Self> {
let n = gram.len();
for row in &gram {
if row.len() != n {
return None;
}
}
for i in 0..n {
if !gram[i][i].is_zero() {
return None;
}
for j in (i + 1)..n {
if gram[i][j] != gram[j][i].neg() {
return None;
}
}
}
Some(SymplecticForm { gram })
}
pub fn hyperbolic(r: usize) -> Self {
let n = 2 * r;
let mut gram = vec![vec![S::zero(); n]; n];
for k in 0..r {
gram[2 * k][2 * k + 1] = S::one();
gram[2 * k + 1][2 * k] = S::one().neg();
}
SymplecticForm { gram }
}
pub fn dim(&self) -> usize {
self.gram.len()
}
pub fn gram(&self) -> &[Vec<S>] {
&self.gram
}
pub fn direct_sum(&self, other: &SymplecticForm<S>) -> SymplecticForm<S> {
let (n, m) = (self.dim(), other.dim());
let mut gram = vec![vec![S::zero(); n + m]; n + m];
for i in 0..n {
for j in 0..n {
gram[i][j] = self.gram[i][j].clone();
}
}
for i in 0..m {
for j in 0..m {
gram[n + i][n + j] = other.gram[i][j].clone();
}
}
SymplecticForm { gram }
}
pub fn classify(&self) -> Option<SymplecticInvariants> {
let n = self.dim();
let radical_dim = crate::linalg::field::unit_pivot_nullspace(self.gram.clone(), n)?.len();
Some(SymplecticInvariants {
rank: n - radical_dim,
radical_dim,
})
}
}
pub fn classify_symplectic<S: Scalar>(gram: Vec<Vec<S>>) -> Option<SymplecticInvariants> {
SymplecticForm::from_gram(gram)?.classify()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::{Nimber, Rational};
fn r(n: i128) -> Rational {
Rational::from_int(n)
}
#[test]
fn hyperbolic_plane_has_rank_two() {
let h = SymplecticForm::<Rational>::hyperbolic(1);
assert_eq!(
h.classify().unwrap(),
SymplecticInvariants {
rank: 2,
radical_dim: 0
}
);
assert_eq!(h.classify().unwrap().planes(), 1);
}
#[test]
fn rank_is_always_even_and_radical_splits_off() {
let f = SymplecticForm::<Rational>::hyperbolic(2).direct_sum(
&SymplecticForm::from_gram(vec![vec![r(0)]]).unwrap(), );
let c = f.classify().unwrap();
assert_eq!((c.rank, c.radical_dim), (4, 1));
assert_eq!(c.rank % 2, 0);
}
#[test]
fn non_alternating_is_rejected() {
assert!(SymplecticForm::from_gram(vec![vec![r(1), r(0)], vec![r(0), r(0)]]).is_none());
assert!(SymplecticForm::from_gram(vec![vec![r(0), r(1)], vec![r(1), r(0)]]).is_none());
}
#[test]
fn char_two_alternating_is_symmetric_with_zero_diagonal() {
let h =
SymplecticForm::from_gram(vec![vec![Nimber(0), Nimber(1)], vec![Nimber(1), Nimber(0)]])
.unwrap();
assert_eq!(
h.classify().unwrap(),
SymplecticInvariants {
rank: 2,
radical_dim: 0
}
);
assert!(SymplecticForm::from_gram(vec![
vec![Nimber(1), Nimber(1)],
vec![Nimber(1), Nimber(0)],
])
.is_none());
}
#[test]
fn degenerate_form_is_all_radical() {
let z = SymplecticForm::<Rational>::from_gram(vec![vec![r(0); 3]; 3]).unwrap();
assert_eq!(
z.classify().unwrap(),
SymplecticInvariants {
rank: 0,
radical_dim: 3
}
);
}
#[test]
fn free_function_matches_method() {
let g = SymplecticForm::<Rational>::hyperbolic(3);
assert_eq!(classify_symplectic(g.gram().to_vec()), g.classify());
}
#[test]
fn nonfield_nonunit_pivot_is_refused() {
use crate::scalar::Integer;
let gram = vec![vec![Integer(0), Integer(2)], vec![Integer(-2), Integer(0)]];
let f = SymplecticForm::from_gram(gram).unwrap();
assert_eq!(f.classify(), None);
}
}