use core::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Blade {
index: usize,
}
impl Blade {
#[inline]
pub const fn from_index(index: usize) -> Self {
Self { index }
}
#[inline]
pub const fn scalar() -> Self {
Self { index: 0 }
}
#[inline]
pub const fn basis_vector(i: usize) -> Self {
Self { index: 1 << i }
}
#[inline]
pub const fn index(&self) -> usize {
self.index
}
#[inline]
pub const fn grade(&self) -> usize {
self.index.count_ones() as usize
}
#[inline]
pub const fn contains_vector(&self, i: usize) -> bool {
(self.index & (1 << i)) != 0
}
#[inline]
pub fn product<F>(&self, other: &Self, metric: F) -> (i8, Self)
where
F: Fn(usize) -> i8,
{
let result_index = self.index ^ other.index;
let sign = compute_sign(self.index, other.index, metric);
(
sign,
Self {
index: result_index,
},
)
}
#[inline]
pub const fn anticommutes_with(&self, other: &Self) -> bool {
let grade_product = self.grade() * other.grade();
grade_product % 2 == 1
}
}
impl fmt::Debug for Blade {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.index == 0 {
write!(f, "Blade(1)")
} else {
write!(f, "Blade(e")?;
for i in 0..usize::BITS as usize {
if self.contains_vector(i) {
write!(f, "{}", i + 1)?; }
}
write!(f, ")")
}
}
}
impl fmt::Display for Blade {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.index == 0 {
write!(f, "1")
} else {
write!(f, "e")?;
for i in 0..usize::BITS as usize {
if self.contains_vector(i) {
let subscripts = ['₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'];
if i < subscripts.len() {
write!(f, "{}", subscripts[i])?;
} else {
write!(f, "_{}", i + 1)?;
}
}
}
Ok(())
}
}
}
#[inline]
fn compute_sign<F>(a: usize, b: usize, metric: F) -> i8
where
F: Fn(usize) -> i8,
{
let common = a & b;
let mut temp_common = common;
while temp_common != 0 {
let i = temp_common.trailing_zeros() as usize;
if metric(i) == 0 {
return 0;
}
temp_common &= temp_common - 1;
}
let swaps = count_swaps(a, b);
let metric_sign = compute_metric_sign(common, &metric);
if swaps.is_multiple_of(2) {
metric_sign
} else {
-metric_sign
}
}
#[inline]
fn count_swaps(a: usize, b: usize) -> usize {
let mut swaps = 0;
let mut b_remaining = b;
while b_remaining != 0 {
let j = b_remaining.trailing_zeros() as usize;
let mask_above_j = !((1usize << (j + 1)) - 1);
swaps += (a & mask_above_j).count_ones() as usize;
b_remaining &= b_remaining - 1;
}
swaps
}
#[inline]
fn compute_metric_sign<F>(common: usize, metric: F) -> i8
where
F: Fn(usize) -> i8,
{
let mut sign: i8 = 1;
let mut remaining = common;
while remaining != 0 {
let i = remaining.trailing_zeros() as usize;
sign *= metric(i);
remaining &= remaining - 1;
}
sign
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn euclidean_metric(_i: usize) -> i8 {
1
}
#[test]
fn blade_constructors() {
assert_eq!(Blade::scalar().index(), 0);
assert_eq!(Blade::basis_vector(0).index(), 1);
assert_eq!(Blade::basis_vector(1).index(), 2);
assert_eq!(Blade::basis_vector(2).index(), 4);
}
#[test]
fn blade_grade() {
assert_eq!(Blade::scalar().grade(), 0);
assert_eq!(Blade::basis_vector(0).grade(), 1);
assert_eq!(Blade::from_index(0b011).grade(), 2);
assert_eq!(Blade::from_index(0b111).grade(), 3);
}
#[test]
fn blade_contains_vector() {
let e12 = Blade::from_index(0b011);
assert!(e12.contains_vector(0));
assert!(e12.contains_vector(1));
assert!(!e12.contains_vector(2));
}
#[test]
fn blade_display() {
assert_eq!(format!("{}", Blade::scalar()), "1");
assert_eq!(format!("{}", Blade::basis_vector(0)), "e₁");
assert_eq!(format!("{}", Blade::from_index(0b011)), "e₁₂");
assert_eq!(format!("{}", Blade::from_index(0b111)), "e₁₂₃");
}
#[test]
fn blade_debug() {
assert_eq!(format!("{:?}", Blade::scalar()), "Blade(1)");
assert_eq!(format!("{:?}", Blade::basis_vector(0)), "Blade(e1)");
}
#[test]
fn vector_products_blade() {
let e1 = Blade::basis_vector(0);
let e2 = Blade::basis_vector(1);
let (sign, result) = e1.product(&e1, euclidean_metric);
assert_eq!(sign, 1);
assert_eq!(result, Blade::scalar());
let (sign, result) = e1.product(&e2, euclidean_metric);
assert_eq!(sign, 1);
assert_eq!(result, Blade::from_index(0b011));
let (sign, result) = e2.product(&e1, euclidean_metric);
assert_eq!(sign, -1);
assert_eq!(result, Blade::from_index(0b011));
}
#[test]
fn bivector_products_blade() {
let e2 = Blade::basis_vector(1);
let e12 = Blade::from_index(0b011);
let (sign, result) = e12.product(&e12, euclidean_metric);
assert_eq!(sign, -1);
assert_eq!(result, Blade::scalar());
let (sign, result) = e12.product(&e2, euclidean_metric);
assert_eq!(sign, 1);
assert_eq!(result, Blade::basis_vector(0));
}
proptest! {
#[test]
fn result_is_xor(a in 0usize..64, b in 0usize..64) {
let blade_a = Blade::from_index(a);
let blade_b = Blade::from_index(b);
let (_, result) = blade_a.product(&blade_b, euclidean_metric);
prop_assert_eq!(result.index(), a ^ b);
}
#[test]
fn scalar_identity(a in 0usize..64) {
let one = Blade::scalar();
let blade = Blade::from_index(a);
let (sign, result) = one.product(&blade, euclidean_metric);
prop_assert_eq!(sign, 1);
prop_assert_eq!(result, blade);
let (sign, result) = blade.product(&one, euclidean_metric);
prop_assert_eq!(sign, 1);
prop_assert_eq!(result, blade);
}
#[test]
fn sign_associativity(a in 0usize..16, b in 0usize..16, c in 0usize..16) {
let blade_a = Blade::from_index(a);
let blade_b = Blade::from_index(b);
let blade_c = Blade::from_index(c);
let (s_bc, bc) = blade_b.product(&blade_c, euclidean_metric);
let (s_a_bc, _) = blade_a.product(&bc, euclidean_metric);
let (s_ab, ab) = blade_a.product(&blade_b, euclidean_metric);
let (s_ab_c, _) = ab.product(&blade_c, euclidean_metric);
prop_assert_eq!(
(s_a_bc as i32) * (s_bc as i32),
(s_ab_c as i32) * (s_ab as i32)
);
}
#[test]
fn grade_is_popcount(index in 0usize..256) {
let blade = Blade::from_index(index);
prop_assert_eq!(blade.grade(), index.count_ones() as usize);
}
}
#[test]
fn minkowski_metric() {
let minkowski = |i: usize| if i == 0 { 1 } else { -1 };
let e0 = Blade::basis_vector(0);
let e1 = Blade::basis_vector(1);
let (sign, result) = e0.product(&e0, minkowski);
assert_eq!(sign, 1);
assert_eq!(result, Blade::scalar());
let (sign, result) = e1.product(&e1, minkowski);
assert_eq!(sign, -1);
assert_eq!(result, Blade::scalar());
}
#[test]
fn null_metric() {
let pga_metric = |i: usize| if i == 0 { 0 } else { 1 };
let e0 = Blade::basis_vector(0);
let (sign, _) = e0.product(&e0, pga_metric);
assert_eq!(sign, 0);
}
}