NeoN
A framework for CFD software
Loading...
Searching...
No Matches
boundaryData.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2024 - 2026 NeoN authors
2//
3// SPDX-License-Identifier: MIT
4
5#pragma once
6
11
12#include <vector>
13#include <utility>
14#include <cstdio>
15#include <algorithm>
16
17#ifdef NF_WITH_MPI_SUPPORT
18#include <mpi.h>
19#include <optional>
23#endif
24
25namespace NeoN
26{
27
28
40template<typename ValueType>
42{
43
44public:
45
46 using BoundaryDataType = ValueType;
47
49 {
50 // commBuffers_ backs the memory of any in-flight MPI_Isend/MPI_Irecv calls.
51 // Destroying them while operations are pending is undefined behaviour, so
52 // drain all outstanding requests before the storage is freed.
53 waitAll();
54 }
55
61 : exec_(rhs.exec_), value_(rhs.value_), refValue_(rhs.refValue_),
62 valueFraction_(rhs.valueFraction_), refGrad_(rhs.refGrad_),
63 boundaryTypes_(rhs.boundaryTypes_), offset_(rhs.offset_), nBoundaries_(rhs.nBoundaries_),
64 nBoundaryFaces_(rhs.nBoundaryFaces_)
65 {}
66
67
73 : exec_(rhs.exec_), value_(exec, rhs.value_), refValue_(exec, rhs.refValue_),
74 valueFraction_(exec, rhs.valueFraction_), refGrad_(exec, rhs.refGrad_),
75 boundaryTypes_(exec, rhs.boundaryTypes_), offset_(SerialExecutor {}, rhs.offset_),
76 nBoundaries_(rhs.nBoundaries_), nBoundaryFaces_(rhs.nBoundaryFaces_)
77 {}
78
79
87 : exec_(exec), value_(exec, nBoundaryFaces, ValueType {}),
88 refValue_(exec, nBoundaryFaces, ValueType {}),
89 valueFraction_(exec, nBoundaryFaces, scalar(0)),
90 refGrad_(exec, nBoundaryFaces, ValueType {}), boundaryTypes_(exec, nBoundaryTypes),
91 offset_(SerialExecutor {}, nBoundaryTypes + 1), nBoundaries_(nBoundaryTypes),
92 nBoundaryFaces_(nBoundaryFaces)
93 {}
94
101 BoundaryData(const Executor& exec, const std::vector<localIdx>& offsets)
102 : BoundaryData(exec, offsets.back(), static_cast<localIdx>(offsets.size() - 1))
103 {
104 offset_ = Vector(SerialExecutor {}, offsets);
105 }
106
107
110 {
111 waitAll();
112 return value_;
113 }
114
122 {
123 waitAll();
124 return value_;
125 }
126
128 const Vector<ValueType>& refValue() const { return refValue_; }
129
134 Vector<ValueType>& refValue() { return refValue_; }
135
137 const Vector<scalar>& valueFraction() const { return valueFraction_; }
138
143 Vector<scalar>& valueFraction() { return valueFraction_; }
144
146 const Vector<ValueType>& refGrad() const { return refGrad_; }
147
152 Vector<ValueType>& refGrad() { return refGrad_; }
153
158 const Vector<int>& boundaryTypes() const { return boundaryTypes_; }
159
164 const Vector<localIdx>& offset() const { return offset_; }
165
170 localIdx nBoundaries() const { return nBoundaries_; }
171
176 localIdx nBoundaryFaces() const { return nBoundaryFaces_; }
177
183 {
184 return offset_.data()[patchId + 1] - offset_.data()[patchId];
185 }
186
187 const Executor& exec() { return exec_; }
188
190 {
191
192 // TODO maybe dont overwrite nBoundaries and nBoundaryFaces
193 // but use them for a sanity check
194 nBoundaries_ = rhs.nBoundaries_;
195 nBoundaryFaces_ = rhs.nBoundaryFaces_;
196
197 value_ = rhs.value_;
198 refValue_ = rhs.refValue_;
199 valueFraction_ = rhs.valueFraction_;
200 refGrad_ = rhs.refGrad_;
201 boundaryTypes_ = rhs.boundaryTypes_;
202 offset_ = rhs.offset_;
203 return *this;
204 }
205
207 {
208
209 // TODO maybe dont overwrite nBoundaries and nBoundaryFaces
210 // but use them for a sanity check
211 nBoundaries_ = rhs.nBoundaries_;
212 nBoundaryFaces_ = rhs.nBoundaryFaces_;
213
214 value_ = std::move(rhs.value_);
215 refValue_ = std::move(rhs.refValue_);
216 valueFraction_ = std::move(rhs.valueFraction_);
217 refGrad_ = std::move(rhs.refGrad_);
218 boundaryTypes_ = std::move(rhs.boundaryTypes_);
219 offset_ = std::move(rhs.offset_);
220 return *this;
221 }
222
223#ifdef NF_WITH_MPI_SUPPORT
224 void communicate(std::pair<localIdx, localIdx> range, int neighborRank)
225 {
226 const auto [rangeStart, rangeEnd] = range;
227 const localIdx patchSize = rangeEnd - rangeStart;
228
229 mpi::Environment mpiEnv;
230 CommBuffer buf;
231 buf.rangeStart = rangeStart;
232 buf.patchSize = patchSize;
233
234 const auto byteCount =
235 static_cast<mpi_label_t>(patchSize) * static_cast<mpi_label_t>(sizeof(ValueType));
236 const auto neighborRankLabel = static_cast<mpi_label_t>(neighborRank);
237
238 // Deterministic, symmetric tag for the processor patch shared by (myRank, neighborRank).
239 // A unique tag per unordered rank pair, identical on both sides, makes each isend/irecv
240 // match its true partner regardless of posting order. min*P+max is symmetric so both
241 // ranks of the pair compute the same tag.
242 const auto nProcs = static_cast<mpi_label_t>(mpiEnv.sizeRank());
243 const auto myRankLabel = static_cast<mpi_label_t>(mpiEnv.rank());
244 const mpi_label_t pairTag = std::min(myRankLabel, neighborRankLabel) * nProcs
245 + std::max(myRankLabel, neighborRankLabel);
246
247 const bool useGpuPath = mpiEnv.gpuAwareMpi() && std::holds_alternative<GPUExecutor>(exec_);
248
249 MPI_Request sendReq, recvReq;
250 if (useGpuPath)
251 {
252 buf.deviceRecvBuf = Vector<ValueType>(exec_, patchSize, ValueType {});
253 mpi::isend<char>(
254 reinterpret_cast<const char*>(value_.data() + rangeStart),
255 byteCount,
256 neighborRankLabel,
257 pairTag,
258 mpiEnv.comm(),
259 &sendReq
260 );
261 mpi::irecv<char>(
262 reinterpret_cast<char*>(buf.deviceRecvBuf->data()),
263 byteCount,
264 neighborRankLabel,
265 pairTag,
266 mpiEnv.comm(),
267 &recvReq
268 );
269 }
270 else
271 {
272 auto valH = value_.copyToHost();
273 buf.sendBuf.resize(static_cast<std::size_t>(patchSize));
274 buf.recvBuf.resize(static_cast<std::size_t>(patchSize));
275 for (localIdx k = 0; k < patchSize; k++)
276 buf.sendBuf[static_cast<std::size_t>(k)] = valH.view()[rangeStart + k];
277 mpi::isend<char>(
278 reinterpret_cast<const char*>(buf.sendBuf.data()),
279 byteCount,
280 neighborRankLabel,
281 pairTag,
282 mpiEnv.comm(),
283 &sendReq
284 );
285 mpi::irecv<char>(
286 reinterpret_cast<char*>(buf.recvBuf.data()),
287 byteCount,
288 neighborRankLabel,
289 pairTag,
290 mpiEnv.comm(),
291 &recvReq
292 );
293 }
294 communicating_ = true;
295 requests_.push_back(sendReq);
296 requests_.push_back(recvReq);
297 commBuffers_.push_back(std::move(buf));
298 }
299
300 bool isComplete() const
301 {
302 if (requests_.empty() || !communicating_) return true;
303 for (auto& req : requests_)
304 {
305 if (!mpi::test(&req)) return false;
306 }
307 communicating_ = false;
308 return true;
309 }
310
311
312#endif
313
314 void waitAll() const
315 {
316#ifdef NF_WITH_MPI_SUPPORT
317 if (requests_.empty() || !communicating_) return;
318 while (!isComplete())
319 {
320 }
321 mpi::Environment mpiEnv;
322 const bool useGpuPath = mpiEnv.gpuAwareMpi() && std::holds_alternative<GPUExecutor>(exec_);
323 if (useGpuPath)
324 {
325 for (const auto& buf : commBuffers_)
326 {
327 auto srcView = buf.deviceRecvBuf->view();
328 auto dstView = value_.view();
329 const localIdx start = buf.rangeStart;
331 exec_,
332 {0, buf.patchSize},
333 KOKKOS_LAMBDA(const localIdx k) { dstView[start + k] = srcView[k]; }
334 );
335 }
336 }
337 else if (std::holds_alternative<GPUExecutor>(exec_))
338 {
339 // GPU executor but not GPU-aware MPI: data was staged through host buffers.
340 // Copy received host data back to the device in-place (via copy-assign, not
341 // move-assign) so that value_.data() stays the same pointer and any View
342 // obtained from value() before the exchange remains valid.
343 auto valH = value_.copyToHost();
344 for (const auto& buf : commBuffers_)
345 {
346 for (localIdx k = 0; k < buf.patchSize; k++)
347 valH.view()[buf.rangeStart + k] = buf.recvBuf[static_cast<std::size_t>(k)];
348 }
349 Vector<ValueType> backToDevice = valH.copyToExecutor(exec_);
350 value_ = backToDevice; // operator=(const Vector&): setContainer dispatches async kernel
351 // Fence before backToDevice goes out of scope: the setContainer kernel reads from
352 // backToDevice.data_; freeing it while the kernel runs is a GPU page fault.
353 fence(exec_);
354 }
355 else
356 {
357 // SerialExecutor or CPUExecutor: value_ lives in host-accessible memory;
358 // write received data directly without any intermediate allocation.
359 for (const auto& buf : commBuffers_)
360 {
361 ValueType* dst = value_.data() + buf.rangeStart;
362 for (localIdx k = 0; k < buf.patchSize; k++)
363 dst[k] = buf.recvBuf[static_cast<std::size_t>(k)];
364 }
365 }
366 requests_.clear();
367 communicating_ = false;
368 commBuffers_.clear();
369#endif
370 }
371
376 std::pair<localIdx, localIdx> range(localIdx patchId) const
377 {
378 return {offset_.data()[patchId], offset_.data()[patchId + 1]};
379 }
380
381private:
382
384 friend struct NoWaitAccess;
385
399 Vector<ValueType>& valueNoWait() { return value_; }
400
401 Executor exec_;
402 mutable Vector<ValueType> value_;
404 Vector<ValueType> refValue_;
406 valueFraction_;
407 Vector<ValueType> refGrad_;
408 Vector<int> boundaryTypes_;
409 Vector<localIdx> offset_;
410 localIdx nBoundaries_;
411 localIdx nBoundaryFaces_;
412
413#ifdef NF_WITH_MPI_SUPPORT
414 struct CommBuffer
415 {
416 std::vector<ValueType> sendBuf; // host staging, used when !gpuAwareMpi
417 std::vector<ValueType> recvBuf; // host staging, used when !gpuAwareMpi
418 std::optional<Vector<ValueType>> deviceRecvBuf; // device buffer, used when gpuAwareMpi
419 localIdx rangeStart;
420 localIdx patchSize;
421 };
422 mutable std::vector<MPI_Request>
423 requests_;
424 mutable std::vector<CommBuffer>
425 commBuffers_;
426 mutable bool communicating_ = false;
427#endif
428};
429
439{
440 template<typename ValueType>
442 {
443 return in.valueNoWait();
444 }
445};
446
447}
Represents the boundary fields for a computational domain.
std::pair< localIdx, localIdx > range(localIdx patchId) const
Get the range for a given patchId.
Vector< ValueType > & refValue()
Get the view storing the Dirichlet boundary values.
ValueType BoundaryDataType
const Vector< ValueType > & refValue() const
Get the view storing the Dirichlet boundary values.
const Vector< ValueType > & value() const
Get the view storing the computed values from the boundary condition.
const Vector< int > & boundaryTypes() const
Get the view storing the boundary types.
localIdx nBoundaries() const
Get the number of boundaries.
Vector< scalar > & valueFraction()
Get the view storing the fraction of the boundary value.
localIdx nBoundaryFaces(localIdx patchId) const
Get the number of boundary faces for this patch.
BoundaryData< ValueType > & operator=(const BoundaryData< ValueType > &&rhs)
BoundaryData(const Executor &exec, localIdx nBoundaryFaces, localIdx nBoundaryTypes)
constructor with default initialized Vectors from sizes.
BoundaryData(const Executor &exec, const BoundaryData< ValueType > &rhs)
Copy constructor.
const Vector< localIdx > & offset() const
Get the view storing the offsets of each boundary.
localIdx nBoundaryFaces() const
Get the number of boundary faces.
const Executor & exec()
const Vector< scalar > & valueFraction() const
Get the view storing the fraction of the boundary value.
BoundaryData(const Executor &exec, const std::vector< localIdx > &offsets)
constructor from a given offsets vector @warn all members except offsets are default constructed
Vector< ValueType > & value()
Get the view storing the computed values from the boundary condition.
const Vector< ValueType > & refGrad() const
Get the view storing the Neumann boundary values.
Vector< ValueType > & refGrad()
Get the view storing the Neumann boundary values.
BoundaryData(const BoundaryData< ValueType > &rhs)
Copy constructor.
BoundaryData< ValueType > & operator=(const BoundaryData< ValueType > &rhs)
Reference executor for serial CPU execution.
A class to contain the data and executors for a field and define some basic operations.
Definition vector.hpp:27
Vector< ValueType > copyToExecutor(Executor dstExec) const
Copies the data to a new field on a specific executor.
ValueType * data()
Direct access to the underlying field data.
Definition vector.hpp:226
Integer types used throughout NeoN.
Definition array.hpp:18
void fence(const Executor &exec)
Definition executor.hpp:23
int32_t localIdx
Definition label.hpp:50
std::variant< SerialExecutor, CPUExecutor, GPUExecutor > Executor
Definition executor.hpp:20
float scalar
Definition scalar.hpp:17
void parallelFor(const ExecutorType &, std::pair< localIdx, localIdx > range, const Kernel &kernel, std::string name)
int mpi_label_t
Definition label.hpp:57
Passkey granting non-draining access to BoundaryData's value storage.
static Vector< ValueType > & value(BoundaryData< ValueType > &in)