use crate::clifford::engine::add_term;
use crate::clifford::{bits, CliffordAlgebra, Multivector, MAX_BASIS_DIM};
use crate::scalar::Scalar;
use std::collections::BTreeMap;
pub fn tensor_square<S: Scalar>(alg: &CliffordAlgebra<S>) -> CliffordAlgebra<S> {
assert!(
alg.dim() * 2 <= MAX_BASIS_DIM,
"tensor square needs 2*dim <= {MAX_BASIS_DIM} for u128 blade encoding"
);
alg.graded_tensor(alg)
}
fn blade_of<S: Scalar>(alg: &CliffordAlgebra<S>, mask: u128) -> Multivector<S> {
alg.blade(&bits(mask))
}
pub fn coproduct<S: Scalar>(alg: &CliffordAlgebra<S>, mv: &Multivector<S>) -> Multivector<S> {
let dim = alg.dim();
assert!(
dim * 2 <= MAX_BASIS_DIM,
"coproduct tensor encoding needs 2*dim <= {MAX_BASIS_DIM}"
);
let mut out: BTreeMap<u128, S> = BTreeMap::new();
for (&mask_s, coeff) in &mv.terms {
let mut t = mask_s;
loop {
let u = mask_s ^ t;
let w = alg.wedge(&blade_of(alg, t), &blade_of(alg, u));
let sign = w.terms.get(&mask_s).cloned().unwrap_or_else(S::zero);
if !sign.is_zero() {
let tens = t | (u << dim);
add_term(&mut out, tens, coeff.mul(&sign));
}
if t == 0 {
break;
}
t = (t - 1) & mask_s;
}
}
Multivector { terms: out }
}
pub fn counit<S: Scalar>(alg: &CliffordAlgebra<S>, mv: &Multivector<S>) -> S {
alg.scalar_part(mv)
}
pub fn antipode<S: Scalar>(alg: &CliffordAlgebra<S>, mv: &Multivector<S>) -> Multivector<S> {
alg.grade_involution(mv)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::clifford::{grade, Metric};
use crate::scalar::Nimber;
use crate::scalar::Rational;
fn r(n: i128) -> Rational {
Rational::from_int(n)
}
fn pairs<S: Scalar>(alg: &CliffordAlgebra<S>, x: &Multivector<S>) -> BTreeMap<(u128, u128), S> {
let dim = alg.dim();
let low = if dim >= MAX_BASIS_DIM {
u128::MAX
} else {
(1u128 << dim) - 1
};
let dtens = coproduct(alg, x);
dtens
.terms
.into_iter()
.map(|(mask, c)| ((mask & low, mask >> dim), c))
.collect()
}
fn check_counit_law<S: Scalar>(alg: &CliffordAlgebra<S>, x: &Multivector<S>) {
let p = pairs(alg, x);
let mut left = alg.zero();
let mut right = alg.zero();
for (&(t, u), c) in &p {
if t == 0 {
left = alg.add(&left, &alg.scalar_mul(c, &alg.blade(&bits(u))));
}
if u == 0 {
right = alg.add(&right, &alg.scalar_mul(c, &alg.blade(&bits(t))));
}
}
assert_eq!(&left, x, "(ε⊗id)∘Δ ≠ id");
assert_eq!(&right, x, "(id⊗ε)∘Δ ≠ id");
}
fn check_coassociativity<S: Scalar>(alg: &CliffordAlgebra<S>, x: &Multivector<S>) {
let p = pairs(alg, x);
let mut lhs: BTreeMap<(u128, u128, u128), S> = BTreeMap::new();
let mut rhs: BTreeMap<(u128, u128, u128), S> = BTreeMap::new();
for (&(t, u), c) in &p {
for (&(t1, t2), d) in &pairs(alg, &alg.blade(&bits(t))) {
let key = (t1, t2, u);
let e = lhs.entry(key).or_insert_with(S::zero);
*e = e.add(&c.mul(d));
if e.is_zero() {
lhs.remove(&key);
}
}
for (&(u1, u2), d) in &pairs(alg, &alg.blade(&bits(u))) {
let key = (t, u1, u2);
let e = rhs.entry(key).or_insert_with(S::zero);
*e = e.add(&c.mul(d));
if e.is_zero() {
rhs.remove(&key);
}
}
}
assert_eq!(lhs, rhs, "coproduct is not coassociative");
}
fn check_antipode_axiom<S: Scalar>(alg: &CliffordAlgebra<S>, x: &Multivector<S>) {
let p = pairs(alg, x);
let mut acc = alg.zero();
for (&(t, u), c) in &p {
let st = antipode(alg, &alg.blade(&bits(t)));
let term = alg.mul(&st, &alg.blade(&bits(u)));
acc = alg.add(&acc, &alg.scalar_mul(c, &term));
}
let expect = alg.scalar(counit(alg, x));
assert_eq!(acc, expect, "antipode axiom failed");
}
fn run_axioms<S: Scalar>(alg: &CliffordAlgebra<S>, elts: &[Multivector<S>]) {
for x in elts {
check_counit_law(alg, x);
check_coassociativity(alg, x);
check_antipode_axiom(alg, x);
}
}
#[test]
fn hopf_axioms_grassmann_rational() {
let alg = CliffordAlgebra::new(3, Metric::<Rational>::grassmann(3));
let elts = [
alg.scalar(r(1)),
alg.e(0),
alg.e(1),
alg.wedge(&alg.e(0), &alg.e(1)),
alg.wedge(&alg.wedge(&alg.e(0), &alg.e(1)), &alg.e(2)),
alg.add(&alg.e(0), &alg.wedge(&alg.e(1), &alg.e(2))),
];
run_axioms(&alg, &elts);
}
#[test]
fn hopf_axioms_grassmann_nimber() {
let alg = CliffordAlgebra::new(3, Metric::<Nimber>::grassmann(3));
let elts = [
alg.scalar(Nimber(1)),
alg.e(0),
alg.wedge(&alg.e(0), &alg.e(1)),
alg.wedge(&alg.wedge(&alg.e(0), &alg.e(1)), &alg.e(2)),
];
run_axioms(&alg, &elts);
}
#[test]
fn antipode_is_grade_involution_not_reversion_twist() {
let alg = CliffordAlgebra::new(3, Metric::<Rational>::grassmann(3));
for mask in 0u128..8 {
let blade = alg.blade(&bits(mask));
let k = grade(mask);
let expect = if k & 1 == 1 {
alg.scalar_mul(&r(-1), &blade)
} else {
blade.clone()
};
assert_eq!(antipode(&alg, &blade), expect, "mask {mask:#b}");
if k == 2 {
assert_eq!(antipode(&alg, &blade), blade);
}
}
}
fn check_bialgebra_compatibility<S: Scalar>(alg: &CliffordAlgebra<S>, elts: &[Multivector<S>]) {
let tensor_alg = tensor_square(alg);
for a in elts {
for b in elts {
let lhs = coproduct(alg, &alg.wedge(a, b));
let rhs = tensor_alg.wedge(&coproduct(alg, a), &coproduct(alg, b));
assert_eq!(lhs, rhs, "Δ(a∧b) != Δ(a)∧Δ(b)");
}
}
}
#[test]
fn coproduct_is_algebra_map_for_wedge_rational() {
let alg = CliffordAlgebra::new(3, Metric::<Rational>::grassmann(3));
let elts = [
alg.scalar(r(1)),
alg.e(0),
alg.e(1),
alg.e(2),
alg.wedge(&alg.e(0), &alg.e(1)),
alg.wedge(&alg.wedge(&alg.e(0), &alg.e(1)), &alg.e(2)),
alg.add(&alg.e(0), &alg.wedge(&alg.e(1), &alg.e(2))),
];
check_bialgebra_compatibility(&alg, &elts);
}
#[test]
fn coproduct_is_algebra_map_for_wedge_small_dim() {
let alg = CliffordAlgebra::new(2, Metric::<Rational>::grassmann(2));
let elts = [
alg.scalar(r(1)),
alg.e(0),
alg.e(1),
alg.wedge(&alg.e(0), &alg.e(1)),
alg.add(&alg.e(0), &alg.e(1)),
];
check_bialgebra_compatibility(&alg, &elts);
}
#[test]
fn coproduct_is_algebra_map_for_wedge_nimber() {
let alg = CliffordAlgebra::new(3, Metric::<Nimber>::grassmann(3));
let elts = [
alg.scalar(Nimber(1)),
alg.e(0),
alg.e(1),
alg.e(2),
alg.wedge(&alg.e(0), &alg.e(1)),
alg.wedge(&alg.wedge(&alg.e(0), &alg.e(1)), &alg.e(2)),
alg.add(&alg.e(0), &alg.wedge(&alg.e(1), &alg.e(2))),
];
check_bialgebra_compatibility(&alg, &elts);
}
#[test]
fn antipode_is_identity_over_nimber() {
let alg = CliffordAlgebra::new(3, Metric::<Nimber>::grassmann(3));
for mask in 0u128..8 {
let blade = alg.blade(&bits(mask));
assert_eq!(antipode(&alg, &blade), blade);
}
}
}