Skip to main content

algebraeon_geometry/
simplicial_complex.rs

1use super::*;
2use crate::{
3    affine_subspace::EmbeddedAffineSubspace,
4    ambient_space::AffineSpace,
5    oriented_simplex::{OrientationSide, OrientedSimplex},
6    partial_simplicial_complex::{LabelledPartialSimplicialComplex, PartialSimplicialComplex},
7    simplex::Simplex,
8    simplex_collection::{
9        InteriorOrBoundary, InteriorOrBoundarySimplexCollection, LabelledSimplexCollection,
10    },
11    simplicial_disjoint_union::LabelledSimplicialDisjointUnion,
12    vector::Vector,
13};
14use std::collections::{HashMap, HashSet};
15
16#[derive(Clone)]
17pub struct SCSpxInfo<'f, FS: OrderedRingSignature + FieldSignature, T: Eq + Clone + Send + Sync> {
18    inv_bdry: HashSet<Simplex<'f, FS>>,
19    label: T,
20}
21
22impl<'f, FS: OrderedRingSignature + FieldSignature, T: Eq + Clone + Send + Sync> std::fmt::Debug
23    for SCSpxInfo<'f, FS, T>
24{
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        f.debug_struct("SCSpxInfo")
27            .field("inv_bdry", &self.inv_bdry)
28            .finish()
29    }
30}
31
32#[derive(Clone)]
33pub struct LabelledSimplicialComplex<
34    'f,
35    FS: OrderedRingSignature + FieldSignature,
36    T: Eq + Clone + Send + Sync,
37> {
38    ambient_space: AffineSpace<'f, FS>,
39    simplexes: HashMap<Simplex<'f, FS>, SCSpxInfo<'f, FS, T>>,
40}
41
42pub type SimplicialComplex<'f, FS> = LabelledSimplicialComplex<'f, FS, ()>;
43
44impl<'f, FS: OrderedRingSignature + FieldSignature, T: Eq + Clone + Send + Sync> std::fmt::Debug
45    for LabelledSimplicialComplex<'f, FS, T>
46{
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_struct("SimplicialComplex")
49            .field("simplexes", &self.simplexes)
50            .finish()
51    }
52}
53
54impl<'f, FS: OrderedRingSignature + FieldSignature, T: Eq + Clone + Send + Sync>
55    LabelledSimplexCollection<'f, FS, T> for LabelledSimplicialComplex<'f, FS, T>
56where
57    FS::Set: Hash,
58{
59    type WithLabel<S: Eq + Clone + Send + Sync> = LabelledSimplicialComplex<'f, FS, S>;
60    type SubsetType = LabelledPartialSimplicialComplex<'f, FS, T>;
61
62    fn try_new_labelled(
63        ambient_space: AffineSpace<'f, FS>,
64        simplexes: HashMap<Simplex<'f, FS>, T>,
65    ) -> Result<Self, &'static str> {
66        for simplex in simplexes.keys() {
67            assert_eq!(simplex.ambient_space(), ambient_space);
68            if simplex.points().is_empty() {
69                return Err("Simplicial complex musn't contain the null simplex");
70            }
71        }
72
73        let mut simplexes = simplexes
74            .into_iter()
75            .map(|(spx, label)| {
76                (
77                    spx,
78                    SCSpxInfo {
79                        inv_bdry: HashSet::new(),
80                        label,
81                    },
82                )
83            })
84            .collect::<HashMap<_, _>>();
85
86        for simplex in simplexes.keys().cloned().collect::<Vec<_>>() {
87            for bdry_spx in simplex.proper_sub_simplices_not_null() {
88                match simplexes.get_mut(&bdry_spx) {
89                    Some(entry) => {
90                        entry.inv_bdry.insert(simplex.clone());
91                    }
92                    None => {
93                        return Err("Simplicial complex must be closed under taking boundaries");
94                    }
95                }
96            }
97        }
98
99        Ok(Self {
100            ambient_space,
101            simplexes,
102        })
103    }
104
105    fn new_labelled_unchecked(
106        ambient_space: AffineSpace<'f, FS>,
107        simplexes: HashMap<Simplex<'f, FS>, T>,
108    ) -> Self {
109        Self::try_new_labelled(ambient_space, simplexes).unwrap()
110    }
111
112    fn ambient_space(&self) -> AffineSpace<'f, FS> {
113        self.ambient_space
114    }
115
116    fn labelled_simplexes(&self) -> HashMap<&Simplex<'f, FS>, &T> {
117        self.simplexes
118            .iter()
119            .map(|(spx, info)| (spx, &info.label))
120            .collect()
121    }
122
123    fn into_labelled_simplexes(self) -> HashMap<Simplex<'f, FS>, T> {
124        self.simplexes
125            .into_iter()
126            .map(|(spx, info)| (spx, info.label))
127            .collect()
128    }
129
130    fn into_partial_simplicial_complex(self) -> LabelledPartialSimplicialComplex<'f, FS, T> {
131        LabelledPartialSimplicialComplex::new_labelled_unchecked(
132            self.ambient_space,
133            self.simplexes
134                .into_iter()
135                .map(|(spx, info)| (spx, info.label))
136                .collect(),
137        )
138    }
139
140    fn to_partial_simplicial_complex(&self) -> LabelledPartialSimplicialComplex<'f, FS, T> {
141        self.clone().into_partial_simplicial_complex()
142    }
143
144    fn into_simplicial_disjoint_union(self) -> LabelledSimplicialDisjointUnion<'f, FS, T> {
145        LabelledSimplicialDisjointUnion::new_labelled_unchecked(
146            self.ambient_space,
147            self.simplexes
148                .into_iter()
149                .map(|(spx, info)| (spx, info.label))
150                .collect(),
151        )
152    }
153
154    fn to_simplicial_disjoint_union(&self) -> LabelledSimplicialDisjointUnion<'f, FS, T> {
155        self.clone().into_simplicial_disjoint_union()
156    }
157}
158
159impl<'f, FS: OrderedRingSignature + FieldSignature, T: Eq + Clone + Send + Sync>
160    LabelledSimplicialComplex<'f, FS, T>
161where
162    FS::Set: Hash,
163{
164    #[allow(unused)]
165    pub(crate) fn check(&self) {
166        let mut inv_bdry_map = HashMap::new();
167        for spx in self.simplexes.keys() {
168            inv_bdry_map.insert(spx.clone(), HashSet::new());
169        }
170
171        for spx in self.simplexes.keys() {
172            for bdry_spx in spx.proper_sub_simplices_not_null() {
173                assert!(self.simplexes.contains_key(&bdry_spx));
174                inv_bdry_map.get_mut(&bdry_spx).unwrap().insert(spx.clone());
175            }
176        }
177
178        for (spx, info) in &self.simplexes {
179            assert_eq!(&info.inv_bdry, inv_bdry_map.get(spx).unwrap());
180        }
181
182        //check that every pair of distinct simplexes intersect in the empty set
183        LabelledSimplicialDisjointUnion::new_labelled_unchecked(
184            self.ambient_space(),
185            self.simplexes
186                .iter()
187                .map(|(spx, info)| (spx.clone(), info.label.clone()))
188                .collect(),
189        )
190        .check();
191    }
192}
193
194impl<'f, FS: OrderedRingSignature + FieldSignature> SimplicialComplex<'f, FS>
195where
196    FS::Set: Hash,
197{
198    pub fn interior_and_boundary(&self) -> LabelledSimplicialComplex<'f, FS, InteriorOrBoundary> {
199        /*
200        let n be the dimension of the space self is living in
201         - every simplex of rank n is part of the interior
202         - a simplex of rank n-1 is the facet of at most 2 simplices of rank n, and is part of the interior if and only if it is the facet of exactly 2 simplices of rank n
203         - a simplex of rank less or equal to n-2 is part of the interior iff it is in the boundary of some strictly higher rank simplex AND every strictly higher rank simplex containing it as part of the boundary is part of the interior
204        */
205
206        let n = self.ambient_space().affine_dimension();
207
208        let mut simplexes = HashMap::new();
209
210        let mut all = self.simplexes.keys().cloned().collect::<Vec<_>>();
211        all.sort_unstable_by_key(|s| std::cmp::Reverse(s.n())); //so that we process largest rank first
212        for simplex in all {
213            let r = simplex.n();
214            if r == n {
215                //rank n simplex is always part of the interior
216                simplexes.insert(simplex, InteriorOrBoundary::Interior);
217            } else {
218                let inv_bdry = &self.simplexes.get(&simplex).unwrap().inv_bdry;
219                if r == n - 1 {
220                    //rank n-1 simplex is part of the boundary of at most 2 rank n simplices
221                    //it is part of the boundary of the simplicial complex iff it is the boundary of exactly 2
222                    match inv_bdry.len() {
223                        0 | 1 => {
224                            simplexes.insert(simplex, InteriorOrBoundary::Boundary);
225                        }
226                        2 => {
227                            simplexes.insert(simplex, InteriorOrBoundary::Interior);
228                        }
229                        _ => panic!(
230                            "rank n-1 simplex should be in the boundary of at most 2 rank n simplices"
231                        ),
232                    }
233                } else {
234                    //rank < n-1 simplex is part of the interior iff it is part of the boundary of at least one simplex and every such simplex is part of the interior
235                    debug_assert!(r < n - 1);
236                    if inv_bdry.is_empty() {
237                        simplexes.insert(simplex, InteriorOrBoundary::Boundary);
238                    } else if inv_bdry
239                        .iter()
240                        .all(|b| simplexes.get(b).unwrap() == &InteriorOrBoundary::Interior)
241                    {
242                        simplexes.insert(simplex, InteriorOrBoundary::Interior);
243                    } else {
244                        simplexes.insert(simplex, InteriorOrBoundary::Boundary);
245                    }
246                }
247            }
248        }
249
250        LabelledSimplicialComplex::try_new_labelled(self.ambient_space(), simplexes).unwrap()
251    }
252
253    pub(crate) fn interior_raw(&self) -> PartialSimplicialComplex<'f, FS> {
254        self.interior_and_boundary().interior()
255    }
256
257    pub fn interior(&self) -> PartialSimplicialComplex<'f, FS> {
258        self.interior_raw().simplify()
259    }
260
261    pub(crate) fn boundary_raw(&self) -> SimplicialComplex<'f, FS> {
262        self.interior_and_boundary()
263            .boundary()
264            .try_into_simplicial_complex()
265            .unwrap()
266    }
267
268    pub fn boundary(&self) -> SimplicialComplex<'f, FS> {
269        self.boundary_raw().simplify()
270    }
271}
272
273/*
274Input:
275    A list of oriented simplicies which join to form a closed region of space
276    with negative side inside and positive side outside which join
277
278    A list of simplicies filling the interior of the region, each labelled
279
280Output:
281    Figure out whether the region can be filled in by fanning out from some point such that the labelling of space is preserved
282    If it can't: return None
283    If it can: return the simplicies to use to fill in the interior region
284
285*/
286
287fn simplify_in_region<'f, FS: OrderedRingSignature + FieldSignature, T: Eq + Clone + Send + Sync>(
288    space: AffineSpace<'f, FS>,
289    boundary_facets: Vec<OrientedSimplex<'f, FS>>,
290    labelled_interior: HashMap<Simplex<'f, FS>, T>,
291) -> Option<HashMap<Simplex<'f, FS>, T>>
292where
293    FS::Set: Hash,
294{
295    let labelled_interior =
296        LabelledSimplicialDisjointUnion::new_labelled_unchecked(space, labelled_interior);
297
298    let interior_uniform_label = {
299        let labels = labelled_interior
300            .labelled_simplexes()
301            .into_values()
302            .collect::<Vec<_>>();
303        let first = *labels.first()?;
304        if labels.iter().all(|label| *label == first) {
305            Some(first)
306        } else {
307            None
308        }
309    };
310
311    for spx in &boundary_facets {
312        debug_assert_eq!(spx.ambient_space(), space);
313    }
314
315    let mut boundary_points: HashMap<Vector<'f, FS>, Vec<usize>> = HashMap::new();
316    for (idx, spx) in boundary_facets.iter().enumerate() {
317        for pt in spx.simplex().points() {
318            if boundary_points.contains_key(pt) {
319                boundary_points.get_mut(pt).unwrap().push(idx);
320            } else {
321                boundary_points.insert(pt.clone(), vec![idx]);
322            }
323        }
324    }
325
326    'BOUNDARY_LOOP: for (boundary_point, adjacent_facets) in boundary_points {
327        let mut nonadjacent_facets = (0..boundary_facets.len()).collect::<HashSet<_>>();
328        for idx in &adjacent_facets {
329            nonadjacent_facets.remove(idx);
330        }
331
332        if nonadjacent_facets.iter().all(|idx| {
333            match boundary_facets[*idx].classify_point(&boundary_point) {
334                OrientationSide::Positive | OrientationSide::Neutral => false,
335                OrientationSide::Negative => true,
336            }
337        }) {
338            let mut nonadjacent_simplexes = HashSet::new();
339            for idx in nonadjacent_facets {
340                for spx in boundary_facets[idx].simplex().sub_simplices_not_null() {
341                    nonadjacent_simplexes.insert(spx);
342                }
343            }
344            for idx in adjacent_facets {
345                for spx in boundary_facets[idx].simplex().sub_simplices_not_null() {
346                    nonadjacent_simplexes.remove(&spx);
347                }
348            }
349
350            let labelled_new_interior = LabelledSimplicialDisjointUnion::new_labelled_unchecked(
351                space,
352                nonadjacent_simplexes
353                    .into_iter()
354                    .enumerate()
355                    .map(|(i, spx)| {
356                        (
357                            {
358                                let mut points = spx.points().clone();
359                                points.push(boundary_point.clone());
360                                spx.ambient_space().simplex(points).unwrap()
361                            },
362                            i,
363                        )
364                    })
365                    .collect(),
366            );
367
368            let filler = if let Some(interior_label) = interior_uniform_label {
369                labelled_new_interior.apply_label_function(|_| interior_label.clone())
370            } else {
371                let interior_intersection = LabelledSimplicialDisjointUnion::intersect_raw(
372                    &labelled_interior,
373                    &labelled_new_interior,
374                );
375
376                debug_assert!(
377                    LabelledSimplicialDisjointUnion::subtract_raw(
378                        &labelled_interior,
379                        &labelled_new_interior,
380                    )
381                    .labelled_simplexes()
382                    .is_empty()
383                );
384
385                debug_assert!(
386                    LabelledSimplicialDisjointUnion::subtract_raw(
387                        &labelled_new_interior,
388                        &labelled_interior,
389                    )
390                    .labelled_simplexes()
391                    .is_empty()
392                );
393
394                let mut i_to_label_map = HashMap::new();
395                for (_, (label, i)) in interior_intersection.labelled_simplexes() {
396                    match i_to_label_map.entry(i) {
397                        std::collections::hash_map::Entry::Occupied(occupied_entry) => {
398                            if *occupied_entry.get() != label {
399                                continue 'BOUNDARY_LOOP;
400                            }
401                        }
402                        std::collections::hash_map::Entry::Vacant(vacant_entry) => {
403                            vacant_entry.insert(label);
404                        }
405                    }
406                }
407
408                labelled_new_interior
409                    .apply_label_function(|label| (*i_to_label_map.get(label).unwrap()).clone())
410            };
411
412            return Some(filler.into_labelled_simplexes());
413        }
414    }
415    None
416}
417
418impl<'f, FS: OrderedRingSignature + FieldSignature, T: Eq + Clone + Send + Sync>
419    LabelledSimplicialComplex<'f, FS, T>
420where
421    FS::Set: Hash,
422{
423    pub fn simplify(mut self) -> Self {
424        //go through each point
425        //compute its star
426        //enter the affine subspace spanned by its star
427        //compute its link as oriented simplices
428        //if point is part of the link then no simplification can be made, so move on
429        //check whether any point of the link is in the interior with respect to every boundary of the link
430        //if such a point exists, it can be used to fill in the star in a simpler way by fanning out
431
432        let mut pts_todo = HashSet::new();
433        for simplex in self.simplexes.keys() {
434            for pt in simplex.points() {
435                pts_todo.insert(pt.clone());
436            }
437        }
438
439        while !pts_todo.is_empty() {
440            let pt = {
441                let mut pts_todo_iter = pts_todo.into_iter();
442                let pt = pts_todo_iter.next().unwrap();
443                pts_todo = pts_todo_iter.collect();
444                pt
445            };
446
447            let pt_spx = self.ambient_space().simplex(vec![pt.clone()]).unwrap();
448            let (star, link) = {
449                let mut star = self.simplexes.get(&pt_spx).unwrap().inv_bdry.clone();
450                star.insert(pt_spx.clone());
451
452                let mut nbd = HashSet::new();
453                for spx in &star {
454                    for bdry in spx.sub_simplices_not_null() {
455                        nbd.insert(bdry);
456                    }
457                }
458
459                let mut link = nbd.clone();
460                for spx in &star {
461                    link.remove(spx);
462                }
463
464                debug_assert_eq!(star.len() + link.len(), nbd.len());
465                for link_spx in &link {
466                    debug_assert!(self.simplexes.contains_key(link_spx));
467                }
468
469                (star, link)
470            };
471
472            let link_points = {
473                let mut link_points: Vec<Vector<'f, FS>> = vec![];
474                for spx in &link {
475                    for p in spx.points() {
476                        link_points.push(p.clone());
477                    }
478                }
479                link_points
480            };
481
482            let nbd_points = {
483                let mut nbd_points = link_points.clone();
484                nbd_points.push(pt.clone());
485                nbd_points
486            };
487
488            let nbd_affine_subspace = EmbeddedAffineSubspace::new_affine_span(
489                self.ambient_space(),
490                nbd_points.iter().collect(),
491            );
492
493            let nbd_points_img = nbd_points
494                .iter()
495                .map(|pt| nbd_affine_subspace.unembed_point(pt).unwrap())
496                .collect::<Vec<_>>();
497
498            let pt_img = nbd_affine_subspace.unembed_point(&pt).unwrap();
499            let pt_img_spx = nbd_affine_subspace
500                .embedded_space()
501                .simplex(vec![pt_img.clone()])
502                .unwrap();
503
504            let star_img = star
505                .iter()
506                .map(|s| {
507                    (
508                        nbd_affine_subspace.unembed_simplex(s).unwrap(),
509                        self.simplexes.get(s).unwrap().label.clone(),
510                    )
511                })
512                .collect::<HashMap<_, _>>();
513
514            let link_img = link
515                .iter()
516                .map(|s| nbd_affine_subspace.unembed_simplex(s).unwrap())
517                .collect::<HashSet<_>>();
518
519            let nbd = LabelledSimplicialComplex::<'f, FS, T>::try_new(
520                nbd_affine_subspace.embedded_space(),
521                {
522                    let mut simplexes = HashSet::new();
523                    simplexes.extend(star_img.keys().cloned());
524                    simplexes.extend(link_img.clone());
525                    simplexes
526                },
527            )
528            .unwrap();
529            let nbd_interior = nbd.interior_raw().into_simplexes();
530
531            if !nbd_interior.contains(&pt_img_spx) {
532                /*
533                pt is on the boundary of nbd and nbd looks something like this
534
535                    l     l      l
536                     +----------+
537                    / \\__       \
538                l  /   \  \__ s   \ l
539                  /   s \    \__   \
540                 /       \      \__ \
541                +---------+---------+
542                r    b    p    b    r
543
544                where
545                    p = point
546                    b = boundary = intersection of star and ndb.boundary
547                    r = rim = closure of boundary minus boundary
548                    l = semilink = link minus rim
549                    s = semistar = star minus boundary
550
551                so the idea here is
552                    simplify the boundary by filling in the rim to replace the boundary
553                    then simplify the rest by filling in the new boundary and the link to replace the star
554                */
555
556                let boundary_img = star_img
557                    .iter()
558                    .filter(|(spx, _)| !nbd_interior.contains(spx))
559                    .collect::<HashMap<_, _>>();
560                let semistar_img = star_img
561                    .iter()
562                    .filter(|(spx, _)| nbd_interior.contains(spx))
563                    .collect::<HashMap<_, _>>();
564
565                let boundary = boundary_img
566                    .keys()
567                    .map(|spx| nbd_affine_subspace.embed_simplex(spx))
568                    .collect::<Vec<_>>();
569                let semistar = semistar_img
570                    .keys()
571                    .map(|spx| nbd_affine_subspace.embed_simplex(spx))
572                    .collect::<Vec<_>>();
573
574                debug_assert_eq!(boundary.len() + semistar.len(), star.len());
575
576                let mut boundary_img_points = HashSet::new();
577                for spx in boundary_img.keys() {
578                    for p in spx.points() {
579                        boundary_img_points.insert(p);
580                    }
581                }
582                let nbd_boundary_affine_subspace = EmbeddedAffineSubspace::new_affine_span(
583                    nbd_affine_subspace.embedded_space(),
584                    boundary_img_points.into_iter().collect(),
585                );
586                debug_assert!(
587                    nbd_boundary_affine_subspace
588                        .embedded_space()
589                        .affine_dimension()
590                        <= nbd_affine_subspace.embedded_space().affine_dimension()
591                );
592                if nbd_boundary_affine_subspace
593                    .embedded_space()
594                    .affine_dimension()
595                    + 1
596                    == nbd_affine_subspace.embedded_space().affine_dimension()
597                {
598                    let ref_point_img = {
599                        let mut ref_point_img = None;
600                        for pt in &nbd_points_img {
601                            if nbd_boundary_affine_subspace.unembed_point(pt).is_none() {
602                                ref_point_img = Some(pt.clone());
603                                break;
604                            }
605                        }
606                        ref_point_img.unwrap()
607                    };
608                    let oriented_hyperplane = OrientedSimplex::new_with_negative_point(
609                        nbd_boundary_affine_subspace.ambient_space(),
610                        nbd_boundary_affine_subspace.get_embedding_points().clone(),
611                        &ref_point_img,
612                    )
613                    .unwrap();
614                    for pt in &nbd_points_img {
615                        debug_assert!(
616                            oriented_hyperplane.classify_point(pt) != OrientationSide::Positive
617                        );
618                    }
619
620                    let rim_img = {
621                        let mut rim_img = HashSet::new();
622                        for spx in boundary_img.keys() {
623                            for bspx in spx.sub_simplices_not_null() {
624                                rim_img.insert(bspx);
625                            }
626                        }
627                        for spx in boundary_img.keys() {
628                            rim_img.remove(spx);
629                        }
630                        rim_img
631                    };
632
633                    let pt_img_img = nbd_boundary_affine_subspace.unembed_point(&pt_img).unwrap();
634
635                    let rim_img_img = rim_img
636                        .iter()
637                        .map(|spx| {
638                            OrientedSimplex::new_with_negative_point(
639                                nbd_boundary_affine_subspace.embedded_space(),
640                                nbd_boundary_affine_subspace
641                                    .unembed_simplex(spx)
642                                    .unwrap()
643                                    .points()
644                                    .clone(),
645                                &pt_img_img,
646                            )
647                            .unwrap()
648                        })
649                        .collect::<Vec<_>>();
650
651                    let boundary_img_img = boundary_img
652                        .iter()
653                        .map(|(spx, label)| {
654                            (
655                                nbd_boundary_affine_subspace.unembed_simplex(spx).unwrap(),
656                                *label,
657                            )
658                        })
659                        .collect::<Vec<_>>();
660
661                    if let Some(new_boundary_img_img) = simplify_in_region(
662                        nbd_boundary_affine_subspace.embedded_space(),
663                        rim_img_img,
664                        boundary_img_img
665                            .iter()
666                            .map(|(spx, label)| (spx.clone(), (*label).clone()))
667                            .collect(),
668                    ) {
669                        let new_boundary_img = new_boundary_img_img
670                            .iter()
671                            .map(|(spx, label)| {
672                                (nbd_boundary_affine_subspace.embed_simplex(spx), label)
673                            })
674                            .collect::<Vec<_>>();
675
676                        let sphere_img = {
677                            let mut sphere_img = vec![];
678                            for (spx, _) in &new_boundary_img {
679                                if spx.n() + 1
680                                    == nbd_affine_subspace.embedded_space().affine_dimension()
681                                {
682                                    sphere_img.push(
683                                        OrientedSimplex::new_with_negative_point(
684                                            nbd_affine_subspace.embedded_space(),
685                                            spx.points().clone(),
686                                            &ref_point_img,
687                                        )
688                                        .unwrap(),
689                                    );
690                                }
691                            }
692                            for spx in &link_img {
693                                if spx.n() + 1
694                                    == nbd_affine_subspace.embedded_space().affine_dimension()
695                                {
696                                    sphere_img.push(
697                                        OrientedSimplex::new_with_negative_point(
698                                            nbd_affine_subspace.embedded_space(),
699                                            spx.points().clone(),
700                                            &pt_img,
701                                        )
702                                        .unwrap(),
703                                    );
704                                }
705                            }
706                            sphere_img
707                        };
708
709                        if let Some(new_star_img) = simplify_in_region(
710                            nbd_affine_subspace.embedded_space(),
711                            sphere_img,
712                            semistar_img
713                                .into_iter()
714                                .map(|(spx, label)| (spx.clone(), label.clone()))
715                                .collect(),
716                        ) {
717                            self.remove_simplexes_unchecked(star.into_iter().collect());
718                            self.add_simplexes_unchecked(
719                                new_boundary_img
720                                    .into_iter()
721                                    .map(|(spx_img, label)| {
722                                        (nbd_affine_subspace.embed_simplex(&spx_img), label.clone())
723                                    })
724                                    .collect(),
725                            );
726                            self.add_simplexes_unchecked(
727                                new_star_img
728                                    .into_iter()
729                                    .map(|(spx_img, label)| {
730                                        (nbd_affine_subspace.embed_simplex(&spx_img), label.clone())
731                                    })
732                                    .collect(),
733                            );
734                            pts_todo.extend(link_points);
735                        }
736                    }
737                }
738            } else {
739                /*
740                pt is in the interior and nbd looks something like
741
742                          l
743                     +---------+
744                    / \       / \
745                l  /   \  s  /   \ l
746                  /  s  \   /  s  \
747                 /       \ /       \
748                +---------p---------+
749                 \       / \       /
750                  \  s  /   \  s  /
751                l  \   /  s  \   / l
752                    \ /       \ /
753                     +---------+
754                          l
755
756                where
757                    p = point
758                    s = star
759                    l = link
760                */
761
762                let boundary_img = link_img
763                    .iter()
764                    .filter(|spx| {
765                        let n = spx.n();
766                        let a = nbd_affine_subspace.embedded_space().affine_dimension();
767                        if n >= a {
768                            unreachable!()
769                        } else if n + 1 == a {
770                            true
771                        } else {
772                            debug_assert!(n + 1 < a);
773                            false
774                        }
775                    })
776                    .map(|spx| {
777                        OrientedSimplex::new_with_negative_point(
778                            nbd_affine_subspace.embedded_space(),
779                            spx.points().clone(),
780                            &pt_img,
781                        )
782                        .unwrap()
783                    })
784                    .collect();
785
786                if let Some(new_star_img) = simplify_in_region(
787                    nbd_affine_subspace.embedded_space(),
788                    boundary_img,
789                    star_img
790                        .iter()
791                        .map(|(spx, label)| (spx.clone(), label.clone()))
792                        .collect(),
793                ) {
794                    self.remove_simplexes_unchecked(star.into_iter().collect());
795                    self.add_simplexes_unchecked(
796                        new_star_img
797                            .into_iter()
798                            .map(|(spx_img, label)| {
799                                (nbd_affine_subspace.embed_simplex(&spx_img), label)
800                            })
801                            .collect(),
802                    );
803                    pts_todo.extend(link_points);
804                }
805            }
806        }
807
808        #[cfg(debug_assertions)]
809        self.check();
810
811        self
812    }
813
814    //remove simplexes and remove them from the inverse boundary of any others
815    //self may not be in a valid state after this operation
816    fn remove_simplexes_unchecked(&mut self, simplexes: Vec<Simplex<'f, FS>>) {
817        for spx in &simplexes {
818            for bdry_spx in spx.proper_sub_simplices_not_null() {
819                if let Some(info) = self.simplexes.get_mut(&bdry_spx) {
820                    info.inv_bdry.remove(spx);
821                }
822            }
823        }
824        for spx in &simplexes {
825            self.simplexes.remove(spx);
826        }
827    }
828
829    pub fn remove_simplexes(&mut self, simplexes: Vec<Simplex<'f, FS>>) {
830        self.remove_simplexes_unchecked(simplexes);
831        #[cfg(debug_assertions)]
832        self.check();
833    }
834
835    //add the given simplexes and add them to the inverse boundary map on anything on their boundaries
836    //must be added together to cover the case where there are mutual boundary relations
837    //self may not be in a valid state after this operation
838    fn add_simplexes_unchecked(&mut self, simplexes: Vec<(Simplex<'f, FS>, T)>) {
839        for (spx, label) in &simplexes {
840            self.simplexes.insert(
841                spx.clone(),
842                SCSpxInfo {
843                    inv_bdry: HashSet::new(),
844                    label: label.clone(),
845                },
846            );
847        }
848        for (spx, _) in simplexes {
849            for bdry_spx in spx.proper_sub_simplices_not_null() {
850                self.simplexes
851                    .get_mut(&bdry_spx)
852                    .unwrap()
853                    .inv_bdry
854                    .insert(spx.clone());
855            }
856        }
857    }
858
859    pub fn add_simplexes(&mut self, simplexes: Vec<(Simplex<'f, FS>, T)>) {
860        self.add_simplexes_unchecked(simplexes);
861        #[cfg(debug_assertions)]
862        self.check();
863    }
864}