chombo-discharge
Loading...
Searching...
No Matches
CD_DischargeIOImplem.H
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2021-2026 SINTEF Energy Research
3 *
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
13#ifndef CD_DISCHARGEIOIMPLEM_H
14#define CD_DISCHARGEIOIMPLEM_H
15
16// Std includes
17#ifdef CH_USE_HDF5
18#include <hdf5.h>
19#endif
20
21// Chombo includes
22#include <ParticleIO.H>
23
24// Our includes
25#include <CD_DischargeIO.H>
26#include <CD_NamespaceHeader.H>
27
28#ifdef CH_USE_HDF5
29namespace DischargeIO {
30
35template <typename...>
36using VoidT = void;
37
42template <typename Traits, typename = void>
43struct HasH5PartColumns : std::false_type
44{
45};
46
51template <typename Traits>
52struct HasH5PartColumns<Traits, VoidT<decltype(Traits::h5PartColumns)>> : std::true_type
53{
54};
55
70template <typename P, typename Traits, typename Gather>
71inline void
72writeH5PartScalarDataset(hid_t a_grp,
73 hid_t a_fileSpace,
74 hid_t a_memSpace,
75 const ParticleContainer<P, Traits>& a_particles,
76 const std::string& a_name,
77 Gather a_gather) noexcept
78{
79 hid_t
80 dataset = H5Dcreate2(a_grp, a_name.c_str(), H5T_NATIVE_DOUBLE, a_fileSpace, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
81
82 std::vector<double> ds;
83
84 for (int lvl = 0; lvl <= a_particles.getFinestLevel(); lvl++) {
85 const DisjointBoxLayout& dbl = a_particles.getGrids()[lvl];
86 const DataIterator& dit = dbl.dataIterator();
87 const int nbox = dit.size();
88
89 for (int mybox = 0; mybox < nbox; mybox++) {
90 const DataIndex& din = dit[mybox];
91 const ParticleSoA<P, Traits>& leaf = a_particles[lvl][din];
92
93 for (std::size_t i = 0; i < leaf.size(); i++) {
94 ds.push_back(a_gather(leaf, i));
95 }
96 }
97 }
98
99 H5Dwrite(dataset, H5T_NATIVE_DOUBLE, a_memSpace, a_fileSpace, H5P_DEFAULT, ds.data());
100 H5Dclose(dataset);
101}
102
115template <std::size_t I, typename P, typename Traits, std::size_t... Dirs>
116inline void
117writeH5PartVectorColumn(hid_t a_grp,
118 hid_t a_fileSpace,
119 hid_t a_memSpace,
120 const ParticleContainer<P, Traits>& a_particles,
121 [[maybe_unused]] std::index_sequence<Dirs...> a_dirs) noexcept
122{
123 constexpr auto desc = std::get<I>(Traits::h5PartColumns);
124 const char* const suffix[3] = {"-x", "-y", "-z"};
125
126 (writeH5PartScalarDataset(a_grp,
127 a_fileSpace,
128 a_memSpace,
129 a_particles,
130 std::string(desc.name) + suffix[Dirs],
131 [](const ParticleSoA<P, Traits>& a_leaf, std::size_t a_i) -> double {
132 constexpr auto member = std::get<Dirs>(std::get<I>(Traits::h5PartColumns).members);
133 return static_cast<double>(a_leaf.template get<member>(a_i));
134 }),
135 ...);
136}
137
149template <std::size_t I, typename P, typename Traits>
150inline void
151writeH5PartColumn(hid_t a_grp,
152 hid_t a_fileSpace,
153 hid_t a_memSpace,
154 const ParticleContainer<P, Traits>& a_particles) noexcept
155{
156 constexpr auto desc = std::get<I>(Traits::h5PartColumns);
157
158 if constexpr (decltype(desc)::s_isVector) {
159 writeH5PartVectorColumn<I>(a_grp, a_fileSpace, a_memSpace, a_particles, std::make_index_sequence<SpaceDim>{});
160 }
161 else {
162 writeH5PartScalarDataset(a_grp,
163 a_fileSpace,
164 a_memSpace,
165 a_particles,
166 desc.name,
167 [](const ParticleSoA<P, Traits>& a_leaf, std::size_t a_i) -> double {
168 constexpr auto member = desc.member;
169 return static_cast<double>(a_leaf.template get<member>(a_i));
170 });
171 }
172}
173
185template <typename P, typename Traits, std::size_t... I>
186inline void
187writeH5PartColumns(hid_t a_grp,
188 hid_t a_fileSpace,
189 hid_t a_memSpace,
190 const ParticleContainer<P, Traits>& a_particles,
191 [[maybe_unused]] std::index_sequence<I...> a_indices) noexcept
192{
193 (writeH5PartColumn<I>(a_grp, a_fileSpace, a_memSpace, a_particles), ...);
194}
195
196} // namespace DischargeIO
197#endif
198
199template <typename P, typename Traits>
200void
201DischargeIO::writeH5Part(std::string a_filename,
202 const ParticleContainer<P, Traits>& a_particles,
203 RealVect a_shift,
204 Real a_time) noexcept
205{
206#ifdef CH_USE_HDF5
207 CH_TIME("DischargeIO::writeH5Part(SoA)");
208
209 // Figure out the number of particles on each rank
210 const unsigned long long numParticlesLocal = a_particles.getNumberOfValidParticlesLocal();
211 const unsigned long long numParticlesGlobal = a_particles.getNumberOfValidParticlesGlobal();
212
213 std::vector<unsigned long long> particlesPerRank;
214#ifdef CH_MPI
215 particlesPerRank.resize(numProc(), 0ULL);
216
217 std::vector<int> recv(numProc(), 1);
218 std::vector<int> displ(numProc(), 0);
219 for (int i = 0; i < numProc(); i++) {
220 displ[i] = i;
221 }
222 MPI_Allgatherv(&numParticlesLocal,
223 1,
224 MPI_UNSIGNED_LONG_LONG,
225 &particlesPerRank[0],
226 &recv[0],
227 &displ[0],
228 MPI_UNSIGNED_LONG_LONG,
229 Chombo_MPI::comm);
230#else
231 particlesPerRank.resize(1);
232 particlesPerRank[0] = numParticlesGlobal;
233#endif
234
235 // Set up file access and create the file.
236 hid_t fileAccess = 0;
237#ifdef CH_MPI
238 fileAccess = H5Pcreate(H5P_FILE_ACCESS);
239 H5Pset_fapl_mpio(fileAccess, Chombo_MPI::comm, MPI_INFO_NULL);
240#endif
241
242 hid_t fileID = H5Fcreate(a_filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, fileAccess);
243#ifdef CH_MPI
244 H5Pclose(fileAccess);
245#endif
246
247 // Define the top group necessary for the H5Part file format
248 hid_t grp = H5Gcreate2(fileID, "Step#0", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
249
250 // Write the time attribute
251 hid_t scal = H5Screate(H5S_SCALAR);
252 hid_t timeAttr = H5Acreate(grp, "time", H5T_NATIVE_DOUBLE, scal, H5P_DEFAULT, H5P_DEFAULT);
253 H5Awrite(timeAttr, H5T_NATIVE_DOUBLE, &a_time);
254 H5Aclose(timeAttr);
255
256 H5Sclose(scal);
257
258 // Define dataspace dimensions.
259 hsize_t dims[1];
260 dims[0] = numParticlesGlobal;
261
262 hid_t fileSpaceID = H5Screate_simple(1, dims, nullptr);
263
264 // Memory space
265 hsize_t memDims[1];
266 memDims[0] = numParticlesLocal;
267 hid_t memSpaceID = H5Screate_simple(1, memDims, nullptr);
268
269 // Set hyperslabs for file and memory
270 hsize_t fileStart[1];
271 hsize_t fileCount[1];
272
273 hsize_t memStart[1];
274 hsize_t memCount[1];
275
276 memStart[0] = 0;
277 fileStart[0] = 0;
278 for (int i = 0; i < procID(); i++) {
279 fileStart[0] += particlesPerRank[i];
280 }
281 fileCount[0] = particlesPerRank[procID()];
282 memCount[0] = particlesPerRank[procID()];
283
284 H5Sselect_hyperslab(fileSpaceID, H5S_SELECT_SET, fileStart, nullptr, fileCount, nullptr);
285 H5Sselect_hyperslab(memSpaceID, H5S_SELECT_SET, memStart, nullptr, memCount, nullptr);
286
287 // Create the ID and positional data sets
288 hid_t datasetID = H5Dcreate2(grp, "id", H5T_NATIVE_LLONG, fileSpaceID, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
289 hid_t datasetX = H5Dcreate2(grp, "x", H5T_NATIVE_DOUBLE, fileSpaceID, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
290 hid_t datasetY = H5Dcreate2(grp, "y", H5T_NATIVE_DOUBLE, fileSpaceID, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
291#if CH_SPACEDIM == 3
292 hid_t datasetZ = H5Dcreate2(grp, "z", H5T_NATIVE_DOUBLE, fileSpaceID, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
293#endif
294
295 std::vector<long long> id;
296 std::vector<double> x;
297 std::vector<double> y;
298 std::vector<double> z;
299
300 for (int lvl = 0; lvl <= a_particles.getFinestLevel(); lvl++) {
301 const DisjointBoxLayout& dbl = a_particles.getGrids()[lvl];
302 const DataIterator& dit = dbl.dataIterator();
303
304 const int nbox = dit.size();
305
306 for (int mybox = 0; mybox < nbox; mybox++) {
307 const DataIndex& din = dit[mybox];
308
309 const ParticleSoA<P, Traits>& leaf = a_particles[lvl][din];
310
311 for (std::size_t i = 0; i < leaf.size(); i++) {
312 const RealVect pos = leaf.position(i);
313
314 id.push_back(static_cast<long long>(leaf.particleID(i)));
315 x.push_back(pos[0] - a_shift[0]);
316 y.push_back(pos[1] - a_shift[1]);
317#if CH_SPACEDIM == 3
318 z.push_back(pos[2] - a_shift[2]);
319#endif
320 }
321 }
322 }
323
324 H5Dwrite(datasetID, H5T_NATIVE_LLONG, memSpaceID, fileSpaceID, H5P_DEFAULT, id.data());
325 H5Dwrite(datasetX, H5T_NATIVE_DOUBLE, memSpaceID, fileSpaceID, H5P_DEFAULT, x.data());
326 H5Dwrite(datasetY, H5T_NATIVE_DOUBLE, memSpaceID, fileSpaceID, H5P_DEFAULT, y.data());
327#if CH_SPACEDIM == 3
328 H5Dwrite(datasetZ, H5T_NATIVE_DOUBLE, memSpaceID, fileSpaceID, H5P_DEFAULT, z.data());
329#endif
330
331 H5Dclose(datasetID);
332 H5Dclose(datasetX);
333 H5Dclose(datasetY);
334#if CH_SPACEDIM == 3
335 H5Dclose(datasetZ);
336#endif
337
338 // Weight is a container-owned column and is always written.
339 writeH5PartScalarDataset(grp,
340 fileSpaceID,
341 memSpaceID,
342 a_particles,
343 "weight",
344 [](const ParticleSoA<P, Traits>& a_leaf, std::size_t a_i) -> double {
345 return a_leaf.weight(a_i);
346 });
347
348 // Payload datasets, derived declaratively from the optional Traits::h5PartColumns descriptor tuple.
349 if constexpr (HasH5PartColumns<Traits>::value) {
350 using H5PartTuple = std::remove_cv_t<std::remove_reference_t<decltype(Traits::h5PartColumns)>>;
351 writeH5PartColumns(grp,
352 fileSpaceID,
353 memSpaceID,
354 a_particles,
355 std::make_index_sequence<std::tuple_size<H5PartTuple>::value>{});
356 }
357
358 // Close dataspaces, top group and file
359 H5Sclose(fileSpaceID);
360 H5Sclose(memSpaceID);
361 H5Gclose(grp);
362 H5Fclose(fileID);
363#endif
364}
365
366#ifdef CH_USE_HDF5
367template <typename P, typename Traits>
368void
369DischargeIO::writeCheckParticlesToHDF(HDF5Handle& a_handle,
370 const LayoutData<ParticleSoA<P, Traits>>& a_particles,
371 const std::string& a_dataType)
372{
373 CH_TIMERS("writeParticlesToHDF(SoA)");
374
375 const BoxLayout& grids = a_particles.boxLayout();
376
377 std::vector<unsigned long long> locParticlesPerBox(grids.size(), 0);
378 unsigned long long numLocalParticles = 0;
379
380 // serial: accumulates into the running counter numLocalParticles
381 const DataIterator& dit = grids.dataIterator();
382 const int nbox = dit.size();
383 for (int mybox = 0; mybox < nbox; mybox++) {
384 const DataIndex& din = dit[mybox];
385 const size_t numItems = a_particles[din].size();
386 numLocalParticles += (unsigned long long)numItems;
387 locParticlesPerBox[grids.index(din)] = (unsigned long long)numItems;
388 }
389
390 std::vector<unsigned long long> particlesPerBox(grids.size());
391
392#ifdef CH_MPI
393 int result = MPI_Allreduce(&locParticlesPerBox[0],
394 &particlesPerBox[0],
395 locParticlesPerBox.size(),
396 MPI_UNSIGNED_LONG_LONG,
397 MPI_SUM,
398 Chombo_MPI::comm);
399 if (result != MPI_SUCCESS) {
400 MayDay::Error("MPI communication error in ParticleIO (SoA)");
401 }
402#else
403 for (int i = 0; i < grids.size(); i++) {
404 particlesPerBox[i] = (unsigned long long)locParticlesPerBox[i];
405 }
406#endif
407
408 write_hdf_part_header(a_handle, grids, particlesPerBox, a_dataType);
409
410 // The SoA HDF5 record (position + weight + the payload h5 subset) is stored as raw bytes
411 // (H5T_NATIVE_CHAR). Payload columns may be ParticleReal (float), so the per-particle byte size need
412 // NOT be a multiple of sizeof(double); a double-typed dataset would truncate the extent
413 // (numComps = objSize/sizeof(double)) and overflow the hyperslab on write/read. Byte-based storage is
414 // exact for any payload and any SpaceDim.
415 const size_t objSize = ParticleSoA<P, Traits>::h5BytesPerParticle();
416
417 std::vector<unsigned long long> offsets;
418 unsigned long long totNumParticles = 0;
419 for (int i = 0; i < grids.size(); i++) {
420 offsets.push_back(objSize * totNumParticles);
421 totNumParticles += particlesPerBox[i];
422 }
423
424 if (totNumParticles == 0) {
425 return;
426 }
427
428 hsize_t dataSize = totNumParticles * objSize;
429 hid_t dataspace = H5Screate_simple(1, &dataSize, nullptr);
430 hid_t H5T_type = H5T_NATIVE_CHAR;
431 std::string dataname = a_dataType + ":data";
432
433#ifdef H516
434 hid_t dataset = H5Dcreate(a_handle.groupID(), dataname.c_str(), H5T_type, dataspace, H5P_DEFAULT);
435#else
436 hid_t dataset = H5Dcreate2(a_handle.groupID(),
437 dataname.c_str(),
438 H5T_type,
439 dataspace,
440 H5P_DEFAULT,
441 H5P_DEFAULT,
442 H5P_DEFAULT);
443#endif
444
445 if (numLocalParticles > 0) {
446 const size_t chunkSize = objSize * _CHUNK;
447 char* const chunkBase = new char[chunkSize];
448 char* chunk = chunkBase;
449
450 if (chunkBase == nullptr) {
451 MayDay::Error("WritePart(SoA)::Error: new returned NULL pointer ");
452 }
453
454 // serial: writes into the shared HDF5 dataset at running per-box offsets and shares the chunk buffer
455 const DataIterator& dit = grids.dataIterator();
456 const int nbox = dit.size();
457
458 for (int mybox = 0; mybox < nbox; mybox++) {
459 const DataIndex& din = dit[mybox];
460 size_t offset = offsets[grids.index(din)];
461
462 const ParticleSoA<P, Traits>& leaf = a_particles[din];
463 const size_t n = leaf.size();
464
465 for (size_t ip = 0; ip < n; ip++) {
466 leaf.h5LinearizeParticle((void*)chunk, ip);
467 chunk += objSize;
468
469 if ((ip + 1) % _CHUNK == 0) {
470 chunk -= chunkSize;
471 writeDataChunk(offset, dataspace, dataset, H5T_type, chunkSize, chunk);
472 }
473 }
474
475 const size_t nResidual = n % _CHUNK;
476 if (nResidual > 0) {
477 chunk -= (nResidual * objSize);
478 writeDataChunk(offset, dataspace, dataset, H5T_type, nResidual * objSize, chunk);
479 }
480
481 chunk = chunkBase;
482 }
483
484 delete[] chunkBase;
485 }
486
487 H5Sclose(dataspace);
488 H5Dclose(dataset);
489}
490#endif
491
492#ifdef CH_USE_HDF5
493template <typename P, typename Traits>
494void
495DischargeIO::readCheckParticlesFromHDF(HDF5Handle& a_handle,
496 LayoutData<ParticleSoA<P, Traits>>& a_particles,
497 const std::string& a_dataType)
498{
499 CH_TIMERS("readParticlesFromHDF(SoA)");
500
501 std::vector<unsigned long long> particlesPerBox;
502 Vector<Box> grids;
503 read_hdf_part_header(a_handle, grids, particlesPerBox, a_dataType, a_handle.getGroup());
504
505 const BoxLayout& bl = a_particles.boxLayout();
506
507 // Byte-based storage (matches writeCheckParticlesToHDF): the per-particle record may not be a multiple
508 // of sizeof(double) (ParticleReal payload columns), so everything is counted in bytes (H5T_NATIVE_CHAR).
509 const size_t objSize = ParticleSoA<P, Traits>::h5BytesPerParticle();
510
511 std::vector<unsigned long long> offsets;
512 unsigned long long totNumParticles = 0;
513 for (int i = 0; i < grids.size(); i++) {
514 offsets.push_back(objSize * totNumParticles);
515 totNumParticles += particlesPerBox[i];
516 }
517
518 if (totNumParticles == 0) {
519 return;
520 }
521
522 hid_t H5T_type = H5T_NATIVE_CHAR;
523 std::string dataname = a_dataType + ":data";
524
525#ifdef H516
526 hid_t dataset = H5Dopen(a_handle.groupID(), dataname.c_str());
527#else
528 hid_t dataset = H5Dopen2(a_handle.groupID(), dataname.c_str(), H5P_DEFAULT);
529#endif
530
531 hid_t dataspace = H5Dget_space(dataset);
532
533 const size_t chunkSize = objSize * _CHUNK;
534 char* chunk = new char[chunkSize];
535
536 // serial: reads from the shared HDF5 dataset at per-box offsets and shares the chunk buffer
537 const DataIterator& dit = bl.dataIterator();
538 const int nbox = dit.size();
539 for (int mybox = 0; mybox < nbox; mybox++) {
540 const DataIndex& din = dit[mybox];
541 size_t boxData = objSize * particlesPerBox[bl.index(din)]; // bytes
542 size_t offset = offsets[bl.index(din)]; // bytes
543
544 ParticleSoA<P, Traits>& leaf = a_particles[din];
545
546 size_t dataIn = 0;
547 while (dataIn < boxData) {
548 int size = ((boxData - dataIn) >= chunkSize) ? chunkSize : boxData - dataIn; // bytes
549 readDataChunk(offset, dataspace, dataset, H5T_type, size, chunk);
550
551 for (int ip = 0; ip < size / (int)objSize; ip++) {
552 leaf.h5DelinearizeAndAppend(chunk);
553 chunk += objSize;
554 }
555 dataIn += size;
556 chunk -= size;
557 }
558 }
559
560 delete[] chunk;
561 chunk = nullptr;
562
563 H5Sclose(dataspace);
564 H5Dclose(dataset);
565}
566#endif
567
568#include <CD_NamespaceFooter.H>
569
570#endif
Silly, but useful functions that override standard Chombo HDF5 IO.
AMR-hierarchy container of computational particles, stored per patch in Struct-of-Arrays form.
Definition CD_ParticleContainer.H:123
Arena-backed Struct-of-Arrays particle container for a single grid patch.
Definition CD_ParticleSoA.H:655
RealVect position(const std::size_t a_index) const noexcept
Position of particle i as a RealVect (by value, assembled from the scalar columns).
Definition CD_ParticleSoA.H:1188
double & weight(const std::size_t a_index) noexcept
Weight of particle i.
Definition CD_ParticleSoA.H:1222
std::size_t size() const noexcept
Number of particles currently stored.
Definition CD_ParticleSoA.H:882
void h5DelinearizeAndAppend(const void *a_buffer)
Append a particle whose HDF5-checkpointed columns come from a buffer.
Definition CD_ParticleSoAImplem.H:275
void h5LinearizeParticle(void *a_buffer, const std::size_t a_index) const noexcept
Linearize the HDF5-checkpointed columns of particle i (no id/rank).
Definition CD_ParticleSoAImplem.H:258
static constexpr std::size_t h5BytesPerParticle() noexcept
Bytes for one HDF5-linearized particle (position + weight + h5 payload subset).
Definition CD_ParticleSoA.H:1380
ParticleID & particleID(const std::size_t a_index) noexcept
Global id of particle i (container-owned metadata).
Definition CD_ParticleSoA.H:1253
Namespace which encapsulates chombo-discharge IO functionality.
Definition CD_DischargeIO.H:42
void writeH5Part(std::string a_filename, const ParticleContainer< P, Traits > &a_particles, RealVect a_shift, Real a_time) noexcept
Write an SoA particle container to an H5Part file (quick visualization).
Definition CD_DischargeIOImplem.H:201