Skip to content

Commit dac3ed3

Browse files
author
Martin D. Weinberg
committed
Updated slabics to write hdf5
1 parent 6b3f73e commit dac3ed3

1 file changed

Lines changed: 273 additions & 30 deletions

File tree

utils/ICs/genslab.cc

Lines changed: 273 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,133 @@
55
#include <iomanip>
66
#include <fstream>
77
#include <cstring>
8+
#include <variant>
89
#include <random>
910
#include <string>
1011
#include <memory>
1112
#include <cmath>
1213

1314
#include <Eigen/Dense>
1415

16+
// HighFive
17+
#include <highfive/H5File.hpp>
18+
#include <highfive/H5DataSet.hpp>
19+
#include <highfive/H5DataSpace.hpp>
20+
21+
// Required for H5Zfilter_avail
22+
#include <H5Zpublic.h>
23+
1524
#include "massmodel1d.H"
1625
#include "interp.H"
1726
#include "cxxopts.H"
1827

28+
using namespace HighFive;
29+
30+
31+
// Factory designed to iterate across filter list available on Ubuntu
32+
// 26.04 LTS and other systems with HDF5 1.14.x and HighFive 2.6.x
33+
HighFive::DataSetCreateProps
34+
createFilterProps(const std::vector<hsize_t>& chunk_dims,
35+
unsigned int filter_id,
36+
// Scale from 1 (fast) to 9 (max compression)
37+
int compression_level = 5)
38+
{
39+
HighFive::DataSetCreateProps props;
40+
props.add(HighFive::Chunking(chunk_dims));
41+
42+
// For most compressors, applying shuffle improves compression for floating-point data.
43+
// (Blosc can do its own shuffling internally; shuffle-only/checksum-only are handled below.)
44+
if (filter_id != 3 && filter_id != 4 && filter_id != 32001) {
45+
props.add(HighFive::Shuffle());
46+
}
47+
hid_t plist_id = props.getId();
48+
std::vector<unsigned int> cd_values;
49+
50+
switch (filter_id) {
51+
case 1: // Deflate / GZIP (Built-in)
52+
// Expects 1 parameter: compression level (0-9)
53+
cd_values = { static_cast<unsigned int>(compression_level) };
54+
break;
55+
56+
case 2: // SZIP (Built-in)
57+
// SZIP is complex and highly variant; HighFive includes native wrappers.
58+
// For raw testing, standard pixels-per-block option is typically 16.
59+
cd_values = { 141, 16 };
60+
break;
61+
62+
case 3: // Shuffle Filter alone (No trailing compression)
63+
props.add(HighFive::Shuffle());
64+
return props;
65+
66+
case 4: // Fletcher32 Checksum alone (Data validation, not compression)
67+
// Fletcher32 uses built-in ID 4 and accepts 0 configuration arguments.
68+
// Call the native HDF5 library directly using HighFive's internal handle.
69+
H5Pset_filter(plist_id, 4, H5Z_FLAG_OPTIONAL, 0, NULL);
70+
return props;
71+
72+
case 307: // BZip2
73+
// Expects 1 parameter: Block size in 100KB steps (1-9)
74+
cd_values = { static_cast<unsigned int>(compression_level) };
75+
break;
76+
77+
case 32004: // LZ4
78+
// Expects 1 parameter: internal chunk/block size.
79+
// 0 falls back to the default (64KB), optimized for CPU caches.
80+
cd_values = { 0 };
81+
break;
82+
83+
case 32001: // Blosc (v1 Meta-Compressor)
84+
// Expects 7 parameters. Slots 0-3 are reserved.
85+
// [4]=level(1-9), [5]=shuffle type(1=byte, 2=bit), [6]=codec code
86+
// Codecs: 0=blosclz, 1=lz4, 2=lz4hc, 3=snappy, 4=zlib, 5=zstd
87+
cd_values = { 0, 0, 0, 0,
88+
static_cast<unsigned int>(compression_level),
89+
1, // 1 = Byte Shuffle (Highly recommended for float/double)
90+
1 }; // 1 = Redirect internal processing to LZ4
91+
break;
92+
93+
default:
94+
throw std::invalid_argument("Unknown or unsupported testing filter ID requested.");
95+
}
96+
97+
// Pass the custom properties down to the HDF5 backend pipeline
98+
if (!cd_values.empty()) {
99+
H5Pset_filter(plist_id, filter_id, H5Z_FLAG_OPTIONAL, cd_values.size(), cd_values.data());
100+
}
101+
102+
return props;
103+
}
104+
105+
// Specify floating point precision
106+
enum class FloatPrecision { FLOAT32, FLOAT64 };
107+
108+
// Type variant for flexible float handling
109+
template<typename T>
110+
struct ParticleDataTemplate
111+
{
112+
std::optional<std::vector<unsigned long>> index; // optional particle index
113+
std::vector<T> m; // mass
114+
std::vector<T> x, y, z; // position
115+
std::vector<T> u, v, w; // velocity
116+
std::vector<std::vector<int>> aux_ints; // auxiliary integer fields
117+
std::vector<std::vector<T>> aux_floats; // auxiliary float fields
118+
119+
size_t num_particles = 0;
120+
size_t num_aux_ints = 0;
121+
size_t num_aux_floats = 0;
122+
};
123+
124+
// Variant for flexible reading
125+
using FloatData = std::variant<std::vector<float>, std::vector<double>>;
126+
19127
int
20128
main(int argc, char **argv)
21129
{
22130
unsigned int seed;
23-
int Ntable, Number;
131+
int Ntable, Number, Num_aux_ints, Num_aux_floats;
24132
double Dratio, Hratio, R, Hmax, DispX, DispZ, fJ, Lx, Ly;
25133
std::string outfile, config, modfile, modelType;
134+
unsigned filter_id = 1;
26135
bool Mu;
27136

28137
// Parse command line
@@ -33,8 +142,15 @@ main(int argc, char **argv)
33142

34143
options.add_options()
35144
("h,help", "Print this help message")
145+
("5,hdf5", "Write HDF5 output (default is ASCII)")
146+
("v,verbose", "Verbose output")
147+
("f,filter", "HDF5 filter ID to use (default: 1 = GZIP)", cxxopts::value<unsigned>(filter_id)->default_value("1"))
36148
("N,number", "Number of bodies",
37149
cxxopts::value<int>(Number)->default_value("10000"))
150+
("nauxint", "Number of auxiliary integer fields",
151+
cxxopts::value<int>(Num_aux_ints)->default_value("0"))
152+
("nauxfloat", "Number of auxiliary float fields",
153+
cxxopts::value<int>(Num_aux_floats)->default_value("0"))
38154
("n,ntable", "Number of points in model table",
39155
cxxopts::value<int>(Ntable)->default_value("400"))
40156
("m,model", "Model type (LowIso, Sech2, Sech2mu, Sech2Halo)",
@@ -85,14 +201,6 @@ main(int argc, char **argv)
85201
return 0;
86202
}
87203

88-
std::ofstream out(outfile);
89-
if (!out) {
90-
std::cerr << "Can't open <" << outfile << ">" << std::endl;
91-
exit(-1);
92-
}
93-
out.precision(6);
94-
out.setf(ios::scientific);
95-
96204
// Define model
97205
//
98206
double h = 1.0;
@@ -137,11 +245,14 @@ main(int argc, char **argv)
137245
std::cout.setf(ios::left);
138246
char prev = cout.fill('.');
139247

140-
std::cout << std::setw(40) << "Model type" << modelType << std::endl;
141-
std::cout << std::setw(40) << "Surface mass density" << mu << std::endl;
142-
std::cout << std::setw(40) << "Jeans' length" << LJ << std::endl;
143-
std::cout << std::setw(40) << "Scale height" << h << std::endl;
144-
std::cout << std::setw(40) << "Maximum thickness" << maxZ << std::endl;
248+
if (vm.count("verbose")) {
249+
std::cout << std::endl << "Slab model parameters:" << std::endl;
250+
std::cout << std::setw(40) << "Model type" << modelType << std::endl;
251+
std::cout << std::setw(40) << "Surface mass density" << mu << std::endl;
252+
std::cout << std::setw(40) << "Jeans' length" << LJ << std::endl;
253+
std::cout << std::setw(40) << "Scale height" << h << std::endl;
254+
std::cout << std::setw(40) << "Maximum thickness" << maxZ << std::endl;
255+
}
145256

146257
cout.fill(prev);
147258

@@ -163,30 +274,162 @@ main(int argc, char **argv)
163274
std::normal_distribution Vv{0.0, sqrt(DispZ)};
164275
std::normal_distribution Vh{0.0, sqrt(DispX)};
165276

166-
// Header line
167-
out << std::setw(10) << Number << std::setw(15) << 0 << std::setw(15) << 0 << std::endl;
168-
169277
double KE = 0.0;
170278
double VC = 0.0;
171279
double mass = mu/Number;
172280

173-
Eigen::MatrixXd posvel(Number, 6);
281+
if (vm.count("verbose")) {
282+
std::cout << std::endl << "Generating " << Number << " particles..." << std::endl;
283+
}
174284

175-
#pragma omp parallel for reduction(+:KE,VC)
176-
for (int n=0; n<Number; n++) {
285+
if (vm.count("hdf5")) {
177286

178-
posvel.row(n) <<
179-
Lx*Unit(gen), Ly*Unit(gen), odd2(Unit(gen)*M.back(), M, Z),
180-
Vh(gen), Vh(gen), Vv(gen);
287+
// Could be updated to allow user selection of precision, but for
288+
// now we default to float32
289+
//
290+
FloatPrecision precision = FloatPrecision::FLOAT32;
291+
ParticleDataTemplate<float> data;
181292

182-
KE += posvel(n, 5)*posvel(n, 5);
183-
VC += posvel(n, 2)*model->get_dpot(posvel(n, 2));
184-
}
293+
data.m.resize(Number);
294+
data.x.resize(Number);
295+
data.y.resize(Number);
296+
data.z.resize(Number);
297+
data.u.resize(Number);
298+
data.v.resize(Number);
299+
data.w.resize(Number);
300+
301+
data.num_particles = Number;
302+
data.num_aux_ints = Num_aux_ints;
303+
data.num_aux_floats = Num_aux_floats;
304+
305+
data.aux_ints.resize(Num_aux_ints);
306+
for (auto& vec : data.aux_ints) {
307+
vec.resize(Number);
308+
vec.assign(Number, 0); // Initialize auxiliary integer fields to zero
309+
}
310+
311+
data.aux_floats.resize(Num_aux_floats);
312+
for (auto& vec : data.aux_floats) {
313+
vec.resize(Number);
314+
vec.assign(Number, 0.0f); // Initialize auxiliary float fields to zero
315+
}
316+
317+
#pragma omp parallel for schedule(dynamic, 256) reduction(+:KE, VC) num_threads(omp_get_max_threads())
318+
for (int n=0; n<Number; n++) {
319+
320+
data.m[n] = static_cast<float>(mass);
321+
data.x[n] = static_cast<float>(Lx*Unit(gen));
322+
data.y[n] = static_cast<float>(Ly*Unit(gen));
323+
data.z[n] = static_cast<float>(odd2(Unit(gen)*M.back(), M, Z));
324+
data.u[n] = static_cast<float>(Vh(gen));
325+
data.v[n] = static_cast<float>(Vh(gen));
326+
data.w[n] = static_cast<float>(Vv(gen));
327+
328+
KE += data.w[n] * data.w[n];
329+
VC += data.z[n] * model->get_dpot(data.z[n]);
330+
}
331+
332+
// Create HDF5 file with compression enabled (SEQUENTIAL - thread-safe)
333+
//
334+
if (vm.count("verbose")) std::cout << "Writing HDF5 file..." << std::endl;
335+
336+
std::string hdf5_file = outfile + ".h5";
337+
File file(hdf5_file, File::ReadWrite | File::Create | File::Truncate);
338+
339+
// Store header information and precision metadata
340+
//
341+
file.createAttribute<int>("Number", DataSpace::From(Number))
342+
.write(Number);
343+
file.createAttribute<int>("num_aux_ints", DataSpace::From(Num_aux_ints))
344+
.write(Num_aux_ints);
345+
file.createAttribute<int>("num_aux_floats", DataSpace::From(Num_aux_floats))
346+
.write(Num_aux_floats);
347+
348+
// Create a group for particle data
349+
//
350+
Group particles_group = file.createGroup("particles");
351+
352+
// Calculate optimized, capped chunk size
353+
//
354+
hsize_t chunk_size = 262144; // Cachesafe cap (~2MB for double)
355+
if (Number < chunk_size) {
356+
chunk_size = std::max<hsize_t>(1024, Number);
357+
}
358+
if (chunk_size > Number) {
359+
chunk_size = Number; // Absolute safety fallback
360+
}
361+
362+
// Define compression filter
363+
//
364+
auto props = createFilterProps({chunk_size}, filter_id, 5);
365+
366+
// Write datasets sequentially (HDF5 is not fully thread-safe for writing)
367+
particles_group.createDataSet("m", data.m, props);
368+
particles_group.createDataSet("x", data.x, props);
369+
particles_group.createDataSet("y", data.y, props);
370+
particles_group.createDataSet("z", data.z, props);
371+
particles_group.createDataSet("u", data.u, props);
372+
particles_group.createDataSet("v", data.v, props);
373+
particles_group.createDataSet("w", data.w, props);
374+
if (data.index.has_value()) {
375+
particles_group.createDataSet("index", *data.index, props);
376+
}
377+
378+
// Write auxiliary integer fields
379+
for (size_t j = 0; j < data.aux_ints.size(); ++j) {
380+
std::string dset_name = "aux_int_" + std::to_string(j);
381+
particles_group.createDataSet(dset_name, data.aux_ints[j], props);
382+
}
383+
384+
// Write auxiliary float fields
385+
for (size_t j = 0; j < data.aux_floats.size(); ++j) {
386+
std::string dset_name = "aux_float_" + std::to_string(j);
387+
particles_group.createDataSet(dset_name, data.aux_floats[j], props);
388+
}
389+
390+
if (vm.count("verbose")) {
391+
std::string precision_str = (precision == FloatPrecision::FLOAT32) ? "float32" : "float64";
392+
std::cout << "Successfully wrote " << Number << " particles to "
393+
<< hdf5_file << " (" << precision_str << ")" << std::endl;
394+
}
395+
396+
} else {
397+
398+
if (vm.count("verbose")) {
399+
std::cout << "Writing ASCII output to " << outfile << std::endl;
400+
}
401+
402+
std::ofstream out(outfile);
403+
if (!out) {
404+
std::cerr << "Can't open <" << outfile << ">" << std::endl;
405+
exit(-1);
406+
}
407+
out.precision(6);
408+
out.setf(ios::scientific);
409+
410+
// Header line
411+
out << std::setw(10) << Number << std::setw(15) << 0 << std::setw(15) << 0 << std::endl;
412+
413+
Eigen::MatrixXd posvel(Number, 6);
414+
415+
// Generate particles
416+
#pragma omp parallel for schedule(dynamic, 256) reduction(+:KE, VC) num_threads(omp_get_max_threads())
417+
for (int n=0; n<Number; n++) {
418+
posvel.row(n) <<
419+
Lx*Unit(gen), Ly*Unit(gen), odd2(Unit(gen)*M.back(), M, Z),
420+
Vh(gen), Vh(gen), Vv(gen);
421+
422+
KE += posvel(n, 5)*posvel(n, 5);
423+
VC += posvel(n, 2)*model->get_dpot(posvel(n, 2));
424+
}
185425

186-
for (int n=0; n<Number; n++) {
187-
out << std::setw(15) << mass;
188-
for (int j=0; j<6; j++) out << std::setw(15) << posvel(n, j);
189-
out << std::endl;
426+
std::cout << "Done generating particles" << std::endl;
427+
428+
for (int n=0; n<Number; n++) {
429+
out << std::setw(15) << mass;
430+
for (int j=0; j<6; j++) out << std::setw(15) << posvel(n, j);
431+
out << std::endl;
432+
}
190433
}
191434

192435
std::cout << std::endl

0 commit comments

Comments
 (0)