Skip to main content

mesh_sieve/algs/
distribute.rs

1// src/algs/distribute.rs
2
3use crate::algs::communicator::{CommTag, Communicator, Wait};
4use crate::algs::completion::{complete_section_with_ownership, complete_sieve};
5use crate::algs::point_sf::{PointSF, balance_partition_boundary_ownership};
6use crate::algs::wire::{WirePointRepr, cast_slice, cast_slice_mut};
7use crate::data::atlas::Atlas;
8use crate::data::coordinates::{Coordinates, HighOrderCoordinates};
9use crate::data::discretization::Discretization;
10use crate::data::global_map::LocalToGlobalMap;
11use crate::data::mixed_section::{MixedSectionStore, TaggedSection};
12use crate::data::section::Section;
13use crate::data::storage::Storage;
14use crate::io::MeshData;
15use crate::mesh_error::MeshSieveError;
16use crate::overlap::delta::{CellTypeDelta, CopyDelta};
17use crate::overlap::overlap::{Overlap, OvlId};
18use crate::overlap::overlap::{ensure_closure_of_support, expand_one_layer_mesh};
19use crate::topology::cell_type::CellType;
20use crate::topology::labels::LabelSet;
21use crate::topology::ownership::PointOwnership;
22use crate::topology::periodic::PointEquivalence;
23use crate::topology::point::PointId;
24use crate::topology::sieve::{MeshSieve, OrientedSieve, Sieve};
25use crate::topology::validation::debug_validate_overlap_ownership_topology;
26use bytemuck::Zeroable;
27use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
28
29/// Distribute a global mesh across ranks, returning the local submesh and overlap graph.
30///
31/// Phase A of distribution: extract the local topology and create structural overlap
32/// links (`Local(p) -> Part(r)`), leaving `remote_point` unresolved for later phases.
33///
34/// # Arguments
35/// - `mesh`: the full global mesh (arrows of type `Payload = ()`)
36/// - `parts`: mapping each `PointId` (1-based) to an owning rank
37/// - `comm`: communicator providing `rank()` and `size()`
38///
39/// # Returns
40/// `(local_mesh, overlap)` where:
41/// - `local_mesh`: only arrows whose endpoints are both owned by this rank
42/// - `overlap`: bipartite `Local(p) -> Part(r)` links for every foreign point
43///
44/// ## Phases
45/// - **Phase A (here):** extract local topology and build structural overlap.
46/// - **Phase B (later):** expand overlap via mesh closure rules.
47/// - **Phase C (later):** resolve remote IDs via exchange/service.
48/// - **Phase D (later):** complete section/stack data using the overlap.
49///
50/// # Example (serial)
51/// ```rust
52/// use mesh_sieve::algs::communicator::NoComm;
53/// use mesh_sieve::topology::sieve::{InMemorySieve, Sieve};
54/// use mesh_sieve::topology::point::PointId;
55/// use mesh_sieve::algs::distribute_mesh;
56/// let mut global = InMemorySieve::<PointId,()>::default();
57/// global.add_arrow(PointId::new(1).unwrap(), PointId::new(2).unwrap(), ());
58/// global.add_arrow(PointId::new(2).unwrap(), PointId::new(3).unwrap(), ());
59/// let parts = vec![0, 1, 1];
60/// let comm = NoComm;
61/// let (_local, overlap) = distribute_mesh(&global, &parts, &comm).unwrap();
62/// let ranks: Vec<_> = overlap.neighbor_ranks().collect();
63/// assert!(ranks.contains(&1));
64/// let links: Vec<_> = overlap.links_to(1).collect();
65/// assert!(links
66///     .iter()
67///     .any(|(p, rp)| *p == PointId::new(3).unwrap() && rp.is_none()));
68/// ```
69/// # Example (MPI)
70/// ```ignore
71/// #![cfg(feature="mpi-support")]
72/// use mesh_sieve::algs::communicator::MpiComm;
73/// // ... same as above, but use MpiComm::new() and run with mpirun -n 2 ...
74/// ```
75pub fn distribute_mesh<M, C>(
76    mesh: &M,
77    parts: &[usize],
78    comm: &C,
79) -> Result<(MeshSieve, Overlap), MeshSieveError>
80where
81    M: OrientedSieve<Point = PointId, Payload = (), Orient = i32>,
82    C: Communicator + Sync,
83{
84    let my_rank = comm.rank();
85
86    // ---------- Pass 0: collect points and validate `parts` ----------
87    let mut max_id = 0u64;
88    let pts: Vec<PointId> = mesh
89        .points()
90        .inspect(|p| max_id = max_id.max(p.get()))
91        .collect();
92    if parts.len() < max_id as usize {
93        return Err(MeshSieveError::PartitionIndexOutOfBounds(parts.len()));
94    }
95
96    // ---------- Pass 1: collect foreign points ----------
97    let mut foreign_pts: Vec<(PointId, usize)> = Vec::new();
98    foreign_pts.reserve(pts.len() / 2);
99
100    for p in &pts {
101        let owner = owner_of(parts, *p)?;
102        if owner != my_rank {
103            foreign_pts.push((*p, owner));
104        }
105    }
106
107    // ---------- Build Overlap ----------
108    let mut overlap = Overlap::default();
109    overlap.try_add_links_structural_bulk(foreign_pts)?;
110
111    #[cfg(any(
112        debug_assertions,
113        feature = "strict-invariants",
114        feature = "check-invariants"
115    ))]
116    overlap.validate_invariants()?;
117
118    // ---------- Build local submesh ----------
119    let mut local = MeshSieve::default();
120    for src in &pts {
121        if owner_of(parts, *src)? == my_rank {
122            for (dst, orient) in mesh.cone_o(*src) {
123                if owner_of(parts, dst)? == my_rank {
124                    local.add_arrow_o(*src, dst, (), orient)?;
125                }
126            }
127        }
128    }
129
130    Ok((local, overlap))
131}
132
133/// Only for single-process demos/tests: set `remote_point = Some(local_p)` for all links.
134pub fn resolve_overlap_identity(overlap: &mut Overlap) {
135    let mut to_resolve = Vec::new();
136    for src in overlap.base_points() {
137        if let OvlId::Local(p) = src {
138            for (dst, rem) in overlap.cone(src) {
139                if let OvlId::Part(r) = dst {
140                    debug_assert_eq!(rem.rank, r);
141                    to_resolve.push((p, r));
142                }
143            }
144        }
145    }
146    for (p, r) in to_resolve {
147        overlap
148            .resolve_remote_point(p, r, p)
149            .expect("resolve_remote_point failed");
150    }
151}
152
153/// Configuration for overlap-aware distribution.
154#[derive(Clone, Copy, Debug)]
155pub struct DistributionConfig {
156    /// Number of ghost layers to include around the partition boundary.
157    pub overlap_depth: usize,
158    /// Whether to copy global section-like data onto ghost points.
159    pub synchronize_sections: bool,
160    /// Balance ownership of partition-boundary points across sharing ranks.
161    pub balance_boundary_ownership: bool,
162}
163
164impl Default for DistributionConfig {
165    fn default() -> Self {
166        Self {
167            overlap_depth: 1,
168            synchronize_sections: true,
169            balance_boundary_ownership: false,
170        }
171    }
172}
173
174/// Result of distributing a mesh plus associated data.
175#[derive(Debug)]
176pub struct DistributedMeshData<V, St, CtSt>
177where
178    St: Storage<V> + Clone,
179    CtSt: Storage<CellType> + Clone,
180{
181    /// Local topology with ghost layers included.
182    pub sieve: MeshSieve,
183    /// Overlap graph with resolved remote IDs.
184    pub overlap: Overlap,
185    /// Point owners derived from the cell partition.
186    pub point_owners: Vec<usize>,
187    /// Ownership metadata for local points, including ghost status.
188    pub ownership: PointOwnership,
189    /// Cell partition assignment used for distribution.
190    pub cell_parts: Vec<usize>,
191    /// Optional coordinate section for local points.
192    pub coordinates: Option<Coordinates<V, St>>,
193    /// Named local sections for distributed points.
194    pub sections: BTreeMap<String, Section<V, St>>,
195    /// Tagged local sections with mixed scalar types.
196    pub mixed_sections: MixedSectionStore,
197    /// Point labels filtered to the local point set.
198    pub labels: Option<LabelSet>,
199    /// Optional cell-type section for local points.
200    pub cell_types: Option<Section<CellType, CtSt>>,
201    /// Optional discretization metadata keyed by regions.
202    pub discretization: Option<Discretization>,
203}
204
205impl<V, St, CtSt> DistributedMeshData<V, St, CtSt>
206where
207    St: Storage<V> + Clone,
208    CtSt: Storage<CellType> + Clone,
209{
210    /// Build a global DOF map for a specific local section using ownership metadata.
211    pub fn build_global_map_for_section<C>(
212        &self,
213        section: &Section<V, St>,
214        comm: &C,
215    ) -> Result<LocalToGlobalMap, MeshSieveError>
216    where
217        C: Communicator + Sync,
218    {
219        LocalToGlobalMap::from_section_with_ownership(
220            section,
221            &self.overlap,
222            &self.ownership,
223            comm,
224            comm.rank(),
225        )
226    }
227
228    /// Build global DOF maps for all named sections in this distributed mesh.
229    pub fn build_global_section_maps<C>(
230        &self,
231        comm: &C,
232    ) -> Result<BTreeMap<String, LocalToGlobalMap>, MeshSieveError>
233    where
234        C: Communicator + Sync,
235    {
236        let mut maps = BTreeMap::new();
237        for (name, section) in &self.sections {
238            let map = self.build_global_map_for_section(section, comm)?;
239            maps.insert(name.clone(), map);
240        }
241        Ok(maps)
242    }
243
244    /// Update ghost values for all registered sections and labels.
245    pub fn distribute_fields<C>(&mut self, comm: &C) -> Result<(), MeshSieveError>
246    where
247        C: Communicator + Sync,
248        V: Clone + Default + Send + PartialEq + bytemuck::Pod + 'static,
249    {
250        let sf = PointSF::with_ownership(&self.overlap, &self.ownership, comm, comm.rank());
251        sf.validate()?;
252
253        if let Some(coords) = &mut self.coordinates {
254            sf.complete_section(coords.section_mut())?;
255            if let Some(high_order) = coords.high_order_mut() {
256                sf.complete_section(high_order.section_mut())?;
257            }
258        }
259
260        for section in self.sections.values_mut() {
261            sf.complete_section(section)?;
262        }
263
264        for (_name, section) in self.mixed_sections.iter_mut() {
265            complete_tagged_section_with_ownership(
266                section,
267                &self.overlap,
268                &self.ownership,
269                comm,
270                comm.rank(),
271            )?;
272        }
273
274        if let Some(cell_types) = &mut self.cell_types {
275            complete_section_with_ownership::<CellType, _, CellTypeDelta, C>(
276                cell_types,
277                &self.overlap,
278                &self.ownership,
279                comm,
280                comm.rank(),
281            )?;
282        }
283
284        if let Some(labels) = &mut self.labels {
285            complete_labels_with_ownership(
286                labels,
287                &self.overlap,
288                &self.ownership,
289                comm,
290                comm.rank(),
291            )?;
292        }
293
294        Ok(())
295    }
296}
297
298/// Partition hook for distributing cell-based meshes.
299pub trait CellPartitioner<M>
300where
301    M: Sieve<Point = PointId, Payload = ()>,
302{
303    /// Return a partition index for each input cell.
304    fn partition_cells(
305        &self,
306        mesh: &M,
307        cells: &[PointId],
308        n_parts: usize,
309    ) -> Result<Vec<usize>, MeshSieveError>;
310}
311
312/// Use a precomputed cell partition.
313pub struct ProvidedPartition<'a> {
314    pub parts: &'a [usize],
315}
316
317impl<M> CellPartitioner<M> for ProvidedPartition<'_>
318where
319    M: Sieve<Point = PointId, Payload = ()>,
320{
321    fn partition_cells(
322        &self,
323        _mesh: &M,
324        cells: &[PointId],
325        _n_parts: usize,
326    ) -> Result<Vec<usize>, MeshSieveError> {
327        if self.parts.len() != cells.len() {
328            return Err(MeshSieveError::PartitionIndexOutOfBounds(self.parts.len()));
329        }
330        Ok(self.parts.to_vec())
331    }
332}
333
334/// Use a custom partitioner callback.
335pub struct CustomPartitioner<F>(pub F);
336
337impl<M, F> CellPartitioner<M> for CustomPartitioner<F>
338where
339    M: Sieve<Point = PointId, Payload = ()>,
340    F: Fn(&M, &[PointId], usize) -> Result<Vec<usize>, MeshSieveError>,
341{
342    fn partition_cells(
343        &self,
344        mesh: &M,
345        cells: &[PointId],
346        n_parts: usize,
347    ) -> Result<Vec<usize>, MeshSieveError> {
348        (self.0)(mesh, cells, n_parts)
349    }
350}
351
352/// Partition cells using METIS (requires the `metis-support` feature).
353#[cfg(feature = "metis-support")]
354pub struct MetisPartitioner;
355
356#[cfg(feature = "metis-support")]
357impl<M> CellPartitioner<M> for MetisPartitioner
358where
359    M: Sieve<Point = PointId, Payload = ()>,
360{
361    fn partition_cells(
362        &self,
363        mesh: &M,
364        cells: &[PointId],
365        n_parts: usize,
366    ) -> Result<Vec<usize>, MeshSieveError> {
367        let dual = crate::algs::dual_graph::build_dual(mesh, cells.to_vec());
368        let partition = dual.metis_partition(
369            n_parts
370                .try_into()
371                .map_err(|_| MeshSieveError::PartitionIndexOutOfBounds(n_parts))?,
372        );
373        Ok(partition.part.into_iter().map(|p| p as usize).collect())
374    }
375}
376
377/// High-level distribution with overlap expansion and data synchronization.
378///
379/// This orchestrates:
380/// 1. Cell partitioning (METIS or custom hook),
381/// 2. Point-owner assignment,
382/// 3. Ghost-layer construction to the requested depth,
383/// 4. Overlap resolution (remote IDs),
384/// 5. Optional synchronization of sections/coordinates/cell types by copying
385///    global data onto local ghost points.
386///
387/// Labels are filtered directly to local points so they survive redistribution
388/// even when no synchronization is required.
389///
390/// # Assumptions
391/// This helper assumes all ranks share a consistent global `PointId` space, so
392/// remote IDs are resolved using the identity mapping.
393pub fn distribute_with_overlap<M, V, St, CtSt, C, P>(
394    mesh_data: &MeshData<M, V, St, CtSt>,
395    cells: &[PointId],
396    partitioner: &P,
397    config: DistributionConfig,
398    comm: &C,
399) -> Result<DistributedMeshData<V, St, CtSt>, MeshSieveError>
400where
401    M: OrientedSieve<Point = PointId, Payload = (), Orient = i32>,
402    V: Clone + Default + Send + PartialEq + bytemuck::Pod + 'static,
403    St: Storage<V> + Clone,
404    CtSt: Storage<CellType> + Clone,
405    C: Communicator + Sync,
406    P: CellPartitioner<M>,
407{
408    distribute_with_overlap_periodic(mesh_data, cells, partitioner, config, comm, None)
409}
410
411/// High-level distribution with overlap expansion and periodic equivalence support.
412///
413/// When `periodic` is supplied, overlap construction and ghost expansion treat
414/// periodic point equivalences as adjacency, and resolved overlap links map to
415/// the periodic counterpart on neighboring ranks.
416pub fn distribute_with_overlap_periodic<M, V, St, CtSt, C, P>(
417    mesh_data: &MeshData<M, V, St, CtSt>,
418    cells: &[PointId],
419    partitioner: &P,
420    config: DistributionConfig,
421    comm: &C,
422    periodic: Option<&PointEquivalence>,
423) -> Result<DistributedMeshData<V, St, CtSt>, MeshSieveError>
424where
425    M: OrientedSieve<Point = PointId, Payload = (), Orient = i32>,
426    V: Clone + Default + Send + PartialEq + bytemuck::Pod + 'static,
427    St: Storage<V> + Clone,
428    CtSt: Storage<CellType> + Clone,
429    C: Communicator + Sync,
430    P: CellPartitioner<M>,
431{
432    let my_rank = comm.rank();
433    let n_ranks = comm.size().max(1);
434    let cell_parts = partitioner.partition_cells(&mesh_data.sieve, cells, n_ranks)?;
435    if cell_parts.len() != cells.len() {
436        return Err(MeshSieveError::PartitionIndexOutOfBounds(cell_parts.len()));
437    }
438    if cell_parts.iter().any(|&p| p >= n_ranks) {
439        let bad = cell_parts.iter().copied().max().unwrap_or(0);
440        return Err(MeshSieveError::PartitionIndexOutOfBounds(bad));
441    }
442
443    let points: Vec<PointId> = mesh_data.sieve.points().collect();
444    let max_id = points.iter().map(|p| p.get()).max().unwrap_or(0) as usize;
445    let mut point_owners = assign_point_owners(mesh_data, cells, &cell_parts, max_id)?;
446    if config.balance_boundary_ownership {
447        let sharing = build_point_sharing(mesh_data, cells, &cell_parts)?;
448        balance_partition_boundary_ownership(&mut point_owners, &sharing, n_ranks)?;
449    }
450
451    let periodic_classes = periodic
452        .map(|eq| build_periodic_classes(&points, eq))
453        .unwrap_or_default();
454    let periodic_remote_map =
455        build_periodic_remote_map(&periodic_classes, &point_owners, point_owners.len());
456
457    let adjacency = build_adjacency(&mesh_data.sieve, max_id, &periodic_classes);
458    let use_comm_completion = should_use_comm_completion(comm);
459
460    let mut owned_set = BTreeSet::new();
461    for &p in &points {
462        let idx = (p.get() - 1) as usize;
463        if point_owners.get(idx).copied().unwrap_or(0) == my_rank {
464            owned_set.insert(p);
465        }
466    }
467
468    let mut overlap = Overlap::default();
469    let mut frontier: BTreeMap<usize, BTreeSet<PointId>> = BTreeMap::new();
470    if config.overlap_depth > 0 {
471        for &p in &owned_set {
472            let p_idx = (p.get() - 1) as usize;
473            for q in adjacency.get(p_idx).into_iter().flat_map(|s| s.iter()) {
474                let q_idx = (q.get() - 1) as usize;
475                let owner_q = point_owners.get(q_idx).copied().unwrap_or(0);
476                if owner_q != my_rank {
477                    overlap.try_add_link_structural_one(p, owner_q)?;
478                    frontier.entry(owner_q).or_default().insert(p);
479                }
480            }
481        }
482
483        ensure_closure_of_support(&mut overlap, &mesh_data.sieve);
484
485        for _layer in 0..config.overlap_depth {
486            if frontier.is_empty() {
487                break;
488            }
489            let mut next_frontier: BTreeMap<usize, BTreeSet<PointId>> = BTreeMap::new();
490            for (&nbr, seeds) in &frontier {
491                if seeds.is_empty() {
492                    continue;
493                }
494                let before: BTreeSet<_> = overlap.links_to(nbr).map(|(p, _)| p).collect();
495                expand_one_layer_mesh(&mut overlap, &mesh_data.sieve, seeds.iter().copied(), nbr);
496                let after: BTreeSet<_> = overlap.links_to(nbr).map(|(p, _)| p).collect();
497                let added: BTreeSet<_> = after.difference(&before).copied().collect();
498                if !added.is_empty() {
499                    next_frontier.insert(nbr, added);
500                }
501            }
502            frontier = next_frontier;
503        }
504    }
505
506    resolve_overlap_via_exchange(
507        &mut overlap,
508        comm,
509        my_rank,
510        Some(&periodic_remote_map),
511        use_comm_completion,
512    )?;
513    ensure_periodic_remote_links(&mut overlap)?;
514
515    let mut local_set = owned_set.clone();
516    for nbr in overlap.neighbor_ranks() {
517        for (p, remote) in overlap.links_to(nbr) {
518            local_set.insert(p);
519            if let Some(remote_point) = remote {
520                local_set.insert(remote_point);
521            }
522        }
523    }
524
525    let local_sieve = if use_comm_completion {
526        let mut local = build_local_sieve(mesh_data, &local_set)?;
527        complete_sieve(&mut local, &overlap, comm, my_rank)?;
528        local
529    } else {
530        build_local_sieve(mesh_data, &local_set)?
531    };
532
533    let local_points: BTreeSet<_> = local_sieve.points().collect();
534    let owned_points: BTreeSet<_> = local_points
535        .iter()
536        .copied()
537        .filter(|p| {
538            let idx = (p.get() - 1) as usize;
539            point_owners.get(idx).copied().unwrap_or(0) == my_rank
540        })
541        .collect();
542
543    let ownership =
544        PointOwnership::from_local_set(local_points.iter().copied(), &point_owners, my_rank)?;
545
546    debug_validate_overlap_ownership_topology(&local_sieve, &ownership, Some(&overlap), my_rank)?;
547
548    let section_points = if config.synchronize_sections {
549        &local_points
550    } else {
551        &owned_points
552    };
553    let copy_all_sections = config.synchronize_sections && !use_comm_completion;
554
555    let labels = mesh_data
556        .labels
557        .as_ref()
558        .map(|l| l.filtered_to_points(section_points.iter().copied()));
559
560    let coordinates = match &mesh_data.coordinates {
561        Some(coords) => {
562            let mut section = if copy_all_sections {
563                build_local_section_full(coords.section(), section_points)?
564            } else {
565                build_local_section_owned(coords.section(), section_points, &owned_points)?
566            };
567            if config.synchronize_sections && use_comm_completion {
568                complete_section_with_ownership::<V, St, CopyDelta, C>(
569                    &mut section,
570                    &overlap,
571                    &ownership,
572                    comm,
573                    my_rank,
574                )?;
575            }
576            let mut out = Coordinates::from_section(
577                coords.topological_dimension(),
578                coords.embedding_dimension(),
579                section,
580            )?;
581            if let Some(high_order) = coords.high_order() {
582                let mut ho_section = if copy_all_sections {
583                    build_local_section_full(high_order.section(), section_points)?
584                } else {
585                    build_local_section_owned(high_order.section(), section_points, &owned_points)?
586                };
587                if config.synchronize_sections && use_comm_completion {
588                    complete_section_with_ownership::<V, St, CopyDelta, C>(
589                        &mut ho_section,
590                        &overlap,
591                        &ownership,
592                        comm,
593                        my_rank,
594                    )?;
595                }
596                let ho = HighOrderCoordinates::from_section(high_order.dimension(), ho_section)?;
597                out.set_high_order(ho)?;
598            }
599            Some(out)
600        }
601        None => None,
602    };
603
604    let mut sections = BTreeMap::new();
605    for (name, section) in &mesh_data.sections {
606        let mut local_section = if copy_all_sections {
607            build_local_section_full(section, section_points)?
608        } else {
609            build_local_section_owned(section, section_points, &owned_points)?
610        };
611        if config.synchronize_sections && use_comm_completion {
612            complete_section_with_ownership::<V, St, CopyDelta, C>(
613                &mut local_section,
614                &overlap,
615                &ownership,
616                comm,
617                my_rank,
618            )?;
619        }
620        sections.insert(name.clone(), local_section);
621    }
622
623    let mut mixed_sections = MixedSectionStore::default();
624    for (name, section) in mesh_data.mixed_sections.iter() {
625        let mut local_section = if copy_all_sections {
626            build_local_tagged_section(section, section_points, section_points)?
627        } else {
628            build_local_tagged_section(section, section_points, &owned_points)?
629        };
630        if config.synchronize_sections && use_comm_completion {
631            complete_tagged_section_with_ownership(
632                &mut local_section,
633                &overlap,
634                &ownership,
635                comm,
636                my_rank,
637            )?;
638        }
639        mixed_sections.insert_tagged(name.clone(), local_section);
640    }
641
642    let cell_types = match &mesh_data.cell_types {
643        Some(section) => {
644            let local_section = if config.synchronize_sections {
645                build_local_section_full(section, section_points)?
646            } else {
647                build_local_section_owned(section, section_points, &owned_points)?
648            };
649            Some(local_section)
650        }
651        None => None,
652    };
653
654    Ok(DistributedMeshData {
655        sieve: local_sieve,
656        overlap,
657        point_owners,
658        ownership,
659        cell_parts,
660        coordinates,
661        sections,
662        mixed_sections,
663        labels,
664        cell_types,
665        discretization: mesh_data.discretization.clone(),
666    })
667}
668
669fn assign_point_owners<M, V, St, CtSt>(
670    mesh_data: &MeshData<M, V, St, CtSt>,
671    cells: &[PointId],
672    cell_parts: &[usize],
673    max_id: usize,
674) -> Result<Vec<usize>, MeshSieveError>
675where
676    M: OrientedSieve<Point = PointId, Payload = (), Orient = i32>,
677    St: Storage<V> + Clone,
678    CtSt: Storage<CellType> + Clone,
679{
680    let mut owners = vec![0usize; max_id];
681    let mut seen = vec![false; max_id];
682    for (cell, &part) in cells.iter().zip(cell_parts.iter()) {
683        for (p, _) in mesh_data.sieve.closure_o(std::iter::once(*cell)) {
684            let idx = p
685                .get()
686                .checked_sub(1)
687                .ok_or(MeshSieveError::PartitionIndexOutOfBounds(p.get() as usize))?
688                as usize;
689            if idx >= owners.len() {
690                return Err(MeshSieveError::PartitionIndexOutOfBounds(idx));
691            }
692            if !seen[idx] || part < owners[idx] {
693                owners[idx] = part;
694                seen[idx] = true;
695            }
696        }
697    }
698    Ok(owners)
699}
700
701fn build_point_sharing<M, V, St, CtSt>(
702    mesh_data: &MeshData<M, V, St, CtSt>,
703    cells: &[PointId],
704    cell_parts: &[usize],
705) -> Result<BTreeMap<PointId, BTreeSet<usize>>, MeshSieveError>
706where
707    M: OrientedSieve<Point = PointId, Payload = (), Orient = i32>,
708    St: Storage<V> + Clone,
709    CtSt: Storage<CellType> + Clone,
710{
711    let mut sharing: BTreeMap<PointId, BTreeSet<usize>> = BTreeMap::new();
712    for (cell, &part) in cells.iter().zip(cell_parts.iter()) {
713        for (p, _) in mesh_data.sieve.closure_o(std::iter::once(*cell)) {
714            sharing.entry(p).or_default().insert(part);
715        }
716    }
717    Ok(sharing)
718}
719
720fn build_adjacency<M>(
721    mesh: &M,
722    max_id: usize,
723    periodic_classes: &BTreeMap<PointId, Vec<PointId>>,
724) -> Vec<BTreeSet<PointId>>
725where
726    M: OrientedSieve<Point = PointId, Payload = (), Orient = i32>,
727{
728    let mut adjacency = vec![BTreeSet::new(); max_id];
729    for src in mesh.points() {
730        let src_idx = (src.get() - 1) as usize;
731        for (dst, _) in mesh.cone_o(src) {
732            let dst_idx = (dst.get() - 1) as usize;
733            if src_idx < max_id {
734                adjacency[src_idx].insert(dst);
735            }
736            if dst_idx < max_id {
737                adjacency[dst_idx].insert(src);
738            }
739        }
740    }
741    apply_periodic_adjacency(&mut adjacency, periodic_classes, max_id);
742    adjacency
743}
744
745fn build_periodic_classes(
746    points: &[PointId],
747    periodic: &PointEquivalence,
748) -> BTreeMap<PointId, Vec<PointId>> {
749    let mut eq = periodic.clone();
750    eq.classes(points.iter().copied())
751}
752
753fn apply_periodic_adjacency(
754    adjacency: &mut [BTreeSet<PointId>],
755    periodic_classes: &BTreeMap<PointId, Vec<PointId>>,
756    max_id: usize,
757) {
758    for class in periodic_classes.values() {
759        if class.len() < 2 {
760            continue;
761        }
762        for i in 0..class.len() {
763            for j in (i + 1)..class.len() {
764                let a = class[i];
765                let b = class[j];
766                let a_idx = (a.get().saturating_sub(1)) as usize;
767                let b_idx = (b.get().saturating_sub(1)) as usize;
768                if a_idx < max_id {
769                    adjacency[a_idx].insert(b);
770                }
771                if b_idx < max_id {
772                    adjacency[b_idx].insert(a);
773                }
774            }
775        }
776    }
777}
778
779fn build_periodic_remote_map(
780    periodic_classes: &BTreeMap<PointId, Vec<PointId>>,
781    owners: &[usize],
782    max_id: usize,
783) -> BTreeMap<(PointId, usize), PointId> {
784    let mut remote_map = BTreeMap::new();
785    for class in periodic_classes.values() {
786        if class.len() < 2 {
787            continue;
788        }
789        let mut rank_to_point: BTreeMap<usize, PointId> = BTreeMap::new();
790        for &p in class {
791            let idx = (p.get().saturating_sub(1)) as usize;
792            if idx >= max_id {
793                continue;
794            }
795            let owner = owners.get(idx).copied().unwrap_or(0);
796            rank_to_point
797                .entry(owner)
798                .and_modify(|existing| {
799                    if p < *existing {
800                        *existing = p;
801                    }
802                })
803                .or_insert(p);
804        }
805        for &p in class {
806            let idx = (p.get().saturating_sub(1)) as usize;
807            if idx >= max_id {
808                continue;
809            }
810            let owner = owners.get(idx).copied().unwrap_or(0);
811            for (&rank, &remote_point) in &rank_to_point {
812                if rank != owner {
813                    remote_map.insert((p, rank), remote_point);
814                }
815            }
816        }
817    }
818    remote_map
819}
820
821fn ensure_periodic_remote_links(overlap: &mut Overlap) -> Result<(), MeshSieveError> {
822    let mut extras: Vec<(PointId, usize)> = Vec::new();
823    for nbr in overlap.neighbor_ranks() {
824        for (local, remote) in overlap.links_to(nbr) {
825            if let Some(remote_point) = remote
826                && remote_point != local
827            {
828                extras.push((remote_point, nbr));
829            }
830        }
831    }
832    extras.sort_unstable();
833    extras.dedup();
834    for (local, nbr) in extras {
835        // The reverse periodic point may already have been inserted and
836        // resolved by the overlap exchange.  In that case its resolved
837        // counterpart is authoritative; trying to reinsert the structural
838        // payload (`remote_point = None`) would be a genuine payload
839        // conflict under the strict relation identity rules.  Only create
840        // missing links, or resolve an as-yet-unresolved structural link.
841        let existing = overlap
842            .links_to(nbr)
843            .find(|(point, _)| *point == local)
844            .map(|(_, remote)| remote);
845        match existing {
846            Some(Some(_)) => continue,
847            Some(None) => overlap.resolve_remote_point(local, nbr, local)?,
848            None => {
849                overlap.try_add_link_structural_one(local, nbr)?;
850                overlap.resolve_remote_point(local, nbr, local)?;
851            }
852        }
853    }
854    Ok(())
855}
856
857fn build_local_sieve<M, V, St, CtSt>(
858    mesh_data: &MeshData<M, V, St, CtSt>,
859    local_set: &BTreeSet<PointId>,
860) -> Result<MeshSieve, MeshSieveError>
861where
862    M: OrientedSieve<Point = PointId, Payload = (), Orient = i32>,
863    St: Storage<V> + Clone,
864    CtSt: Storage<CellType> + Clone,
865{
866    let mut local = MeshSieve::default();
867    for &point in local_set {
868        local.add_point(point);
869    }
870    let points: Vec<PointId> = mesh_data.sieve.points().collect();
871    for src in &points {
872        if !local_set.contains(src) {
873            continue;
874        }
875        for (dst, orient) in mesh_data.sieve.cone_o(*src) {
876            if local_set.contains(&dst) {
877                local.add_arrow_o(*src, dst, (), orient)?;
878            }
879        }
880    }
881    Ok(local)
882}
883
884fn build_local_section_owned<V, St>(
885    section: &Section<V, St>,
886    local_points: &BTreeSet<PointId>,
887    owned_points: &BTreeSet<PointId>,
888) -> Result<Section<V, St>, MeshSieveError>
889where
890    V: Clone + Default,
891    St: Storage<V> + Clone,
892{
893    let mut atlas = Atlas::default();
894    for p in local_points {
895        if let Some((_off, len)) = section.atlas().get(*p) {
896            atlas.try_insert(*p, len)?;
897        }
898    }
899    let mut local_section = Section::<V, St>::new(atlas);
900    for p in owned_points {
901        if local_section.atlas().get(*p).is_none() {
902            continue;
903        }
904        let data = section.try_restrict(*p)?;
905        local_section.try_set(*p, data)?;
906    }
907    Ok(local_section)
908}
909
910fn build_local_section_full<V, St>(
911    section: &Section<V, St>,
912    local_points: &BTreeSet<PointId>,
913) -> Result<Section<V, St>, MeshSieveError>
914where
915    V: Clone + Default,
916    St: Storage<V> + Clone,
917{
918    build_local_section_owned(section, local_points, local_points)
919}
920
921fn build_local_tagged_section(
922    section: &TaggedSection,
923    local_points: &BTreeSet<PointId>,
924    owned_points: &BTreeSet<PointId>,
925) -> Result<TaggedSection, MeshSieveError> {
926    Ok(match section {
927        TaggedSection::F64(sec) => {
928            TaggedSection::F64(build_local_section_owned(sec, local_points, owned_points)?)
929        }
930        TaggedSection::F32(sec) => {
931            TaggedSection::F32(build_local_section_owned(sec, local_points, owned_points)?)
932        }
933        TaggedSection::I32(sec) => {
934            TaggedSection::I32(build_local_section_owned(sec, local_points, owned_points)?)
935        }
936        TaggedSection::I64(sec) => {
937            TaggedSection::I64(build_local_section_owned(sec, local_points, owned_points)?)
938        }
939        TaggedSection::U32(sec) => {
940            TaggedSection::U32(build_local_section_owned(sec, local_points, owned_points)?)
941        }
942        TaggedSection::U64(sec) => {
943            TaggedSection::U64(build_local_section_owned(sec, local_points, owned_points)?)
944        }
945    })
946}
947
948fn complete_tagged_section_with_ownership<C>(
949    section: &mut TaggedSection,
950    overlap: &Overlap,
951    ownership: &PointOwnership,
952    comm: &C,
953    my_rank: usize,
954) -> Result<(), MeshSieveError>
955where
956    C: Communicator + Sync,
957{
958    match section {
959        TaggedSection::F64(sec) => complete_section_with_ownership::<f64, _, CopyDelta, C>(
960            sec, overlap, ownership, comm, my_rank,
961        ),
962        TaggedSection::F32(sec) => complete_section_with_ownership::<f32, _, CopyDelta, C>(
963            sec, overlap, ownership, comm, my_rank,
964        ),
965        TaggedSection::I32(sec) => complete_section_with_ownership::<i32, _, CopyDelta, C>(
966            sec, overlap, ownership, comm, my_rank,
967        ),
968        TaggedSection::I64(sec) => complete_section_with_ownership::<i64, _, CopyDelta, C>(
969            sec, overlap, ownership, comm, my_rank,
970        ),
971        TaggedSection::U32(sec) => complete_section_with_ownership::<u32, _, CopyDelta, C>(
972            sec, overlap, ownership, comm, my_rank,
973        ),
974        TaggedSection::U64(sec) => complete_section_with_ownership::<u64, _, CopyDelta, C>(
975            sec, overlap, ownership, comm, my_rank,
976        ),
977    }
978}
979
980fn complete_labels_with_ownership<C>(
981    labels: &mut LabelSet,
982    overlap: &Overlap,
983    ownership: &PointOwnership,
984    comm: &C,
985    my_rank: usize,
986) -> Result<(), MeshSieveError>
987where
988    C: Communicator + Sync,
989{
990    #[cfg(any(
991        debug_assertions,
992        feature = "strict-invariants",
993        feature = "check-invariants"
994    ))]
995    overlap.validate_invariants()?;
996
997    let mut nb: BTreeSet<usize> = overlap.neighbor_ranks().collect();
998    nb.remove(&my_rank);
999    if nb.is_empty() {
1000        return Ok(());
1001    }
1002
1003    labels.clear_points(ownership.ghost_points());
1004
1005    let mut send_payloads: HashMap<usize, Vec<u8>> = HashMap::new();
1006    for (name, point, value) in labels.iter() {
1007        let owner = ownership.owner_or_err(point)?;
1008        if owner != my_rank {
1009            continue;
1010        }
1011        let name_len: u16 = name
1012            .len()
1013            .try_into()
1014            .map_err(|_| MeshSieveError::CommError {
1015                neighbor: my_rank,
1016                source: format!("label name too long: {name}").into(),
1017            })?;
1018        for (_dst, rem) in overlap.cone(crate::overlap::overlap::local(point)) {
1019            if rem.rank == my_rank {
1020                continue;
1021            }
1022            let remote_pt = rem
1023                .remote_point
1024                .ok_or(MeshSieveError::OverlapLinkMissing(point, rem.rank))?;
1025            let payload = send_payloads.entry(rem.rank).or_default();
1026            payload.extend_from_slice(&remote_pt.get().to_le_bytes());
1027            payload.extend_from_slice(&value.to_le_bytes());
1028            payload.extend_from_slice(&name_len.to_le_bytes());
1029            payload.extend_from_slice(name.as_bytes());
1030        }
1031    }
1032
1033    let neighbors: HashSet<usize> = nb.iter().copied().collect();
1034    let tag = CommTag::new(0xD1A5);
1035    let counts = crate::algs::completion::size_exchange::exchange_sizes_symmetric::<_, u8>(
1036        &send_payloads,
1037        comm,
1038        tag,
1039        &neighbors,
1040    )?;
1041
1042    let mut recvs = Vec::new();
1043    for &nbr in &nb {
1044        let n = counts.get(&nbr).copied().unwrap_or(0) as usize;
1045        let mut buf = vec![0u8; n];
1046        let h = comm.irecv_result(nbr, tag.offset(1).as_u16(), &mut buf)?;
1047        recvs.push((nbr, h, buf));
1048    }
1049
1050    let mut sends = Vec::new();
1051    for &nbr in &nb {
1052        let out = send_payloads.get(&nbr).map_or(&[][..], |v| &v[..]);
1053        sends.push(comm.isend_result(nbr, tag.offset(1).as_u16(), out)?);
1054    }
1055
1056    let mut maybe_err: Option<MeshSieveError> = None;
1057    for (nbr, h, mut buf) in recvs {
1058        match h.wait() {
1059            Some(raw) if raw.len() == buf.len() => {
1060                buf.copy_from_slice(&raw);
1061                if let Err(err) = decode_label_payload(&buf, nbr).map(|entries| {
1062                    for (point, name, value) in entries {
1063                        labels.set_label(point, &name, value);
1064                    }
1065                }) && maybe_err.is_none()
1066                {
1067                    maybe_err = Some(err);
1068                }
1069            }
1070            Some(raw) if maybe_err.is_none() => {
1071                maybe_err = Some(MeshSieveError::CommError {
1072                    neighbor: nbr,
1073                    source: format!(
1074                        "label payload size mismatch: expected {}B, got {}B",
1075                        buf.len(),
1076                        raw.len()
1077                    )
1078                    .into(),
1079                });
1080            }
1081            None if maybe_err.is_none() => {
1082                maybe_err = Some(MeshSieveError::CommError {
1083                    neighbor: nbr,
1084                    source: format!("failed to receive label payload from rank {nbr}").into(),
1085                });
1086            }
1087            _ => {}
1088        }
1089    }
1090
1091    for send in sends {
1092        let _ = send.wait();
1093    }
1094
1095    if let Some(err) = maybe_err {
1096        Err(err)
1097    } else {
1098        Ok(())
1099    }
1100}
1101
1102fn decode_label_payload(
1103    buf: &[u8],
1104    neighbor: usize,
1105) -> Result<Vec<(PointId, String, i32)>, MeshSieveError> {
1106    let mut out = Vec::new();
1107    let mut idx = 0usize;
1108    while idx < buf.len() {
1109        let remaining = buf.len() - idx;
1110        if remaining < (8 + 4 + 2) {
1111            return Err(MeshSieveError::CommError {
1112                neighbor,
1113                source: "label payload truncated header".into(),
1114            });
1115        }
1116        let mut raw_id = [0u8; 8];
1117        raw_id.copy_from_slice(&buf[idx..idx + 8]);
1118        idx += 8;
1119        let mut raw_val = [0u8; 4];
1120        raw_val.copy_from_slice(&buf[idx..idx + 4]);
1121        idx += 4;
1122        let mut raw_len = [0u8; 2];
1123        raw_len.copy_from_slice(&buf[idx..idx + 2]);
1124        idx += 2;
1125        let name_len = u16::from_le_bytes(raw_len) as usize;
1126        if idx + name_len > buf.len() {
1127            return Err(MeshSieveError::CommError {
1128                neighbor,
1129                source: "label payload truncated name".into(),
1130            });
1131        }
1132        let name_bytes = &buf[idx..idx + name_len];
1133        idx += name_len;
1134        let name = std::str::from_utf8(name_bytes).map_err(|e| MeshSieveError::CommError {
1135            neighbor,
1136            source: format!("label name invalid utf8: {e}").into(),
1137        })?;
1138        let point = PointId::new(u64::from_le_bytes(raw_id))?;
1139        let value = i32::from_le_bytes(raw_val);
1140        out.push((point, name.to_string(), value));
1141    }
1142    Ok(out)
1143}
1144
1145fn resolve_overlap_via_exchange<C>(
1146    overlap: &mut Overlap,
1147    comm: &C,
1148    my_rank: usize,
1149    periodic_remote_map: Option<&BTreeMap<(PointId, usize), PointId>>,
1150    use_comm_completion: bool,
1151) -> Result<(), MeshSieveError>
1152where
1153    C: Communicator + Sync,
1154{
1155    #[cfg(any(
1156        debug_assertions,
1157        feature = "strict-invariants",
1158        feature = "check-invariants"
1159    ))]
1160    overlap.validate_invariants()?;
1161
1162    let mut nb: BTreeSet<usize> = overlap.neighbor_ranks().collect();
1163    nb.remove(&my_rank);
1164    if nb.is_empty() {
1165        return Ok(());
1166    }
1167
1168    let neighbors: Vec<usize> = nb.into_iter().collect();
1169    if !use_comm_completion || comm.is_no_comm() || comm.size() <= 1 {
1170        for &nbr in &neighbors {
1171            let points: Vec<PointId> = overlap.links_to(nbr).map(|(p, _)| p).collect();
1172            for p in points {
1173                let remote = periodic_remote_map
1174                    .and_then(|map| map.get(&(p, nbr)).copied())
1175                    .unwrap_or(p);
1176                overlap.resolve_remote_point(p, nbr, remote)?;
1177            }
1178        }
1179        return Ok(());
1180    }
1181
1182    let mut send_points: HashMap<usize, Vec<WirePointRepr>> = HashMap::new();
1183    let mut local_points: HashMap<usize, Vec<PointId>> = HashMap::new();
1184    for &nbr in &neighbors {
1185        let mut pts: Vec<PointId> = overlap.links_to(nbr).map(|(p, _)| p).collect();
1186        pts.sort_unstable();
1187        pts.dedup();
1188        let send: Vec<WirePointRepr> = pts.iter().map(|p| WirePointRepr::of(p.get())).collect();
1189        send_points.insert(nbr, send);
1190        local_points.insert(nbr, pts);
1191    }
1192
1193    let all_neighbors: HashSet<usize> = neighbors.iter().copied().collect();
1194    let tag = CommTag::new(0xD00D);
1195    let counts = crate::algs::completion::size_exchange::exchange_sizes_symmetric(
1196        &send_points,
1197        comm,
1198        tag,
1199        &all_neighbors,
1200    )?;
1201
1202    let mut recvs = Vec::new();
1203    for &nbr in &neighbors {
1204        let n = counts.get(&nbr).copied().unwrap_or(0) as usize;
1205        let mut buf = vec![WirePointRepr::zeroed(); n];
1206        let h = comm.irecv_result(nbr, tag.offset(1).as_u16(), cast_slice_mut(&mut buf))?;
1207        recvs.push((nbr, h, buf));
1208    }
1209
1210    let mut sends = Vec::new();
1211    for &nbr in &neighbors {
1212        let out = send_points.get(&nbr).map_or(&[][..], |v| &v[..]);
1213        sends.push(comm.isend_result(nbr, tag.offset(1).as_u16(), cast_slice(out))?);
1214    }
1215
1216    let mut recv_sets: HashMap<usize, HashSet<u64>> = HashMap::new();
1217    let mut maybe_err: Option<MeshSieveError> = None;
1218    for (nbr, h, mut buf) in recvs {
1219        match h.wait() {
1220            Some(raw) if raw.len() == buf.len() * std::mem::size_of::<WirePointRepr>() => {
1221                cast_slice_mut(&mut buf).copy_from_slice(&raw);
1222                let set: HashSet<u64> = buf.iter().map(|p| p.get()).collect();
1223                recv_sets.insert(nbr, set);
1224            }
1225            Some(raw) if maybe_err.is_none() => {
1226                let exp = buf.len() * std::mem::size_of::<WirePointRepr>();
1227                maybe_err = Some(MeshSieveError::CommError {
1228                    neighbor: nbr,
1229                    source: format!("payload size mismatch: expected {exp}B, got {}B", raw.len())
1230                        .into(),
1231                });
1232            }
1233            None if maybe_err.is_none() => {
1234                maybe_err = Some(MeshSieveError::CommError {
1235                    neighbor: nbr,
1236                    source: "recv returned None".into(),
1237                });
1238            }
1239            _ => {}
1240        }
1241    }
1242
1243    for h in sends {
1244        let _ = h.wait();
1245    }
1246
1247    if let Some(err) = maybe_err {
1248        return Err(err);
1249    }
1250
1251    // If overlap construction is asymmetric, prune links that neighbors don't recognize.
1252    let mut to_remove: Vec<(PointId, usize)> = Vec::new();
1253    for (&nbr, points) in &local_points {
1254        let recv_set = recv_sets.get(&nbr).ok_or(MeshSieveError::MissingOverlap {
1255            source: format!("missing neighbor list from rank {nbr}").into(),
1256        })?;
1257        for &p in points {
1258            let remote = periodic_remote_map
1259                .and_then(|map| map.get(&(p, nbr)).copied())
1260                .unwrap_or(p);
1261            if !recv_set.contains(&remote.get()) {
1262                to_remove.push((p, nbr));
1263                continue;
1264            }
1265            overlap.resolve_remote_point(p, nbr, remote)?;
1266        }
1267    }
1268
1269    if !to_remove.is_empty() {
1270        for (p, nbr) in to_remove {
1271            let _ = overlap.remove_link(p, nbr);
1272        }
1273        overlap.prune_empty_parts();
1274    }
1275
1276    Ok(())
1277}
1278
1279fn should_use_comm_completion<C: Communicator + Sync + 'static>(comm: &C) -> bool {
1280    !comm.is_no_comm()
1281        && comm.size() > 1
1282        && std::any::TypeId::of::<C>()
1283            != std::any::TypeId::of::<crate::algs::communicator::RayonComm>()
1284}
1285
1286#[inline]
1287fn owner_of(parts: &[usize], p: PointId) -> Result<usize, MeshSieveError> {
1288    let idx = p
1289        .get()
1290        .checked_sub(1)
1291        .ok_or(MeshSieveError::PartitionIndexOutOfBounds(p.get() as usize))? as usize;
1292    parts
1293        .get(idx)
1294        .copied()
1295        .ok_or(MeshSieveError::PartitionIndexOutOfBounds(idx))
1296}