Version: SMASH-3.3
outputformatter.h
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2024-2025
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
10 #ifndef SRC_INCLUDE_SMASH_OUTPUTFORMATTER_H_
11 #define SRC_INCLUDE_SMASH_OUTPUTFORMATTER_H_
12 
13 #include <functional>
14 #include <map>
15 #include <optional>
16 #include <sstream>
17 #include <stdexcept>
18 #include <string>
19 #include <utility>
20 #include <vector>
21 
22 #include "smash/particledata.h"
23 #include "smash/particles.h"
24 
25 namespace smash {
26 
31 struct ToASCII {
33  using type = std::string;
34 
36  static constexpr std::optional<char> separator{' '};
37 
39  static constexpr std::optional<char> end_of_line{'\n'};
40 
46  type as_integer(int value) const { return std::to_string(value); }
47 
64  type as_double(double value) {
65  constexpr size_t kBufferSize = 13;
66  char buffer[kBufferSize];
67  const auto length = std::snprintf(buffer, kBufferSize, "%g", value);
68  assert(static_cast<size_t>(length) < kBufferSize);
69  assert(length > 0);
70  return std::string{buffer, buffer + length};
71  }
72 
87  type as_precise_double(double value) {
88  constexpr size_t kBufferSize = 16;
89  char buffer[kBufferSize];
90  const auto length = std::snprintf(buffer, kBufferSize, "%.9g", value);
91  assert(static_cast<size_t>(length) < kBufferSize);
92  assert(length > 0);
93  return std::string{buffer, buffer + length};
94  }
95 
102  type as_string(const std::string& str) const { return str; }
103 };
104 
109 class ToBinary {
110  public:
112  using type = std::vector<char>;
113 
115  static constexpr std::optional<char> separator = std::nullopt;
116 
118  static constexpr std::optional<char> end_of_line = std::nullopt;
119 
126  type as_integer(int32_t value) const { return as_binary_data(value); }
127 
134  type as_double(double value) const { return as_binary_data(value); }
135 
145  type as_precise_double(double value) const { return as_double(value); }
146 
153  type as_string(const std::string& str) const {
154  return type(str.begin(), str.end());
155  }
156 
157  private:
164  template <typename T>
165  type as_binary_data(T value) const {
166  type binary_data(sizeof(T));
167  std::memcpy(binary_data.data(), &value, sizeof(T));
168  return binary_data;
169  }
170 };
171 
190 template <typename Converter,
191  std::enable_if_t<std::is_same_v<Converter, ToASCII> ||
192  std::is_same_v<Converter, ToBinary>,
193  bool> = true>
195  public:
209  explicit OutputFormatter(const std::vector<std::string>& in_quantities)
210  : quantities_(in_quantities) {
212  for (const std::string& quantity : quantities_) {
213  if (quantity == "t") {
214  getters_.push_back([this](const ParticleData& in) {
215  return this->converter_.as_double(in.position()[0]);
216  });
217  } else if (quantity == "x") {
218  getters_.push_back([this](const ParticleData& in) {
219  return this->converter_.as_double(in.position()[1]);
220  });
221  } else if (quantity == "y") {
222  getters_.push_back([this](const ParticleData& in) {
223  return this->converter_.as_double(in.position()[2]);
224  });
225  } else if (quantity == "z") {
226  getters_.push_back([this](const ParticleData& in) {
227  return this->converter_.as_double(in.position()[3]);
228  });
229  } else if (quantity == "mass") {
230  getters_.push_back([this](const ParticleData& in) {
231  return this->converter_.as_double(in.effective_mass());
232  });
233  } else if (quantity == "p0") {
234  getters_.push_back([this](const ParticleData& in) {
235  return this->converter_.as_precise_double(in.momentum()[0]);
236  });
237  } else if (quantity == "px") {
238  getters_.push_back([this](const ParticleData& in) {
239  return this->converter_.as_precise_double(in.momentum()[1]);
240  });
241  } else if (quantity == "py") {
242  getters_.push_back([this](const ParticleData& in) {
243  return this->converter_.as_precise_double(in.momentum()[2]);
244  });
245  } else if (quantity == "pz") {
246  getters_.push_back([this](const ParticleData& in) {
247  return this->converter_.as_precise_double(in.momentum()[3]);
248  });
249  } else if (quantity == "pdg") {
250  getters_.push_back([this](const ParticleData& in) {
251  return this->converter_.as_integer(in.pdgcode().get_decimal());
252  });
253  } else if (quantity == "ID" || quantity == "id") {
254  getters_.push_back([this](const ParticleData& in) {
255  return this->converter_.as_integer(in.id());
256  });
257  } else if (quantity == "charge") {
258  getters_.push_back([this](const ParticleData& in) {
259  return this->converter_.as_integer(in.type().charge());
260  });
261  } else if (quantity == "ncoll") {
262  getters_.push_back([this](const ParticleData& in) {
263  return this->converter_.as_integer(
265  });
266  } else if (quantity == "form_time") {
267  getters_.push_back([this](const ParticleData& in) {
268  return this->converter_.as_double(in.formation_time());
269  });
270  } else if (quantity == "xsecfac") {
271  getters_.push_back([this](const ParticleData& in) {
272  return this->converter_.as_double(in.xsec_scaling_factor());
273  });
274  } else if (quantity == "proc_id_origin") {
275  getters_.push_back([this](const ParticleData& in) {
276  return this->converter_.as_integer(in.get_history().id_process);
277  });
278  } else if (quantity == "proc_type_origin") {
279  getters_.push_back([this](const ParticleData& in) {
280  return this->converter_.as_integer(
281  static_cast<int>(in.get_history().process_type));
282  });
283  } else if (quantity == "time_last_coll") {
284  getters_.push_back([this](const ParticleData& in) {
285  return this->converter_.as_double(
287  });
288  } else if (quantity == "pdg_mother1") {
289  getters_.push_back([this](const ParticleData& in) {
290  return this->converter_.as_integer(in.get_history().p1.get_decimal());
291  });
292  } else if (quantity == "pdg_mother2") {
293  getters_.push_back([this](const ParticleData& in) {
294  return this->converter_.as_integer(in.get_history().p2.get_decimal());
295  });
296  } else if (quantity == "baryon_number") {
297  getters_.push_back([this](const ParticleData& in) {
298  return this->converter_.as_integer(in.pdgcode().baryon_number());
299  });
300  } else if (quantity == "strangeness") {
301  getters_.push_back([this](const ParticleData& in) {
302  return this->converter_.as_integer(in.pdgcode().strangeness());
303  });
304  } else if (quantity == "spin0") {
305  getters_.push_back([this](const ParticleData& in) {
306  return this->converter_.as_double(in.spin_vector()[0]);
307  });
308  } else if (quantity == "spinx") {
309  getters_.push_back([this](const ParticleData& in) {
310  return this->converter_.as_double(in.spin_vector()[1]);
311  });
312  } else if (quantity == "spiny") {
313  getters_.push_back([this](const ParticleData& in) {
314  return this->converter_.as_double(in.spin_vector()[2]);
315  });
316  } else if (quantity == "spinz") {
317  getters_.push_back([this](const ParticleData& in) {
318  return this->converter_.as_double(in.spin_vector()[3]);
319  });
320  } else if (quantity == "0") { // for OSCAR1999
321  getters_.push_back([this]([[maybe_unused]] const ParticleData& in) {
322  return this->converter_.as_integer(0);
323  });
324  } else if (quantity == "tau") {
325  getters_.push_back([this](const ParticleData& in) {
326  return this->converter_.as_double(in.hyperbolic_time());
327  });
328  } else if (quantity == "eta" || quantity == "eta_s") {
329  getters_.push_back([this](const ParticleData& in) {
330  return this->converter_.as_double(in.spatial_rapidity());
331  });
332  } else if (quantity == "mt") {
333  getters_.push_back([this](const ParticleData& in) {
334  return this->converter_.as_double(in.transverse_mass());
335  });
336  } else if (quantity == "Rap" || quantity == "y_rap") {
337  // "Rap" is used for compatibility with vHLLE
338  getters_.push_back([this](const ParticleData& in) {
339  return this->converter_.as_double(in.rapidity());
340  });
341  } else if (quantity == "perturbative_weight") {
342  getters_.push_back([this](const ParticleData& in) {
343  return this->converter_.as_double(in.perturbative_weight());
344  });
345  }
346  }
347  }
348 
362  std::size_t compute_single_size(const ParticleData& sample) const {
363  const std::size_t n = getters_.size();
364  if (n == 0) {
365  return 0;
366  }
367  std::size_t size = 0;
368  for (const auto& getter : getters_) {
369  const typename Converter::type tmp = getter(sample);
370  size += tmp.size();
371  }
372 
373  if (Converter::separator.has_value()) {
374  size += (n - 1);
375  }
376 
377  if (Converter::end_of_line.has_value()) {
378  size += 1;
379  }
380 
381  return size;
382  }
392  typename Converter::type single_particle_data(const ParticleData& p) const {
393  typename Converter::type chunk{};
394  append_to_buffer(p, chunk);
395  return chunk;
396  }
397 
420  template <class Range,
421  std::enable_if_t<std::is_same_v<Range, Particles> ||
422  std::is_same_v<Range, ParticleList>,
423  bool> = true>
424  typename Converter::type particles_data_chunk(const Range& particles) const {
425  typename Converter::type chunk{};
426  if (particles.size() == 0)
427  return chunk;
428 
429  chunk.reserve(particles.size() * compute_single_size(particles.front()));
430 
431  for (const ParticleData& p : particles) {
432  append_to_buffer(p, chunk);
433  }
434  return chunk;
435  }
436 
446  typename Converter::type quantities_line() const {
447  typename Converter::type out{};
448 
449  // Educated size guess to reduce the number of reallocations in push_backs:
450  out.reserve(quantities_.size() * 5);
451 
452  for (const auto& string_name : quantities_) {
453  if constexpr (Converter::separator) {
454  if (!out.empty()) {
455  out.push_back(*Converter::separator);
456  }
457  }
458  typename Converter::type name = converter_.as_string(string_name);
459  out.insert(out.end(), name.begin(), name.end());
460  }
461  if constexpr (Converter::end_of_line) {
462  out.push_back(*Converter::end_of_line);
463  }
464  return out;
465  }
466 
476  typename Converter::type unit_line() const {
477  typename Converter::type out{};
478 
479  // Educated size guess to reduce the number of reallocations in push_backs:
480  out.reserve(quantities_.size() * 5);
481 
482  for (const auto& key : quantities_) {
483  if constexpr (Converter::separator) {
484  if (!out.empty()) {
485  out.push_back(*Converter::separator);
486  }
487  }
488  const auto& unit = converter_.as_string(units_.at(key));
489  out.insert(out.end(), unit.begin(), unit.end());
490  }
491  if constexpr (Converter::end_of_line) {
492  out.push_back(*Converter::end_of_line);
493  }
494  return out;
495  }
496 
501  struct UnknownQuantity : public std::invalid_argument {
502  using std::invalid_argument::invalid_argument;
503  };
504 
509  struct RepeatedQuantity : public std::invalid_argument {
510  using std::invalid_argument::invalid_argument;
511  };
512 
517  struct AliasesQuantity : public std::invalid_argument {
518  using std::invalid_argument::invalid_argument;
519  };
520 
521  private:
523  Converter converter_{};
524 
526  std::vector<std::string> quantities_{};
527 
529  std::vector<std::function<typename Converter::type(const ParticleData&)>>
531 
533  const std::map<std::string, std::string> units_ = {
534  {"t", "fm"},
535  {"x", "fm"},
536  {"y", "fm"},
537  {"z", "fm"},
538  {"mass", "GeV"},
539  {"p0", "GeV"},
540  {"px", "GeV"},
541  {"py", "GeV"},
542  {"pz", "GeV"},
543  {"pdg", "none"},
544  {"ID", "none"},
545  {"id", "none"}, // used in OSCAR1999
546  {"charge", "e"},
547  {"ncoll", "none"},
548  {"form_time", "fm"},
549  {"xsecfac", "none"},
550  {"proc_id_origin", "none"},
551  {"proc_type_origin", "none"},
552  {"time_last_coll", "fm"},
553  {"pdg_mother1", "none"},
554  {"pdg_mother2", "none"},
555  {"baryon_number", "none"},
556  {"strangeness", "none"},
557  {"0", "0"}, // for OSCAR1999;
558  {"tau", "fm"},
559  {"eta", "none"},
560  {"eta_s", "none"},
561  {"mt", "GeV"},
562  {"Rap", "none"},
563  {"y_rap", "none"},
564  {"spin0", "none"},
565  {"spinx", "none"},
566  {"spiny", "none"},
567  {"spinz", "none"},
568  {"perturbative_weight", "none"}};
569 
571  const std::vector<std::pair<std::string, std::string>> aliases_ = {
572  {"ID", "id"}, {"eta_s", "eta"}, {"y_rap", "Rap"}};
573 
576  if (quantities_.empty()) {
577  throw std::invalid_argument(
578  "OutputFormatter: Empty quantities handed over to the class.");
579  }
580  std::string repeated{};
581  for (const std::string& quantity : quantities_) {
582  if (std::count(quantities_.begin(), quantities_.end(), quantity) > 1) {
583  repeated += "'" + quantity + "',";
584  }
585  }
586  if (!repeated.empty()) {
587  throw RepeatedQuantity("Repeated \"Quantities\": " + repeated +
588  " please fix the configuration file.\n");
589  }
590  std::string unknown{};
591  for (const std::string& quantity : quantities_) {
592  if (units_.count(quantity) == 0) {
593  unknown += "'" + quantity + "',";
594  }
595  }
596  if (!unknown.empty()) {
597  throw UnknownQuantity("Unknown \"Quantities\": " + unknown +
598  " please fix the configuration file.\n");
599  }
600  for (const auto& alias : aliases_) {
601  const bool first_quantity_is_given =
602  std::find(quantities_.begin(), quantities_.end(), alias.first) !=
603  quantities_.end();
604  const bool second_quantity_is_given =
605  std::find(quantities_.begin(), quantities_.end(), alias.second) !=
606  quantities_.end();
607  if (first_quantity_is_given && second_quantity_is_given) {
608  throw AliasesQuantity(
609  "Both '" + alias.first + "' and '" + alias.second +
610  "' cannot be provided in the \"Quantities\" key together. Please, "
611  "fix the configuration file.");
612  }
613  }
614  }
615 
628  void append_to_buffer(const ParticleData& particle,
629  typename Converter::type& buffer) const {
630  for (std::size_t i = 0; i < getters_.size(); ++i) {
631  if constexpr (Converter::separator) {
632  if (i > 0)
633  buffer.push_back(*Converter::separator);
634  }
635  auto data = getters_[i](particle);
636  buffer.insert(buffer.end(), std::make_move_iterator(data.begin()),
637  std::make_move_iterator(data.end()));
638  }
639  if constexpr (Converter::end_of_line) {
640  buffer.push_back(*Converter::end_of_line);
641  }
642  }
643 };
644 
645 namespace details {
646 
691 template <typename Converter, class Range,
692  std::enable_if_t<std::is_same_v<Range, Particles> ||
693  std::is_same_v<Range, ParticleList>,
694  bool> = true>
696  const Range& particles, const OutputFormatter<Converter>& formatter,
697  std::function<void(const typename Converter::type&)> write,
698  std::size_t max_buffer_bytes = 1'000'000'000) {
699  if (particles.size() == 0)
700  return;
701 
702  const std::size_t bytes_per_particle =
703  formatter.compute_single_size(particles.front());
704 
705  if (2.0 * bytes_per_particle > max_buffer_bytes) {
706  throw std::runtime_error(
707  "write_in_chunk_impl: the estimated size of a single particle line "
708  "exceeds half of the configured max_buffer_bytes.\n"
709  "This effectively means only one particle would fit per chunk, "
710  "which defeats the purpose of chunked writing.\n"
711  "Increase max_buffer_bytes to at least twice the particle line size "
712  "to use this function correctly.");
713  }
714 
715  if (particles.size() * bytes_per_particle <= max_buffer_bytes) {
716  write(formatter.particles_data_chunk(particles));
717  return;
718  }
719 
720  using Buffer = typename Converter::type;
721  Buffer buffer;
722  buffer.reserve(max_buffer_bytes);
723  std::size_t current_size = 0;
724 
725  for (const auto& particle : particles) {
726  Buffer line = formatter.single_particle_data(particle);
727  const std::size_t line_size = line.size();
728  if (current_size + line_size > max_buffer_bytes) {
729  write(buffer);
730  buffer.clear();
731  current_size = 0;
732  }
733  buffer.insert(buffer.end(), std::make_move_iterator(line.begin()),
734  std::make_move_iterator(line.end()));
735  current_size += line_size;
736  }
737 
738  if (!buffer.empty()) {
739  write(buffer);
740  }
741 }
742 } // namespace details
743 
751 template <typename Converter, class Range,
752  std::enable_if_t<std::is_same_v<Range, Particles> ||
753  std::is_same_v<Range, ParticleList>,
754  bool> = true>
756  const Range& particles, const OutputFormatter<Converter>& formatter,
757  std::function<void(const typename Converter::type&)> write) {
758  details::write_in_chunk_impl(particles, formatter, write);
759 }
760 
761 } // namespace smash
762 
763 #endif // SRC_INCLUDE_SMASH_OUTPUTFORMATTER_H_
A general-purpose formatter for output, supporting both ASCII and binary formats.
void append_to_buffer(const ParticleData &particle, typename Converter::type &buffer) const
Appends the Converter::type representation of a single particle to the provided buffer.
Converter converter_
Member to convert data into the correct output format.
Converter::type quantities_line() const
Produces the line with quantities for the header of the output file.
const std::vector< std::pair< std::string, std::string > > aliases_
Map with known quantities and corresponding units.
std::vector< std::string > quantities_
List of quantities to be written.
Converter::type single_particle_data(const ParticleData &p) const
Produces a line of data representing a single particle suitable for writing to an output file.
std::vector< std::function< typename Converter::type(const ParticleData &)> > getters_
List of getters to extract output data from the ParticleData object.
std::size_t compute_single_size(const ParticleData &sample) const
Computes and returns the total size of the formatted representation of a single particle using all re...
void validate_quantities()
Checks whether the quantities requested are known and unique.
OutputFormatter(const std::vector< std::string > &in_quantities)
Creates the formatter.
Converter::type particles_data_chunk(const Range &particles) const
Builds multiple particle lines for a range of particles by appending the Converter::type representati...
Converter::type unit_line() const
Produces the line with units for the header of the output file.
const std::map< std::string, std::string > units_
Map with known quantities and corresponding units.
ParticleData contains the dynamic information of a certain particle.
Definition: particledata.h:59
PdgCode pdgcode() const
Get the pdgcode of the particle.
Definition: particledata.h:88
const ParticleType & type() const
Get the type of the particle.
Definition: particledata.h:132
double xsec_scaling_factor(double delta_time=0.) const
Return the cross section scaling factor at a given time.
double hyperbolic_time() const
Particle (hyperbolic time)
Definition: particledata.h:404
const FourVector & momentum() const
Get the particle's 4-momentum.
Definition: particledata.h:171
double formation_time() const
Get the absolute formation time of the particle.
Definition: particledata.h:249
double effective_mass() const
Get the particle's effective mass.
Definition: particledata.cc:25
int32_t id() const
Get the id of the particle.
Definition: particledata.h:77
const FourVector & spin_vector() const
Get the mean spin 4-vector (Pauli–Lubanski vector) of the particle (const reference,...
Definition: particledata.h:365
double perturbative_weight() const
Get the perturbative weight.
Definition: particledata.h:421
HistoryData get_history() const
Get history information.
Definition: particledata.h:143
double rapidity() const
Particle momentum rapidity .
Definition: particledata.h:410
double transverse_mass() const
Particle .
Definition: particledata.h:408
double spatial_rapidity() const
Particle spacetime rapidity .
Definition: particledata.h:406
const FourVector & position() const
Get the particle's position in Minkowski space.
Definition: particledata.h:217
int32_t charge() const
The charge of the particle.
Definition: particletype.h:189
int baryon_number() const
Definition: pdgcode.h:388
int strangeness() const
Definition: pdgcode.h:611
int32_t get_decimal() const
Definition: pdgcode.h:837
Structure to convert a given value into binary format, such that all methods return a std::vector<cha...
type as_binary_data(T value) const
Template method to convert numbers into binary format.
type as_double(double value) const
Converts a double to binary format.
type as_precise_double(double value) const
Converts a double to binary format, intended for precise representation.
static constexpr std::optional< char > end_of_line
Endline char is not needed for Binary format.
static constexpr std::optional< char > separator
separator char is not needed for Binary format
type as_integer(int32_t value) const
Converts an integer to binary format.
std::vector< char > type
Return type of this converter.
type as_string(const std::string &str) const
Converts a string to binary format.
void write_in_chunk_impl(const Range &particles, const OutputFormatter< Converter > &formatter, std::function< void(const typename Converter::type &)> write, std::size_t max_buffer_bytes=1 '000 '000 '000)
Writes particle data in multiple chunks if the total buffer size exceeds a predefined maximum.
constexpr int p
Proton.
constexpr int n
Neutron.
Definition: action.h:24
void write_in_chunk(const Range &particles, const OutputFormatter< Converter > &formatter, std::function< void(const typename Converter::type &)> write)
User-facing wrapper for chunked particle writing.
std::string to_string(ThermodynamicQuantity quantity)
Convert a ThermodynamicQuantity enum value to its corresponding string.
Definition: stringify.cc:26
double time_last_collision
Time of the last action (excluding walls), time of kinetic freeze_out for HBT analysis this time shou...
Definition: particledata.h:44
int32_t id_process
id of the last action
Definition: particledata.h:35
PdgCode p2
PdgCode of the second parent particles.
Definition: particledata.h:48
PdgCode p1
PdgCode of the first parent particles.
Definition: particledata.h:46
int32_t collisions_per_particle
Collision counter per particle, zero only for initially present particles.
Definition: particledata.h:33
ProcessType process_type
type of the last action
Definition: particledata.h:37
Thrown when the synonym quantities are used.
Thrown when the same quantity is repeated.
Thrown when a not existing quantity is used.
Structure to convert a given value into ASCII format, such that all methods return a std::string.
type as_integer(int value) const
Converts an integer.
std::string type
Return type of this converter.
static constexpr std::optional< char > end_of_line
Character used to mark the end of a record in ASCII output.
static constexpr std::optional< char > separator
Character used to separate fields in ASCII output.
type as_double(double value)
Converts a double with 6 digits of precision.
type as_precise_double(double value)
Converts a double with 9 digits of precision.
type as_string(const std::string &str) const
Because ToASCII converts into strings, this simply returns the string itself.