OpenPFC  0.1.4
Phase Field Crystal simulation framework
Loading...
Searching...
No Matches
halo_pattern.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2025 VTT Technical Research Centre of Finland Ltd
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
45#pragma once
46
47#include <map>
54#include <vector>
55
56namespace pfc {
57namespace halo {
58
59using Int3 = pfc::types::Int3;
60
64enum class Connectivity {
65 Faces, // 6 neighbors: ±X, ±Y, ±Z (4-connectivity in 2D, 6-connectivity in 3D)
66 Edges, // 18 neighbors: faces + edges (8-connectivity in 2D)
67 All // 26 neighbors: faces + edges + corners (full 3D connectivity)
68};
69
91template <typename BackendTag = backend::CpuTag>
93create_send_halo(const decomposition::Decomposition &decomp, int rank,
94 const Int3 &direction, int halo_width) {
95 const auto &local_world = decomposition::get_subworld(decomp, rank);
96 (void)decomp; // For future use (periodic boundaries, etc.)
97
98 // Get local domain size and bounds
99 auto local_size = world::get_size(local_world);
102
103 // Calculate which face/edge/corner we're sending
104 std::vector<size_t> indices;
105
106 // Determine the region to extract based on direction and halo_width
107 // For send: we extract from the boundary of our local domain
108 Int3 send_lower = local_lower;
109 Int3 send_upper = local_upper;
110
111 // Adjust boundaries based on direction
112 // For +X direction: send rightmost face (last halo_width rows in X)
113 if (direction[0] > 0) {
115 send_upper[0] = local_upper[0];
116 } else if (direction[0] < 0) {
117 send_lower[0] = local_lower[0];
118 send_upper[0] = local_lower[0] + halo_width;
119 } else {
120 // direction[0] == 0: not sending in X direction, use full range
121 send_lower[0] = local_lower[0];
122 send_upper[0] = local_upper[0];
123 }
124
125 if (direction[1] > 0) {
126 send_lower[1] = local_upper[1] - halo_width;
127 send_upper[1] = local_upper[1];
128 } else if (direction[1] < 0) {
129 send_lower[1] = local_lower[1];
130 send_upper[1] = local_lower[1] + halo_width;
131 } else {
132 send_lower[1] = local_lower[1];
133 send_upper[1] = local_upper[1];
134 }
135
136 if (direction[2] > 0) {
137 send_lower[2] = local_upper[2] - halo_width;
138 send_upper[2] = local_upper[2];
139 } else if (direction[2] < 0) {
140 send_lower[2] = local_lower[2];
141 send_upper[2] = local_lower[2] + halo_width;
142 } else {
143 send_lower[2] = local_lower[2];
144 send_upper[2] = local_upper[2];
145 }
146
147 // Extract indices in local coordinate space
148 // Convert 3D indices to linear index (row-major: x varies fastest, then y, then z)
149 // Note: send_lower/send_upper are in global coordinates, we convert to local
150 for (int z = send_lower[2]; z < send_upper[2]; ++z) {
151 for (int y = send_lower[1]; y < send_upper[1]; ++y) {
152 for (int x = send_lower[0]; x < send_upper[0]; ++x) {
153 // Convert global 3D index to local linear index
154 // Local coordinates relative to local_lower
155 int local_z = z - local_lower[2];
156 int local_y = y - local_lower[1];
157 int local_x = x - local_lower[0];
158
159 // Bounds check (should always pass, but safety first)
160 if (local_x >= 0 && local_x < local_size[0] && local_y >= 0 &&
161 local_y < local_size[1] && local_z >= 0 && local_z < local_size[2]) {
162 // Row-major indexing: idx = z * (ny * nx) + y * nx + x
163 size_t local_idx =
164 static_cast<size_t>(local_z) * static_cast<size_t>(local_size[1]) *
165 static_cast<size_t>(local_size[0]) +
166 static_cast<size_t>(local_y) * static_cast<size_t>(local_size[0]) +
167 static_cast<size_t>(local_x);
168 indices.push_back(local_idx);
169 }
170 }
171 }
172 }
173
174 return core::SparseVector<BackendTag, size_t>(indices);
175}
176
195template <typename BackendTag = backend::CpuTag>
196core::SparseVector<BackendTag, size_t>
198 const Int3 &direction, int halo_width) {
199 const auto &local_world = decomposition::get_subworld(decomp, rank);
200 [[maybe_unused]] const auto &global_world =
201 decomposition::get_global_world(decomp);
202 [[maybe_unused]] const auto &grid = decomposition::get_grid(decomp);
203
204 auto local_size = world::get_size(local_world);
207
208 std::vector<size_t> indices;
209
210 // For receive: we receive into the halo zone at the opposite boundary
211 Int3 recv_lower = local_lower;
212 Int3 recv_upper = local_upper;
213
214 // Adjust boundaries: receive halo is at the opposite side from send
215 // If receiving from +X neighbor, we receive into leftmost halo zone
216 // Note: For fields with halo padding, these indices point into the halo zone
217 // For fields without halo padding, caller must handle allocation
218 if (direction[0] > 0) {
219 recv_lower[0] = local_lower[0];
221 } else if (direction[0] < 0) {
223 recv_upper[0] = local_upper[0];
224 } else {
225 // direction[0] == 0: not receiving in X direction, use full range
226 recv_lower[0] = local_lower[0];
227 recv_upper[0] = local_upper[0];
228 }
229
230 if (direction[1] > 0) {
231 recv_lower[1] = local_lower[1];
233 } else if (direction[1] < 0) {
235 recv_upper[1] = local_upper[1];
236 } else {
237 recv_lower[1] = local_lower[1];
238 recv_upper[1] = local_upper[1];
239 }
240
241 if (direction[2] > 0) {
242 recv_lower[2] = local_lower[2];
244 } else if (direction[2] < 0) {
246 recv_upper[2] = local_upper[2];
247 } else {
248 recv_lower[2] = local_lower[2];
249 recv_upper[2] = local_upper[2];
250 }
251
252 // Extract indices in local coordinate space
253 // Note: recv_lower/recv_upper are in global coordinates
254 // For receive halo: we receive into the halo zone at the boundary
255 // The indices are relative to local_lower, so they may be negative or
256 // beyond local_size if the field doesn't have halo padding.
257 // Caller is responsible for allocating field with appropriate halo space.
258
259 for (int z = recv_lower[2]; z < recv_upper[2]; ++z) {
260 for (int y = recv_lower[1]; y < recv_upper[1]; ++y) {
261 for (int x = recv_lower[0]; x < recv_upper[0]; ++x) {
262 // Convert global 3D index to local coordinates
263 int local_z = z - local_lower[2];
264 int local_y = y - local_lower[1];
265 int local_x = x - local_lower[0];
266
267 // For receive halo, these coordinates might be negative (if receiving
268 // into halo zone before the owned region) or >= local_size (if receiving
269 // into halo zone after the owned region).
270 //
271 // If field has halo padding: indices should account for halo offset
272 // If field doesn't have halo padding: caller must handle mapping
273 //
274 // For now, we calculate indices assuming the field layout matches
275 // the local domain bounds. Caller must adjust if using halo-padded layout.
276
277 // Row-major indexing: idx = z * (ny * nx) + y * nx + x
278 // This gives local index relative to local_lower
279 size_t local_idx =
280 static_cast<size_t>(local_z) * static_cast<size_t>(local_size[1]) *
281 static_cast<size_t>(local_size[0]) +
282 static_cast<size_t>(local_y) * static_cast<size_t>(local_size[0]) +
283 static_cast<size_t>(local_x);
284 indices.push_back(local_idx);
285 }
286 }
287 }
288
290}
291
304template <typename BackendTag = backend::CpuTag>
305std::map<Int3, std::pair<core::SparseVector<BackendTag, size_t>,
309 std::map<Int3, std::pair<core::SparseVector<BackendTag, size_t>,
311 patterns;
312
313 // Get neighbors based on connectivity
314 std::map<Int3, int> neighbors;
315 switch (connectivity) {
316 case Connectivity::Faces:
317 neighbors = decomposition::find_face_neighbors(decomp, rank);
318 break;
319 case Connectivity::All:
320 neighbors = decomposition::find_all_neighbors(decomp, rank);
321 break;
322 default: neighbors = decomposition::find_face_neighbors(decomp, rank); break;
323 }
324
325 // Create send/recv halos for each neighbor
326 for (const auto &[direction, neighbor_rank] : neighbors) {
327 if (neighbor_rank >= 0) {
328 auto send_halo =
330 auto recv_halo =
332 patterns.emplace(direction,
333 std::make_pair(std::move(send_halo), std::move(recv_halo)));
334 }
335 }
336
337 return patterns;
338}
339
340} // namespace halo
341} // namespace pfc
Backend tags for compile-time backend selection.
Sparse vector for indexed data views.
Definition sparse_vector.hpp:66
Core type definitions for World parameters.
std::array< int, 3 > Int3
Type aliases for clarity.
Definition types.hpp:45
Domain decomposition for parallel MPI simulations.
Neighbor finding utilities for Decomposition.
core::SparseVector< BackendTag, size_t > create_recv_halo(const decomposition::Decomposition &decomp, int rank, const Int3 &direction, int halo_width)
Create SparseVector representing receive halo region.
Definition halo_pattern.hpp:197
Connectivity
Connectivity pattern for halo exchange.
Definition halo_pattern.hpp:64
std::map< Int3, std::pair< core::SparseVector< BackendTag, size_t >, core::SparseVector< BackendTag, size_t > > > create_halo_patterns(const decomposition::Decomposition &decomp, int rank, Connectivity connectivity, int halo_width)
Create all halo patterns for a rank.
Definition halo_pattern.hpp:307
const auto & get_upper(const CartesianWorld &world) noexcept
Get the upper bounds of the world in a specific dimension.
Definition world_queries.hpp:168
const auto & get_lower(const CartesianWorld &world) noexcept
Get the lower bounds of the world.
Definition world_queries.hpp:149
Sparse vector for halo exchange and indexed data views.
Describes a static, pure partitioning of the global simulation domain into local subdomains.
Definition decomposition.hpp:182
Represents the global simulation domain (the "world").
Definition world.hpp:91
World class definition and unified interface.