1use crate::data::section::Section;
10use crate::data::storage::Storage;
11use crate::mesh_error::MeshSieveError;
12use crate::topology::point::PointId;
13use crate::topology::sieve::{Orientation, OrientedSieve, Sieve};
14use std::collections::{HashMap, HashSet, VecDeque};
15use std::hash::Hash;
16use std::ops::AddAssign;
17
18pub type TopologyVersion = u64;
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23pub struct ClosureIndexKey {
24 pub cell: PointId,
26 pub section_version: u64,
28 pub topology_version: TopologyVersion,
30}
31
32#[derive(Clone, Debug, PartialEq, Eq)]
34pub enum ClosureOrder {
35 BreadthFirstDmpLex,
37 LexicographicTensor {
41 dims: Vec<usize>,
43 },
44 Custom(Vec<PointId>),
47}
48
49impl Default for ClosureOrder {
50 fn default() -> Self {
51 Self::BreadthFirstDmpLex
52 }
53}
54
55#[derive(Clone, Debug, PartialEq, Eq)]
57pub struct ClosurePointIndex<O> {
58 pub point: PointId,
60 pub orientation: O,
62 pub offset: usize,
64 pub len: usize,
66 pub permutation: Vec<usize>,
68}
69
70#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct ClosureIndex<O> {
73 pub key: ClosureIndexKey,
75 pub points: Vec<ClosurePointIndex<O>>,
77 pub len: usize,
79}
80
81impl<O> ClosureIndex<O> {
82 pub fn point_order(&self) -> impl Iterator<Item = PointId> + '_ {
84 self.points.iter().map(|entry| entry.point)
85 }
86}
87
88pub trait SectionSym<O> {
93 fn permutation(&self, point: PointId, orientation: O, dof_count: usize) -> Option<Vec<usize>>;
95}
96
97#[derive(Clone, Copy, Debug, Default)]
99pub struct IdentitySectionSym;
100
101impl<O: Copy> SectionSym<O> for IdentitySectionSym {
102 #[inline]
103 fn permutation(
104 &self,
105 _point: PointId,
106 _orientation: O,
107 _dof_count: usize,
108 ) -> Option<Vec<usize>> {
109 None
110 }
111}
112
113#[derive(Clone, Debug, Default)]
115pub struct TableSectionSym<O> {
116 by_point_orientation: HashMap<(PointId, O), Vec<usize>>,
117 by_orientation: HashMap<O, Vec<usize>>,
118}
119
120impl<O> TableSectionSym<O>
121where
122 O: Copy + Eq + Hash,
123{
124 pub fn new() -> Self {
126 Self {
127 by_point_orientation: HashMap::new(),
128 by_orientation: HashMap::new(),
129 }
130 }
131
132 pub fn insert_point(&mut self, point: PointId, orientation: O, permutation: Vec<usize>) {
134 self.by_point_orientation
135 .insert((point, orientation), permutation);
136 }
137
138 pub fn insert_orientation(&mut self, orientation: O, permutation: Vec<usize>) {
140 self.by_orientation.insert(orientation, permutation);
141 }
142}
143
144impl<O> SectionSym<O> for TableSectionSym<O>
145where
146 O: Copy + Eq + Hash,
147{
148 fn permutation(&self, point: PointId, orientation: O, _dof_count: usize) -> Option<Vec<usize>> {
149 self.by_point_orientation
150 .get(&(point, orientation))
151 .or_else(|| self.by_orientation.get(&orientation))
152 .cloned()
153 }
154}
155
156pub struct FnSectionSym<F>(pub F);
158
159impl<O, F> SectionSym<O> for FnSectionSym<F>
160where
161 F: Fn(PointId, O, usize) -> Option<Vec<usize>>,
162{
163 fn permutation(&self, point: PointId, orientation: O, dof_count: usize) -> Option<Vec<usize>> {
164 (self.0)(point, orientation, dof_count)
165 }
166}
167
168#[derive(Clone, Debug, Default)]
170pub struct ClosureIndexCache<O> {
171 entries: HashMap<ClosureIndexKey, ClosureIndex<O>>,
172}
173
174impl<O> ClosureIndexCache<O>
175where
176 O: Orientation + Eq + Hash,
177{
178 pub fn new() -> Self {
180 Self {
181 entries: HashMap::new(),
182 }
183 }
184
185 pub fn clear(&mut self) {
187 self.entries.clear();
188 }
189
190 pub fn get_or_build<T, V, Sct, Sym>(
192 &mut self,
193 topology: &T,
194 section: &Section<V, Sct>,
195 cell: PointId,
196 topology_version: TopologyVersion,
197 order: &ClosureOrder,
198 sym: &Sym,
199 ) -> Result<&ClosureIndex<O>, MeshSieveError>
200 where
201 T: OrientedSieve<Point = PointId, Orient = O>,
202 Sct: Storage<V>,
203 Sym: SectionSym<O>,
204 {
205 let key = ClosureIndexKey {
206 cell,
207 section_version: section.atlas().version(),
208 topology_version,
209 };
210 if let std::collections::hash_map::Entry::Vacant(e) = self.entries.entry(key) {
211 let index = build_closure_index(topology, section, cell, topology_version, order, sym)?;
212 e.insert(index);
213 }
214 Ok(self.entries.get(&key).expect("inserted or present"))
215 }
216}
217
218impl ClosureIndexCache<()> {
219 pub fn get_or_build_unoriented<T, V, Sct, Sym>(
221 &mut self,
222 topology: &T,
223 section: &Section<V, Sct>,
224 cell: PointId,
225 topology_version: TopologyVersion,
226 order: &ClosureOrder,
227 sym: &Sym,
228 ) -> Result<&ClosureIndex<()>, MeshSieveError>
229 where
230 T: Sieve<Point = PointId>,
231 Sct: Storage<V>,
232 Sym: SectionSym<()>,
233 {
234 let key = ClosureIndexKey {
235 cell,
236 section_version: section.atlas().version(),
237 topology_version,
238 };
239 if let std::collections::hash_map::Entry::Vacant(e) = self.entries.entry(key) {
240 let index = build_closure_index_unoriented(
241 topology,
242 section,
243 cell,
244 topology_version,
245 order,
246 sym,
247 )?;
248 e.insert(index);
249 }
250 Ok(self.entries.get(&key).expect("inserted or present"))
251 }
252}
253
254pub fn build_closure_index<T, V, Sct, O, Sym>(
256 topology: &T,
257 section: &Section<V, Sct>,
258 cell: PointId,
259 topology_version: TopologyVersion,
260 order: &ClosureOrder,
261 sym: &Sym,
262) -> Result<ClosureIndex<O>, MeshSieveError>
263where
264 T: OrientedSieve<Point = PointId, Orient = O>,
265 Sct: Storage<V>,
266 O: Orientation + Eq + Hash,
267 Sym: SectionSym<O>,
268{
269 let key = ClosureIndexKey {
270 cell,
271 section_version: section.atlas().version(),
272 topology_version,
273 };
274 let oriented_points = ordered_oriented_closure(topology, cell, order);
275 let mut offset = 0usize;
276 let mut points = Vec::with_capacity(oriented_points.len());
277 for (point, orientation) in oriented_points {
278 let len = match section.atlas().get(point) {
279 Some((_, len)) => len,
280 None => continue,
281 };
282 let permutation =
283 normalized_permutation(point, len, sym.permutation(point, orientation, len))?;
284 points.push(ClosurePointIndex {
285 point,
286 orientation,
287 offset,
288 len,
289 permutation,
290 });
291 offset += len;
292 }
293 Ok(ClosureIndex {
294 key,
295 points,
296 len: offset,
297 })
298}
299
300pub fn build_closure_index_unoriented<T, V, Sct, Sym>(
302 topology: &T,
303 section: &Section<V, Sct>,
304 cell: PointId,
305 topology_version: TopologyVersion,
306 order: &ClosureOrder,
307 sym: &Sym,
308) -> Result<ClosureIndex<()>, MeshSieveError>
309where
310 T: Sieve<Point = PointId>,
311 Sct: Storage<V>,
312 Sym: SectionSym<()>,
313{
314 let key = ClosureIndexKey {
315 cell,
316 section_version: section.atlas().version(),
317 topology_version,
318 };
319 let points_only = ordered_closure(topology, cell, order);
320 let mut offset = 0usize;
321 let mut points = Vec::with_capacity(points_only.len());
322 for point in points_only {
323 let len = match section.atlas().get(point) {
324 Some((_, len)) => len,
325 None => continue,
326 };
327 let permutation = normalized_permutation(point, len, sym.permutation(point, (), len))?;
328 points.push(ClosurePointIndex {
329 point,
330 orientation: (),
331 offset,
332 len,
333 permutation,
334 });
335 offset += len;
336 }
337 Ok(ClosureIndex {
338 key,
339 points,
340 len: offset,
341 })
342}
343
344pub fn get_closure<V, Sct, O>(
346 section: &Section<V, Sct>,
347 index: &ClosureIndex<O>,
348) -> Result<Vec<V>, MeshSieveError>
349where
350 V: Clone,
351 Sct: Storage<V>,
352{
353 validate_section_version(section, index)?;
354 let mut out = Vec::with_capacity(index.len);
355 for entry in &index.points {
356 let slice = section.try_restrict(entry.point)?;
357 for &src in &entry.permutation {
358 out.push(slice[src].clone());
359 }
360 }
361 Ok(out)
362}
363
364pub fn set_closure<V, Sct, O>(
366 section: &mut Section<V, Sct>,
367 index: &ClosureIndex<O>,
368 values: &[V],
369) -> Result<(), MeshSieveError>
370where
371 V: Clone,
372 Sct: Storage<V>,
373{
374 validate_section_version(section, index)?;
375 validate_closure_len(index, values.len())?;
376 for entry in &index.points {
377 let slice = section.try_restrict_mut(entry.point)?;
378 for (local_slot, §ion_slot) in entry.permutation.iter().enumerate() {
379 slice[section_slot] = values[entry.offset + local_slot].clone();
380 }
381 }
382 Ok(())
383}
384
385pub fn add_closure<V, Sct, O>(
387 section: &mut Section<V, Sct>,
388 index: &ClosureIndex<O>,
389 values: &[V],
390) -> Result<(), MeshSieveError>
391where
392 V: Clone + AddAssign<V>,
393 Sct: Storage<V>,
394{
395 validate_section_version(section, index)?;
396 validate_closure_len(index, values.len())?;
397 for entry in &index.points {
398 let slice = section.try_restrict_mut(entry.point)?;
399 for (local_slot, §ion_slot) in entry.permutation.iter().enumerate() {
400 slice[section_slot] += values[entry.offset + local_slot].clone();
401 }
402 }
403 Ok(())
404}
405
406pub fn get_closure_oriented<T, V, Sct, O, Sym>(
408 topology: &T,
409 section: &Section<V, Sct>,
410 cell: PointId,
411 topology_version: TopologyVersion,
412 order: &ClosureOrder,
413 sym: &Sym,
414) -> Result<Vec<V>, MeshSieveError>
415where
416 T: OrientedSieve<Point = PointId, Orient = O>,
417 V: Clone,
418 Sct: Storage<V>,
419 O: Orientation + Eq + Hash,
420 Sym: SectionSym<O>,
421{
422 let index = build_closure_index(topology, section, cell, topology_version, order, sym)?;
423 get_closure(section, &index)
424}
425
426fn validate_section_version<V, Sct, O>(
427 section: &Section<V, Sct>,
428 index: &ClosureIndex<O>,
429) -> Result<(), MeshSieveError>
430where
431 Sct: Storage<V>,
432{
433 let current = section.atlas().version();
434 if current != index.key.section_version {
435 return Err(MeshSieveError::AtlasPlanStale {
436 expected: index.key.section_version,
437 found: current,
438 });
439 }
440 Ok(())
441}
442
443fn validate_closure_len<O>(index: &ClosureIndex<O>, found: usize) -> Result<(), MeshSieveError> {
444 if index.len != found {
445 return Err(MeshSieveError::ScatterLengthMismatch {
446 expected: index.len,
447 found,
448 });
449 }
450 Ok(())
451}
452
453fn normalized_permutation(
454 point: PointId,
455 len: usize,
456 permutation: Option<Vec<usize>>,
457) -> Result<Vec<usize>, MeshSieveError> {
458 let perm = permutation.unwrap_or_else(|| (0..len).collect());
459 if perm.len() != len {
460 return Err(MeshSieveError::SliceLengthMismatch {
461 point,
462 expected: len,
463 found: perm.len(),
464 });
465 }
466 let mut seen = vec![false; len];
467 for &idx in &perm {
468 if idx >= len || seen[idx] {
469 return Err(MeshSieveError::InvalidPermutation(format!(
470 "invalid closure permutation for {point:?}: {perm:?}"
471 )));
472 }
473 seen[idx] = true;
474 }
475 Ok(perm)
476}
477
478fn ordered_closure<T>(topology: &T, cell: PointId, order: &ClosureOrder) -> Vec<PointId>
479where
480 T: Sieve<Point = PointId>,
481{
482 let bfs = bfs_closure(topology, cell);
483 apply_order(bfs, order)
484}
485
486fn ordered_oriented_closure<T, O>(
487 topology: &T,
488 cell: PointId,
489 order: &ClosureOrder,
490) -> Vec<(PointId, O)>
491where
492 T: OrientedSieve<Point = PointId, Orient = O>,
493 O: Orientation + Eq + Hash,
494{
495 let bfs = bfs_oriented_closure(topology, cell);
496 let ordered_points = apply_order(bfs.iter().map(|(point, _)| *point).collect(), order);
497 let orientations: HashMap<PointId, O> = bfs.into_iter().collect();
498 ordered_points
499 .into_iter()
500 .filter_map(|point| orientations.get(&point).copied().map(|o| (point, o)))
501 .collect()
502}
503
504fn apply_order(mut bfs: Vec<PointId>, order: &ClosureOrder) -> Vec<PointId> {
505 match order {
506 ClosureOrder::BreadthFirstDmpLex => bfs,
507 ClosureOrder::LexicographicTensor { .. } => {
508 if let Some((&cell, rest)) = bfs.split_first() {
509 let mut out = vec![cell];
510 let mut rest = rest.to_vec();
511 rest.sort_unstable();
512 out.extend(rest);
513 out
514 } else {
515 bfs
516 }
517 }
518 ClosureOrder::Custom(custom) => {
519 let bfs_set: HashSet<_> = bfs.iter().copied().collect();
520 let mut used = HashSet::new();
521 let mut out = Vec::with_capacity(bfs.len());
522 for &point in custom {
523 if bfs_set.contains(&point) && used.insert(point) {
524 out.push(point);
525 }
526 }
527 for point in bfs.drain(..) {
528 if used.insert(point) {
529 out.push(point);
530 }
531 }
532 out
533 }
534 }
535}
536
537fn bfs_closure<T>(topology: &T, cell: PointId) -> Vec<PointId>
538where
539 T: Sieve<Point = PointId>,
540{
541 let mut seen = HashSet::new();
542 let mut queue = VecDeque::from([cell]);
543 let mut out = Vec::new();
544 while let Some(point) = queue.pop_front() {
545 if !seen.insert(point) {
546 continue;
547 }
548 out.push(point);
549 for child in topology.cone_points(point) {
550 if !seen.contains(&child) {
551 queue.push_back(child);
552 }
553 }
554 }
555 out
556}
557
558fn bfs_oriented_closure<T, O>(topology: &T, cell: PointId) -> Vec<(PointId, O)>
559where
560 T: OrientedSieve<Point = PointId, Orient = O>,
561 O: Orientation + Eq + Hash,
562{
563 let mut seen = HashSet::new();
564 let mut queue = VecDeque::from([(cell, O::default())]);
565 let mut out = Vec::new();
566 while let Some((point, orientation)) = queue.pop_front() {
567 if !seen.insert(point) {
568 continue;
569 }
570 out.push((point, orientation));
571 for (child, edge_orientation) in topology.cone_o(point) {
572 if !seen.contains(&child) {
573 queue.push_back((child, O::compose(orientation, edge_orientation)));
574 }
575 }
576 }
577 out
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583 use crate::data::atlas::Atlas;
584 use crate::data::storage::VecStorage;
585 use crate::topology::orientation::Sign;
586 use crate::topology::sieve::{InMemoryOrientedSieve, InMemorySieve};
587
588 fn p(id: u64) -> PointId {
589 PointId::new(id).unwrap()
590 }
591
592 #[test]
593 fn get_set_add_unoriented_closure() {
594 let mut topo = InMemorySieve::<PointId, ()>::default();
595 topo.add_arrow(p(1), p(2), ());
596 topo.add_arrow(p(1), p(3), ());
597
598 let mut atlas = Atlas::default();
599 atlas.try_insert(p(1), 1).unwrap();
600 atlas.try_insert(p(2), 2).unwrap();
601 atlas.try_insert(p(3), 1).unwrap();
602 let mut section = Section::<i32, VecStorage<i32>>::new(atlas);
603 section.try_scatter_in_order(&[10, 20, 21, 30]).unwrap();
604
605 let index = build_closure_index_unoriented(
606 &topo,
607 §ion,
608 p(1),
609 0,
610 &ClosureOrder::BreadthFirstDmpLex,
611 &IdentitySectionSym,
612 )
613 .unwrap();
614 assert_eq!(get_closure(§ion, &index).unwrap(), vec![10, 20, 21, 30]);
615
616 set_closure(&mut section, &index, &[1, 2, 3, 4]).unwrap();
617 add_closure(&mut section, &index, &[10, 20, 30, 40]).unwrap();
618 assert_eq!(get_closure(§ion, &index).unwrap(), vec![11, 22, 33, 44]);
619
620 let mut cache = ClosureIndexCache::new();
621 let cached = cache
622 .get_or_build_unoriented(
623 &topo,
624 §ion,
625 p(1),
626 0,
627 &ClosureOrder::BreadthFirstDmpLex,
628 &IdentitySectionSym,
629 )
630 .unwrap();
631 assert_eq!(cached.key.cell, p(1));
632 }
633
634 #[test]
635 fn oriented_symmetry_permutates_point_dofs() {
636 let mut topo = InMemoryOrientedSieve::<PointId, (), Sign>::default();
637 topo.add_arrow_o(p(1), p(2), (), Sign(true));
638
639 let mut atlas = Atlas::default();
640 atlas.try_insert(p(2), 3).unwrap();
641 let mut section = Section::<i32, VecStorage<i32>>::new(atlas);
642 section.try_scatter_in_order(&[1, 2, 3]).unwrap();
643
644 let mut sym = TableSectionSym::new();
645 sym.insert_orientation(Sign(true), vec![2, 1, 0]);
646 let index = build_closure_index(
647 &topo,
648 §ion,
649 p(1),
650 7,
651 &ClosureOrder::BreadthFirstDmpLex,
652 &sym,
653 )
654 .unwrap();
655 assert_eq!(index.key.topology_version, 7);
656 assert_eq!(get_closure(§ion, &index).unwrap(), vec![3, 2, 1]);
657 }
658
659 #[test]
660 fn custom_order_appends_unlisted_points() {
661 let mut topo = InMemorySieve::<PointId, ()>::default();
662 topo.add_arrow(p(1), p(2), ());
663 topo.add_arrow(p(1), p(3), ());
664 let ordered = ordered_closure(&topo, p(1), &ClosureOrder::Custom(vec![p(3)]));
665 assert_eq!(ordered, vec![p(3), p(1), p(2)]);
666 }
667}