Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
output.cxx
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file output.cxx
1 // TRENTO: Reduced Thickness Event-by-event Nuclear Topology
2 // Copyright 2015 Jonah E. Bernhard, J. Scott Moreland
3 // TRENTO3D: Three-dimensional extension of TRENTO by Weiyao Ke
4 // MIT License
5 
6 #include "output.h"
7 
8 #include <algorithm>
9 #include <cmath>
10 #include <iomanip>
11 #include <iostream>
12 
13 #include <boost/filesystem.hpp>
14 #include <boost/filesystem/fstream.hpp>
15 #include <boost/program_options/variables_map.hpp>
16 
17 #include "event.h"
18 #include "hdf5_utils.h"
19 
20 namespace trento {
21 
22 namespace {
23 
24 // These output functions are invoked by the Output class.
25 
26 void write_stream(std::ostream& os, int width,
27  int num, double impact_param, const Event& event) {
28  using std::fixed;
29  using std::setprecision;
30  using std::setw;
31  using std::scientific;
32 
33  // Write a nicely-formatted line of event properties.
34  os << setprecision(10)
35  << setw(width) << num
36  << setw(15) << fixed << impact_param
37  << setw(5) << event.npart();
38  if (event.with_ncoll()) os << setw(8) << event.ncoll();
39  os << setw(18) << scientific << event.multiplicity()
40  << fixed;
41 
42  for (const auto& ecc : event.eccentricity())
43  os << setw(14) << ecc.second;
44 
45  //for (const auto& psi : event.participant_plane())
46  // os << setw(14) << psi.second;
47 
48  os << '\n';
49 }
50 
51 void write_text_file(const fs::path& output_dir, int width,
52  int num, double impact_param, const Event& event, bool header) {
53  // Open a numbered file in the output directory.
54  // Pad the filename with zeros.
55  std::ostringstream padded_fname{};
56  padded_fname << std::setw(width) << std::setfill('0') << num << ".dat";
57  fs::ofstream ofs{output_dir / padded_fname.str()};
58 
59  if (header) {
60  // Write a commented header of event properties as key = value pairs.
61  ofs << std::setprecision(10)
62  << "# event " << num << '\n'
63  << "# b = " << impact_param << '\n'
64  << "# npart = " << event.npart() << '\n'
65  << "# mult = " << event.multiplicity() << '\n';
66 
67  for (const auto& ecc : event.eccentricity())
68  ofs << "# e" << ecc.first << " = " << ecc.second << '\n';
69 
70  for (const auto& psi : event.participant_plane())
71  ofs << "# psi" << psi.first << " = " << psi.second << '\n';
72  }
73 
74  // Write IC profile as a block grid. Use C++ default float format (not
75  // fixed-width) so that trailing zeros are omitted. This significantly
76  // increases output speed and saves disk space since many grid elements are
77  // zero.
78  bool is3d;
79  if (event.density_grid().shape()[2] == 1) is3d = false;
80  else is3d = true;
81 
82 
83  for (const auto& slice : event.density_grid()) {
84  for (const auto& row : slice) {
85  for (const auto& item : row) {
86  ofs << item << " ";
87  }
88  if (is3d) ofs << std::endl;
89  }
90  if (!is3d) ofs << std::endl;
91  }
92 }
93 
94 #ifdef TRENTO_HDF5
95 
97 class HDF5Writer {
98  public:
100  HDF5Writer(const fs::path& filename);
101 
103  void operator()(int num, double impact_param, const Event& event) const;
104 
105  private:
107  H5::H5File file_;
108 };
109 
110 // Add a simple scalar attribute to an HDF5 dataset.
111 //template <typename T>
112 //void hdf5_add_scalar_attr(
113 // const H5::DataSet& dataset, const std::string& name, const T& value) {
114 // const auto& datatype = hdf5::type<T>();
115 // auto attr = dataset.createAttribute(name, datatype, H5::DataSpace{});
116 // attr.write(datatype, &value);
117 //}
118 template <typename T>
119 void hdf5_add_scalar_attr(
120  const H5::Group& group, const std::string& name, const T& value) {
121  const auto& datatype = hdf5::type<T>();
122  auto attr = group.createAttribute(name, datatype, H5::DataSpace{});
123  attr.write(datatype, &value);
124 }
125 
126 HDF5Writer::HDF5Writer(const fs::path& filename)
127  : file_(filename.string(), H5F_ACC_TRUNC)
128 {}
129 
130 void HDF5Writer::operator()(
131  int num, double impact_param, const Event& event) const {
132  const auto& grid1 = event.density_grid();
133  const auto& grid2 = event.TAB_grid();
134 
135  // The dataset name is a prefix plus the event number.
136  const std::string gp_name{"/event_" + std::to_string(num)};
137  const std::string sd_name{gp_name + "/matter_density"};
138  const std::string tab_name{gp_name + "/Ncoll_density"};
139 
140  // create a group called event_i
141  auto group = H5::Group(file_.createGroup(gp_name));
142  // Write event attributes.
143  hdf5_add_scalar_attr(group, "b", impact_param);
144  hdf5_add_scalar_attr(group, "npart", event.npart());
145  hdf5_add_scalar_attr(group, "ncoll", event.ncoll());
146  hdf5_add_scalar_attr(group, "mult", event.multiplicity());
147  hdf5_add_scalar_attr(group, "dxy", event.dxy());
148  hdf5_add_scalar_attr(group, "deta", event.deta());
149  hdf5_add_scalar_attr(group, "Ny", grid1.shape()[0]);
150  hdf5_add_scalar_attr(group, "Nx", grid1.shape()[1]);
151  hdf5_add_scalar_attr(group, "Nz", grid1.shape()[2]);
152  for (const auto& ecc : event.eccentricity())
153  hdf5_add_scalar_attr(group, "e" + std::to_string(ecc.first), ecc.second);
154  for (const auto& psi : event.participant_plane())
155  hdf5_add_scalar_attr(group, "psi" + std::to_string(psi.first), psi.second);
156 
158  // Define HDF5 datatype and dataspace to match the density (3D) grid.
159 
160  const auto& datatype1 = hdf5::type<Event::Grid3D::element>();
161  std::array<hsize_t, Event::Grid3D::dimensionality> shape1;
162  std::copy(grid1.shape(), grid1.shape() + shape1.size(), shape1.begin());
163  auto dataspace1 = hdf5::make_dataspace(shape1);
164 
165  // Set dataset storage properties.
166  H5::DSetCreatPropList proplist1{};
167  // Set chunk size to the entire grid. For typical grid sizes (~100x100), this
168  // works out to ~80 KiB, which is pretty optimal. Anyway, it makes logical
169  // sense to chunk this way, since chunks must be read contiguously and there's
170  // no reason to read a partial grid.
171  proplist1.setChunk(shape1.size(), shape1.data());
172  // Set gzip compression level. 4 is the default in h5py.
173  proplist1.setDeflate(4);
174 
175  // Create the new dataset and write the grid for matter density
176  auto dataset1 = file_.createDataSet(sd_name, datatype1, dataspace1, proplist1);
177  dataset1.write(grid1.data(), datatype1);
178 
180  // Define HDF5 datatype and dataspace to match the Ncoll (2D) grid.
181  const auto& datatype2 = hdf5::type<Event::Grid::element>();
182  std::array<hsize_t, Event::Grid::dimensionality> shape2;
183  std::copy(grid2.shape(), grid2.shape() + shape2.size(), shape2.begin());
184  auto dataspace2 = hdf5::make_dataspace(shape2);
185 
186  // Set dataset storage properties.
187  H5::DSetCreatPropList proplist2{};
188  // Set chunk size to the entire grid. For typical grid sizes (~100x100), this
189  // works out to ~80 KiB, which is pretty optimal. Anyway, it makes logical
190  // sense to chunk this way, since chunks must be read contiguously and there's
191  // no reason to read a partial grid.
192  proplist1.setChunk(shape2.size(), shape2.data());
193  // Set gzip compression level. 4 is the default in h5py.
194  proplist1.setDeflate(4);
195 
196  // Create the new dataset and write the grid for matter density
197  auto dataset2 = file_.createDataSet(tab_name, datatype2, dataspace2, proplist2);
198  dataset2.write(grid2.data(), datatype2);
199 }
200 
201 #endif // TRENTO_HDF5
202 
203 } // unnamed namespace
204 
205 Output::Output(const VarMap& var_map){
206  // Determine the required width (padding) of the event number. For example if
207  // there are 10 events, the numbers are 0-9 and so no padding is necessary.
208  // However given 11 events, the numbers are 00-10 with padded 00, 01, ...
209  auto nevents = var_map["number-events"].as<int>();
210  auto width = static_cast<int>(std::ceil(std::log10(nevents)));
211 
212  // Write to stdout unless the quiet option was specified.
213  if (!var_map["quiet"].as<bool>()) {
214  writers_.emplace_back(
215  [width](int num, double impact_param, const Event& event) {
216  write_stream(std::cout, width, num, impact_param, event);
217  }
218  );
219  }
220 
221  // Possibly write to text or HDF5 files.
222  if (var_map.count("output")) {
223  const auto& output_path = var_map["output"].as<fs::path>();
224  if (hdf5::filename_is_hdf5(output_path)) {
225 #ifdef TRENTO_HDF5
226  if (fs::exists(output_path) && !fs::is_empty(output_path))
227  throw std::runtime_error{"file '" + output_path.string() +
228  "' exists, will not overwrite"};
229  writers_.emplace_back(HDF5Writer{output_path});
230 #else
231  throw std::runtime_error{"HDF5 output was not compiled"};
232 #endif // TRENTO_HDF5
233  } else {
234  // Text files are all written into a single directory. Require the
235  // directory to be initially empty to avoid accidental overwriting and/or
236  // spewing files into an already-used location. If the directory does not
237  // exist, create it.
238  if (fs::exists(output_path)) {
239  if (!fs::is_empty(output_path)) {
240  throw std::runtime_error{"output directory '" + output_path.string() +
241  "' must be empty"};
242  }
243  } else {
244  fs::create_directories(output_path);
245  }
246  auto header = !var_map["no-header"].as<bool>();
247  writers_.emplace_back(
248  [output_path, width, header](
249  int num, double impact_param, const Event& event) {
250  write_text_file(output_path, width, num, impact_param, event, header);
251  }
252  );
253  }
254  }
255 }
256 
257 } // namespace trento