Skip to main content

mesh_sieve/data/refine/
helpers.rs

1//! Helpers for pulling per-point slices out along a Sieve.
2//! (Previously lived in section.rs)
3//!
4//! This module provides utility functions for extracting slices of data
5//! associated with points in a mesh, following closure or star traversals
6//! of a [`Sieve`]. It also provides a read-only wrapper for section data.
7//!
8//! The legacy infallible helpers and [`Map`] adapter are gated behind the
9//! `map-adapter` feature (off by default). Prefer the fallible [`FallibleMap`]
10//! trait and `try_*` helpers.
11
12use crate::data::section::FallibleMap;
13#[cfg(feature = "map-adapter")]
14use crate::data::section::Map;
15use crate::data::storage::{Storage, VecStorage};
16use crate::mesh_error::MeshSieveError;
17use crate::topology::point::PointId;
18use crate::topology::sieve::Sieve;
19use core::marker::PhantomData;
20
21#[cfg(feature = "map-adapter")]
22#[cfg_attr(docsrs, doc(cfg(feature = "map-adapter")))]
23/// Restrict a map along the closure of the given seed points.
24///
25/// Returns an iterator over `(PointId, &[V])` for all points in the closure.
26///
27/// # Migration
28/// Prefer [`try_restrict_closure`] with [`FallibleMap`] for robust error handling;
29/// this helper panics if a point is missing.
30pub fn restrict_closure<'s, M, V: 's>(
31    sieve: &'s impl Sieve<Point = PointId>,
32    map: &'s M,
33    seeds: impl IntoIterator<Item = PointId>,
34) -> impl Iterator<Item = (PointId, &'s [V])> + 's
35where
36    M: Map<V> + 's,
37{
38    sieve.closure(seeds).map(move |p| (p, map.get(p)))
39}
40
41#[cfg(feature = "map-adapter")]
42#[cfg_attr(docsrs, doc(cfg(feature = "map-adapter")))]
43/// Restrict a map along the star of the given seed points.
44///
45/// Returns an iterator over `(PointId, &[V])` for all points in the star.
46///
47/// # Migration
48/// Prefer [`try_restrict_star`] with [`FallibleMap`] for robust error handling;
49/// this helper panics if a point is missing.
50pub fn restrict_star<'s, M, V: 's>(
51    sieve: &'s impl Sieve<Point = PointId>,
52    map: &'s M,
53    seeds: impl IntoIterator<Item = PointId>,
54) -> impl Iterator<Item = (PointId, &'s [V])> + 's
55where
56    M: Map<V> + 's,
57{
58    sieve.star(seeds).map(move |p| (p, map.get(p)))
59}
60
61#[cfg(feature = "map-adapter")]
62#[cfg_attr(docsrs, doc(cfg(feature = "map-adapter")))]
63/// Restrict a map along the closure of the given seed points, collecting results into a vector.
64///
65/// # Migration
66/// Prefer [`try_restrict_closure_vec`] with [`FallibleMap`] for robust error handling;
67/// this helper panics if a point is missing.
68pub fn restrict_closure_vec<'s, M, V: 's>(
69    sieve: &'s impl Sieve<Point = PointId>,
70    map: &'s M,
71    seeds: impl IntoIterator<Item = PointId>,
72) -> Vec<(PointId, &'s [V])>
73where
74    M: Map<V> + 's,
75{
76    restrict_closure(sieve, map, seeds).collect()
77}
78
79#[cfg(feature = "map-adapter")]
80#[cfg_attr(docsrs, doc(cfg(feature = "map-adapter")))]
81/// Restrict a map along the star of the given seed points, collecting results into a vector.
82///
83/// # Migration
84/// Prefer [`try_restrict_star_vec`] with [`FallibleMap`] for robust error handling;
85/// this helper panics if a point is missing.
86pub fn restrict_star_vec<'s, M, V: 's>(
87    sieve: &'s impl Sieve<Point = PointId>,
88    map: &'s M,
89    seeds: impl IntoIterator<Item = PointId>,
90) -> Vec<(PointId, &'s [V])>
91where
92    M: Map<V> + 's,
93{
94    restrict_star(sieve, map, seeds).collect()
95}
96
97/// Restrict a map along the closure of the given seed points, propagating errors.
98///
99/// # Errors
100/// Returns [`MeshSieveError`] if any point is missing in the underlying map.
101///
102/// # Complexity
103/// **O(n)** in the size of the closure.
104pub fn try_restrict_closure<'s, M, V: 's>(
105    sieve: &'s impl Sieve<Point = PointId>,
106    map: &'s M,
107    seeds: impl IntoIterator<Item = PointId>,
108) -> impl Iterator<Item = Result<(PointId, &'s [V]), MeshSieveError>> + 's
109where
110    M: FallibleMap<V> + 's,
111{
112    sieve
113        .closure(seeds)
114        .map(move |p| map.try_get(p).map(|sl| (p, sl)))
115}
116
117/// Restrict a map along the star of the given seed points, propagating errors.
118///
119/// # Errors
120/// Returns [`MeshSieveError`] if any point is missing in the underlying map.
121///
122/// # Complexity
123/// **O(n)** in the size of the star.
124pub fn try_restrict_star<'s, M, V: 's>(
125    sieve: &'s impl Sieve<Point = PointId>,
126    map: &'s M,
127    seeds: impl IntoIterator<Item = PointId>,
128) -> impl Iterator<Item = Result<(PointId, &'s [V]), MeshSieveError>> + 's
129where
130    M: FallibleMap<V> + 's,
131{
132    sieve
133        .star(seeds)
134        .map(move |p| map.try_get(p).map(|sl| (p, sl)))
135}
136
137/// Collects the closure restriction into a vector, short-circuiting on error.
138///
139/// # Errors
140/// Propagates the first error encountered while traversing the closure.
141pub fn try_restrict_closure_vec<'s, M, V: 's>(
142    sieve: &'s impl Sieve<Point = PointId>,
143    map: &'s M,
144    seeds: impl IntoIterator<Item = PointId>,
145) -> Result<Vec<(PointId, &'s [V])>, MeshSieveError>
146where
147    M: FallibleMap<V> + 's,
148{
149    try_restrict_closure(sieve, map, seeds).collect()
150}
151
152/// Collects the star restriction into a vector, short-circuiting on error.
153///
154/// # Errors
155/// Propagates the first error encountered while traversing the star.
156pub fn try_restrict_star_vec<'s, M, V: 's>(
157    sieve: &'s impl Sieve<Point = PointId>,
158    map: &'s M,
159    seeds: impl IntoIterator<Item = PointId>,
160) -> Result<Vec<(PointId, &'s [V])>, MeshSieveError>
161where
162    M: FallibleMap<V> + 's,
163{
164    try_restrict_star(sieve, map, seeds).collect()
165}
166
167#[cfg(feature = "rayon")]
168pub fn try_restrict_closure_vec_parallel<'s, M, V: Send + Sync + 's>(
169    sieve: &impl Sieve<Point = PointId>,
170    map: &'s M,
171    seeds: impl IntoIterator<Item = PointId>,
172) -> Result<Vec<(PointId, &'s [V])>, MeshSieveError>
173where
174    M: FallibleMap<V> + Sync + 's,
175{
176    use rayon::prelude::*;
177    let pts: Vec<_> = sieve.closure(seeds).collect();
178    pts.par_iter()
179        .map(|&p| map.try_get(p).map(|sl| (p, sl)))
180        .collect::<Result<Vec<_>, MeshSieveError>>()
181}
182
183#[cfg(feature = "rayon")]
184pub fn try_restrict_star_vec_parallel<'s, M, V: Send + Sync + 's>(
185    sieve: &impl Sieve<Point = PointId>,
186    map: &'s M,
187    seeds: impl IntoIterator<Item = PointId>,
188) -> Result<Vec<(PointId, &'s [V])>, MeshSieveError>
189where
190    M: FallibleMap<V> + Sync + 's,
191{
192    use rayon::prelude::*;
193    let pts: Vec<_> = sieve.star(seeds).collect();
194    pts.par_iter()
195        .map(|&p| map.try_get(p).map(|sl| (p, sl)))
196        .collect::<Result<Vec<_>, MeshSieveError>>()
197}
198
199/// Read-only wrapper for a section.
200pub struct ReadOnlyMap<'a, V, S: Storage<V> = VecStorage<V>> {
201    /// Underlying [`Section`] providing slice data.
202    pub section: &'a crate::data::section::Section<V, S>,
203    _marker: PhantomData<V>,
204}
205
206impl<'a, V, S> FallibleMap<V> for ReadOnlyMap<'a, V, S>
207where
208    S: Storage<V>,
209{
210    #[inline]
211    fn try_get(&self, p: crate::topology::point::PointId) -> Result<&[V], MeshSieveError> {
212        self.section.try_restrict(p)
213    }
214
215    #[inline]
216    fn try_get_mut(
217        &mut self,
218        _p: crate::topology::point::PointId,
219    ) -> Result<&mut [V], MeshSieveError> {
220        Err(MeshSieveError::UnsupportedStackOperation(
221            "ReadOnlyMap::try_get_mut",
222        ))
223    }
224}
225
226#[cfg(feature = "map-adapter")]
227#[cfg_attr(docsrs, doc(cfg(feature = "map-adapter")))]
228impl<'a, V, S> Map<V> for ReadOnlyMap<'a, V, S>
229where
230    S: Storage<V>,
231{
232    fn get(&self, p: crate::topology::point::PointId) -> &[V] {
233        self.section
234            .try_restrict(p)
235            .unwrap_or_else(|e| panic!("ReadOnlyMap::get({p:?}) failed: {e}"))
236    }
237    // get_mut left as default (None)
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use crate::data::atlas::Atlas;
244    #[cfg(feature = "map-adapter")]
245    use crate::data::section::Map;
246    use crate::data::section::{FallibleMap, Section};
247    use crate::data::storage::VecStorage;
248    use crate::topology::point::PointId;
249    use crate::topology::sieve::in_memory::InMemorySieve;
250
251    fn v(i: u64) -> PointId {
252        PointId::new(i).unwrap()
253    }
254
255    #[test]
256    fn restrict_helpers_basic() {
257        // Build tiny mesh: 1→2→3
258        let mut s = InMemorySieve::<PointId, ()>::default();
259        s.add_arrow(v(1), v(2), ());
260        s.add_arrow(v(2), v(3), ());
261        let mut atlas = Atlas::default();
262        atlas.try_insert(v(1), 1).unwrap();
263        atlas.try_insert(v(2), 1).unwrap();
264        atlas.try_insert(v(3), 1).unwrap();
265        let mut sec = Section::<i32, VecStorage<i32>>::new(atlas);
266        sec.try_set(v(1), &[10]).unwrap();
267        sec.try_set(v(2), &[20]).unwrap();
268        sec.try_set(v(3), &[30]).unwrap();
269
270        // fallible helpers succeed
271        let expected: Vec<_> = s
272            .closure([v(1)])
273            .map(|p| (p, sec.try_restrict(p).unwrap()))
274            .collect();
275        assert_eq!(
276            try_restrict_closure_vec(&s, &sec, [v(1)]).unwrap(),
277            expected
278        );
279
280        // fallible helpers return Err on missing point
281        assert!(matches!(
282            try_restrict_closure_vec(&s, &sec, [v(99)]),
283            Err(MeshSieveError::PointNotInAtlas(pid)) if pid == v(99)
284        ));
285
286        // ReadOnlyMap fallible
287        #[allow(unused_mut)]
288        let mut rom = ReadOnlyMap {
289            section: &sec,
290            _marker: PhantomData,
291        };
292        assert_eq!(
293            <ReadOnlyMap<'_, i32, VecStorage<i32>> as FallibleMap<i32>>::try_get(&rom, v(3))
294                .unwrap(),
295            sec.try_restrict(v(3)).unwrap()
296        );
297        assert!(
298            <ReadOnlyMap<'_, i32, VecStorage<i32>> as FallibleMap<i32>>::try_get(&rom, v(99))
299                .is_err()
300        );
301
302        #[cfg(feature = "map-adapter")]
303        assert!(
304            <ReadOnlyMap<'_, i32, VecStorage<i32>> as Map<i32>>::get_mut(&mut rom, v(3)).is_none()
305        );
306    }
307
308    #[cfg(feature = "map-adapter")]
309    #[test]
310    fn restrict_helpers_legacy() {
311        // Build tiny mesh: 1→2→3
312        let mut s = InMemorySieve::<PointId, ()>::default();
313        s.add_arrow(v(1), v(2), ());
314        s.add_arrow(v(2), v(3), ());
315        let mut atlas = Atlas::default();
316        atlas.try_insert(v(1), 1).unwrap();
317        atlas.try_insert(v(2), 1).unwrap();
318        atlas.try_insert(v(3), 1).unwrap();
319        let mut sec = Section::<i32, VecStorage<i32>>::new(atlas);
320        sec.try_set(v(1), &[10]).unwrap();
321        sec.try_set(v(2), &[20]).unwrap();
322        sec.try_set(v(3), &[30]).unwrap();
323
324        // restrict_closure
325        let out: Vec<_> = restrict_closure(&s, &sec, [v(1)]).collect();
326        let expected: Vec<_> = s
327            .closure([v(1)])
328            .map(|p| (p, sec.try_restrict(p).unwrap()))
329            .collect();
330        assert_eq!(out, expected);
331        // empty
332        let empty: Vec<_> = restrict_star(&s, &sec, std::iter::empty()).collect();
333        assert!(empty.is_empty());
334        // vec variants match
335        assert_eq!(
336            restrict_closure_vec(&s, &sec, [v(2)]),
337            restrict_closure(&s, &sec, [v(2)]).collect::<Vec<_>>()
338        );
339        // fallible helpers match legacy
340        assert_eq!(
341            try_restrict_closure_vec(&s, &sec, [v(1)]).unwrap(),
342            restrict_closure_vec(&s, &sec, [v(1)])
343        );
344        // legacy helper panics on missing point
345        assert!(std::panic::catch_unwind(|| restrict_closure_vec(&s, &sec, [v(99)])).is_err());
346    }
347}