use crate::clifford::Metric;
use crate::forms::Complex64;
use crate::forms::{arf_f2, ArfInvariants, OrthogonalType};
use crate::scalar::Nimber;
use std::fmt;
pub const HEISENBERG_WEIL_MATRIX_RANK_CAP: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtraspecialError {
DimensionTooLarge,
InvalidF2Data,
NonF2Metric,
GeneralBilinearMetric,
SingularPolarForm,
}
impl fmt::Display for ExtraspecialError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExtraspecialError::DimensionTooLarge => {
f.write_str("extraspecial bitmask dimension exceeds 128")
}
ExtraspecialError::InvalidF2Data => {
f.write_str("invalid F2 quadratic data for extraspecial group")
}
ExtraspecialError::NonF2Metric => {
f.write_str("extraspecial group needs a Metric<Nimber> over F2 entries")
}
ExtraspecialError::GeneralBilinearMetric => {
f.write_str("extraspecial group is undefined for general-bilinear metrics")
}
ExtraspecialError::SingularPolarForm => {
f.write_str("extraspecial group needs a nonsingular polar form")
}
}
}
}
impl std::error::Error for ExtraspecialError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtraspecialType {
Plus,
Minus,
}
impl ExtraspecialType {
pub fn orthogonal_type(self) -> OrthogonalType {
match self {
ExtraspecialType::Plus => OrthogonalType::OPlus,
ExtraspecialType::Minus => OrthogonalType::OMinus,
}
}
}
impl fmt::Display for ExtraspecialType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExtraspecialType::Plus => f.write_str("+"),
ExtraspecialType::Minus => f.write_str("-"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExtraspecialElement {
central: bool,
vector: u128,
}
impl ExtraspecialElement {
pub fn new(central: bool, vector: u128) -> Self {
ExtraspecialElement { central, vector }
}
pub fn central(&self) -> bool {
self.central
}
pub fn vector(&self) -> u128 {
self.vector
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Extraspecial2Group {
dim: usize,
qd: Vec<bool>,
bmat: Vec<u128>,
arf: ArfInvariants,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct CoordinateRow {
pivot: usize,
vector: u128,
coords: u128,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HeisenbergWeilRepresentation {
group: Extraspecial2Group,
symplectic_basis: Vec<u128>,
coordinate_rows: Vec<CoordinateRow>,
}
impl Extraspecial2Group {
pub fn from_f2(qd: Vec<bool>, bmat: Vec<u128>) -> Result<Self, ExtraspecialError> {
let dim = qd.len();
validate_f2_data(dim, &bmat)?;
if dim == 0 {
return Err(ExtraspecialError::SingularPolarForm);
}
let arf = arf_f2(dim, &qd, &bmat);
if arf.radical_dim != 0 || arf.rank != dim {
return Err(ExtraspecialError::SingularPolarForm);
}
Ok(Extraspecial2Group { dim, qd, bmat, arf })
}
pub fn from_nimber_metric(metric: &Metric<Nimber>) -> Result<Self, ExtraspecialError> {
if !metric.a().is_empty() {
return Err(ExtraspecialError::GeneralBilinearMetric);
}
let dim = metric.dim();
if dim > 128 {
return Err(ExtraspecialError::DimensionTooLarge);
}
let qd = metric
.q()
.iter()
.map(|x| match x.0 {
0 => Ok(false),
1 => Ok(true),
_ => Err(ExtraspecialError::NonF2Metric),
})
.collect::<Result<Vec<_>, _>>()?;
let mut bmat = vec![0u128; dim];
for (&(i, j), v) in metric.b() {
let bit = match v.0 {
0 => false,
1 => true,
_ => return Err(ExtraspecialError::NonF2Metric),
};
if bit {
bmat[i] |= 1u128 << j;
bmat[j] |= 1u128 << i;
}
}
Self::from_f2(qd, bmat)
}
pub fn dim(&self) -> usize {
self.dim
}
pub fn order_exponent(&self) -> usize {
self.dim + 1
}
pub fn order_u128(&self) -> Option<u128> {
(self.order_exponent() < 128).then_some(1u128 << self.order_exponent())
}
pub fn arf(&self) -> &ArfInvariants {
&self.arf
}
pub fn extraspecial_type(&self) -> ExtraspecialType {
if self.arf.arf == 0 {
ExtraspecialType::Plus
} else {
ExtraspecialType::Minus
}
}
pub fn heisenberg_weil_representation(&self) -> Option<HeisenbergWeilRepresentation> {
HeisenbergWeilRepresentation::from_group(self.clone())
}
pub fn identity(&self) -> ExtraspecialElement {
ExtraspecialElement::new(false, 0)
}
pub fn central_generator(&self) -> ExtraspecialElement {
ExtraspecialElement::new(true, 0)
}
pub fn generator(&self, i: usize) -> Option<ExtraspecialElement> {
(i < self.dim).then_some(ExtraspecialElement::new(false, 1u128 << i))
}
pub fn element(&self, central: bool, vector: u128) -> Option<ExtraspecialElement> {
(vector & !self.mask() == 0).then_some(ExtraspecialElement::new(central, vector))
}
pub fn contains(&self, x: &ExtraspecialElement) -> bool {
x.vector & !self.mask() == 0
}
pub fn q_value(&self, vector: u128) -> Option<bool> {
if vector & !self.mask() == 0 {
Some(self.q_value_unchecked(vector))
} else {
None
}
}
pub fn polar_value(&self, u: u128, v: u128) -> Option<bool> {
if (u | v) & !self.mask() == 0 {
Some(self.polar_value_unchecked(u, v))
} else {
None
}
}
pub fn cocycle_value(&self, u: u128, v: u128) -> Option<bool> {
if (u | v) & !self.mask() == 0 {
Some(self.cocycle_value_unchecked(u, v))
} else {
None
}
}
pub fn multiply(
&self,
x: &ExtraspecialElement,
y: &ExtraspecialElement,
) -> Option<ExtraspecialElement> {
if !self.contains(x) || !self.contains(y) {
return None;
}
Some(ExtraspecialElement::new(
x.central ^ y.central ^ self.cocycle_value_unchecked(x.vector, y.vector),
x.vector ^ y.vector,
))
}
pub fn inverse(&self, x: &ExtraspecialElement) -> Option<ExtraspecialElement> {
if !self.contains(x) {
return None;
}
Some(ExtraspecialElement::new(
x.central ^ self.q_value_unchecked(x.vector),
x.vector,
))
}
pub fn square(&self, x: &ExtraspecialElement) -> Option<ExtraspecialElement> {
if !self.contains(x) {
return None;
}
Some(ExtraspecialElement::new(
self.q_value_unchecked(x.vector),
0,
))
}
pub fn commutator(
&self,
x: &ExtraspecialElement,
y: &ExtraspecialElement,
) -> Option<ExtraspecialElement> {
if !self.contains(x) || !self.contains(y) {
return None;
}
Some(ExtraspecialElement::new(
self.polar_value_unchecked(x.vector, y.vector),
0,
))
}
fn mask(&self) -> u128 {
mask_for_dim(self.dim)
}
fn q_value_unchecked(&self, vector: u128) -> bool {
let mut acc = false;
let mut vv = vector;
while vv != 0 {
let i = vv.trailing_zeros() as usize;
vv &= vv - 1;
acc ^= self.qd[i];
acc ^= parity(self.bmat[i] & vector & above(i));
}
acc
}
fn polar_value_unchecked(&self, u: u128, v: u128) -> bool {
let mut acc = false;
let mut uu = u;
while uu != 0 {
let i = uu.trailing_zeros() as usize;
uu &= uu - 1;
acc ^= parity(self.bmat[i] & v);
}
acc
}
fn cocycle_value_unchecked(&self, u: u128, v: u128) -> bool {
let mut acc = false;
let mut uu = u;
while uu != 0 {
let i = uu.trailing_zeros() as usize;
uu &= uu - 1;
acc ^= self.qd[i] && ((v >> i) & 1 == 1);
acc ^= parity(self.bmat[i] & v & above(i));
}
acc
}
fn symplectic_basis(&self) -> Option<Vec<u128>> {
let mut remaining: Vec<u128> = (0..self.dim).map(|i| 1u128 << i).collect();
let mut basis = Vec::with_capacity(self.dim);
while !remaining.is_empty() {
let u = remaining[0];
let v = *remaining
.iter()
.find(|&&w| self.polar_value_unchecked(u, w))?;
basis.push(u);
basis.push(v);
let mut next = Vec::new();
for &w in &remaining {
let mut projected = w;
if self.polar_value_unchecked(w, v) {
projected ^= u;
}
if self.polar_value_unchecked(w, u) {
projected ^= v;
}
insert_independent(&mut next, projected);
}
remaining = next;
}
(basis.len() == self.dim).then_some(basis)
}
}
impl HeisenbergWeilRepresentation {
fn from_group(group: Extraspecial2Group) -> Option<Self> {
let symplectic_basis = group.symplectic_basis()?;
let coordinate_rows = coordinate_rows_for_basis(&symplectic_basis)?;
Some(HeisenbergWeilRepresentation {
group,
symplectic_basis,
coordinate_rows,
})
}
pub fn group(&self) -> &Extraspecial2Group {
&self.group
}
pub fn rank(&self) -> usize {
self.group.dim / 2
}
pub fn quotient_dim(&self) -> usize {
self.group.dim
}
pub fn hilbert_dim_u128(&self) -> Option<u128> {
(self.rank() < 128).then_some(1u128 << self.rank())
}
pub fn symplectic_basis(&self) -> &[u128] {
&self.symplectic_basis
}
pub fn basis_coordinates(&self, vector: u128) -> Option<u128> {
if vector & !self.group.mask() != 0 {
return None;
}
let mut v = vector;
let mut coords = 0u128;
for row in &self.coordinate_rows {
if (v >> row.pivot) & 1 == 1 {
v ^= row.vector;
coords ^= row.coords;
}
}
(v == 0).then_some(coords)
}
pub fn apply_to_basis_state(
&self,
x: &ExtraspecialElement,
ket: u128,
) -> Option<(Complex64, u128)> {
if !self.group.contains(x) || ket & !mask_for_dim(self.rank()) != 0 {
return None;
}
let coords = self.basis_coordinates(x.vector)?;
let product = self.ordered_product(coords)?;
let mut phase = Complex64::one();
let mut target = ket;
let mut cc = coords;
let mut ops = Vec::new();
while cc != 0 {
let k = cc.trailing_zeros() as usize;
cc &= cc - 1;
ops.push(k);
}
for k in ops.into_iter().rev() {
let (p, next) = self.apply_basis_operator(k, target);
phase = phase.mul(&p);
target = next;
}
if product.central ^ x.central {
phase = phase.scale(-1.0);
}
Some((phase, target))
}
pub fn matrix(&self, x: &ExtraspecialElement) -> Option<Vec<Vec<Complex64>>> {
let n = self.matrix_dim()?;
let mut out = vec![vec![Complex64::zero(); n]; n];
for col in 0..n {
let (phase, row) = self.apply_to_basis_state(x, col as u128)?;
out[row as usize][col] = phase;
}
Some(out)
}
pub fn transvection_intertwiner(&self, a: u128) -> Option<Vec<Vec<Complex64>>> {
self.transvection_intertwiner_with_sign(a, false)
}
pub fn verify_transvection_intertwines(&self, a: u128) -> bool {
if a == 0 || a & !self.group.mask() != 0 {
return false;
}
let Some(u) = self.transvection_intertwiner(a) else {
return false;
};
let Some(u_inv) = self.transvection_intertwiner_with_sign(a, true) else {
return false;
};
for i in 0..self.group.dim {
let v = 1u128 << i;
let target = if self.group.polar_value_unchecked(v, a) {
v ^ a
} else {
v
};
let Some(lhs) = self
.matrix(&ExtraspecialElement::new(false, v))
.map(|m| mat_mul(&mat_mul(&u, &m), &u_inv))
else {
return false;
};
let Some(rhs) = self.matrix(&ExtraspecialElement::new(false, target)) else {
return false;
};
if !mat_projectively_approx_eq(&lhs, &rhs, 1e-8) {
return false;
}
}
true
}
fn matrix_dim(&self) -> Option<usize> {
if self.rank() > HEISENBERG_WEIL_MATRIX_RANK_CAP {
return None;
}
Some(1usize << self.rank())
}
fn ordered_product(&self, coords: u128) -> Option<ExtraspecialElement> {
let mut acc = self.group.identity();
let mut cc = coords;
while cc != 0 {
let k = cc.trailing_zeros() as usize;
cc &= cc - 1;
let basis_element = ExtraspecialElement::new(false, self.symplectic_basis[k]);
acc = self.group.multiply(&acc, &basis_element)?;
}
Some(acc)
}
fn apply_basis_operator(&self, k: usize, ket: u128) -> (Complex64, u128) {
let q = self.group.q_value_unchecked(self.symplectic_basis[k]);
let mut phase = if q {
Complex64::eighth_root(2)
} else {
Complex64::one()
};
let i = k / 2;
if k.is_multiple_of(2) {
(phase, ket ^ (1u128 << i))
} else {
if (ket >> i) & 1 == 1 {
phase = phase.scale(-1.0);
}
(phase, ket)
}
}
fn transvection_intertwiner_with_sign(
&self,
a: u128,
inverse: bool,
) -> Option<Vec<Vec<Complex64>>> {
if a == 0 || a & !self.group.mask() != 0 {
return None;
}
let p = self.matrix(&ExtraspecialElement::new(false, a))?;
let lambda = if self.group.q_value_unchecked(a) {
Complex64::one()
} else {
Complex64::eighth_root(2)
};
let signed_lambda = if inverse { lambda.scale(-1.0) } else { lambda };
let qp = mat_scale(&p, signed_lambda);
let id = mat_identity(qp.len());
Some(mat_scale(
&mat_add(&id, &qp),
Complex64::one().scale(std::f64::consts::FRAC_1_SQRT_2),
))
}
}
pub fn extraspecial_group_f2(
qd: Vec<bool>,
bmat: Vec<u128>,
) -> Result<Extraspecial2Group, ExtraspecialError> {
Extraspecial2Group::from_f2(qd, bmat)
}
pub fn extraspecial_group_nimber(
metric: &Metric<Nimber>,
) -> Result<Extraspecial2Group, ExtraspecialError> {
Extraspecial2Group::from_nimber_metric(metric)
}
pub fn heisenberg_weil_representation_f2(
qd: Vec<bool>,
bmat: Vec<u128>,
) -> Result<HeisenbergWeilRepresentation, ExtraspecialError> {
Extraspecial2Group::from_f2(qd, bmat).map(|g| {
g.heisenberg_weil_representation()
.expect("nonsingular alternating forms admit a symplectic basis")
})
}
pub fn heisenberg_weil_representation_nimber(
metric: &Metric<Nimber>,
) -> Result<HeisenbergWeilRepresentation, ExtraspecialError> {
Extraspecial2Group::from_nimber_metric(metric).map(|g| {
g.heisenberg_weil_representation()
.expect("nonsingular alternating forms admit a symplectic basis")
})
}
fn validate_f2_data(dim: usize, bmat: &[u128]) -> Result<(), ExtraspecialError> {
if dim > 128 {
return Err(ExtraspecialError::DimensionTooLarge);
}
if bmat.len() != dim {
return Err(ExtraspecialError::InvalidF2Data);
}
let mask = mask_for_dim(dim);
for i in 0..dim {
if bmat[i] & !mask != 0 || ((bmat[i] >> i) & 1 == 1) {
return Err(ExtraspecialError::InvalidF2Data);
}
for j in (i + 1)..dim {
if ((bmat[i] >> j) & 1) != ((bmat[j] >> i) & 1) {
return Err(ExtraspecialError::InvalidF2Data);
}
}
}
Ok(())
}
fn mask_for_dim(dim: usize) -> u128 {
if dim == 128 {
!0u128
} else if dim == 0 {
0
} else {
(1u128 << dim) - 1
}
}
fn above(i: usize) -> u128 {
if i >= 127 {
0
} else {
(!0u128) << (i + 1)
}
}
fn parity(mask: u128) -> bool {
mask.count_ones() & 1 == 1
}
fn insert_independent(rows: &mut Vec<u128>, mut v: u128) -> bool {
if v == 0 {
return false;
}
for &row in rows.iter() {
let p = row.trailing_zeros() as usize;
if (v >> p) & 1 == 1 {
v ^= row;
}
}
if v == 0 {
return false;
}
let pivot = v.trailing_zeros() as usize;
for row in rows.iter_mut() {
if (*row >> pivot) & 1 == 1 {
*row ^= v;
}
}
rows.push(v);
rows.sort_by_key(|row| row.trailing_zeros());
true
}
fn coordinate_rows_for_basis(basis: &[u128]) -> Option<Vec<CoordinateRow>> {
let mut rows: Vec<CoordinateRow> = Vec::new();
for (j, &b) in basis.iter().enumerate() {
let mut vector = b;
let mut coords = 1u128 << j;
for row in &rows {
if (vector >> row.pivot) & 1 == 1 {
vector ^= row.vector;
coords ^= row.coords;
}
}
if vector == 0 {
return None;
}
let pivot = vector.trailing_zeros() as usize;
for row in rows.iter_mut() {
if (row.vector >> pivot) & 1 == 1 {
row.vector ^= vector;
row.coords ^= coords;
}
}
rows.push(CoordinateRow {
pivot,
vector,
coords,
});
rows.sort_by_key(|row| row.pivot);
}
Some(rows)
}
fn mat_identity(n: usize) -> Vec<Vec<Complex64>> {
let mut out = vec![vec![Complex64::zero(); n]; n];
for (i, row) in out.iter_mut().enumerate() {
row[i] = Complex64::one();
}
out
}
fn mat_add(a: &[Vec<Complex64>], b: &[Vec<Complex64>]) -> Vec<Vec<Complex64>> {
a.iter()
.zip(b)
.map(|(ra, rb)| ra.iter().zip(rb).map(|(x, y)| x.add(y)).collect())
.collect()
}
fn mat_mul(a: &[Vec<Complex64>], b: &[Vec<Complex64>]) -> Vec<Vec<Complex64>> {
let n = a.len();
let m = b.first().map_or(0, Vec::len);
let inner = b.len();
let mut out = vec![vec![Complex64::zero(); m]; n];
for i in 0..n {
for k in 0..inner {
for j in 0..m {
out[i][j] = out[i][j].add(&a[i][k].mul(&b[k][j]));
}
}
}
out
}
fn mat_scale(a: &[Vec<Complex64>], c: Complex64) -> Vec<Vec<Complex64>> {
a.iter()
.map(|row| row.iter().map(|x| x.mul(&c)).collect())
.collect()
}
#[cfg(test)]
fn mat_approx_eq(a: &[Vec<Complex64>], b: &[Vec<Complex64>], tol: f64) -> bool {
a.len() == b.len()
&& a.iter().zip(b).all(|(ra, rb)| {
ra.len() == rb.len() && ra.iter().zip(rb).all(|(x, y)| x.approx_eq(y, tol))
})
}
fn mat_projectively_approx_eq(a: &[Vec<Complex64>], b: &[Vec<Complex64>], tol: f64) -> bool {
if a.len() != b.len() {
return false;
}
let mut scalar = None;
for (ra, rb) in a.iter().zip(b) {
if ra.len() != rb.len() {
return false;
}
for (x, y) in ra.iter().zip(rb) {
if y.abs() > tol {
let denom = y.re * y.re + y.im * y.im;
scalar = Some(Complex64 {
re: (x.re * y.re + x.im * y.im) / denom,
im: (x.im * y.re - x.re * y.im) / denom,
});
break;
} else if x.abs() > tol {
return false;
}
}
if scalar.is_some() {
break;
}
}
let Some(scalar) = scalar else {
return true;
};
a.iter().zip(b).all(|(ra, rb)| {
ra.iter()
.zip(rb)
.all(|(x, y)| x.approx_eq(&scalar.mul(y), tol))
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::clifford::Metric;
use crate::forms::arf_nimber;
use crate::scalar::Nimber;
use std::collections::BTreeMap;
fn bmat(dim: usize, pairs: &[(usize, usize)]) -> Vec<u128> {
let mut rows = vec![0u128; dim];
for &(i, j) in pairs {
rows[i] |= 1u128 << j;
rows[j] |= 1u128 << i;
}
rows
}
fn all_elements(g: &Extraspecial2Group) -> Vec<ExtraspecialElement> {
let mut out = Vec::new();
for vector in 0..(1u128 << g.dim()) {
out.push(g.element(false, vector).unwrap());
out.push(g.element(true, vector).unwrap());
}
out
}
fn check_representation(g: &Extraspecial2Group) {
let rep = g.heisenberg_weil_representation().unwrap();
assert_eq!(rep.quotient_dim(), g.dim());
assert_eq!(rep.hilbert_dim_u128(), Some(1u128 << rep.rank()));
let elems = all_elements(g);
for x in &elems {
let mx = rep.matrix(x).unwrap();
for y in &elems {
let my = rep.matrix(y).unwrap();
let xy = g.multiply(x, y).unwrap();
let mxy = rep.matrix(&xy).unwrap();
assert!(mat_approx_eq(&mat_mul(&mx, &my), &mxy, 1e-8));
}
}
}
fn check_group_laws(g: &Extraspecial2Group) {
let elems = all_elements(g);
let id = g.identity();
for x in &elems {
assert_eq!(g.multiply(&id, x), Some(*x));
assert_eq!(g.multiply(x, &id), Some(*x));
let inv = g.inverse(x).unwrap();
assert_eq!(g.multiply(x, &inv), Some(id));
assert_eq!(g.multiply(&inv, x), Some(id));
assert_eq!(
g.square(x),
Some(ExtraspecialElement::new(g.q_value(x.vector()).unwrap(), 0))
);
for y in &elems {
assert_eq!(
g.commutator(x, y),
Some(ExtraspecialElement::new(
g.polar_value(x.vector(), y.vector()).unwrap(),
0
))
);
for z in &elems {
let xy_z = g.multiply(&g.multiply(x, y).unwrap(), z).unwrap();
let x_yz = g.multiply(x, &g.multiply(y, z).unwrap()).unwrap();
assert_eq!(xy_z, x_yz);
}
}
}
}
#[test]
fn hyperbolic_plane_is_plus_type_d8_cell() {
let g = Extraspecial2Group::from_f2(vec![false, false], bmat(2, &[(0, 1)])).unwrap();
assert_eq!(g.order_exponent(), 3);
assert_eq!(g.order_u128(), Some(8));
assert_eq!(g.arf().arf, 0);
assert_eq!(g.extraspecial_type(), ExtraspecialType::Plus);
assert_eq!(
g.extraspecial_type().orthogonal_type(),
OrthogonalType::OPlus
);
let x = g.generator(0).unwrap();
let y = g.generator(1).unwrap();
assert_eq!(g.square(&x), Some(g.identity()));
assert_eq!(g.square(&y), Some(g.identity()));
assert_eq!(g.commutator(&x, &y), Some(g.central_generator()));
assert_eq!(
g.square(&g.multiply(&x, &y).unwrap()),
Some(g.central_generator())
);
check_group_laws(&g);
check_representation(&g);
let rep = g.heisenberg_weil_representation().unwrap();
assert!(rep.verify_transvection_intertwines(x.vector()));
assert!(rep.verify_transvection_intertwines(y.vector()));
}
#[test]
fn transvection_intertwiner_verifies_on_the_q_equals_one_branch() {
let g = Extraspecial2Group::from_f2(vec![false, false], bmat(2, &[(0, 1)])).unwrap();
let x = g.generator(0).unwrap();
let y = g.generator(1).unwrap();
let a = x.vector() ^ y.vector();
assert_eq!(g.q_value(a), Some(true));
let rep = g.heisenberg_weil_representation().unwrap();
assert!(rep.verify_transvection_intertwines(a));
}
#[test]
fn anisotropic_plane_is_minus_type_q8_cell() {
let g = Extraspecial2Group::from_f2(vec![true, true], bmat(2, &[(0, 1)])).unwrap();
assert_eq!(g.arf().arf, 1);
assert_eq!(g.extraspecial_type(), ExtraspecialType::Minus);
assert_eq!(
g.extraspecial_type().orthogonal_type(),
OrthogonalType::OMinus
);
for v in 1..4 {
let x = g.element(false, v).unwrap();
assert_eq!(g.square(&x), Some(g.central_generator()));
}
check_group_laws(&g);
check_representation(&g);
}
#[test]
fn heisenberg_representation_handles_rank_two_symplectic_reduction() {
let g =
Extraspecial2Group::from_f2(vec![false, true, true, false], bmat(4, &[(0, 2), (1, 3)]))
.unwrap();
let rep = g.heisenberg_weil_representation().unwrap();
assert_eq!(rep.rank(), 2);
assert_eq!(rep.hilbert_dim_u128(), Some(4));
for pair in rep.symplectic_basis().chunks(2) {
assert_eq!(g.polar_value(pair[0], pair[1]), Some(true));
}
check_representation(&g);
assert!(rep.verify_transvection_intertwines(0b0101));
}
#[test]
fn nimber_metric_route_matches_arf_classifier() {
let mut b = BTreeMap::new();
b.insert((0usize, 1usize), Nimber(1));
b.insert((2usize, 3usize), Nimber(1));
let metric = Metric::new(vec![Nimber(1), Nimber(1), Nimber(0), Nimber(0)], b);
let g = Extraspecial2Group::from_nimber_metric(&metric).unwrap();
let arf = arf_nimber(&metric).unwrap();
assert_eq!(g.arf(), &arf);
assert_eq!(g.extraspecial_type(), ExtraspecialType::Minus);
}
#[test]
fn rejects_singular_non_f2_and_general_metrics() {
assert_eq!(
Extraspecial2Group::from_f2(vec![true, true], bmat(2, &[])),
Err(ExtraspecialError::SingularPolarForm)
);
assert_eq!(
Extraspecial2Group::from_f2(vec![false, false], vec![0b10, 0]),
Err(ExtraspecialError::InvalidF2Data)
);
let metric = Metric::diagonal(vec![Nimber(2), Nimber(1)]);
assert_eq!(
Extraspecial2Group::from_nimber_metric(&metric),
Err(ExtraspecialError::NonF2Metric)
);
let mut upper = BTreeMap::new();
upper.insert((0usize, 1usize), Nimber(1));
let metric = Metric::general(vec![Nimber(0), Nimber(0)], BTreeMap::new(), upper);
assert_eq!(
Extraspecial2Group::from_nimber_metric(&metric),
Err(ExtraspecialError::GeneralBilinearMetric)
);
}
}