Skip to main content

mpi/
topology.rs

1//! Groups and communicators. Mirrors `mpi::topology` in rsmpi (0.8.x), where
2//! the former `SystemCommunicator`/`UserCommunicator` split is unified into a
3//! single [`SimpleCommunicator`]. Backwards-compatible aliases for the old
4//! names are provided.
5
6use std::any::Any;
7use std::collections::HashMap;
8use std::sync::atomic::{AtomicU32, Ordering};
9use std::sync::{Arc, Mutex};
10
11use crate::datatype::{Buffer, BufferMut, DatatypeRef};
12use crate::point_to_point::{AnyProcess, Process};
13use crate::transport;
14use crate::{Count, Rank, Tag};
15
16/// Key used to order processes within a colour when splitting a communicator.
17pub type Key = i32;
18
19/// The high context bit reserved to isolate collective traffic from user
20/// point-to-point traffic on the same communicator.
21pub(crate) const COLL_CONTEXT_BIT: u32 = 0x8000_0000;
22
23/// Internal shared description of a communicator: its context id, this
24/// process's rank within it, its size, and the mapping from communicator-local
25/// rank to world rank (used for routing on the transport, which addresses
26/// peers by world rank).
27pub struct CommData {
28    pub(crate) context: u32,
29    pub(crate) rank: Rank,
30    pub(crate) size: Rank,
31    /// `world_ranks[comm_rank] == world_rank`.
32    pub(crate) world_ranks: Vec<i32>,
33    /// Monotonic counter used to derive child-communicator contexts. All
34    /// members increment it in lock-step (collective calls happen in the same
35    /// order on every member), so derived contexts agree across processes.
36    pub(crate) child_seq: AtomicU32,
37    /// The communicator's name (`MPI_Comm_set_name` / `MPI_Comm_get_name`).
38    pub(crate) name: Mutex<Option<String>>,
39    /// Cached attributes, keyed by keyval id (attribute caching).
40    pub(crate) attributes: Mutex<HashMap<i32, Box<dyn Any + Send + Sync>>>,
41}
42
43impl CommData {
44    /// Build the `MPI_COMM_WORLD` description for this process.
45    fn world() -> CommData {
46        let rt = transport::runtime();
47        let size = rt.size;
48        CommData {
49            context: 0,
50            rank: rt.rank,
51            size,
52            world_ranks: (0..size).collect(),
53            child_seq: AtomicU32::new(0),
54            name: Mutex::new(Some("MPI_COMM_WORLD".to_string())),
55            attributes: Mutex::new(HashMap::new()),
56        }
57    }
58
59    /// Context id used for this communicator's collective operations.
60    pub(crate) fn coll_context(&self) -> u32 {
61        self.context | COLL_CONTEXT_BIT
62    }
63
64    /// World rank of a communicator-local rank.
65    pub(crate) fn world_rank(&self, comm_rank: Rank) -> i32 {
66        self.world_ranks[comm_rank as usize]
67    }
68
69    /// An owned copy of this communicator's routing info under a fresh context,
70    /// for running a collective on a background thread (async collectives).
71    pub(crate) fn async_clone(&self, context: u32) -> CommData {
72        CommData {
73            context,
74            rank: self.rank,
75            size: self.size,
76            world_ranks: self.world_ranks.clone(),
77            child_seq: AtomicU32::new(0),
78            name: Mutex::new(None),
79            attributes: Mutex::new(HashMap::new()),
80        }
81    }
82
83    /// Derive a fresh, globally-agreed child context from a discriminator
84    /// (e.g. a colour). All members that pass the same `seq` and `disc`
85    /// compute the same value.
86    pub(crate) fn derive_context(&self, disc: u32) -> u32 {
87        let seq = self.child_seq.fetch_add(1, Ordering::SeqCst);
88        // A small avalanche mix of (parent context, sequence, discriminator).
89        let mut h = self
90            .context
91            .wrapping_mul(0x9E37_79B1)
92            .wrapping_add(seq.wrapping_mul(0x85EB_CA77))
93            .wrapping_add(disc.wrapping_mul(0xC2B2_AE3D));
94        h ^= h >> 15;
95        h = h.wrapping_mul(0x2545_F491);
96        h ^= h >> 13;
97        // Keep the collective bit clear; it is added on demand.
98        h & !COLL_CONTEXT_BIT
99    }
100}
101
102/// How two communicators relate (`MPI_Comm_compare`).
103#[derive(Copy, Clone, Debug, PartialEq, Eq)]
104pub enum CommunicatorRelation {
105    /// The communicators are handles to the same object.
106    Identical,
107    /// Same group and rank order, different context.
108    Congruent,
109    /// Same members, different rank order.
110    Similar,
111    /// The groups differ.
112    Unequal,
113}
114
115/// How two groups relate (`MPI_Group_compare`).
116#[derive(Copy, Clone, Debug, PartialEq, Eq)]
117pub enum GroupRelation {
118    /// Same members in the same order.
119    Identical,
120    /// Same members in a different order.
121    Similar,
122    /// The members differ.
123    Unequal,
124}
125
126/// A colour used by [`Communicator::split_by_color`]. `undefined` processes are
127/// dropped from the split (they receive `None`).
128#[derive(Copy, Clone, Debug, PartialEq, Eq)]
129pub struct Color(Option<i32>);
130
131impl Color {
132    /// A concrete colour value; processes sharing a value end up together.
133    pub fn with_value(value: Rank) -> Color {
134        Color(Some(value))
135    }
136    /// The "undefined" colour (`MPI_UNDEFINED`); such a process is excluded.
137    pub fn undefined() -> Color {
138        Color(None)
139    }
140    fn value(&self) -> Option<i32> {
141        self.0
142    }
143}
144
145/// A group of processes, identified by their world ranks. Mirrors rsmpi's
146/// `Group` / `UserGroup`.
147#[derive(Clone, Debug)]
148pub struct Group {
149    /// Member world ranks, in group-rank order.
150    members: Vec<i32>,
151}
152
153/// Backwards-compatible alias for [`Group`].
154pub type UserGroup = Group;
155
156impl Group {
157    pub(crate) fn from_world_ranks(mut members: Vec<i32>) -> Group {
158        members.dedup();
159        Group { members }
160    }
161
162    /// The empty group (`MPI_GROUP_EMPTY`).
163    pub fn empty() -> Group {
164        Group {
165            members: Vec::new(),
166        }
167    }
168
169    /// Number of processes in the group.
170    pub fn size(&self) -> Rank {
171        self.members.len() as Rank
172    }
173
174    /// This process's rank within the group, if it is a member.
175    pub fn rank(&self) -> Option<Rank> {
176        let me = transport::runtime().rank;
177        self.members
178            .iter()
179            .position(|&w| w == me)
180            .map(|p| p as Rank)
181    }
182
183    /// Translate group-local ranks of `self` into the ranks they occupy in
184    /// `other` (or `None` if not present).
185    pub fn translate_ranks(&self, ranks: &[Rank], other: &Group) -> Vec<Option<Rank>> {
186        ranks
187            .iter()
188            .map(|&r| {
189                let w = self.members.get(r as usize).copied()?;
190                other
191                    .members
192                    .iter()
193                    .position(|&o| o == w)
194                    .map(|p| p as Rank)
195            })
196            .collect()
197    }
198
199    /// Set union with `other` (ranks of `self` first, then new ranks of
200    /// `other`, in order).
201    pub fn union(&self, other: &Group) -> Group {
202        let mut m = self.members.clone();
203        for &w in &other.members {
204            if !m.contains(&w) {
205                m.push(w);
206            }
207        }
208        Group { members: m }
209    }
210
211    /// Set intersection with `other` (order of `self`).
212    pub fn intersection(&self, other: &Group) -> Group {
213        let m = self
214            .members
215            .iter()
216            .copied()
217            .filter(|w| other.members.contains(w))
218            .collect();
219        Group { members: m }
220    }
221
222    /// Set difference `self \ other` (order of `self`).
223    pub fn difference(&self, other: &Group) -> Group {
224        let m = self
225            .members
226            .iter()
227            .copied()
228            .filter(|w| !other.members.contains(w))
229            .collect();
230        Group { members: m }
231    }
232
233    /// Sub-group containing only the listed group-local ranks (in the given
234    /// order).
235    pub fn include(&self, ranks: &[Rank]) -> Group {
236        let m = ranks
237            .iter()
238            .filter_map(|&r| self.members.get(r as usize).copied())
239            .collect();
240        Group { members: m }
241    }
242
243    /// Sub-group excluding the listed group-local ranks.
244    pub fn exclude(&self, ranks: &[Rank]) -> Group {
245        let drop: Vec<i32> = ranks
246            .iter()
247            .filter_map(|&r| self.members.get(r as usize).copied())
248            .collect();
249        let m = self
250            .members
251            .iter()
252            .copied()
253            .filter(|w| !drop.contains(w))
254            .collect();
255        Group { members: m }
256    }
257
258    /// Compare two groups.
259    pub fn compare(&self, other: &Group) -> GroupRelation {
260        if self.members == other.members {
261            GroupRelation::Identical
262        } else {
263            let mut a = self.members.clone();
264            let mut b = other.members.clone();
265            a.sort_unstable();
266            b.sort_unstable();
267            if a == b {
268                GroupRelation::Similar
269            } else {
270                GroupRelation::Unequal
271            }
272        }
273    }
274
275    pub(crate) fn members(&self) -> &[i32] {
276        &self.members
277    }
278}
279
280/// The behaviour shared by every communicator. Mirrors rsmpi's
281/// [`Communicator`](https://docs.rs/mpi/latest/mpi/topology/trait.Communicator.html)
282/// trait; the required method here is the internal handle accessor rather than
283/// `target_size`.
284pub trait Communicator {
285    /// Internal: the shared communicator description. Not part of the stable
286    /// public API.
287    #[doc(hidden)]
288    fn comm_data(&self) -> &CommData;
289
290    /// Number of processes in the communicator (`MPI_Comm_size`).
291    fn size(&self) -> Rank {
292        self.comm_data().size
293    }
294
295    /// This process's rank in the communicator (`MPI_Comm_rank`).
296    fn rank(&self) -> Rank {
297        self.comm_data().rank
298    }
299
300    /// Number of processes on the remote side (equals [`Communicator::size`]
301    /// for an intra-communicator).
302    fn target_size(&self) -> Rank {
303        self.comm_data().size
304    }
305
306    /// A handle to the process with the given rank.
307    fn process_at_rank(&self, r: Rank) -> Process<'_> {
308        Process::new(self.comm_data(), r)
309    }
310
311    /// A handle to this process.
312    fn this_process(&self) -> Process<'_> {
313        Process::new(self.comm_data(), self.comm_data().rank)
314    }
315
316    /// A handle matching any source (`MPI_ANY_SOURCE`), for receives.
317    fn any_process(&self) -> AnyProcess<'_> {
318        AnyProcess::new(self.comm_data())
319    }
320
321    /// The group underlying this communicator (`MPI_Comm_group`).
322    fn group(&self) -> Group {
323        Group::from_world_ranks(self.comm_data().world_ranks.clone())
324    }
325
326    /// Compare this communicator to `other` (`MPI_Comm_compare`).
327    fn compare(&self, other: &dyn Communicator) -> CommunicatorRelation {
328        let a = self.comm_data();
329        let b = other.comm_data();
330        if a.context == b.context {
331            CommunicatorRelation::Identical
332        } else if a.world_ranks == b.world_ranks {
333            CommunicatorRelation::Congruent
334        } else {
335            let mut sa = a.world_ranks.clone();
336            let mut sb = b.world_ranks.clone();
337            sa.sort_unstable();
338            sb.sort_unstable();
339            if sa == sb {
340                CommunicatorRelation::Similar
341            } else {
342                CommunicatorRelation::Unequal
343            }
344        }
345    }
346
347    /// Duplicate the communicator with a fresh context (`MPI_Comm_dup`).
348    /// Collective over all members.
349    fn duplicate(&self) -> SimpleCommunicator {
350        let data = self.comm_data();
351        let ctx = data.derive_context(0xD00Du32);
352        SimpleCommunicator::from_parts(ctx, data.rank, data.size, data.world_ranks.clone())
353    }
354
355    /// Split the communicator by colour (`MPI_Comm_split`). Collective.
356    fn split_by_color(&self, color: Color) -> Option<SimpleCommunicator> {
357        self.split_by_color_with_key(color, self.rank())
358    }
359
360    /// Split by colour, ordering new ranks by `key` then old rank
361    /// (`MPI_Comm_split`). Collective.
362    fn split_by_color_with_key(&self, color: Color, key: Key) -> Option<SimpleCommunicator> {
363        split_impl(self.comm_data(), color, key)
364    }
365
366    /// Split by an explicit subgroup (`MPI_Comm_create` style). Collective;
367    /// returns `None` for processes not in `group`.
368    fn split_by_subgroup(&self, group: &Group) -> Option<SimpleCommunicator> {
369        let data = self.comm_data();
370        let me = transport::runtime().rank;
371        if !group.members().contains(&me) {
372            return None;
373        }
374        // Derive a context agreed by all members. Using the group's member set
375        // as the discriminator keeps disjoint subgroups distinct.
376        let disc = group
377            .members()
378            .iter()
379            .fold(0u32, |a, &w| a.wrapping_mul(31).wrapping_add(w as u32));
380        let ctx = data.derive_context(disc ^ 0x5EED);
381        let world_ranks: Vec<i32> = group.members().to_vec();
382        let rank = world_ranks.iter().position(|&w| w == me).unwrap() as Rank;
383        let size = world_ranks.len() as Rank;
384        Some(SimpleCommunicator::from_parts(ctx, rank, size, world_ranks))
385    }
386
387    /// Abort the whole job with an exit code (`MPI_Abort`). Notifies every peer
388    /// so the entire job exits rather than leaving ranks blocked.
389    fn abort(&self, errorcode: i32) -> ! {
390        eprintln!(
391            "MPI_Abort called on rank {} (code {})",
392            self.rank(),
393            errorcode
394        );
395        transport::abort_job(errorcode);
396    }
397
398    /// Set the communicator's name (`MPI_Comm_set_name`).
399    fn set_name(&self, name: &str) {
400        *self.comm_data().name.lock().unwrap() = Some(name.to_string());
401    }
402
403    /// Get the communicator's name (`MPI_Comm_get_name`).
404    fn get_name(&self) -> String {
405        self.comm_data()
406            .name
407            .lock()
408            .unwrap()
409            .clone()
410            .unwrap_or_default()
411    }
412
413    /// Number of bytes needed to pack `incount` elements of datatype `dt`
414    /// (`MPI_Pack_size`).
415    fn pack_size(&self, incount: Count, dt: DatatypeRef) -> Count {
416        incount * dt.size as Count
417    }
418
419    /// Pack a buffer into a freshly allocated byte vector (`MPI_Pack`). Since
420    /// this implementation uses contiguous native layouts, packing is a copy.
421    fn pack<Buf: Buffer + ?Sized>(&self, inbuf: &Buf) -> Vec<u8>
422    where
423        Self: Sized,
424    {
425        inbuf.as_bytes().to_vec()
426    }
427
428    /// Pack a buffer into `outbuf` starting at byte offset `position`, returning
429    /// the new position (`MPI_Pack`).
430    fn pack_into<Buf: Buffer + ?Sized>(
431        &self,
432        inbuf: &Buf,
433        outbuf: &mut [u8],
434        position: Count,
435    ) -> Count
436    where
437        Self: Sized,
438    {
439        let bytes = inbuf.as_bytes();
440        let start = position as usize;
441        outbuf[start..start + bytes.len()].copy_from_slice(bytes);
442        position + bytes.len() as Count
443    }
444
445    /// Unpack from `inbuf` starting at byte offset `position` into `outbuf`,
446    /// returning the new position (`MPI_Unpack`).
447    ///
448    /// # Safety
449    ///
450    /// `outbuf` must be able to receive the unpacked bytes; retained `unsafe`
451    /// for signature parity with rsmpi.
452    unsafe fn unpack_into<Buf: BufferMut + ?Sized>(
453        &self,
454        inbuf: &[u8],
455        outbuf: &mut Buf,
456        position: Count,
457    ) -> Count
458    where
459        Self: Sized,
460    {
461        let dst = outbuf.as_bytes_mut();
462        let start = position as usize;
463        let n = dst.len().min(inbuf.len() - start);
464        dst[..n].copy_from_slice(&inbuf[start..start + n]);
465        position + n as Count
466    }
467
468    /// Create a graph topology communicator (`MPI_Graph_create`).
469    ///
470    /// `index[i]` is the cumulative number of neighbours of nodes `0..=i`, and
471    /// `edges` is the concatenation of each node's neighbour list. Ranks
472    /// `>= index.len()` are excluded (they receive `None`).
473    fn create_graph_communicator(
474        &self,
475        index: &[Count],
476        edges: &[Count],
477    ) -> Option<GraphCommunicator> {
478        let nnodes = index.len() as Count;
479        let color = if self.rank() < nnodes {
480            Color::with_value(0)
481        } else {
482            Color::undefined()
483        };
484        let sub = self.split_by_color(color)?;
485        Some(GraphCommunicator {
486            comm: sub,
487            index: index.to_vec(),
488            edges: edges.to_vec(),
489        })
490    }
491
492    /// Create a distributed-graph topology where this rank receives from
493    /// `sources` and sends to `destinations` (`MPI_Dist_graph_create_adjacent`).
494    /// Collective; each rank supplies its own adjacency.
495    fn create_dist_graph_adjacent(
496        &self,
497        sources: &[Rank],
498        destinations: &[Rank],
499    ) -> DistGraphCommunicator {
500        DistGraphCommunicator {
501            comm: self.duplicate(),
502            sources: sources.to_vec(),
503            destinations: destinations.to_vec(),
504        }
505    }
506
507    /// Collectively split this communicator into an inter-communicator between
508    /// two disjoint groups. Ranks passing `true` form one group ("A"); the rest
509    /// form the other. Each rank's [`InterCommunicator`] has the *other* group
510    /// as its remote group (`MPI_Intercomm_create`-style, collective).
511    fn split_intercommunicator(&self, in_group_a: bool) -> InterCommunicator {
512        let data = self.comm_data();
513        let me = transport::runtime().rank;
514        let mut rec = Vec::with_capacity(5);
515        rec.push(in_group_a as u8);
516        rec.extend_from_slice(&me.to_le_bytes());
517        let table = allgather_bytes(data, &rec);
518
519        let mut group_a = Vec::new();
520        let mut group_b = Vec::new();
521        for r in &table {
522            let a = r[0] != 0;
523            let w = i32::from_le_bytes(r[1..5].try_into().unwrap());
524            if a {
525                group_a.push(w);
526            } else {
527                group_b.push(w);
528            }
529        }
530        let (local, remote) = if in_group_a {
531            (group_a, group_b)
532        } else {
533            (group_b, group_a)
534        };
535        let ctx = data.derive_context(0x1E7E_1C0D);
536        let my_local_rank = local.iter().position(|&w| w == me).unwrap() as Rank;
537        InterCommunicator::new(ctx, my_local_rank, local, remote)
538    }
539
540    /// Create a Cartesian topology communicator (`MPI_Cart_create`).
541    ///
542    /// `dims` gives the extent of each dimension, `periods` whether each
543    /// dimension wraps around. Processes whose rank is outside the grid
544    /// (`rank >= product(dims)`) receive `None`. `reorder` is accepted for
545    /// signature parity but ranks are not reordered.
546    fn create_cartesian_communicator(
547        &self,
548        dims: &[Count],
549        periods: &[bool],
550        _reorder: bool,
551    ) -> Option<CartesianCommunicator> {
552        assert_eq!(
553            dims.len(),
554            periods.len(),
555            "dims and periods length mismatch"
556        );
557        let total: Count = dims.iter().product();
558        let color = if self.rank() < total {
559            Color::with_value(0)
560        } else {
561            Color::undefined()
562        };
563        let sub = self.split_by_color(color)?;
564        Some(CartesianCommunicator {
565            comm: sub,
566            dims: dims.to_vec(),
567            periods: periods.to_vec(),
568        })
569    }
570
571    /// If this process was created by [`crate::collective::Root::spawn`],
572    /// return the inter-communicator to the parent group (`MPI_Comm_get_parent`).
573    /// The local group is this world; the remote group is the spawner.
574    fn parent(&self) -> Option<InterCommunicator> {
575        let (ictx, paddrs) = transport::spawn_parent()?;
576        transport::runtime().register_context_peers(ictx, paddrs.clone());
577        let data = self.comm_data();
578        Some(InterCommunicator::new_spawned(
579            ictx,
580            data.rank,
581            data.world_ranks.clone(),
582            paddrs.len(),
583        ))
584    }
585}
586
587/// A communicator. In rsmpi 0.8.x this single type replaces the earlier
588/// `SystemCommunicator` (world) and `UserCommunicator` (derived) types.
589pub struct SimpleCommunicator {
590    inner: Arc<CommData>,
591}
592
593/// Backwards-compatible alias: `MPI_COMM_WORLD`-style communicator.
594pub type SystemCommunicator = SimpleCommunicator;
595/// Backwards-compatible alias: a derived communicator.
596pub type UserCommunicator = SimpleCommunicator;
597
598impl SimpleCommunicator {
599    /// The world communicator for the current process.
600    pub fn world() -> SimpleCommunicator {
601        SimpleCommunicator {
602            inner: Arc::new(CommData::world()),
603        }
604    }
605
606    fn from_parts(
607        context: u32,
608        rank: Rank,
609        size: Rank,
610        world_ranks: Vec<i32>,
611    ) -> SimpleCommunicator {
612        SimpleCommunicator {
613            inner: Arc::new(CommData {
614                context,
615                rank,
616                size,
617                world_ranks,
618                child_seq: AtomicU32::new(0),
619                name: Mutex::new(None),
620                attributes: Mutex::new(HashMap::new()),
621            }),
622        }
623    }
624}
625
626impl Communicator for SimpleCommunicator {
627    fn comm_data(&self) -> &CommData {
628        &self.inner
629    }
630}
631
632impl Clone for SimpleCommunicator {
633    fn clone(&self) -> Self {
634        SimpleCommunicator {
635            inner: Arc::clone(&self.inner),
636        }
637    }
638}
639
640/// Collective colour/key exchange implementing `MPI_Comm_split`.
641fn split_impl(parent: &CommData, color: Color, key: Key) -> Option<SimpleCommunicator> {
642    // Each process contributes (color_defined, color, key, its world rank).
643    let mut rec = Vec::with_capacity(16);
644    let c = color.value();
645    rec.extend_from_slice(&(c.is_some() as i32).to_le_bytes());
646    rec.extend_from_slice(&c.unwrap_or(0).to_le_bytes());
647    rec.extend_from_slice(&key.to_le_bytes());
648    rec.extend_from_slice(&transport::runtime().rank.to_le_bytes());
649
650    let table = allgather_bytes(parent, &rec);
651
652    // Decode all records.
653    struct Entry {
654        defined: bool,
655        color: i32,
656        key: i32,
657        world: i32,
658    }
659    let entries: Vec<Entry> = table
660        .iter()
661        .map(|r| Entry {
662            defined: i32::from_le_bytes(r[0..4].try_into().unwrap()) != 0,
663            color: i32::from_le_bytes(r[4..8].try_into().unwrap()),
664            key: i32::from_le_bytes(r[8..12].try_into().unwrap()),
665            world: i32::from_le_bytes(r[12..16].try_into().unwrap()),
666        })
667        .collect();
668
669    let my_world = transport::runtime().rank;
670    let my_color = color.value()?; // undefined -> None
671
672    // Collect co-coloured members, ordered by (key, original parent rank).
673    let mut members: Vec<(&Entry, usize)> = entries
674        .iter()
675        .enumerate()
676        .filter(|(_, e)| e.defined && e.color == my_color)
677        .map(|(i, e)| (e, i))
678        .collect();
679    members.sort_by(|a, b| a.0.key.cmp(&b.0.key).then(a.1.cmp(&b.1)));
680
681    let world_ranks: Vec<i32> = members.iter().map(|(e, _)| e.world).collect();
682    let rank = world_ranks.iter().position(|&w| w == my_world).unwrap() as Rank;
683    let size = world_ranks.len() as Rank;
684
685    // Context is derived from the parent context + colour so that every member
686    // of a colour agrees while distinct colours differ.
687    let ctx = parent.derive_context(my_color as u32);
688    Some(SimpleCommunicator::from_parts(ctx, rank, size, world_ranks))
689}
690
691const SPLIT_GATHER_TAG: Tag = 1;
692const SPLIT_BCAST_TAG: Tag = 2;
693
694/// A minimal all-gather of equal-length byte records over a communicator,
695/// implemented directly on the transport (linear gather to rank 0 followed by
696/// a broadcast). Used by control-plane operations such as `split`.
697pub(crate) fn allgather_bytes(comm: &CommData, mine: &[u8]) -> Vec<Vec<u8>> {
698    let rt = transport::runtime();
699    let ctx = comm.coll_context();
700    let n = comm.size;
701    let me = comm.rank;
702    let dt = crate::datatype::ids::U8;
703
704    if me == 0 {
705        let mut table: Vec<Vec<u8>> = vec![Vec::new(); n as usize];
706        table[0] = mine.to_vec();
707        for src in 1..n {
708            let (_s, _t, _c, _d, payload) = rt.recv(ctx, src, SPLIT_GATHER_TAG);
709            table[src as usize] = payload;
710        }
711        // Serialize the whole table and broadcast to every other member.
712        let mut blob = Vec::new();
713        blob.extend_from_slice(&(n as u32).to_le_bytes());
714        for rec in &table {
715            blob.extend_from_slice(&(rec.len() as u32).to_le_bytes());
716            blob.extend_from_slice(rec);
717        }
718        for dst in 1..n {
719            rt.send(
720                ctx,
721                0,
722                comm.world_rank(dst),
723                SPLIT_BCAST_TAG,
724                blob.len() as u64,
725                dt,
726                &blob,
727            )
728            .expect("split broadcast failed");
729        }
730        table
731    } else {
732        rt.send(
733            ctx,
734            me,
735            comm.world_rank(0),
736            SPLIT_GATHER_TAG,
737            mine.len() as u64,
738            dt,
739            mine,
740        )
741        .expect("split gather failed");
742        let (_s, _t, _c, _d, blob) = rt.recv(ctx, 0, SPLIT_BCAST_TAG);
743        decode_table(&blob)
744    }
745}
746
747fn decode_table(blob: &[u8]) -> Vec<Vec<u8>> {
748    let mut pos = 0;
749    let n = u32::from_le_bytes(blob[pos..pos + 4].try_into().unwrap()) as usize;
750    pos += 4;
751    let mut out = Vec::with_capacity(n);
752    for _ in 0..n {
753        let len = u32::from_le_bytes(blob[pos..pos + 4].try_into().unwrap()) as usize;
754        pos += 4;
755        out.push(blob[pos..pos + len].to_vec());
756        pos += len;
757    }
758    out
759}
760
761/// A communicator carrying a Cartesian grid topology (`MPI_Cart_create`).
762/// Coordinates are laid out row-major (the last dimension varies fastest),
763/// matching the MPI standard.
764#[derive(Clone)]
765pub struct CartesianCommunicator {
766    comm: SimpleCommunicator,
767    dims: Vec<Count>,
768    periods: Vec<bool>,
769}
770
771impl Communicator for CartesianCommunicator {
772    fn comm_data(&self) -> &CommData {
773        self.comm.comm_data()
774    }
775}
776
777impl CartesianCommunicator {
778    /// The number of grid dimensions (`MPI_Cartdim_get`).
779    pub fn num_dimensions(&self) -> usize {
780        self.dims.len()
781    }
782
783    /// The extent of each dimension.
784    pub fn dimensions(&self) -> &[Count] {
785        &self.dims
786    }
787
788    /// Whether each dimension is periodic.
789    pub fn periods(&self) -> &[bool] {
790        &self.periods
791    }
792
793    /// The Cartesian coordinates of a given rank (`MPI_Cart_coords`).
794    pub fn coordinates(&self, rank: Rank) -> Vec<Count> {
795        let mut coords = vec![0; self.dims.len()];
796        let mut r = rank;
797        for i in (0..self.dims.len()).rev() {
798            coords[i] = r % self.dims[i];
799            r /= self.dims[i];
800        }
801        coords
802    }
803
804    /// This process's Cartesian coordinates.
805    pub fn my_coordinates(&self) -> Vec<Count> {
806        self.coordinates(self.rank())
807    }
808
809    /// The rank at the given coordinates (`MPI_Cart_rank`). Returns `None` if a
810    /// non-periodic coordinate is out of range; periodic coordinates wrap.
811    pub fn rank_from_coordinates(&self, coords: &[Count]) -> Option<Rank> {
812        let mut rank = 0;
813        for ((&dim, &periodic), &coord) in self.dims.iter().zip(&self.periods).zip(coords) {
814            let mut c = coord;
815            if periodic {
816                c = c.rem_euclid(dim);
817            } else if c < 0 || c >= dim {
818                return None;
819            }
820            rank = rank * dim + c;
821        }
822        Some(rank)
823    }
824
825    /// Compute the source and destination ranks for a shift along `direction`
826    /// by `disp` steps (`MPI_Cart_shift`). Returns `(source, dest)`, either of
827    /// which is `None` at a non-periodic boundary.
828    pub fn shift(&self, direction: usize, disp: Count) -> (Option<Rank>, Option<Rank>) {
829        let coords = self.my_coordinates();
830        let mut dest = coords.clone();
831        dest[direction] += disp;
832        let mut source = coords;
833        source[direction] -= disp;
834        (
835            self.rank_from_coordinates(&source),
836            self.rank_from_coordinates(&dest),
837        )
838    }
839}
840
841/// A communicator carrying a general graph topology (`MPI_Graph_create`).
842#[derive(Clone)]
843pub struct GraphCommunicator {
844    comm: SimpleCommunicator,
845    index: Vec<Count>,
846    edges: Vec<Count>,
847}
848
849impl Communicator for GraphCommunicator {
850    fn comm_data(&self) -> &CommData {
851        self.comm.comm_data()
852    }
853}
854
855impl GraphCommunicator {
856    /// Total number of nodes in the graph.
857    pub fn num_nodes(&self) -> Count {
858        self.index.len() as Count
859    }
860
861    /// Total number of (directed) edges in the graph.
862    pub fn num_edges(&self) -> Count {
863        self.edges.len() as Count
864    }
865
866    /// The number of neighbours of `rank` (`MPI_Graph_neighbors_count`).
867    pub fn neighbor_count(&self, rank: Rank) -> Count {
868        let (s, e) = self.range(rank);
869        (e - s) as Count
870    }
871
872    /// The neighbours of `rank` (`MPI_Graph_neighbors`).
873    pub fn neighbors(&self, rank: Rank) -> Vec<Rank> {
874        let (s, e) = self.range(rank);
875        self.edges[s..e].to_vec()
876    }
877
878    /// This process's neighbours.
879    pub fn my_neighbors(&self) -> Vec<Rank> {
880        self.neighbors(self.rank())
881    }
882
883    /// Gather each rank's `sendbuf` from all of its neighbours
884    /// (`MPI_Neighbor_allgather`). `recvbuf` holds one block per neighbour, in
885    /// neighbour order.
886    pub fn neighbor_all_gather_into<S, R>(&self, sendbuf: &S, recvbuf: &mut R)
887    where
888        S: Buffer + ?Sized,
889        R: BufferMut + ?Sized,
890    {
891        const TAG: Tag = 40;
892        let rt = transport::runtime();
893        let ctx = self.comm.comm_data().coll_context();
894        let me = self.comm.comm_data().rank;
895        let nbrs = self.my_neighbors();
896        let send = sendbuf.as_bytes();
897        let dt = sendbuf.as_datatype().id;
898        for &nb in &nbrs {
899            rt.send(
900                ctx,
901                me,
902                self.comm.comm_data().world_rank(nb),
903                TAG,
904                sendbuf.count() as u64,
905                dt,
906                send,
907            )
908            .expect("neighbor_all_gather send");
909        }
910        let out = recvbuf.as_bytes_mut();
911        let blk = send.len();
912        for (k, &nb) in nbrs.iter().enumerate() {
913            let (_s, _t, _c, _d, payload) = rt.recv(ctx, nb, TAG);
914            out[k * blk..k * blk + payload.len()].copy_from_slice(&payload);
915        }
916    }
917
918    /// Exchange a distinct block with each neighbour
919    /// (`MPI_Neighbor_alltoall`). `sendbuf` and `recvbuf` hold one block per
920    /// neighbour, in neighbour order.
921    pub fn neighbor_all_to_all_into<S, R>(&self, sendbuf: &S, recvbuf: &mut R)
922    where
923        S: Buffer + ?Sized,
924        R: BufferMut + ?Sized,
925    {
926        const TAG: Tag = 41;
927        let rt = transport::runtime();
928        let ctx = self.comm.comm_data().coll_context();
929        let me = self.comm.comm_data().rank;
930        let nbrs = self.my_neighbors();
931        let send = sendbuf.as_bytes();
932        let dt = sendbuf.as_datatype().id;
933        let esize = sendbuf.as_datatype().size.max(1);
934        let blk = if nbrs.is_empty() {
935            0
936        } else {
937            send.len() / nbrs.len()
938        };
939        for (k, &nb) in nbrs.iter().enumerate() {
940            rt.send(
941                ctx,
942                me,
943                self.comm.comm_data().world_rank(nb),
944                TAG,
945                (blk / esize) as u64,
946                dt,
947                &send[k * blk..(k + 1) * blk],
948            )
949            .expect("neighbor_all_to_all send");
950        }
951        let out = recvbuf.as_bytes_mut();
952        for (k, &nb) in nbrs.iter().enumerate() {
953            let (_s, _t, _c, _d, payload) = rt.recv(ctx, nb, TAG);
954            out[k * blk..k * blk + payload.len()].copy_from_slice(&payload);
955        }
956    }
957
958    fn range(&self, rank: Rank) -> (usize, usize) {
959        let r = rank as usize;
960        let start = if r == 0 {
961            0
962        } else {
963            self.index[r - 1] as usize
964        };
965        let end = self.index[r] as usize;
966        (start, end)
967    }
968}
969
970/// An inter-communicator linking two disjoint groups of processes
971/// (`MPI_Intercomm`). Point-to-point operations address the **remote** group
972/// by rank; [`InterCommunicator::merge`] flattens both groups into an ordinary
973/// intra-communicator.
974pub struct InterCommunicator {
975    /// A communicator description whose `world_ranks` are the *remote* group,
976    /// `rank` is this process's rank in its *local* group, and `size` is the
977    /// local group size.
978    data: CommData,
979    local: Vec<i32>,
980    remote: Vec<i32>,
981}
982
983impl InterCommunicator {
984    fn new(context: u32, local_rank: Rank, local: Vec<i32>, remote: Vec<i32>) -> InterCommunicator {
985        let data = CommData {
986            context,
987            rank: local_rank,
988            size: local.len() as Rank,
989            world_ranks: remote.clone(),
990            child_seq: AtomicU32::new(0),
991            name: Mutex::new(None),
992            attributes: Mutex::new(HashMap::new()),
993        };
994        InterCommunicator {
995            data,
996            local,
997            remote,
998        }
999    }
1000
1001    /// Build a spawned inter-communicator whose remote group lives in another
1002    /// world, reached via a registered context-peer table (see
1003    /// [`crate::window`]-style routing). `world_ranks` is set to the identity
1004    /// `0..remote_count` so routing indexes the context-peer table directly.
1005    pub(crate) fn new_spawned(
1006        context: u32,
1007        local_rank: Rank,
1008        local: Vec<i32>,
1009        remote_count: usize,
1010    ) -> InterCommunicator {
1011        let remote: Vec<i32> = (0..remote_count as i32).collect();
1012        let data = CommData {
1013            context,
1014            rank: local_rank,
1015            size: local.len() as Rank,
1016            world_ranks: remote.clone(),
1017            child_seq: AtomicU32::new(0),
1018            name: Mutex::new(None),
1019            attributes: Mutex::new(HashMap::new()),
1020        };
1021        InterCommunicator {
1022            data,
1023            local,
1024            remote,
1025        }
1026    }
1027
1028    /// The size of the local group (`MPI_Comm_size` on an inter-communicator).
1029    pub fn local_size(&self) -> Rank {
1030        self.local.len() as Rank
1031    }
1032
1033    /// The size of the remote group (`MPI_Comm_remote_size`).
1034    pub fn remote_size(&self) -> Rank {
1035        self.remote.len() as Rank
1036    }
1037
1038    /// This process's rank within the local group.
1039    pub fn rank(&self) -> Rank {
1040        self.data.rank
1041    }
1042
1043    /// The local group.
1044    pub fn local_group(&self) -> Group {
1045        Group::from_world_ranks(self.local.clone())
1046    }
1047
1048    /// The remote group.
1049    pub fn remote_group(&self) -> Group {
1050        Group::from_world_ranks(self.remote.clone())
1051    }
1052
1053    /// Merge the two groups into a single intra-communicator
1054    /// (`MPI_Intercomm_merge`). Ranks are ordered by world rank.
1055    pub fn merge(&self) -> SimpleCommunicator {
1056        let mut all = self.local.clone();
1057        all.extend_from_slice(&self.remote);
1058        all.sort_unstable();
1059        all.dedup();
1060        // Deterministic context agreed by all ranks from the member set.
1061        let mut ctx = 0x4D_4552u32; // "MER"
1062        for &w in &all {
1063            ctx = ctx.wrapping_mul(31).wrapping_add(w as u32);
1064        }
1065        ctx &= !COLL_CONTEXT_BIT;
1066        let me = transport::runtime().rank;
1067        let rank = all.iter().position(|&w| w == me).unwrap() as Rank;
1068        let size = all.len() as Rank;
1069        SimpleCommunicator::from_parts(ctx, rank, size, all)
1070    }
1071}
1072
1073impl Communicator for InterCommunicator {
1074    fn comm_data(&self) -> &CommData {
1075        &self.data
1076    }
1077
1078    /// On an inter-communicator, `size` is the local group size.
1079    fn size(&self) -> Rank {
1080        self.local.len() as Rank
1081    }
1082}
1083
1084/// A distributed-graph topology communicator (`MPI_Dist_graph_create_adjacent`)
1085/// where each rank declares its own in-neighbours (`sources`) and out-neighbours
1086/// (`destinations`).
1087#[derive(Clone)]
1088pub struct DistGraphCommunicator {
1089    comm: SimpleCommunicator,
1090    sources: Vec<Rank>,
1091    destinations: Vec<Rank>,
1092}
1093
1094impl Communicator for DistGraphCommunicator {
1095    fn comm_data(&self) -> &CommData {
1096        self.comm.comm_data()
1097    }
1098}
1099
1100impl DistGraphCommunicator {
1101    /// Number of in-neighbours.
1102    pub fn in_degree(&self) -> usize {
1103        self.sources.len()
1104    }
1105
1106    /// Number of out-neighbours.
1107    pub fn out_degree(&self) -> usize {
1108        self.destinations.len()
1109    }
1110
1111    /// The ranks this process receives from.
1112    pub fn sources(&self) -> &[Rank] {
1113        &self.sources
1114    }
1115
1116    /// The ranks this process sends to.
1117    pub fn destinations(&self) -> &[Rank] {
1118        &self.destinations
1119    }
1120
1121    /// Send `sendbuf` to every out-neighbour and gather one block from each
1122    /// in-neighbour, in source order (`MPI_Neighbor_allgather`).
1123    pub fn neighbor_all_gather_into<S, R>(&self, sendbuf: &S, recvbuf: &mut R)
1124    where
1125        S: Buffer + ?Sized,
1126        R: BufferMut + ?Sized,
1127    {
1128        const TAG: Tag = 42;
1129        let comm = self.comm.comm_data();
1130        let ctx = comm.coll_context();
1131        let me = comm.rank;
1132        let send = sendbuf.as_bytes();
1133        let dt = sendbuf.as_datatype().id;
1134        for &d in &self.destinations {
1135            transport::runtime()
1136                .send(
1137                    ctx,
1138                    me,
1139                    comm.world_rank(d),
1140                    TAG,
1141                    sendbuf.count() as u64,
1142                    dt,
1143                    send,
1144                )
1145                .expect("dist-graph neighbor_all_gather send");
1146        }
1147        let out = recvbuf.as_bytes_mut();
1148        let blk = send.len();
1149        for (k, &s) in self.sources.iter().enumerate() {
1150            let (_s, _t, _c, _d, payload) = transport::runtime().recv(ctx, s, TAG);
1151            out[k * blk..k * blk + payload.len()].copy_from_slice(&payload);
1152        }
1153    }
1154}