MFEM v4.10.0
Finite element discretization library
Loading...
Searching...
No Matches
particleset.cpp
Go to the documentation of this file.
1// Copyright (c) 2010-2026, Lawrence Livermore National Security, LLC. Produced
2// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3// LICENSE and NOTICE for details. LLNL-CODE-806117.
4//
5// This file is part of the MFEM library. For more information and source code
6// availability visit https://mfem.org.
7//
8// MFEM is free software; you can redistribute it and/or modify it under the
9// terms of the BSD-3 license. We welcome feedback and contributions, see file
10// CONTRIBUTING.md for details.
11
12#include "particleset.hpp"
13#include "../general/forall.hpp"
14
15#if defined(MFEM_USE_MPI) && defined(MFEM_USE_GSLIB)
16
17// Ignore warnings from the gslib header (GCC version)
18#ifdef MFEM_HAVE_GCC_PRAGMA_DIAGNOSTIC
19#pragma GCC diagnostic push
20#pragma GCC diagnostic ignored "-Wunused-function"
21#endif
22
23namespace gslib
24{
25extern "C"
26{
27#include <gslib.h>
28} // extern C
29} // namespace gslib
30
31#ifdef MFEM_HAVE_GCC_PRAGMA_DIAGNOSTIC
32#pragma GCC diagnostic pop
33#endif
34
35#endif // MFEM_USE_MPI && MFEM_USE_GSLIB
36
37
38namespace mfem
39{
40
41Particle::Particle(int dim, const Array<int> &field_vdims, int num_tags)
42 : coords(dim), fields(), tags()
43{
44 coords = 0.0;
45
46 fields.reserve(field_vdims.Size());
47 for (int f = 0; f < field_vdims.Size(); f++)
48 {
49 fields.emplace_back(field_vdims[f]);
50 fields.back() = 0.0;
51 }
52
53 tags.reserve(num_tags);
54 for (int t = 0; t < num_tags; t++)
55 {
56 tags.emplace_back(1);
57 tags.back()[0] = 0;
58 }
59}
60
61void Particle::SetTagRef(int t, int *tag_data)
62{
63 MFEM_ASSERT(t >= 0 &&
64 static_cast<size_t>(t) < tags.size(), "Invalid tag index");
65 tags[t].MakeRef(tag_data, 1);
66}
67
68void Particle::SetFieldRef(int f, real_t *field_data)
69{
70 MFEM_ASSERT(f >= 0 &&
71 static_cast<size_t>(f) < fields.size(), "Invalid field "
72 "index");
73 Vector temp(field_data, fields[f].Size());
74 fields[f].MakeRef(temp, 0, fields[f].Size());
75}
76
77bool Particle::operator==(const Particle &rhs) const
78{
79 // Compare coordinate size and values
80 if (coords.Size() != rhs.coords.Size())
81 {
82 return false;
83 }
84 for (int d = 0; d < coords.Size(); d++)
85 {
86 if (coords[d] != rhs.coords[d])
87 {
88 return false;
89 }
90 }
91 // Compare fields vdim and values
92 if (fields.size() != rhs.fields.size())
93 {
94 return false;
95 }
96 for (size_t f = 0; f < fields.size(); f++)
97 {
98 if (fields[f].Size() != rhs.fields[f].Size())
99 {
100 return false;
101 }
102 for (int c = 0; c < fields[f].Size(); c++)
103 {
104 if (fields[f][c] != rhs.fields[f][c])
105 {
106 return false;
107 }
108 }
109 }
110 // Compare tags size and values
111 if (tags.size() != rhs.tags.size())
112 {
113 return false;
114 }
115 for (size_t t = 0; t < tags.size(); t++)
116 {
117 if (tags[t][0] != rhs.tags[t][0])
118 {
119 return false;
120 }
121 }
122
123 return true;
124}
125
126void Particle::Print(std::ostream &os) const
127{
128 os << "Coords: (";
129 for (int d = 0; d < coords.Size(); d++)
130 {
131 os << coords[d] << ( (d+1 < coords.Size()) ? "," : ")\n");
132 }
133 for (size_t f = 0; f < fields.size(); f++)
134 {
135 os << "Field " << f << ": (";
136 for (int c = 0; c < fields[f].Size(); c++)
137 {
138 os << fields[f][c] << ( (c+1 < fields[f].Size()) ? "," : ")\n");
139 }
140 }
141 for (size_t t = 0; t < tags.size(); t++)
142 {
143 os << "Tag " << t << ": " << tags[t][0] << "\n";
144 }
145}
146
147Array<Ordering::Type> ParticleSet::GetOrderingArray(Ordering::Type o, int N)
148{
149 Array<Ordering::Type> ordering_arr(N);
150 ordering_arr = o;
151 return ordering_arr;
152}
153std::string ParticleSet::GetDefaultFieldName(int i)
154{
155 return "Field_" + std::to_string(i);
156}
157
158std::string ParticleSet::GetDefaultTagName(int i)
159{
160 return "Tag_" + std::to_string(i);
161}
162
163Array<const char*> ParticleSet::GetEmptyNameArray(int N)
164{
165 Array<const char*> names(N);
166 names = nullptr;
167 return names;
168}
169
170#ifdef MFEM_USE_MPI
171int ParticleSet::GetRank(MPI_Comm comm_)
172{
173 int r; MPI_Comm_rank(comm_, &r);
174 return r;
175}
176int ParticleSet::GetSize(MPI_Comm comm_)
177{
178 int s; MPI_Comm_size(comm_, &s);
179 return s;
180}
181#endif // MFEM_USE_MPI
182
184{
185 ids.Reserve(res);
186
187 // Reserve fields
188 for (int f = -1; f < GetNFields(); f++)
189 {
190 ParticleVector &pv = (f == -1 ? coords : *fields[f]);
191 pv.Reserve(res*pv.GetVDim());
192 }
193
194 // Reserve tags
195 for (int t = 0; t < GetNTags(); t++)
196 {
197 tags[t]->Reserve(res);
198 }
199
200}
201
203{
204 Array<int> field_vdims(GetNFields());
205 for (int f = 0; f < GetNFields(); f++)
206 {
207 field_vdims[f] = Field(f).GetVDim();
208 }
209 return field_vdims;
210}
211
213 Array<int> *new_indices)
214{
215 int num_add = new_ids.Size();
216 int old_np = GetNParticles();
217 int new_np = old_np + num_add;
218
219 // Set indices of new particles
220 if (new_indices)
221 {
222 new_indices->SetSize(num_add);
223 for (int i = 0; i < num_add; i++)
224 {
225 (*new_indices)[i] = ids.Size() + i;
226 }
227 }
228 // Add new ids
230 ids.Append(new_ids);
231
232 // Update data
233 for (int f = -1; f < GetNFields(); f++)
234 {
235 ParticleVector &pv = (f == -1 ? coords : *fields[f]);
236 pv.SetNumParticles(new_np); // does not delete existing data
237 }
238
239 // Update tags
240 for (int t = 0; t < GetNTags(); t++)
241 {
242 tags[t]->SetSize(new_np);
243 }
244}
245
246#if defined(MFEM_USE_MPI) && defined(MFEM_USE_GSLIB)
247
248/// \cond DO_NOT_DOCUMENT
249// Static helper: gather selected particle-vector entries into a compact buffer.
250// nvcc does not allow extended host/device lambdas in non-public members.
251static void GatherParticleVectorDevice(const ParticleVector &pv,
252 const Array<int> &send_idxs,
253 Vector &send_data,
254 int nsend)
255{
256 const int vdim = pv.GetVDim();
257 const int ordering = pv.GetOrdering();
258 const int num_particles = pv.GetNumParticles();
259 const MemoryClass device_mc = Device::GetDeviceMemoryClass();
260 send_data.SetSize(nsend*vdim);
261 real_t *d_send_data =
262 send_data.GetMemory().Write(device_mc, send_data.Size());
263 const real_t *d_src = pv.GetMemory().Read(device_mc, pv.Size());
264 const int *d_send_idxs = send_idxs.GetMemory().Read(device_mc, nsend);
265
266 mfem::forall(nsend, [=] MFEM_HOST_DEVICE (int i)
267 {
268 const int p = d_send_idxs[i];
269 const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
270 const int stride = (ordering == Ordering::byVDIM) ? 1 : num_particles;
271
272 for (int c = 0; c < vdim; c++)
273 {
274 d_send_data[i*vdim + c] = d_src[offset + c*stride];
275 }
276 });
277}
278
279// Static helper: gather selected tag values into a compact buffer.
280// nvcc does not allow extended host/device lambdas in non-public members.
281static void GatherParticleTagsDevice(const Array<int> &tag,
282 const Array<int> &send_idxs,
283 Array<int> &send_tag,
284 int nsend)
285{
286 const MemoryClass device_mc = Device::GetDeviceMemoryClass();
287 send_tag.SetSize(nsend);
288 int *d_send_tag = send_tag.GetMemory().Write(device_mc, nsend);
289 const int *d_tag = tag.GetMemory().Read(device_mc, tag.Size());
290 const int *d_send_idxs = send_idxs.GetMemory().Read(device_mc, nsend);
291
292 mfem::forall(nsend, [=] MFEM_HOST_DEVICE (int i)
293 {
294 d_send_tag[i] = d_tag[d_send_idxs[i]];
295 });
296}
297
298// Static helper: scatter compact particle-vector entries to particle storage.
299// nvcc does not allow extended host/device lambdas in non-public members.
300static void ScatterParticleVectorDevice(ParticleVector &pv,
301 const Vector &recv_data,
302 const Array<int> &recv_locs,
303 int nrecv)
304{
305 const int vdim = pv.GetVDim();
306 const int ordering = pv.GetOrdering();
307 const int num_particles = pv.GetNumParticles();
308 const MemoryClass device_mc = Device::GetDeviceMemoryClass();
309 const real_t *d_recv_data =
310 recv_data.GetMemory().Read(device_mc, recv_data.Size());
311 const int *d_recv_locs = recv_locs.GetMemory().Read(device_mc, nrecv);
312 real_t *d_dst = pv.GetMemory().ReadWrite(device_mc, pv.Size());
313
314 mfem::forall(nrecv, [=] MFEM_HOST_DEVICE (int i)
315 {
316 const int p = d_recv_locs[i];
317 const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
318 const int stride = (ordering == Ordering::byVDIM) ? 1 : num_particles;
319
320 for (int c = 0; c < vdim; c++)
321 {
322 d_dst[offset + c*stride] = d_recv_data[i*vdim + c];
323 }
324 });
325}
326
327// Static helper: scatter compact tag values to particle storage.
328// nvcc does not allow extended host/device lambdas in non-public members.
329static void ScatterParticleTagsDevice(Array<int> &tag,
330 const Array<int> &recv_tag,
331 const Array<int> &recv_locs,
332 int nrecv)
333{
334 const MemoryClass device_mc = Device::GetDeviceMemoryClass();
335 const int *d_recv_tag = recv_tag.GetMemory().Read(device_mc, nrecv);
336 const int *d_recv_locs = recv_locs.GetMemory().Read(device_mc, nrecv);
337 int *d_tag = tag.GetMemory().ReadWrite(device_mc, tag.Size());
338
339 mfem::forall(nrecv, [=] MFEM_HOST_DEVICE (int i)
340 {
341 d_tag[d_recv_locs[i]] = d_recv_tag[i];
342 });
343}
344
345template<size_t NBytes>
346void ParticleSet::TransferParticlesImpl(ParticleSet &pset,
347 const Array<int> &send_idxs,
348 const Array<unsigned int> &send_ranks)
349{
350 struct pdata_t
351 {
352 alignas(real_t) std::array<std::byte, NBytes> data;
353 IDType id;
354 };
355
356 int nreals = pset.GetFieldVDims().Sum() + pset.Coords().GetVDim();
357 int ntags = pset.GetNTags();
358 size_t nbytes = nreals*sizeof(real_t) + ntags*sizeof(int);
359 MFEM_VERIFY(nbytes <= NBytes, "More data than can be packed.");
360
361 using parr_t = pdata_t;
362 gslib::array gsl_arr;
363 parr_t *pdata_arr;
364 array_init(parr_t, &gsl_arr, send_idxs.Size());
365 pdata_arr = (parr_t*) gsl_arr.ptr;
366
367 int nparticles = pset.GetNParticles();
368 int nsend = send_idxs.Size();
369 gsl_arr.n = send_idxs.Size();
370
371 const int *h_send_idxs_initial = send_idxs.HostRead();
372 const IDType *h_ids = pset.GetIDs().HostRead();
373 for (int i = 0; i < send_idxs.Size(); i++)
374 {
375 parr_t &pdata = pdata_arr[i];
376 pdata.id = h_ids[h_send_idxs_initial[i]];
377 }
378
379 // Pack coords and fields into the GSLIB send buffer. Device-resident data
380 // is first gathered into a compact device buffer so that only selected
381 // particles are copied back to host. Host-resident data is packed directly.
382 int max_vdim = pset.Coords().GetVDim();
383 for (int f = 0; f < pset.GetNFields(); f++)
384 {
385 int f_vdim = pset.Field(f).GetVDim();
386 if (f_vdim > max_vdim) { max_vdim = f_vdim; }
387 }
388 Vector send_data;
389 Array<int> send_tag;
390 if (Device::IsEnabled())
391 {
392 send_data.SetSize(nsend * max_vdim); // allocate max size over all fields
393 send_tag.SetSize(nsend);
394 }
395
396 size_t counter = 0;
397 for (int f = -1; f < pset.GetNFields(); f++)
398 {
399 const ParticleVector &pv = f == -1 ? pset.Coords() : pset.Field(f);
400 const int vdim = pv.GetVDim();
401 const int ordering = pv.GetOrdering();
402 const int num_particles = pv.GetNumParticles();
403 const bool use_dev = Device::IsEnabled() && pv.UseDevice();
404
405 if (use_dev)
406 {
407 GatherParticleVectorDevice(pv, send_idxs, send_data, nsend);
408
409 const real_t *h_send_data = send_data.HostRead();
410 for (int i = 0; i < nsend; i++)
411 {
412 std::memcpy(pdata_arr[i].data.data() + counter,
413 h_send_data + i*vdim, vdim * sizeof(real_t));
414 }
415 }
416 else
417 {
418 const real_t *h_src = pv.HostRead();
419 const int *h_send_idxs = send_idxs.HostRead();
420 for (int i = 0; i < nsend; i++)
421 {
422 parr_t &pdata = pdata_arr[i];
423 const int p = h_send_idxs[i];
424 const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
425 const int stride = (ordering == Ordering::byVDIM) ? 1 :
426 num_particles;
427
428 for (int c = 0; c < vdim; c++)
429 {
430 std::memcpy(pdata.data.data() + counter + c*sizeof(real_t),
431 h_src + offset + c*stride, sizeof(real_t));
432 }
433 }
434 }
435
436 counter += vdim*sizeof(real_t);
437 }
438
439 // Pack tags after all real_t data. Each tag uses the same selective
440 // device gather path when its Array is device-resident.
441 for (int t = 0; t < pset.GetNTags(); t++)
442 {
443 const Array<int> &tag = pset.Tag(t);
444 const size_t tag_counter = counter + t*sizeof(int);
445 const bool use_dev = Device::IsEnabled() && tag.UseDevice();
446
447 if (use_dev)
448 {
449 GatherParticleTagsDevice(tag, send_idxs, send_tag, nsend);
450
451 const int *h_send_tag = send_tag.HostRead();
452 for (int i = 0; i < nsend; i++)
453 {
454 std::memcpy(pdata_arr[i].data.data() + tag_counter,
455 h_send_tag + i, sizeof(int));
456 }
457 }
458 else
459 {
460 const int *h_tag = tag.HostRead();
461 const int *h_send_idxs = send_idxs.HostRead();
462 for (int i = 0; i < nsend; i++)
463 {
464 std::memcpy(pdata_arr[i].data.data() + tag_counter,
465 h_tag + h_send_idxs[i], sizeof(int));
466 }
467 }
468 }
469
470 // Transfer particles
471 sarray_transfer_ext(parr_t, &gsl_arr, send_ranks.GetData(),
472 sizeof(unsigned int), pset.cr);
473
474 // Make sure we have enough space for received particles
475 int nrecv = (int) gsl_arr.n;
476
477 Vector recv_data;
478 Array<int> recv_tag;
479 if (Device::IsEnabled())
480 {
481 recv_data.SetSize(nrecv * max_vdim);
482 recv_tag.SetSize(nrecv);
483 }
484
485 int ndelete = nsend - nrecv;
486 if (ndelete > 0)
487 {
488 // Remove unneeded particles
489 auto datap = const_cast<int*>(send_idxs.HostRead());
490 Array<int> delete_idxs(datap + nrecv, ndelete);
491 pset.RemoveParticles(delete_idxs);
492 }
493 else
494 {
495 pset.Reserve(nparticles-ndelete);
496 }
497
498 pdata_arr = (parr_t*) gsl_arr.ptr;
499
500 // Make a list of new IDs to add
501 int num_new = nrecv > nsend ? nrecv - nsend : 0;
502 Array<IDType> new_ids(num_new);
503 for (int i = 0; i < num_new; i++)
504 {
505 new_ids[i] = pdata_arr[nsend + i].id;
506 }
507
508 // Add particles in batch
509 Array<int> new_indices;
510 if (num_new > 0)
511 {
512 pset.AddParticles(new_ids, &new_indices);
513 }
514
515 // Map each received packet to the local particle slot it updates.
516 Array<int> recv_locs(nrecv);
517 int *h_recv_locs = recv_locs.HostWrite();
518 const int *h_send_idxs_recv = send_idxs.HostRead();
519 for (int i = 0; i < nrecv; i++)
520 {
521 parr_t &pdata = pdata_arr[i];
522 if (i < nsend) // update existing particle
523 {
524 h_recv_locs[i] = h_send_idxs_recv[i];
525 pset.UpdateID(h_recv_locs[i], pdata.id);
526 }
527 else
528 {
529 h_recv_locs[i] = new_indices[i - nsend];
530 }
531 }
532
533 // Unpack coords and fields from GSLIB host packets. Device-resident
534 // destinations use a compact host buffer followed by a device scatter.
535 size_t recv_counter = 0;
536 for (int f = -1; f < pset.GetNFields(); f++)
537 {
538 ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
539 const int vdim = pv.GetVDim();
540 const int ordering = pv.GetOrdering();
541 const int num_particles = pv.GetNumParticles();
542 const bool use_dev = Device::IsEnabled() && pv.UseDevice();
543
544 if (use_dev)
545 {
546 recv_data.SetSize(nrecv*vdim);
547 real_t *h_recv_data = recv_data.HostWrite();
548
549 for (int i = 0; i < nrecv; i++)
550 {
551 std::memcpy(h_recv_data + i*vdim,
552 pdata_arr[i].data.data() + recv_counter,
553 vdim*sizeof(real_t));
554 }
555
556 ScatterParticleVectorDevice(pv, recv_data, recv_locs, nrecv);
557 }
558 else
559 {
560 real_t *h_dst = pv.HostReadWrite();
561 const int *h_recv_locs_read = recv_locs.HostRead();
562 for (int i = 0; i < nrecv; i++)
563 {
564 parr_t &pdata = pdata_arr[i];
565 const int p = h_recv_locs_read[i];
566 const int offset = (ordering == Ordering::byVDIM) ? p * vdim : p;
567 const int stride = (ordering == Ordering::byVDIM) ? 1 :
568 num_particles;
569
570 for (int c = 0; c < vdim; c++)
571 {
572 std::memcpy(h_dst + offset + c*stride,
573 pdata.data.data() + recv_counter + c*sizeof(real_t),
574 sizeof(real_t));
575 }
576 }
577 }
578
579 recv_counter += vdim*sizeof(real_t);
580 }
581
582 // Unpack tags after all real_t data, using the same compact scatter path
583 // for device-resident tag arrays.
584 for (int t = 0; t < pset.GetNTags(); t++)
585 {
586 Array<int> &tag = pset.Tag(t);
587 const size_t tag_counter = recv_counter + t*sizeof(int);
588 const bool use_dev = Device::IsEnabled() && tag.UseDevice();
589
590 if (use_dev)
591 {
592 recv_tag.SetSize(nrecv);
593 int *h_recv_tag = recv_tag.HostWrite();
594
595 for (int i = 0; i < nrecv; i++)
596 {
597 std::memcpy(h_recv_tag + i,
598 pdata_arr[i].data.data() + tag_counter, sizeof(int));
599 }
600
601 ScatterParticleTagsDevice(tag, recv_tag, recv_locs, nrecv);
602 }
603 else
604 {
605 int *h_tag = tag.HostReadWrite();
606 const int *h_recv_locs_read = recv_locs.HostRead();
607 for (int i = 0; i < nrecv; i++)
608 {
609 std::memcpy(h_tag + h_recv_locs_read[i],
610 pdata_arr[i].data.data() + tag_counter, sizeof(int));
611 }
612 }
613 }
614 array_free(&gsl_arr);
615
616 // Restore Device validity if needed
617 for (int f = -1; f < pset.GetNFields(); f++)
618 {
619 ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
620 pv.ReadWrite(pv.UseDevice());
621 }
622 for (int t = 0; t < pset.GetNTags(); t++)
623 {
624 Array<int> &tag_arr = pset.Tag(t);
625 if (tag_arr.UseDevice()) { tag_arr.ReadWrite(true); }
626 }
627}
628
629template<size_t NBytes>
630ParticleSet::TransferParticlesType ParticleSet::TransferParticles::Kernel()
631{
632 return &ParticleSet::TransferParticlesImpl<NBytes>;
633}
634
635ParticleSet::Kernels::Kernels()
636{
637 constexpr size_t sizd = sizeof(real_t);
638 TransferParticles::Specialization<2*sizd>::Add();
639 TransferParticles::Specialization<3*sizd>::Add();
640 TransferParticles::Specialization<4*sizd>::Add();
641 TransferParticles::Specialization<8*sizd>::Add();
642 TransferParticles::Specialization<12*sizd>::Add();
643 TransferParticles::Specialization<16*sizd>::Add();
644 TransferParticles::Specialization<20*sizd>::Add();
645 TransferParticles::Specialization<24*sizd>::Add();
646 TransferParticles::Specialization<28*sizd>::Add();
647 TransferParticles::Specialization<32*sizd>::Add();
648 TransferParticles::Specialization<36*sizd>::Add();
649 TransferParticles::Specialization<40*sizd>::Add();
650}
651
652auto ParticleSet::TransferParticles::Fallback(size_t bufsize)
653-> ParticleSet::TransferParticlesType
654{
655 constexpr size_t sizd = sizeof(real_t);
656 if (bufsize < 4*sizd)
657 {
658 return &ParticleSet::TransferParticlesImpl<4*sizd>;
659 }
660 else if (bufsize < 8*sizd)
661 {
662 return &ParticleSet::TransferParticlesImpl<8*sizd>;
663 }
664 else if (bufsize < 12*sizd)
665 {
666 return &ParticleSet::TransferParticlesImpl<12*sizd>;
667 }
668 else if (bufsize < 16*sizd)
669 {
670 return &ParticleSet::TransferParticlesImpl<16*sizd>;
671 }
672 else if (bufsize < 20*sizd)
673 {
674 return &ParticleSet::TransferParticlesImpl<20*sizd>;
675 }
676 else if (bufsize < 24*sizd)
677 {
678 return &ParticleSet::TransferParticlesImpl<24*sizd>;
679 }
680 else if (bufsize < 28*sizd)
681 {
682 return &ParticleSet::TransferParticlesImpl<28*sizd>;
683 }
684 else if (bufsize < 32*sizd)
685 {
686 return &ParticleSet::TransferParticlesImpl<32*sizd>;
687 }
688 else if (bufsize < 36*sizd)
689 {
690 return &ParticleSet::TransferParticlesImpl<36*sizd>;
691 }
692 else if (bufsize < 40*sizd)
693 {
694 return &ParticleSet::TransferParticlesImpl<40*sizd>;
695 }
696 return &ParticleSet::TransferParticlesImpl<60*sizd>;
697}
698/// \endcond DO_NOT_DOCUMENT
699
701{
702 MFEM_ASSERT(rank_list.Size() == GetNParticles(),
703 "rank_list must be of size GetNParticles().");
704
705 int rank = GetRank(comm);
706
707 // Get particles to be transferred
708 // (Avoid unnecessary copies of particle data into and out of buffers)
709 Array<int> send_idxs;
710 Array<unsigned int> send_ranks;
711 send_idxs.Reserve(rank_list.Size());
712 send_ranks.Reserve(rank_list.Size());
713 for (int i = 0; i < rank_list.Size(); i++)
714 {
715 if (rank != static_cast<int>(rank_list[i]))
716 {
717 send_idxs.Append(i);
718 send_ranks.Append(rank_list[i]);
719 }
720 }
721
722 // Compute number of bytes of a single particle
723 int nreals = GetFieldVDims().Sum() + coords.GetVDim();
724 int ntags = GetNTags();
725 size_t nbytes = nreals*sizeof(real_t) + ntags*sizeof(int);
726
727 // Dispatch to appropriate redistribution function for this size
728 TransferParticles::Run(nbytes, *this, send_idxs, send_ranks);
729}
730
731#endif // MFEM_USE_MPI && MFEM_USE_GSLIB
732
737
738void ParticleSet::WriteToFile(const char *fname,
739 const std::stringstream &ss_header, const std::stringstream &ss_data)
740{
741
742#ifdef MFEM_USE_MPI
743 // Parallel:
744 int rank = GetRank(comm);
745
746 MPI_File_delete(fname, MPI_INFO_NULL); // delete old file if it exists
747 MPI_File file;
748 int mpi_err = MPI_File_open(comm, fname, MPI_MODE_CREATE | MPI_MODE_WRONLY,
749 MPI_INFO_NULL, &file);
750 MFEM_VERIFY(mpi_err == MPI_SUCCESS, "MPI_File_open failed.");
751
752 // Print header
753 if (rank == 0)
754 {
755 MPI_File_write_at(file, 0, ss_header.str().data(), ss_header.str().size(),
756 MPI_CHAR, MPI_STATUS_IGNORE);
757 }
758
759 // Compute the data size in bytes
760 MPI_Offset data_size = ss_data.str().size();
761 MPI_Offset offset;
762
763 // Compute the offsets using an exclusive scan
764 MPI_Exscan(&data_size, &offset, 1, MPI_OFFSET, MPI_SUM, comm);
765 if (rank == 0)
766 {
767 offset = 0;
768 }
769
770 // Add offset from the header
771 offset += ss_header.str().size();
772
773 // Write data collectively
774 MPI_File_write_at_all(file, offset, ss_data.str().data(),
775 data_size, MPI_BYTE, MPI_STATUS_IGNORE);
776
777 // Close file
778 MPI_File_close(&file);
779#else
780 // Serial:
781 std::ofstream ofs(fname);
782 MFEM_VERIFY(ofs.is_open() && !ofs.fail(),
783 "Error: Could not open file " << fname << " for writing.");
784 ofs << ss_header.str() << ss_data.str();
785 ofs.close();
786#endif // MFEM_USE_MPI
787}
788
789ParticleSet::ParticleSet(int id_stride_, IDType id_counter_, int num_particles,
790 int dim, Ordering::Type coords_ordering, const Array<int> &field_vdims,
791 const Array<Ordering::Type> &field_orderings,
792 const Array<const char*> &field_names_, int num_tags,
793 const Array<const char*> &tag_names_,
794 bool use_device)
795 : id_stride(id_stride_),
796 id_counter(id_counter_),
797 coords(dim, coords_ordering)
798{
799 if (use_device) { coords.UseDevice(true); }
800
801 // Initialize fields
802 for (int f = 0; f < field_vdims.Size(); f++)
803 {
804 AddField(field_vdims[f], field_orderings[f], field_names_[f]);
805 }
806
807 // Initialize tags
808 for (int t = 0; t < num_tags; t++)
809 {
810 AddTag(tag_names_[t]);
811 }
812
813 // Add num_particles
814 Array<IDType> init_ids(num_particles);
815 for (int i = 0; i < num_particles; i++)
816 {
817 init_ids[i] = id_counter;
819 }
820 AddParticles(init_ids);
821}
822
824{
825 if (p.GetDim() != GetDim())
826 {
827 return false;
828 }
829 if (p.GetNFields() != GetNFields())
830 {
831 return false;
832 }
833 for (int f = 0; f < GetNFields(); f++)
834 {
835 if (p.GetFieldVDim(f) != Field(f).GetVDim())
836 {
837 return false;
838 }
839 }
840 if (p.GetNTags() != GetNTags())
841 {
842 return false;
843 }
844
845 return true;
846
847}
848
849ParticleSet::ParticleSet(int num_particles, int dim,
850 Ordering::Type coords_ordering,
851 bool use_device)
852 : ParticleSet(1, 0, num_particles, dim, coords_ordering, Array<int>(),
853 Array<Ordering::Type>(), Array<const char*>(), 0,
854 Array<const char*>(), use_device)
855{
856
857}
858
859ParticleSet::ParticleSet(int num_particles, int dim,
860 const Array<int> &field_vdims, int num_tags,
861 Ordering::Type all_ordering, bool use_device)
862 : ParticleSet(1, 0, num_particles, dim, all_ordering, field_vdims,
863 GetOrderingArray(all_ordering, field_vdims.Size()),
864 GetEmptyNameArray(field_vdims.Size()), num_tags,
865 GetEmptyNameArray(num_tags), use_device)
866{
867}
868
869ParticleSet::ParticleSet(int num_particles, int dim,
870 const Array<int> &field_vdims, const Array<const
871 char*> &field_names_, int num_tags,
872 const Array<const char*> &tag_names_,
873 Ordering::Type all_ordering, bool use_device)
874 : ParticleSet(1, 0, num_particles, dim, all_ordering, field_vdims,
875 GetOrderingArray(all_ordering, field_vdims.Size()),
876 field_names_, num_tags,
877 tag_names_, use_device)
878{
879
880}
881
882ParticleSet::ParticleSet(int num_particles, int dim,
883 Ordering::Type coords_ordering,
884 const Array<int> &field_vdims,
885 const Array<Ordering::Type> &field_orderings,
886 const Array<const char*> &field_names_, int num_tags,
887 const Array<const char*> &tag_names_, bool use_device)
888 : ParticleSet(1, 0, num_particles, dim, coords_ordering, field_vdims,
889 field_orderings, field_names_, num_tags, tag_names_, use_device)
890{
891
892}
893
894
895
896#ifdef MFEM_USE_MPI
897ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
898 Ordering::Type coords_ordering, bool use_device)
899 : ParticleSet(comm_, rank_num_particles, dim, coords_ordering, Array<int>(),
900 Array<Ordering::Type>(), Array<const char*>(), 0,
901 Array<const char*>(), use_device)
902{
903
904};
905
906ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
907 const Array<int> &field_vdims, int num_tags,
908 Ordering::Type all_ordering, bool use_device)
909 : ParticleSet(comm_, rank_num_particles, dim, all_ordering, field_vdims,
910 GetOrderingArray(all_ordering, field_vdims.Size()),
911 GetEmptyNameArray(field_vdims.Size()), num_tags,
912 GetEmptyNameArray(num_tags), use_device)
913{
914
915}
916
917ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
918 const Array<int> &field_vdims, const Array<const
919 char*> &field_names_,
920 int num_tags, const Array<const char*> &tag_names_,
921 Ordering::Type all_ordering, bool use_device)
922 : ParticleSet(comm_, rank_num_particles, dim, all_ordering, field_vdims,
923 GetOrderingArray(all_ordering, field_vdims.Size()),
924 field_names_, num_tags,
925 tag_names_, use_device)
926{
927
928}
929
930ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
931 Ordering::Type coords_ordering,
932 const Array<int> &field_vdims,
933 const Array<Ordering::Type> &field_orderings,
934 const Array<const char*> &field_names_, int num_tags,
935 const Array<const char*> &tag_names_, bool use_device)
936 : ParticleSet(GetSize(comm_), (IDType)GetRank(comm_),
937 rank_num_particles,
938 dim,
939 coords_ordering,
940 field_vdims,
941 field_orderings,
942 field_names_,
943 num_tags,
944 tag_names_, use_device)
945{
946 comm = comm_;
947#ifdef MFEM_USE_GSLIB
948 gsl_comm = new gslib::comm;
949 cr = new gslib::crystal;
950 comm_init(gsl_comm, comm);
951 crystal_init(cr, gsl_comm);
952#endif // MFEM_USE_GSLIB
953}
954#endif // MFEM_USE_MPI
955
957{
958 IDType total = (IDType)GetNParticles();
959#ifdef MFEM_USE_MPI
960 MPI_Allreduce(MPI_IN_PLACE, &total, 1, MPI_UNSIGNED_LONG_LONG,
961 MPI_SUM, comm);
962#endif // MFEM_USE_MPI
963 return total;
964}
965
966int ParticleSet::AddField(int vdim, Ordering::Type field_ordering,
967 const char* field_name)
968{
969 std::string field_name_str(field_name ? field_name : "");
970 if (!field_name)
971 {
972 field_name_str = GetDefaultFieldName(field_names.size());
973 }
974 fields.emplace_back(std::make_unique<ParticleVector>(vdim, field_ordering,
975 GetNParticles()));
976 if (coords.UseDevice()) { fields.back()->UseDevice(true); }
977 field_names.emplace_back(field_name_str);
978
979 return GetNFields() - 1;
980}
981
982int ParticleSet::AddTag(const char* tag_name)
983{
984 std::string tag_name_str(tag_name ? tag_name : "");
985 if (!tag_name)
986 {
987 tag_name_str = GetDefaultTagName(tag_names.size());
988 }
989 tags.emplace_back(std::make_unique<Array<int>>(GetNParticles()));
990 if (coords.UseDevice()) { tags.back()->GetMemory().UseDevice(true); }
991 tag_names.emplace_back(tag_name_str);
992
993 return GetNTags() - 1;
994}
995
997{
998 MFEM_ASSERT(IsValidParticle(p),
999 "Particle is incompatible with ParticleSet.");
1000
1001 // Add the particle
1002 Array<int> idxs;
1005
1006 // Set the new particle data
1007 int idx = idxs[0];
1008 SetParticle(idx, p);
1009}
1010
1011void ParticleSet::AddParticles(int num_particles, Array<int> *new_indices)
1012{
1013 Array<IDType> add_ids(num_particles);
1014 for (int i = 0; i < num_particles; i++)
1015 {
1016 add_ids[i] = id_counter;
1018 }
1019
1020 AddParticles(add_ids, new_indices);
1021}
1022
1024{
1025 // Delete IDs
1026 ids.DeleteAt(list);
1027
1028 // Delete data
1029 for (int f = -1; f < GetNFields(); f++)
1030 {
1031 ParticleVector &pv = (f == -1 ? coords : *fields[f]);
1032 pv.DeleteParticles(list);
1033 }
1034
1035 // Delete tags
1036 for (int t = 0; t < GetNTags(); t++)
1037 {
1038 tags[t]->DeleteAt(list);
1039 }
1040}
1041
1043{
1045
1046 Coords().GetValues(i, p.Coords());
1047
1048 for (int f = 0; f < GetNFields(); f++)
1049 {
1050 Field(f).GetValues(i, p.Field(f));
1051 }
1052
1053 for (int t = 0; t < GetNTags(); t++)
1054 {
1055 p.Tag(t) = Tag(t).HostRead()[i];
1056 }
1057
1058 return p;
1059}
1060
1062{
1064 {
1065 return false;
1066 }
1067 for (int f = 0; f < GetNFields(); f++)
1068 {
1069 if (fields[f]->GetOrdering() == Ordering::byNODES ||
1070 fields[f]->UseDevice())
1071 {
1072 return false;
1073 }
1074 }
1075 for (int t = 0; t < GetNTags(); t++)
1076 {
1077 if (tags[t]->UseDevice())
1078 {
1079 return false;
1080 }
1081 }
1082 return true;
1083}
1084
1086{
1087 MFEM_ASSERT(IsParticleRefValid(),
1088 "GetParticleRef is only valid when coordinates and fields are "
1089 "ordered byVDIM and particle data is host-resident.");
1090
1092
1093 Coords().GetValuesRef(i, p.Coords());
1094
1095 for (int f = 0; f < GetNFields(); f++)
1096 {
1097 MFEM_ASSERT(Field(f).GetOrdering() == Ordering::byVDIM,
1098 "GetParticleRef only valid when all fields ordered byVDIM.");
1099 p.SetFieldRef(f, Field(f).GetData() + i*Field(f).GetVDim());
1100 }
1101
1102 for (int t = 0; t < GetNTags(); t++)
1103 {
1104 p.SetTagRef(t, &(*tags[t])[i]);
1105 }
1106
1107 return p;
1108}
1109
1111{
1112 MFEM_ASSERT(IsValidParticle(p),
1113 "Particle is incompatible with ParticleSet.");
1114
1115 Coords().SetValues(i, p.Coords());
1116
1117 for (int f = 0; f < GetNFields(); f++)
1118 {
1119 Field(f).SetValues(i, p.Field(f));
1120 }
1121
1122 for (int t = 0; t < GetNTags(); t++)
1123 {
1124 Tag(t).HostReadWrite()[i] = p.Tag(t);
1125 }
1126}
1127
1128void ParticleSet::PrintCSV(const char *fname, int precision)
1129{
1130 Array<int> all_field_idxs(GetNFields()), all_tag_idxs(GetNTags());
1131
1132 for (int f = 0; f < GetNFields(); f++)
1133 {
1134 all_field_idxs[f] = f;
1135 }
1136
1137 for (int t = 0; t < GetNTags(); t++)
1138 {
1139 all_tag_idxs[t] = t;
1140 }
1141
1142 PrintCSV(fname, all_field_idxs, all_tag_idxs, precision);
1143}
1144
1145void ParticleSet::PrintCSV(const char *fname, const Array<int> &field_idxs,
1146 const Array<int> &tag_idxs, int precision)
1147{
1148 std::stringstream ss_header;
1149
1150 // Configure header:
1151 ss_header << "id";
1152
1153#ifdef MFEM_USE_MPI
1154 ss_header << ",rank";
1155#endif // MFEM_USE_MPI
1156
1157 std::array<char, 3> ax = {'X', 'Y', 'Z'};
1158 for (int c = 0; c < coords.GetVDim(); c++)
1159 {
1160 ss_header << "," << ax[c];
1161 }
1162
1163 for (int f = 0; f < field_idxs.Size(); f++)
1164 {
1165 ParticleVector &pv = *fields[field_idxs[f]];
1166 for (int c = 0; c < pv.GetVDim(); c++)
1167 {
1168 ss_header << "," << field_names[field_idxs[f]] <<
1169 (pv.GetVDim() > 1 ? "_" + std::to_string(c) : "");
1170 }
1171 }
1172
1173 for (int t = 0; t < tag_idxs.Size(); t++)
1174 {
1175 ss_header << "," << tag_names[tag_idxs[t]];
1176 }
1177 ss_header << "\n";
1178
1179 // Configure data
1180 std::stringstream ss_data;
1181 ss_data.precision(precision);
1182#ifdef MFEM_USE_MPI
1183 int rank = GetRank(comm);
1184#endif // MFEM_USE_MPI
1185 // make sure we can read tag data on host. fields and coords will be read as
1186 // needed in the loop below, so we don't need to pre-read them here.
1187 for (int i = 0; i < GetNTags(); i++)
1188 {
1189 tags[i]->HostRead();
1190 }
1191 ids.HostRead();
1192
1193 // Write particle data
1194 for (int i = 0; i < GetNParticles(); i++)
1195 {
1196 ss_data << ids[i];
1197#ifdef MFEM_USE_MPI
1198 ss_data << "," << rank;
1199#endif // MFEM_USE_MPI
1200
1201 for (int c = 0; c < coords.GetVDim(); c++)
1202 {
1203 ss_data << "," << coords(i, c);
1204 }
1205 for (int f = 0; f < field_idxs.Size(); f++)
1206 {
1207 ParticleVector &pv = *fields[field_idxs[f]];
1208 for (int c = 0; c < pv.GetVDim(); c++)
1209 {
1210 ss_data << "," << pv(i, c);
1211 }
1212 }
1213 for (int t = 0; t < tag_idxs.Size(); t++)
1214 {
1215 ss_data << "," << (*tags[tag_idxs[t]])[i];
1216 }
1217 ss_data << "\n";
1218 }
1219
1220 // Write
1221 WriteToFile(fname, ss_header, ss_data);
1222}
1223
1225{
1226#if defined(MFEM_USE_MPI) && defined(MFEM_USE_GSLIB)
1227 if (gsl_comm)
1228 {
1229 if (!Mpi::IsFinalized()) // currently segfaults inside gslib otherwise
1230 {
1231 crystal_free(cr);
1232 comm_free(gsl_comm);
1233 delete gsl_comm;
1234 delete cr;
1235 }
1236 }
1237#endif // MFEM_USE_MPI && MFEM_USE_GSLIB
1238}
1239
1240
1241} // namespace mfem
Memory< T > & GetMemory()
Return a reference to the Memory object used by the Array.
Definition array.hpp:164
const T * HostRead() const
Shortcut for mfem::Read(a.GetMemory(), a.Size(), false).
Definition array.hpp:414
void Reserve(int capacity)
Ensures that the allocated size is at least the given size.
Definition array.hpp:210
void SetSize(int nsize)
Change the logical size of the array, keep existing entries.
Definition array.hpp:869
int Size() const
Return the logical size of the array.
Definition array.hpp:192
int Append(const T &el)
Append element 'el' to array, resize if necessary.
Definition array.hpp:941
T * HostReadWrite()
Shortcut for mfem::ReadWrite(a.GetMemory(), a.Size(), false).
Definition array.hpp:430
void DeleteAt(const Array< int > &indices)
Delete entries at indices, and resize.
Definition array.hpp:1036
T Sum() const
Return the sum of all the array entries using the '+'' operator for class 'T'.
Definition array.cpp:145
static MemoryClass GetDeviceMemoryClass()
Get the current Device MemoryClass. This is the MemoryClass used by most MFEM device kernels to acces...
Definition device.hpp:306
static bool IsEnabled()
Return true if any backend other than Backend::CPU is enabled.
Definition device.hpp:252
T * Write(MemoryClass mc, int size)
Get write-only access to the memory with the given MemoryClass.
const T * Read(MemoryClass mc, int size) const
Get read-only access to the memory with the given MemoryClass.
static bool IsFinalized()
Return true if MPI has been finalized.
The ordering method used when the number of unknowns per mesh node (vector dimension) is bigger than ...
Definition ordering.hpp:13
Type
Ordering methods:
Definition ordering.hpp:17
ParticleSet initializes and manages data associated with particles.
Array< IDType > ids
Global unique IDs of particles owned by this rank.
ParticleVector coords
Spatial coordinates of particles owned by this rank.
~ParticleSet()
Destructor.
unsigned long long IDType
void Redistribute(const Array< unsigned int > &rank_list)
Redistribute particle data to rank_list.
int GetNTags() const
Get the number of tags registered to particles.
void AddParticle(const Particle &p)
Add a particle using Particle .
bool IsValidParticle(const Particle &p) const
Check if a particle could belong in this ParticleSet by comparing field and tag dimension.
Particle GetParticleRef(int i)
Get Particle object whose members reference the actual data associated with particle i in this Partic...
std::vector< std::string > tag_names
Tag names, to be written when PrintCSV() is called.
std::vector< std::unique_ptr< ParticleVector > > fields
All particle fields for particles owned by this rank.
ParticleSet(int id_stride_, IDType id_counter_, int num_particles, int dim, Ordering::Type coords_ordering, const Array< int > &field_vdims, const Array< Ordering::Type > &field_orderings, const Array< const char * > &field_names_, int num_tags, const Array< const char * > &tag_names_, bool use_device)
Hidden main constructor of ParticleSet.
Particle GetParticle(int i) const
Get new Particle object with copy of data associated with particle i .
ParticleVector & Coords()
Get a reference to the coordinates ParticleVector.
int GetNFields() const
Get the number of fields registered to particles.
int AddTag(const char *tag_name=nullptr)
Add a tag to the ParticleSet.
const int id_stride
Stride for IDs (used internally when new particles are added).
bool IsParticleRefValid() const
Determine if GetParticleRef is valid.
ParticleVector & Field(int f)
Get a reference to field f 's ParticleVector.
int GetDim() const
Get the spatial dimension.
int GetNParticles() const
Get the number of active particles currently held by this ParticleSet.
void WriteToFile(const char *fname, const std::stringstream &ss_header, const std::stringstream &ss_data)
Write string in ss_header , followed by ss_data , to a single file; compatible in parallel.
Array< int > & Tag(int t)
Get a reference to tag t 's Array<int>.
int AddField(int vdim, Ordering::Type field_ordering=Ordering::byVDIM, const char *field_name=nullptr)
Add a field to the ParticleSet.
struct gslib::crystal * cr
IDType GetGlobalNParticles() const
Get the global number of active particles across all ranks.
void PrintCSV(const char *fname, int precision=16)
Print all particle data to a comma-delimited CSV file.
void Reserve(int res)
Reserve memory for res particles.
Array< int > GetFieldVDims() const
Get an Array<int> of the field vector-dimensions registered to particles.
std::vector< std::unique_ptr< Array< int > > > tags
All particle tags for particles owned by this rank.
IDType id_counter
Current globally unique ID to be assigned to the next particle added.
void RemoveParticles(const Array< int > &list)
Remove particle data specified by list of particle indices.
struct gslib::comm * gsl_comm
void AddParticles(const Array< IDType > &new_ids, Array< int > *new_indices=nullptr)
Add particles with global identifiers new_ids and optionally get the local indices of new particles i...
std::vector< std::string > field_names
Field names, to be written when PrintCSV() is called.
Particle CreateParticle() const
Create a Particle object with the same spatial dimension, number of fields and field vdims,...
void SetParticle(int i, const Particle &p)
Set data for particle at index i with data from provided particle p.
ParticleVector carries vector data (of a given vector dimension) for an arbitrary number of particles...
void SetValues(int i, const Vector &nvals)
Set particle i 's data to nvals .
void GetValues(int i, Vector &nvals) const
Get a copy of particle i 's data.
int GetNumParticles() const
Get the number of particle data in the ParticleVector.
void DeleteParticles(const Array< int > &indices)
Remove particle data at indices.
void SetNumParticles(int num_vectors, bool keep_data=true)
Set the number of particle Vector data to be held by the ParticleVector, keeping existing data.
int GetVDim() const
Get the Vector dimension of the ParticleVector.
void GetValuesRef(int i, Vector &nref)
For GetOrdering == Ordering::byVDIM, set nref to refer to particle i 's data.
Ordering::Type GetOrdering() const
Get the ordering of data in the ParticleVector.
Container for data associated with a single particle.
std::vector< Array< int > > tags
A std::vector of Array<int> where each Array<int> holds data for a given tag.
bool operator==(const Particle &rhs) const
Particle equality operator.
std::vector< Vector > fields
A std::vector of Vector where each Vector holds data for a given field (e.g., mass,...
Vector coords
Spatial coordinates.
Particle(int dim, const Array< int > &field_vdims, int num_tags)
Construct a Particle instance.
void Print(std::ostream &os=mfem::out) const
Print all particle data to os.
void SetTagRef(int t, int *tag_data)
Set tag t to reference external data.
void SetFieldRef(int f, real_t *field_data)
Set field f to reference external data.
Vector data type.
Definition vector.hpp:82
Memory< real_t > & GetMemory()
Return a reference to the Memory object used by the Vector.
Definition vector.hpp:265
int Size() const
Returns the size of the vector.
Definition vector.hpp:234
virtual void UseDevice(bool use_dev) const
Enable execution of Vector operations using the mfem::Device.
Definition vector.hpp:145
void SetSize(int s)
Resize the vector to size s.
Definition vector.hpp:633
void Reserve(int res)
Update Capacity() to res (if less than current), keeping existing entries.
Definition vector.hpp:682
int dim
Definition ex24.cpp:53
mfem::real_t real_t
MemoryClass
Memory classes identify sets of memory types.
float real_t
Definition config.hpp:46
std::function< real_t(const Vector &)> f(real_t mass_coeff)
Definition lor_mms.hpp:30
void forall(int N, lambda &&body)
Definition forall.hpp:1134
real_t p(const Vector &x, real_t t)