Skip to main content

mesh_sieve/data/
section.rs

1//! Section: Field data storage over a topology atlas.
2//!
3//! The `Section<V, S>` type couples an `Atlas` (mapping points to slices in a
4//! contiguous array) with pluggable storage `S` to hold the actual data. It
5//! provides methods for inserting, accessing, and iterating per-point data
6//! slices.
7//!
8//! A legacy, infallible adapter trait `Map<V>` is available behind the
9//! `map-adapter` feature. Prefer [`FallibleMap`] and `try_*` APIs.
10
11use crate::data::atlas::Atlas;
12use crate::data::refine::delta::SliceDelta;
13use crate::data::storage::Storage;
14use crate::debug_invariants::DebugInvariants;
15use crate::mesh_error::MeshSieveError;
16use crate::topology::cache::InvalidateCache;
17use crate::topology::point::PointId;
18
19/// Precomputed plan for scattering data into a section.
20#[derive(Clone, Debug)]
21pub struct ScatterPlan {
22    pub(crate) atlas_version: u64,
23    pub(crate) spans: Vec<(usize, usize)>,
24}
25
26/// Storage for per-point field data, backed by an `Atlas`.
27///
28/// # Invariants
29/// - `data.len()` equals the atlas' [`total_len`](Atlas::total_len).
30/// - Every `(offset,len)` in the atlas falls within `data`.
31///
32/// These checks run after mutations in debug builds and when the
33/// `strict-invariants` feature (or its alias `check-invariants`) is enabled. They can also be verified manually via
34/// [`validate_invariants`](Self::validate_invariants).
35use core::marker::PhantomData;
36
37#[derive(Clone, Debug)]
38pub struct Section<V, S: Storage<V>> {
39    /// Atlas mapping each `PointId` to (offset, length) in `data`.
40    atlas: Atlas,
41    /// Contiguous storage of values for all points.
42    data: S,
43    _marker: PhantomData<V>,
44}
45
46/// Policy for handling slice length changes during atlas mutations.
47#[derive(Clone, Debug)]
48pub enum ResizePolicy<V> {
49    /// Fill the entire new slice with `V::default()` when length changes.
50    ZeroInit,
51    /// Copy `min(old, new)` elements from the start; pad the tail if growing.
52    PreservePrefix,
53    /// Copy `min(old, new)` elements from the end; pad the head if growing.
54    PreserveSuffix,
55    /// Fill the slice with a provided value when (re)initializing.
56    PadWith(V),
57}
58
59impl<V, S> Section<V, S>
60where
61    S: Storage<V>,
62{
63    /// Read-only view of the data slice for a given point `p`.
64    ///
65    /// # Errors
66    /// Returns `Err(PointNotInAtlas(p))` if the point is not registered in the atlas,
67    /// or `Err(MissingSectionPoint(p))` if the data buffer is inconsistent.
68    #[inline]
69    pub fn try_restrict(&self, p: PointId) -> Result<&[V], MeshSieveError> {
70        let (offset, len) = self
71            .atlas
72            .get(p)
73            .ok_or(MeshSieveError::PointNotInAtlas(p))?;
74        self.data
75            .as_slice()
76            .get(offset..offset + len)
77            .ok_or(MeshSieveError::MissingSectionPoint(p))
78    }
79
80    /// Mutable view of the data slice for a given point `p`.
81    ///
82    /// # Errors
83    /// Returns `Err(PointNotInAtlas(p))` if the point is not registered in the atlas,
84    /// or `Err(MissingSectionPoint(p))` if the data buffer is inconsistent.
85    #[inline]
86    pub fn try_restrict_mut(&mut self, p: PointId) -> Result<&mut [V], MeshSieveError> {
87        let (offset, len) = self
88            .atlas
89            .get(p)
90            .ok_or(MeshSieveError::PointNotInAtlas(p))?;
91        self.data
92            .as_mut_slice()
93            .get_mut(offset..offset + len)
94            .ok_or(MeshSieveError::MissingSectionPoint(p))
95    }
96
97    /// Read-only handle to the backing atlas.
98    ///
99    /// Mutations must go through [`with_atlas_mut`](Self::with_atlas_mut)
100    /// to keep `Section` and its data buffer consistent.
101    ///
102    /// # Complexity
103    /// **O(1)**.
104    #[inline]
105    pub fn atlas(&self) -> &Atlas {
106        &self.atlas
107    }
108
109    /// Iterate over `(PointId, &[V])` for all points in atlas order.
110    pub fn iter(&self) -> impl Iterator<Item = (PointId, &[V])> + '_ {
111        self.atlas
112            .points()
113            .filter_map(move |pid| self.try_restrict(pid).ok().map(|sl| (pid, sl)))
114    }
115
116    /// Read-only view of the entire flat buffer in insertion order.
117    #[inline]
118    pub fn as_flat_slice(&self) -> &[V] {
119        self.data.as_slice()
120    }
121
122    /// Apply a closure to every `(PointId, &mut [V])` in insertion order.
123    pub fn for_each_in_order_mut<F>(&mut self, mut f: F)
124    where
125        F: FnMut(PointId, &mut [V]),
126    {
127        #[cfg(any(
128            debug_assertions,
129            feature = "strict-invariants",
130            feature = "check-invariants"
131        ))]
132        self.debug_assert_invariants();
133        for pid in self.atlas.points() {
134            let (off, len) = self.atlas.get(pid).expect("invariants");
135            let buf = self.data.as_mut_slice();
136            f(pid, &mut buf[off..off + len]);
137        }
138        crate::topology::cache::InvalidateCache::invalidate_cache(self);
139        #[cfg(any(
140            debug_assertions,
141            feature = "strict-invariants",
142            feature = "check-invariants"
143        ))]
144        self.debug_assert_invariants();
145    }
146
147    /// Read-only variant of [`for_each_in_order_mut`].
148    pub fn for_each_in_order<F>(&self, mut f: F)
149    where
150        F: FnMut(PointId, &[V]),
151    {
152        #[cfg(any(
153            debug_assertions,
154            feature = "strict-invariants",
155            feature = "check-invariants"
156        ))]
157        self.debug_assert_invariants();
158        for pid in self.atlas.points() {
159            let (off, len) = self.atlas.get(pid).expect("invariants");
160            let buf = self.data.as_slice();
161            f(pid, &buf[off..off + len]);
162        }
163    }
164
165    #[inline]
166    fn spans_cover_contiguously(spans: &[(usize, usize)], total: usize) -> bool {
167        if spans.is_empty() {
168            return total == 0;
169        }
170        let mut expected_off = 0usize;
171        for &(off, len) in spans {
172            if off != expected_off {
173                return false;
174            }
175            match expected_off.checked_add(len) {
176                Some(next) => expected_off = next,
177                None => return false,
178            }
179        }
180        expected_off == total
181    }
182}
183
184impl<V: Clone + Default, S: Storage<V>> Section<V, S> {
185    /// Gather the entire section into a flat buffer in insertion order.
186    #[inline]
187    pub fn gather_in_order(&self) -> Vec<V> {
188        self.data.as_slice().to_vec()
189    }
190
191    /// Overwrite the data slice at point `p` with the values in `val`.
192    ///
193    /// # Errors
194    /// Returns `Err(PointNotInAtlas(p))` if the point is not registered in the atlas,
195    /// or `Err(SliceLengthMismatch)` if the input slice length does not match the expected length.
196    pub fn try_set(&mut self, p: PointId, val: &[V]) -> Result<(), MeshSieveError> {
197        let target = self.try_restrict_mut(p)?;
198        let expected = target.len();
199        let found = val.len();
200        if expected != found {
201            return Err(MeshSieveError::SliceLengthMismatch {
202                point: p,
203                expected,
204                found,
205            });
206        }
207        target.clone_from_slice(val);
208        crate::topology::cache::InvalidateCache::invalidate_cache(self);
209        #[cfg(any(
210            debug_assertions,
211            feature = "strict-invariants",
212            feature = "check-invariants"
213        ))]
214        self.debug_assert_invariants();
215        Ok(())
216    }
217
218    /// Remove a point from the section, rebuilding data to keep slices contiguous.
219    ///
220    /// # Errors
221    /// Returns `Err(MissingSectionPoint)` if a point is missing from the old atlas or data.
222    ///
223    /// # Complexity
224    /// **O(n)** atlas reindex + **O(total_len_new)** data rebuild.
225    ///
226    /// # Determinism
227    /// Deterministic rebuild in insertion order with no gaps.
228    pub fn try_remove_point(&mut self, p: PointId) -> Result<(), MeshSieveError> {
229        let old_atlas = self.atlas.clone();
230        self.atlas.remove_point(p)?;
231        let total_len_new = self.atlas.total_len();
232        let mut new_data = Vec::with_capacity(total_len_new);
233        let buf = self.data.as_slice();
234        for pid in self.atlas.points() {
235            let (old_offset, old_len) = old_atlas
236                .get(pid)
237                .ok_or(MeshSieveError::MissingSectionPoint(pid))?;
238            let old_slice = buf
239                .get(old_offset..old_offset + old_len)
240                .ok_or(MeshSieveError::MissingSectionPoint(pid))?;
241            new_data.extend_from_slice(old_slice);
242        }
243        let mut next = S::with_len(total_len_new, V::default());
244        next.as_mut_slice().clone_from_slice(&new_data);
245        self.data = next;
246        crate::topology::cache::InvalidateCache::invalidate_cache(self);
247        #[cfg(any(
248            debug_assertions,
249            feature = "strict-invariants",
250            feature = "check-invariants"
251        ))]
252        self.debug_assert_invariants();
253        Ok(())
254    }
255
256    /// Apply a delta from `src_point` → `dst_point` directly within the section buffer.
257    ///
258    /// In debug builds or when the `strict-invariants` feature (or alias `check-invariants`) is enabled, section
259    /// invariants are validated before and after applying the delta. Violations
260    /// panic prior to any slicing.
261    ///
262    /// If the underlying slices do not overlap, the delta is applied without any
263    /// additional allocation. Otherwise, the source slice is first copied into a
264    /// temporary buffer to maintain aliasing safety.
265    ///
266    /// # Errors
267    /// - [`MeshSieveError::PointNotInAtlas`] if either point is missing.
268    /// - [`MeshSieveError::SliceLengthMismatch`] if the slice lengths differ.
269    /// - [`MeshSieveError::MissingSectionPoint`] if either slice is out of bounds.
270    /// - [`MeshSieveError::ScatterChunkMismatch`] if an offset/length overflows.
271    pub fn try_apply_delta_between_points<D: SliceDelta<V>>(
272        &mut self,
273        src_point: PointId,
274        dst_point: PointId,
275        delta: &D,
276    ) -> Result<(), MeshSieveError> {
277        use MeshSieveError::*;
278
279        #[cfg(any(
280            debug_assertions,
281            feature = "strict-invariants",
282            feature = "check-invariants"
283        ))]
284        self.debug_assert_invariants();
285
286        let (soff, slen) = self
287            .atlas
288            .get(src_point)
289            .ok_or(PointNotInAtlas(src_point))?;
290        let (doff, dlen) = self
291            .atlas
292            .get(dst_point)
293            .ok_or(PointNotInAtlas(dst_point))?;
294
295        if slen != dlen {
296            return Err(SliceLengthMismatch {
297                point: dst_point,
298                expected: slen,
299                found: dlen,
300            });
301        }
302
303        let src_end = soff.checked_add(slen).ok_or(ScatterChunkMismatch {
304            offset: soff,
305            len: slen,
306        })?;
307        let dst_end = doff.checked_add(dlen).ok_or(ScatterChunkMismatch {
308            offset: doff,
309            len: dlen,
310        })?;
311
312        if src_end > self.data.len() {
313            return Err(MissingSectionPoint(src_point));
314        }
315        if dst_end > self.data.len() {
316            return Err(MissingSectionPoint(dst_point));
317        }
318
319        if slen == 0 {
320            crate::topology::cache::InvalidateCache::invalidate_cache(self);
321            #[cfg(any(
322                debug_assertions,
323                feature = "strict-invariants",
324                feature = "check-invariants"
325            ))]
326            self.debug_assert_invariants();
327            return Ok(());
328        }
329
330        let disjoint = src_end <= doff || dst_end <= soff;
331
332        if disjoint {
333            let data = self.data.as_mut_slice();
334            if soff < doff {
335                let (a, b) = data.split_at_mut(doff);
336                let src = &a[soff..src_end];
337                let dst = &mut b[0..dlen];
338                delta.apply(src, dst)?;
339            } else {
340                let (a, b) = data.split_at_mut(soff);
341                let dst = &mut a[doff..dst_end];
342                let src = &b[0..slen];
343                delta.apply(src, dst)?;
344            }
345        } else {
346            let src_copy: Vec<V> = self.data.as_slice()[soff..src_end].to_vec();
347            let dst = &mut self.data.as_mut_slice()[doff..dst_end];
348            delta.apply(&src_copy, dst)?;
349        }
350
351        crate::topology::cache::InvalidateCache::invalidate_cache(self);
352        #[cfg(any(
353            debug_assertions,
354            feature = "strict-invariants",
355            feature = "check-invariants"
356        ))]
357        self.debug_assert_invariants();
358        Ok(())
359    }
360
361    /// Scatter values from an external buffer `other` into this section.
362    ///
363    /// # Errors
364    /// Returns `Err(ScatterLengthMismatch)` if the input length does not match expected,
365    /// or `Err(ScatterChunkMismatch)` if a chunk is out of bounds.
366    ///
367    /// # Complexity
368    /// **O(total_len)** to copy slices; validates bounds in **O(n)** where `n` is points.
369    ///
370    /// # Determinism
371    /// Deterministic copy; `*_with_plan` additionally validates the plan version for safety.
372    pub fn try_scatter_from(
373        &mut self,
374        other: &[V],
375        atlas_map: &[(usize, usize)],
376    ) -> Result<(), MeshSieveError> {
377        if other.len() == self.data.len()
378            && Self::spans_cover_contiguously(atlas_map, self.data.len())
379        {
380            self.data.as_mut_slice().clone_from_slice(other);
381            crate::topology::cache::InvalidateCache::invalidate_cache(self);
382            #[cfg(any(
383                debug_assertions,
384                feature = "strict-invariants",
385                feature = "check-invariants"
386            ))]
387            self.debug_assert_invariants();
388            return Ok(());
389        }
390
391        let total_expected: usize = atlas_map.iter().map(|&(_, l)| l).sum();
392        let found = other.len();
393        if total_expected != found {
394            return Err(MeshSieveError::ScatterLengthMismatch {
395                expected: total_expected,
396                found,
397            });
398        }
399
400        for &(offset, len) in atlas_map {
401            let end = offset
402                .checked_add(len)
403                .ok_or(MeshSieveError::ScatterChunkMismatch { offset, len })?;
404            if end > self.data.len() {
405                return Err(MeshSieveError::ScatterChunkMismatch { offset, len });
406            }
407        }
408
409        let mut start = 0usize;
410        for &(offset, len) in atlas_map {
411            let end = start + len;
412            let chunk = other
413                .get(start..end)
414                .ok_or(MeshSieveError::ScatterChunkMismatch { offset: start, len })?;
415            let dest = &mut self.data.as_mut_slice()[offset..offset + len];
416            dest.clone_from_slice(chunk);
417            start = end;
418        }
419
420        crate::topology::cache::InvalidateCache::invalidate_cache(self);
421        #[cfg(any(
422            debug_assertions,
423            feature = "strict-invariants",
424            feature = "check-invariants"
425        ))]
426        self.debug_assert_invariants();
427        Ok(())
428    }
429
430    /// Scatter using a precomputed plan; fails if the atlas has changed.
431    ///
432    /// # Complexity
433    /// **O(total_len)** to copy slices; validates bounds in **O(n)** where `n` is points.
434    ///
435    /// # Determinism
436    /// Deterministic copy; `*_with_plan` additionally validates the plan version for safety.
437    pub fn try_scatter_with_plan(
438        &mut self,
439        buf: &[V],
440        plan: &ScatterPlan,
441    ) -> Result<(), MeshSieveError> {
442        let cur = self.atlas.version();
443        if plan.atlas_version != cur {
444            return Err(MeshSieveError::AtlasPlanStale {
445                expected: plan.atlas_version,
446                found: cur,
447            });
448        }
449        self.try_scatter_from(buf, &plan.spans)
450    }
451}
452
453impl<V: Clone + Default, S: Storage<V> + Clone> Section<V, S> {
454    /// Construct a new `Section` given an existing `Atlas`.
455    ///
456    /// Initializes the data buffer with `V::default()` repeated for each
457    /// degree of freedom in the atlas.
458    ///
459    /// # Complexity
460    /// **O(total_len)** to fill with `V::default()`.
461    ///
462    /// # Determinism
463    /// Initial layout matches the atlas’ insertion order deterministically.
464    pub fn new(atlas: Atlas) -> Self {
465        let data = S::with_len(atlas.total_len(), V::default());
466        Section {
467            atlas,
468            data,
469            _marker: PhantomData,
470        }
471    }
472
473    /// Add a new point to the section, resizing data as needed.
474    ///
475    /// # Errors
476    /// Returns `Err(AtlasInsertionFailed)` if the atlas insertion fails.
477    ///
478    /// # Complexity
479    /// **O(n)** atlas insertion (reindex) + **O(total_len_new)** data resize.
480    ///
481    /// # Determinism
482    /// Data remains contiguous in **insertion order**; the new point is appended.
483    pub fn try_add_point(&mut self, p: PointId, len: usize) -> Result<(), MeshSieveError> {
484        self.atlas
485            .try_insert(p, len)
486            .map_err(|e| MeshSieveError::AtlasInsertionFailed(p, Box::new(e)))?;
487        let new_total = self.atlas.total_len();
488        let old_total = self.data.len();
489        if new_total > old_total {
490            self.data.resize(new_total, V::default());
491        }
492        crate::topology::cache::InvalidateCache::invalidate_cache(self);
493        #[cfg(any(
494            debug_assertions,
495            feature = "strict-invariants",
496            feature = "check-invariants"
497        ))]
498        self.debug_assert_invariants();
499        Ok(())
500    }
501
502    /// Safely mutate the atlas and rebuild `data` to remain consistent.
503    ///
504    /// - New points get `len` default-initialized values.
505    /// - Removed points drop data.
506    /// - Reordered/retuned points keep their old values (by `PointId`),
507    ///   copied into the new contiguous layout.
508    /// - **Length changes for existing points are rejected.** Use
509    ///   [`with_atlas_resize`](Self::with_atlas_resize) to allow slice length
510    ///   changes with an explicit policy.
511    ///
512    /// # Errors
513    /// Returns [`MeshSieveError::AtlasPointLengthChanged`] if any existing
514    /// point's slice length differs after mutation.
515    ///
516    /// # Complexity
517    /// **O(n)** mapping + **O(total_len_new)** copy/initialize.
518    ///
519    /// # Determinism
520    /// Rebuild order follows atlas insertion order deterministically.
521    pub fn with_atlas_mut<F>(&mut self, f: F) -> Result<(), MeshSieveError>
522    where
523        F: FnOnce(&mut Atlas),
524    {
525        // Snapshot current atlas to pull old spans
526        let before = self.atlas.clone();
527
528        // Let the user mutate
529        f(&mut self.atlas);
530
531        // Validate new atlas
532        #[cfg(any(
533            debug_assertions,
534            feature = "strict-invariants",
535            feature = "check-invariants"
536        ))]
537        self.atlas.debug_assert_invariants();
538
539        // Gather points to avoid borrowing issues
540        let new_points: Vec<_> = self.atlas.points().collect();
541
542        // STRICT check: existing points must retain slice lengths
543        use MeshSieveError::AtlasPointLengthChanged;
544        for &pid in &new_points {
545            if let Some((_, len_old)) = before.get(pid) {
546                let (_, len_new) = self
547                    .atlas
548                    .get(pid)
549                    .expect("pid was just iterated from new atlas");
550                if len_old != len_new {
551                    self.atlas = before;
552                    return Err(AtlasPointLengthChanged {
553                        point: pid,
554                        expected: len_old,
555                        found: len_new,
556                    });
557                }
558            }
559        }
560
561        // Rebuild data following insertion order of the new atlas
562        let total_len_new = self.atlas.total_len();
563        let mut new_data = Vec::with_capacity(total_len_new);
564        let old_buf = self.data.as_slice();
565        for pid in new_points {
566            match before.get(pid) {
567                // Existing point: copy old slice
568                Some((off, len)) => {
569                    let end = off + len;
570                    let src = old_buf
571                        .get(off..end)
572                        .ok_or(MeshSieveError::MissingSectionPoint(pid))?;
573                    new_data.extend_from_slice(src);
574                }
575                // New point: fill with defaults
576                None => {
577                    let (_off_new, len_new) = self
578                        .atlas
579                        .get(pid)
580                        .ok_or(MeshSieveError::MissingAtlasPoint(pid))?;
581                    new_data.extend(std::iter::repeat_with(V::default).take(len_new));
582                }
583            }
584        }
585
586        let mut next = S::with_len(total_len_new, V::default());
587        next.as_mut_slice().clone_from_slice(&new_data);
588        self.data = next;
589        crate::topology::cache::InvalidateCache::invalidate_cache(self);
590        #[cfg(any(
591            debug_assertions,
592            feature = "strict-invariants",
593            feature = "check-invariants"
594        ))]
595        self.debug_assert_invariants();
596        Ok(())
597    }
598
599    /// Mutate the atlas, allowing slice length changes per `policy`.
600    ///
601    /// Existing points preserve or initialize values according to `policy`.
602    /// New points are initialized per `policy` as well. Removed points drop
603    /// their data. On error, the atlas and data are rolled back to their
604    /// original state.
605    pub fn with_atlas_resize<F>(
606        &mut self,
607        policy: ResizePolicy<V>,
608        f: F,
609    ) -> Result<(), MeshSieveError>
610    where
611        F: FnOnce(&mut Atlas),
612    {
613        let before_atlas = self.atlas.clone();
614        let before_data = self.data.clone();
615
616        f(&mut self.atlas);
617
618        #[cfg(any(
619            debug_assertions,
620            feature = "strict-invariants",
621            feature = "check-invariants"
622        ))]
623        self.atlas.debug_assert_invariants();
624
625        let rebuild = (|| -> Result<Vec<V>, MeshSieveError> {
626            let mut new_data = Vec::with_capacity(self.atlas.total_len());
627            let extend_fill = |buf: &mut Vec<V>, n: usize| match &policy {
628                ResizePolicy::ZeroInit
629                | ResizePolicy::PreservePrefix
630                | ResizePolicy::PreserveSuffix => {
631                    buf.extend(std::iter::repeat_with(V::default).take(n));
632                }
633                ResizePolicy::PadWith(val) => {
634                    buf.extend(std::iter::repeat_n(val.clone(), n));
635                }
636            };
637            let before_slice = before_data.as_slice();
638            for pid in self.atlas.points() {
639                let (_off_new, new_len) = self
640                    .atlas
641                    .get(pid)
642                    .ok_or(MeshSieveError::MissingAtlasPoint(pid))?;
643                if let Some((off_old, old_len)) = before_atlas.get(pid) {
644                    let src = before_slice
645                        .get(off_old..off_old + old_len)
646                        .ok_or(MeshSieveError::MissingSectionPoint(pid))?;
647                    match &policy {
648                        ResizePolicy::ZeroInit => {
649                            extend_fill(&mut new_data, new_len);
650                        }
651                        ResizePolicy::PadWith(_) => {
652                            extend_fill(&mut new_data, new_len);
653                        }
654                        ResizePolicy::PreservePrefix => {
655                            let k = core::cmp::min(old_len, new_len);
656                            new_data.extend_from_slice(&src[..k]);
657                            if new_len > k {
658                                extend_fill(&mut new_data, new_len - k);
659                            }
660                        }
661                        ResizePolicy::PreserveSuffix => {
662                            let k = core::cmp::min(old_len, new_len);
663                            if new_len > k {
664                                extend_fill(&mut new_data, new_len - k);
665                            }
666                            new_data.extend_from_slice(&src[old_len - k..]);
667                        }
668                    }
669                } else {
670                    extend_fill(&mut new_data, new_len);
671                }
672            }
673
674            Ok(new_data)
675        })();
676
677        match rebuild {
678            Ok(new_data) => {
679                let mut next = S::with_len(self.atlas.total_len(), V::default());
680                next.as_mut_slice().clone_from_slice(&new_data);
681                self.data = next;
682                crate::topology::cache::InvalidateCache::invalidate_cache(self);
683                #[cfg(any(
684                    debug_assertions,
685                    feature = "strict-invariants",
686                    feature = "check-invariants"
687                ))]
688                self.debug_assert_invariants();
689                Ok(())
690            }
691            Err(e) => {
692                self.atlas = before_atlas;
693                self.data = before_data;
694                Err(e)
695            }
696        }
697    }
698
699    /// Scatter a flat buffer into the section in atlas insertion order.
700    ///
701    /// # Complexity
702    /// **O(total_len)** to copy slices; validates bounds in **O(n)** where `n` is points.
703    ///
704    /// # Determinism
705    /// Deterministic copy; `*_with_plan` additionally validates the plan version for safety.
706    pub fn try_scatter_in_order(&mut self, buf: &[V]) -> Result<(), MeshSieveError> {
707        #[cfg(any(
708            debug_assertions,
709            feature = "strict-invariants",
710            feature = "check-invariants"
711        ))]
712        self.debug_assert_invariants();
713        let spans = self.atlas.atlas_map();
714        self.try_scatter_from(buf, &spans)
715    }
716}
717
718/// Fallible read/write access to per-point slices.
719pub trait FallibleMap<V> {
720    /// Immutable access to `p`'s slice.
721    fn try_get(&self, p: PointId) -> Result<&[V], MeshSieveError>;
722    /// Mutable access to `p`'s slice.
723    fn try_get_mut(&mut self, p: PointId) -> Result<&mut [V], MeshSieveError>;
724}
725
726/// Implement `FallibleMap` for `Section<V>`.
727impl<V, S> FallibleMap<V> for Section<V, S>
728where
729    S: Storage<V>,
730{
731    #[inline]
732    fn try_get(&self, p: PointId) -> Result<&[V], MeshSieveError> {
733        self.try_restrict(p)
734    }
735
736    #[inline]
737    fn try_get_mut(&mut self, p: PointId) -> Result<&mut [V], MeshSieveError> {
738        self.try_restrict_mut(p)
739    }
740}
741
742#[cfg(feature = "map-adapter")]
743/// Infallible adapter for read/write access; intended for legacy code.
744/// Prefer [`FallibleMap`] in new code.
745///
746/// # Panics
747#[cfg(feature = "map-adapter")]
748mod sealed {
749    pub trait Sealed {}
750    impl<V, S: crate::data::storage::Storage<V>> Sealed for super::Section<V, S> {}
751    impl<'a, V, S: crate::data::storage::Storage<V>> Sealed
752        for crate::data::refine::helpers::ReadOnlyMap<'a, V, S>
753    {
754    }
755}
756
757/// Infallible adapter for read/write access; intended for legacy code.
758/// Prefer [`FallibleMap`] in new code.
759///
760/// # Panics
761/// Implementations may panic if `p` is unknown.
762#[cfg(feature = "map-adapter")]
763#[cfg_attr(docsrs, doc(cfg(feature = "map-adapter")))]
764pub trait Map<V>: sealed::Sealed {
765    /// Immutable access to the data slice for `p`.
766    fn get(&self, p: PointId) -> &[V];
767
768    /// Optional mutable access to the data slice for `p`.
769    ///
770    /// Default implementation returns `None`, meaning the map is read-only.
771    fn get_mut(&mut self, _p: PointId) -> Option<&mut [V]> {
772        None
773    }
774}
775
776#[cfg(feature = "map-adapter")]
777impl<V, S> Map<V> for Section<V, S>
778where
779    S: Storage<V>,
780{
781    #[inline]
782    fn get(&self, p: PointId) -> &[V] {
783        self.try_restrict(p)
784            .unwrap_or_else(|e| panic!("Map::get({p:?}) failed: {e}"))
785    }
786
787    #[inline]
788    fn get_mut(&mut self, p: PointId) -> Option<&mut [V]> {
789        Some(
790            self.try_restrict_mut(p)
791                .unwrap_or_else(|e| panic!("Map::get_mut({p:?}) failed: {e}")),
792        )
793    }
794}
795
796impl<V, S> DebugInvariants for Section<V, S>
797where
798    S: Storage<V>,
799{
800    fn debug_assert_invariants(&self) {
801        crate::debug_invariants!(self.validate_invariants(), "Section invalid");
802    }
803
804    fn validate_invariants(&self) -> Result<(), MeshSieveError> {
805        // Validate atlas first
806        self.atlas.validate_invariants()?;
807
808        if self.data.len() != self.atlas.total_len() {
809            return Err(MeshSieveError::ScatterLengthMismatch {
810                expected: self.atlas.total_len(),
811                found: self.data.len(),
812            });
813        }
814
815        for (pid, (off, len)) in self.atlas.iter_entries() {
816            let end = off
817                .checked_add(len)
818                .ok_or_else(|| MeshSieveError::ScatterChunkMismatch { offset: off, len })?;
819            if end > self.data.len() {
820                return Err(MeshSieveError::MissingSectionPoint(pid));
821            }
822        }
823
824        Ok(())
825    }
826}
827
828impl<V, S: Storage<V>> InvalidateCache for Section<V, S> {
829    fn invalidate_cache(&mut self) {
830        // If you ever cache anything derived from atlas/data, clear it here.
831    }
832}
833
834#[cfg(test)]
835mod tests {
836    use super::*;
837    use crate::data::atlas::Atlas;
838    use crate::data::storage::VecStorage;
839    use crate::topology::point::PointId;
840
841    fn make_section() -> Section<f64, VecStorage<f64>> {
842        let mut atlas = Atlas::default();
843        atlas.try_insert(PointId::new(1).unwrap(), 2).unwrap(); // 2 dof
844        atlas.try_insert(PointId::new(2).unwrap(), 1).unwrap();
845        Section::<f64, VecStorage<f64>>::new(atlas)
846    }
847
848    #[test]
849    fn restrict_and_set() {
850        let mut s = make_section();
851        s.try_set(PointId::new(1).unwrap(), &[1.0, 2.0]).unwrap();
852        s.try_set(PointId::new(2).unwrap(), &[3.5]).unwrap();
853
854        assert_eq!(
855            s.try_restrict(PointId::new(1).unwrap()).unwrap(),
856            &[1.0, 2.0]
857        );
858        assert_eq!(s.try_restrict(PointId::new(2).unwrap()).unwrap(), &[3.5]);
859    }
860
861    #[test]
862    fn iter_order() {
863        let mut s = make_section();
864        s.try_set(PointId::new(1).unwrap(), &[9.0, 8.0]).unwrap();
865        s.try_set(PointId::new(2).unwrap(), &[7.0]).unwrap();
866
867        let collected: Vec<_> = s.iter().map(|(_, sl)| sl[0]).collect();
868        assert_eq!(collected, vec![9.0, 7.0]); // atlas order
869    }
870
871    #[test]
872    fn fallible_map_on_section() {
873        use super::FallibleMap;
874        let mut s = make_section();
875        s.try_set(PointId::new(1).unwrap(), &[1.0, 2.0]).unwrap();
876        // successful access
877        assert_eq!(
878            <Section<f64, VecStorage<f64>> as FallibleMap<f64>>::try_get(
879                &s,
880                PointId::new(1).unwrap()
881            )
882            .unwrap(),
883            &[1.0, 2.0]
884        );
885        // missing point yields error
886        assert!(
887            <Section<f64, VecStorage<f64>> as FallibleMap<f64>>::try_get(
888                &s,
889                PointId::new(99).unwrap()
890            )
891            .is_err()
892        );
893        // mutable access works
894        assert!(
895            <Section<f64, VecStorage<f64>> as FallibleMap<f64>>::try_get_mut(
896                &mut s,
897                PointId::new(1).unwrap()
898            )
899            .is_ok()
900        );
901    }
902
903    #[cfg(feature = "map-adapter")]
904    #[test]
905    fn map_trait_section_get_and_mut() {
906        use super::Map;
907        let mut s = make_section();
908        s.try_set(PointId::new(1).unwrap(), &[1.0, 2.0]).unwrap();
909        s.try_set(PointId::new(2).unwrap(), &[3.5]).unwrap();
910        // get == restrict
911        assert_eq!(
912            <Section<f64, VecStorage<f64>> as Map<f64>>::get(&s, PointId::new(1).unwrap()),
913            s.try_restrict(PointId::new(1).unwrap()).unwrap()
914        );
915        // get_mut returns Some for Section
916        assert!(
917            <Section<f64, VecStorage<f64>> as Map<f64>>::get_mut(&mut s, PointId::new(1).unwrap())
918                .is_some()
919        );
920    }
921
922    #[test]
923    fn scatter_from() {
924        let mut s = make_section();
925        // Initial data: [0.0, 0.0, 0.0]
926        assert_eq!(s.as_flat_slice(), &[0.0, 0.0, 0.0]);
927
928        // Scatter in two chunks: [1.0, 2.0] and [3.5]
929        let _ = s.try_scatter_from(&[1.0, 2.0, 3.5], &[(0, 2), (2, 1)]);
930
931        // Resulting data: [1.0, 2.0, 3.5]
932        assert_eq!(s.as_flat_slice(), &[1.0, 2.0, 3.5]);
933    }
934
935    #[test]
936    fn section_round_trip_and_scatter() {
937        let mut atlas = Atlas::default();
938        atlas.try_insert(PointId::new(1).unwrap(), 2).unwrap();
939        atlas.try_insert(PointId::new(2).unwrap(), 1).unwrap();
940        let mut s = Section::<f64, VecStorage<f64>>::new(atlas.clone());
941        // set and restrict
942        s.try_set(PointId::new(1).unwrap(), &[1.1, 2.2]).unwrap();
943        s.try_set(PointId::new(2).unwrap(), &[3.3]).unwrap();
944        assert_eq!(
945            s.try_restrict(PointId::new(1).unwrap()).unwrap(),
946            &[1.1, 2.2]
947        );
948        assert_eq!(s.try_restrict(PointId::new(2).unwrap()).unwrap(), &[3.3]);
949        // scatter_from
950        let mut s2 = Section::<f64, VecStorage<f64>>::new(atlas);
951        s2.try_scatter_from(&[1.1, 2.2, 3.3], &[(0, 2), (2, 1)])
952            .unwrap();
953        assert_eq!(
954            s2.try_restrict(PointId::new(1).unwrap()).unwrap(),
955            &[1.1, 2.2]
956        );
957        assert_eq!(s2.try_restrict(PointId::new(2).unwrap()).unwrap(), &[3.3]);
958    }
959
960    #[cfg(feature = "map-adapter")]
961    #[test]
962    fn section_map_trait_and_readonly() {
963        use super::Map;
964        let mut atlas = Atlas::default();
965        atlas.try_insert(PointId::new(1).unwrap(), 1).unwrap();
966        let mut s = Section::<i32, VecStorage<i32>>::new(atlas);
967        s.try_set(PointId::new(1).unwrap(), &[42]).unwrap();
968        // Map trait get
969        assert_eq!(
970            <Section<i32, VecStorage<i32>> as Map<i32>>::get(&s, PointId::new(1).unwrap()),
971            &[42]
972        );
973        // Map trait get_mut
974        assert!(
975            <Section<i32, VecStorage<i32>> as Map<i32>>::get_mut(&mut s, PointId::new(1).unwrap())
976                .is_some()
977        );
978    }
979
980    #[test]
981    fn add_point_expands_and_defaults() {
982        let mut atlas = Atlas::default();
983        atlas.try_insert(PointId::new(1).unwrap(), 2).unwrap();
984        let mut s = Section::<i32, VecStorage<i32>>::new(atlas.clone());
985        // initial capacity = 2
986        assert_eq!(s.iter().count(), 1);
987        // add a new point of length 3
988        s.try_add_point(PointId::new(2).unwrap(), 3).unwrap();
989        // now iter() yields 2 points
990        let mut pts: Vec<_> = s.iter().map(|(p, _)| p).collect();
991        pts.sort_unstable();
992        assert_eq!(
993            pts,
994            vec![PointId::new(1).unwrap(), PointId::new(2).unwrap()]
995        );
996        // its slice is all default (0)
997        assert_eq!(
998            s.try_restrict(PointId::new(2).unwrap()).unwrap(),
999            &[0, 0, 0]
1000        );
1001        // setting and reading works
1002        s.try_set(PointId::new(2).unwrap(), &[7, 8, 9]).unwrap();
1003        assert_eq!(
1004            s.try_restrict(PointId::new(2).unwrap()).unwrap(),
1005            &[7, 8, 9]
1006        );
1007    }
1008
1009    #[test]
1010    fn remove_point_compacts_and_forgets() {
1011        // build atlas with 3 points, lengths [2,1,2]
1012        let mut atlas = Atlas::default();
1013        atlas.try_insert(PointId::new(1).unwrap(), 2).unwrap();
1014        atlas.try_insert(PointId::new(2).unwrap(), 1).unwrap();
1015        atlas.try_insert(PointId::new(3).unwrap(), 2).unwrap();
1016        let mut s = Section::<i32, VecStorage<i32>>::new(atlas);
1017        // set some dummy values
1018        s.try_set(PointId::new(1).unwrap(), &[10, 11]).unwrap();
1019        s.try_set(PointId::new(2).unwrap(), &[22]).unwrap();
1020        s.try_set(PointId::new(3).unwrap(), &[33, 34]).unwrap();
1021        // remove the middle point
1022        let _ = s.try_remove_point(PointId::new(2).unwrap());
1023        // now only 1 and 3 remain, in order
1024        let pts: Vec<_> = s.iter().map(|(p, _)| p).collect();
1025        assert_eq!(
1026            pts,
1027            vec![PointId::new(1).unwrap(), PointId::new(3).unwrap()]
1028        );
1029        // data buffer should be [10,11,33,34]
1030        let all: Vec<_> = s.as_flat_slice().iter().copied().collect();
1031        assert_eq!(all, vec![10, 11, 33, 34]);
1032        // restricting the removed point panics
1033        std::panic::catch_unwind(|| {
1034            let _ = s.try_restrict(PointId::new(2).unwrap()).unwrap();
1035        })
1036        .expect_err("should panic");
1037    }
1038
1039    #[test]
1040    #[should_panic(expected = "called `Result::unwrap()` on an `Err` value")]
1041    fn restrict_missing_panics() {
1042        let s = make_section();
1043        let _ = s.try_restrict(PointId::new(99).unwrap()).unwrap();
1044    }
1045
1046    #[test]
1047    fn set_wrong_length_returns_err() {
1048        let mut s = make_section();
1049        let err = s.try_set(PointId::new(1).unwrap(), &[1.0]).unwrap_err();
1050        assert_eq!(
1051            err,
1052            MeshSieveError::SliceLengthMismatch {
1053                point: PointId::new(1).unwrap(),
1054                expected: 2,
1055                found: 1
1056            }
1057        );
1058    }
1059
1060    #[test]
1061    fn invalidate_cache_noop() {
1062        let mut s = make_section();
1063        // Just ensure this compiles and does nothing
1064        InvalidateCache::invalidate_cache(&mut s);
1065    }
1066}
1067
1068#[cfg(test)]
1069mod tests_resize {
1070    use super::*;
1071    use crate::data::storage::VecStorage;
1072    use crate::topology::point::PointId;
1073
1074    fn pid(i: u64) -> PointId {
1075        PointId::new(i).unwrap()
1076    }
1077
1078    #[test]
1079    fn strict_with_atlas_mut_rejects_len_change() {
1080        let mut atlas = Atlas::default();
1081        let p = pid(1);
1082        atlas.try_insert(p, 3).unwrap();
1083        let mut s: Section<i32, VecStorage<i32>> = Section::new(atlas);
1084
1085        s.try_set(p, &[1, 2, 3]).unwrap();
1086
1087        let err = s
1088            .with_atlas_mut(|a| {
1089                a.remove_point(p).unwrap();
1090                a.try_insert(p, 5).unwrap();
1091            })
1092            .unwrap_err();
1093
1094        match err {
1095            MeshSieveError::AtlasPointLengthChanged {
1096                point,
1097                expected,
1098                found,
1099            } => {
1100                assert_eq!(point, p);
1101                assert_eq!(expected, 3);
1102                assert_eq!(found, 5);
1103            }
1104            e => panic!("unexpected error: {e:?}"),
1105        }
1106
1107        assert_eq!(s.atlas().get(p).unwrap().1, 3);
1108        assert_eq!(s.try_restrict(p).unwrap(), &[1, 2, 3]);
1109    }
1110
1111    #[test]
1112    fn resize_preserve_prefix_and_zero_extend() {
1113        let mut atlas = Atlas::default();
1114        let p = pid(1);
1115        atlas.try_insert(p, 3).unwrap();
1116        let mut s: Section<i32, VecStorage<i32>> = Section::new(atlas);
1117        s.try_set(p, &[10, 20, 30]).unwrap();
1118
1119        s.with_atlas_resize(ResizePolicy::PreservePrefix, |a| {
1120            a.remove_point(p).unwrap();
1121            a.try_insert(p, 5).unwrap();
1122        })
1123        .unwrap();
1124
1125        assert_eq!(s.try_restrict(p).unwrap(), &[10, 20, 30, 0, 0]);
1126    }
1127
1128    #[test]
1129    fn resize_preserve_suffix_and_zero_prepad() {
1130        let mut atlas = Atlas::default();
1131        let p = pid(1);
1132        atlas.try_insert(p, 3).unwrap();
1133        let mut s: Section<i32, VecStorage<i32>> = Section::new(atlas);
1134        s.try_set(p, &[10, 20, 30]).unwrap();
1135
1136        s.with_atlas_resize(ResizePolicy::PreserveSuffix, |a| {
1137            a.remove_point(p).unwrap();
1138            a.try_insert(p, 5).unwrap();
1139        })
1140        .unwrap();
1141
1142        assert_eq!(s.try_restrict(p).unwrap(), &[0, 0, 10, 20, 30]);
1143    }
1144
1145    #[test]
1146    fn resize_pad_with_custom_value() {
1147        let mut atlas = Atlas::default();
1148        let p = pid(1);
1149        atlas.try_insert(p, 2).unwrap();
1150        let mut s: Section<i32, VecStorage<i32>> = Section::new(atlas);
1151        s.try_set(p, &[7, 9]).unwrap();
1152
1153        s.with_atlas_resize(ResizePolicy::PadWith(-1), |a| {
1154            a.remove_point(p).unwrap();
1155            a.try_insert(p, 4).unwrap();
1156        })
1157        .unwrap();
1158
1159        assert_eq!(s.try_restrict(p).unwrap(), &[-1, -1, -1, -1]);
1160    }
1161
1162    #[test]
1163    fn resize_shrink_preserve_prefix() {
1164        let mut atlas = Atlas::default();
1165        let p = pid(1);
1166        atlas.try_insert(p, 4).unwrap();
1167        let mut s: Section<i32, VecStorage<i32>> = Section::new(atlas);
1168        s.try_set(p, &[1, 2, 3, 4]).unwrap();
1169
1170        s.with_atlas_resize(ResizePolicy::PreservePrefix, |a| {
1171            a.remove_point(p).unwrap();
1172            a.try_insert(p, 2).unwrap();
1173        })
1174        .unwrap();
1175
1176        assert_eq!(s.try_restrict(p).unwrap(), &[1, 2]);
1177    }
1178
1179    #[test]
1180    fn resize_shrink_preserve_suffix() {
1181        let mut atlas = Atlas::default();
1182        let p = pid(1);
1183        atlas.try_insert(p, 4).unwrap();
1184        let mut s: Section<i32, VecStorage<i32>> = Section::new(atlas);
1185        s.try_set(p, &[1, 2, 3, 4]).unwrap();
1186
1187        s.with_atlas_resize(ResizePolicy::PreserveSuffix, |a| {
1188            a.remove_point(p).unwrap();
1189            a.try_insert(p, 2).unwrap();
1190        })
1191        .unwrap();
1192
1193        assert_eq!(s.try_restrict(p).unwrap(), &[3, 4]);
1194    }
1195}
1196
1197#[cfg(test)]
1198mod tests_invariants_precheck {
1199    use super::*;
1200    use crate::data::atlas::Atlas;
1201    use crate::data::storage::VecStorage;
1202    use crate::topology::point::PointId;
1203
1204    #[cfg(not(debug_assertions))]
1205    use crate::{mesh_error::MeshSieveError, topology::arrow::Polarity};
1206
1207    fn pid(i: u64) -> PointId {
1208        PointId::new(i).unwrap()
1209    }
1210
1211    #[test]
1212    #[cfg(not(debug_assertions))]
1213    fn delta_returns_missing_point_in_release_paths() {
1214        let mut atlas = Atlas::default();
1215        let p0 = pid(1);
1216        let p1 = pid(2);
1217        atlas.try_insert(p0, 2).unwrap();
1218        atlas.try_insert(p1, 2).unwrap();
1219        let mut s: Section<i32, VecStorage<i32>> = Section::new(atlas);
1220
1221        let mut bad = s.atlas.clone();
1222        bad.remove_point(p1).unwrap();
1223        let dummy = pid(99);
1224        bad.try_insert(dummy, s.as_flat_slice().len() + 8).unwrap();
1225        bad.try_insert(p1, 2).unwrap();
1226        s.atlas = bad;
1227
1228        let e = s
1229            .try_apply_delta_between_points(p0, p1, &Polarity::Forward)
1230            .unwrap_err();
1231
1232        match e {
1233            MeshSieveError::MissingSectionPoint(q) => assert_eq!(q, p1),
1234            _ => panic!("unexpected error: {e:?}"),
1235        }
1236    }
1237
1238    #[test]
1239    fn for_each_in_order_prechecks_in_debug() {
1240        let mut atlas = Atlas::default();
1241        let p = pid(7);
1242        atlas.try_insert(p, 1).unwrap();
1243        let mut s: Section<i32, VecStorage<i32>> = Section::new(atlas);
1244
1245        s.data.resize(0, 0);
1246
1247        #[cfg(any(
1248            debug_assertions,
1249            feature = "strict-invariants",
1250            feature = "check-invariants"
1251        ))]
1252        {
1253            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1254                s.for_each_in_order(|_, _| {});
1255            }));
1256            assert!(result.is_err(), "expected invariant pre-check to panic");
1257        }
1258    }
1259}
1260
1261pub use crate::data::refine::Sifter;