algebraeon_geometry/simplexes/
convex_hull.rs

1use std::collections::{HashMap, HashSet};
2
3use super::*;
4
5#[derive(Clone)]
6pub struct ConvexHull<
7    FS: OrderedRingStructure + FieldStructure,
8    SP: Borrow<AffineSpace<FS>> + Clone,
9> where
10    FS::Set: Hash,
11{
12    // the space in which this convex hull lives
13    ambient_space: SP,
14    // the affine subspace spanned by this convex hull
15    // so that this convex hull is "full" in this embedded subspace
16    subspace: EmbeddedAffineSubspace<FS, SP, Rc<AffineSpace<FS>>>,
17    // oriented facets belonging to the embedded subspace such that
18    // the positive side of each facet is on the interior of the convex hull and
19    // the negative side of each facet is on the outside of the convex hull.
20    // These facets should form a simplicial complex
21    facets: Vec<OrientedSimplex<FS, Rc<AffineSpace<FS>>>>,
22    // These interior simplicies are the full-dimensional simplicies in the embedded subspace forming the interior of the convex hull
23    interior: Vec<Simplex<FS, Rc<AffineSpace<FS>>>>,
24    /*
25    Consider the case of a convex hull given by a simplex in dimension d:
26
27    d    | #facets | #interior
28    3    | 4 tri   | 1 tet
29    2    | 3 line  | 1 tri
30    1    | 2 pt    | 1 line
31    0    | 1 null  | 1 pt
32    null | 0 n/a   | 1 null
33
34    This highlights what the behavour should been in the case where the dimension is null and 0
35    */
36}
37
38impl<FS: OrderedRingStructure + FieldStructure, SP: Borrow<AffineSpace<FS>> + Clone> std::fmt::Debug
39    for ConvexHull<FS, SP>
40where
41    FS::Set: std::hash::Hash + std::fmt::Debug,
42{
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        f.debug_struct("ConvexHull")
45            .field(
46                "embedded_in_dim",
47                &self.subspace.embedded_space().affine_dimension(),
48            )
49            .field("facets", &self.facets)
50            .field("interior", &self.interior)
51            .finish()
52    }
53}
54
55// #[derive(Debug, Clone)]
56// pub struct ConvexHullAsSimplicialComplexResult<
57//     FS: OrderedRingStructure + FieldStructure,
58//     SP: Borrow<AffineSpace<FS>> + Clone,
59// > {
60//     pub entire: SimplicialComplex<FS, SP>,
61//     pub boundary: SimplicialComplex<FS, SP>,
62//     pub interior: PartialSimplicialComplex<FS, SP>,
63// }
64
65impl<FS: OrderedRingStructure + FieldStructure, SP: Borrow<AffineSpace<FS>> + Clone>
66    ConvexHull<FS, SP>
67where
68    FS::Set: Hash,
69{
70    fn check(&self) -> Result<(), &'static str> {
71        assert_eq!(
72            self.subspace.ambient_space().borrow(),
73            self.ambient_space.borrow()
74        );
75
76        {
77            for facet in &self.facets {
78                if facet.ambient_space() != self.subspace.embedded_space() {
79                    return Err("Facet must belong to the embedded subspace");
80                }
81            }
82            //interior simplicies must have dimenion equal to self.subspace
83            for spx in &self.interior {
84                if spx.ambient_space() != self.subspace.embedded_space() {
85                    return Err("Interior simplex must belong to the embedded subspace");
86                }
87                if spx.n() != self.subspace.embedded_space().affine_dimension() {
88                    return Err("Interior simplex must span the embedded subspace");
89                }
90            }
91        }
92
93        match self.subspace.borrow().embedded_space().affine_dimension() {
94            0 => {
95                if self.facets.len() != 0 {
96                    return Err("Empty convex hull should have no facets");
97                }
98                if self.interior
99                    != vec![Simplex::new(self.subspace.embedded_space(), vec![]).unwrap()]
100                {
101                    return Err(
102                        "Empty convex hull should have a single null simplex for its interior",
103                    );
104                }
105            }
106            1 => {
107                if self.facets.len() != 1 {
108                    return Err("0D convex hull should have one null facet");
109                }
110                if self.interior
111                    != vec![
112                        Simplex::new(
113                            self.subspace.embedded_space(),
114                            vec![Vector::construct(
115                                self.subspace.embedded_space(),
116                                |_| unreachable!(),
117                            )],
118                        )
119                        .unwrap(),
120                    ]
121                {
122                    return Err("0D convex hull should have one point for its interior");
123                }
124            }
125            _ => {}
126        }
127
128        //facets should be non-empty whenenver self.subspace has dimension >= 1
129        match self.subspace.borrow().embedded_space().linear_dimension() {
130            Some(_) => {
131                if self.facets.is_empty() {
132                    return Err("Facets should be non-empty whenenver the subspace is non-empty");
133                }
134            }
135            None => {}
136        }
137
138        //check that facets each share exactly one ridge
139        {
140            let mut ridges_count = HashMap::new();
141            for facet in &self.facets {
142                for ridge in facet.simplex().facets() {
143                    if !ridges_count.contains_key(&ridge) {
144                        ridges_count.insert(ridge.clone(), 0);
145                    }
146                    *ridges_count.get_mut(&ridge).unwrap() += 1;
147                }
148            }
149            if !ridges_count.into_iter().all(|(_ridge, count)| count == 2) {
150                return Err("Ridges of facets should each be shared between exactly two facets");
151            }
152        }
153
154        //check that facets are convex with negative side inwards by checking that no point is on the positive side of a facet
155        {
156            let mut all_pts = HashSet::new();
157            for facet in &self.facets {
158                for pt in facet.simplex().points() {
159                    all_pts.insert(pt);
160                }
161            }
162            for spx in &self.interior {
163                for pt in spx.points() {
164                    all_pts.insert(pt);
165                }
166            }
167            for facet in &self.facets {
168                for pt in &all_pts {
169                    match facet.classify_point(pt) {
170                        OrientationSide::Negative => {
171                            return Err(
172                                "Every point must be on the positive or neutral side of every facet",
173                            );
174                        }
175                        OrientationSide::Neutral | OrientationSide::Positive => {}
176                    }
177                }
178            }
179        }
180
181        Ok(())
182    }
183
184    pub fn new_empty(ambient_space: SP) -> Self {
185        let subspace = EmbeddedAffineSubspace::new_empty(ambient_space.clone());
186        Self {
187            ambient_space: ambient_space.clone(),
188            subspace: subspace.clone(),
189            facets: vec![],
190            interior: vec![Simplex::new(subspace.embedded_space(), vec![]).unwrap()],
191        }
192    }
193
194    pub fn new(ambient_space: SP, points: Vec<Vector<FS, SP>>) -> Self {
195        let mut ch = Self::new_empty(ambient_space);
196        for point in points {
197            ch.extend_by_point(point);
198        }
199        ch
200    }
201
202    pub fn affine_span_dimension(&self) -> usize {
203        self.subspace.embedded_space().affine_dimension()
204    }
205
206    pub fn from_simplex(spx: Simplex<FS, SP>) -> Self {
207        let ambient_space = spx.ambient_space();
208        let (subspace, embedded_pts) =
209            EmbeddedAffineSubspace::new_affine_span(ambient_space.clone(), spx.into_points())
210                .unwrap();
211        let embedded_spx = Simplex::new(subspace.embedded_space(), embedded_pts).unwrap();
212        Self {
213            ambient_space,
214            subspace,
215            facets: embedded_spx.oriented_facets(),
216            interior: vec![embedded_spx],
217        }
218    }
219
220    pub fn ambient_space(&self) -> SP {
221        self.subspace.ambient_space()
222    }
223
224    pub fn is_empty(&self) -> bool {
225        self.subspace.borrow().embedded_space().affine_dimension() == 0
226    }
227
228    pub fn defining_points(&self) -> HashSet<Vector<FS, SP>> {
229        match self.subspace.borrow().embedded_space().affine_dimension() {
230            0 => HashSet::new(),
231            1 => {
232                //need to handle affine_dim = 1 case seperately from higher dimensional cases
233                //because the facet in the 1d case is the null simplex with no points
234                let (root, span) = self.subspace.get_root_and_span().unwrap();
235                debug_assert_eq!(span.len(), 0);
236                HashSet::from([root])
237            }
238            _ => {
239                let mut points = HashSet::new();
240                for facet in &self.facets {
241                    for point in facet.simplex().points() {
242                        points.insert(self.subspace.embed_point(point));
243                    }
244                }
245                points
246            }
247        }
248    }
249
250    pub fn extend_by_point(&mut self, pt: Vector<FS, SP>) {
251        assert_eq!(pt.ambient_space().borrow(), self.ambient_space.borrow());
252        #[cfg(debug_assertions)]
253        self.check().unwrap();
254
255        // println!(
256        //     "subspace_dim={:?}",
257        //     self.subspace.embedded_space().linear_dimension()
258        // );
259
260        match self.subspace.unembed_point(&pt) {
261            Some(subsp_pt) => {
262                //Partition into visible / hidden facets
263                //Compute horizon as ridges where visible meets hidden
264                //Delete visible facets and extend horizion ridges to the new point for replacement facets
265
266                let mut visible = vec![];
267                let mut hidden = vec![];
268                for facet in &self.facets {
269                    match facet.classify_point(&subsp_pt) {
270                        OrientationSide::Negative => {
271                            visible.push(facet);
272                        }
273                        OrientationSide::Neutral | OrientationSide::Positive => {
274                            hidden.push(facet);
275                        }
276                    }
277                }
278
279                let mut horizon = HashMap::new(); //horizon ridge -> (its adjacent facet, the other vertex)
280                for facet in &visible {
281                    let facet_simplex = facet.simplex();
282                    for i in 0..facet_simplex.n() {
283                        let (ridge, pt) = (facet_simplex.facet(i), &facet_simplex.points()[i]);
284                        match horizon.contains_key(&ridge) {
285                            true => {
286                                horizon.remove(&ridge);
287                            }
288                            false => {
289                                horizon.insert(ridge, (facet, pt));
290                            }
291                        }
292                    }
293                }
294                #[cfg(debug_assertions)]
295                {
296                    let mut horizon_alt = HashSet::new();
297                    for facet in &hidden {
298                        for ridge in facet.simplex().facets() {
299                            match horizon_alt.contains(&ridge) {
300                                true => {
301                                    horizon_alt.remove(&ridge);
302                                }
303                                false => {
304                                    horizon_alt.insert(ridge);
305                                }
306                            }
307                        }
308                    }
309                    assert_eq!(
310                        horizon.keys().map(|r| r.clone()).collect::<HashSet<_>>(),
311                        horizon_alt
312                    );
313                }
314
315                // println!(
316                //     "#visible={:?} #hidden={:?} #horizon={:?}",
317                //     visible.len(),
318                //     hidden.len(),
319                //     horizon.len()
320                // );
321
322                (self.facets, self.interior) = (
323                    horizon
324                        .iter()
325                        .map(|(ridge, (_facet, facet_pt))| {
326                            OrientedSimplex::new_with_positive_point(
327                                self.subspace.embedded_space(),
328                                {
329                                    let mut points = ridge.points().clone();
330                                    points.push(subsp_pt.clone());
331                                    points
332                                },
333                                *facet_pt,
334                            )
335                            .unwrap()
336                        })
337                        .chain(hidden.into_iter().map(|f| f.clone()))
338                        .collect::<Vec<_>>(),
339                    visible
340                        .into_iter()
341                        .map(|facet| {
342                            Simplex::new(self.subspace.embedded_space(), {
343                                let mut points = facet.simplex().points().clone();
344                                points.push(subsp_pt.clone());
345                                points
346                            })
347                            .unwrap()
348                        })
349                        .chain(self.interior.iter().map(|s| s.clone()))
350                        .collect::<Vec<_>>(),
351                );
352            }
353            None => {
354                (self.subspace, self.facets, self.interior) = {
355                    //The new point is outside the current embedded affine subspace
356                    //The new facets are given by the old interior union the old facets extended into the new dimension
357                    //The new interior is given by the old interior extended into the new dimension
358                    {
359                        let (iota, new_subspace_embedding, pt_in_new_subspace) =
360                            self.subspace.extend_dimension_by_point_unsafe(pt.clone());
361
362                        let new_subspace = iota.ambient_space();
363                        debug_assert_eq!(new_subspace, new_subspace_embedding.embedded_space());
364
365                        //new_facets <- old_interior & old_facets
366                        //new_interior <- old_interior
367                        let mut new_facets = vec![];
368                        let mut new_interior = vec![];
369                        for old_facet in &self.facets {
370                            debug_assert_eq!(
371                                old_facet.ambient_space(),
372                                self.subspace.embedded_space()
373                            );
374                            new_facets.push(
375                                OrientedSimplex::new_with_positive_point(
376                                    new_subspace.clone(),
377                                    {
378                                        let mut points = vec![];
379                                        for pt in old_facet.simplex().points() {
380                                            points.push(iota.embed_point(pt));
381                                        }
382                                        points.push(pt_in_new_subspace.clone());
383                                        points
384                                    },
385                                    &{
386                                        //If old_facet is null living inside 0D space then take the iota-embedding of the unique point in the 0D space as the reference
387                                        //If old_facet is not null then it comes equiped with a reference point which we embed via iota and use
388                                        match old_facet.positive_point() {
389                                            Some(pos_pt) => iota.embed_point(&pos_pt),
390                                            None => {
391                                                debug_assert_eq!(
392                                                    self.subspace
393                                                        .embedded_space()
394                                                        .affine_dimension(),
395                                                    1
396                                                );
397                                                iota.embed_point(
398                                                    &self
399                                                        .subspace
400                                                        .embedded_space()
401                                                        .origin()
402                                                        .unwrap(),
403                                                )
404                                            }
405                                        }
406                                    },
407                                )
408                                .unwrap(),
409                            );
410                        }
411                        for old_interior in &self.interior {
412                            debug_assert_eq!(
413                                old_interior.ambient_space(),
414                                self.subspace.embedded_space()
415                            );
416                            new_facets.push(
417                                OrientedSimplex::new_with_positive_point(
418                                    new_subspace.clone(),
419                                    old_interior
420                                        .points()
421                                        .iter()
422                                        .map(|pt| iota.embed_point(pt))
423                                        .collect(),
424                                    &pt_in_new_subspace,
425                                )
426                                .unwrap(),
427                            );
428                            new_interior.push(
429                                Simplex::new(new_subspace.clone(), {
430                                    let mut points = old_interior
431                                        .points()
432                                        .iter()
433                                        .map(|pt| iota.embed_point(pt))
434                                        .collect::<Vec<_>>();
435                                    points.push(pt_in_new_subspace.clone());
436                                    points
437                                })
438                                .unwrap(),
439                            );
440                        }
441
442                        (new_subspace_embedding, new_facets, new_interior)
443                    }
444                }
445            }
446        };
447
448        #[cfg(debug_assertions)]
449        self.check().unwrap();
450    }
451
452    fn embedded_interior_simplexes(&self) -> Vec<Simplex<FS, SP>> {
453        self.interior
454            .iter()
455            .map(|spx| {
456                Simplex::new(
457                    self.ambient_space.clone(),
458                    spx.points()
459                        .iter()
460                        .map(|pt| self.subspace.embed_point(pt))
461                        .collect(),
462                )
463                .unwrap()
464            })
465            .collect()
466    }
467
468    fn embedded_facet_simplexes(&self) -> Vec<Simplex<FS, SP>> {
469        self.facets
470            .iter()
471            .map(|ospx| {
472                Simplex::new(
473                    self.ambient_space.clone(),
474                    ospx.simplex()
475                        .points()
476                        .iter()
477                        .map(|pt| self.subspace.embed_point(pt))
478                        .collect(),
479                )
480                .unwrap()
481            })
482            .collect()
483    }
484
485    pub fn as_simplicial_complex(
486        &self,
487    ) -> LabelledSimplicialComplex<FS, SP, InteriorBoundaryLabel> {
488        let boundary_simplexes = self
489            .embedded_facet_simplexes()
490            .into_iter()
491            .map(|spx| spx.sub_simplices_not_null())
492            .flatten()
493            .collect::<HashSet<_>>();
494
495        let all_simplexes = boundary_simplexes
496            .clone()
497            .into_iter()
498            .chain(
499                self.embedded_interior_simplexes()
500                    .into_iter()
501                    .map(|spx| spx.sub_simplices_not_null())
502                    .flatten(),
503            )
504            .collect::<HashSet<_>>();
505
506        LabelledSimplicialComplex::new_labelled(
507            self.ambient_space().clone(),
508            all_simplexes
509                .into_iter()
510                .map(|spx| {
511                    let label = match boundary_simplexes.contains(&spx) {
512                        false => InteriorBoundaryLabel::Interior,
513                        true => InteriorBoundaryLabel::Boundary,
514                    };
515                    (spx, label)
516                })
517                .collect(),
518        )
519        .unwrap()
520
521        // let mut interior_simplexes = HashSet::new();
522        // for spx in &all_simplexes {
523        //     if !boundary_simplexes.contains(spx) {
524        //         interior_simplexes.insert(spx.clone());
525        //     }
526        // }
527
528        // let entire =
529        //     LabelledSimplicialComplex::new(self.ambient_space.clone(), all_simplexes).unwrap();
530        // ConvexHullAsSimplicialComplexResult {
531        //     entire: entire.clone(),
532        //     boundary: LabelledSimplicialComplex::new(
533        //         self.ambient_space.clone(),
534        //         boundary_simplexes.into_iter().collect(),
535        //     )
536        //     .unwrap(),
537        //     interior: PartialSimplicialComplex::new_unchecked(
538        //         self.ambient_space.clone(),
539        //         interior_simplexes,
540        //     ),
541        // }
542    }
543}
544
545#[derive(Clone)]
546struct ConvexHullWireframe<
547    FS: OrderedRingStructure + FieldStructure,
548    SP: Borrow<AffineSpace<FS>> + Clone,
549> {
550    ambient_space: SP,
551    points: Vec<Vector<FS, SP>>,
552    edges: Vec<(Vector<FS, SP>, Vector<FS, SP>)>,
553}
554
555impl<FS: OrderedRingStructure + FieldStructure, SP: Borrow<AffineSpace<FS>> + Clone>
556    ConvexHullWireframe<FS, SP>
557where
558    FS::Set: Hash,
559{
560    fn from_convex_hull(ch: &ConvexHull<FS, SP>) -> Self {
561        let mut outer_points = HashSet::new();
562        let mut outer_edges = HashSet::new();
563        for facet in &ch.facets {
564            for point in facet.simplex().points() {
565                outer_points.insert(point.clone());
566            }
567            for edge in facet.simplex().edges() {
568                outer_edges.insert(edge);
569            }
570        }
571
572        // type cast
573        let mut outer_points = outer_points.into_iter().collect::<Vec<_>>();
574        let mut outer_edges = outer_edges
575            .into_iter()
576            .map(|line| {
577                let mut pts = line.into_points().into_iter();
578                (pts.next().unwrap(), pts.next().unwrap())
579            })
580            .collect::<Vec<_>>();
581
582        // Note that in linear dimension 0 we need to explicitly add the unique point to outer points.
583        if ch.subspace.embedded_space().affine_dimension() == 1 {
584            let (_root, span) = ch.subspace.get_root_and_span().unwrap();
585            debug_assert_eq!(span.len(), 0);
586            debug_assert_eq!(outer_points.len(), 0);
587            outer_points.push(Vector::new(ch.subspace.embedded_space(), vec![]));
588        }
589
590        // Note that in linear dimension 1 this doesnt quite work for outer edges since the boundary is not connected. Instead, outer edges should just be the edge between the two points.
591        if ch.subspace.embedded_space().affine_dimension() == 2 {
592            debug_assert_eq!(outer_points.len(), 2);
593            debug_assert_eq!(outer_edges.len(), 0);
594            let mut pts = outer_points.iter();
595            outer_edges.push((
596                (*pts.next().unwrap()).clone(),
597                (*pts.next().unwrap()).clone(),
598            ));
599        }
600
601        Self {
602            ambient_space: ch.ambient_space(),
603            points: outer_points
604                .into_iter()
605                .map(|pt| ch.subspace.embed_point(&pt))
606                .collect(),
607            edges: outer_edges
608                .into_iter()
609                .map(|(pt1, pt2)| (ch.subspace.embed_point(&pt1), ch.subspace.embed_point(&pt2)))
610                .collect(),
611        }
612    }
613
614    fn into_convex_hull(self) -> ConvexHull<FS, SP> {
615        ConvexHull::new(self.ambient_space, self.points)
616    }
617
618    pub fn intersect_with_oriented_hyperplane(
619        &self,
620        hyperplane: &OrientedHyperplane<FS, SP>,
621        region: OrientationSide, //TODO: make this const generic once rust has const generic enums
622    ) -> Self {
623        /*
624        Find positive_points, middle_points, negative_points such that
625        ConvexHull(middle_points) = self intersect hyperplane
626        ConvexHull(positive_points union middle_points) = self intersect (hyperplane union hyperplane_posside)
627        ConvexHull(negative_points union middle_points) = self intersect (hyperplane union hyperplane_negside)
628        */
629        let mut positive_points = HashSet::new();
630        let mut middle_points = HashSet::new();
631        let mut negative_points = HashSet::new();
632        for point in &self.points {
633            match hyperplane.classify_point(&point) {
634                OrientationSide::Positive => {
635                    positive_points.insert(point.clone());
636                }
637                OrientationSide::Neutral => {
638                    middle_points.insert(point.clone());
639                }
640                OrientationSide::Negative => {
641                    negative_points.insert(point.clone());
642                }
643            }
644        }
645        let mut positive_edges = HashSet::new();
646        let mut negative_edges = HashSet::new();
647        for (a, b) in &self.edges {
648            match hyperplane.intersect_line(&a, &b) {
649                OrientedHyperplaneIntersectLineSegmentResult::PositivePositive
650                | OrientedHyperplaneIntersectLineSegmentResult::PositiveNeutral
651                | OrientedHyperplaneIntersectLineSegmentResult::NeutralPositive => {
652                    positive_edges.insert((a.clone(), b.clone()));
653                }
654                OrientedHyperplaneIntersectLineSegmentResult::NegativeNegative
655                | OrientedHyperplaneIntersectLineSegmentResult::NegativeNeutral
656                | OrientedHyperplaneIntersectLineSegmentResult::NeutralNegative => {
657                    negative_edges.insert((a.clone(), b.clone()));
658                }
659
660                OrientedHyperplaneIntersectLineSegmentResult::NeutralNeutral => {
661                    //We deal with middle edges later
662                }
663                OrientedHyperplaneIntersectLineSegmentResult::PositiveNegative {
664                    intersection_point: m,
665                } => {
666                    positive_edges.insert((a.clone(), m.clone()));
667                    negative_edges.insert((m.clone(), b.clone()));
668                    middle_points.insert(m);
669                }
670                OrientedHyperplaneIntersectLineSegmentResult::NegativePositive {
671                    intersection_point: m,
672                } => {
673                    positive_edges.insert((b.clone(), m.clone()));
674                    negative_edges.insert((m.clone(), a.clone()));
675                    middle_points.insert(m);
676                }
677            }
678        }
679
680        let ConvexHullWireframe {
681            ambient_space: _,
682            points: middle_points,
683            edges: middle_edges,
684        } = Self::from_convex_hull(&ConvexHull::new(
685            self.ambient_space.clone(),
686            middle_points.into_iter().collect(),
687        ));
688
689        if !positive_points.is_empty() && !negative_points.is_empty() {
690            debug_assert!(!middle_points.is_empty());
691        }
692
693        let mut points: HashSet<Vector<FS, SP>> = middle_points.into_iter().collect();
694        let mut edges: HashSet<(Vector<FS, SP>, Vector<FS, SP>)> =
695            middle_edges.into_iter().collect();
696
697        match region {
698            OrientationSide::Positive => {
699                for pt in positive_points {
700                    points.insert(pt);
701                }
702                for edge in positive_edges {
703                    edges.insert(edge);
704                }
705            }
706            OrientationSide::Neutral => {}
707            OrientationSide::Negative => {
708                for pt in negative_points {
709                    points.insert(pt);
710                }
711                for edge in negative_edges {
712                    edges.insert(edge);
713                }
714            }
715        }
716
717        Self {
718            ambient_space: self.ambient_space.clone(),
719            points: points.into_iter().collect(),
720            edges: edges.into_iter().collect(),
721        }
722    }
723}
724
725impl<FS: OrderedRingStructure + FieldStructure, SP: Borrow<AffineSpace<FS>> + Clone>
726    ConvexHull<FS, SP>
727where
728    FS::Set: Hash,
729{
730    pub fn intersect_with_oriented_hyperplane(
731        &self,
732        hyperplane: &OrientedHyperplane<FS, SP>,
733        region: OrientationSide, //TODO: make this const generic once rust has const generic enums
734    ) -> Self {
735        ConvexHullWireframe::from_convex_hull(self)
736            .intersect_with_oriented_hyperplane(hyperplane, region)
737            .into_convex_hull()
738    }
739
740    pub fn intersect_mut(&mut self, other: &Self) {
741        let ambient_space = self.ambient_space();
742        assert_eq!(ambient_space.borrow(), other.ambient_space().borrow());
743        match other.subspace.as_hyperplane_intersection() {
744            Some(hyperplanes) => {
745                // step 1: intersect with the affine space spanned by other
746                for hyperplane in hyperplanes {
747                    *self = self
748                        .intersect_with_oriented_hyperplane(&hyperplane, OrientationSide::Neutral);
749                }
750                // step2: embed self into other.affine_subspace
751                let embedded_self_in_other_subspace = &ConvexHull::new(
752                    other.subspace.embedded_space(),
753                    self.defining_points()
754                        .into_iter()
755                        .map(|point| other.subspace.unembed_point(&point).unwrap())
756                        .collect(),
757                );
758                debug_assert_eq!(
759                    embedded_self_in_other_subspace
760                        .subspace
761                        .embedded_space()
762                        .affine_dimension(),
763                    self.subspace.embedded_space().affine_dimension()
764                );
765                let mut embedded_self_in_other_subspace =
766                    ConvexHullWireframe::from_convex_hull(&embedded_self_in_other_subspace);
767                // step3 : intersect self with each oriented facet of other
768                for facet in &other.facets {
769                    embedded_self_in_other_subspace = embedded_self_in_other_subspace
770                        .intersect_with_oriented_hyperplane(
771                            &facet.clone().into_oriented_hyperplane(),
772                            OrientationSide::Positive,
773                        );
774                }
775
776                // step4: unembed self back to the ambient space
777                *self = ConvexHull::new(
778                    self.ambient_space(),
779                    embedded_self_in_other_subspace
780                        .into_convex_hull()
781                        .defining_points()
782                        .into_iter()
783                        .map(|point| other.subspace.embed_point(&point))
784                        .collect(),
785                );
786            }
787            None => {
788                //other is empty, so the intersection with other is empty
789                *self = Self::new_empty(self.ambient_space.clone());
790            }
791        }
792    }
793
794    pub fn intersect(&self, other: &Self) -> Self {
795        let mut self_mut = self.clone();
796        self_mut.intersect_mut(other);
797        self_mut
798    }
799}
800#[cfg(test)]
801mod tests {
802    use algebraeon_nzq::rational::*;
803    use algebraeon_sets::structure::*;
804
805    use super::*;
806
807    #[test]
808    fn construct_convex_hull() {
809        let space = AffineSpace::new_linear(Rational::structure(), 2);
810        let mut ch = ConvexHull::new_empty(&space);
811        //add a point to get started
812        ch.extend_by_point(Vector::new(
813            &space,
814            vec![Rational::from(1), Rational::from(1)],
815        ));
816        //add the same point again
817        ch.extend_by_point(Vector::new(
818            &space,
819            vec![Rational::from(1), Rational::from(1)],
820        ));
821        //add a point to form a line
822        ch.extend_by_point(Vector::new(
823            &space,
824            vec![Rational::from(0), Rational::from(1) / Rational::from(2)],
825        ));
826        //add a point such that the line is extended to a longer line
827        ch.extend_by_point(Vector::new(
828            &space,
829            vec![Rational::from(-1), Rational::from(0)],
830        ));
831        //add that point again
832        ch.extend_by_point(Vector::new(
833            &space,
834            vec![Rational::from(-1), Rational::from(0)],
835        ));
836        //add the middle point of the line again
837        ch.extend_by_point(Vector::new(
838            &space,
839            vec![Rational::from(0), Rational::from(1) / Rational::from(2)],
840        ));
841        //add a middle point in the middle of one of the two line segments
842        ch.extend_by_point(Vector::new(
843            &space,
844            vec![
845                Rational::from(1) / Rational::from(2),
846                Rational::from(3) / Rational::from(4),
847            ],
848        ));
849        //add a point to form a double triangle
850        ch.extend_by_point(Vector::new(
851            &space,
852            vec![Rational::from(2), Rational::from(-1)],
853        ));
854        //add a point to extend by one triangle
855        ch.extend_by_point(Vector::new(
856            &space,
857            vec![Rational::from(0), Rational::from(-2)],
858        ));
859        //extend by a triangle such that the boundary ends up with two points on the same straight edge
860        ch.extend_by_point(Vector::new(
861            &space,
862            vec![Rational::from(2), Rational::from(0)],
863        ));
864        //add a point to extend by three triangles
865        ch.extend_by_point(Vector::new(
866            &space,
867            vec![Rational::from(2), Rational::from(2)],
868        ));
869    }
870
871    #[test]
872    fn convex_hull_intersect_hyperplane() {
873        let space = AffineSpace::new_linear(Rational::structure(), 2);
874        let ch = ConvexHull::new(
875            &space,
876            vec![
877                Vector::new(&space, vec![Rational::from(1), Rational::from(1)]),
878                Vector::new(&space, vec![Rational::from(1), Rational::from(1)]),
879                Vector::new(
880                    &space,
881                    vec![Rational::from(0), Rational::from(1) / Rational::from(2)],
882                ),
883                Vector::new(&space, vec![Rational::from(-1), Rational::from(0)]),
884                Vector::new(&space, vec![Rational::from(-1), Rational::from(0)]),
885                Vector::new(
886                    &space,
887                    vec![Rational::from(0), Rational::from(1) / Rational::from(2)],
888                ),
889                Vector::new(
890                    &space,
891                    vec![
892                        Rational::from(1) / Rational::from(2),
893                        Rational::from(3) / Rational::from(4),
894                    ],
895                ),
896                Vector::new(&space, vec![Rational::from(2), Rational::from(-1)]),
897                Vector::new(&space, vec![Rational::from(0), Rational::from(-2)]),
898                Vector::new(&space, vec![Rational::from(2), Rational::from(0)]),
899                Vector::new(&space, vec![Rational::from(2), Rational::from(2)]),
900            ],
901        );
902
903        let ohsp = OrientedSimplex::new_with_positive_point(
904            &space,
905            vec![
906                Vector::new(&space, vec![Rational::from(1), Rational::from(4)]),
907                Vector::new(&space, vec![Rational::from(1), Rational::from(-4)]),
908            ],
909            &Vector::new(&space, vec![Rational::from(10), Rational::from(0)]),
910        )
911        .unwrap()
912        .into_oriented_hyperplane();
913
914        let smaller_ch = ch.intersect_with_oriented_hyperplane(&ohsp, OrientationSide::Neutral);
915        println!("{:?}", smaller_ch);
916        let smaller_ch = ch.intersect_with_oriented_hyperplane(&ohsp, OrientationSide::Positive);
917        println!("{:?}", smaller_ch);
918        let smaller_ch = ch.intersect_with_oriented_hyperplane(&ohsp, OrientationSide::Negative);
919        println!("{:?}", smaller_ch);
920    }
921
922    #[test]
923    fn convex_hull_intersections() {
924        let space = AffineSpace::new_linear(Rational::structure(), 2);
925        //2d intersect 2d
926        {
927            let ch1 = ConvexHull::new(
928                &space,
929                vec![
930                    Vector::new(&space, vec![Rational::from(1), Rational::from(1)]),
931                    Vector::new(&space, vec![Rational::from(1), Rational::from(1)]),
932                    Vector::new(
933                        &space,
934                        vec![Rational::from(0), Rational::from(1) / Rational::from(2)],
935                    ),
936                    Vector::new(&space, vec![Rational::from(-1), Rational::from(0)]),
937                    Vector::new(&space, vec![Rational::from(-1), Rational::from(0)]),
938                    Vector::new(
939                        &space,
940                        vec![Rational::from(0), Rational::from(1) / Rational::from(2)],
941                    ),
942                    Vector::new(
943                        &space,
944                        vec![
945                            Rational::from(1) / Rational::from(2),
946                            Rational::from(3) / Rational::from(4),
947                        ],
948                    ),
949                    Vector::new(&space, vec![Rational::from(2), Rational::from(-1)]),
950                    Vector::new(&space, vec![Rational::from(0), Rational::from(-2)]),
951                    Vector::new(&space, vec![Rational::from(2), Rational::from(0)]),
952                    Vector::new(&space, vec![Rational::from(2), Rational::from(2)]),
953                ],
954            );
955            let ch2 = ConvexHull::new(
956                &space,
957                vec![
958                    Vector::new(&space, vec![Rational::from(-2), Rational::from(0)]),
959                    Vector::new(&space, vec![Rational::from(3), Rational::from(0)]),
960                    Vector::new(&space, vec![Rational::from(3), Rational::from(2)]),
961                    Vector::new(&space, vec![Rational::from(0), Rational::from(-1)]),
962                ],
963            );
964            let ch3 = ch1.intersect(&ch2);
965            println!("{:?}", ch3);
966        }
967        //2d intersect 1d
968        {
969            let ch1 = ConvexHull::new(
970                &space,
971                vec![
972                    Vector::new(&space, vec![Rational::from(1), Rational::from(1)]),
973                    Vector::new(&space, vec![Rational::from(1), Rational::from(1)]),
974                    Vector::new(
975                        &space,
976                        vec![Rational::from(0), Rational::from(1) / Rational::from(2)],
977                    ),
978                    Vector::new(&space, vec![Rational::from(-1), Rational::from(0)]),
979                    Vector::new(&space, vec![Rational::from(-1), Rational::from(0)]),
980                    Vector::new(
981                        &space,
982                        vec![Rational::from(0), Rational::from(1) / Rational::from(2)],
983                    ),
984                    Vector::new(
985                        &space,
986                        vec![
987                            Rational::from(1) / Rational::from(2),
988                            Rational::from(3) / Rational::from(4),
989                        ],
990                    ),
991                    Vector::new(&space, vec![Rational::from(2), Rational::from(-1)]),
992                    Vector::new(&space, vec![Rational::from(0), Rational::from(-2)]),
993                    Vector::new(&space, vec![Rational::from(2), Rational::from(0)]),
994                    Vector::new(&space, vec![Rational::from(2), Rational::from(2)]),
995                ],
996            );
997            let ch2 = ConvexHull::new(
998                &space,
999                vec![
1000                    Vector::new(&space, vec![Rational::from(3), Rational::from(2)]),
1001                    Vector::new(&space, vec![Rational::from(0), Rational::from(-1)]),
1002                ],
1003            );
1004            let ch3 = ch1.intersect(&ch2);
1005            println!("{:?}", ch3);
1006        }
1007        //line misses line
1008        {
1009            let ch1 = ConvexHull::new(
1010                &space,
1011                vec![
1012                    Vector::new(&space, vec![Rational::from(-2), Rational::from(-1)]),
1013                    Vector::new(&space, vec![Rational::from(-1), Rational::from(1)]),
1014                ],
1015            );
1016            let ch2 = ConvexHull::new(
1017                &space,
1018                vec![
1019                    Vector::new(&space, vec![Rational::from(1), Rational::from(1)]),
1020                    Vector::new(&space, vec![Rational::from(-1), Rational::from(0)]),
1021                ],
1022            );
1023            let ch3 = ch1.intersect(&ch2);
1024            debug_assert_eq!(ch3.subspace.embedded_space().affine_dimension(), 0);
1025        }
1026        //line hits line
1027        {
1028            let ch1 = ConvexHull::new(
1029                &space,
1030                vec![
1031                    Vector::new(&space, vec![Rational::from(2), Rational::from(0)]),
1032                    Vector::new(&space, vec![Rational::from(-2), Rational::from(-1)]),
1033                ],
1034            );
1035            let ch2 = ConvexHull::new(
1036                &space,
1037                vec![
1038                    Vector::new(&space, vec![Rational::from(0), Rational::from(-1)]),
1039                    Vector::new(&space, vec![Rational::from(1), Rational::from(1)]),
1040                ],
1041            );
1042            let ch3 = ch1.intersect(&ch2);
1043            debug_assert_eq!(ch3.subspace.embedded_space().affine_dimension(), 1);
1044        }
1045    }
1046
1047    #[test]
1048    fn convex_hull_from_simplex() {
1049        let space = AffineSpace::new_linear(Rational::structure(), 3);
1050        let p1 = Vector::new(
1051            &space,
1052            vec![Rational::from(4), Rational::from(2), Rational::from(2)],
1053        );
1054        let p2 = Vector::new(
1055            &space,
1056            vec![Rational::from(5), Rational::from(-3), Rational::from(3)],
1057        );
1058        let p3 = Vector::new(
1059            &space,
1060            vec![Rational::from(-5), Rational::from(6), Rational::from(2)],
1061        );
1062        let p4 = Vector::new(
1063            &space,
1064            vec![Rational::from(8), Rational::from(2), Rational::from(-9)],
1065        );
1066
1067        ConvexHull::from_simplex(Simplex::new(&space, vec![]).unwrap())
1068            .check()
1069            .unwrap();
1070        ConvexHull::from_simplex(Simplex::new(&space, vec![p1.clone()]).unwrap())
1071            .check()
1072            .unwrap();
1073        ConvexHull::from_simplex(Simplex::new(&space, vec![p1.clone(), p2.clone()]).unwrap())
1074            .check()
1075            .unwrap();
1076        ConvexHull::from_simplex(
1077            Simplex::new(&space, vec![p1.clone(), p2.clone(), p3.clone()]).unwrap(),
1078        )
1079        .check()
1080        .unwrap();
1081        ConvexHull::from_simplex(
1082            Simplex::new(&space, vec![p1.clone(), p2.clone(), p3.clone(), p4.clone()]).unwrap(),
1083        )
1084        .check()
1085        .unwrap();
1086    }
1087}