Block-Structured AMR Software Framework
AMReX_Conduit_Blueprint_ParticlesI.H
Go to the documentation of this file.
1 //
3 // Template implementation of functions for Conduit Mesh Blueprint Support
4 // for AMReX Particle Containers
5 //
6 // This file is included in AMReX_Conduit_Blueprint.H when
7 // when USE_PARTICLES = TRUE
8 //
10 
11 #include <sstream>
12 #include <conduit/conduit_blueprint.hpp>
13 #include <conduit/conduit_relay.hpp>
14 
15 namespace amrex
16 {
17 //---------------------------------------------------------------------------//
18 // Converts a AMReX Particle Tile into a Conduit Mesh Blueprint Hierarchy.
19 //---------------------------------------------------------------------------//
20 // Note:
21 // This is a helper function, it's not part of the AMReX Blueprint Interface.
22 //---------------------------------------------------------------------------//
23 template <typename ParticleType, int NArrayReal, int NArrayInt>
24 void
26  NArrayReal,
27  NArrayInt> &ptile,
28  const Vector<std::string> &real_comp_names,
29  const Vector<std::string> &int_comp_names,
30  conduit::Node &res,
31  const std::string &topology_name)
32 {
33  int num_particles = ptile.size();
34 
35  // knowing the above, we can zero copy the x,y,z positions + id, cpu
36  // and any user fields in the AOS
37 
38  // setup a blueprint description for the particle mesh
39  // create a coordinate set
40  std::string coordset_name = topology_name + "_coords";
41  conduit::Node &n_coords = res["coordsets"][coordset_name];
42  n_coords["type"] = "explicit";
43 
44  // create an explicit points topology
45  conduit::Node &n_topo = res["topologies"][topology_name];
46  n_topo["coordset"] = coordset_name;
47  n_topo["type"] = "unstructured";
48  n_topo["elements/shape"] = "point";
49  n_topo["elements/connectivity"].set(
50  conduit::DataType::c_int(num_particles));
51  int *conn = n_topo["elements/connectivity"].value();
52 
53  for(int i = 0; i < num_particles ; i++)
54  {
55  conn[i] = i;
56  }
57 
58  //----------------------------------//
59  // point locations from from aos
60  //----------------------------------//
61  char* pbuf = nullptr;
62 
63  if constexpr(ParticleType::is_soa_particle)
64  {
66 
67  const auto &soa = ptile.GetStructOfArrays();
68 
69  // for soa entries, we can use standard strides,
70  // since these are contiguous arrays
71 
72  n_coords["values/x"].set_external(const_cast<ParticleReal*>(soa.GetRealData(0).data()),
73  num_particles);
74 #if AMREX_SPACEDIM > 1
75  n_coords["values/y"].set_external(const_cast<ParticleReal*>(soa.GetRealData(1).data()),
76  num_particles);
77 #endif
78 #if AMREX_SPACEDIM > 2
79  n_coords["values/z"].set_external(const_cast<ParticleReal*>(soa.GetRealData(2).data()),
80  num_particles);
81 #endif
82  } else
83  {
84  // get the first particle's struct
85  const auto &pstruct = ptile.GetArrayOfStructs();
86  const int struct_size = sizeof(ParticleType);
87 
88  const char* pbuf_const = reinterpret_cast<const char*>(pstruct.data());
89  pbuf = const_cast<char*>(pbuf_const);
90 
91  ParticleReal* xp = reinterpret_cast<ParticleReal*>(pbuf); pbuf += sizeof(ParticleReal);
92  n_coords["values/x"].set_external(xp,
93  num_particles,
94  0,
95  struct_size);
96 #if AMREX_SPACEDIM > 1
97  ParticleReal* yp = reinterpret_cast<ParticleReal*>(pbuf); pbuf += sizeof(ParticleReal);
98  n_coords["values/y"].set_external(yp,
99  num_particles,
100  0,
101  struct_size);
102 #endif
103 #if AMREX_SPACEDIM > 2
104  ParticleReal* zp = reinterpret_cast<ParticleReal*>(pbuf); pbuf += sizeof(ParticleReal);
105  n_coords["values/z"].set_external(zp,
106  num_particles,
107  0,
108  struct_size);
109 #endif
110  }
111 
112  // fields
113  conduit::Node &n_fields = res["fields"];
114 
115  // -----------------------------
116  // user defined, real aos fields
117  // -----------------------------
118 
119  int vname_real_idx = 0;
120  if constexpr(!ParticleType::is_soa_particle)
121  {
122  constexpr int struct_size = sizeof(ParticleType);
123  constexpr int NStructReal = ParticleType::NReal;
124 
125  // struct real fields, the first set are always the particle positions
126  // which we wrap above
127  for (int i = 0; i < NStructReal; i++)
128  {
129  ParticleReal* val = reinterpret_cast<ParticleReal*>(pbuf); pbuf += sizeof(ParticleReal);
130  conduit::Node &n_f = n_fields[real_comp_names.at(vname_real_idx)];
131  n_f["topology"] = topology_name;
132  n_f["association"] = "element";
133  n_f["values"].set_external(val,
134  num_particles,
135  0,
136  struct_size);
137 
138  vname_real_idx++;
139  }
140  }
141 
142  //----------------------------------//
143  // standard integer fields from aos
144  // (id, cpu)
145  //----------------------------------//
146 
147  if constexpr(!ParticleType::is_soa_particle)
148  {
149  const int struct_size = sizeof(ParticleType);
150 
151  // id is the first int entry
152  int* id = reinterpret_cast<int*>(pbuf); pbuf += sizeof(int);
153  conduit::Node &n_f_id = n_fields[topology_name + "_id"];
154 
155  n_f_id["topology"] = topology_name;
156  n_f_id["association"] = "element";
157  n_f_id["values"].set_external(id,
158  num_particles,
159  0,
160  struct_size);
161 
162  // cpu is the second int entry
163  int* cpu = reinterpret_cast<int*>(pbuf); pbuf += sizeof(int);
164  conduit::Node &n_f_cpu = n_fields[topology_name + "_cpu"];
165 
166  n_f_cpu["topology"] = topology_name;
167  n_f_cpu["association"] = "element";
168  n_f_cpu["values"].set_external(cpu,
169  num_particles,
170  0,
171  struct_size);
172  } else {
173  const auto &soa = ptile.GetStructOfArrays();
174 
175  // for soa entries, we can use standard strides,
176  // since these are contiguous arrays
177 
178  conduit::Node &n_f_idcpu = n_fields[topology_name + "_idcpu"];
179 
180  n_f_idcpu["topology"] = topology_name;
181  n_f_idcpu["association"] = "element";
182  n_f_idcpu["values"].set_external(const_cast<uint64_t*>(soa.GetIdCPUData().data()),
183  num_particles);
184  }
185 
186  // --------------------------------
187  // user defined, integer aos fields
188  // --------------------------------
189 
190  int vname_int_idx = 0;
191  if constexpr(!ParticleType::is_soa_particle)
192  {
193  constexpr int struct_size = sizeof(ParticleType);
194  constexpr int NStructInt = ParticleType::NInt;
195 
196  for (int i = 0; i < NStructInt; i++)
197  {
198  int* val = reinterpret_cast<int*>(pbuf); pbuf += sizeof(int);
199  conduit::Node &n_f = n_fields[int_comp_names.at(vname_int_idx)];
200  n_f["topology"] = topology_name;
201  n_f["association"] = "element";
202  n_f["values"].set_external(val,
203  num_particles,
204  0,
205  struct_size);
206  vname_int_idx++;
207  }
208  }
209 
210  // -------------------------
211  // user defined soa fields
212  // -------------------------
213 
214  const auto &soa = ptile.GetStructOfArrays();
215 
216  // for soa entries, we can use standard strides,
217  // since these are contiguous arrays
218 
219  // array real fields
220  for (int i = 0; i < NArrayReal; i++)
221  {
222  conduit::Node &n_f = n_fields[real_comp_names.at(vname_real_idx)];
223  n_f["topology"] = topology_name;
224  n_f["association"] = "element";
225  n_f["values"].set_external(const_cast<ParticleReal*>(soa.GetRealData(i).data()),
226  num_particles);
227 
228  vname_real_idx++;
229  }
230 
231  // array int fields
232  for (int i = 0; i < NArrayInt; i++)
233  {
234  conduit::Node &n_f = n_fields[int_comp_names.at(vname_int_idx)];
235  n_f["topology"] = topology_name;
236  n_f["association"] = "element";
237  n_f["values"].set_external(const_cast<int*>(soa.GetIntData(i).data()),
238  num_particles);
239 
240  vname_int_idx++;
241  }
242 }
243 
244 //---------------------------------------------------------------------------//
245 // Converts a AMReX Particle Container into a Conduit Mesh Blueprint Hierarchy.
246 //---------------------------------------------------------------------------//
247 template <typename ParticleType, int NArrayReal, int NArrayInt>
248 void
250  NArrayReal,
251  NArrayInt> &pc,
252  const Vector<std::string> &real_comp_names,
253  const Vector<std::string> &int_comp_names,
254  conduit::Node &res,
255  const std::string &topology_name)
256 {
257  BL_PROFILE("ParticleContainerToBlueprint()");
258 
259  // validate varnames, which are used to provide field names
260  // for user defined aos and soa values.
261 
262  if constexpr(ParticleType::is_soa_particle) {
263  BL_ASSERT(real_comp_names.size() == NArrayReal);
264  BL_ASSERT(int_comp_names.size() == NArrayInt);
265  } else {
266  BL_ASSERT(real_comp_names.size() == (ParticleType::NReal + NArrayReal) );
267  BL_ASSERT(int_comp_names.size() == (ParticleType::NInt + NArrayInt) );
268  }
269 
270  int num_levels = pc.maxLevel() + 1;
271  int num_domains = 0;
272 
273  // get global domains already present in node
274  long domain_offset = (long)res.number_of_children();
275  ParallelDescriptor::ReduceLongSum(domain_offset);
276 
277  // loop over levels
278 
279  int rank = ParallelDescriptor::MyProc();
280  int nprocs = ParallelDescriptor::NProcs();
281 
283 
284  //
285  // blueprint expects unique ids for each domain published
286  // for particle data, this means we need a unique id for each
287  // tile across levels.
288  //
289  // to generate unique domain ids, we use a counting loop to
290  // calculate a per-level, per-rank offset
291  //
292 
293  // this holds our unique offset for each level for the current rank
294  std::vector<int> my_lvl_offsets;
295  my_lvl_offsets.resize(num_levels);
296 
297  // these hold the values we need to do a scan to calc offsets
298  std::vector<int> within_lvl_counts;
299  std::vector<int> within_lvl_offsets;
300  within_lvl_counts.resize(nprocs);
301  within_lvl_offsets.resize(nprocs);
302 
303  int total_num_domains = 0;
304  for (int lev = 0; lev < num_levels; ++lev)
305  {
306  // count how many tiles this mpi task has
307  long level_num_domains = 0;
308  for (MyParConstIter pti(pc, lev); pti.isValid(); ++pti)
309  {
310  level_num_domains+=1;
311  }
312 
313  // clear the within_lvl_counts and offsets data
314  for(int rank_idx = 0; rank_idx < nprocs ; rank_idx++)
315  {
316  within_lvl_counts[rank_idx] = 0;
317  within_lvl_offsets[rank_idx] = 0;
318  }
319 
320  // set number of domains for current rank at current level
321  within_lvl_counts[rank] = level_num_domains;
322  // mpi sum reduce to get all rank's counts for this level
323  ParallelDescriptor::ReduceIntSum(&within_lvl_counts[0],
324  nprocs);
325 
326  long level_total_num_domains = 0;
327  // scan to calc offset and total number of domains
328  for(int rank_idx = 0; rank_idx < nprocs ; rank_idx++)
329  {
330  within_lvl_offsets[rank_idx] = level_total_num_domains;
331  level_total_num_domains += within_lvl_counts[rank_idx];
332  }
333 
334  // the offset for this rank at this level is the current
335  // total number of domains + the offset for this level
336  my_lvl_offsets[lev] = within_lvl_offsets[rank] + total_num_domains;
337 
338  total_num_domains += level_num_domains;
339  }
340 
341  for (int lev = 0; lev < num_levels; ++lev)
342  {
343  // get our unique global offset for this rank at this level
344  int lvl_offset = my_lvl_offsets[lev];
345  // keep a counter of local # of tiles
346  int num_lvl_local_tiles = 0;
347  for (MyParConstIter pti(pc, lev); pti.isValid(); ++pti)
348  {
349  // this will give us a unique id across levels for each tile
350  int domain_id = lvl_offset + num_lvl_local_tiles + domain_offset;
351 
352  const auto& ptile = pti.GetParticleTile();
353  const std::string& patch_name = amrex::Concatenate("domain_",
354  domain_id,
355  6);
356  conduit::Node &patch = res[patch_name];
357  // add basic state info
358  patch["state/domain_id"] = domain_id;
360  real_comp_names,
361  int_comp_names,
362  patch,
363  topology_name);
364  num_lvl_local_tiles += 1;
365  }
366  }
367 
368  conduit::Node info;
369  // blueprint verify makes sure we conform to what's expected
370  // for a multi-domain mesh
371  if(!conduit::blueprint::mesh::verify(res,info))
372  {
373  // ERROR -- doesn't conform to the mesh blueprint
374  // show what went wrong
375  amrex::Print() << "ERROR: Conduit Mesh Blueprint Verify Failed!\n"
376  << info.to_yaml();
377  }
378 }
379 
380 
381 
382 }
#define BL_PROFILE(a)
Definition: AMReX_BLProfiler.H:551
#define BL_ASSERT(EX)
Definition: AMReX_BLassert.H:39
Definition: AMReX_ParIter.H:142
A distributed container for Particles sorted onto the levels, grids, and tiles of a block-structured ...
Definition: AMReX_ParticleContainer.H:145
This class provides the user with a few print options.
Definition: AMReX_Print.H:35
Long size() const noexcept
Definition: AMReX_Vector.H:50
void ReduceIntSum(int &)
Integer sum reduction.
Definition: AMReX_ParallelDescriptor.cpp:1252
int MyProc() noexcept
return the rank number local to the current Parallel Context
Definition: AMReX_ParallelDescriptor.H:125
void ReduceLongSum(Long &)
Long sum reduction.
Definition: AMReX_ParallelDescriptor.cpp:1223
int NProcs() noexcept
return the number of MPI ranks local to the current Parallel Context
Definition: AMReX_ParallelDescriptor.H:243
Definition: AMReX_Amr.cpp:49
std::string Concatenate(const std::string &root, int num, int mindigits)
Returns rootNNNN where NNNN == num.
Definition: AMReX_String.cpp:34
void ParticleTileToBlueprint(const ParticleTile< ParticleType, NArrayReal, NArrayInt > &ptile, const Vector< std::string > &real_comp_names, const Vector< std::string > &int_comp_names, conduit::Node &res, const std::string &topology_name)
Definition: AMReX_Conduit_Blueprint_ParticlesI.H:25
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void ignore_unused(const Ts &...)
This shuts up the compiler about unused variables.
Definition: AMReX.H:111
const int[]
Definition: AMReX_BLProfiler.cpp:1664
void ParticleContainerToBlueprint(const ParticleContainer_impl< ParticleType, NArrayReal, NArrayInt > &pc, const Vector< std::string > &real_comp_names, const Vector< std::string > &int_comp_names, conduit::Node &res, const std::string &topology_name)
Definition: AMReX_Conduit_Blueprint_ParticlesI.H:249
Definition: AMReX_ParticleTile.H:693