use std::{
fmt::{Debug, Display, Formatter},
marker::PhantomData,
};
use num_traits::One;
use super::*;
use crate::{
algebras::Algebra,
groups::{AbelianGroup, Group},
modules::{LeftModule, RightModule, TwoSidedModule},
rings::Field,
tensors::SVector,
};
pub trait QuadraticFormMarker<F: Field, const N: usize>: 'static + Copy + Debug {
fn coefficients() -> SVector<F, N>;
fn evaluate(v: &SVector<F, N>) -> F {
let coeffs = Self::coefficients();
let mut result = F::zero();
for i in 0..N {
result += coeffs[i] * v[i] * v[i];
}
result
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Signature<
const N: usize,
const C0: i64 = 1,
const C1: i64 = 1,
const C2: i64 = 1,
const C3: i64 = 1,
>;
impl<const N: usize, const C0: i64, const C1: i64, const C2: i64, const C3: i64>
QuadraticFormMarker<f64, N> for Signature<N, C0, C1, C2, C3>
{
fn coefficients() -> SVector<f64, N> {
let mut coeffs = SVector::<f64, N>::zeros();
if N > 0 {
coeffs[0] = C0 as f64;
}
if N > 1 {
coeffs[1] = C1 as f64;
}
if N > 2 {
coeffs[2] = C2 as f64;
}
if N > 3 {
coeffs[3] = C3 as f64;
}
coeffs
}
}
impl<const N: usize, const C0: i64, const C1: i64, const C2: i64, const C3: i64>
QuadraticFormMarker<f32, N> for Signature<N, C0, C1, C2, C3>
{
fn coefficients() -> SVector<f32, N> {
let mut coeffs = SVector::<f32, N>::zeros();
if N > 0 {
coeffs[0] = C0 as f32;
}
if N > 1 {
coeffs[1] = C1 as f32;
}
if N > 2 {
coeffs[2] = C2 as f32;
}
if N > 3 {
coeffs[3] = C3 as f32;
}
coeffs
}
}
pub type Euclidean2D = Signature<2, 1, 1>;
pub type Euclidean3D = Signature<3, 1, 1, 1>;
pub type Euclidean4D = Signature<4, 1, 1, 1, 1>;
pub type Minkowski = Signature<4, 1, -1, -1, -1>;
pub type AntiMinkowski = Signature<4, -1, 1, 1, 1>;
pub type SplitComplex = Signature<2, 1, -1>;
pub type ComplexSig = Signature<1, -1>;
pub type QuaternionSig = Signature<2, -1, -1>;
pub struct CliffordAlgebra<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> {
_marker: PhantomData<(F, Q)>,
}
impl<F: Field + Copy, const N: usize, Q: QuadraticFormMarker<F, N>> CliffordAlgebra<F, N, Q>
where [(); 1 << N]:
{
pub const fn new() -> Self { Self { _marker: PhantomData } }
pub fn element(&self, value: SVector<F, { 1 << N }>) -> CliffordAlgebraElement<F, N, Q> {
CliffordAlgebraElement { value, _marker: PhantomData }
}
pub fn blade<const I: usize>(&self, indices: [usize; I]) -> CliffordAlgebraElement<F, N, Q> {
for i in 1..I {
assert!(
indices[i - 1] < indices[i] && indices[i] < N,
"Indices must be sorted and in
range"
);
}
let bit_position = Self::blade_indices_to_bit(&indices);
let mut value = SVector::<F, { 1 << N }>::zeros();
value[bit_position] = <F as One>::one();
CliffordAlgebraElement { value, _marker: PhantomData }
}
fn blade_indices_to_bit(indices: &[usize]) -> usize {
let grade = indices.len();
let mut bit_position = 0;
for g in 0..grade {
bit_position += binomial(N, g);
}
let mut remaining_bits = 0;
for (i, &idx) in indices.iter().enumerate() {
for j in if i == 0 { 0 } else { indices[i - 1] + 1 }..idx {
remaining_bits += binomial(N - j - 1, grade - i - 1);
}
}
bit_position + remaining_bits
}
}
impl<F: Field + Copy, const N: usize, Q: QuadraticFormMarker<F, N>> Default
for CliffordAlgebra<F, N, Q>
where [(); 1 << N]:
{
fn default() -> Self { Self::new() }
}
fn binomial(n: usize, k: usize) -> usize {
if k > n {
return 0;
}
if k == 0 || k == n {
return 1;
}
let k = std::cmp::min(k, n - k);
let mut result = 1;
for i in 1..=k {
result = result * (n - k + i) / i;
}
result
}
#[derive(Clone, Copy, Debug)]
pub struct CliffordAlgebraElement<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>>
where [(); 1 << N]: {
value: SVector<F, { 1 << N }>,
_marker: PhantomData<Q>,
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> PartialEq
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
fn eq(&self, other: &Self) -> bool { self.value == other.value }
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> Add for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
type Output = Self;
fn add(self, other: Self) -> Self::Output {
Self { value: self.value + other.value, _marker: PhantomData }
}
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> AddAssign
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
fn add_assign(&mut self, rhs: Self) { self.value += rhs.value; }
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> Neg for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
type Output = Self;
fn neg(self) -> Self::Output { Self { value: -self.value, _marker: PhantomData } }
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> Sub for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
type Output = Self;
fn sub(self, other: Self) -> Self::Output { self + -other }
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> SubAssign
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
fn sub_assign(&mut self, rhs: Self) { self.value -= rhs.value; }
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> Mul for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
type Output = Self;
fn mul(self, other: Self) -> Self::Output {
let quadratic_coeffs = Q::coefficients();
let mut result = SVector::<F, { 1 << N }>::zeros();
for i in 0..(1 << N) {
if self.value[i].is_zero() {
continue;
}
for j in 0..(1 << N) {
if other.value[j].is_zero() {
continue;
}
let left_indices = bit_to_blade_indices::<N>(i);
let right_indices = bit_to_blade_indices::<N>(j);
let (sign, product_indices) = multiply_blades::<N>(&left_indices, &right_indices);
let mut coefficient = self.value[i] * other.value[j];
coefficient = match sign {
Sign::Positive => coefficient,
Sign::Negative => -coefficient,
};
let mut repeated_indices = Vec::new();
let mut i = 0;
let mut j = 0;
while i < left_indices.len() && j < right_indices.len() {
match left_indices[i].cmp(&right_indices[j]) {
std::cmp::Ordering::Equal => {
repeated_indices.push(left_indices[i]);
i += 1;
j += 1;
},
std::cmp::Ordering::Less => {
i += 1;
},
std::cmp::Ordering::Greater => {
j += 1;
},
}
}
for &idx in &repeated_indices {
coefficient *= quadratic_coeffs[idx];
}
let product_bit = blade_indices_to_bit::<N>(&product_indices);
result[product_bit] += coefficient;
}
}
Self { value: result, _marker: PhantomData }
}
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> MulAssign
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
fn mul_assign(&mut self, rhs: Self) {
let result = *self * rhs;
*self = result;
}
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> Mul<F>
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
type Output = Self;
fn mul(self, rhs: F) -> Self::Output { Self { value: self.value * rhs, _marker: PhantomData } }
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> Multiplicative
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> Zero
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
fn zero() -> Self { Self { value: SVector::<F, { 1 << N }>::zeros(), _marker: PhantomData } }
fn is_zero(&self) -> bool { self.value.iter().all(num_traits::Zero::is_zero) }
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> Additive
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> Group
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
fn identity() -> Self { Self { value: SVector::<F, { 1 << N }>::zeros(), _marker: PhantomData } }
fn inverse(&self) -> Self { Self { value: -self.value, _marker: PhantomData } }
}
impl<F: Field, const N: usize, Q: QuadraticFormMarker<F, N>> AbelianGroup
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
}
impl<F: Field + Mul<Self>, const N: usize, Q: QuadraticFormMarker<F, N>> LeftModule
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
type Ring = F;
}
impl<F: Field + Mul<Self>, const N: usize, Q: QuadraticFormMarker<F, N>> RightModule
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
type Ring = F;
}
impl<F: Field + Mul<Self>, const N: usize, Q: QuadraticFormMarker<F, N>> TwoSidedModule
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
type Ring = F;
}
impl<F: Field + Mul<Self>, const N: usize, Q: QuadraticFormMarker<F, N>> VectorSpace
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
}
impl<F: Field + Mul<Self>, const N: usize, Q: QuadraticFormMarker<F, N>> Algebra
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
}
impl<F: Field + Display, const N: usize, Q: QuadraticFormMarker<F, N>> Display
for CliffordAlgebraElement<F, N, Q>
where [(); 1 << N]:
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut first = true;
let write_basis = |f: &mut Formatter<'_>, indices: &[usize]| -> std::fmt::Result {
if indices.is_empty() {
return Ok(());
}
write!(f, "e")?;
for (i, &index) in indices.iter().enumerate() {
let num = index;
for digit in num.to_string().chars() {
let subscript = match digit {
'0' => "₀",
'1' => "₁",
'2' => "₂",
'3' => "₃",
'4' => "₄",
'5' => "₅",
'6' => "₆",
'7' => "₇",
'8' => "₈",
'9' => "₉",
_ => panic!("Invalid digit"),
};
write!(f, "{subscript}")?;
}
if i < indices.len() - 1 {
write!(f, "‚")?;
}
}
Ok(())
};
for grade in 0..=N {
let mut indices = Vec::with_capacity(grade);
let mut combinations = Vec::new();
generate_combinations(0, N, grade, &mut indices, &mut combinations);
combinations.sort_by_key(|indices| CliffordAlgebra::<F, N, Q>::blade_indices_to_bit(indices));
for indices in combinations {
let bit_position = CliffordAlgebra::<F, N, Q>::blade_indices_to_bit(&indices);
if !self.value[bit_position].is_zero() {
if !first {
write!(f, " + ")?;
}
write!(f, "{}", self.value[bit_position])?;
if grade > 0 {
write_basis(f, &indices)?;
}
first = false;
}
}
}
if first {
write!(f, "0")?;
}
Ok(())
}
}
fn generate_combinations(
start: usize,
n: usize,
k: usize,
current: &mut Vec<usize>,
result: &mut Vec<Vec<usize>>,
) {
if k == 0 {
result.push(current.clone());
return;
}
for i in start..n {
current.push(i);
generate_combinations(i + 1, n, k - 1, current, result);
current.pop();
}
}
#[macro_export]
macro_rules! impl_mul_scalar_clifford {
($($t:ty)*) => ($(
impl<const N: usize, Q: QuadraticFormMarker<$t, N>> Mul<CliffordAlgebraElement<$t, N, Q>> for $t
where [(); 1 << N]:
{
type Output = CliffordAlgebraElement<$t, N, Q>;
fn mul(self, rhs: CliffordAlgebraElement<$t, N, Q>) -> Self::Output { rhs * self }
}
)*)
}
impl_mul_scalar_clifford!(f32);
impl_mul_scalar_clifford!(f64);
#[derive(Debug)]
pub enum Sign {
Positive,
Negative,
}
fn multiply_blades<const N: usize>(left: &[usize], right: &[usize]) -> (Sign, Vec<usize>) {
let mut result_indices = Vec::new();
let mut sign = Sign::Positive;
if left.is_empty() {
return (Sign::Positive, right.to_vec());
}
if right.is_empty() {
return (Sign::Positive, left.to_vec());
}
let mut i = 0;
let mut j = 0;
while i < left.len() && j < right.len() {
match left[i].cmp(&right[j]) {
std::cmp::Ordering::Equal => {
i += 1;
j += 1;
},
std::cmp::Ordering::Less => {
result_indices.push(left[i]);
i += 1;
},
std::cmp::Ordering::Greater => {
result_indices.push(right[j]);
if (left.len() - i) % 2 == 1 {
sign = match sign {
Sign::Positive => Sign::Negative,
Sign::Negative => Sign::Positive,
};
}
j += 1;
},
}
}
result_indices.extend_from_slice(&left[i..]);
result_indices.extend_from_slice(&right[j..]);
(sign, result_indices)
}
fn bit_to_blade_indices<const N: usize>(bits: usize) -> Vec<usize> {
let mut remaining_bits = bits;
let mut grade = 0;
while grade <= N {
let grade_size = binomial(N, grade);
if remaining_bits < grade_size {
break;
}
remaining_bits -= grade_size;
grade += 1;
}
let mut indices = Vec::with_capacity(grade);
let mut current = 0;
for _ in 0..grade {
while current < N {
let remaining_combinations = binomial(N - current - 1, grade - indices.len() - 1);
if remaining_bits < remaining_combinations {
indices.push(current);
current += 1;
break;
}
remaining_bits -= remaining_combinations;
current += 1;
}
}
indices
}
fn blade_indices_to_bit<const N: usize>(indices: &[usize]) -> usize {
let grade = indices.len();
let mut bit_position = 0;
for g in 0..grade {
bit_position += binomial(N, g);
}
let mut remaining_bits = 0;
for (i, &idx) in indices.iter().enumerate() {
for j in if i == 0 { 0 } else { indices[i - 1] + 1 }..idx {
remaining_bits += binomial(N - j - 1, grade - i - 1);
}
}
bit_position + remaining_bits
}
#[cfg(test)]
mod tests {
use super::*;
type NonEuclidean3D = Signature<3, 1, 1, -1>;
fn clifford_algebra_non_euclidean() -> CliffordAlgebra<f64, 3, NonEuclidean3D> {
CliffordAlgebra::new()
}
fn clifford_algebra_euclidean() -> CliffordAlgebra<f64, 3, Euclidean3D> { CliffordAlgebra::new() }
#[test]
fn test_display_order() {
let algebra = clifford_algebra_non_euclidean();
let one =
algebra.element(SVector::<f64, 8>::from_row_slice(&[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]));
let e0 =
algebra.element(SVector::<f64, 8>::from_row_slice(&[0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]));
let e1 =
algebra.element(SVector::<f64, 8>::from_row_slice(&[0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]));
let e2 =
algebra.element(SVector::<f64, 8>::from_row_slice(&[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]));
let e01 =
algebra.element(SVector::<f64, 8>::from_row_slice(&[0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]));
let e02 =
algebra.element(SVector::<f64, 8>::from_row_slice(&[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]));
let e12 =
algebra.element(SVector::<f64, 8>::from_row_slice(&[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]));
let e012 =
algebra.element(SVector::<f64, 8>::from_row_slice(&[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]));
let sum = one + 2.0 * e0 + 3.0 * e1 + 4.0 * e2 + 5.0 * e01 + 6.0 * e02 + 7.0 * e12 + 8.0 * e012;
assert_eq!(format!("{sum}"), "1 + 2e₀ + 3e₁ + 4e₂ + 5e₀‚₁ + 6e₀‚₂ + 7e₁‚₂ + 8e₀‚₁‚₂");
}
#[test]
fn test_blade_indices_to_bit() {
assert_eq!(CliffordAlgebra::<f64, 3, Euclidean3D>::blade_indices_to_bit(&[]), 0);
assert_eq!(CliffordAlgebra::<f64, 3, Euclidean3D>::blade_indices_to_bit(&[0]), 1);
assert_eq!(CliffordAlgebra::<f64, 3, Euclidean3D>::blade_indices_to_bit(&[1]), 2);
assert_eq!(CliffordAlgebra::<f64, 3, Euclidean3D>::blade_indices_to_bit(&[2]), 3);
assert_eq!(CliffordAlgebra::<f64, 3, Euclidean3D>::blade_indices_to_bit(&[0, 1]), 4);
assert_eq!(CliffordAlgebra::<f64, 3, Euclidean3D>::blade_indices_to_bit(&[0, 2]), 5);
assert_eq!(CliffordAlgebra::<f64, 3, Euclidean3D>::blade_indices_to_bit(&[1, 2]), 6);
assert_eq!(CliffordAlgebra::<f64, 3, Euclidean3D>::blade_indices_to_bit(&[0, 1, 2]), 7);
}
#[test]
fn test_blade() {
let algebra = clifford_algebra_non_euclidean();
let e1 = algebra.blade([1]);
assert_eq!(format!("{e1}"), "1e₁");
}
#[test]
fn test_add() {
let algebra = clifford_algebra_non_euclidean();
let e1 = algebra.blade([1]);
let e2 = algebra.blade([2]);
let sum = e1 + e2;
assert_eq!(format!("{sum}"), "1e₁ + 1e₂");
}
#[test]
fn test_mul_basic() {
let algebra = clifford_algebra_non_euclidean();
let e1 = algebra.blade([1]);
let e2 = algebra.blade([2]);
let product = e1 * e2;
assert_eq!(format!("{product}"), "1e₁‚₂");
}
#[test]
fn test_mul_with_quadratic_form() {
let algebra = clifford_algebra_non_euclidean();
let e1 = algebra.blade([1]);
let e01 = algebra.blade([0, 1]);
let product = e1 * e01;
assert_eq!(format!("{product}"), "-1e₀");
}
#[test]
fn test_mul_euclidean() {
let algebra = clifford_algebra_euclidean();
let e1 = algebra.blade([1]);
let e01 = algebra.blade([0, 1]);
let product = e1 * e01;
assert_eq!(format!("{product}"), "-1e₀");
}
#[test]
fn test_mul_anti_commutativity() {
let algebra = clifford_algebra_non_euclidean();
let e0 = algebra.blade([0]);
let e1 = algebra.blade([1]);
let e2 = algebra.blade([2]);
assert_eq!(format!("{}", e0 * e1), "1e₀‚₁");
assert_eq!(format!("{}", e1 * e0), "-1e₀‚₁");
assert_eq!(format!("{}", e1 * e2), "1e₁‚₂");
assert_eq!(format!("{}", e2 * e1), "-1e₁‚₂");
}
#[test]
fn test_mul_scalar() {
let algebra = clifford_algebra_non_euclidean();
let one =
algebra.element(SVector::<f64, 8>::from_row_slice(&[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]));
let e1 = algebra.blade([1]);
let e2 = algebra.blade([2]);
assert_eq!(format!("{}", one * e1), "1e₁");
assert_eq!(format!("{}", e1 * one), "1e₁");
assert_eq!(format!("{}", one * e2), "1e₂");
assert_eq!(format!("{}", e2 * one), "1e₂");
}
#[test]
fn test_mul_quadratic_form_application() {
let algebra = clifford_algebra_non_euclidean();
let e0 = algebra.blade([0]);
let e1 = algebra.blade([1]);
let e2 = algebra.blade([2]);
assert_eq!(format!("{}", e0 * e0), "1"); assert_eq!(format!("{}", e1 * e1), "1"); assert_eq!(format!("{}", e2 * e2), "-1"); }
#[test]
fn test_mul_higher_grade() {
let algebra = clifford_algebra_non_euclidean();
let e01 = algebra.blade([0, 1]);
let e12 = algebra.blade([1, 2]);
let e02 = algebra.blade([0, 2]);
assert_eq!(format!("{}", e01 * e12), "1e₀‚₂");
assert_eq!(format!("{}", e12 * e01), "1e₀‚₂");
assert_eq!(format!("{}", e02 * e12), "1e₀‚₁"); }
#[test]
fn test_mul_trivector() {
let algebra = clifford_algebra_non_euclidean();
let e01 = algebra.blade([0, 1]);
let e2 = algebra.blade([2]);
let e012 = algebra.blade([0, 1, 2]);
assert_eq!(format!("{}", e01 * e2), "1e₀‚₁‚₂");
assert_eq!(format!("{}", e2 * e01), "1e₀‚₁‚₂");
assert_eq!(format!("{}", e012 * e2), "-1e₀‚₁"); }
#[test]
fn test_type_safety() {
let euclidean = CliffordAlgebra::<f64, 3, Euclidean3D>::new();
let non_euclidean = clifford_algebra_non_euclidean();
let e1_euclidean = euclidean.blade([1]);
let _e1_non_euclidean = non_euclidean.blade([1]);
let e2_euclidean = euclidean.blade([2]);
let _sum = e1_euclidean + e2_euclidean; }
}