Version: SMASH-3.4
smash::detail Namespace Reference

specialize a type for all of the STL containers. More...

Classes

struct  KeyTraits
 Class template to store Key traits outside the Key class, allowing for reuse both in the Key class itself and in helper implementation details. More...
 
struct  is_stl_container
 Implementation of the type trait to infer if a type is an STL container. More...
 
struct  is_stl_container< std::vector< Args... > >
 Trait specialization for std::vector. More...
 
struct  is_stl_container< std::set< Args... > >
 Trait specialization for std::set. More...
 
struct  is_stl_container< std::map< Args... > >
 Trait specialization for std::map. More...
 

Functions

void sample_manybody_phasespace_impl (double sqrts, const ParticleTypePtrList &types, std::vector< FourVector > &sampled_momenta)
 Implementation of the full n-body phase-space sampling (masses, momenta, angles) in the center-of-mass frame for the final state particles, using the M-method from CERN-68-15, paragraph 9.6. More...
 
void sample_manybody_phasespace_MCMC (const ParticleTypePtrList &types, std::vector< FourVector > &sampled_momenta)
 Metropolis–Hastings sampling of the many body phase space, starting from an initial guess in sampled_momenta that is assumed appropriate. More...
 
double interpolate_trilinear (double ax, double ay, double az, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8)
 Perform a trilinear 1st order interpolation. More...
 
template<typename T >
const KeyTraits< T >::validator_type & get_default_validator () noexcept
 Function template to get a default trivial validator. More...
 
template<typename T >
constexpr auto type_name ()
 Get type of variable as string in a human-readable way. More...
 
template<typename N , typename = std::enable_if_t<std::is_floating_point_v<N>>>
bool almost_equal_knuthish (const N x, const N y, const N epsilon, const N threshold=N{0.0}) noexcept
 Compare whether two floating-point numbers are approximately equal à la Knuth up to a given tolerance. More...
 
template<typename Converter , class Range , std::enable_if_t< std::is_same_v< Range, Particles >||std::is_same_v< Range, ParticleList >, bool > = true>
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. More...
 

Detailed Description

specialize a type for all of the STL containers.

Function Documentation

◆ sample_manybody_phasespace_impl()

void smash::detail::sample_manybody_phasespace_impl ( double  sqrts,
const ParticleTypePtrList &  types,
std::vector< FourVector > &  sampled_momenta 
)

Implementation of the full n-body phase-space sampling (masses, momenta, angles) in the center-of-mass frame for the final state particles, using the M-method from CERN-68-15, paragraph 9.6.

The algorithm proceeds in two stages:

  1. Generate invariant masses \(M_{12}, M_{123}, M_{1234}, \ldots\) from the measure

    \[ dM_{12}\, dM_{123}\, dM_{1234}\, \cdots, \]

    while respecting non-trivial kinematic limits.

    Introduce shifted variables

    \[ T_{12} = M_{12} - (m_1 + m_2),\qquad T_{123} = M_{123} - (m_1 + m_2 + m_3),\ \ldots \]

    and sample uniformly under the ordering constraint

    \[ 0 \le T_{12} \le T_{123} \le T_{1234} \le \cdots \le \sqrt{s} - \sum_i m_i. \]

    A practical trick is to draw values uniformly in \([0,\,\sqrt{s} - \sum_i m_i]\) and sort them.

  2. Accept or reject each invariant-mass configuration with weight proportional to

    \[ R_2(\sqrt{s}, M_{n-1}, m_n) \times R_2(M_{n-1}, M_{n-2}, m_{n-1}) \times \cdots \times R_2(M_2, m_1, m_2) \times \prod_i M_i. \]

The maximum weight is estimated heuristically; following an idea by Scott Pratt, it is expected near

\[ T_{12} = T_{123} = T_{1234} = \cdots = \frac{\sqrt{s} - \sum_i m_i}{n - 1}. \]

Definition at line 410 of file action.cc.

412  {
413  const size_t n = types.size();
414  assert(n > 1);
415  sampled_momenta.resize(n);
416  std::vector<double> masses{}, masses_sum(n), Minv(n);
417  // Maximum estimate is rough and can be wrong. We multiply it by additional
418  // factor to be on the safer side, and increase it if needed.
419  double safety_factor = 1.1 + (n - 2) * 0.2;
420  int rejection_counter = 0;
421  constexpr int rejection_limit = 200;
422  double available_energy =
423  sqrts - std::accumulate(types.begin(), types.end(), 0.0,
424  [](double sum, const ParticleTypePtr &type) {
425  return sum + type->min_mass_spectral();
426  });
427  double acceptance = 1;
428  do {
429  double random_01;
430  // This loop increases the maximum if the safety_factor is too small
431  safety_factor *= std::sqrt(acceptance);
432  do {
433  // Mass sampling from spectral functions
434  double weight_sqr_max = safety_factor * safety_factor;
435  masses.clear();
436  masses_sum.clear();
437  for (const auto &type : types) {
438  if (type->is_stable()) {
439  masses.push_back(type->mass());
440  } else {
441  masses.push_back(
442  type->sample_breit_wigner_spectral_function(available_energy));
443  const double max_ratio = std::max(
444  type->max_ratio_spectral_full_to_breit_wigner(),
445  type->ratio_spectral_full_to_breit_wigner(available_energy));
446  weight_sqr_max *= max_ratio * max_ratio;
447  }
448  }
449  // Arrange a convenient vector of m1, m1 + m2, m1 + m2 + m3, ...
450  std::partial_sum(masses.begin(), masses.end(),
451  std::back_inserter(masses_sum));
452  const double masses_sum_all = masses_sum[n - 1];
453  const double Ekin_share = (sqrts - masses_sum_all) / (n - 1);
454  for (size_t i = 1; i < n; i++) {
455  // This maximum estimate idea is due Scott Pratt: maximum should be
456  // roughly at equal kinetic energies
457  weight_sqr_max *=
458  pCM_sqr(i * Ekin_share + masses_sum[i],
459  (i - 1) * Ekin_share + masses_sum[i - 1], masses[i]);
460  }
461  // Generate invariant masses of 1, 12, 123, 1234, etc.
462  // Minv = {m1, M12, M123, ..., M123n-1, sqrts}
463  Minv[0] = 0.0;
464  Minv[n - 1] = sqrts - masses_sum_all;
465  for (size_t i = 1; i < n - 1; i++) {
466  Minv[i] = random::uniform(0.0, sqrts - masses_sum_all);
467  }
468  std::sort(Minv.begin(), Minv.end());
469  for (size_t i = 0; i < n; i++) {
470  Minv[i] += masses_sum[i];
471  }
472 
473  double weight_sqr = 1;
474  for (size_t i = 0; i < n; i++) {
475  const double ratio =
476  types[i]->is_stable()
477  ? 1
478  : types[i]->ratio_spectral_full_to_breit_wigner(masses[i]);
479  weight_sqr *= ratio * ratio;
480  }
481  for (size_t i = 1; i < n; i++) {
482  weight_sqr *= pCM_sqr(Minv[i], Minv[i - 1], masses[i]);
483  }
484  acceptance = weight_sqr / weight_sqr_max;
485  rejection_counter++;
486  random_01 = random::canonical();
487  } while (acceptance < random_01 * random_01 &&
488  rejection_counter < rejection_limit);
489  if (acceptance > 1) {
490  logg[LAction].debug()
491  << "sample_manybody_phasespace_impl: alarm, weight > 1, w^2 = "
492  << acceptance << ". Increasing safety factor.";
493  }
494  } while (acceptance > 1 && rejection_counter < rejection_limit);
495 
496  // Boost particles to the right frame
497  std::vector<ThreeVector> beta(n);
498  for (size_t i = n - 1; i > 0; i--) {
499  const double pcm = pCM(Minv[i], Minv[i - 1], masses[i]);
500  Angles phitheta;
501  phitheta.distribute_isotropically();
502  const ThreeVector isotropic_unitvector = phitheta.threevec();
503  sampled_momenta[i] =
504  FourVector(std::sqrt(masses[i] * masses[i] + pcm * pcm),
505  pcm * isotropic_unitvector);
506  if (i >= 2) {
507  beta[i - 2] = pcm * isotropic_unitvector /
508  std::sqrt(pcm * pcm + Minv[i - 1] * Minv[i - 1]);
509  }
510  if (i == 1) {
511  sampled_momenta[0] =
512  FourVector(std::sqrt(masses[0] * masses[0] + pcm * pcm),
513  -pcm * isotropic_unitvector);
514  }
515  }
516 
517  for (size_t i = 0; i < n - 2; i++) {
518  // After each boost except the last one the sum of 3-momenta should be 0
519  FourVector ptot = FourVector(0.0, 0.0, 0.0, 0.0);
520  for (size_t j = 0; j <= i + 1; j++) {
521  ptot += sampled_momenta[j];
522  }
523  logg[LAction].debug() << "Total momentum of 0.." << i + 1 << " = "
524  << ptot.threevec() << " and should be (0, 0, 0).";
525 
526  // Boost the first i+1 particles to the next CM frame
527  for (size_t j = 0; j <= i + 1; j++) {
528  sampled_momenta[j] = sampled_momenta[j].lorentz_boost(beta[i]);
529  }
530  }
531 
532  FourVector ptot_all = FourVector(0.0, 0.0, 0.0, 0.0);
533  for (size_t j = 0; j < n; j++) {
534  ptot_all += sampled_momenta[j];
535  }
536  logg[LAction].debug() << "Total 4-momentum = " << ptot_all << ", should be ("
537  << sqrts << ", 0, 0, 0)";
538  if (rejection_counter >= rejection_limit) {
539  logg[LAction].warn()
540  << "Failed to sample kinematically correct 4-momenta of "
541  << std::accumulate(types.begin(), types.end(), std::string{},
542  [](const std::string &a, const ParticleTypePtr &b) {
543  return a + b->name();
544  })
545  << " with energy " << sqrts
546  << " GeV.\n Using MCMC fallback, which conserves energy and momentum "
547  "but may deviate slightly from the correct spectral distribution.";
548  sample_manybody_phasespace_MCMC(types, sampled_momenta);
549  }
550 }
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > & logg
An array that stores all pre-configured Logger objects.
Definition: logging.h:245
void sample_manybody_phasespace_MCMC(const ParticleTypePtrList &types, std::vector< FourVector > &sampled_momenta)
Metropolis–Hastings sampling of the many body phase space, starting from an initial guess in sampled_...
Definition: action.cc:552
constexpr int n
Neutron.
T beta(T a, T b)
Draws a random number from a beta-distribution, where probability density of is .
Definition: random.h:373
T uniform(T min, T max)
Definition: random.h:91
T canonical()
Definition: random.h:122
T pCM(const T sqrts, const T mass_a, const T mass_b) noexcept
Definition: kinematics.h:79
T pCM_sqr(const T sqrts, const T mass_a, const T mass_b) noexcept
Definition: kinematics.h:91
static constexpr int LAction
Definition: action.h:25
Here is the call graph for this function:
Here is the caller graph for this function:

◆ sample_manybody_phasespace_MCMC()

void smash::detail::sample_manybody_phasespace_MCMC ( const ParticleTypePtrList &  types,
std::vector< FourVector > &  sampled_momenta 
)

Metropolis–Hastings sampling of the many body phase space, starting from an initial guess in sampled_momenta that is assumed appropriate.

The algorithm works by repeatedly picking a random pair of particles, resampling their masses from the spectral functions, and adjusting their momenta accordingly in the CM frame of the pair, which conserves energy and momentum. This is done for a fixed number of iterations heuristically chosen (200), but no systematic analysis was done.

The function is for now used as a fallback for the rejection algorithm in sample_manybody_phasespace_impl and takes its initial guess from there.

Definition at line 552 of file action.cc.

553  {
554  constexpr int monte_carlo_iterations = 200;
555  // sampled_momenta already contains an initial guess
556  const int n = types.size();
557  int idx1, idx2;
558 
559  assert(sampled_momenta.size() == static_cast<size_t>(n));
560  // It is enough to check that the energy is a positive number
561  const bool all_zero =
562  std::all_of(sampled_momenta.begin(), sampled_momenta.end(),
563  [](const FourVector &p) { return p[0] < really_small; });
564  if (all_zero) {
565  throw std::runtime_error(
566  "All initial momenta for the MCMC algorithm manybody phase space "
567  "sampling are zero, which should not happen.");
568  }
569 
570  for (int i = 0; i < monte_carlo_iterations; i++) {
571  // Pick random pair of particles and resample their masses
572  do {
573  idx1 = random::uniform_int(0, n - 1);
574  do {
575  idx2 = random::uniform_int(0, n - 1);
576  } while (idx2 == idx1);
577  } while (types[idx1]->is_stable() && types[idx2]->is_stable());
578 
579  // Energy of the pair and CM frame velocity
580  const FourVector p_sum = sampled_momenta[idx1] + sampled_momenta[idx2];
581  const ThreeVector beta = p_sum.velocity();
582  const double sqrts_12 = p_sum.abs();
583  double m1, m2;
584  do {
585  m1 = types[idx1]->sample_full_spectral_function(sqrts_12);
586  m2 = types[idx2]->sample_full_spectral_function(sqrts_12);
587  } while (sqrts_12 < m1 + m2);
588  const double pcm = pCM(sqrts_12, m1, m2);
589  Angles phitheta;
590  phitheta.distribute_isotropically();
591  /*
592  * Metropolis-Hastings acceptance. As the Breit Wigner is biased towards
593  * lower masses compared to the full spectral function, we generally want to
594  * accept more often if the ratio increases, and less if it decreases.
595  */
596  double acc = 1.0;
597  if (!types[idx1]->is_stable()) {
598  acc *= types[idx1]->ratio_spectral_full_to_breit_wigner(m1) /
599  types[idx1]->ratio_spectral_full_to_breit_wigner(
600  sampled_momenta[idx1].abs());
601  }
602  if (!types[idx2]->is_stable()) {
603  acc *= types[idx2]->ratio_spectral_full_to_breit_wigner(m2) /
604  types[idx2]->ratio_spectral_full_to_breit_wigner(
605  sampled_momenta[idx2].abs());
606  }
607 
608  if (random::canonical() < acc) {
609  // Accept and impose momentum conservation with back-to-back particles
610  sampled_momenta[idx1] =
611  FourVector(std::sqrt(m1 * m1 + pcm * pcm), pcm * phitheta.threevec())
612  .lorentz_boost(-beta);
613  sampled_momenta[idx2] =
614  FourVector(std::sqrt(m2 * m2 + pcm * pcm), -pcm * phitheta.threevec())
615  .lorentz_boost(-beta);
616  }
617  }
618 }
constexpr int p
Proton.
T uniform_int(T min, T max)
Definition: random.h:106
bool all_of(Container &&c, UnaryPredicate &&p)
Convenience wrapper for std::all_of that operates on a complete container.
Definition: algorithms.h:80
Here is the call graph for this function:
Here is the caller graph for this function:

◆ interpolate_trilinear()

double smash::detail::interpolate_trilinear ( double  ax,
double  ay,
double  az,
double  f1,
double  f2,
double  f3,
double  f4,
double  f5,
double  f6,
double  f7,
double  f8 
)

Perform a trilinear 1st order interpolation.

Assume, we seek the value of a function \( f \) at position \((x, y, z)\). We know the position \((x, y, z)\) lies within a 3D cube, for which the values of the function f are known at each corner \((f_1, ..., f_8)\). We can now interpolate those values trilinearly to obtain an estimate of \( f \) at position \((x, y, z)\).

For this interpolation, linear functions are used in each direction \(x\), \(y\), and \(z\) respectively with \( a_x \), \( a_y \), and \( a_z \) as the slope parameters, e.g., \( f_1 + a_y \cdot (f_3 - f_1) \) for an approximation between the corners \( f_1 \) and \( f_3 \). For the \(y\)-direction, the linear functions are based on the cube's corners. The \(x\)-direction then uses the four obtained values from the interpolation in \(y\)-direction and finally the \(z\)-direction interpolation is based on the two values obtained by the combined interpolations in \(x\)- and \(y\)-direction. Since the position \((x, y, z)\) of the wanted value of function \( f \) is within the cube, the allowed values for \( a_x \), \( a_y \), and \( a_z \) are between 0 and 1.

Positional placement of the cube:

  • \(x\)-direction: lower left front to lower right front corner \( (f_1 \) to \( f_2) \)
  • \(y\)-direction: lower left front to upper left front corner \( (f_1 \) to \( f_3) \)
  • \(z\)-direction: lower left front to lower left back corner \( (f_1 \) to \( f_5) \)
Note
\( a_x \), \( a_y \), and \( a_z \) have to be chosen in a way that the linear interpolations reflect the position \( (x, y, z) \), i.e. \( (x, y, z) = (x_1 + a_x \cdot x_2, y_1 + a_y \cdot y_3, z_1 + a_z \cdot z_5) \) with \( (x_i, y_i, z_i) \) representing the coordinates of the corner at \( f_i \).
Parameters
[in]axFraction of the step in x-direction and used as slope parameter
[in]ayFraction of the step in y-direction and used as slope parameter
[in]azFraction of the step in z-direction and used as slope parameter
[in]f1Value at the lower left front corner of the cube
[in]f2Value at the lower right front corner of the cube
[in]f3Value at the upper left front corner of the cube
[in]f4Value at the upper right front corner of the cube
[in]f5Value at the lower left back corner of the cube
[in]f6Value at the lower right back corner of the cube
[in]f7Value at the upper left back corner of the cube
[in]f8Value at the upper right back corner of the cube
Returns
Interpolated value

Definition at line 658 of file hadgas_eos.cc.

660  {
661  assert(ax >= 0 && ax <= 1);
662  assert(ay >= 0 && ay <= 1);
663  assert(az >= 0 && az <= 1);
664  double res = az * (ax * (ay * f8 + (1.0 - ay) * f6) +
665  (1.0 - ax) * (ay * f7 + (1.0 - ay) * f5)) +
666  (1 - az) * (ax * (ay * f4 + (1.0 - ay) * f2) +
667  (1.0 - ax) * (ay * f3 + (1.0 - ay) * f1));
668  return res;
669 }
Here is the caller graph for this function:

◆ get_default_validator()

template<typename T >
const KeyTraits<T>::validator_type& smash::detail::get_default_validator ( )
noexcept

Function template to get a default trivial validator.

Returns
A const reference to a functor that always returns true .
Attention
It might look unnecessary to have a function returning the functor and you might think that the functor as a constant global variable template would be enough. However, this would be in general wrong because this functor is used in the Key constructors which are used by the InputKeys class, that is a collection of static Keys. Hence, since initialization order of static/global objects in C++ is undefined, we need to do something else. We use therefore the "construct on first use idiom", making the functor a static object in a function scope. For more information, refer for example to ISO C++ FAQ.

Definition at line 103 of file key.h.

103  {
104  static const typename KeyTraits<T>::validator_type always_true =
105  [](const T&) noexcept { return true; };
106  return always_true;
107 }

◆ type_name()

template<typename T >
constexpr auto smash::detail::type_name ( )
constexpr

Get type of variable as string in a human-readable way.

Template Parameters
TThe type to be returned.
Returns
A std::string containing the name of the type.

Definition at line 27 of file numeric_cast.h.

27  {
28  std::string_view name, prefix, suffix;
29 #ifdef __clang__
30  name = __PRETTY_FUNCTION__;
31  prefix = "auto smash::detail::type_name() [T = ";
32  suffix = "]";
33 #elif defined(__GNUC__)
34  name = __PRETTY_FUNCTION__;
35  prefix = "constexpr auto smash::detail::type_name() [with T = ";
36  suffix = "]";
37 #elif defined(_MSC_VER)
38  name = __FUNCSIG__;
39  prefix = "auto __cdecl smash::detail::type_name<";
40  suffix = ">(void)";
41 #else
42  name = "UNKNOWN";
43  prefix = "";
44  suffix = "";
45 #endif
46  name.remove_prefix(prefix.size());
47  name.remove_suffix(suffix.size());
48  return std::string{name};
49 }

◆ almost_equal_knuthish()

template<typename N , typename = std::enable_if_t<std::is_floating_point_v<N>>>
bool smash::detail::almost_equal_knuthish ( const N  x,
const N  y,
const N  epsilon,
const N  threshold = N{0.0} 
)
noexcept

Compare whether two floating-point numbers are approximately equal à la Knuth up to a given tolerance.

On top of Knuth's tolerance predicate some corner cases are treated and the caller can specify a threshold as last parameter to make the test consider numbers equal if the absolute value of their difference is below of it.

Parameters
[in]xFirst of the two numbers.
[in]ySecond of the two numbers.
[in]epsilonThe relative tolerance for the test.
[in]thresholdThreshold for the number comparison. By default this is zero, implying no threshold is considered.
Returns
false if either x or y is not a finite number, provided that the type supports non-numeric representations (i.e. is infinite or NAN);
true if x == y;
true if x == 0 and if \( |x| \le \varepsilon\);
true if y == 0 and if \( |y| \le \varepsilon\);
true if \( |x - y| \le M_\mathrm{threshold} \);
true if \( |x - y| \le \varepsilon \cdot \max(|x|, |y|) \) (Knuth's tolerance predicate);
false otherwise.

Definition at line 59 of file numerics.h.

60  {0.0}) noexcept {
61  assert(epsilon > 0);
62  assert(threshold >= 0);
63  if constexpr (std::numeric_limits<N>::is_iec559) {
64  if (!std::isfinite(x) || !std::isfinite(y)) {
65  return false;
66  }
67  }
68  if (x == y)
69  return true;
70  else if (x == 0)
71  return std::abs(y) <= epsilon;
72  else if (y == 0)
73  return std::abs(x) <= epsilon;
74  else
75  return std::abs(x - y) <= threshold ||
76  std::abs(x - y) <= epsilon * std::max(std::abs(x), std::abs(y));
77 }

◆ write_in_chunk_impl()

template<typename Converter , class Range , std::enable_if_t< std::is_same_v< Range, Particles >||std::is_same_v< Range, ParticleList >, bool > = true>
void smash::detail::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.

This method avoids creating a single excessively large binary buffer when writing many particles at once. Instead, it splits the write into several smaller chunks. This can prevent excessive memory usage and improve stability on systems or filesystems that may have trouble with very large write calls.

The maximum buffer size is currently set to 1 GB (10^9 bytes), but this can be adapted in the future if needed. If the total data size of the particle block is below this threshold, the method simply delegates to the write function which should perform a single write call.

Otherwise, the data is accumulated particle by particle until the buffer reaches the threshold. The buffer is then given to the write function which should flush to disk.

Note
This utility does not strictly belong to this file. At the moment, the objects using it do not have a clean hierarchy that would allow both OscarOutput and BinaryOutput to inherit a shared implementation, hence it lives here temporarily.
Todo:
Once the hierarchy is cleaned up, move this into a common base class that OscarOutput and BinaryOutput inherit from.
Template Parameters
ConverterConverter used by OutputFormatter to produce the buffer type (must define Converter::type).
RangeContainer type — enforced to be either Particles or ParticleList.
Parameters
[in]particlesContainer of particles whose particle_line representation is to be written.
[in]formatterFormatter responsible for converting particles into the corresponding Converter::type buffer representation.
[in]writeCallable that receives each filled buffer chunk and performs the actual write to the underlying output (e.g. file).
[in]max_buffer_bytesMaximum buffer size in bytes before the accumulated data is flushed via write (default: 1'000'000'000).
Exceptions
std::runtime_errorIf the estimated size of a single particle line exceeds half of max_buffer_bytes. In that case, only one particle would fit per chunk, which defeats the purpose of chunked writing, and the caller must increase max_buffer_bytes accordingly.

Definition at line 695 of file outputformatter.h.

698  {
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 }
Here is the call graph for this function:
Here is the caller graph for this function: