DNDSR 0.2.1
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
test_PermutationTransfer.cpp
Go to the documentation of this file.
1/**
2 * @file test_PermutationTransfer.cpp
3 * @brief Unit tests for DNDS::PermutationTransfer.
4 *
5 * Tests local permutation, distributed partition transfer, and lookup
6 * resolution under MPI with 1, 2, 4, and 8 ranks.
7 */
8
9#define DOCTEST_CONFIG_IMPLEMENT
10#include "doctest.h"
13#include <numeric>
14#include <algorithm>
15
16using namespace DNDS;
17
18int main(int argc, char **argv)
19{
20 MPI_Init(&argc, &argv);
21 doctest::Context ctx;
22 ctx.applyCommandLine(argc, argv);
23 int res = ctx.run();
24 MPI_Finalize();
25 return res;
26}
27
28static MPIInfo worldMPI()
29{
31 mpi.setWorld();
32 return mpi;
33}
34
35// =================================================================
36// Test: fromLocalPermutation — reverse permutation
37// =================================================================
38
39TEST_CASE("PermutationTransfer::fromLocalPermutation reverse")
40{
41 auto mpi = worldMPI();
42 const DNDS::index nLocal = 10;
43
44 // Create a simple array: each rank owns 10 entries with values = global index
46 arr.InitPair("test_arr", mpi);
47 arr.father->Resize(nLocal);
48 arr.father->createGlobalMapping();
49
50 DNDS::index myOffset = (*arr.father->pLGlobalMapping)(mpi.rank, 0);
51 for (DNDS::index i = 0; i < nLocal; i++)
52 arr(i, 0) = myOffset + i; // value = own global index
53
54 // Build reverse permutation: new[i] = old[N-1-i]
55 std::vector<DNDS::index> old2new(nLocal);
56 for (DNDS::index i = 0; i < nLocal; i++)
57 old2new[i] = nLocal - 1 - i;
58
59 auto pt = PermutationTransfer::fromLocalPermutation(old2new, arr.father->pLGlobalMapping, mpi);
60
61 CHECK(pt.isLocalOnly);
62 CHECK(pt.size() == nLocal);
63 CHECK(pt.localOld2New.size() == static_cast<size_t>(nLocal));
64
65 // Verify new global indices
66 for (DNDS::index i = 0; i < nLocal; i++)
67 CHECK(pt.newGlobalIndices[i] == myOffset + old2new[i]);
68
69 // Transfer rows
70 pt.transferRows(arr, mpi);
71
72 // After transfer: arr(newSlot, 0) should contain the old global of
73 // the entity that was moved there.
74 // old slot i had value (myOffset + i), moved to new slot old2new[i].
75 // So new slot j should have value (myOffset + reverseOf(j)).
76 // reverseOf(j) = N-1-j (since old2new[i] = N-1-i => i = N-1-j when j = old2new[i])
77 for (DNDS::index j = 0; j < nLocal; j++)
78 {
79 DNDS::index expectedOldSlot = nLocal - 1 - j;
80 DNDS::index expectedValue = myOffset + expectedOldSlot;
81 CHECK(arr(j, 0) == expectedValue);
82 }
83}
84
85// =================================================================
86// Test: fromLocalPermutation — identity (no change)
87// =================================================================
88
89TEST_CASE("PermutationTransfer::fromLocalPermutation identity")
90{
91 auto mpi = worldMPI();
92 const DNDS::index nLocal = 5;
93
95 arr.InitPair("test_arr", mpi);
96 arr.father->Resize(nLocal);
97 arr.father->createGlobalMapping();
98
99 DNDS::index myOffset = (*arr.father->pLGlobalMapping)(mpi.rank, 0);
100 for (DNDS::index i = 0; i < nLocal; i++)
101 arr(i, 0) = 100 + myOffset + i;
102
103 // Identity permutation
104 std::vector<DNDS::index> old2new(nLocal);
105 std::iota(old2new.begin(), old2new.end(), DNDS::index{0});
106
107 auto pt = PermutationTransfer::fromLocalPermutation(old2new, arr.father->pLGlobalMapping, mpi);
108 CHECK(pt.isLocalOnly);
109
110 pt.transferRows(arr, mpi);
111
112 for (DNDS::index i = 0; i < nLocal; i++)
113 CHECK(arr(i, 0) == 100 + myOffset + i);
114}
115
116// =================================================================
117// Test: fromPartition — all stay on same rank (local-only detected)
118// =================================================================
119
120TEST_CASE("PermutationTransfer::fromPartition all-local")
121{
122 auto mpi = worldMPI();
123 const DNDS::index nLocal = 8;
124
126 arr.InitPair("test_arr", mpi);
127 arr.father->Resize(nLocal);
128 arr.father->createGlobalMapping();
129
130 DNDS::index myOffset = (*arr.father->pLGlobalMapping)(mpi.rank, 0);
131 for (DNDS::index i = 0; i < nLocal; i++)
132 arr(i, 0) = myOffset + i;
133
134 // Partition: all entities stay on current rank
135 std::vector<MPI_int> partition(nLocal, mpi.rank);
136
137 auto pt = PermutationTransfer::fromPartition(partition, arr.father->pLGlobalMapping, mpi);
138
139 CHECK(pt.isLocalOnly);
140 CHECK(pt.size() == nLocal);
141
142 // New globals should be contiguous starting at newGlobalOffsets[mpi.rank]
143 DNDS::index newOffset = pt.newGlobalOffsets[mpi.rank];
144 for (DNDS::index i = 0; i < nLocal; i++)
145 CHECK(pt.newGlobalIndices[i] == newOffset + i);
146
147 // Transfer should be identity (since partition = all-self, ordering preserved)
148 pt.transferRows(arr, mpi);
149 for (DNDS::index i = 0; i < nLocal; i++)
150 CHECK(arr(i, 0) == myOffset + i);
151}
152
153// =================================================================
154// Test: fromPartition — round-robin redistribution
155// =================================================================
156
157TEST_CASE("PermutationTransfer::fromPartition round-robin")
158{
159 auto mpi = worldMPI();
160 if (mpi.size < 2)
161 return; // skip on 1 rank
162
163 const DNDS::index nLocal = 6;
164
166 arr.InitPair("test_arr", mpi);
167 arr.father->Resize(nLocal);
168 arr.father->createGlobalMapping();
169
170 DNDS::index myOffset = (*arr.father->pLGlobalMapping)(mpi.rank, 0);
171 for (DNDS::index i = 0; i < nLocal; i++)
172 arr(i, 0) = myOffset + i; // value = old global
173
174 // Round-robin: entity i goes to rank (i % nRanks)
175 std::vector<MPI_int> partition(nLocal);
176 for (DNDS::index i = 0; i < nLocal; i++)
177 partition[i] = static_cast<MPI_int>(i % mpi.size);
178
179 auto pt = PermutationTransfer::fromPartition(partition, arr.father->pLGlobalMapping, mpi);
180
181 CHECK_FALSE(pt.isLocalOnly);
182
183 // Transfer rows
184 pt.transferRows(arr, mpi);
185
186 // After transfer, this rank should have received entities from all ranks
187 // whose slot i satisfies (i % nRanks == mpi.rank).
188 // Count expected: each rank sends nLocal/nRanks entities to this rank
189 // (approximately — depends on nLocal and nRanks).
190 DNDS::index expectedCount = 0;
191 for (DNDS::index i = 0; i < nLocal * mpi.size; i++)
192 if (i % mpi.size == mpi.rank)
193 expectedCount++; // but we only count from all ranks
194
195 // More precise: each rank sends (number of slots where i%size == mpi.rank) entities
196 DNDS::index myReceiveCount = 0;
197 for (DNDS::index i = 0; i < nLocal; i++)
198 if (partition[i] == mpi.rank)
199 myReceiveCount++; // from self
200 // From other ranks: they also have nLocal entities and send some here
201 // Total receive = sum over all ranks of (their slots targeting me)
202 // Each rank has nLocal entities; slot i of rank r targets rank (i % size).
203 // So from rank r, I receive count of {i : i%size == mpi.rank, 0 <= i < nLocal}
204
205 DNDS::index totalReceive = 0;
206 for (int r = 0; r < mpi.size; r++)
207 {
208 for (DNDS::index i = 0; i < nLocal; i++)
209 if (i % mpi.size == mpi.rank)
210 totalReceive++;
211 }
212
213 CHECK(arr.father->Size() == totalReceive);
214
215 // Verify all received values are valid old globals (in range [0, nLocal*nRanks))
216 DNDS::index globalTotal = nLocal * mpi.size;
217 for (DNDS::index i = 0; i < arr.father->Size(); i++)
218 {
219 DNDS::index val = arr(i, 0);
220 CHECK(val >= 0);
221 CHECK(val < globalTotal);
222 }
223}
224
225// =================================================================
226// Test: buildLookup — resolve old globals to new globals
227// =================================================================
228
229TEST_CASE("PermutationTransfer::buildLookup resolve")
230{
231 auto mpi = worldMPI();
232 const DNDS::index nLocal = 4;
233
235 arr.InitPair("test_arr", mpi);
236 arr.father->Resize(nLocal);
237 arr.father->createGlobalMapping();
238
239 DNDS::index myOffset = (*arr.father->pLGlobalMapping)(mpi.rank, 0);
240
241 // All-local partition (identity-like)
242 std::vector<MPI_int> partition(nLocal, mpi.rank);
243 auto pt = PermutationTransfer::fromPartition(partition, arr.father->pLGlobalMapping, mpi);
244
245 // Pull set: request globals from other ranks (first 2 from each neighbor)
246 std::vector<DNDS::index> pullSet;
247 for (int r = 0; r < mpi.size; r++)
248 {
249 if (r == mpi.rank)
250 continue;
251 DNDS::index rOffset = (*arr.father->pLGlobalMapping)(r, 0);
252 for (DNDS::index i = 0; i < std::min(nLocal, DNDS::index{2}); i++)
253 pullSet.push_back(rOffset + i);
254 }
255 std::sort(pullSet.begin(), pullSet.end());
256
257 auto lookup = pt.buildLookup(pullSet, mpi);
258
259 // Resolve own globals: should map to new globals
260 for (DNDS::index i = 0; i < nLocal; i++)
261 {
262 DNDS::index oldGlobal = myOffset + i;
263 DNDS::index newGlobal = lookup.resolve(oldGlobal);
264 CHECK(newGlobal == pt.newGlobalIndices[i]);
265 }
266
267 // Resolve pulled globals from other ranks
268 for (auto oldGlobal : pullSet)
269 {
270 DNDS::index newGlobal = lookup.resolve(oldGlobal);
271 // The new global should be valid (in range [0, total))
272 CHECK(newGlobal >= 0);
273 CHECK(newGlobal < pt.newGlobalOffsets.back());
274 }
275
276 // UnInitIndex passthrough
277 CHECK(lookup.resolve(UnInitIndex) == UnInitIndex);
278}
279
280// =================================================================
281// Test: fromPartition + buildLookup — distributed with cross-rank resolve
282// =================================================================
283
284TEST_CASE("PermutationTransfer distributed lookup cross-rank")
285{
286 auto mpi = worldMPI();
287 if (mpi.size < 2)
288 return;
289
290 const DNDS::index nLocal = 4;
291
293 arr.InitPair("test_arr", mpi);
294 arr.father->Resize(nLocal);
295 arr.father->createGlobalMapping();
296
297 DNDS::index myOffset = (*arr.father->pLGlobalMapping)(mpi.rank, 0);
298
299 // Send all entities to the next rank (ring shift)
300 MPI_int targetRank = (mpi.rank + 1) % mpi.size;
301 std::vector<MPI_int> partition(nLocal, targetRank);
302
303 auto pt = PermutationTransfer::fromPartition(partition, arr.father->pLGlobalMapping, mpi);
304 CHECK_FALSE(pt.isLocalOnly);
305
306 // Build lookup: pull the previous rank's old globals
307 MPI_int sourceRank = (mpi.rank - 1 + mpi.size) % mpi.size;
308 DNDS::index sourceOffset = (*arr.father->pLGlobalMapping)(sourceRank, 0);
309 std::vector<DNDS::index> pullSet;
310 for (DNDS::index i = 0; i < nLocal; i++)
311 pullSet.push_back(sourceOffset + i);
312 std::sort(pullSet.begin(), pullSet.end());
313
314 auto lookup = pt.buildLookup(pullSet, mpi);
315
316 // Verify: my old globals should resolve to new globals on targetRank
317 for (DNDS::index i = 0; i < nLocal; i++)
318 {
319 DNDS::index oldGlobal = myOffset + i;
320 DNDS::index newGlobal = lookup.resolve(oldGlobal);
321 // New global should be in [newGlobalOffsets[target], newGlobalOffsets[target+1])
322 CHECK(newGlobal >= pt.newGlobalOffsets[targetRank]);
323 CHECK(newGlobal < pt.newGlobalOffsets[targetRank + 1]);
324 }
325
326 // Verify: previous rank's old globals should also resolve
327 for (auto oldGlobal : pullSet)
328 {
329 DNDS::index newGlobal = lookup.resolve(oldGlobal);
330 // sourceRank sent to (sourceRank+1)%size == mpi.rank
331 CHECK(newGlobal >= pt.newGlobalOffsets[mpi.rank]);
332 CHECK(newGlobal < pt.newGlobalOffsets[mpi.rank + 1]);
333 }
334}
335
336// =================================================================
337// Test: transferRows with CSR (variable-row-size) array
338// =================================================================
339
340TEST_CASE("PermutationTransfer::transferRows CSR local permutation")
341{
342 auto mpi = worldMPI();
343 const DNDS::index nLocal = 6;
344
345 // Create a CSR array where row i has (i+1) entries
347 arr.InitPair("test_csr", mpi);
348 arr.father->Resize(nLocal);
349 for (DNDS::index i = 0; i < nLocal; i++)
350 arr.father->ResizeRow(i, static_cast<rowsize>(i + 1));
351 arr.father->Compress();
352 arr.father->createGlobalMapping();
353
354 // Fill: row i, entry j = i * 100 + j
355 for (DNDS::index i = 0; i < nLocal; i++)
356 for (rowsize j = 0; j < arr.father->RowSize(i); j++)
357 arr(i, j) = i * 100 + j;
358
359 // Reverse permutation
360 std::vector<DNDS::index> old2new(nLocal);
361 for (DNDS::index i = 0; i < nLocal; i++)
362 old2new[i] = nLocal - 1 - i;
363
364 auto pt = PermutationTransfer::fromLocalPermutation(old2new, arr.father->pLGlobalMapping, mpi);
365 pt.transferRows(arr, mpi);
366
367 // Verify: new slot j should contain old slot (N-1-j)'s data
368 for (DNDS::index j = 0; j < nLocal; j++)
369 {
370 DNDS::index oldSlot = nLocal - 1 - j;
371 rowsize expectedRowSize = static_cast<rowsize>(oldSlot + 1);
372 CHECK(arr.father->RowSize(j) == expectedRowSize);
373 for (rowsize k = 0; k < expectedRowSize; k++)
374 CHECK(arr(j, k) == oldSlot * 100 + k);
375 }
376}
Adjacency array (CSR-like index storage) built on ParArray.
Utility for distributed or local row permutation/transfer of arrays.
int main()
Definition dummy.cpp:3
the host side operators are provided as implemented
DNDS_CONSTANT const index UnInitIndex
Sentinel "not initialised" index value (= INT64_MIN).
Definition Defines.hpp:181
int32_t rowsize
Row-width / per-row element-count type (signed 32-bit).
Definition Defines.hpp:114
int64_t index
Global row / DOF index type (signed 64-bit; handles multi-billion-cell meshes).
Definition Defines.hpp:112
int MPI_int
MPI counterpart type for MPI_int (= C int). Used for counts and ranks in MPI calls.
Definition MPI.hpp:54
Convenience bundle of a father, son, and attached ArrayTransformer.
Lightweight bundle of an MPI communicator and the calling rank's coordinates.
Definition MPI.hpp:231
static PermutationTransfer fromPartition(const std::vector< MPI_int > &partition, const ssp< GlobalOffsetsMapping > &oldGlobalMapping, const MPIInfo &mpi)
static PermutationTransfer fromLocalPermutation(const std::vector< index > &old2new, const ssp< GlobalOffsetsMapping > &oldGlobalMapping, const MPIInfo &mpi)
constexpr DNDS::index nLocal
tVec r(NCells)
CHECK(result.size()==3)
TEST_CASE("3D: VFV P2 HQM error < P1 on sinCos3D")