algebraeon_geometry/simplexes/
simplicial_complex.rs

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