use approx::{AbsDiffEq, RelativeEq, UlpsEq};
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Add, AddAssign, BitXor, Div, Mul, MulAssign, Neg, Sub, SubAssign};
use generic_array::{GenericArray, sequence::GenericSequence};
use typenum::Unsigned;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::basis::Blade;
use crate::scalar::Float;
use crate::signature::Signature;
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Multivector<T: Float, S: Signature> {
coeffs: GenericArray<T, S::NumBlades>,
_signature: PhantomData<S>,
}
impl<T: Float, S: Signature> Copy for Multivector<T, S> where GenericArray<T, S::NumBlades>: Copy {}
impl<T: Float, S: Signature> Multivector<T, S> {
#[inline]
pub fn zero() -> Self {
Self {
coeffs: GenericArray::generate(|_| T::zero()),
_signature: PhantomData,
}
}
#[inline]
pub fn one() -> Self {
Self::scalar(T::one())
}
#[inline]
pub fn scalar(value: T) -> Self {
let mut mv = Self::zero();
mv.coeffs[0] = value;
mv
}
#[inline]
pub fn from_blade(blade: Blade) -> Self {
let mut mv = Self::zero();
mv.coeffs[blade.index()] = T::one();
mv
}
#[inline]
pub fn basis_vector(i: usize) -> Self {
assert!(
i < S::DIM,
"basis vector index {i} out of range for dimension {}",
S::DIM
);
Self::from_blade(Blade::basis_vector(i))
}
#[inline]
pub fn vector(components: &[T]) -> Self {
assert!(
components.len() <= S::DIM,
"too many components ({}) for dimension {}",
components.len(),
S::DIM
);
let mut mv = Self::zero();
for (i, &c) in components.iter().enumerate() {
mv.coeffs[1 << i] = c;
}
mv
}
#[inline]
pub fn from_coeffs(coeffs: &[T]) -> Self {
let mut mv = Self::zero();
let n = coeffs.len().min(S::NumBlades::USIZE);
mv.coeffs[..n].copy_from_slice(&coeffs[..n]);
mv
}
}
impl<T: Float, S: Signature> Multivector<T, S> {
#[inline]
pub fn get(&self, blade: Blade) -> T {
self.coeffs[blade.index()]
}
#[inline]
pub fn set(&mut self, blade: Blade, value: T) {
self.coeffs[blade.index()] = value;
}
#[inline]
pub fn scalar_part(&self) -> T {
self.coeffs[0]
}
#[inline]
pub fn is_zero(&self, epsilon: T) -> bool {
self.coeffs.iter().all(|&c| c.abs() < epsilon)
}
}
impl<T: Float, S: Signature> Multivector<T, S> {
pub fn reverse(&self) -> Self {
let mut result = Self::zero();
for i in 0..S::NumBlades::USIZE {
let grade = Blade::from_index(i).grade();
let sign = if grade < 2 || (grade / 2).is_multiple_of(2) {
T::one()
} else {
-T::one()
};
result.coeffs[i] = sign * self.coeffs[i];
}
result
}
pub fn involute(&self) -> Self {
let mut result = Self::zero();
for i in 0..S::NumBlades::USIZE {
let grade = Blade::from_index(i).grade();
let sign = if grade.is_multiple_of(2) {
T::one()
} else {
-T::one()
};
result.coeffs[i] = sign * self.coeffs[i];
}
result
}
pub fn conjugate(&self) -> Self {
let mut result = Self::zero();
for i in 0..S::NumBlades::USIZE {
let grade = Blade::from_index(i).grade();
let sign = if (grade * (grade + 1) / 2).is_multiple_of(2) {
T::one()
} else {
-T::one()
};
result.coeffs[i] = sign * self.coeffs[i];
}
result
}
}
impl<T: Float, S: Signature> Multivector<T, S> {
pub fn norm_squared(&self) -> T {
(self * &self.reverse()).scalar_part()
}
pub fn norm(&self) -> T {
self.norm_squared().abs().sqrt()
}
pub fn normalize(&self) -> Option<Self> {
let n = self.norm();
if n < T::epsilon() {
None
} else {
Some(self / n)
}
}
pub fn inverse(&self) -> Option<Self> {
let norm_sq = self.norm_squared();
if norm_sq.abs() < T::epsilon() {
None
} else {
Some(&self.reverse() / norm_sq)
}
}
}
impl<T: Float, S: Signature> Multivector<T, S> {
pub fn grade_select(&self, k: usize) -> Self {
let mut result = Self::zero();
for i in 0..S::NumBlades::USIZE {
if Blade::from_index(i).grade() == k {
result.coeffs[i] = self.coeffs[i];
}
}
result
}
pub fn even(&self) -> Self {
let mut result = Self::zero();
for i in 0..S::NumBlades::USIZE {
if Blade::from_index(i).grade().is_multiple_of(2) {
result.coeffs[i] = self.coeffs[i];
}
}
result
}
pub fn odd(&self) -> Self {
let mut result = Self::zero();
for i in 0..S::NumBlades::USIZE {
if !Blade::from_index(i).grade().is_multiple_of(2) {
result.coeffs[i] = self.coeffs[i];
}
}
result
}
pub fn grade(&self, epsilon: T) -> Option<usize> {
let mut found_grade = None;
for i in 0..S::NumBlades::USIZE {
if self.coeffs[i].abs() > epsilon {
let g = Blade::from_index(i).grade();
match found_grade {
None => found_grade = Some(g),
Some(prev) if prev != g => return None,
_ => {}
}
}
}
found_grade
}
pub fn pseudoscalar() -> Self {
let ps_index = (1 << S::DIM) - 1;
Self::from_blade(Blade::from_index(ps_index))
}
}
impl<T: Float, S: Signature> Multivector<T, S> {
pub fn exterior(&self, other: &Self) -> Self {
let mut result = Self::zero();
for i in 0..S::NumBlades::USIZE {
if self.coeffs[i] == T::zero() {
continue;
}
let blade_i = Blade::from_index(i);
let grade_i = blade_i.grade();
for j in 0..S::NumBlades::USIZE {
if other.coeffs[j] == T::zero() {
continue;
}
let blade_j = Blade::from_index(j);
let grade_j = blade_j.grade();
let (sign, result_blade) = blade_i.product(&blade_j, S::metric);
if sign != 0 && result_blade.grade() == grade_i + grade_j {
let coeff = T::from_i8(sign) * self.coeffs[i] * other.coeffs[j];
result.coeffs[result_blade.index()] += coeff;
}
}
}
result
}
#[deprecated(since = "0.2.0", note = "use `exterior` instead")]
#[inline]
pub fn outer(&self, other: &Self) -> Self {
self.exterior(other)
}
pub fn left_contract(&self, other: &Self) -> Self {
let mut result = Self::zero();
for i in 0..S::NumBlades::USIZE {
if self.coeffs[i] == T::zero() {
continue;
}
let blade_i = Blade::from_index(i);
let grade_i = blade_i.grade();
for j in 0..S::NumBlades::USIZE {
if other.coeffs[j] == T::zero() {
continue;
}
let blade_j = Blade::from_index(j);
let grade_j = blade_j.grade();
if grade_i > grade_j {
continue;
}
let (sign, result_blade) = blade_i.product(&blade_j, S::metric);
if sign != 0 && result_blade.grade() == grade_j - grade_i {
let coeff = T::from_i8(sign) * self.coeffs[i] * other.coeffs[j];
result.coeffs[result_blade.index()] += coeff;
}
}
}
result
}
pub fn right_contract(&self, other: &Self) -> Self {
let mut result = Self::zero();
for i in 0..S::NumBlades::USIZE {
if self.coeffs[i] == T::zero() {
continue;
}
let blade_i = Blade::from_index(i);
let grade_i = blade_i.grade();
for j in 0..S::NumBlades::USIZE {
if other.coeffs[j] == T::zero() {
continue;
}
let blade_j = Blade::from_index(j);
let grade_j = blade_j.grade();
if grade_j > grade_i {
continue;
}
let (sign, result_blade) = blade_i.product(&blade_j, S::metric);
if sign != 0 && result_blade.grade() == grade_i - grade_j {
let coeff = T::from_i8(sign) * self.coeffs[i] * other.coeffs[j];
result.coeffs[result_blade.index()] += coeff;
}
}
}
result
}
pub fn inner(&self, other: &Self) -> Self {
self.left_contract(other)
}
pub fn dual(&self) -> Self {
let ps = Self::pseudoscalar();
let ps_inv = ps.inverse().expect("pseudoscalar should be invertible");
self.left_contract(&ps_inv)
}
pub fn undual(&self) -> Self {
let ps = Self::pseudoscalar();
self.left_contract(&ps)
}
pub fn complement(&self) -> Self {
let ps_index = S::NumBlades::USIZE - 1;
let mut result = Self::zero();
for i in 0..S::NumBlades::USIZE {
if self.coeffs[i] == T::zero() {
continue;
}
let complement_index = ps_index ^ i;
let sign = exterior_sign(i, complement_index);
result.coeffs[complement_index] += T::from_i8(sign) * self.coeffs[i];
}
result
}
#[deprecated(since = "0.3.0", note = "use `complement` instead")]
pub fn right_complement(&self) -> Self {
self.complement()
}
#[deprecated(since = "0.3.0", note = "use `complement` instead")]
pub fn left_complement(&self) -> Self {
self.complement()
}
pub fn regressive(&self, other: &Self) -> Self {
self.complement().exterior(&other.complement()).complement()
}
#[inline]
pub fn antiwedge(&self, other: &Self) -> Self {
self.regressive(other)
}
pub fn bulk_dual(&self) -> Self {
let ps = Self::pseudoscalar();
&self.reverse() * &ps
}
pub fn left_bulk_dual(&self) -> Self {
let ps = Self::pseudoscalar();
&ps * &self.reverse()
}
pub fn weight_dual(&self) -> Self {
let scalar = Self::scalar(T::one());
self.reverse().antiproduct(&scalar)
}
pub fn bulk_contraction(&self, other: &Self) -> Self {
self.antiwedge(&other.bulk_dual())
}
pub fn weight_contraction(&self, other: &Self) -> Self {
self.antiwedge(&other.weight_dual())
}
pub fn bulk_expansion(&self, other: &Self) -> Self {
self.exterior(&other.bulk_dual())
}
pub fn weight_expansion(&self, other: &Self) -> Self {
self.exterior(&other.weight_dual())
}
pub fn antiproduct(&self, other: &Self) -> Self {
(&self.complement() * &other.complement()).complement()
}
pub fn antisandwich(&self, x: &Self) -> Self {
self.antiproduct(x).antiproduct(&self.reverse())
}
pub fn sandwich(&self, x: &Self) -> Self {
&(self * x) * &self.reverse()
}
pub fn project(&self, other: &Self) -> Self {
other.antiwedge(&self.exterior(&other.weight_dual()))
}
pub fn antiproject(&self, other: &Self) -> Self {
other.exterior(&self.antiwedge(&other.weight_dual()))
}
}
impl<T: Float, S: Signature> Mul for &Multivector<T, S> {
type Output = Multivector<T, S>;
fn mul(self, rhs: Self) -> Self::Output {
let mut result = Multivector::zero();
for i in 0..S::NumBlades::USIZE {
if self.coeffs[i] == T::zero() {
continue;
}
for j in 0..S::NumBlades::USIZE {
if rhs.coeffs[j] == T::zero() {
continue;
}
let blade_i = Blade::from_index(i);
let blade_j = Blade::from_index(j);
let (sign, result_blade) = blade_i.product(&blade_j, S::metric);
if sign != 0 {
let coeff = T::from_i8(sign) * self.coeffs[i] * rhs.coeffs[j];
result.coeffs[result_blade.index()] += coeff;
}
}
}
result
}
}
impl<T: Float, S: Signature> Mul for Multivector<T, S> {
type Output = Multivector<T, S>;
fn mul(self, rhs: Self) -> Self::Output {
&self * &rhs
}
}
impl<T: Float, S: Signature> Mul<&Multivector<T, S>> for Multivector<T, S> {
type Output = Multivector<T, S>;
fn mul(self, rhs: &Self) -> Self::Output {
&self * rhs
}
}
impl<T: Float, S: Signature> Mul<Multivector<T, S>> for &Multivector<T, S> {
type Output = Multivector<T, S>;
fn mul(self, rhs: Multivector<T, S>) -> Self::Output {
self * &rhs
}
}
impl<T: Float, S: Signature> Mul<T> for &Multivector<T, S> {
type Output = Multivector<T, S>;
fn mul(self, scalar: T) -> Self::Output {
let mut result = Multivector::zero();
for i in 0..S::NumBlades::USIZE {
result.coeffs[i] = self.coeffs[i] * scalar;
}
result
}
}
impl<T: Float, S: Signature> Mul<T> for Multivector<T, S> {
type Output = Multivector<T, S>;
fn mul(self, scalar: T) -> Self::Output {
&self * scalar
}
}
impl<T: Float, S: Signature> Div<T> for &Multivector<T, S> {
type Output = Multivector<T, S>;
fn div(self, scalar: T) -> Self::Output {
let mut result = Multivector::zero();
for i in 0..S::NumBlades::USIZE {
result.coeffs[i] = self.coeffs[i] / scalar;
}
result
}
}
impl<T: Float, S: Signature> Div<T> for Multivector<T, S> {
type Output = Multivector<T, S>;
fn div(self, scalar: T) -> Self::Output {
&self / scalar
}
}
impl<T: Float, S: Signature> BitXor for &Multivector<T, S> {
type Output = Multivector<T, S>;
fn bitxor(self, rhs: Self) -> Self::Output {
self.exterior(rhs)
}
}
impl<T: Float, S: Signature> BitXor for Multivector<T, S> {
type Output = Multivector<T, S>;
fn bitxor(self, rhs: Self) -> Self::Output {
self.exterior(&rhs)
}
}
impl<T: Float, S: Signature> BitXor<&Multivector<T, S>> for Multivector<T, S> {
type Output = Multivector<T, S>;
fn bitxor(self, rhs: &Multivector<T, S>) -> Self::Output {
self.exterior(rhs)
}
}
impl<T: Float, S: Signature> BitXor<Multivector<T, S>> for &Multivector<T, S> {
type Output = Multivector<T, S>;
fn bitxor(self, rhs: Multivector<T, S>) -> Self::Output {
self.exterior(&rhs)
}
}
impl<T: Float, S: Signature> Add for &Multivector<T, S> {
type Output = Multivector<T, S>;
fn add(self, rhs: Self) -> Self::Output {
let mut result = Multivector::zero();
for i in 0..S::NumBlades::USIZE {
result.coeffs[i] = self.coeffs[i] + rhs.coeffs[i];
}
result
}
}
impl<T: Float, S: Signature> Add for Multivector<T, S> {
type Output = Multivector<T, S>;
fn add(self, rhs: Self) -> Self::Output {
&self + &rhs
}
}
impl<T: Float, S: Signature> Add<&Multivector<T, S>> for Multivector<T, S> {
type Output = Multivector<T, S>;
fn add(self, rhs: &Self) -> Self::Output {
&self + rhs
}
}
impl<T: Float, S: Signature> Add<Multivector<T, S>> for &Multivector<T, S> {
type Output = Multivector<T, S>;
fn add(self, rhs: Multivector<T, S>) -> Self::Output {
self + &rhs
}
}
impl<T: Float, S: Signature> Sub for &Multivector<T, S> {
type Output = Multivector<T, S>;
fn sub(self, rhs: Self) -> Self::Output {
let mut result = Multivector::zero();
for i in 0..S::NumBlades::USIZE {
result.coeffs[i] = self.coeffs[i] - rhs.coeffs[i];
}
result
}
}
impl<T: Float, S: Signature> Sub for Multivector<T, S> {
type Output = Multivector<T, S>;
fn sub(self, rhs: Self) -> Self::Output {
&self - &rhs
}
}
impl<T: Float, S: Signature> Sub<&Multivector<T, S>> for Multivector<T, S> {
type Output = Multivector<T, S>;
fn sub(self, rhs: &Self) -> Self::Output {
&self - rhs
}
}
impl<T: Float, S: Signature> Sub<Multivector<T, S>> for &Multivector<T, S> {
type Output = Multivector<T, S>;
fn sub(self, rhs: Multivector<T, S>) -> Self::Output {
self - &rhs
}
}
impl<T: Float, S: Signature> Neg for &Multivector<T, S> {
type Output = Multivector<T, S>;
fn neg(self) -> Self::Output {
let mut result = Multivector::zero();
for i in 0..S::NumBlades::USIZE {
result.coeffs[i] = -self.coeffs[i];
}
result
}
}
impl<T: Float, S: Signature> Neg for Multivector<T, S> {
type Output = Multivector<T, S>;
fn neg(self) -> Self::Output {
-&self
}
}
impl<T: Float, S: Signature> AddAssign<&Multivector<T, S>> for Multivector<T, S> {
fn add_assign(&mut self, rhs: &Self) {
for i in 0..S::NumBlades::USIZE {
self.coeffs[i] += rhs.coeffs[i];
}
}
}
impl<T: Float, S: Signature> AddAssign for Multivector<T, S> {
fn add_assign(&mut self, rhs: Self) {
*self += &rhs;
}
}
impl<T: Float, S: Signature> SubAssign<&Multivector<T, S>> for Multivector<T, S> {
fn sub_assign(&mut self, rhs: &Self) {
for i in 0..S::NumBlades::USIZE {
self.coeffs[i] -= rhs.coeffs[i];
}
}
}
impl<T: Float, S: Signature> SubAssign for Multivector<T, S> {
fn sub_assign(&mut self, rhs: Self) {
*self -= &rhs;
}
}
impl<T: Float, S: Signature> MulAssign<T> for Multivector<T, S> {
fn mul_assign(&mut self, scalar: T) {
for i in 0..S::NumBlades::USIZE {
self.coeffs[i] *= scalar;
}
}
}
impl<T: Float, S: Signature> Default for Multivector<T, S> {
fn default() -> Self {
Self::zero()
}
}
impl<T: Float, S: Signature> PartialEq for Multivector<T, S> {
fn eq(&self, other: &Self) -> bool {
self.coeffs == other.coeffs
}
}
impl<T: Float, S: Signature> AbsDiffEq for Multivector<T, S> {
type Epsilon = T;
fn default_epsilon() -> Self::Epsilon {
T::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.coeffs
.iter()
.zip(other.coeffs.iter())
.all(|(a, b)| T::abs_diff_eq(a, b, epsilon))
}
}
impl<T: Float, S: Signature> RelativeEq for Multivector<T, S> {
fn default_max_relative() -> Self::Epsilon {
T::default_max_relative()
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.coeffs
.iter()
.zip(other.coeffs.iter())
.all(|(a, b)| T::relative_eq(a, b, epsilon, max_relative))
}
}
impl<T: Float, S: Signature> UlpsEq for Multivector<T, S> {
fn default_max_ulps() -> u32 {
T::default_max_ulps()
}
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.coeffs
.iter()
.zip(other.coeffs.iter())
.all(|(a, b)| T::ulps_eq(a, b, epsilon, max_ulps))
}
}
impl<T: Float, S: Signature> fmt::Debug for Multivector<T, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Multivector(")?;
let mut first = true;
for i in 0..S::NumBlades::USIZE {
if self.coeffs[i] != T::zero() {
if !first {
write!(f, " + ")?;
}
write!(f, "{:?}*{}", self.coeffs[i], Blade::from_index(i))?;
first = false;
}
}
if first {
write!(f, "0")?;
}
write!(f, ")")
}
}
impl<T: Float, S: Signature> fmt::Display for Multivector<T, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for i in 0..S::NumBlades::USIZE {
if self.coeffs[i] != T::zero() {
if !first {
write!(f, " + ")?;
}
let blade = Blade::from_index(i);
if i == 0 {
write!(f, "{}", self.coeffs[i])?;
} else {
write!(f, "{}{}", self.coeffs[i], blade)?;
}
first = false;
}
}
if first {
write!(f, "0")?;
}
Ok(())
}
}
#[inline]
fn exterior_sign(a: usize, b: usize) -> i8 {
debug_assert_eq!(a & b, 0, "blades must not overlap for exterior sign");
let mut transpositions = 0;
let mut b_remaining = b;
while b_remaining != 0 {
let lowest_b = b_remaining & b_remaining.wrapping_neg();
let b_pos = lowest_b.trailing_zeros();
let a_above = a >> (b_pos + 1);
transpositions += a_above.count_ones();
b_remaining &= !lowest_b;
}
if transpositions % 2 == 0 { 1 } else { -1 }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algebra::arbitrary::{NonZeroVectorE3, UnitVectorE3, VectorE3};
use crate::signature::{Euclidean2, Euclidean3};
use crate::test_utils::RELATIVE_EQ_EPS;
use approx::relative_eq;
use proptest::prelude::*;
#[test]
fn test_zero() {
let zero: Multivector<f64, Euclidean3> = Multivector::zero();
assert!(zero.is_zero(RELATIVE_EQ_EPS));
}
#[test]
fn test_one() {
let one: Multivector<f64, Euclidean3> = Multivector::one();
assert!(relative_eq!(
one.scalar_part(),
1.0,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn test_basis_vector() {
let e1: Multivector<f64, Euclidean3> = Multivector::basis_vector(0);
assert!(relative_eq!(
e1.get(Blade::basis_vector(0)),
1.0,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
e1.get(Blade::basis_vector(1)),
0.0,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn test_vector() {
let v: Multivector<f64, Euclidean3> = Multivector::vector(&[3.0, 4.0, 0.0]);
assert!(relative_eq!(
v.get(Blade::basis_vector(0)),
3.0,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
v.get(Blade::basis_vector(1)),
4.0,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn test_vector_squares_to_scalar() {
let e1: Multivector<f64, Euclidean3> = Multivector::basis_vector(0);
let e1_sq = e1 * e1;
assert!(relative_eq!(
e1_sq.scalar_part(),
1.0,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
e1_sq,
Multivector::one(),
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn test_bivector_squares_to_minus_one() {
let e1: Multivector<f64, Euclidean3> = Multivector::basis_vector(0);
let e2: Multivector<f64, Euclidean3> = Multivector::basis_vector(1);
let e12 = e1 * e2;
let e12_sq = e12 * e12;
assert!(relative_eq!(
e12_sq.scalar_part(),
-1.0,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn test_scalar_is_identity() {
let one: Multivector<f64, Euclidean3> = Multivector::one();
let v: Multivector<f64, Euclidean3> = Multivector::vector(&[1.0, 2.0, 3.0]);
assert!(relative_eq!(
one * v,
v,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
v * one,
v,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn test_reverse_vector() {
let v: Multivector<f64, Euclidean3> = Multivector::basis_vector(0);
assert!(relative_eq!(
v.reverse(),
v,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
)); }
#[test]
fn test_reverse_bivector() {
let e1: Multivector<f64, Euclidean3> = Multivector::basis_vector(0);
let e2: Multivector<f64, Euclidean3> = Multivector::basis_vector(1);
let e12 = e1 * e2;
assert!(relative_eq!(
e12.reverse(),
-e12,
max_relative = RELATIVE_EQ_EPS
)); }
#[test]
fn test_involute_vector() {
let v: Multivector<f64, Euclidean3> = Multivector::basis_vector(0);
assert!(relative_eq!(
v.involute(),
-v,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
)); }
#[test]
fn test_norm_squared_vector() {
let v: Multivector<f64, Euclidean3> = Multivector::vector(&[3.0, 4.0, 0.0]);
assert!(relative_eq!(
v.norm_squared(),
25.0,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn test_norm_vector() {
let v: Multivector<f64, Euclidean3> = Multivector::vector(&[3.0, 4.0, 0.0]);
assert!(relative_eq!(
v.norm(),
5.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn test_inverse_vector() {
let v: Multivector<f64, Euclidean3> = Multivector::vector(&[2.0, 0.0, 0.0]);
let v_inv = v.inverse().unwrap();
let product = v * v_inv;
assert!(relative_eq!(
product,
Multivector::one(),
max_relative = RELATIVE_EQ_EPS
));
}
proptest! {
#[test]
fn geometric_product_associative(
a in any::<Multivector<f64, Euclidean3>>(),
b in any::<Multivector<f64, Euclidean3>>(),
c in any::<Multivector<f64, Euclidean3>>(),
) {
let lhs = (a * b) * c;
let rhs = a * (b * c);
prop_assert!(relative_eq!(lhs, rhs, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn geometric_product_distributive(
a in any::<Multivector<f64, Euclidean3>>(),
b in any::<Multivector<f64, Euclidean3>>(),
c in any::<Multivector<f64, Euclidean3>>(),
) {
let lhs = a * (b + c);
let rhs = (a * b) + (a * c);
prop_assert!(relative_eq!(lhs, rhs, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn reverse_involutory(a in any::<Multivector<f64, Euclidean3>>()) {
prop_assert!(relative_eq!(a.reverse().reverse(), a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn reverse_antimorphism(
a in any::<Multivector<f64, Euclidean3>>(),
b in any::<Multivector<f64, Euclidean3>>(),
) {
let lhs = (a * b).reverse();
let rhs = b.reverse() * a.reverse();
prop_assert!(relative_eq!(lhs, rhs, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn involute_involutory(a in any::<Multivector<f64, Euclidean3>>()) {
prop_assert!(relative_eq!(a.involute().involute(), a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn inverse_property(a in any::<NonZeroVectorE3>()) {
let inv = a.inverse().expect("non-zero vector should be invertible");
let product = *a * inv;
prop_assert!(relative_eq!(product, Multivector::one(), epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn add_commutative(
a in any::<Multivector<f64, Euclidean3>>(),
b in any::<Multivector<f64, Euclidean3>>()
) {
prop_assert!(relative_eq!(a + b, b + a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn add_associative(
a in any::<Multivector<f64, Euclidean3>>(),
b in any::<Multivector<f64, Euclidean3>>(),
c in any::<Multivector<f64, Euclidean3>>(),
) {
let lhs = (a + b) + c;
let rhs = a + (b + c);
prop_assert!(relative_eq!(lhs, rhs, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn zero_is_additive_identity(a in any::<Multivector<f64, Euclidean3>>()) {
let zero = Multivector::<f64, Euclidean3>::zero();
prop_assert!(relative_eq!(a + zero, a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn one_is_multiplicative_identity(a in any::<Multivector<f64, Euclidean3>>()) {
let one = Multivector::<f64, Euclidean3>::one();
prop_assert!(relative_eq!(a * one, a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
prop_assert!(relative_eq!(one * a, a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn grade_decomposition_complete(a in any::<Multivector<f64, Euclidean3>>()) {
let sum = (0..=3)
.map(|k| a.grade_select(k))
.fold(Multivector::<f64, Euclidean3>::zero(), |acc, x| acc + x);
prop_assert!(relative_eq!(sum, a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn even_plus_odd_equals_original(a in any::<Multivector<f64, Euclidean3>>()) {
let reconstructed = a.even() + a.odd();
prop_assert!(relative_eq!(reconstructed, a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn grade_select_idempotent(
a in any::<Multivector<f64, Euclidean3>>(),
k in 0usize..4
) {
let once = a.grade_select(k);
let twice = once.grade_select(k);
prop_assert!(relative_eq!(once, twice, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn exterior_anticommutative_vectors(
a in any::<VectorE3>(),
b in any::<VectorE3>(),
) {
let ab = a.exterior(&*b);
let ba = b.exterior(&*a);
prop_assert!(relative_eq!(ab, -&ba, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn outer_associative(
a in any::<Multivector<f64, Euclidean3>>(),
b in any::<Multivector<f64, Euclidean3>>(),
c in any::<Multivector<f64, Euclidean3>>(),
) {
let lhs = a.exterior(&b).exterior(&c);
let rhs = a.exterior(&b.exterior(&c));
prop_assert!(relative_eq!(lhs, rhs, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn outer_of_vectors_is_grade_2(
a in any::<VectorE3>(),
b in any::<VectorE3>(),
) {
let wedge = a.exterior(&*b);
prop_assert!(wedge.grade_select(0).is_zero(RELATIVE_EQ_EPS));
prop_assert!(wedge.grade_select(1).is_zero(RELATIVE_EQ_EPS));
}
#[test]
fn inner_product_of_vectors_is_scalar(
a in any::<VectorE3>(),
b in any::<VectorE3>(),
) {
let dot = a.inner(&*b);
prop_assert!(dot.grade_select(1).is_zero(RELATIVE_EQ_EPS));
prop_assert!(dot.grade_select(2).is_zero(RELATIVE_EQ_EPS));
prop_assert!(dot.grade_select(3).is_zero(RELATIVE_EQ_EPS));
}
#[test]
fn inner_product_symmetric_for_vectors(
a in any::<VectorE3>(),
b in any::<VectorE3>(),
) {
let ab = a.inner(&*b);
let ba = b.inner(&*a);
prop_assert!(relative_eq!(ab, ba, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn dual_undual_roundtrip(a in any::<Multivector<f64, Euclidean3>>()) {
let roundtrip = a.dual().undual();
prop_assert!(relative_eq!(roundtrip, a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn dual_changes_grade(a in any::<NonZeroVectorE3>()) {
let dual = a.dual();
prop_assert_eq!(dual.grade(RELATIVE_EQ_EPS), Some(2));
}
#[test]
fn sandwich_by_unit_vector_preserves_norm(
n in any::<UnitVectorE3>(),
v in any::<NonZeroVectorE3>(),
) {
let reflected = n.sandwich(&*v);
prop_assert!(relative_eq!(reflected.norm(), v.norm(), epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn sandwich_by_unit_vector_is_reflection(
n in any::<UnitVectorE3>(),
v in any::<VectorE3>(),
) {
let reflected = n.sandwich(&*v);
let double_reflected = n.sandwich(&reflected);
prop_assert!(relative_eq!(double_reflected, *v, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn de_morgan_complement_of_geometric_product(
a in any::<Multivector<f64, Euclidean3>>(),
b in any::<Multivector<f64, Euclidean3>>(),
) {
let lhs = (a * b).complement();
let rhs = a.complement().antiproduct(&b.complement());
prop_assert!(relative_eq!(lhs, rhs, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn de_morgan_complement_of_antiproduct(
a in any::<Multivector<f64, Euclidean3>>(),
b in any::<Multivector<f64, Euclidean3>>(),
) {
let lhs = a.antiproduct(&b).complement();
let rhs = a.complement() * b.complement();
prop_assert!(relative_eq!(lhs, rhs, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn complement_involutory(
a in any::<Multivector<f64, Euclidean3>>(),
) {
let double_comp = a.complement().complement();
let lhs_norm_sq = (double_comp * double_comp.reverse()).scalar_part();
let rhs_norm_sq = (a * a.reverse()).scalar_part();
prop_assert!(relative_eq!(lhs_norm_sq, rhs_norm_sq, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
}
#[test]
fn test_2d_complex_number_behavior() {
let e1: Multivector<f64, Euclidean2> = Multivector::basis_vector(0);
let e2: Multivector<f64, Euclidean2> = Multivector::basis_vector(1);
let i = e1 * e2;
let i_sq = i * i;
assert!(relative_eq!(
i_sq.scalar_part(),
-1.0,
max_relative = RELATIVE_EQ_EPS
));
let z = Multivector::scalar(3.0) + i * 4.0; let norm_sq = (z * z.reverse()).scalar_part();
assert!(relative_eq!(
norm_sq,
25.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
)); }
#[test]
fn test_copy_semantics() {
let v: Multivector<f64, Euclidean3> = Multivector::vector(&[1.0, 2.0, 3.0]);
let v2 = v;
assert!(relative_eq!(
v,
v2,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
let sum = v + v2;
assert!(relative_eq!(
sum.get(Blade::basis_vector(0)),
2.0,
max_relative = RELATIVE_EQ_EPS
));
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_roundtrip() {
let v: Multivector<f64, Euclidean3> = Multivector::vector(&[1.0, 2.0, 3.0]);
let json = serde_json::to_string(&v).expect("serialization failed");
let v2: Multivector<f64, Euclidean3> =
serde_json::from_str(&json).expect("deserialization failed");
assert!(relative_eq!(
v,
v2,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
}