DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
test_ArrayDerived.cpp
Go to the documentation of this file.
1/**
2 * @file test_ArrayDerived.cpp
3 * @brief Comprehensive doctest-based unit tests for DNDS ArrayDerived types:
4 * ArrayAdjacency, ArrayEigenVector, ArrayEigenMatrix,
5 * ArrayEigenMatrixBatch, and ArrayEigenUniMatrixBatch.
6 *
7 * Run under mpirun with 1, 2, and 4 ranks. Tests construction, resize,
8 * compress, element access, ghost communication via ArrayTransformer, and
9 * clone independence for each derived array type.
10 *
11 * @see @ref dnds_unit_tests for the full test-suite overview.
12 * @test ArrayAdjacency (basics, ghost comm, clone, fixed-size),
13 * ArrayEigenVector (static, dynamic, ghost comm),
14 * ArrayEigenMatrix (static, dynamic, NonUniform, ghost comm),
15 * ArrayEigenMatrixBatch (basics, ghost comm),
16 * ArrayEigenUniMatrixBatch (static, dynamic).
17 */
18
19#define DOCTEST_CONFIG_IMPLEMENT
20#include "doctest.h"
21#include "DNDS/ArrayPair.hpp"
23#include <memory>
24#include <numeric>
25#include <vector>
26
27using namespace DNDS;
28
29int main(int argc, char **argv)
30{
31 MPI_Init(&argc, &argv);
32 doctest::Context ctx;
33 ctx.applyCommandLine(argc, argv);
34 int res = ctx.run();
35 MPI_Finalize();
36 return res;
37}
38
39// ---------------------------------------------------------------------------
40// Helpers
41// ---------------------------------------------------------------------------
42
43static MPIInfo worldMPI()
44{
46 mpi.setWorld();
47 return mpi;
48}
49
50/// Build a vector of global indices: first `nPer` elements from each
51/// non-local rank, for use as ghost pull indices.
52static std::vector<DNDS::index> pullFirstNFromOthers(
53 const MPIInfo &mpi,
54 const GlobalOffsetsMapping &gm,
55 DNDS::index nPerRank)
56{
57 std::vector<DNDS::index> idx;
58 for (MPI_int r = 0; r < mpi.size; r++)
59 {
60 if (r == mpi.rank)
61 continue;
62 DNDS::index base = gm(r, 0);
63 for (DNDS::index i = 0; i < nPerRank; i++)
64 idx.push_back(base + i);
65 }
66 return idx;
67}
68
69// ===========================================================================
70// ArrayAdjacency basics
71// ===========================================================================
72TEST_CASE("ArrayAdjacency basics")
73{
74 MPIInfo mpi = worldMPI();
75 constexpr DNDS::index N = 20;
76
78 adj.Resize(N);
79
80 // Assign each row a varying size and fill with known values
81 for (DNDS::index i = 0; i < N; i++)
82 adj.ResizeRow(i, static_cast<DNDS::rowsize>(i % 3 + 1));
83
84 for (DNDS::index i = 0; i < N; i++)
85 {
86 DNDS::index *rp = adj.rowPtr(i);
87 for (DNDS::rowsize j = 0; j < adj.RowSize(i); j++)
88 rp[j] = i * 100 + j;
89 }
90
91 adj.Compress();
92
93 SUBCASE("verify all values after Compress")
94 {
95 for (DNDS::index i = 0; i < N; i++)
96 {
97 CHECK(adj.RowSize(i) == static_cast<DNDS::rowsize>(i % 3 + 1));
98 for (DNDS::rowsize j = 0; j < adj.RowSize(i); j++)
99 {
100 DNDS::index expected = i * 100 + j;
101 CHECK(adj.rowPtr(i)[j] == expected);
102 }
103 }
104 }
105
106 SUBCASE("operator[] returns AdjacencyRow with correct data")
107 {
108 for (DNDS::index i = 0; i < N; i++)
109 {
110 auto row = adj[i];
111 CHECK(row.size() == static_cast<DNDS::rowsize>(i % 3 + 1));
112 for (DNDS::rowsize j = 0; j < row.size(); j++)
113 CHECK(row[j] == i * 100 + j);
114 }
115 }
116
117 SUBCASE("rowPtr returns valid pointer")
118 {
119 for (DNDS::index i = 0; i < N; i++)
120 {
121 DNDS::index *ptr = adj.rowPtr(i);
122 REQUIRE(ptr != nullptr);
123 CHECK(ptr[0] == i * 100);
124 }
125 }
126}
127
128// ===========================================================================
129// ArrayAdjacency ghost communication
130// ===========================================================================
131TEST_CASE("ArrayAdjacency ghost communication")
132{
133 MPIInfo mpi = worldMPI();
134 constexpr DNDS::index nLocal = 20;
135 constexpr DNDS::index nGhostPer = 3;
136
137 auto father = std::make_shared<ArrayAdjacency<NonUniformSize>>(mpi);
138 father->Resize(nLocal, [](DNDS::index i) -> DNDS::rowsize
139 { return static_cast<DNDS::rowsize>(i % 3 + 1); });
140
141 father->createGlobalMapping();
142 DNDS::index gOff = (*father->pLGlobalMapping)(mpi.rank, 0);
143
144 // Fill: each element encodes its global index
145 for (DNDS::index i = 0; i < nLocal; i++)
146 for (DNDS::rowsize j = 0; j < father->RowSize(i); j++)
147 father->operator()(i, j) = (gOff + i) * 100 + j;
148
149 auto son = std::make_shared<ArrayAdjacency<NonUniformSize>>(mpi);
152 trans.createFatherGlobalMapping();
153
154 auto pullIdx = pullFirstNFromOthers(mpi, *trans.pLGlobalMapping, nGhostPer);
155 trans.createGhostMapping(std::vector<DNDS::index>(pullIdx));
156 trans.createMPITypes();
157 trans.pullOnce();
158
159 CHECK(son->Size() == static_cast<DNDS::index>(pullIdx.size()));
160
161 // Verify ghost adjacency data
162 for (DNDS::index g = 0; g < son->Size(); g++)
163 {
164 DNDS::index ghostGlobal = trans.pLGhostMapping->ghostIndex[g];
165 MPI_int srcRank = -1;
166 DNDS::index srcLoc = -1;
167 bool found = trans.pLGlobalMapping->search(ghostGlobal, srcRank, srcLoc);
168 CHECK(found);
169
170 DNDS::rowsize expectedRowSize = static_cast<DNDS::rowsize>(srcLoc % 3 + 1);
171 CHECK(son->RowSize(g) == expectedRowSize);
172
173 for (DNDS::rowsize j = 0; j < son->RowSize(g); j++)
174 CHECK(son->operator()(g, j) == ghostGlobal * 100 + j);
175 }
176}
177
178// ===========================================================================
179// ArrayEigenVector basics
180// ===========================================================================
181TEST_CASE("ArrayEigenVector basics")
182{
183 MPIInfo mpi = worldMPI();
184 constexpr DNDS::index N = 30;
185 constexpr DNDS::rowsize vecSize = 5;
186
188 vec.Resize(N, vecSize);
189
190 CHECK(vec.Size() == N);
191 CHECK(vec.RowSize() == vecSize);
192
193 // Fill each row with a known pattern: v(j) = i * 10 + j
194 for (DNDS::index i = 0; i < N; i++)
195 {
196 auto v = vec[i];
197 for (DNDS::rowsize j = 0; j < vecSize; j++)
198 v(j) = static_cast<DNDS::real>(i * 10 + j);
199 }
200
201 SUBCASE("operator[] returns Eigen::Map with correct values")
202 {
203 for (DNDS::index i = 0; i < N; i++)
204 {
205 auto v = vec[i];
206 CHECK(v.size() == vecSize);
207 for (DNDS::rowsize j = 0; j < vecSize; j++)
208 CHECK(v(j) == doctest::Approx(static_cast<DNDS::real>(i * 10 + j)));
209 }
210 }
211
212 SUBCASE("Size and RowSize")
213 {
214 CHECK(vec.Size() == N);
215 CHECK(vec.RowSize() == vecSize);
216 }
217}
218
219// ===========================================================================
220// ArrayEigenVector ghost communication
221// ===========================================================================
222TEST_CASE("ArrayEigenVector ghost communication")
223{
224 MPIInfo mpi = worldMPI();
225 constexpr DNDS::index nLocal = 25;
226 constexpr DNDS::rowsize vecSize = 4;
227 constexpr DNDS::index nGhostPer = 5;
228
229 auto father = std::make_shared<ArrayEigenVector<DynamicSize>>(mpi);
230 father->Resize(nLocal, vecSize);
231 father->createGlobalMapping();
232 DNDS::index gOff = (*father->pLGlobalMapping)(mpi.rank, 0);
233
234 // Fill with rank-specific vectors: v(j) = (gOff + i)*10 + j
235 for (DNDS::index i = 0; i < nLocal; i++)
236 {
237 auto v = father->operator[](i);
238 for (DNDS::rowsize j = 0; j < vecSize; j++)
239 v(j) = static_cast<DNDS::real>((gOff + i) * 10 + j);
240 }
241
242 auto son = std::make_shared<ArrayEigenVector<DynamicSize>>(mpi);
245 trans.createFatherGlobalMapping();
246
247 auto pullIdx = pullFirstNFromOthers(mpi, *trans.pLGlobalMapping, nGhostPer);
248 trans.createGhostMapping(std::vector<DNDS::index>(pullIdx));
249 trans.createMPITypes();
250 trans.pullOnce();
251
252 CHECK(son->Size() == static_cast<DNDS::index>(pullIdx.size()));
253
254 for (DNDS::index g = 0; g < son->Size(); g++)
255 {
256 DNDS::index ghostGlobal = trans.pLGhostMapping->ghostIndex[g];
257 auto v = son->operator[](g);
258 for (DNDS::rowsize j = 0; j < vecSize; j++)
259 CHECK(v(j) == doctest::Approx(static_cast<DNDS::real>(ghostGlobal * 10 + j)));
260 }
261}
262
263// ===========================================================================
264// ArrayEigenMatrix basics (static sizes)
265// ===========================================================================
266TEST_CASE("ArrayEigenMatrix basics")
267{
268 MPIInfo mpi = worldMPI();
269 constexpr DNDS::index N = 20;
270
271 ArrayEigenMatrix<3, 4> mat(mpi);
272 mat.Resize(N, 3, 4);
273
274 CHECK(mat.Size() == N);
275 CHECK(mat.MatRowSize() == 3);
276 CHECK(mat.MatColSize() == 4);
277
278 // Fill each matrix: Identity-like + scaled offset
279 for (DNDS::index i = 0; i < N; i++)
280 {
281 auto m = mat[i];
282 m.setZero();
283 for (int r = 0; r < 3; r++)
284 for (int c = 0; c < 4; c++)
285 m(r, c) = static_cast<DNDS::real>(i * 100 + r * 10 + c);
286 }
287
288 SUBCASE("verify via operator[] Eigen::Map")
289 {
290 for (DNDS::index i = 0; i < N; i++)
291 {
292 auto m = mat[i];
293 CHECK(m.rows() == 3);
294 CHECK(m.cols() == 4);
295 for (int r = 0; r < 3; r++)
296 for (int c = 0; c < 4; c++)
297 CHECK(m(r, c) == doctest::Approx(static_cast<DNDS::real>(i * 100 + r * 10 + c)));
298 }
299 }
300
301 SUBCASE("MatRowSize and MatColSize")
302 {
303 CHECK(mat.MatRowSize() == 3);
304 CHECK(mat.MatColSize() == 4);
305 }
306}
307
308// ===========================================================================
309// ArrayEigenMatrix dynamic sizes
310// ===========================================================================
311TEST_CASE("ArrayEigenMatrix dynamic sizes")
312{
313 MPIInfo mpi = worldMPI();
314 constexpr DNDS::index N = 15;
315 constexpr DNDS::rowsize dynRows = 4;
316 constexpr DNDS::rowsize dynCols = 3;
317
319 mat.Resize(N, dynRows, dynCols);
320
321 CHECK(mat.Size() == N);
322 CHECK(mat.MatRowSize() == dynRows);
323 CHECK(mat.MatColSize() == dynCols);
324
325 // Fill and verify
326 for (DNDS::index i = 0; i < N; i++)
327 {
328 auto m = mat[i];
329 for (int r = 0; r < dynRows; r++)
330 for (int c = 0; c < dynCols; c++)
331 m(r, c) = static_cast<DNDS::real>(i * 1000 + r * 10 + c);
332 }
333
334 for (DNDS::index i = 0; i < N; i++)
335 {
336 auto m = mat[i];
337 CHECK(m.rows() == dynRows);
338 CHECK(m.cols() == dynCols);
339 for (int r = 0; r < dynRows; r++)
340 for (int c = 0; c < dynCols; c++)
341 CHECK(m(r, c) == doctest::Approx(static_cast<DNDS::real>(i * 1000 + r * 10 + c)));
342 }
343}
344
345// ===========================================================================
346// ArrayEigenMatrix ghost communication
347// ===========================================================================
348TEST_CASE("ArrayEigenMatrix ghost communication")
349{
350 MPIInfo mpi = worldMPI();
351 constexpr DNDS::index nLocal = 20;
352 constexpr DNDS::rowsize matR = 3;
353 constexpr DNDS::rowsize matC = 4;
354 constexpr DNDS::index nGhostPer = 4;
355
356 // Use ArrayPair which properly manages father/son/transformer
358 pair.InitPair("matGhost::pair", mpi);
359 pair.father->Resize(nLocal, matR, matC);
360 pair.father->createGlobalMapping();
361 DNDS::index gOff = (*pair.father->pLGlobalMapping)(mpi.rank, 0);
362
363 for (DNDS::index i = 0; i < nLocal; i++)
364 {
365 auto m = pair.father->operator[](i);
366 for (int r = 0; r < matR; r++)
367 for (int c = 0; c < matC; c++)
368 m(r, c) = static_cast<DNDS::real>((gOff + i) * 1000 + r * 10 + c);
369 }
370
371 pair.TransAttach();
372 pair.trans.createFatherGlobalMapping();
373
374 auto pullIdx = pullFirstNFromOthers(mpi, *pair.trans.pLGlobalMapping, nGhostPer);
375 pair.trans.createGhostMapping(std::vector<DNDS::index>(pullIdx));
376 pair.trans.createMPITypes();
377 pair.trans.pullOnce();
378
379 CHECK(pair.son->Size() == static_cast<DNDS::index>(pullIdx.size()));
380
381 for (DNDS::index g = 0; g < pair.son->Size(); g++)
382 {
383 DNDS::index ghostGlobal = pair.trans.pLGhostMapping->ghostIndex[g];
384 auto m = pair.son->operator[](g);
385 for (int r = 0; r < matR; r++)
386 for (int c = 0; c < matC; c++)
387 CHECK(m(r, c) == doctest::Approx(
388 static_cast<DNDS::real>(ghostGlobal * 1000 + r * 10 + c)));
389 }
390}
391
392// ===========================================================================
393// ArrayEigenMatrixBatch basics
394// ===========================================================================
395TEST_CASE("ArrayEigenMatrixBatch basics")
396{
397 MPIInfo mpi = worldMPI();
398 constexpr DNDS::index N = 10;
399
400 ArrayEigenMatrixBatch batch(mpi);
401 batch.Resize(N);
402
403 // For each row, create a batch of (i%3 + 1) matrices of varying sizes
404 for (DNDS::index i = 0; i < N; i++)
405 {
406 int nMats = static_cast<int>(i % 3 + 1);
407 std::vector<Eigen::MatrixXd> matrices;
408 matrices.reserve(nMats);
409 for (int k = 0; k < nMats; k++)
410 {
411 int rows = k + 1;
412 int cols = k + 2;
413 Eigen::MatrixXd m(rows, cols);
414 for (int r = 0; r < rows; r++)
415 for (int c = 0; c < cols; c++)
416 m(r, c) = static_cast<DNDS::real>(i * 10000 + k * 100 + r * 10 + c);
417 matrices.push_back(m);
418 }
419 batch.InitializeWriteRow(i, matrices);
420 }
421
422 batch.Compress();
423
424 SUBCASE("verify BatchSize")
425 {
426 for (DNDS::index i = 0; i < N; i++)
427 {
428 auto expectedBatchSize = static_cast<DNDS::index>(i % 3 + 1);
429 CHECK(batch.BatchSize(i) == static_cast<DNDS::index>(expectedBatchSize));
430 }
431 }
432
433 SUBCASE("read back via operator()(i, j)")
434 {
435 for (DNDS::index i = 0; i < N; i++)
436 {
437 int nMats = static_cast<int>(i % 3 + 1);
438 for (int k = 0; k < nMats; k++)
439 {
440 auto m = batch(i, k);
441 int rows = k + 1;
442 int cols = k + 2;
443 CHECK(m.rows() == rows);
444 CHECK(m.cols() == cols);
445 for (int r = 0; r < rows; r++)
446 for (int c = 0; c < cols; c++)
447 CHECK(m(r, c) == doctest::Approx(
448 static_cast<DNDS::real>(i * 10000 + k * 100 + r * 10 + c)));
449 }
450 }
451 }
452}
453
454// ===========================================================================
455// ArrayEigenUniMatrixBatch basics
456// ===========================================================================
457TEST_CASE("ArrayEigenUniMatrixBatch basics")
458{
459 MPIInfo mpi = worldMPI();
460 constexpr DNDS::index N = 15;
461 constexpr int matRows = 3;
462 constexpr int matCols = 4;
463
465 ubatch.Resize(N, matRows, matCols);
466
467 CHECK(ubatch.Size() == N);
468 CHECK(ubatch.Rows() == matRows);
469 CHECK(ubatch.Cols() == matCols);
470 CHECK(ubatch.MSize() == matRows * matCols);
471
472 // Assign each row a batch size of (i % 4 + 1)
473 for (DNDS::index i = 0; i < N; i++)
474 ubatch.ResizeBatch(i, static_cast<DNDS::rowsize>(i % 4 + 1));
475
476 // Fill matrices
477 for (DNDS::index i = 0; i < N; i++)
478 {
479 DNDS::rowsize bs = ubatch.BatchSize(i);
480 for (DNDS::rowsize j = 0; j < bs; j++)
481 {
482 auto m = ubatch(i, j);
483 for (int r = 0; r < matRows; r++)
484 for (int c = 0; c < matCols; c++)
485 m(r, c) = static_cast<DNDS::real>(i * 10000 + j * 100 + r * 10 + c);
486 }
487 }
488
489 ubatch.Compress();
490
491 SUBCASE("verify BatchSize after Compress")
492 {
493 for (DNDS::index i = 0; i < N; i++)
494 {
495 DNDS::rowsize expected = static_cast<DNDS::rowsize>(i % 4 + 1);
496 CHECK(ubatch.BatchSize(i) == expected);
497 }
498 }
499
500 SUBCASE("verify matrix values after Compress")
501 {
502 for (DNDS::index i = 0; i < N; i++)
503 {
504 DNDS::rowsize bs = ubatch.BatchSize(i);
505 for (DNDS::rowsize j = 0; j < bs; j++)
506 {
507 auto m = ubatch(i, j);
508 CHECK(m.rows() == matRows);
509 CHECK(m.cols() == matCols);
510 for (int r = 0; r < matRows; r++)
511 for (int c = 0; c < matCols; c++)
512 CHECK(m(r, c) == doctest::Approx(
513 static_cast<DNDS::real>(i * 10000 + j * 100 + r * 10 + c)));
514 }
515 }
516 }
517}
518
519// ===========================================================================
520// ArrayEigenUniMatrixBatch static sizes
521// ===========================================================================
522TEST_CASE("ArrayEigenUniMatrixBatch static sizes")
523{
524 MPIInfo mpi = worldMPI();
525 constexpr DNDS::index N = 10;
526
528 ubatch.Resize(N);
529
530 CHECK(ubatch.Rows() == 2);
531 CHECK(ubatch.Cols() == 3);
532 CHECK(ubatch.MSize() == 6);
533
534 for (DNDS::index i = 0; i < N; i++)
535 ubatch.ResizeBatch(i, static_cast<DNDS::rowsize>(i % 3 + 1));
536
537 for (DNDS::index i = 0; i < N; i++)
538 {
539 DNDS::rowsize bs = ubatch.BatchSize(i);
540 for (DNDS::rowsize j = 0; j < bs; j++)
541 {
542 auto m = ubatch(i, j);
543 m.setConstant(static_cast<DNDS::real>(i * 100 + j));
544 }
545 }
546
547 ubatch.Compress();
548
549 for (DNDS::index i = 0; i < N; i++)
550 {
551 DNDS::rowsize bs = ubatch.BatchSize(i);
552 CHECK(bs == static_cast<DNDS::rowsize>(i % 3 + 1));
553 for (DNDS::rowsize j = 0; j < bs; j++)
554 {
555 auto m = ubatch(i, j);
556 for (int r = 0; r < 2; r++)
557 for (int c = 0; c < 3; c++)
558 CHECK(m(r, c) == doctest::Approx(static_cast<DNDS::real>(i * 100 + j)));
559 }
560 }
561}
562
563// ===========================================================================
564// ArrayAdjacency clone
565// ===========================================================================
566TEST_CASE("ArrayAdjacency clone")
567{
568 MPIInfo mpi = worldMPI();
569 constexpr DNDS::index N = 10;
570
572 adj.Resize(N, [](DNDS::index i) -> DNDS::rowsize
573 { return static_cast<DNDS::rowsize>(i % 3 + 1); });
574
575 for (DNDS::index i = 0; i < N; i++)
576 for (DNDS::rowsize j = 0; j < adj.RowSize(i); j++)
577 adj.rowPtr(i)[j] = i * 100 + j;
578
580 adj2.clone(adj);
581
582 for (DNDS::index i = 0; i < N; i++)
583 {
584 CHECK(adj2.RowSize(i) == adj.RowSize(i));
585 for (DNDS::rowsize j = 0; j < adj.RowSize(i); j++)
586 CHECK(adj2.rowPtr(i)[j] == adj.rowPtr(i)[j]);
587 }
588
589 // Modifying original does not affect clone
590 adj.rowPtr(0)[0] = -999;
591 CHECK(adj2.rowPtr(0)[0] != -999);
592}
593
594// ===========================================================================
595// ArrayEigenMatrix NonUniformSize rows
596// ===========================================================================
597TEST_CASE("ArrayEigenMatrix NonUniformSize rows")
598{
599 MPIInfo mpi = worldMPI();
600 constexpr DNDS::index N = 12;
601
603 // Initial size with some default row/col dimensions
604 mat.Resize(N, 2, 3);
605
606 // Resize individual matrices to different dimensions
607 for (DNDS::index i = 0; i < N; i++)
608 {
609 DNDS::rowsize nr = static_cast<DNDS::rowsize>(i % 3 + 1);
610 DNDS::rowsize nc = static_cast<DNDS::rowsize>(i % 2 + 2);
611 mat.ResizeMat(i, nr, nc);
612 }
613
614 // Fill
615 for (DNDS::index i = 0; i < N; i++)
616 {
617 auto m = mat[i];
618 for (int r = 0; r < m.rows(); r++)
619 for (int c = 0; c < m.cols(); c++)
620 m(r, c) = static_cast<DNDS::real>(i * 1000 + r * 10 + c);
621 }
622
623 mat.Compress();
624
625 // Verify
626 for (DNDS::index i = 0; i < N; i++)
627 {
628 DNDS::rowsize nr = static_cast<DNDS::rowsize>(i % 3 + 1);
629 DNDS::rowsize nc = static_cast<DNDS::rowsize>(i % 2 + 2);
630 CHECK(mat.MatRowSize(i) == nr);
631 CHECK(mat.MatColSize(i) == nc);
632
633 auto m = mat[i];
634 for (int r = 0; r < nr; r++)
635 for (int c = 0; c < nc; c++)
636 CHECK(m(r, c) == doctest::Approx(static_cast<DNDS::real>(i * 1000 + r * 10 + c)));
637 }
638}
639
640// ===========================================================================
641// ArrayEigenVector with static size
642// ===========================================================================
643TEST_CASE("ArrayEigenVector static size")
644{
645 MPIInfo mpi = worldMPI();
646 constexpr DNDS::index N = 20;
647
648 ArrayEigenVector<5> vec(mpi);
649 vec.Resize(N);
650
651 CHECK(vec.Size() == N);
652 CHECK(vec.RowSize() == 5);
653
654 for (DNDS::index i = 0; i < N; i++)
655 {
656 auto v = vec[i];
657 v.setLinSpaced(5, static_cast<DNDS::real>(i), static_cast<DNDS::real>(i + 4));
658 }
659
660 for (DNDS::index i = 0; i < N; i++)
661 {
662 auto v = vec[i];
663 CHECK(v.size() == 5);
664 // setLinSpaced(5, i, i+4) produces {i, i+1, i+2, i+3, i+4}
665 for (int j = 0; j < 5; j++)
666 CHECK(v(j) == doctest::Approx(static_cast<DNDS::real>(i + j)));
667 }
668}
669
670// ===========================================================================
671// ArrayEigenMatrixBatch ghost communication
672// ===========================================================================
673TEST_CASE("ArrayEigenMatrixBatch ghost communication")
674{
675 MPIInfo mpi = worldMPI();
676 constexpr DNDS::index nLocal = 10;
677 constexpr DNDS::index nGhostPer = 2;
678
679 // Use ArrayPair for proper ghost communication
681 pair.InitPair("batchGhost::pair", mpi);
682 pair.father->Resize(nLocal);
683
684 // Uniform batches: each row has 2 matrices of size 2x2
685 for (DNDS::index i = 0; i < nLocal; i++)
686 {
687 std::vector<Eigen::MatrixXd> matrices;
688 for (int k = 0; k < 2; k++)
689 {
690 Eigen::MatrixXd m(2, 2);
691 m.setZero();
692 matrices.push_back(m);
693 }
694 pair.father->InitializeWriteRow(i, matrices);
695 }
696 pair.father->Compress();
697 pair.father->createGlobalMapping();
698
699 DNDS::index gOff = (*pair.father->pLGlobalMapping)(mpi.rank, 0);
700
701 // Fill with global-index-based values
702 for (DNDS::index i = 0; i < nLocal; i++)
703 {
704 for (int k = 0; k < 2; k++)
705 {
706 auto m = pair.father->operator()(i, k);
707 m.setConstant(static_cast<DNDS::real>((gOff + i) * 1000 + k));
708 }
709 }
710
711 pair.TransAttach();
712 pair.trans.createFatherGlobalMapping();
713
714 auto pullIdx = pullFirstNFromOthers(mpi, *pair.trans.pLGlobalMapping, nGhostPer);
715 pair.trans.createGhostMapping(std::vector<DNDS::index>(pullIdx));
716 pair.trans.createMPITypes();
717 pair.trans.pullOnce();
718
719 CHECK(pair.son->Size() == static_cast<DNDS::index>(pullIdx.size()));
720
721 // Verify ghost batch data
722 for (DNDS::index g = 0; g < pair.son->Size(); g++)
723 {
724 DNDS::index ghostGlobal = pair.trans.pLGhostMapping->ghostIndex[g];
725 CHECK(pair.son->BatchSize(g) == 2);
726 for (int k = 0; k < 2; k++)
727 {
728 auto m = pair.son->operator()(g, k);
729 CHECK(m.rows() == 2);
730 CHECK(m.cols() == 2);
731 DNDS::real expected = static_cast<DNDS::real>(ghostGlobal * 1000 + k);
732 for (int r = 0; r < 2; r++)
733 for (int c = 0; c < 2; c++)
734 CHECK(m(r, c) == doctest::Approx(expected));
735 }
736 }
737}
738
739// ===========================================================================
740// ArrayAdjacency fixed-size variant
741// ===========================================================================
742TEST_CASE("ArrayAdjacency fixed-size variant")
743{
744 MPIInfo mpi = worldMPI();
745 constexpr DNDS::index N = 25;
746
747 // ArrayAdjacency<3> has TABLE_StaticFixed layout with 3 columns per row
749 adj.Resize(N);
750
751 CHECK(adj.Size() == N);
752 CHECK(adj.RowSize() == 3);
753
754 for (DNDS::index i = 0; i < N; i++)
755 {
756 auto row = adj[i];
757 for (DNDS::rowsize j = 0; j < 3; j++)
758 row[j] = i * 10 + j;
759 }
760
761 for (DNDS::index i = 0; i < N; i++)
762 {
763 auto row = adj[i];
764 CHECK(row.size() == 3);
765 for (DNDS::rowsize j = 0; j < 3; j++)
766 CHECK(row[j] == i * 10 + j);
767 }
768}
769
770// ===========================================================================
771// Parametric: ArrayAdjacency across row sizes
772// ===========================================================================
773// Axes: RS = {2, 5, 8, NonUniformSize}
774
775template <DNDS::rowsize RS>
776struct AdjTag
777{
778 static constexpr DNDS::rowsize rs = RS;
779};
780
785
786TEST_CASE_TEMPLATE("ArrayAdjacency parametric", Tag,
788{
789 constexpr DNDS::rowsize RS = Tag::rs;
790 MPIInfo mpi = worldMPI();
791 constexpr DNDS::index N = 25;
792
794
795 if constexpr (RS == NonUniformSize)
796 {
797 adj.Resize(N);
798 for (DNDS::index i = 0; i < N; i++)
799 adj.ResizeRow(i, static_cast<DNDS::rowsize>(i % 5 + 1));
800 adj.Compress();
801 }
802 else
803 {
804 adj.Resize(N);
805 }
806
807 // Fill
808 for (DNDS::index i = 0; i < N; i++)
809 for (DNDS::rowsize j = 0; j < adj.RowSize(i); j++)
810 adj(i, j) = i * 100 + j;
811
812 SUBCASE("data round-trip")
813 {
814 for (DNDS::index i = 0; i < N; i++)
815 {
816 if constexpr (RS == NonUniformSize)
817 CHECK(adj.RowSize(i) == static_cast<DNDS::rowsize>(i % 5 + 1));
818 else
819 CHECK(adj.RowSize(i) == RS);
820 for (DNDS::rowsize j = 0; j < adj.RowSize(i); j++)
821 CHECK(adj(i, j) == i * 100 + j);
822 }
823 }
824
825 SUBCASE("ghost pull")
826 {
827 if (mpi.size < 2)
828 return;
829
830 adj.createGlobalMapping();
831 DNDS::index gOff = (*adj.pLGlobalMapping)(mpi.rank, 0);
832
833 // Re-fill with global-index encoding
834 for (DNDS::index i = 0; i < N; i++)
835 for (DNDS::rowsize j = 0; j < adj.RowSize(i); j++)
836 adj(i, j) = (gOff + i) * 100 + j;
837
838 auto father = std::make_shared<ArrayAdjacency<RS>>(adj);
839 auto son = std::make_shared<ArrayAdjacency<RS>>(mpi);
840 using TTransformer = typename ArrayTransformerType<ArrayAdjacency<RS>>::Type;
841 TTransformer trans;
842 trans.setFatherSon(father, son);
843 trans.createFatherGlobalMapping();
844 auto pullIdx = pullFirstNFromOthers(mpi, *trans.pLGlobalMapping, 3);
845 trans.createGhostMapping(std::vector<DNDS::index>(pullIdx));
846 trans.createMPITypes();
847 trans.pullOnce();
848
849 CHECK(son->Size() == static_cast<DNDS::index>(pullIdx.size()));
850 for (DNDS::index g = 0; g < son->Size(); g++)
851 {
852 DNDS::index ghostGlobal = trans.pLGhostMapping->ghostIndex[g];
853 for (DNDS::rowsize j = 0; j < son->RowSize(g); j++)
854 CHECK(son->operator()(g, j) == ghostGlobal * 100 + j);
855 }
856 }
857}
858
859// ===========================================================================
860// Parametric: ArrayEigenVector across vector sizes
861// ===========================================================================
862// Axes: vec_size = {1, 3, 7, DynamicSize}
863
864template <DNDS::rowsize VS>
865struct VecTag
866{
867 static constexpr DNDS::rowsize vs = VS;
868};
869
874
875TEST_CASE_TEMPLATE("ArrayEigenVector parametric", Tag,
877{
878 constexpr DNDS::rowsize VS = Tag::vs;
879 MPIInfo mpi = worldMPI();
880 constexpr DNDS::index N = 30;
881 constexpr DNDS::rowsize actualSize = (VS == DynamicSize) ? 5 : VS;
882
883 ArrayEigenVector<VS> vec(mpi);
884 if constexpr (VS == DynamicSize)
885 vec.Resize(N, actualSize);
886 else
887 vec.Resize(N);
888
889 CHECK(vec.Size() == N);
890 CHECK(vec.RowSize() == actualSize);
891
892 // Fill
893 for (DNDS::index i = 0; i < N; i++)
894 {
895 auto v = vec[i];
896 for (DNDS::rowsize j = 0; j < actualSize; j++)
897 v(j) = static_cast<DNDS::real>(i * 100 + j);
898 }
899
900 SUBCASE("data round-trip")
901 {
902 for (DNDS::index i = 0; i < N; i++)
903 {
904 auto v = vec[i];
905 CHECK(v.size() == actualSize);
906 for (DNDS::rowsize j = 0; j < actualSize; j++)
907 CHECK(v(j) == doctest::Approx(static_cast<DNDS::real>(i * 100 + j)));
908 }
909 }
910
911 SUBCASE("ghost pull")
912 {
913 if (mpi.size < 2)
914 return;
915
917 DNDS::index gOff = (*vec.pLGlobalMapping)(mpi.rank, 0);
918 for (DNDS::index i = 0; i < N; i++)
919 {
920 auto v = vec[i];
921 for (DNDS::rowsize j = 0; j < actualSize; j++)
922 v(j) = static_cast<DNDS::real>((gOff + i) * 100 + j);
923 }
924
925 auto father = std::make_shared<ArrayEigenVector<VS>>(vec);
926 auto son = std::make_shared<ArrayEigenVector<VS>>(mpi);
927 using TTransformer = typename ArrayTransformerType<ArrayEigenVector<VS>>::Type;
928 TTransformer trans;
929 trans.setFatherSon(father, son);
930 trans.createFatherGlobalMapping();
931 auto pullIdx = pullFirstNFromOthers(mpi, *trans.pLGlobalMapping, 4);
932 trans.createGhostMapping(std::vector<DNDS::index>(pullIdx));
933 trans.createMPITypes();
934 trans.pullOnce();
935
936 CHECK(son->Size() == static_cast<DNDS::index>(pullIdx.size()));
937 for (DNDS::index g = 0; g < son->Size(); g++)
938 {
939 DNDS::index ghostGlobal = trans.pLGhostMapping->ghostIndex[g];
940 auto v = son->operator[](g);
941 for (DNDS::rowsize j = 0; j < actualSize; j++)
942 CHECK(v(j) == doctest::Approx(static_cast<DNDS::real>(ghostGlobal * 100 + j)));
943 }
944 }
945}
946
947// ===========================================================================
948// Parametric: ArrayEigenMatrix across matrix dimensions
949// ===========================================================================
950// Axes: (ni, nj) = {(2,3), (4,5), (1,7), (DynamicSize,DynamicSize)}
951
952template <DNDS::rowsize NI, DNDS::rowsize NJ>
953struct MatTag
954{
955 static constexpr DNDS::rowsize ni = NI;
956 static constexpr DNDS::rowsize nj = NJ;
957};
958
963
964TEST_CASE_TEMPLATE("ArrayEigenMatrix parametric", Tag,
967{
968 constexpr DNDS::rowsize NI = Tag::ni;
969 constexpr DNDS::rowsize NJ = Tag::nj;
970 MPIInfo mpi = worldMPI();
971 constexpr DNDS::index N = 20;
972 constexpr DNDS::rowsize actualNI = (NI == DynamicSize) ? 3 : NI;
973 constexpr DNDS::rowsize actualNJ = (NJ == DynamicSize) ? 4 : NJ;
974
976 mat.Resize(N, actualNI, actualNJ);
977
978 CHECK(mat.Size() == N);
979 CHECK(mat.MatRowSize() == actualNI);
980 CHECK(mat.MatColSize() == actualNJ);
981
982 // Fill
983 for (DNDS::index i = 0; i < N; i++)
984 {
985 auto m = mat[i];
986 for (int r = 0; r < actualNI; r++)
987 for (int c = 0; c < actualNJ; c++)
988 m(r, c) = static_cast<DNDS::real>(i * 1000 + r * 10 + c);
989 }
990
991 SUBCASE("data round-trip")
992 {
993 for (DNDS::index i = 0; i < N; i++)
994 {
995 auto m = mat[i];
996 CHECK(m.rows() == actualNI);
997 CHECK(m.cols() == actualNJ);
998 for (int r = 0; r < actualNI; r++)
999 for (int c = 0; c < actualNJ; c++)
1000 CHECK(m(r, c) == doctest::Approx(static_cast<DNDS::real>(i * 1000 + r * 10 + c)));
1001 }
1002 }
1003
1004 SUBCASE("ghost pull")
1005 {
1006 if (mpi.size < 2)
1007 return;
1008
1010 pair.InitPair("matParam::pair", mpi);
1011 pair.father->Resize(N, actualNI, actualNJ);
1012 pair.son->Resize(0, actualNI, actualNJ);
1013
1014 pair.father->createGlobalMapping();
1015 DNDS::index gOff = (*pair.father->pLGlobalMapping)(mpi.rank, 0);
1016 for (DNDS::index i = 0; i < N; i++)
1017 {
1018 auto m = pair.father->operator[](i);
1019 for (int r = 0; r < actualNI; r++)
1020 for (int c = 0; c < actualNJ; c++)
1021 m(r, c) = static_cast<DNDS::real>((gOff + i) * 1000 + r * 10 + c);
1022 }
1023
1024 pair.TransAttach();
1025 pair.trans.createFatherGlobalMapping();
1026
1027 auto pullIdx = pullFirstNFromOthers(mpi, *pair.trans.pLGlobalMapping, 3);
1028 pair.trans.createGhostMapping(std::vector<DNDS::index>(pullIdx));
1029 pair.trans.createMPITypes();
1030 pair.trans.pullOnce();
1031
1032 CHECK(pair.son->Size() == static_cast<DNDS::index>(pullIdx.size()));
1033 for (DNDS::index g = 0; g < pair.son->Size(); g++)
1034 {
1035 DNDS::index ghostGlobal = pair.trans.pLGhostMapping->ghostIndex[g];
1036 auto m = pair.son->operator[](g);
1037 for (int r = 0; r < actualNI; r++)
1038 for (int c = 0; c < actualNJ; c++)
1039 CHECK(m(r, c) == doctest::Approx(
1040 static_cast<DNDS::real>(ghostGlobal * 1000 + r * 10 + c)));
1041 }
1042 }
1043}
Father-son array pairs with device views and ghost communication.
ParArray (MPI-aware array) and ArrayTransformer (ghost/halo communication).
Mesh-connectivity array: ParArray<index> whose operator[] yields an AdjacencyRow typed view.
index * rowPtr(index i)
Raw index* pointer to row i (bypasses the typed wrapper).
void clone(const t_self &R)
Shallow copy (same semantics as assignment).
CSR array storing a variable-sized batch of Eigen matrices per row.
void InitializeWriteRow(index i, const std::vector< t_matrices_elem > &matrices)
ParArray<real> whose operator[] returns an Eigen::Map<Matrix<real, Ni, Nj>>.
rowsize MatColSize(index iMat=0) const
void ResizeMat(index iMat, rowsize nSizeRow, rowsize nSizeCol)
void Resize(index nSize, rowsize nSizeRowDynamic, rowsize nSizeColDynamic)
rowsize MatRowSize(index iMat=0) const
CSR array whose rows store a batch of identically-sized Eigen matrices.
void Resize(index n_size, int r, int c)
void ResizeBatch(index i, rowsize b_size)
ParArray<real, N> whose operator[] returns an Eigen::Map<Vector>.
Ghost-communication engine for a father / son ParArray pair.
void setFatherSon(const t_pArray &n_father, const t_pArray &n_son)
Attach father and son arrays. First setup step.
void Resize(index nSize, rowsize nRow_size_dynamic)
Resize the array, setting a uniform or maximum row width.
Definition Array.hpp:395
rowsize RowSize() const
Uniform row width for fixed layouts (no row index needed).
Definition Array.hpp:176
void Compress()
Layout-polymorphic compress: no-op for non-CSR, calls CSRCompress for CSR.
Definition Array.hpp:355
index Size() const
Number of rows currently stored. O(1).
Definition Array.hpp:171
Table mapping rank-local row indices to the global index space.
t_pLGlobalMapping pLGlobalMapping
Shared pointer to the global-offsets table. Populated by createGlobalMapping; may be pointed at an ex...
void createGlobalMapping()
Collective: build the global offsets table.
int main()
Definition dummy.cpp:3
the host side operators are provided as implemented
int32_t rowsize
Row-width / per-row element-count type (signed 32-bit).
Definition Defines.hpp:109
DNDS_CONSTANT const rowsize DynamicSize
Template parameter flag: "row width is set at runtime but uniform".
Definition Defines.hpp:277
DNDS_CONSTANT const rowsize NonUniformSize
Template parameter flag: "each row has an independent width".
Definition Defines.hpp:279
int64_t index
Global row / DOF index type (signed 64-bit; handles multi-billion-cell meshes).
Definition Defines.hpp:107
double real
Canonical floating-point scalar used throughout DNDSR (double precision).
Definition Defines.hpp:105
int MPI_int
MPI counterpart type for MPI_int (= C int). Used for counts and ranks in MPI calls.
Definition MPI.hpp:43
static constexpr DNDS::rowsize rs
DNDS_DEVICE_CALLABLE index Size() const
Combined father + son row count.
Definition ArrayPair.hpp:33
Convenience bundle of a father, son, and attached ArrayTransformer.
void TransAttach()
Bind the transformer to the current father / son pointers.
ssp< TArray > father
Owned-side array (must be resized before ghost setup).
void InitPair(const std::string &name, Args &&...args)
Allocate both father and son arrays, forwarding all args to TArray constructor.
ssp< TArray > son
Ghost-side array (sized automatically by createMPITypes / BorrowAndPull).
TTrans trans
Ghost-communication engine bound to father and son.
Type trait computing the ArrayTransformer type for a given Array type.
Lightweight bundle of an MPI communicator and the calling rank's coordinates.
Definition MPI.hpp:215
static constexpr DNDS::rowsize nj
static constexpr DNDS::rowsize ni
static constexpr DNDS::rowsize vs
Eigen::Matrix< real, 5, 1 > v
constexpr DNDS::index N
TEST_CASE_TEMPLATE("ArrayAdjacency parametric", Tag, AdjTag< 2 >, AdjTag< 5 >, AdjTag< 8 >, AdjTag< NonUniformSize >)
TYPE_TO_STRING(AdjTag< 2 >)
ArrayTransformer< DNDS::real, DynamicSize > trans
constexpr DNDS::index nLocal
constexpr DNDS::rowsize dynCols
DNDS::index gOff
tVec r(NCells)
auto adj
CHECK(result.size()==3)
real expected
auto res
Definition test_ODE.cpp:196
TEST_CASE("3D: VFV P2 HQM error < P1 on sinCos3D")