Version: SMASH-2.2
collidermodus.cc
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2014-2021
3  * SMASH Team
4  *
5  * GNU General Public License (GPLv3 or later)
6  */
7 
8 #include "smash/collidermodus.h"
9 
10 #include <algorithm>
11 #include <cmath>
12 #include <cstdlib>
13 #include <cstring>
14 #include <memory>
15 #include <stdexcept>
16 #include <string>
17 #include <tuple>
18 #include <utility>
19 #include <vector>
20 
21 #include "smash/configuration.h"
22 #include "smash/customnucleus.h"
23 #include "smash/cxx14compat.h"
25 #include "smash/fourvector.h"
26 #include "smash/logging.h"
27 #include "smash/random.h"
28 
29 namespace smash {
30 static constexpr int LCollider = LogArea::Collider::id;
323  const ExperimentParameters &params) {
324  Configuration modus_cfg = modus_config["Collider"];
325  // Get the reference frame for the collision calculation.
326  if (modus_cfg.has_value({"Calculation_Frame"})) {
327  frame_ = modus_cfg.take({"Calculation_Frame"});
328  }
329 
330  Configuration proj_cfg = modus_cfg["Projectile"];
331  Configuration targ_cfg = modus_cfg["Target"];
332  /* Needed to check if projectile and target in customnucleus are read from
333  * the same input file.*/
334  bool same_file = false;
335  // Set up the projectile nucleus
336  if (proj_cfg.has_value({"Deformed"})) {
337  projectile_ =
338  create_deformed_nucleus(proj_cfg, params.testparticles, "projectile");
339  } else if (proj_cfg.has_value({"Custom"})) {
340  same_file = same_inputfile(proj_cfg, targ_cfg);
341  projectile_ =
342  make_unique<CustomNucleus>(proj_cfg, params.testparticles, same_file);
343  } else {
344  projectile_ = make_unique<Nucleus>(proj_cfg, params.testparticles);
345  }
346  if (projectile_->size() < 1) {
347  throw ColliderEmpty("Input Error: Projectile nucleus is empty.");
348  }
350 
351  // Set up the target nucleus
352  if (targ_cfg.has_value({"Deformed"})) {
353  target_ = create_deformed_nucleus(targ_cfg, params.testparticles, "target");
354  } else if (targ_cfg.has_value({"Custom"})) {
355  target_ =
356  make_unique<CustomNucleus>(targ_cfg, params.testparticles, same_file);
357  } else {
358  target_ = make_unique<Nucleus>(targ_cfg, params.testparticles);
359  }
360  if (target_->size() < 1) {
361  throw ColliderEmpty("Input Error: Target nucleus is empty.");
362  }
363  target_->set_label(BelongsTo::Target);
364 
365  // Get the Fermi-Motion input (off, on, frozen)
366  if (modus_cfg.has_value({"Fermi_Motion"})) {
367  // We only read the value, because it is still required by the experiment
368  // class to make sure we don't use frozen Fermi momenta with potentials.
369  fermi_motion_ = modus_cfg.read({"Fermi_Motion"});
370  }
371 
372  // Get the total nucleus-nucleus collision energy. Since there is
373  // no meaningful choice for a default energy, we require the user to
374  // give one (and only one) energy input from the available options.
375  int energy_input = 0;
376  const double mass_projec = projectile_->mass();
377  const double mass_target = target_->mass();
378  // average mass of a particle in that nucleus
379  const double mass_a =
380  projectile_->mass() / projectile_->number_of_particles();
381  const double mass_b = target_->mass() / target_->number_of_particles();
382  // Option 1: Center of mass energy.
383  if (modus_cfg.has_value({"Sqrtsnn"})) {
384  sqrt_s_NN_ = modus_cfg.take({"Sqrtsnn"});
385  // Check that input satisfies the lower bound (everything at rest).
386  if (sqrt_s_NN_ <= mass_a + mass_b) {
388  "Input Error: sqrt(s_NN) is not larger than masses:\n" +
389  std::to_string(sqrt_s_NN_) + " GeV <= " + std::to_string(mass_a) +
390  " GeV + " + std::to_string(mass_b) + " GeV.");
391  }
392  // Set the total nucleus-nucleus collision energy.
393  total_s_ = (sqrt_s_NN_ * sqrt_s_NN_ - mass_a * mass_a - mass_b * mass_b) *
394  mass_projec * mass_target / (mass_a * mass_b) +
395  mass_projec * mass_projec + mass_target * mass_target;
396  energy_input++;
397  }
398  /* Option 2: Total energy per nucleon of the projectile nucleus
399  * (target at rest). */
400  if (modus_cfg.has_value({"E_Tot"})) {
401  const double e_tot = modus_cfg.take({"E_Tot"});
402  if (e_tot < 0) {
404  "Input Error: "
405  "E_Tot must be nonnegative.");
406  }
407  // Set the total nucleus-nucleus collision energy.
408  total_s_ = s_from_Etot(e_tot * projectile_->number_of_particles(),
409  mass_projec, mass_target);
410  sqrt_s_NN_ = std::sqrt(s_from_Etot(e_tot, mass_a, mass_b));
411  energy_input++;
412  }
413  /* Option 3: Kinetic energy per nucleon of the projectile nucleus
414  * (target at rest). */
415  if (modus_cfg.has_value({"E_Kin"})) {
416  const double e_kin = modus_cfg.take({"E_Kin"});
417  if (e_kin < 0) {
419  "Input Error: "
420  "E_Kin must be nonnegative.");
421  }
422  // Set the total nucleus-nucleus collision energy.
423  total_s_ = s_from_Ekin(e_kin * projectile_->number_of_particles(),
424  mass_projec, mass_target);
425  sqrt_s_NN_ = std::sqrt(s_from_Ekin(e_kin, mass_a, mass_b));
426  energy_input++;
427  }
428  // Option 4: Momentum of the projectile nucleus (target at rest).
429  if (modus_cfg.has_value({"P_Lab"})) {
430  const double p_lab = modus_cfg.take({"P_Lab"});
431  if (p_lab < 0) {
433  "Input Error: "
434  "P_Lab must be nonnegative.");
435  }
436  // Set the total nucleus-nucleus collision energy.
437  total_s_ = s_from_plab(p_lab * projectile_->number_of_particles(),
438  mass_projec, mass_target);
439  sqrt_s_NN_ = std::sqrt(s_from_plab(p_lab, mass_a, mass_b));
440  energy_input++;
441  }
442  // Option 5: Total energy per nucleon of _each_ beam
443  if (proj_cfg.has_value({"E_Tot"}) && targ_cfg.has_value({"E_Tot"})) {
444  const double e_tot_p = proj_cfg.take({"E_Tot"});
445  const double e_tot_t = targ_cfg.take({"E_tot"});
446  if (e_tot_p < 0 || e_tot_t < 0) {
448  "Input Error: "
449  "E_Tot must be nonnegative.");
450  }
451  total_s_ = s_from_Etot(e_tot_p * projectile_->number_of_particles(),
452  e_tot_t * target_->number_of_particles(),
453  mass_projec, mass_target);
454  sqrt_s_NN_ = std::sqrt(s_from_Ekin(e_tot_p, e_tot_t, mass_a, mass_b));
455  energy_input++;
456  }
457  // Option 6: Kinetic energy per nucleon of _each_ beam
458  if (proj_cfg.has_value({"E_Kin"}) && targ_cfg.has_value({"E_Kin"})) {
459  const double e_kin_p = proj_cfg.take({"E_Kin"});
460  const double e_kin_t = targ_cfg.take({"E_Kin"});
461  if (e_kin_p < 0 || e_kin_t < 0) {
463  "Input Error: "
464  "E_Kin must be nonnegative.");
465  }
466  total_s_ = s_from_Ekin(e_kin_p * projectile_->number_of_particles(),
467  e_kin_t * target_->number_of_particles(),
468  mass_projec, mass_target);
469  sqrt_s_NN_ = std::sqrt(s_from_Ekin(e_kin_p, e_kin_t, mass_a, mass_b));
470  energy_input++;
471  }
472  // Option 7: Momentum per nucleon of _each_ beam
473  if (proj_cfg.has_value({"P_Lab"}) && targ_cfg.has_value({"P_Lab"})) {
474  const double p_lab_p = proj_cfg.take({"P_Lab"});
475  const double p_lab_t = targ_cfg.take({"P_Lab"});
476  if (p_lab_p < 0 || p_lab_t < 0) {
478  "Input Error: "
479  "P_Lab must be nonnegative.");
480  }
481  total_s_ = s_from_plab(p_lab_p * projectile_->number_of_particles(),
482  p_lab_t * target_->number_of_particles(),
483  mass_projec, mass_target);
484  sqrt_s_NN_ = std::sqrt(s_from_plab(p_lab_p, p_lab_t, mass_a, mass_b));
485  energy_input++;
486  }
487  if (energy_input == 0) {
488  throw std::domain_error(
489  "Input Error: Non-existent collision energy. "
490  "Please provide one of Sqrtsnn/E_Kin/P_Lab.");
491  }
492  if (energy_input > 1) {
493  throw std::domain_error(
494  "Input Error: Redundant collision energy. "
495  "Please provide only one of Sqrtsnn/E_Kin/P_Lab.");
496  }
497 
498  /* Impact parameter setting: Either "Value", "Range", "Max" or "Sample".
499  * Unspecified means 0 impact parameter.*/
500  if (modus_cfg.has_value({"Impact", "Value"})) {
501  impact_ = modus_cfg.take({"Impact", "Value"});
502  imp_min_ = impact_;
503  imp_max_ = impact_;
504  } else {
505  // If impact is not supplied by value, inspect sampling parameters:
506  if (modus_cfg.has_value({"Impact", "Sample"})) {
507  sampling_ = modus_cfg.take({"Impact", "Sample"});
508  if (sampling_ == Sampling::Custom) {
509  if (!(modus_cfg.has_value({"Impact", "Values"}) ||
510  modus_cfg.has_value({"Impact", "Yields"}))) {
511  throw std::domain_error(
512  "Input Error: Need impact parameter spectrum for custom "
513  "sampling. "
514  "Please provide Values and Yields.");
515  }
516  const std::vector<double> impacts =
517  modus_cfg.take({"Impact", "Values"});
518  const std::vector<double> yields = modus_cfg.take({"Impact", "Yields"});
519  if (impacts.size() != yields.size()) {
520  throw std::domain_error(
521  "Input Error: Need as many impact parameter values as yields. "
522  "Please make sure that Values and Yields have the same length.");
523  }
524  impact_interpolation_ = make_unique<InterpolateDataLinear<double>>(
525  InterpolateDataLinear<double>(impacts, yields));
526 
527  const auto imp_minmax =
528  std::minmax_element(impacts.begin(), impacts.end());
529  imp_min_ = *imp_minmax.first;
530  imp_max_ = *imp_minmax.second;
531  yield_max_ = *std::max_element(yields.begin(), yields.end());
532  }
533  }
534  if (modus_cfg.has_value({"Impact", "Range"})) {
535  const std::array<double, 2> range = modus_cfg.take({"Impact", "Range"});
536  imp_min_ = range[0];
537  imp_max_ = range[1];
538  }
539  if (modus_cfg.has_value({"Impact", "Max"})) {
540  imp_min_ = 0.0;
541  imp_max_ = modus_cfg.take({"Impact", "Max"});
542  }
543  }
545  // whether the direction of separation should be ramdomly smapled
547  modus_cfg.take({"Impact", "Random_Reaction_Plane"}, false);
548  // Look for user-defined initial separation between nuclei.
549  if (modus_cfg.has_value({"Initial_Distance"})) {
550  initial_z_displacement_ = modus_cfg.take({"Initial_Distance"});
551  // the displacement is half the distance (both nuclei are shifted
552  // initial_z_displacement_ away from origin)
554  }
555 
557  logg[LCollider].info() << "Fermi motion is ON.";
558  } else if (fermi_motion_ == FermiMotion::Frozen) {
559  logg[LCollider].info() << "FROZEN Fermi motion is on.";
560  } else if (fermi_motion_ == FermiMotion::Off) {
561  logg[LCollider].info() << "Fermi motion is OFF.";
562  }
563 }
564 
565 std::ostream &operator<<(std::ostream &out, const ColliderModus &m) {
566  return out << "-- Collider Modus:\n"
567  << "sqrt(S) (nucleus-nucleus) = "
568  << format(std::sqrt(m.total_s_), "GeV\n")
569  << "sqrt(S) (nucleon-nucleon) = " << format(m.sqrt_s_NN_, "GeV\n")
570  << "Projectile:\n"
571  << *m.projectile_ << "\nTarget:\n"
572  << *m.target_;
573 }
574 
575 std::unique_ptr<DeformedNucleus> ColliderModus::create_deformed_nucleus(
576  Configuration &nucleus_cfg, int ntest, const std::string &nucleus_type) {
577  bool auto_deform = nucleus_cfg.take({"Deformed", "Automatic"});
578  bool is_beta2 = nucleus_cfg.has_value({"Deformed", "Beta_2"}) ? true : false;
579  bool is_beta4 = nucleus_cfg.has_value({"Deformed", "Beta_4"}) ? true : false;
580  std::unique_ptr<DeformedNucleus> nucleus;
581 
582  if ((auto_deform && (!is_beta2 && !is_beta4)) ||
583  (!auto_deform && (is_beta2 && is_beta4))) {
584  nucleus = make_unique<DeformedNucleus>(nucleus_cfg, ntest, auto_deform);
585  return nucleus;
586  } else {
587  throw std::domain_error("Deformation of " + nucleus_type +
588  " nucleus not configured "
589  "properly, please check whether all necessary "
590  "parameters are set.");
591  }
592 }
593 
595  const ExperimentParameters &) {
596  // Populate the nuclei with appropriately distributed nucleons.
597  // If deformed, this includes rotating the nucleus.
598  projectile_->arrange_nucleons();
599  target_->arrange_nucleons();
600 
601  // Use the total mandelstam variable to get the frame-dependent velocity for
602  // each nucleus. Position a is projectile, position b is target.
603  double v_a, v_b;
604  std::tie(v_a, v_b) =
605  get_velocities(total_s_, projectile_->mass(), target_->mass());
606 
607  // If velocities are larger or equal to 1, throw an exception.
608  if (v_a >= 1.0 || v_b >= 1.0) {
609  throw std::domain_error(
610  "Found velocity equal to or larger than 1 in "
611  "ColliderModus::initial_conditions.\nConsider using "
612  "the center of velocity reference frame.");
613  }
614 
615  // Calculate the beam velocity of the projectile and the target, which will be
616  // used to calculate the beam momenta in experiment.cc
618  velocity_projectile_ = v_a;
619  velocity_target_ = v_b;
620  }
621 
622  // Generate Fermi momenta if necessary
625  // Frozen: Fermi momenta will be ignored during the propagation to
626  // avoid that the nuclei will fly apart.
627  projectile_->generate_fermi_momenta();
628  target_->generate_fermi_momenta();
629  } else if (fermi_motion_ == FermiMotion::Off) {
630  } else {
631  throw std::domain_error("Invalid Fermi_Motion input.");
632  }
633 
634  // Boost the nuclei to the appropriate velocity.
635  projectile_->boost(v_a);
636  target_->boost(v_b);
637 
638  // Shift the nuclei into starting positions. Contracted spheres with
639  // nuclear radii should touch exactly at t=0. Modus starts at negative
640  // time corresponding to additional initial displacement.
641  const double d_a = std::max(0., projectile_->get_diffusiveness());
642  const double d_b = std::max(0., target_->get_diffusiveness());
643  const double r_a = projectile_->get_nuclear_radius();
644  const double r_b = target_->get_nuclear_radius();
645  const double dz = initial_z_displacement_;
646 
647  const double simulation_time = -dz / std::abs(v_a);
648  const double proj_z = -dz - std::sqrt(1.0 - v_a * v_a) * (r_a + d_a);
649  const double targ_z =
650  +dz * std::abs(v_b / v_a) + std::sqrt(1.0 - v_b * v_b) * (r_b + d_b);
651  // rotation angle in the transverse plane
652  const double phi =
653  random_reaction_plane_ ? random::uniform(0.0, 2.0 * M_PI) : 0.0;
654 
655  projectile_->shift(proj_z, +impact_ / 2.0, simulation_time);
656  target_->shift(targ_z, -impact_ / 2.0, simulation_time);
657 
658  // Put the particles in the nuclei into code particles.
659  projectile_->copy_particles(particles);
660  target_->copy_particles(particles);
661  rotate_reaction_plane(phi, particles);
662  return simulation_time;
663 }
664 
665 void ColliderModus::rotate_reaction_plane(double phi, Particles *particles) {
666  for (ParticleData &p : *particles) {
667  ThreeVector pos = p.position().threevec();
668  ThreeVector mom = p.momentum().threevec();
669  pos.rotate_around_z(phi);
670  mom.rotate_around_z(phi);
671  p.set_3position(pos);
672  p.set_3momentum(mom);
673  }
674 }
675 
677  switch (sampling_) {
678  case Sampling::Quadratic: {
679  // quadratic sampling: Note that for bmin > bmax, this still yields
680  // the correct distribution (however canonical() = 0 is then the
681  // upper end, not the lower).
682  impact_ = std::sqrt(imp_min_ * imp_min_ +
685  } break;
686  case Sampling::Custom: {
687  // rejection sampling based on given distribution
688  assert(impact_interpolation_ != nullptr);
689  double probability_random = 1;
690  double probability = 0;
691  double b;
692  while (probability_random > probability) {
694  probability = (*impact_interpolation_)(b) / yield_max_;
695  assert(probability < 1.);
696  probability_random = random::uniform(0., 1.);
697  }
698  impact_ = b;
699  } break;
700  case Sampling::Uniform: {
701  // linear sampling. Still, min > max works fine.
703  }
704  }
705 }
706 
707 std::pair<double, double> ColliderModus::get_velocities(double s, double m_a,
708  double m_b) {
709  double v_a = 0.0;
710  double v_b = 0.0;
711  // Frame dependent calculations of velocities. Assume v_a >= 0, v_b <= 0.
712  switch (frame_) {
714  v_a = center_of_velocity_v(s, m_a, m_b);
715  v_b = -v_a;
716  break;
718  // Compute center of mass momentum.
719  double pCM = pCM_from_s(s, m_a, m_b);
720  v_a = pCM / std::sqrt(m_a * m_a + pCM * pCM);
721  v_b = -pCM / std::sqrt(m_b * m_b + pCM * pCM);
722  } break;
724  v_a = fixed_target_projectile_v(s, m_a, m_b);
725  break;
726  default:
727  throw std::domain_error(
728  "Invalid reference frame in "
729  "ColliderModus::get_velocities.");
730  }
731  return std::make_pair(v_a, v_b);
732 }
733 
734 std::string ColliderModus::custom_file_path(const std::string &file_directory,
735  const std::string &file_name) {
736  // make sure that path is correct even if the / at the end is missing
737  if (file_directory.back() == '/') {
738  return file_directory + file_name;
739  } else {
740  return file_directory + '/' + file_name;
741  }
742 }
743 
745  Configuration &targ_config) {
746  /* Check if both nuclei are custom
747  * Only check target as function is called after if statement for projectile.
748  */
749  if (!targ_config.has_value({"Custom"})) {
750  return false;
751  }
752  std::string projectile_file_directory =
753  proj_config.read({"Custom", "File_Directory"});
754  std::string target_file_directory =
755  targ_config.read({"Custom", "File_Directory"});
756  std::string projectile_file_name = proj_config.read({"Custom", "File_Name"});
757  std::string target_file_name = targ_config.read({"Custom", "File_Name"});
758  // Check if files are the same for projectile and target
759  std::string proj_path =
760  custom_file_path(projectile_file_directory, projectile_file_name);
761  std::string targ_path =
762  custom_file_path(target_file_directory, target_file_name);
763  if (proj_path == targ_path) {
764  return true;
765  } else {
766  return false;
767  }
768 }
769 
770 } // namespace smash
ColliderModus: Provides a modus for colliding nuclei.
Definition: collidermodus.h:43
CalculationFrame frame_
Reference frame for the system, as specified from config.
double imp_min_
Minimum value of impact parameter.
double initial_z_displacement_
Initial z-displacement of nuclei.
double yield_max_
Maximum value of yield. Needed for custom impact parameter sampling.
bool random_reaction_plane_
Whether the reaction plane should be randomized.
void rotate_reaction_plane(double phi, Particles *particles)
Rotate the reaction plane about the angle phi.
std::pair< double, double > get_velocities(double mandelstam_s, double m_a, double m_b)
Get the frame dependent velocity for each nucleus, using the current reference frame.
void sample_impact()
Sample impact parameter.
std::unique_ptr< Nucleus > projectile_
Projectile.
std::unique_ptr< InterpolateDataLinear< double > > impact_interpolation_
Pointer to the impact parameter interpolation.
FermiMotion fermi_motion_
An option to include Fermi motion ("off", "on", "frozen")
double velocity_projectile_
Beam velocity of the projectile.
Sampling sampling_
Method used for sampling of impact parameter.
std::string custom_file_path(const std::string &file_directory, const std::string &file_name)
Creates full path string consisting of file_directory and file_name Needed to initialize a customnucl...
double total_s_
Center-of-mass energy squared of the nucleus-nucleus collision.
std::unique_ptr< Nucleus > target_
Target.
ColliderModus(Configuration modus_config, const ExperimentParameters &parameters)
Constructor.
double impact_
Impact parameter.
double sqrt_s_NN_
Center-of-mass energy of a nucleon-nucleon collision.
double initial_conditions(Particles *particles, const ExperimentParameters &parameters)
Generates initial state of the particles in the system.
bool same_inputfile(Configuration &proj_config, Configuration &targ_config)
Checks if target and projectile are read from the same external file if they are both initialized as ...
static std::unique_ptr< DeformedNucleus > create_deformed_nucleus(Configuration &nucleus_cfg, const int ntest, const std::string &nucleus_type)
Configure Deformed Nucleus.
double velocity_target_
Beam velocity of the target.
double imp_max_
Maximum value of impact parameter.
Interface to the SMASH configuration files.
bool has_value(std::initializer_list< const char * > keys) const
Returns whether there is a non-empty value behind the requested keys.
Value take(std::initializer_list< const char * > keys)
The default interface for SMASH to read configuration values.
Value read(std::initializer_list< const char * > keys) const
Additional interface for SMASH to read configuration values without removing them.
Represent a piecewise linear interpolation.
Definition: interpolation.h:62
ParticleData contains the dynamic information of a certain particle.
Definition: particledata.h:58
The Particles class abstracts the storage and manipulation of particles.
Definition: particles.h:33
The ThreeVector class represents a physical three-vector with the components .
Definition: threevector.h:31
void rotate_around_z(double theta)
Rotate the vector around the z axis by the given angle theta.
Definition: threevector.h:308
@ On
Use fermi motion in combination with potentials.
@ Frozen
Use fermi motion without potentials.
@ Off
Don't use fermi motion.
@ Quadratic
Sample from areal / quadratic distribution.
@ Custom
Sample from custom, user-defined distribution.
@ Uniform
Sample from uniform distribution.
std::ostream & operator<<(std::ostream &out, const ActionPtr &action)
Convenience: dereferences the ActionPtr to Action.
Definition: action.h:532
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > logg
An array that stores all pre-configured Logger objects.
Definition: logging.cc:39
FormattingHelper< T > format(const T &value, const char *unit, int width=-1, int precision=-1)
Acts as a stream modifier for std::ostream to output an object with an optional suffix string and wit...
Definition: logging.h:307
constexpr int p
Proton.
T uniform(T min, T max)
Definition: random.h:88
T canonical()
Definition: random.h:113
Definition: action.h:24
T pCM(const T sqrts, const T mass_a, const T mass_b) noexcept
Definition: kinematics.h:79
double fixed_target_projectile_v(double s, double ma, double mb)
Definition: kinematics.h:39
double s_from_Ekin(double e_kin, double m_P, double m_T)
Convert E_kin to Mandelstam-s for a fixed-target setup, with a projectile of mass m_P and a kinetic e...
Definition: kinematics.h:239
double center_of_velocity_v(double s, double ma, double mb)
Definition: kinematics.h:26
T pCM_from_s(const T s, const T mass_a, const T mass_b) noexcept
Definition: kinematics.h:66
static constexpr int LCollider
double s_from_Etot(double e_tot, double m_P, double m_T)
Convert E_tot to Mandelstam-s for a fixed-target setup, with a projectile of mass m_P and a total ene...
Definition: kinematics.h:211
double s_from_plab(double plab, double m_P, double m_T)
Convert p_lab to Mandelstam-s for a fixed-target setup, with a projectile of mass m_P and momentum pl...
Definition: kinematics.h:265
Thrown when either projectile_ or target_ nuclei are empty.
Helper structure for Experiment.
int testparticles
Number of test-particles.
Thrown when the requested energy is smaller than the masses of two particles.
Definition: modusdefault.h:210