1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! Integration test for `Communicator::create_from_group`.
//!
//! Exercises `MPI_Comm_create` via the `ferrompi_comm_create_from_group_parent`
//! shim. Verifies that:
//!
//! - Ranks 0 and 2 receive `Ok(Some(comm))` with `comm.size() == 2`.
//! - Ranks 1 and 3 receive `Ok(None)` (they are not in the sub-group).
//! - The sub-communicator is functional: `allreduce_scalar(1.0, Sum)` returns
//! `2.0` on ranks that are members.
//!
//! All assertions are guarded by a sentinel `allreduce_scalar(Min)` before any
//! `process::exit` so that no rank exits while others are still inside MPI
//! collective calls.
//!
//! Run with: mpiexec -n 4 ./target/debug/examples/test_comm_from_group
use ferrompi::{Mpi, ReduceOp};
fn main() {
let mpi = Mpi::init().expect("MPI init failed");
let world = mpi.world();
let rank = world.rank();
let size = world.size();
assert!(
size == 4,
"test_comm_from_group requires exactly 4 processes, got {size}"
);
// local_ok tracks whether this rank passed all its assertions.
let mut local_ok = true;
// ========================================================================
// Build the sub-group {0, 2} from the world group.
// ========================================================================
let world_group = world.group().expect("world.group() failed");
let sub_group = world_group
.include(&[0, 2])
.expect("group.include(&[0, 2]) failed");
// ========================================================================
// Test 1: create_from_group — collective over all 4 ranks.
//
// Ranks 0 and 2 must receive Some(comm); ranks 1 and 3 must receive None.
// ========================================================================
let sub_comm = match world.create_from_group(&sub_group) {
Ok(c) => c,
Err(e) => {
eprintln!("rank {rank}: FAIL: create_from_group returned error: {e}");
let _ = world.allreduce_scalar(0i32, ReduceOp::Min);
std::process::exit(1);
}
};
if rank == 0 || rank == 2 {
// ====================================================================
// Test 1a: member ranks must receive Some(comm).
// ====================================================================
let comm = match &sub_comm {
Some(c) => c,
None => {
eprintln!("rank {rank}: FAIL Test 1a: expected Some(comm), got None");
local_ok = false;
// Participate in subsequent collective calls with fallback.
// We cannot join sub-comm collectives without a comm, so we
// skip them and let the sentinel allreduce catch the failure.
let _ = world.allreduce_scalar(local_ok as i32, ReduceOp::Min);
std::process::exit(1);
}
};
// ====================================================================
// Test 1b: sub-communicator must have size 2.
// ====================================================================
let sub_size = comm.size();
if sub_size == 2 {
if rank == 0 {
println!("PASS: Test 1b — sub-comm size = {sub_size}");
}
} else {
eprintln!("rank {rank}: FAIL Test 1b: sub-comm size = {sub_size}, expected 2");
local_ok = false;
}
// ====================================================================
// Test 2: allreduce on the sub-communicator.
//
// Each of the 2 member ranks contributes 1.0; the sum must be 2.0.
// ====================================================================
match comm.allreduce_scalar(1.0f64, ReduceOp::Sum) {
Ok(result) if (result - 2.0f64).abs() < f64::EPSILON => {
if rank == 0 {
println!("PASS: Test 2 — sub-comm allreduce(1.0, Sum) = {result}");
}
}
Ok(result) => {
eprintln!("rank {rank}: FAIL Test 2: allreduce result = {result}, expected 2.0");
local_ok = false;
}
Err(e) => {
eprintln!("rank {rank}: FAIL Test 2: allreduce failed: {e}");
local_ok = false;
}
}
} else {
// ====================================================================
// Test 1a (non-member): ranks 1 and 3 must receive None.
// ====================================================================
if sub_comm.is_none() {
if rank == 1 {
println!("PASS: Test 1a — non-member rank {rank} received None");
}
} else {
eprintln!("rank {rank}: FAIL Test 1a: expected None for non-member rank, got Some");
local_ok = false;
}
}
// ========================================================================
// Sentinel allreduce(Min) — gate process::exit so no rank exits early.
// ========================================================================
let global_ok = world
.allreduce_scalar(local_ok as i32, ReduceOp::Min)
.expect("sentinel allreduce failed");
if global_ok == 0 {
if rank == 0 {
eprintln!("FAIL: at least one rank failed a create_from_group assertion");
}
std::process::exit(1);
}
if rank == 0 {
println!();
println!("========================================");
println!("All create_from_group tests passed!");
println!("========================================");
}
}