use super::Signature;
use typenum::{U8, U16};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Projective2;
impl Signature for Projective2 {
type NumBlades = U8;
const P: usize = 2;
const Q: usize = 0;
const R: usize = 1;
#[inline]
fn metric(i: usize) -> i8 {
match i {
0 | 1 => 1, 2 => 0, _ => panic!("basis index {i} out of range for Projective2 (DIM=3)"),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Projective3;
impl Signature for Projective3 {
type NumBlades = U16;
const P: usize = 3;
const Q: usize = 0;
const R: usize = 1;
#[inline]
fn metric(i: usize) -> i8 {
match i {
0..=2 => 1, 3 => 0, _ => panic!("basis index {i} out of range for Projective3 (DIM=4)"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algebra::Multivector;
use crate::basis::Blade;
use crate::test_utils::RELATIVE_EQ_EPS;
use approx::relative_eq;
use proptest::prelude::*;
proptest! {
#[test]
fn projective2_euclidean_metric_positive(i in 0usize..2) {
prop_assert_eq!(Projective2::metric(i), 1);
}
#[test]
fn projective3_euclidean_metric_positive(i in 0usize..3) {
prop_assert_eq!(Projective3::metric(i), 1);
}
}
#[test]
fn projective2_null_metric() {
assert_eq!(Projective2::metric(2), 0);
}
#[test]
fn projective3_null_metric() {
assert_eq!(Projective3::metric(3), 0);
}
#[test]
fn projective_dimensions() {
assert_eq!(Projective2::DIM, 3);
assert_eq!(Projective2::num_blades(), 8);
assert_eq!(Projective3::DIM, 4);
assert_eq!(Projective3::num_blades(), 16);
}
#[test]
fn projective_pqr_consistency() {
assert_eq!(
Projective2::P + Projective2::Q + Projective2::R,
Projective2::DIM
);
assert_eq!(
Projective3::P + Projective3::Q + Projective3::R,
Projective3::DIM
);
}
#[test]
fn pga2_null_vector_squares_to_zero() {
let e0: Multivector<f64, Projective2> = Multivector::basis_vector(2);
let e0_sq = e0 * e0;
assert!(e0_sq.is_zero(RELATIVE_EQ_EPS));
}
#[test]
fn pga3_null_vector_squares_to_zero() {
let e0: Multivector<f64, Projective3> = Multivector::basis_vector(3);
let e0_sq = e0 * e0;
assert!(e0_sq.is_zero(RELATIVE_EQ_EPS));
}
#[test]
fn pga3_euclidean_vectors_square_to_one() {
let e1: Multivector<f64, Projective3> = Multivector::basis_vector(0);
let e2: Multivector<f64, Projective3> = Multivector::basis_vector(1);
let e3: Multivector<f64, Projective3> = Multivector::basis_vector(2);
assert!(relative_eq!(
(e1 * e1).scalar_part(),
1.0,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
(e2 * e2).scalar_part(),
1.0,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
(e3 * e3).scalar_part(),
1.0,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn pga3_euclidean_bivector_squares_to_minus_one() {
let e1: Multivector<f64, Projective3> = Multivector::basis_vector(0);
let e2: Multivector<f64, Projective3> = 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 pga3_null_bivector_squares_to_zero() {
let e0: Multivector<f64, Projective3> = Multivector::basis_vector(3);
let e1: Multivector<f64, Projective3> = Multivector::basis_vector(0);
let e01 = e0 * e1;
let e01_sq = e01 * e01;
assert!(e01_sq.is_zero(RELATIVE_EQ_EPS));
}
#[test]
fn pga3_point_at_origin() {
let origin: Multivector<f64, Projective3> = Multivector::basis_vector(3);
assert_eq!(origin.grade(RELATIVE_EQ_EPS), Some(1));
}
#[test]
fn pga3_finite_point() {
let mut point: Multivector<f64, Projective3> = Multivector::zero();
point.set(Blade::basis_vector(0), 1.0); point.set(Blade::basis_vector(1), 2.0); point.set(Blade::basis_vector(2), 3.0); point.set(Blade::basis_vector(3), 1.0);
assert_eq!(point.grade(RELATIVE_EQ_EPS), Some(1));
}
#[test]
fn pga3_ideal_point() {
let mut ideal: Multivector<f64, Projective3> = Multivector::zero();
ideal.set(Blade::basis_vector(0), 1.0);
assert_eq!(ideal.grade(RELATIVE_EQ_EPS), Some(1));
assert!(relative_eq!(
ideal.get(Blade::basis_vector(3)),
0.0,
max_relative = RELATIVE_EQ_EPS
));
}
proptest! {
#[test]
fn pga2_geometric_product_associative(
a in any::<Multivector<f64, Projective2>>(),
b in any::<Multivector<f64, Projective2>>(),
c in any::<Multivector<f64, Projective2>>(),
) {
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 pga2_geometric_product_distributive(
a in any::<Multivector<f64, Projective2>>(),
b in any::<Multivector<f64, Projective2>>(),
c in any::<Multivector<f64, Projective2>>(),
) {
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 pga2_scaled_null_vector_squares_to_zero(s in -100.0f64..100.0) {
let e0: Multivector<f64, Projective2> = Multivector::basis_vector(2);
let scaled = e0 * s;
let sq = scaled * scaled;
prop_assert!(sq.is_zero(RELATIVE_EQ_EPS));
}
#[test]
fn pga2_reverse_involutory(a in any::<Multivector<f64, Projective2>>()) {
prop_assert!(relative_eq!(a.reverse().reverse(), a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn pga2_reverse_antimorphism(
a in any::<Multivector<f64, Projective2>>(),
b in any::<Multivector<f64, Projective2>>(),
) {
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 pga2_involute_involutory(a in any::<Multivector<f64, Projective2>>()) {
prop_assert!(relative_eq!(a.involute().involute(), a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn pga2_outer_associative(
a in any::<Multivector<f64, Projective2>>(),
b in any::<Multivector<f64, Projective2>>(),
c in any::<Multivector<f64, Projective2>>(),
) {
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 pga2_one_is_multiplicative_identity(a in any::<Multivector<f64, Projective2>>()) {
let one = Multivector::<f64, Projective2>::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 pga2_zero_is_additive_identity(a in any::<Multivector<f64, Projective2>>()) {
let zero = Multivector::<f64, Projective2>::zero();
prop_assert!(relative_eq!(a + zero, a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn pga2_add_commutative(
a in any::<Multivector<f64, Projective2>>(),
b in any::<Multivector<f64, Projective2>>()
) {
prop_assert!(relative_eq!(a + b, b + a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn pga2_grade_decomposition_complete(a in any::<Multivector<f64, Projective2>>()) {
let sum = (0..=3)
.map(|k| a.grade_select(k))
.fold(Multivector::<f64, Projective2>::zero(), |acc, x| acc + x);
prop_assert!(relative_eq!(sum, a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
}
proptest! {
#[test]
fn pga3_geometric_product_associative(
a in any::<Multivector<f64, Projective3>>(),
b in any::<Multivector<f64, Projective3>>(),
c in any::<Multivector<f64, Projective3>>(),
) {
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 pga3_geometric_product_distributive(
a in any::<Multivector<f64, Projective3>>(),
b in any::<Multivector<f64, Projective3>>(),
c in any::<Multivector<f64, Projective3>>(),
) {
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 pga3_scaled_null_vector_squares_to_zero(s in -100.0f64..100.0) {
let e0: Multivector<f64, Projective3> = Multivector::basis_vector(3);
let scaled = e0 * s;
let sq = scaled * scaled;
prop_assert!(sq.is_zero(RELATIVE_EQ_EPS));
}
#[test]
fn pga3_reverse_involutory(a in any::<Multivector<f64, Projective3>>()) {
prop_assert!(relative_eq!(a.reverse().reverse(), a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn pga3_reverse_antimorphism(
a in any::<Multivector<f64, Projective3>>(),
b in any::<Multivector<f64, Projective3>>(),
) {
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 pga3_involute_involutory(a in any::<Multivector<f64, Projective3>>()) {
prop_assert!(relative_eq!(a.involute().involute(), a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn pga3_outer_associative(
a in any::<Multivector<f64, Projective3>>(),
b in any::<Multivector<f64, Projective3>>(),
c in any::<Multivector<f64, Projective3>>(),
) {
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 pga3_one_is_multiplicative_identity(a in any::<Multivector<f64, Projective3>>()) {
let one = Multivector::<f64, Projective3>::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 pga3_zero_is_additive_identity(a in any::<Multivector<f64, Projective3>>()) {
let zero = Multivector::<f64, Projective3>::zero();
prop_assert!(relative_eq!(a + zero, a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn pga3_add_commutative(
a in any::<Multivector<f64, Projective3>>(),
b in any::<Multivector<f64, Projective3>>()
) {
prop_assert!(relative_eq!(a + b, b + a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn pga3_grade_decomposition_complete(a in any::<Multivector<f64, Projective3>>()) {
let sum = (0..=4)
.map(|k| a.grade_select(k))
.fold(Multivector::<f64, Projective3>::zero(), |acc, x| acc + x);
prop_assert!(relative_eq!(sum, a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn pga3_even_plus_odd_equals_original(a in any::<Multivector<f64, Projective3>>()) {
let reconstructed = a.even() + a.odd();
prop_assert!(relative_eq!(reconstructed, a, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
}
#[cfg(feature = "nalgebra-0_33")]
mod nalgebra_consistency_0_33 {
use super::*;
use nalgebra_0_33 as na;
proptest! {
#[test]
fn pga3_euclidean_dot_matches_nalgebra(
ax in -100.0f64..100.0, ay in -100.0f64..100.0, az in -100.0f64..100.0,
bx in -100.0f64..100.0, by in -100.0f64..100.0, bz in -100.0f64..100.0,
) {
let pga_a: Multivector<f64, Projective3> =
Multivector::vector(&[ax, ay, az, 0.0]);
let pga_b: Multivector<f64, Projective3> =
Multivector::vector(&[bx, by, bz, 0.0]);
let na_a = na::Vector3::new(ax, ay, az);
let na_b = na::Vector3::new(bx, by, bz);
let pga_dot = pga_a.inner(&pga_b).scalar_part();
let na_dot = na_a.dot(&na_b);
prop_assert!(relative_eq!(pga_dot, na_dot, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn pga3_euclidean_vectors_product(
ax in -10.0f64..10.0, ay in -10.0f64..10.0, az in -10.0f64..10.0,
bx in -10.0f64..10.0, by in -10.0f64..10.0, bz in -10.0f64..10.0,
) {
let pga_a: Multivector<f64, Projective3> =
Multivector::vector(&[ax, ay, az, 0.0]);
let pga_b: Multivector<f64, Projective3> =
Multivector::vector(&[bx, by, bz, 0.0]);
let na_a = na::Vector3::new(ax, ay, az);
let na_b = na::Vector3::new(bx, by, bz);
let product = pga_a * pga_b;
let expected_scalar = na_a.dot(&na_b);
prop_assert!(relative_eq!(
product.scalar_part(),
expected_scalar,
max_relative = RELATIVE_EQ_EPS
));
prop_assert!(product.grade_select(1).is_zero(RELATIVE_EQ_EPS));
prop_assert!(product.grade_select(3).is_zero(RELATIVE_EQ_EPS));
}
#[test]
fn pga3_wedge_matches_cross_product(
ax in -10.0f64..10.0, ay in -10.0f64..10.0, az in -10.0f64..10.0,
bx in -10.0f64..10.0, by in -10.0f64..10.0, bz in -10.0f64..10.0,
) {
let pga_a: Multivector<f64, Projective3> =
Multivector::vector(&[ax, ay, az, 0.0]);
let pga_b: Multivector<f64, Projective3> =
Multivector::vector(&[bx, by, bz, 0.0]);
let na_a = na::Vector3::new(ax, ay, az);
let na_b = na::Vector3::new(bx, by, bz);
let na_cross = na_a.cross(&na_b);
let wedge = pga_a.exterior(&pga_b);
let biv_12 = wedge.get(Blade::from_index(0b0011)); let biv_13 = wedge.get(Blade::from_index(0b0101)); let biv_23 = wedge.get(Blade::from_index(0b0110));
prop_assert!(relative_eq!(biv_23, na_cross.x, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
prop_assert!(relative_eq!(-biv_13, na_cross.y, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
prop_assert!(relative_eq!(biv_12, na_cross.z, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
}
}
#[cfg(feature = "nalgebra-0_34")]
mod nalgebra_consistency_0_34 {
use super::*;
use nalgebra_0_34 as na;
proptest! {
#[test]
fn pga3_euclidean_dot_matches_nalgebra(
ax in -100.0f64..100.0, ay in -100.0f64..100.0, az in -100.0f64..100.0,
bx in -100.0f64..100.0, by in -100.0f64..100.0, bz in -100.0f64..100.0,
) {
let pga_a: Multivector<f64, Projective3> =
Multivector::vector(&[ax, ay, az, 0.0]);
let pga_b: Multivector<f64, Projective3> =
Multivector::vector(&[bx, by, bz, 0.0]);
let na_a = na::Vector3::new(ax, ay, az);
let na_b = na::Vector3::new(bx, by, bz);
let pga_dot = pga_a.inner(&pga_b).scalar_part();
let na_dot = na_a.dot(&na_b);
prop_assert!(relative_eq!(pga_dot, na_dot, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
#[test]
fn pga3_euclidean_vectors_product(
ax in -10.0f64..10.0, ay in -10.0f64..10.0, az in -10.0f64..10.0,
bx in -10.0f64..10.0, by in -10.0f64..10.0, bz in -10.0f64..10.0,
) {
let pga_a: Multivector<f64, Projective3> =
Multivector::vector(&[ax, ay, az, 0.0]);
let pga_b: Multivector<f64, Projective3> =
Multivector::vector(&[bx, by, bz, 0.0]);
let na_a = na::Vector3::new(ax, ay, az);
let na_b = na::Vector3::new(bx, by, bz);
let product = &pga_a * &pga_b;
let expected_scalar = na_a.dot(&na_b);
prop_assert!(relative_eq!(
product.scalar_part(),
expected_scalar,
max_relative = RELATIVE_EQ_EPS
));
prop_assert!(product.grade_select(1).is_zero(RELATIVE_EQ_EPS));
prop_assert!(product.grade_select(3).is_zero(RELATIVE_EQ_EPS));
}
#[test]
fn pga3_wedge_matches_cross_product(
ax in -10.0f64..10.0, ay in -10.0f64..10.0, az in -10.0f64..10.0,
bx in -10.0f64..10.0, by in -10.0f64..10.0, bz in -10.0f64..10.0,
) {
let pga_a: Multivector<f64, Projective3> =
Multivector::vector(&[ax, ay, az, 0.0]);
let pga_b: Multivector<f64, Projective3> =
Multivector::vector(&[bx, by, bz, 0.0]);
let na_a = na::Vector3::new(ax, ay, az);
let na_b = na::Vector3::new(bx, by, bz);
let na_cross = na_a.cross(&na_b);
let wedge = pga_a.exterior(&pga_b);
let biv_12 = wedge.get(Blade::from_index(0b0011)); let biv_13 = wedge.get(Blade::from_index(0b0101)); let biv_23 = wedge.get(Blade::from_index(0b0110));
prop_assert!(relative_eq!(biv_23, na_cross.x, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
prop_assert!(relative_eq!(-biv_13, na_cross.y, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
prop_assert!(relative_eq!(biv_12, na_cross.z, epsilon = RELATIVE_EQ_EPS, max_relative = RELATIVE_EQ_EPS));
}
}
}
}