use crate::scalar::{FiniteField, Scalar, Surcomplex};
use std::cmp::Ordering;
#[derive(Debug, Clone, PartialEq)]
pub struct HermitianForm<S: Scalar> {
gram: Vec<Vec<Surcomplex<S>>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HermitianSignature {
pub pos: usize,
pub neg: usize,
pub radical: usize,
}
impl HermitianSignature {
pub fn display(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for HermitianSignature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"HermitianSignature(pos={}, neg={}, radical={})",
self.pos, self.neg, self.radical
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FiniteHermitianForm<F: FiniteField> {
gram: Vec<Vec<F>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FiniteHermitianInvariants {
pub rank: usize,
pub radical_dim: usize,
pub characteristic: u128,
pub base_degree: usize,
pub extension_degree: usize,
pub base_field_order: Option<u128>,
pub extension_field_order: Option<u128>,
}
impl FiniteHermitianInvariants {
pub fn display(&self) -> String {
self.to_string()
}
}
impl std::fmt::Display for FiniteHermitianInvariants {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ext = self.extension_field_order.map_or_else(
|| format!("{}^{}", self.characteristic, self.extension_degree),
|q| q.to_string(),
);
let base = self.base_field_order.map_or_else(
|| format!("{}^{}", self.characteristic, self.base_degree),
|q| q.to_string(),
);
write!(
f,
"FiniteHermitianInvariants(rank={}, radical_dim={}, field=F_{ext} over F_{base})",
self.rank, self.radical_dim,
)
}
}
fn checked_pow_u128(base: u128, exp: usize) -> Option<u128> {
let mut out = 1u128;
for _ in 0..exp {
out = out.checked_mul(base)?;
}
Some(out)
}
fn ensure_supported_finite_hermitian<F: FiniteField>() -> bool {
F::ext_degree() > 0 && F::ext_degree().is_multiple_of(2)
}
fn finite_hermitian_conj<F: FiniteField>(x: F) -> F {
x.frobenius_iter(F::ext_degree() / 2)
}
fn matrix_rank<F: Scalar>(rows: Vec<Vec<F>>) -> usize {
let ncols = rows.first().map_or(0, |r| r.len());
let nullspace = crate::linalg::field::unit_pivot_nullspace(rows, ncols)
.expect("finite-field pivot is always invertible; unit_pivot_nullspace returned None");
ncols - nullspace.len()
}
impl<F: FiniteField> FiniteHermitianForm<F> {
pub fn from_gram(gram: Vec<Vec<F>>) -> Option<Self> {
if !ensure_supported_finite_hermitian::<F>() {
return None;
}
let n = gram.len();
for row in &gram {
if row.len() != n {
return None;
}
}
for i in 0..n {
if finite_hermitian_conj(gram[i][i]) != gram[i][i] {
return None;
}
for j in 0..n {
if gram[i][j] != finite_hermitian_conj(gram[j][i]) {
return None;
}
}
}
Some(FiniteHermitianForm { gram })
}
pub fn diagonal(entries: Vec<F>) -> Option<Self> {
if entries.iter().any(|&x| finite_hermitian_conj(x) != x) {
return None;
}
let n = entries.len();
let mut gram = vec![vec![F::zero(); n]; n];
for (i, x) in entries.into_iter().enumerate() {
gram[i][i] = x;
}
Self::from_gram(gram)
}
pub fn dim(&self) -> usize {
self.gram.len()
}
pub fn gram(&self) -> &[Vec<F>] {
&self.gram
}
pub fn direct_sum(&self, other: &FiniteHermitianForm<F>) -> FiniteHermitianForm<F> {
let (n, m) = (self.dim(), other.dim());
let mut gram = vec![vec![F::zero(); n + m]; n + m];
for i in 0..n {
for j in 0..n {
gram[i][j] = self.gram[i][j];
}
}
for i in 0..m {
for j in 0..m {
gram[n + i][n + j] = other.gram[i][j];
}
}
FiniteHermitianForm { gram }
}
pub fn rank(&self) -> usize {
matrix_rank(self.gram.clone())
}
pub fn classify(&self) -> FiniteHermitianInvariants {
let rank = self.rank();
let extension_degree = F::ext_degree();
let base_degree = extension_degree / 2;
FiniteHermitianInvariants {
rank,
radical_dim: self.dim() - rank,
characteristic: F::characteristic(),
base_degree,
extension_degree,
base_field_order: checked_pow_u128(F::characteristic(), base_degree),
extension_field_order: checked_pow_u128(F::characteristic(), extension_degree),
}
}
}
fn combine<S: Scalar>(
h: &mut [Vec<Surcomplex<S>>],
target: usize,
source: usize,
lambda: &Surcomplex<S>,
) {
let n = h.len();
for r in 0..n {
let add = lambda.mul(&h[r][source]);
h[r][target] = h[r][target].add(&add);
}
let cl = lambda.conj();
for c in 0..n {
let add = cl.mul(&h[source][c]);
h[target][c] = h[target][c].add(&add);
}
}
fn swap_rows_cols<S: Scalar>(h: &mut [Vec<Surcomplex<S>>], k: usize, i: usize) {
h.swap(k, i);
for row in h.iter_mut() {
row.swap(k, i);
}
}
fn ensure_pivot<S: Scalar>(h: &mut [Vec<Surcomplex<S>>], k: usize) -> bool {
let n = h.len();
if !h[k][k].is_zero() {
return true;
}
for i in (k + 1)..n {
if !h[i][i].is_zero() {
swap_rows_cols(h, k, i);
return true;
}
}
for j in (k + 1)..n {
if !h[k][j].is_zero() {
let lambda = h[k][j].conj();
combine(h, k, j, &lambda);
return true;
}
}
false }
impl<S: Scalar> HermitianForm<S> {
pub fn from_gram(gram: Vec<Vec<Surcomplex<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].im.is_zero() {
return None; }
for j in 0..n {
if gram[i][j] != gram[j][i].conj() {
return None; }
}
}
Some(HermitianForm { gram })
}
pub fn from_skew(gram: Vec<Vec<Surcomplex<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].re.is_zero() {
return None; }
for j in 0..n {
if gram[i][j] != gram[j][i].conj().neg() {
return None; }
}
}
let i_unit = Surcomplex::i();
let h: Vec<Vec<Surcomplex<S>>> = gram
.iter()
.map(|row| row.iter().map(|x| i_unit.mul(x)).collect())
.collect();
Self::from_gram(h)
}
pub fn diagonal(reals: Vec<S>) -> Self {
let n = reals.len();
let mut gram = vec![vec![Surcomplex::zero(); n]; n];
for (i, r) in reals.into_iter().enumerate() {
gram[i][i] = Surcomplex::new(r, S::zero());
}
HermitianForm { gram }
}
pub fn dim(&self) -> usize {
self.gram.len()
}
pub fn gram(&self) -> &[Vec<Surcomplex<S>>] {
&self.gram
}
pub fn direct_sum(&self, other: &HermitianForm<S>) -> HermitianForm<S> {
let (n, m) = (self.dim(), other.dim());
let mut gram = vec![vec![Surcomplex::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();
}
}
HermitianForm { gram }
}
pub fn diagonalize(&self) -> Vec<S> {
let n = self.dim();
let mut h = self.gram.clone();
for k in 0..n {
if !ensure_pivot(&mut h, k) {
continue; }
let pinv = h[k][k]
.inv()
.expect("nonzero real pivot inverts in a field");
for i in (k + 1)..n {
if !h[i][k].is_zero() {
let mu = h[k][i].neg().mul(&pinv); combine(&mut h, i, k, &mu);
}
}
}
(0..n).map(|k| h[k][k].re.clone()).collect()
}
pub fn signature(&self, sign: impl Fn(&S) -> Ordering) -> HermitianSignature {
let mut sig = HermitianSignature {
pos: 0,
neg: 0,
radical: 0,
};
for d in self.diagonalize() {
match sign(&d) {
Ordering::Greater => sig.pos += 1,
Ordering::Less => sig.neg += 1,
Ordering::Equal => sig.radical += 1,
}
}
sig
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::{Fpn, Nimber, Rational, Surreal};
type GC = Surcomplex<Rational>;
fn gc(re: i128, im: i128) -> GC {
Surcomplex::new(Rational::from_int(re), Rational::from_int(im))
}
fn rsign(x: &Rational) -> Ordering {
x.sign()
}
#[test]
fn finite_hermitian_forms_over_f9_are_rank_classified() {
type F9 = Fpn<3, 2>;
let one = F9::one();
let two = F9::from_int(2);
let x = F9::from_coeffs(&[0, 1]);
let xbar = x.frobenius_iter(1);
let h = FiniteHermitianForm::<F9>::from_gram(vec![vec![one, x], vec![xbar, two]])
.expect("H* = H for the middle Frobenius involution");
let inv = h.classify();
assert_eq!(inv.rank, 2);
assert_eq!(inv.radical_dim, 0);
assert_eq!(inv.characteristic, 3);
assert_eq!(inv.base_degree, 1);
assert_eq!(inv.extension_degree, 2);
assert_eq!(inv.base_field_order, Some(3));
assert_eq!(inv.extension_field_order, Some(9));
let split = FiniteHermitianForm::<F9>::diagonal(vec![one, one]).unwrap();
let hyperbolic = FiniteHermitianForm::<F9>::from_gram(vec![
vec![F9::zero(), one],
vec![one, F9::zero()],
])
.unwrap();
assert_eq!(split.classify(), hyperbolic.classify());
assert!(
FiniteHermitianForm::<F9>::from_gram(vec![vec![one, x], vec![x, two]]).is_none(),
"lower off-diagonal entry must be conjugated"
);
assert!(
FiniteHermitianForm::<F9>::diagonal(vec![x]).is_none(),
"diagonal entries must be fixed by conjugation"
);
}
#[test]
fn finite_hermitian_forms_include_char2_even_degree_fields() {
type F16 = Fpn<2, 4>;
let one = F16::one();
let x = F16::from_coeffs(&[0, 1, 0, 0]);
let xbar = x.frobenius_iter(2);
let h = FiniteHermitianForm::<F16>::from_gram(vec![
vec![one, x, F16::zero()],
vec![xbar, one, F16::zero()],
vec![F16::zero(), F16::zero(), F16::zero()],
])
.unwrap();
let inv = h.classify();
assert_eq!(inv.rank, 2);
assert_eq!(inv.radical_dim, 1);
assert_eq!(inv.characteristic, 2);
assert_eq!(inv.base_degree, 2);
assert_eq!(inv.base_field_order, Some(4));
assert_eq!(inv.extension_field_order, Some(16));
}
#[test]
fn finite_hermitian_forms_reject_odd_degree_fields() {
type F27 = Fpn<3, 3>;
assert!(FiniteHermitianForm::<F27>::from_gram(vec![vec![F27::one()]]).is_none());
}
#[test]
fn nimber_quadratic_middle_frobenius_reports_width_boundary() {
let h = FiniteHermitianForm::<Nimber>::diagonal(vec![Nimber(1), Nimber(0)]).unwrap();
let inv = h.classify();
assert_eq!(inv.rank, 1);
assert_eq!(inv.radical_dim, 1);
assert_eq!(inv.characteristic, 2);
assert_eq!(inv.base_degree, 64);
assert_eq!(inv.extension_degree, 128);
assert_eq!(inv.base_field_order, Some(1u128 << 64));
assert_eq!(inv.extension_field_order, None);
}
#[test]
fn diagonal_real_form_has_sylvester_signature() {
let h = HermitianForm::<Rational>::diagonal(vec![
Rational::from_int(1),
Rational::from_int(1),
Rational::from_int(-1),
]);
assert_eq!(
h.signature(rsign),
HermitianSignature {
pos: 2,
neg: 1,
radical: 0
}
);
}
#[test]
fn off_diagonal_hermitian_diagonalizes() {
let h = HermitianForm::from_gram(vec![vec![gc(2, 0), gc(0, 1)], vec![gc(0, -1), gc(2, 0)]])
.unwrap();
assert_eq!(
h.diagonalize(),
vec![Rational::from_int(2), Rational::new(3, 2)]
);
assert_eq!(
h.signature(rsign),
HermitianSignature {
pos: 2,
neg: 0,
radical: 0
}
);
assert!(HermitianForm::from_gram(vec![
vec![gc(2, 0), gc(0, 1)],
vec![gc(0, 1), gc(2, 0)], ])
.is_none());
}
#[test]
fn off_diagonal_pivot_uses_conjugate_partner() {
let h = HermitianForm::from_gram(vec![vec![gc(0, 0), gc(1, 1)], vec![gc(1, -1), gc(0, 0)]])
.unwrap();
assert_eq!(
h.diagonalize(),
vec![Rational::from_int(4), Rational::new(-1, 2)]
);
assert_eq!(
h.signature(rsign),
HermitianSignature {
pos: 1,
neg: 1,
radical: 0
}
);
}
#[test]
fn indefinite_and_radical() {
let h = HermitianForm::from_gram(vec![vec![gc(1, 0), gc(0, 0)], vec![gc(0, 0), gc(-1, 0)]])
.unwrap();
assert_eq!(h.signature(rsign).pos, 1);
assert_eq!(h.signature(rsign).neg, 1);
let rad =
HermitianForm::<Rational>::diagonal(vec![Rational::from_int(0), Rational::from_int(5)]);
assert_eq!(h.direct_sum(&h).signature(rsign).pos, 2); assert_eq!(rad.signature(rsign).radical, 1);
}
#[test]
fn skew_hermitian_signature_via_multiplication_by_i() {
let h = HermitianForm::<Rational>::from_skew(vec![
vec![gc(0, 0), gc(1, 0)],
vec![gc(-1, 0), gc(0, 0)],
])
.unwrap();
let sig = h.signature(rsign);
assert_eq!((sig.pos, sig.neg), (1, 1));
assert!(HermitianForm::<Rational>::from_skew(vec![
vec![gc(0, 2), gc(0, 0)],
vec![gc(0, 0), gc(0, -3)],
])
.is_some());
assert!(HermitianForm::<Rational>::from_skew(vec![
vec![gc(1, 0), gc(0, 0)],
vec![gc(0, 0), gc(0, 0)],
])
.is_none());
}
#[test]
fn signature_over_surreal_base() {
let h =
HermitianForm::<Surreal>::diagonal(vec![Surreal::omega(), Surreal::epsilon().neg()]);
let sig = h.signature(|x| x.sign());
assert_eq!(sig.pos, 1);
assert_eq!(sig.neg, 1);
}
}