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+
19127int
20128main (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