37 std::span<Cell *const> ghost_cells) {
38 std::vector<std::string> violations;
41 std::unordered_set<ParticleList const *> ghost_set;
42 for (
Cell *c : ghost_cells) {
43 ghost_set.insert(&c->particles());
53 bool const has_collective =
56 std::unordered_set<ParticleList const *> collective_set;
59 collective_set.insert(pl);
74 std::unordered_set<ParticleList const *> referenced_ghosts;
75 for (
Cell *c : local_cells) {
76 for (
Cell *n : c->neighbors().all()) {
78 if (ghost_set.contains(pl)) {
79 referenced_ghosts.
insert(pl);
85 std::unordered_map<ParticleList const *, int> fill_count;
86 std::unordered_set<int> seen_peers;
90 if (!seen_peers.insert(nc.peer).second) {
91 std::ostringstream oss;
92 oss <<
"peer " << nc.peer <<
" appears in more than one NeighborComm";
93 violations.emplace_back(oss.str());
97 if (nc.send.size() != nc.recv.size()) {
98 std::ostringstream oss;
99 oss <<
"NeighborComm peer=" << nc.peer
100 <<
" has send.size()=" << nc.send.size()
101 <<
" != recv.size()=" << nc.recv.size();
102 violations.emplace_back(oss.str());
107 if (not ghost_set.contains(pl)) {
108 std::ostringstream oss;
109 oss <<
"NeighborComm peer=" << nc.peer
110 <<
" recv target is not a ghost cell";
111 violations.emplace_back(oss.str());
118 for (
auto const &lc : plan.
local) {
120 if (not ghost_set.contains(pl)) {
121 violations.emplace_back(
"LocalComm dst target is not a ghost cell");
135 for (
Cell *c : ghost_cells) {
137 auto it = fill_count.find(pl);
138 int count = (it != fill_count.end()) ? it->second : 0;
140 if (not collective_set.contains(pl) and referenced_ghosts.contains(pl)) {
141 violations.emplace_back(
142 "ghost cell is never filled (missing recv/dst)");
144 }
else if (count > 1) {
145 std::ostringstream oss;
146 oss <<
"ghost cell is filled " << count <<
" times (expected 1)";
147 violations.emplace_back(oss.str());
153 for (
Cell *c : local_cells) {
154 for (
Cell *n : c->neighbors().all()) {
156 if (not ghost_set.contains(pl)) {
159 if (collective_set.contains(pl)) {
162 auto it = fill_count.find(pl);
163 int count = (it != fill_count.end()) ? it->second : 0;
165 violations.emplace_back(
166 "local cell has ghost neighbor that is not a covered recv/dst "
167 "target (referenced-but-uncommunicated ghost)");
174 for (
Cell *c : local_cells) {
175 if (c->is_boundary()) {
178 for (
Cell *n : c->neighbors().all()) {
179 if (ghost_set.contains(&n->particles())) {
180 violations.emplace_back(
"interior cell has a ghost neighbor");
195 std::unordered_set<ParticleList const *> interior_set;
196 for (
Cell *c : local_cells) {
197 if (!c->is_boundary()) {
198 interior_set.insert(&c->particles());
201 if (!interior_set.empty()) {
203 for (
auto const &sr : nc.send) {
204 if (interior_set.count(sr.cell)) {
205 std::ostringstream oss;
206 oss <<
"interior cell appears as NeighborComm send source (peer="
207 << nc.peer <<
") — overlap-safety invariant violated";
208 violations.emplace_back(oss.str());
212 for (
auto const &lc : plan.
local) {
213 if (interior_set.count(lc.src)) {
214 violations.emplace_back(
215 "interior cell appears as LocalComm src — overlap-safety invariant "
225 std::vector<std::string> violations;
227 int const n = plan.
comm.size();
228 int const me = plan.
comm.rank();
231 std::vector<int> my_send_to(n, 0);
232 std::vector<int> my_recv_from(n, 0);
234 my_send_to[nc.peer] =
static_cast<int>(nc.send.size());
235 my_recv_from[nc.peer] =
static_cast<int>(nc.recv.size());
240 std::vector<int> peers_send_to_me(n, 0);
241 boost::mpi::all_to_all(plan.
comm, my_send_to, peers_send_to_me);
244 for (
int j = 0; j < n; ++j) {
247 if (my_recv_from[j] != peers_send_to_me[j]) {
248 std::ostringstream oss;
249 oss <<
"symmetry mismatch with peer " << j <<
": I expect to recv "
250 << my_recv_from[j] <<
" items but peer sends " << peers_send_to_me[j]
252 violations.emplace_back(oss.str());
std::vector< std::string > validate_halo_plan(HaloPlan const &plan, std::span< Cell *const > local_cells, std::span< Cell *const > ghost_cells)
Validate a HaloPlan for correctness.