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