use crate::{
ambient_space::AffineSpace,
simplex::Simplex,
vector::{DotProduct, Vector},
};
use algebraeon_rings::{
matrix::{Matrix, MatrixStructure},
structure::{FieldSignature, OrderedRingSignature},
};
#[derive(Clone)]
pub struct OrientedSimplex<'f, FS: OrderedRingSignature + FieldSignature> {
simplex: Simplex<'f, FS>,
orientation: Option<OrientedSimplexOrientation<'f, FS>>, }
impl<'f, FS: OrderedRingSignature + FieldSignature> std::fmt::Debug for OrientedSimplex<'f, FS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OrientedSimplex")
.field("simplex", &self.simplex)
.field("orientation", &self.orientation)
.finish()
}
}
impl<'f, FS: OrderedRingSignature + FieldSignature> OrientedSimplex<'f, FS> {
pub fn new_with_positive_point(
ambient_space: AffineSpace<'f, FS>,
points: Vec<Vector<'f, FS>>,
ref_point: &Vector<'f, FS>,
) -> Result<Self, &'static str> {
assert_eq!(ref_point.ambient_space(), ambient_space);
if points.len() != ambient_space.linear_dimension().unwrap() {
return Err("Oriented simplex must have dimension one less than the ambient space");
}
let n = points.len();
if n == 0 {
Ok(Self {
simplex: ambient_space.simplex(points)?,
orientation: None,
})
} else {
let root = &points[0];
let positive_normal = {
let mat = Matrix::construct(n - 1, n, |r, c| {
ambient_space
.field()
.sub(points[r + 1].coordinate(c), points[0].coordinate(c))
});
let kernel = MatrixStructure::<FS, &FS>::new(ambient_space.field())
.col_kernel(mat)
.basis();
if kernel.len() != 1 {
return Err("points are not affine independent");
}
ambient_space.vector(kernel.into_iter().next().unwrap())
};
let flip = match ambient_space.field().cmp(
&(ref_point - root).dot(&positive_normal),
&ambient_space.field().zero(),
) {
std::cmp::Ordering::Less => true,
std::cmp::Ordering::Equal => {
return Err("ref_point lines inside the hyperplane");
}
std::cmp::Ordering::Greater => false,
};
let plane_point = root.clone();
Ok(Self {
simplex: ambient_space.simplex(points)?,
orientation: Some(OrientedSimplexOrientation {
flip,
plane_point,
positive_normal,
}),
})
}
}
pub fn new_with_negative_point(
ambient_space: AffineSpace<'f, FS>,
points: Vec<Vector<'f, FS>>,
ref_point: &Vector<'f, FS>,
) -> Result<Self, &'static str> {
let mut ans = Self::new_with_positive_point(ambient_space, points, ref_point)?;
ans.flip();
Ok(ans)
}
pub fn positive_point(&self) -> Option<Vector<'f, FS>> {
if let Some(orientation) = self.orientation.as_ref() {
match orientation.flip {
false => Some(&orientation.plane_point + &orientation.positive_normal),
true => Some(&orientation.plane_point - &orientation.positive_normal),
}
} else {
None
}
}
pub fn negative_point(&self) -> Option<Vector<'f, FS>> {
let positive_point = self.positive_point()?;
Some({
let pt: &Vector<'f, FS> = &self.simplex.points()[0];
&(pt + pt) - &positive_point
})
}
pub fn ambient_space(&self) -> AffineSpace<'f, FS> {
self.simplex().ambient_space()
}
pub fn simplex(&self) -> &Simplex<'f, FS> {
&self.simplex
}
pub fn into_simplex(self) -> Simplex<'f, FS> {
self.simplex
}
pub fn into_oriented_hyperplane(self) -> OrientedHyperplane<'f, FS> {
OrientedHyperplane {
oriented_simplex: self,
}
}
pub fn flip(&mut self) {
if let Some(OrientedSimplexOrientation { flip, .. }) = &mut self.orientation {
*flip = !*flip;
}
if let Some(OrientedSimplexOrientation {
flip,
plane_point,
positive_normal,
}) = &self.orientation
{
debug_assert_eq!(
self.classify_point(&(plane_point + positive_normal)),
match flip {
false => OrientationSide::Positive,
true => OrientationSide::Negative,
}
);
}
}
fn classify_point_quantitatively(&self, point: &Vector<'f, FS>) -> FS::Set {
match &self.orientation {
Some(OrientedSimplexOrientation {
flip,
plane_point,
positive_normal,
}) => {
let mut value = positive_normal.dot(&(point - plane_point));
if *flip {
value = self.ambient_space().field().neg(&value);
}
value
}
None => self.ambient_space().field().zero(),
}
}
pub fn classify_point(&self, point: &Vector<'f, FS>) -> OrientationSide {
let space = self.ambient_space();
let field = space.field();
let value = self.classify_point_quantitatively(point);
match field.cmp(&value, &field.zero()) {
std::cmp::Ordering::Less => OrientationSide::Negative,
std::cmp::Ordering::Equal => OrientationSide::Neutral,
std::cmp::Ordering::Greater => OrientationSide::Positive,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrientationSide {
Positive,
Neutral,
Negative,
}
#[derive(Debug, Clone)]
struct OrientedSimplexOrientation<'f, FS: OrderedRingSignature + FieldSignature> {
flip: bool, plane_point: Vector<'f, FS>, positive_normal: Vector<'f, FS>, }
#[derive(Clone)]
pub struct OrientedHyperplane<'f, FS: OrderedRingSignature + FieldSignature> {
oriented_simplex: OrientedSimplex<'f, FS>,
}
impl<'f, FS: OrderedRingSignature + FieldSignature> std::fmt::Debug for OrientedHyperplane<'f, FS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OrientedHyperplane")
.field("oriented_simplex", &self.oriented_simplex)
.finish()
}
}
pub enum OrientedHyperplaneIntersectLineSegmentResult<'f, FS: OrderedRingSignature + FieldSignature>
{
PositivePositive,
NegativeNegative,
PositiveNeutral,
NegativeNeutral,
NeutralPositive,
NeutralNegative,
NeutralNeutral,
PositiveNegative { intersection_point: Vector<'f, FS> },
NegativePositive { intersection_point: Vector<'f, FS> },
}
impl<'f, FS: OrderedRingSignature + FieldSignature> OrientedHyperplane<'f, FS> {
pub fn ambient_space(&self) -> AffineSpace<'f, FS> {
self.oriented_simplex.ambient_space()
}
pub fn classify_point(&self, point: &Vector<'f, FS>) -> OrientationSide {
self.oriented_simplex.classify_point(point)
}
pub fn intersect_line(
&self,
a: &Vector<'f, FS>,
b: &Vector<'f, FS>,
) -> OrientedHyperplaneIntersectLineSegmentResult<'f, FS> {
let space = self.ambient_space();
let field = space.field();
let a_val = self.oriented_simplex.classify_point_quantitatively(a);
let b_val = self.oriented_simplex.classify_point_quantitatively(b);
match (
field.cmp(&a_val, &field.zero()),
field.cmp(&b_val, &field.zero()),
) {
(std::cmp::Ordering::Less, std::cmp::Ordering::Greater) => {
let t = field
.try_divide(&a_val, &field.add(&a_val, &field.neg(&b_val)))
.unwrap();
{
debug_assert_eq!(
self.oriented_simplex.classify_point(a),
OrientationSide::Negative
);
debug_assert_eq!(
self.oriented_simplex.classify_point(b),
OrientationSide::Positive
);
OrientedHyperplaneIntersectLineSegmentResult::NegativePositive {
intersection_point: a + &(b - a).scalar_mul(&t),
}
}
}
(std::cmp::Ordering::Greater, std::cmp::Ordering::Less) => {
let t = field
.try_divide(&a_val, &field.add(&a_val, &field.neg(&b_val)))
.unwrap();
{
debug_assert_eq!(
self.oriented_simplex.classify_point(a),
OrientationSide::Positive
);
debug_assert_eq!(
self.oriented_simplex.classify_point(b),
OrientationSide::Negative
);
OrientedHyperplaneIntersectLineSegmentResult::PositiveNegative {
intersection_point: a + &(b - a).scalar_mul(&t),
}
}
}
(std::cmp::Ordering::Less, std::cmp::Ordering::Less) => {
OrientedHyperplaneIntersectLineSegmentResult::NegativeNegative
}
(std::cmp::Ordering::Less, std::cmp::Ordering::Equal) => {
OrientedHyperplaneIntersectLineSegmentResult::NegativeNeutral
}
(std::cmp::Ordering::Equal, std::cmp::Ordering::Less) => {
OrientedHyperplaneIntersectLineSegmentResult::NeutralNegative
}
(std::cmp::Ordering::Equal, std::cmp::Ordering::Equal) => {
OrientedHyperplaneIntersectLineSegmentResult::NeutralNeutral
}
(std::cmp::Ordering::Equal, std::cmp::Ordering::Greater) => {
OrientedHyperplaneIntersectLineSegmentResult::NeutralPositive
}
(std::cmp::Ordering::Greater, std::cmp::Ordering::Equal) => {
OrientedHyperplaneIntersectLineSegmentResult::PositiveNeutral
}
(std::cmp::Ordering::Greater, std::cmp::Ordering::Greater) => {
OrientedHyperplaneIntersectLineSegmentResult::PositivePositive
}
}
}
pub fn flip(&mut self) {
self.oriented_simplex.flip();
}
}
#[cfg(test)]
mod tests {
use algebraeon_nzq::Rational;
use super::*;
#[test]
fn make_oriented_simplex() {
let space = AffineSpace::new_linear(Rational::structure_ref(), 2);
let v1 = space.vector([1, 0]);
let v2 = space.vector([0, 1]);
let v3 = space.vector([2, 3]);
let s_pos =
OrientedSimplex::new_with_positive_point(space, vec![v1.clone(), v2.clone()], &v3)
.unwrap();
let s_neg = OrientedSimplex::new_with_negative_point(space, vec![v1, v2], &v3).unwrap();
assert_ne!(
s_pos.orientation.unwrap().flip,
s_neg.orientation.unwrap().flip
);
}
}