1use super::sieve::InMemorySieve;
16use crate::mesh_error::MeshSieveError;
17use crate::topology::_debug_invariants::{debug_invariants, inv_assert};
18use crate::topology::bounds::{PayloadLike, PointLike};
19use crate::topology::cache::InvalidateCache;
20use std::collections::HashMap;
21use std::sync::Arc;
22
23pub trait Stack {
36 type Point: Copy + Eq + std::hash::Hash;
38 type CapPt: Copy + Eq + std::hash::Hash;
40 type VerticalPayload: Clone;
42 type BaseSieve: crate::topology::sieve::sieve_trait::Sieve<Point = Self::Point>;
44 type CapSieve: crate::topology::sieve::sieve_trait::Sieve<Point = Self::CapPt>;
46
47 fn lift<'a>(
51 &'a self,
52 p: Self::Point,
53 ) -> Box<dyn Iterator<Item = (Self::CapPt, Self::VerticalPayload)> + 'a>;
54
55 fn drop<'a>(
58 &'a self,
59 q: Self::CapPt,
60 ) -> Box<dyn Iterator<Item = (Self::Point, Self::VerticalPayload)> + 'a>;
61
62 fn add_arrow(
65 &mut self,
66 base: Self::Point,
67 cap: Self::CapPt,
68 pay: Self::VerticalPayload,
69 ) -> Result<(), MeshSieveError>;
70
71 fn remove_arrow(
73 &mut self,
74 base: Self::Point,
75 cap: Self::CapPt,
76 ) -> Result<Option<Self::VerticalPayload>, MeshSieveError>;
77
78 fn base(&self) -> &Self::BaseSieve;
84 fn cap(&self) -> &Self::CapSieve;
89
90 fn base_mut(&mut self) -> Result<&mut Self::BaseSieve, MeshSieveError>;
92 fn cap_mut(&mut self) -> Result<&mut Self::CapSieve, MeshSieveError>;
94}
95
96#[derive(Clone, Debug)]
104pub struct InMemoryStack<B: PointLike, C: PointLike, V = (), PB = (), PC = ()> {
105 base: InMemorySieve<B, PB>,
107 cap: InMemorySieve<C, PC>,
109 pub up: HashMap<B, Vec<(C, V)>>,
111 pub down: HashMap<C, Vec<(B, V)>>,
113}
114
115impl<B, C, V, PB, PC> InMemoryStack<B, C, V, PB, PC>
116where
117 B: PointLike,
118 C: PointLike,
119{
120 pub fn new() -> Self {
122 Self {
123 base: InMemorySieve::default(),
124 cap: InMemorySieve::default(),
125 up: HashMap::new(),
126 down: HashMap::new(),
127 }
128 }
129
130 #[inline]
132 pub fn reserve_lift(&mut self, base: B, additional: usize) {
133 self.up.entry(base).or_default().reserve(additional);
134 }
135
136 #[inline]
138 pub fn reserve_drop(&mut self, cap: C, additional: usize) {
139 self.down.entry(cap).or_default().reserve(additional);
140 }
141
142 pub fn shrink_to_fit(&mut self) {
144 for v in self.up.values_mut() {
145 v.shrink_to_fit();
146 }
147 for v in self.down.values_mut() {
148 v.shrink_to_fit();
149 }
150 }
151}
152
153impl<B, C, V, PB, PC> Default for InMemoryStack<B, C, V, PB, PC>
155where
156 B: PointLike,
157 C: PointLike,
158 V: PayloadLike,
159{
160 fn default() -> Self {
161 Self {
162 base: InMemorySieve::default(),
163 cap: InMemorySieve::default(),
164 up: HashMap::new(),
165 down: HashMap::new(),
166 }
167 }
168}
169
170impl<B, C, V, PB, PC> Stack for InMemoryStack<B, C, V, PB, PC>
171where
172 B: PointLike,
173 C: PointLike,
174 V: PayloadLike,
175 PB: PayloadLike,
176 PC: PayloadLike,
177{
178 type Point = B;
179 type CapPt = C;
180 type VerticalPayload = V;
181 type BaseSieve = InMemorySieve<B, PB>;
182 type CapSieve = InMemorySieve<C, PC>;
183
184 fn lift<'a>(&'a self, p: B) -> Box<dyn Iterator<Item = (C, V)> + 'a> {
185 match self.up.get(&p) {
186 Some(vec) => Box::new(vec.iter().cloned()),
187 None => Box::new(std::iter::empty()),
188 }
189 }
190
191 fn drop<'a>(&'a self, q: C) -> Box<dyn Iterator<Item = (B, V)> + 'a> {
192 match self.down.get(&q) {
193 Some(vec) => Box::new(vec.iter().cloned()),
194 None => Box::new(std::iter::empty()),
195 }
196 }
197
198 fn add_arrow(&mut self, base: B, cap: C, pay: V) -> Result<(), MeshSieveError> {
199 if !self.base.contains_point(base) {
200 inv_assert!(false, "stack add_arrow: base point missing: {base:?}");
201 return Err(MeshSieveError::StackMissingPoint {
202 role: "base",
203 point: format!("{base:?}"),
204 });
205 }
206 if !self.cap.contains_point(cap) {
207 inv_assert!(false, "stack add_arrow: cap point missing: {cap:?}");
208 return Err(MeshSieveError::StackMissingPoint {
209 role: "cap",
210 point: format!("{cap:?}"),
211 });
212 }
213
214 let ups = self.up.entry(base).or_default();
215 if let Some((_, existing)) = ups.iter().find(|(c, _)| *c == cap) {
216 if *existing == pay {
217 return Ok(());
218 }
219 return Err(MeshSieveError::RelationConflict {
220 src: format!("{base:?}"),
221 dst: format!("{cap:?}"),
222 kind: crate::mesh_error::RelationConflictKind::Payload,
223 });
224 }
225 if let Some((_, existing)) = self
226 .down
227 .get(&cap)
228 .and_then(|v| v.iter().find(|(b, _)| *b == base))
229 {
230 if *existing != pay {
231 return Err(MeshSieveError::RelationConflict {
232 src: format!("{base:?}"),
233 dst: format!("{cap:?}"),
234 kind: crate::mesh_error::RelationConflictKind::Payload,
235 });
236 }
237 }
239 ups.push((cap, pay.clone()));
240
241 let downs = self.down.entry(cap).or_default();
242 if let Some(slot) = downs.iter_mut().find(|(b, _)| *b == base) {
243 slot.1 = pay.clone();
244 } else {
245 downs.push((base, pay.clone()));
246 }
247
248 InvalidateCache::invalidate_cache(&mut self.base);
249 InvalidateCache::invalidate_cache(&mut self.cap);
250 debug_invariants!(self);
251 Ok(())
252 }
253
254 fn remove_arrow(&mut self, base: B, cap: C) -> Result<Option<V>, MeshSieveError> {
255 if !self.base.contains_point(base) {
256 inv_assert!(false, "stack remove_arrow: base point missing: {base:?}");
257 }
258 if !self.cap.contains_point(cap) {
259 inv_assert!(false, "stack remove_arrow: cap point missing: {cap:?}");
260 }
261
262 let mut removed = None;
263
264 let remove_up = if let Some(vec) = self.up.get_mut(&base) {
265 if let Some(pos) = vec.iter().position(|(c, _)| *c == cap) {
266 removed = Some(vec.remove(pos).1);
267 }
268 vec.is_empty()
269 } else {
270 false
271 };
272 if remove_up {
273 self.up.remove(&base);
274 }
275
276 let remove_down = if let Some(vec) = self.down.get_mut(&cap) {
277 if let Some(pos) = vec.iter().position(|(b, _)| *b == base) {
278 vec.remove(pos);
279 }
280 vec.is_empty()
281 } else {
282 false
283 };
284 if remove_down {
285 self.down.remove(&cap);
286 }
287 InvalidateCache::invalidate_cache(&mut self.base);
288 InvalidateCache::invalidate_cache(&mut self.cap);
289 debug_invariants!(self);
290 Ok(removed)
291 }
292
293 fn base(&self) -> &Self::BaseSieve {
294 &self.base
295 }
296 fn cap(&self) -> &Self::CapSieve {
297 &self.cap
298 }
299 fn base_mut(&mut self) -> Result<&mut Self::BaseSieve, MeshSieveError> {
300 Ok(&mut self.base)
301 }
302 fn cap_mut(&mut self) -> Result<&mut Self::CapSieve, MeshSieveError> {
303 Ok(&mut self.cap)
304 }
305}
306
307impl<B, C, T, PB, PC> InMemoryStack<B, C, Arc<T>, PB, PC>
308where
309 B: PointLike,
310 C: PointLike,
311 T: PartialEq,
312 PB: PayloadLike,
313 PC: PayloadLike,
314{
315 #[inline]
316 pub fn add_arrow_val(&mut self, base: B, cap: C, payload: T) -> Result<(), MeshSieveError> {
317 self.add_arrow(base, cap, Arc::new(payload))
318 }
319}
320
321impl<B, C, V, PB, PC> InMemoryStack<B, C, V, PB, PC>
323where
324 B: PointLike,
325 C: PointLike,
326 V: PayloadLike,
327{
328 pub fn base_points(&self) -> impl Iterator<Item = B> + '_ {
329 self.up.keys().copied()
330 }
331 pub fn cap_points(&self) -> impl Iterator<Item = C> + '_ {
332 self.down.keys().copied()
333 }
334 #[cfg(any(debug_assertions, feature = "strict-invariants"))]
335 pub(crate) fn debug_assert_invariants(&self) {
336 use std::collections::HashSet;
337
338 for (b, v) in &self.up {
339 let mut seen = HashSet::new();
340 for (c, _) in v {
341 crate::topology::_debug_invariants::inv_assert!(
342 seen.insert(*c),
343 "duplicate vertical arrow base={b:?} cap={c:?}"
344 );
345 }
346 }
347 for (c, v) in &self.down {
348 let mut seen = HashSet::new();
349 for (b, _) in v {
350 crate::topology::_debug_invariants::inv_assert!(
351 seen.insert(*b),
352 "duplicate vertical arrow cap={c:?} base={b:?}"
353 );
354 }
355 }
356
357 let out_total: usize = self.up.values().map(|v| v.len()).sum();
358 let in_total: usize = self.down.values().map(|v| v.len()).sum();
359 crate::topology::_debug_invariants::inv_assert_eq!(
360 out_total,
361 in_total,
362 "stack up/down totals differ",
363 );
364
365 for (b, ups) in &self.up {
366 crate::topology::_debug_invariants::inv_assert!(
367 self.base.adjacency_out.contains_key(b) || self.base.adjacency_in.contains_key(b),
368 "vertical base point {b:?} not present in base sieve"
369 );
370 for (c, _) in ups {
371 crate::topology::_debug_invariants::inv_assert!(
372 self.cap.adjacency_out.contains_key(c) || self.cap.adjacency_in.contains_key(c),
373 "vertical cap point {c:?} not present in cap sieve"
374 );
375 let has = self
376 .down
377 .get(c)
378 .is_some_and(|v| v.iter().any(|(bb, _)| *bb == *b));
379 crate::topology::_debug_invariants::inv_assert!(
380 has,
381 "stack mirror missing: up {b:?}->{c:?} has no down",
382 );
383 }
384 }
385 for (c, downs) in &self.down {
386 crate::topology::_debug_invariants::inv_assert!(
387 self.cap.adjacency_out.contains_key(c) || self.cap.adjacency_in.contains_key(c),
388 "vertical cap point {c:?} not present in cap sieve"
389 );
390 for (b, _) in downs {
391 crate::topology::_debug_invariants::inv_assert!(
392 self.base.adjacency_out.contains_key(b)
393 || self.base.adjacency_in.contains_key(b),
394 "vertical base point {b:?} not present in base sieve"
395 );
396 let has = self
397 .up
398 .get(b)
399 .is_some_and(|v| v.iter().any(|(cc, _)| *cc == *c));
400 crate::topology::_debug_invariants::inv_assert!(
401 has,
402 "stack mirror missing: down {b:?}->{c:?} has no up",
403 );
404 }
405 }
406 }
407}
408
409pub struct ComposedStack<'a, S1, S2, F, VO>
436where
437 S1: Stack,
438 S2: Stack<Point = S1::CapPt>,
439 F: Fn(&S1::VerticalPayload, &S2::VerticalPayload) -> VO,
440 VO: Clone,
441{
442 pub lower: &'a S1,
443 pub upper: &'a S2,
444 pub compose_payload: F,
445 _phantom: core::marker::PhantomData<VO>,
446}
447
448impl<'a, S1, S2, F, VO> ComposedStack<'a, S1, S2, F, VO>
449where
450 S1: Stack,
451 S2: Stack<Point = S1::CapPt>,
452 F: Fn(&S1::VerticalPayload, &S2::VerticalPayload) -> VO,
453 VO: Clone,
454{
455 pub fn new(lower: &'a S1, upper: &'a S2, compose_payload: F) -> Self {
456 Self {
457 lower,
458 upper,
459 compose_payload,
460 _phantom: core::marker::PhantomData,
461 }
462 }
463}
464
465impl<'a, S1, S2, F, VO> Stack for ComposedStack<'a, S1, S2, F, VO>
466where
467 S1: Stack,
468 S2: Stack<Point = S1::CapPt>,
469 F: Fn(&S1::VerticalPayload, &S2::VerticalPayload) -> VO + Sync + Send,
470 VO: Clone,
471{
472 type Point = S1::Point;
473 type CapPt = S2::CapPt;
474 type VerticalPayload = VO;
475 type BaseSieve = S1::BaseSieve;
476 type CapSieve = S2::CapSieve;
477
478 fn lift<'b>(&'b self, p: S1::Point) -> Box<dyn Iterator<Item = (S2::CapPt, VO)> + 'b> {
479 let lower = self.lower.lift(p);
480 let iter = lower.flat_map(move |(mid, pay1)| {
481 self.upper
482 .lift(mid)
483 .map(move |(cap, pay2)| (cap, (self.compose_payload)(&pay1, &pay2)))
484 });
485 Box::new(iter)
486 }
487
488 fn drop<'b>(&'b self, q: S2::CapPt) -> Box<dyn Iterator<Item = (S1::Point, VO)> + 'b> {
489 let upper = self.upper.drop(q);
490 let iter = upper.flat_map(move |(mid, pay2)| {
491 self.lower
492 .drop(mid)
493 .map(move |(base, pay1)| (base, (self.compose_payload)(&pay1, &pay2)))
494 });
495 Box::new(iter)
496 }
497
498 fn add_arrow(
499 &mut self,
500 _base: S1::Point,
501 _cap: S2::CapPt,
502 _pay: VO,
503 ) -> Result<(), MeshSieveError> {
504 Err(MeshSieveError::UnsupportedStackOperation(
505 "add_arrow on ComposedStack",
506 ))
507 }
508 fn remove_arrow(
509 &mut self,
510 _base: S1::Point,
511 _cap: S2::CapPt,
512 ) -> Result<Option<VO>, MeshSieveError> {
513 Err(MeshSieveError::UnsupportedStackOperation(
514 "remove_arrow on ComposedStack",
515 ))
516 }
517 fn base_mut(&mut self) -> Result<&mut Self::BaseSieve, MeshSieveError> {
518 Err(MeshSieveError::UnsupportedStackOperation(
519 "base_mut on ComposedStack",
520 ))
521 }
522 fn cap_mut(&mut self) -> Result<&mut Self::CapSieve, MeshSieveError> {
523 Err(MeshSieveError::UnsupportedStackOperation(
524 "cap_mut on ComposedStack",
525 ))
526 }
527 fn base(&self) -> &Self::BaseSieve {
532 panic!("base() is not supported on ComposedStack")
533 }
534 fn cap(&self) -> &Self::CapSieve {
539 panic!("cap() is not supported on ComposedStack")
540 }
541}
542
543#[test]
544fn composed_stack_no_leak() {
545 }
547
548impl<B, C, V, PB, PC> InvalidateCache for InMemoryStack<B, C, V, PB, PC>
549where
550 B: PointLike,
551 C: PointLike,
552 V: PayloadLike,
553{
554 fn invalidate_cache(&mut self) {
555 }
557}
558
559impl<B, C, V, PB, PC> InMemoryStack<B, C, V, PB, PC>
560where
561 B: PointLike,
562 C: PointLike,
563 PB: PayloadLike,
564 PC: PayloadLike,
565{
566 #[inline]
567 pub fn invalidate_base_and_cap(&mut self) {
568 self.base.invalidate_cache();
569 self.cap.invalidate_cache();
570 }
571}
572
573#[cfg(test)]
574mod tests {
575 use super::*;
576 use crate::topology::sieve::sieve_trait::Sieve;
577 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
578 struct V(u32);
579 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
580 struct Dof(u32);
581
582 #[test]
583 fn stack_payloads_decoupled() {
584 type S = InMemoryStack<u32, u32, bool, u8, String>;
586 let mut st = S::default();
587 st.base.add_arrow(1, 2, 7u8);
588 st.cap.add_arrow(10, 20, "x".to_string());
589 st.add_arrow(1, 10, true).unwrap();
590 let v: Vec<_> = st.lift(1).collect();
591 assert_eq!(v, vec![(10, true)]);
592 }
593 #[test]
594 fn add_and_lift_drop() {
595 let mut stack = InMemoryStack::<V, Dof, i32>::new();
596 stack.base.add_arrow(V(1), V(1), ());
597 stack.cap.add_arrow(Dof(10), Dof(10), ());
598 stack.cap.add_arrow(Dof(11), Dof(11), ());
599 let _ = stack.add_arrow(V(1), Dof(10), 42);
600 let _ = stack.add_arrow(V(1), Dof(11), 43);
601 let mut lifted: Vec<_> = stack.lift(V(1)).collect();
602 lifted.sort_by_key(|(dof, _)| dof.0);
603 assert_eq!(lifted, vec![(Dof(10), 42), (Dof(11), 43)]);
604 let dropped: Vec<_> = stack.drop(Dof(10)).collect();
605 assert_eq!(dropped, vec![(V(1), 42)]);
606 }
607 #[test]
608 fn remove_arrow_behavior() {
609 let mut stack = InMemoryStack::<V, Dof, i32>::new();
610 stack.base.add_arrow(V(1), V(1), ());
611 stack.cap.add_arrow(Dof(10), Dof(10), ());
612 let _ = stack.add_arrow(V(1), Dof(10), 99);
613 assert_eq!(stack.remove_arrow(V(1), Dof(10)).unwrap(), Some(99));
614 assert_eq!(stack.remove_arrow(V(1), Dof(10)).unwrap(), None);
616 assert!(stack.lift(V(1)).next().is_none());
618 assert!(stack.drop(Dof(10)).next().is_none());
619 }
620
621 #[test]
622 fn composed_stack_lift_drop() {
623 use super::*;
624 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
625 struct A(u32);
626 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
627 struct B(u32);
628 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
629 struct C(u32);
630 let mut s1 = InMemoryStack::<A, B, i32>::new();
632 s1.base.add_arrow(A(1), A(1), ());
633 s1.cap.add_arrow(B(10), B(10), ());
634 s1.cap.add_arrow(B(11), B(11), ());
635 let _ = s1.add_arrow(A(1), B(10), 2);
636 let _ = s1.add_arrow(A(1), B(11), 3);
637 let mut s2 = InMemoryStack::<B, C, i32>::new();
639 s2.base.add_arrow(B(10), B(10), ());
640 s2.base.add_arrow(B(11), B(11), ());
641 s2.cap.add_arrow(C(100), C(100), ());
642 s2.cap.add_arrow(C(101), C(101), ());
643 let _ = s2.add_arrow(B(10), C(100), 5);
644 let _ = s2.add_arrow(B(11), C(101), 7);
645 let composed = ComposedStack::new(&s1, &s2, |p1, p2| p1 + p2);
647 let mut lifted: Vec<_> = composed.lift(A(1)).collect();
648 lifted.sort_by_key(|(c, _)| (*c).0);
649 assert_eq!(lifted, vec![(C(100), 7), (C(101), 10)]);
650 let dropped: Vec<_> = composed.drop(C(100)).collect();
651 assert_eq!(dropped, vec![(A(1), 7)]);
652 }
653
654 #[test]
655 fn vertical_edits_invalidate_horizontal_caches() {
656 let mut s = InMemoryStack::<u32, u32, (), (), ()>::default();
657 s.base.add_arrow(1, 2, ());
658 s.cap.add_arrow(10, 20, ());
659 assert!(s.base.strata.get().is_none());
660 assert!(s.cap.strata.get().is_none());
661 let _ = s.base.chart_points();
662 let _ = s.cap.chart_points();
663 assert!(s.base.strata.get().is_some());
664 assert!(s.cap.strata.get().is_some());
665 s.add_arrow(1, 10, ()).unwrap();
666 assert!(s.base.strata.get().is_none());
667 assert!(s.cap.strata.get().is_none());
668 }
669
670 #[test]
671 fn base_and_cap_points_reflect_maps() {
672 let mut s = InMemoryStack::<u32, u32, ()>::new();
673 s.base.add_arrow(1, 1, ());
674 s.cap.add_arrow(10, 10, ());
675 assert!(s.base_points().next().is_none());
677 assert!(s.cap_points().next().is_none());
678 let _ = s.add_arrow(1, 10, ());
680 let bases: Vec<_> = s.base_points().collect();
681 let caps: Vec<_> = s.cap_points().collect();
682 assert_eq!(bases, vec![1]);
683 assert_eq!(caps, vec![10]);
684 }
685
686 #[test]
687 fn new_and_default_empty() {
688 let s1 = InMemoryStack::<u8, u8, u8>::new();
689 let s2: InMemoryStack<u8, u8, u8> = Default::default();
690 assert_eq!(s1.base_points().count(), 0);
691 assert_eq!(s2.cap_points().count(), 0);
692 }
693
694 #[test]
695 fn composed_stack_add_arrow_error() {
696 let s1 = InMemoryStack::<u8, u8, u8>::new();
697 let s2 = InMemoryStack::<u8, u8, u8>::new();
698 let mut cs = ComposedStack::new(&s1, &s2, |a, _b| *a);
699 let err = cs.add_arrow(0, 0, 0).unwrap_err();
700 assert_eq!(
701 err.to_string(),
702 MeshSieveError::UnsupportedStackOperation("add_arrow on ComposedStack").to_string()
703 );
704 }
705 #[test]
706 fn composed_stack_remove_arrow_error() {
707 let s1 = InMemoryStack::<u8, u8, u8>::new();
708 let s2 = InMemoryStack::<u8, u8, u8>::new();
709 let mut cs = ComposedStack::new(&s1, &s2, |a, _b| *a);
710 let err = cs.remove_arrow(0, 0).unwrap_err();
711 assert_eq!(
712 err.to_string(),
713 MeshSieveError::UnsupportedStackOperation("remove_arrow on ComposedStack").to_string()
714 );
715 }
716 #[test]
717 fn composed_stack_base_mut_error() {
718 let s1 = InMemoryStack::<u8, u8, u8>::new();
719 let s2 = InMemoryStack::<u8, u8, u8>::new();
720 let mut cs = ComposedStack::new(&s1, &s2, |a, _b| *a);
721 let err = cs.base_mut().unwrap_err();
722 assert_eq!(
723 err.to_string(),
724 MeshSieveError::UnsupportedStackOperation("base_mut on ComposedStack").to_string()
725 );
726 }
727 #[test]
728 fn composed_stack_cap_mut_error() {
729 let s1 = InMemoryStack::<u8, u8, u8>::new();
730 let s2 = InMemoryStack::<u8, u8, u8>::new();
731 let mut cs = ComposedStack::new(&s1, &s2, |a, _b| *a);
732 let err = cs.cap_mut().unwrap_err();
733 assert_eq!(
734 err.to_string(),
735 MeshSieveError::UnsupportedStackOperation("cap_mut on ComposedStack").to_string()
736 );
737 }
738
739 #[test]
740 fn remove_nonexistent_returns_none() {
741 use crate::topology::sieve::MutableSieve;
742 let mut s = InMemoryStack::<u32, u32, ()>::new();
743 MutableSieve::add_base_point(s.base_mut().unwrap(), 5);
744 MutableSieve::add_cap_point(s.cap_mut().unwrap(), 50);
745 assert_eq!(s.remove_arrow(5, 50).unwrap(), None);
746 }
747
748 #[test]
749 fn remove_arrow_cleans_empty_maps() {
750 let mut s = InMemoryStack::<u32, u32, ()>::new();
751 s.base.add_arrow(1, 1, ());
752 s.cap.add_arrow(10, 10, ());
753 let _ = s.add_arrow(1, 10, ());
754 assert_eq!(s.remove_arrow(1, 10).unwrap(), Some(()));
756 assert!(s.base_points().next().is_none());
757 assert!(s.cap_points().next().is_none());
758 }
759
760 #[test]
761 fn invalidate_cache_noop() {
762 let mut s = InMemoryStack::<u32, u32, i32>::new();
763 s.base.add_arrow(1, 2, ());
764 s.cap.add_arrow(10, 20, ());
765 let _ = s.base.chart_points();
766 let _ = s.cap.chart_points();
767 assert!(s.base.strata.get().is_some());
768 assert!(s.cap.strata.get().is_some());
769 s.invalidate_cache();
770 assert!(s.base.strata.get().is_some());
771 assert!(s.cap.strata.get().is_some());
772 }
773
774 #[test]
775 fn lift_drop_empty_iter() {
776 let s = InMemoryStack::<u8, u8, ()>::new();
777 assert!(s.lift(0).next().is_none());
778 assert!(s.drop(0).next().is_none());
779 }
780
781 #[test]
782 fn stack_add_arrow_missing_base_rejected() {
783 use crate::topology::sieve::MutableSieve;
784 let mut st = InMemoryStack::<u32, u32, ()>::new();
785 MutableSieve::add_cap_point(st.cap_mut().unwrap(), 20);
786 let res =
787 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| st.add_arrow(10, 20, ())));
788 match res {
789 Ok(Err(e)) => assert!(format!("{e}").contains("base")),
790 Err(_) => (), Ok(Ok(_)) => panic!("expected failure"),
792 }
793 }
794
795 #[test]
796 fn stack_add_arrow_ok_when_points_exist() {
797 use crate::topology::sieve::MutableSieve;
798 let mut st = InMemoryStack::<u32, u32, ()>::new();
799 MutableSieve::add_base_point(st.base_mut().unwrap(), 10);
800 MutableSieve::add_cap_point(st.cap_mut().unwrap(), 20);
801 st.add_arrow(10, 20, ()).unwrap();
802 }
803
804 #[cfg(any(debug_assertions, feature = "strict-invariants"))]
805 #[test]
806 #[should_panic]
807 fn strict_build_panics_on_missing_cap() {
808 use crate::topology::sieve::MutableSieve;
809 let mut st = InMemoryStack::<u32, u32, ()>::new();
810 MutableSieve::add_base_point(st.base_mut().unwrap(), 10);
811 let _ = st.add_arrow(10, 20, ());
812 }
813
814 #[test]
832 fn stack_vertical_arrows_are_correct() {
833 let mut s = InMemoryStack::<u32, u32, i32>::new();
834 s.base.add_arrow(2, 2, ());
835 s.cap.add_arrow(20, 20, ());
836 let _ = s.add_arrow(2, 20, 5);
837 let lifted: Vec<_> = s.lift(2).collect();
839 assert_eq!(lifted, vec![(20, 5)]);
840 let dropped: Vec<_> = s.drop(20).collect();
841 assert_eq!(dropped, vec![(2, 5)]);
842 }
843}