Skip to content

Commit 91d3a49

Browse files
committed
test(integration): add parallel scaling tests
Add domain decomposition, global coverage, and halo pattern tests to validate MPI decomposition and halo exchange. - Tagged [integration][parallel_scaling][mpi]
1 parent f496a05 commit 91d3a49

File tree

3 files changed

+133
-0
lines changed

3 files changed

+133
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-FileCopyrightText: 2025 VTT Technical Research Centre of Finland Ltd
2+
// SPDX-License-Identifier: AGPL-3.0-or-later
3+
4+
#include <catch2/catch_test_macros.hpp>
5+
#include <mpi.h>
6+
#include <openpfc/core/decomposition.hpp>
7+
#include <openpfc/core/world.hpp>
8+
#include <openpfc/fft.hpp>
9+
10+
using namespace pfc;
11+
12+
TEST_CASE("Domain decomposition basic properties",
13+
"[integration][mpi][decomposition]") {
14+
int rank = 0, size = 1;
15+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
16+
MPI_Comm_size(MPI_COMM_WORLD, &size);
17+
18+
auto world = world::uniform(32, 1.0);
19+
auto decomp = decomposition::create(world, size);
20+
auto fft = fft::create(decomp);
21+
22+
// Validate local subdomain for this rank
23+
const auto &local_world = decomposition::get_subworld(decomp, rank);
24+
auto local_size = world::get_size(local_world);
25+
REQUIRE(local_size[0] > 0);
26+
REQUIRE(local_size[1] > 0);
27+
REQUIRE(local_size[2] > 0);
28+
29+
// Inbox size matches local domain cell count
30+
const std::size_t local_cells = static_cast<std::size_t>(local_size[0]) *
31+
static_cast<std::size_t>(local_size[1]) *
32+
static_cast<std::size_t>(local_size[2]);
33+
REQUIRE(fft.size_inbox() == local_cells);
34+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-FileCopyrightText: 2025 VTT Technical Research Centre of Finland Ltd
2+
// SPDX-License-Identifier: AGPL-3.0-or-later
3+
4+
#include <catch2/catch_test_macros.hpp>
5+
#include <mpi.h>
6+
#include <openpfc/core/decomposition.hpp>
7+
#include <openpfc/core/world.hpp>
8+
9+
using namespace pfc;
10+
11+
TEST_CASE("Global coverage equals sum of local coverage",
12+
"[integration][mpi][decomposition]") {
13+
int rank = 0, size = 1;
14+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
15+
MPI_Comm_size(MPI_COMM_WORLD, &size);
16+
17+
auto world = world::uniform(24, 1.0);
18+
auto decomp = decomposition::create(world, size);
19+
20+
const auto &local_world = decomposition::get_subworld(decomp, rank);
21+
auto local_size = world::get_size(local_world);
22+
long long local_cells = static_cast<long long>(local_size[0]) *
23+
static_cast<long long>(local_size[1]) *
24+
static_cast<long long>(local_size[2]);
25+
26+
long long global_sum = 0;
27+
MPI_Allreduce(&local_cells, &global_sum, 1, MPI_LONG_LONG, MPI_SUM,
28+
MPI_COMM_WORLD);
29+
30+
auto global_size = world::get_size(world);
31+
long long global_cells = static_cast<long long>(global_size[0]) *
32+
static_cast<long long>(global_size[1]) *
33+
static_cast<long long>(global_size[2]);
34+
35+
if (rank == 0) {
36+
REQUIRE(global_sum == global_cells);
37+
}
38+
39+
// Every rank should have non-zero local coverage when size > 1
40+
if (size > 1) {
41+
REQUIRE(local_cells > 0);
42+
} else {
43+
SUCCEED("Single rank run - multi-rank coverage check skipped");
44+
}
45+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// SPDX-FileCopyrightText: 2025 VTT Technical Research Centre of Finland Ltd
2+
// SPDX-License-Identifier: AGPL-3.0-or-later
3+
4+
#include <catch2/catch_test_macros.hpp>
5+
#include <mpi.h>
6+
#include <openpfc/core/decomposition.hpp>
7+
#include <openpfc/core/halo_pattern.hpp>
8+
#include <openpfc/core/world.hpp>
9+
10+
using namespace pfc;
11+
12+
TEST_CASE("Halo send/recv sizes match expected face areas",
13+
"[integration][mpi][halo]") {
14+
int rank = 0, size = 1;
15+
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
16+
MPI_Comm_size(MPI_COMM_WORLD, &size);
17+
18+
auto world = world::uniform(24, 1.0);
19+
auto decomp = decomposition::create(world, size);
20+
21+
const auto &local_world = decomposition::get_subworld(decomp, rank);
22+
auto local_size = world::get_size(local_world);
23+
auto local_lower = world::get_lower(local_world);
24+
auto local_upper = world::get_upper(local_world);
25+
26+
const int halo_width = 1;
27+
auto patterns = halo::create_halo_patterns(decomp, rank, halo::Connectivity::Faces,
28+
halo_width);
29+
30+
for (const auto &entry : patterns) {
31+
const auto &dir = entry.first;
32+
const auto &send_recv = entry.second;
33+
const auto &send = send_recv.first;
34+
const auto &recv = send_recv.second;
35+
36+
// Expected indices count based on direction
37+
// Compute lengths directly from bounds used by halo implementation
38+
const long long nx = static_cast<long long>(local_upper[0] - local_lower[0]);
39+
const long long ny = static_cast<long long>(local_upper[1] - local_lower[1]);
40+
const long long nz = static_cast<long long>(local_upper[2] - local_lower[2]);
41+
42+
long long expected = 0;
43+
if (dir[0] != 0) {
44+
expected = static_cast<long long>(halo_width) * ny * nz;
45+
} else if (dir[1] != 0) {
46+
expected = nx * static_cast<long long>(halo_width) * nz;
47+
} else if (dir[2] != 0) {
48+
expected = nx * ny * static_cast<long long>(halo_width);
49+
}
50+
51+
REQUIRE(static_cast<long long>(send.size()) == expected);
52+
REQUIRE(static_cast<long long>(recv.size()) == expected);
53+
}
54+
}

0 commit comments

Comments
 (0)