Version: SMASH-3.4
outputformatter.h
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2024-2026
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 
27 /**
28  * Structure to convert a given value into ASCII format, such that all methods
29  * return a \c std::string.
30  */
31 struct ToASCII {
32  /// Return type of this converter.
33  using type = std::string;
34 
35  /// Character used to separate fields in ASCII output.
36  static constexpr std::optional<char> separator{' '};
37 
38  /// Character used to mark the end of a record in ASCII output.
39  static constexpr std::optional<char> end_of_line{'\n'};
40 
41  /**
42  * Converts an integer.
43  *
44  * \param[in] value number to convert
45  */
46  type as_integer(int value) const { return std::to_string(value); }
47 
48  /**
49  * Converts a double with 6 digits of precision.
50  *
51  * \note The usage of \c std::snprintf over \c std::ostringstream is because
52  * of performance reasons (in C++20 this will be replaced with \c std::format
53  * which is even better). The returned string is constructed from the buffer
54  * in a way to exclude the terminating null character from the buffer. The
55  * hard-coded buffer size should fit any number, but a couple of assert are
56  * used to possibly investigate unexpected behaviour.
57  *
58  * \warning Since the buffer size is needed twice, it makes sense to store it
59  * in a variable. However, cpplint complains if the variable name is not
60  * starting with \c k followed by CamelCase.
61  *
62  * \param[in] value number to convert
63  */
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 
73  /**
74  * Converts a double with 9 digits of precision.
75  *
76  * \see \c as_double for further information.
77  *
78  * \note The duplication of the code of the \c as_double method is done on
79  * purpose as naively extracting a function passing the \c std::snprintf
80  * format string as parameter would trigger a warning in compilation (the
81  * format has to be a literal in order to be checked by the compiler at
82  * compile time) and the effort to avoid this is not worth now, especially
83  * since this code will be changed anyhow when using C++20.
84  *
85  * \param[in] value number to convert
86  */
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 
96  /**
97  * Because %ToASCII converts into strings, this simply returns the string
98  * itself.
99  *
100  * \param[inout] str string to be written
101  */
102  type as_string(const std::string& str) const { return str; }
103 };
104 
105 /**
106  * Structure to convert a given value into binary format, such that all methods
107  * return a \c std::vector<char>.
108  */
109 class ToBinary {
110  public:
111  /// Return type of this converter.
112  using type = std::vector<char>;
113 
114  /// separator char is not needed for Binary format
115  static constexpr std::optional<char> separator = std::nullopt;
116 
117  /// Endline char is not needed for Binary format
118  static constexpr std::optional<char> end_of_line = std::nullopt;
119 
120  /**
121  * Converts an integer to binary format.
122  *
123  * \param[in] value number to convert
124  * \return a vector of char representing the binary format of the integer
125  */
126  type as_integer(int32_t value) const { return as_binary_data(value); }
127 
128  /**
129  * Converts a double to binary format.
130  *
131  * \param[in] value number to convert
132  * \return a vector of char representing the binary format of the double
133  */
134  type as_double(double value) const { return as_binary_data(value); }
135 
136  /**
137  * Converts a double to binary format, intended for precise representation.
138  * Note that for binary output there is no difference between this and the
139  * \c ToBinary::as_double method, but this method has still to be introduced
140  * to allow other classes to get the converter class as a template parameter.
141  *
142  * \param[in] value number to convert
143  * \return a vector of char representing the binary format of the double
144  */
145  type as_precise_double(double value) const { return as_double(value); }
146 
147  /**
148  * Converts a string to binary format.
149  *
150  * \param[in] str string to convert
151  * \return a vector of char representing the binary format of the string
152  */
153  type as_string(const std::string& str) const {
154  return type(str.begin(), str.end());
155  }
156 
157  private:
158  /**
159  * Template method to convert numbers into binary format.
160  *
161  * \param[in] value number to convert
162  * \return a vector of char representing the binary format of the number
163  */
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 
172 /**
173  * A general-purpose formatter for output, supporting both ASCII and binary
174  * formats.
175  *
176  * This class allows the output of particle data in a flexible and configurable
177  * manner, either in human-readable ASCII format or compact binary format. It
178  * uses a template parameter `Converter` to determine the desired output format,
179  * which must conform to the interface of either \c ToASCII or \c ToBinary.
180  *
181  * New quantities can be added for output by:
182  * 1. Adding their corresponding getter to the constructor, which extracts the
183  * value from a \c ParticleData instance.
184  * 2. Adding the proper key-value pair to the \c units_ map, specifying the unit
185  * of the quantity.
186  *
187  * \tparam Converter The desired output format. At the moment it must be either
188  * \c ToASCII or `ToBinary`.
189  */
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:
196  /**
197  * Creates the formatter. This reduces the number of literal strings flying
198  * around in the codebase, and since this is called only once per output file,
199  * in the beginning of the run, there is almost no efficiency lost compared to
200  * having fixed strings.
201  *
202  * \param[in] in_quantities list of quantities to be output.
203  *
204  * \throw std::invalid_argument if the list of quantities is empty
205  * \throw std::invalid_argument if unknown quantities exist in the list
206  * \throw std::invalid_argument if incompatible quantities exist in the list
207  * \throw std::invalid_argument if there are repeated quantities
208  */
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 
349  /**
350  * Computes and returns the total size of the formatted representation
351  * of a single particle using all registered getters.
352  *
353  * The total size consists of:
354  * - the sum of the sizes of all field strings (from each getter),
355  * - (n - 1) separator characters if a separator is defined,
356  * since separators appear only between fields, not after the last one,
357  * - one end-of-line character if an end-of-line marker is defined.
358  *
359  * \param[in] sample A representative particle used to compute the size.
360  * \return Total size of the formatted representation of one particle.
361  */
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  }
383  /**
384  * Produces a line of data representing a single particle
385  * suitable for writing to an output file.
386  *
387  * \param[in] p Particle whose information is to be written.
388  * \return A buffer containing the formatted data.
389  *
390  * \see append_to_buffer
391  */
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 
398  /**
399  * Builds multiple particle lines for a range of particles by appending the
400  * Converter::type representation of each particle into a single buffer.
401  *
402  * The resulting buffer reflects exactly what the Converter defines(
403  * per-record separators and end-of-line markers, if any). No additional
404  * formatting is applied beyond what the Converter specifies.
405  *
406  * \tparam Range Container type — constrained to `Particles` or
407  * `ParticleList`.
408  * \param[in] particles Container of particles whose data should be
409  * formatted and added to the buffer
410  * \return Converter::type A buffer containing the concatenated
411  * representation of all particles in the block.
412  *
413  * \note Capacity is reserved using a size estimate based on the first
414  * element.
415  *
416  * \see append_to_buffer
417  *
418  * \return Converter::type
419  */
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 
437  /**
438  * Produces the line with quantities for the header of the output file.
439  *
440  * Appends each quantity name to the output buffer, separating them
441  * by the separator character if it is defined in the Converter. End of line
442  * character is added last if defined.
443  *
444  * \return Converter::type
445  */
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 
467  /**
468  * Produces the line with units for the header of the output file.
469  *
470  * Looks up each quantity’s unit in the units_ map and appends it to the
471  * output buffer. Each unit is separated by the separator character if
472  * defined. End of line character is added last if defined.
473  *
474  * \return Converter::type
475  */
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 
497  /**
498  * \ingroup exception
499  * Thrown when a not existing quantity is used.
500  */
501  struct UnknownQuantity : public std::invalid_argument {
502  using std::invalid_argument::invalid_argument;
503  };
504 
505  /**
506  * \ingroup exception
507  * Thrown when the same quantity is repeated.
508  */
509  struct RepeatedQuantity : public std::invalid_argument {
510  using std::invalid_argument::invalid_argument;
511  };
512 
513  /**
514  * \ingroup exception
515  * Thrown when the synonym quantities are used.
516  */
517  struct AliasesQuantity : public std::invalid_argument {
518  using std::invalid_argument::invalid_argument;
519  };
520 
521  private:
522  /// Member to convert data into the correct output format.
523  Converter converter_{};
524 
525  /// List of quantities to be written.
526  std::vector<std::string> quantities_{};
527 
528  /// List of getters to extract output data from the `ParticleData` object.
529  std::vector<std::function<typename Converter::type(const ParticleData&)>>
531 
532  /// Map with known quantities and corresponding units.
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 
570  /// Map with known quantities and corresponding units.
571  const std::vector<std::pair<std::string, std::string>> aliases_ = {
572  {"ID", "id"}, {"eta_s", "eta"}, {"y_rap", "Rap"}};
573 
574  /// Checks whether the quantities requested are known and unique
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 
616  /**
617  * Appends the Converter::type representation of a single particle to the
618  * provided buffer.
619  *
620  * The buffer is used exactly as provided by the caller; no assumptions or
621  * modifications are made to its initial state or capacity.
622  *
623  * \param[in] particle Particle whose information is to be appended.
624  * \param[out] buffer Destination buffer to which the converted data is
625  * appended.
626  */
627 
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 detail {
646 
647 /**
648  * \brief Writes particle data in multiple chunks if the total buffer size
649  * exceeds a predefined maximum.
650  *
651  * This method avoids creating a single excessively large binary buffer when
652  * writing many particles at once. Instead, it splits the write into several
653  * smaller chunks. This can prevent excessive memory usage and improve
654  * stability on systems or filesystems that may have trouble with very large
655  * write calls.
656  *
657  * The maximum buffer size is currently set to 1 GB (10^9 bytes), but this can
658  * be adapted in the future if needed. If the total data size of the particle
659  * block is below this threshold, the method simply delegates to the
660  * `write` function which should perform a single write call.
661  *
662  * Otherwise, the data is accumulated particle by particle until the buffer
663  * reaches the threshold. The buffer is then given to the write function which
664  * should flush to disk.
665  *
666  * \note This utility does not strictly belong to this file. At the moment,
667  * the objects using it do not have a clean hierarchy that would allow
668  * both OscarOutput and BinaryOutput to inherit a shared implementation,
669  * hence it lives here temporarily.
670  *
671  * \todo Once the hierarchy is cleaned up, move this into a common base class
672  * that OscarOutput and BinaryOutput inherit from.
673  *
674  * \tparam Converter Converter used by OutputFormatter to produce the
675  * buffer type (must define Converter::type).
676  * \tparam Range Container type — enforced to be either `Particles` or
677  * `ParticleList`.
678  * \param[in] particles Container of particles whose particle_line
679  * representation is to be written.
680  * \param[in] formatter Formatter responsible for converting particles into
681  * the corresponding Converter::type buffer representation.
682  * \param[in] write Callable that receives each filled buffer chunk and
683  * performs the actual write to the underlying output (e.g. file).
684  * \param[in] max_buffer_bytes Maximum buffer size in bytes before the
685  * accumulated data is flushed via \p write (default: 1'000'000'000).
686  * \throws std::runtime_error If the estimated size of a single particle line
687  * exceeds half of \p max_buffer_bytes. In that case, only one particle
688  * would fit per chunk, which defeats the purpose of chunked writing,
689  * and the caller must increase \p max_buffer_bytes accordingly.
690  */
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 detail
743 
744 /**
745  * \brief User-facing wrapper for chunked particle writing.
746  *
747  * Forwards the call to the internal implementation.
748  *
749  * \see detail::write_in_chunk_impl
750  */
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  detail::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:191
int baryon_number() const
Definition: pdgcode.h:388
int strangeness() const
Definition: pdgcode.h:626
int32_t get_decimal() const
Definition: pdgcode.h:852
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.