Version: SMASH-3.3
scatteractionsfinder.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2014-2025
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
11 
12 #include <algorithm>
13 #include <map>
14 #include <vector>
15 
16 #include "smash/constants.h"
17 #include "smash/decaymodes.h"
18 #include "smash/logging.h"
19 #include "smash/parametrizations.h"
20 #include "smash/scatteraction.h"
23 #include "smash/stringfunctions.h"
24 
25 namespace smash {
26 static constexpr int LFindScatter = LogArea::FindScatter::id;
35  Configuration& config, const ExperimentParameters& parameters)
36  : finder_parameters_(config, parameters),
37  isotropic_(config.take(InputKeys::collTerm_isotropic)),
38  box_length_(parameters.box_length),
39  string_formation_time_(
40  config.take(InputKeys::collTerm_stringParam_formationTime)) {
42  logg[LFindScatter].info(
43  "Constant elastic isotropic cross-section mode:", " using ",
44  finder_parameters_.elastic_parameter, " mb as maximal cross-section.");
45  }
48  throw std::invalid_argument(
49  "Multi-body reactions (like e.g. 3->1 or 3->2) are only possible with "
50  "the stochastic "
51  "collision "
52  "criterion. Change your config accordingly.");
53  }
54 
57  1 &&
59  .included_2to2[IncludedReactions::PiDeuteron_to_pidprime] == 1 ||
61  .included_2to2[IncludedReactions::NDeuteron_to_Ndprime] == 1)) {
62  throw std::invalid_argument(
63  "To prevent double counting it is not possible to enable deuteron 3->2 "
64  "reactions\nand reactions involving the d' at the same time\ni.e. to "
65  "include \"Deuteron_3to2\" in `Multi_Particle_Reactions` and\n "
66  "\"PiDeuteron_to_pidprime\" "
67  "or \"NDeuteron_to_Ndprime\" in `Included_2to2` at the same time.\n"
68  "Change your config accordingly.");
69  }
70 
73  1 &&
75  throw std::invalid_argument(
76  "Do not use the d' resonance and enable \"Deuteron_3to2\" "
77  "`Multi_Particle_Reactions` at the same time. Either use the direct "
78  "3-to-2 reactions or the d' together with \"PiDeuteron_to_pidprime\" "
79  "and \"NDeuteron_to_Ndprime\" in `Included_2to2`. Otherwise the "
80  "deuteron 3-to-2 reactions would be double counted.");
81  }
82 
86  1) ||
89  1 &&
91  throw std::invalid_argument(
92  "In order to conserve detailed balance, when \"NNbar_5to2\" is "
93  "included in\n`Multi_Particle_Reactions`, the `NNbarTreatment` has to "
94  "be set to \"two to five\" and vice versa.");
95  }
96 
99  throw std::invalid_argument(
100  "'NNbar' has to be in the list of allowed 2 to 2 processes "
101  "to enable annihilation to go through resonances");
102  }
103 
105  string_process_interface_ = std::make_unique<StringProcess>(
126  parameters.use_monash_tune_default.value()));
127  }
128 }
129 
131  Configuration& config) {
132  auto sqrts_range_Npi = config.take(InputKeys::collTerm_stringTrans_rangeNpi);
133  auto sqrts_range_NN = config.take(InputKeys::collTerm_stringTrans_rangeNN);
134 
135  if (sqrts_range_Npi.first < nucleon_mass + pion_mass) {
136  sqrts_range_Npi.first = nucleon_mass + pion_mass;
137  if (sqrts_range_Npi.second < sqrts_range_Npi.first)
138  sqrts_range_Npi.second = sqrts_range_Npi.first;
139  logg[LFindScatter].warn(
140  "Lower bound of Sqrts_Range_Npi too small, setting it to mass "
141  "threshold. New range is [",
142  sqrts_range_Npi.first, ',', sqrts_range_Npi.second, "] GeV");
143  }
144  if (sqrts_range_NN.first < 2 * nucleon_mass) {
145  sqrts_range_NN.first = 2 * nucleon_mass;
146  if (sqrts_range_NN.second < sqrts_range_NN.first)
147  sqrts_range_NN.second = sqrts_range_NN.first;
148  logg[LFindScatter].warn(
149  "Lower bound of Sqrts_Range_NN too small, setting it to mass "
150  "threshold. New range is [",
151  sqrts_range_NN.first, ',', sqrts_range_NN.second, "] GeV.");
152  }
153 
154  return {sqrts_range_Npi,
155  sqrts_range_NN,
160 }
161 
163  Configuration& config, const ExperimentParameters& parameters)
164  : elastic_parameter(config.take(InputKeys::collTerm_elasticCrossSection)),
165  low_snn_cut(parameters.low_snn_cut),
166  scale_xs(parameters.scale_xs),
167  additional_el_xs(
168  config.take(InputKeys::collTerm_additionalElasticCrossSection)),
169  maximum_cross_section(parameters.maximum_cross_section),
170  coll_crit(parameters.coll_crit),
171  nnbar_treatment(parameters.nnbar_treatment),
172  included_2to2(parameters.included_2to2),
173  included_multi(parameters.included_multi),
174  testparticles(parameters.testparticles),
175  two_to_one(parameters.two_to_one),
176  allow_collisions_within_nucleus(
177  config.take(InputKeys::modi_collider_collisionWithinNucleus)),
178  spin_interaction_type(parameters.spin_interaction_type),
179  strings_switch(parameters.strings_switch),
180  use_AQM(config.take(InputKeys::collTerm_useAQM)),
181  strings_with_probability(
182  config.take(InputKeys::collTerm_stringsWithProbability)),
183  only_warn_for_high_prob(
184  config.take(InputKeys::collTerm_onlyWarnForHighProbability)),
185  transition_high_energy{create_string_transition_parameters(config)},
186  total_xs_strategy(config.take(InputKeys::collTerm_totXsStrategy)),
187  pseudoresonance_method(config.take(InputKeys::collTerm_pseudoresonance)),
188  AQM_charm_suppression(
189  config.take(InputKeys::collTerm_HF_AQMcSuppression)),
190  AQM_bottom_suppression(
191  config.take(InputKeys::collTerm_HF_AQMbSuppression)) {
193  logg[LFindScatter].info(
194  "Evaluating total cross sections from partial processes.");
195  } else if (parameters.included_2to2[IncludedReactions::Elastic] == 1 &&
196  parameters.included_2to2.count() == 1) {
197  throw std::invalid_argument(
198  "The BottomUp strategy for total cross section evaluation is needed to "
199  "have only elastic interactions, please change the configuration "
200  "accordingly.");
202  logg[LFindScatter].info(
203  "Evaluating total cross sections from parametrizations.");
205  logg[LFindScatter].info(
206  "Evaluating total cross sections from parametrizations only for "
207  "measured processes.");
208  }
209 
212  throw std::invalid_argument(
213  "Suppression factors for AQM should be between 0 and 1.");
214  }
215 }
216 
218  const ParticleData& data_a, const ParticleData& data_b, double dt,
219  const std::vector<FourVector>& beam_momentum,
220  const double gcell_vol) const {
221  /* If the two particles
222  * 1) belong to one of the two colliding nuclei, and
223  * 2) both of them have never experienced any collisions,
224  * then the collisions between them are banned. */
226  assert(data_a.id() >= 0);
227  assert(data_b.id() >= 0);
228  bool in_same_nucleus = (data_a.belongs_to() == BelongsTo::Projectile &&
229  data_b.belongs_to() == BelongsTo::Projectile) ||
230  (data_a.belongs_to() == BelongsTo::Target &&
231  data_b.belongs_to() == BelongsTo::Target);
232  bool never_interacted_before =
233  data_a.get_history().collisions_per_particle == 0 &&
234  data_b.get_history().collisions_per_particle == 0;
235  if (in_same_nucleus && never_interacted_before) {
236  return nullptr;
237  }
238  }
239 
240  // No grid or search in cell means no collision for stochastic criterion
242  gcell_vol < really_small) {
243  return nullptr;
244  }
245 
246  // Determine time of collision.
247  const double time_until_collision =
248  collision_time(data_a, data_b, dt, beam_momentum);
249 
250  // Check that collision happens in this timestep.
251  if (time_until_collision < 0. || time_until_collision >= dt) {
252  return nullptr;
253  }
254 
255  // Determine which total cross section to use
256  bool incoming_parametrized = (finder_parameters_.total_xs_strategy ==
260  const PdgCode& pdg_a = data_a.type().pdgcode();
261  const PdgCode& pdg_b = data_b.type().pdgcode();
262  incoming_parametrized = parametrization_exists(pdg_a, pdg_b);
263  }
264 
265  // Create ScatterAction object.
266  ScatterActionPtr act = std::make_unique<ScatterAction>(
267  data_a, data_b, time_until_collision, isotropic_, string_formation_time_,
268  box_length_, incoming_parametrized,
270 
272  act->set_stochastic_pos_idx();
273  }
274 
276  act->set_string_interface(string_process_interface_.get());
277  }
278 
279  // Distance squared calculation not needed for stochastic criterion
280  const double distance_squared =
282  ? act->transverse_distance_sqr()
284  ? act->cov_transverse_distance_sqr()
285  : 0.0;
286 
287  // Don't calculate cross section if the particles are very far apart.
288  // Not needed for stochastic criterion because of cell structure.
290  distance_squared >=
292  return nullptr;
293  }
294 
295  if (incoming_parametrized) {
296  act->set_parametrized_total_cross_section(finder_parameters_);
297  } else {
298  // Add various subprocesses.
299  act->add_all_scatterings(finder_parameters_);
300  }
301 
302  double xs = act->cross_section() * fm2_mb /
303  static_cast<double>(finder_parameters_.testparticles);
304 
305  // Take cross section scaling factors into account
306  xs *= data_a.xsec_scaling_factor(time_until_collision);
307  xs *= data_b.xsec_scaling_factor(time_until_collision);
308 
310  const double v_rel = act->relative_velocity();
311  /* Collision probability for 2-particle scattering, see
312  * \iref{Staudenmaier:2021lrg}. */
313  const double prob = xs * v_rel * dt / gcell_vol;
314 
315  logg[LFindScatter].debug(
316  "Stochastic collison criterion parameters (2-particles):\nprob = ",
317  prob, ", xs = ", xs, ", v_rel = ", v_rel, ", dt = ", dt,
318  ", gcell_vol = ", gcell_vol,
319  ", testparticles = ", finder_parameters_.testparticles);
320 
321  if (prob > 1.) {
322  std::stringstream err;
323  err << "Probability larger than 1 for stochastic rates. ( P_22 = " << prob
324  << " )\n"
325  << data_a.type().name() << data_b.type().name() << " with masses "
326  << data_a.momentum().abs() << " and " << data_b.momentum().abs()
327  << " at sqrts[GeV] = " << act->sqrt_s()
328  << " with xs[fm^2]/Ntest = " << xs
329  << "\nConsider using smaller timesteps.";
331  logg[LFindScatter].warn(err.str());
332  } else {
333  throw std::runtime_error(err.str());
334  }
335  }
336 
337  // probability criterion
338  double random_no = random::uniform(0., 1.);
339  if (random_no > prob) {
340  return nullptr;
341  }
342 
345  // just collided with this particle
346  if (data_a.id_process() > 0 && data_a.id_process() == data_b.id_process()) {
347  logg[LFindScatter].debug("Skipping collided particles at time ",
348  data_a.position().x0(), " due to process ",
349  data_a.id_process(), "\n ", data_a, "\n<-> ",
350  data_b);
351 
352  return nullptr;
353  }
354 
355  // Cross section for collision criterion
356  const double cross_section_criterion = xs * M_1_PI;
357 
358  // distance criterion according to cross_section
359  if (distance_squared >= cross_section_criterion) {
360  return nullptr;
361  }
362 
363  logg[LFindScatter].debug("particle distance squared: ", distance_squared,
364  "\n ", data_a, "\n<-> ", data_b);
365  }
366 
367  // Include possible outgoing branches
368  if (incoming_parametrized) {
369  act->add_all_scatterings(finder_parameters_);
370  }
371 
372  return act;
373 }
374 
376  const ParticleList& plist, double dt, const double gcell_vol) const {
377  /* If all particles
378  * 1) belong to the two colliding nuclei
379  * 2) are within the same nucleus
380  * 3) have never experienced any collisons,
381  * then the collision between them are banned also for multi-particle
382  * interactions. */
384  bool all_projectile =
385  std::all_of(plist.begin(), plist.end(), [&](const ParticleData& data) {
386  return data.belongs_to() == BelongsTo::Projectile;
387  });
388  bool all_target =
389  std::all_of(plist.begin(), plist.end(), [&](const ParticleData& data) {
390  return data.belongs_to() == BelongsTo::Target;
391  });
392  bool none_collided =
393  std::all_of(plist.begin(), plist.end(), [&](const ParticleData& data) {
394  return data.get_history().collisions_per_particle == 0;
395  });
396  if ((all_projectile || all_target) && none_collided) {
397  return nullptr;
398  }
399  }
400  // No grid or search in cell
401  if (gcell_vol < really_small) {
402  return nullptr;
403  }
404 
405  /* Optimisation for later: Already check here at the beginning
406  * if collision with plist is possible before constructing actions. */
407 
408  // 1. Determine time of collision.
409  const double time_until_collision = dt * random::uniform(0., 1.);
410 
411  // 2. Create ScatterAction object.
412  ScatterActionMultiPtr act =
413  std::make_unique<ScatterActionMulti>(plist, time_until_collision);
414 
415  act->set_stochastic_pos_idx();
416 
417  // 3. Add possible final states (dt and gcell_vol for probability calculation)
418  act->add_possible_reactions(dt, gcell_vol, finder_parameters_.included_multi);
419 
420  /* 4. Return total collision probability
421  * Scales with 1 over the number of testpartciles to the power of the
422  * number of incoming particles - 1 */
423  const double prob =
424  act->get_total_weight() /
425  std::pow(finder_parameters_.testparticles, plist.size() - 1);
426 
427  // 5. Check that probability is smaller than one
428  if (prob > 1.) {
429  std::stringstream err;
430  err << "Probability " << prob << " larger than 1 for stochastic rates for ";
431  for (const ParticleData& data : plist) {
432  err << data.type().name();
433  }
434  err << " at sqrts[GeV] = " << act->sqrt_s()
435  << "\nConsider using smaller timesteps.";
437  logg[LFindScatter].warn(err.str());
438  } else {
439  throw std::runtime_error(err.str());
440  }
441  }
442 
443  // 6. Perform probability decisions
444  double random_no = random::uniform(0., 1.);
445  if (random_no > prob) {
446  return nullptr;
447  }
448 
449  return act;
450 }
451 
453  const ParticleList& search_list, double dt, const double gcell_vol,
454  const std::vector<FourVector>& beam_momentum) const {
455  std::vector<ActionPtr> actions;
456  for (const ParticleData& p1 : search_list) {
457  for (const ParticleData& p2 : search_list) {
458  // Check for 2 particle scattering
459  if (p1.id() < p2.id()) {
460  ActionPtr act =
461  check_collision_two_part(p1, p2, dt, beam_momentum, gcell_vol);
462  if (act) {
463  actions.push_back(std::move(act));
464  }
465  }
467  // Also, check for 3 particle scatterings with stochastic criterion
468  for (const ParticleData& p3 : search_list) {
473  if (p1.id() < p2.id() && p2.id() < p3.id()) {
474  ActionPtr act =
475  check_collision_multi_part({p1, p2, p3}, dt, gcell_vol);
476  if (act) {
477  actions.push_back(std::move(act));
478  }
479  }
480  }
481  for (const ParticleData& p4 : search_list) {
484  if (p1.id() < p2.id() && p2.id() < p3.id() && p3.id() < p4.id()) {
485  ActionPtr act =
486  check_collision_multi_part({p1, p2, p3, p4}, dt, gcell_vol);
487  if (act) {
488  actions.push_back(std::move(act));
489  }
490  }
491  }
494  search_list.size() >= 5) {
495  for (const ParticleData& p5 : search_list) {
496  if ((p1.id() < p2.id() && p2.id() < p3.id() &&
497  p3.id() < p4.id() && p4.id() < p5.id()) &&
498  (p1.is_pion() && p2.is_pion() && p3.is_pion() &&
499  p4.is_pion() && p5.is_pion())) {
500  // at the moment only pure pion 5-body reactions
501  ActionPtr act = check_collision_multi_part(
502  {p1, p2, p3, p4, p5}, dt, gcell_vol);
503  if (act) {
504  actions.push_back(std::move(act));
505  }
506  }
507  }
508  }
509  }
510  }
511  }
512  }
513  }
514  return actions;
515 }
516 
518  const ParticleList& search_list, const ParticleList& neighbors_list,
519  double dt, const std::vector<FourVector>& beam_momentum) const {
520  std::vector<ActionPtr> actions;
522  // Only search in cells
523  return actions;
524  }
525  for (const ParticleData& p1 : search_list) {
526  for (const ParticleData& p2 : neighbors_list) {
527  assert(p1.id() != p2.id());
528  // Check if a collision is possible.
529  ActionPtr act = check_collision_two_part(p1, p2, dt, beam_momentum);
530  if (act) {
531  actions.push_back(std::move(act));
532  }
533  }
534  }
535  return actions;
536 }
537 
539  const ParticleList& search_list, const Particles& surrounding_list,
540  double dt, const std::vector<FourVector>& beam_momentum) const {
541  std::vector<ActionPtr> actions;
543  // Only search in cells
544  return actions;
545  }
546  for (const ParticleData& p2 : surrounding_list) {
547  /* don't look for collisions if the particle from the surrounding list is
548  * also in the search list */
549  auto result = std::find_if(
550  search_list.begin(), search_list.end(),
551  [&p2](const ParticleData& p) { return p.id() == p2.id(); });
552  if (result != search_list.end()) {
553  continue;
554  }
555  for (const ParticleData& p1 : search_list) {
556  // Check if a collision is possible.
557  ActionPtr act = check_collision_two_part(p1, p2, dt, beam_momentum);
558  if (act) {
559  actions.push_back(std::move(act));
560  }
561  }
562  }
563  return actions;
564 }
565 
567  constexpr double time = 0.0;
568 
569  const size_t N_isotypes = IsoParticleType::list_all().size();
570  const size_t N_pairs = N_isotypes * (N_isotypes - 1) / 2;
571 
572  std::cout << N_isotypes << " iso-particle types." << std::endl;
573  std::cout << "They can make " << N_pairs << " pairs." << std::endl;
574  std::vector<double> momentum_scan_list = {0.1, 0.3, 0.5, 1.0,
575  2.0, 3.0, 5.0, 10.0};
576  for (const IsoParticleType& A_isotype : IsoParticleType::list_all()) {
577  for (const IsoParticleType& B_isotype : IsoParticleType::list_all()) {
578  if (&A_isotype > &B_isotype) {
579  continue;
580  }
581  bool any_nonzero_cs = false;
582  std::vector<std::string> r_list;
583  for (const ParticleTypePtr A_type : A_isotype.get_states()) {
584  for (const ParticleTypePtr B_type : B_isotype.get_states()) {
585  if (A_type > B_type) {
586  continue;
587  }
588  ParticleData A(*A_type), B(*B_type);
589  for (auto mom : momentum_scan_list) {
590  A.set_4momentum(A.pole_mass(), mom, 0.0, 0.0);
591  B.set_4momentum(B.pole_mass(), -mom, 0.0, 0.0);
592  ScatterActionPtr act = std::make_unique<ScatterAction>(
593  A, B, time, isotropic_, string_formation_time_, -1, false,
596  act->set_string_interface(string_process_interface_.get());
597  }
598  act->add_all_scatterings(finder_parameters_);
599  const double total_cs = act->cross_section();
600  if (total_cs <= 0.0) {
601  continue;
602  }
603  any_nonzero_cs = true;
604  for (const auto& channel : act->collision_channels()) {
605  const auto type = channel->get_type();
606  std::string r;
607  if (is_string_soft_process(type) ||
608  type == ProcessType::StringHard) {
609  r = A_type->name() + B_type->name() + std::string(" → strings");
610  } else {
611  std::string r_type =
612  (type == ProcessType::Elastic) ? std::string(" (el)")
613  : (channel->get_type() == ProcessType::TwoToTwo)
614  ? std::string(" (inel)")
615  : std::string(" (?)");
616  r = A_type->name() + B_type->name() + std::string(" → ") +
617  channel->particle_types()[0]->name() +
618  channel->particle_types()[1]->name() + r_type;
619  }
620  isoclean(r);
621  r_list.push_back(r);
622  }
623  }
624  }
625  }
626  std::sort(r_list.begin(), r_list.end());
627  r_list.erase(std::unique(r_list.begin(), r_list.end()), r_list.end());
628  if (any_nonzero_cs) {
629  for (auto r : r_list) {
630  std::cout << r;
631  if (r_list.back() != r) {
632  std::cout << ", ";
633  }
634  }
635  std::cout << std::endl;
636  }
637  }
638  }
639 }
640 
644  std::string name_;
645 
648 
650  double mass_;
651 
660  FinalStateCrossSection(const std::string& name, double cross_section,
661  double mass)
662  : name_(name), cross_section_(cross_section), mass_(mass) {}
663 };
664 
665 namespace decaytree {
666 
678 struct Node {
679  public:
681  std::string name_;
682 
684  double weight_;
685 
687  ParticleTypePtrList initial_particles_;
688 
690  ParticleTypePtrList final_particles_;
691 
693  ParticleTypePtrList state_;
694 
696  std::vector<Node> children_;
697 
699  Node(const Node&) = delete;
701  Node(Node&&) = default;
702 
713  Node(const std::string& name, double weight,
714  ParticleTypePtrList&& initial_particles,
715  ParticleTypePtrList&& final_particles, ParticleTypePtrList&& state,
716  std::vector<Node>&& children)
717  : name_(name),
718  weight_(weight),
719  initial_particles_(std::move(initial_particles)),
720  final_particles_(std::move(final_particles)),
721  state_(std::move(state)),
722  children_(std::move(children)) {}
723 
735  Node& add_action(const std::string& name, double weight,
736  ParticleTypePtrList&& initial_particles,
737  ParticleTypePtrList&& final_particles) {
738  // Copy parent state and update it.
739  ParticleTypePtrList state(state_);
740  for (const auto& p : initial_particles) {
741  state.erase(std::find(state.begin(), state.end(), p));
742  }
743  for (const auto& p : final_particles) {
744  state.push_back(p);
745  }
746  // Sort the state to normalize the output.
747  std::sort(state.begin(), state.end(),
749  return a->name() < b->name();
750  });
751  // Push new node to children.
752  Node new_node(name, weight, std::move(initial_particles),
753  std::move(final_particles), std::move(state), {});
754  children_.emplace_back(std::move(new_node));
755  return children_.back();
756  }
757 
759  void print() const { print_helper(0); }
760 
764  std::vector<FinalStateCrossSection> final_state_cross_sections() const {
765  std::vector<FinalStateCrossSection> result;
766  final_state_cross_sections_helper(0, result, "", 1.);
767  return result;
768  }
769 
770  private:
777  void print_helper(uint64_t depth) const {
778  for (uint64_t i = 0; i < depth; i++) {
779  std::cout << " ";
780  }
781  std::cout << name_ << " " << weight_ << std::endl;
782  for (const auto& child : children_) {
783  child.print_helper(depth + 1);
784  }
785  }
786 
799  uint64_t depth, std::vector<FinalStateCrossSection>& result,
800  const std::string& name, double weight,
801  bool show_intermediate_states = false) const {
802  // The first node corresponds to the total cross section and has to be
803  // ignored. The second node corresponds to the partial cross section. All
804  // further nodes correspond to branching ratios.
805  if (depth > 0) {
806  weight *= weight_;
807  }
808 
809  std::string new_name;
810  double mass = 0.;
811 
812  if (show_intermediate_states) {
813  new_name = name;
814  if (!new_name.empty()) {
815  new_name += "->";
816  }
817  new_name += name_;
818  new_name += "{";
819  } else {
820  new_name = "";
821  }
822  for (const auto& s : state_) {
823  new_name += s->name();
824  mass += s->mass();
825  }
826  if (show_intermediate_states) {
827  new_name += "}";
828  }
829 
830  if (children_.empty()) {
831  result.emplace_back(FinalStateCrossSection(new_name, weight, mass));
832  return;
833  }
834  for (const auto& child : children_) {
835  child.final_state_cross_sections_helper(depth + 1, result, new_name,
836  weight, show_intermediate_states);
837  }
838  }
839 };
840 
849 static std::string make_decay_name(const std::string& res_name,
850  const DecayBranchPtr& decay,
851  ParticleTypePtrList& final_state) {
852  std::stringstream name;
853  name << "[" << res_name << "->";
854  for (const auto& p : decay->particle_types()) {
855  name << p->name();
856  final_state.push_back(p);
857  }
858  name << "]";
859  return name.str();
860 }
861 
869 static void add_decays(Node& node, double sqrts) {
870  // If there is more than one unstable particle in the current state, then
871  // there will be redundant paths in the decay tree, corresponding to
872  // reorderings of the decays. To avoid double counting, we normalize by the
873  // number of possible decay orderings. Normalizing by the number of unstable
874  // particles recursively corresponds to normalizing by the factorial that
875  // gives the number of reorderings.
876  //
877  // Ideally, the redundant paths should never be added to the decay tree, but
878  // we never have more than two redundant paths, so it probably does not
879  // matter much.
880  uint32_t n_unstable = 0;
881  double sqrts_minus_masses = sqrts;
882  for (const ParticleTypePtr ptype : node.state_) {
883  if (!ptype->is_stable()) {
884  n_unstable += 1;
885  }
886  sqrts_minus_masses -= ptype->mass();
887  }
888  const double norm =
889  n_unstable != 0 ? 1. / static_cast<double>(n_unstable) : 1.;
890 
891  for (const ParticleTypePtr ptype : node.state_) {
892  if (!ptype->is_stable()) {
893  const double sqrts_decay = sqrts_minus_masses + ptype->mass();
894  bool can_decay = false;
895  for (const auto& decay : ptype->decay_modes().decay_mode_list()) {
896  // Make sure to skip kinematically impossible decays.
897  // In principle, we would have to integrate over the mass of the
898  // resonance, but as an approximation we just assume it at its pole.
899  double final_state_mass = 0.;
900  for (const auto& p : decay->particle_types()) {
901  final_state_mass += p->mass();
902  }
903  if (final_state_mass > sqrts_decay) {
904  continue;
905  }
906  can_decay = true;
907 
908  ParticleTypePtrList parts;
909  const auto name = make_decay_name(ptype->name(), decay, parts);
910  auto& new_node = node.add_action(name, norm * decay->weight(), {ptype},
911  std::move(parts));
912  add_decays(new_node, sqrts_decay);
913  }
914  if (!can_decay) {
915  // Remove final-state cross sections with resonances that cannot
916  // decay due to our "mass = pole mass" approximation.
917  node.weight_ = 0;
918  return;
919  }
920  }
921  }
922 }
923 
924 } // namespace decaytree
925 
931 static void deduplicate(std::vector<FinalStateCrossSection>& final_state_xs) {
932  std::sort(final_state_xs.begin(), final_state_xs.end(),
933  [](const FinalStateCrossSection& a,
934  const FinalStateCrossSection& b) { return a.name_ < b.name_; });
935  auto current = final_state_xs.begin();
936  while (current != final_state_xs.end()) {
937  auto adjacent = std::adjacent_find(
938  current, final_state_xs.end(),
939  [](const FinalStateCrossSection& a, const FinalStateCrossSection& b) {
940  return a.name_ == b.name_;
941  });
942  current = adjacent;
943  if (adjacent != final_state_xs.end()) {
944  adjacent->cross_section_ += (adjacent + 1)->cross_section_;
945  final_state_xs.erase(adjacent + 1);
946  }
947  }
948 }
949 
951  const ParticleType& a, const ParticleType& b, double m_a, double m_b,
952  bool final_state, std::vector<double>& plab) const {
953  typedef std::vector<std::pair<double, double>> xs_saver;
954  std::map<std::string, xs_saver> xs_dump;
955  std::map<std::string, double> outgoing_total_mass;
956 
957  ParticleData a_data(a), b_data(b);
958  int n_momentum_points = 200;
959  constexpr double momentum_step = 0.02;
960  if (plab.size() > 0) {
961  n_momentum_points = plab.size();
962  // Remove duplicates.
963  std::sort(plab.begin(), plab.end());
964  plab.erase(std::unique(plab.begin(), plab.end()), plab.end());
965  }
966  for (int i = 0; i < n_momentum_points; i++) {
967  double momentum;
968  if (plab.size() > 0) {
969  momentum = pCM_from_s(s_from_plab(plab.at(i), m_a, m_b), m_a, m_b);
970  } else {
971  momentum = momentum_step * (i + 1);
972  }
973  a_data.set_4momentum(m_a, momentum, 0.0, 0.0);
974  b_data.set_4momentum(m_b, -momentum, 0.0, 0.0);
975  const double sqrts = (a_data.momentum() + b_data.momentum()).abs();
976  const ParticleList incoming = {a_data, b_data};
977  ScatterActionPtr act = std::make_unique<ScatterAction>(
978  a_data, b_data, 0., isotropic_, string_formation_time_, -1, false,
981  act->set_string_interface(string_process_interface_.get());
982  }
983  act->add_all_scatterings(finder_parameters_);
984  decaytree::Node tree(a.name() + b.name(), act->cross_section(), {&a, &b},
985  {&a, &b}, {&a, &b}, {});
986  const CollisionBranchList& processes = act->collision_channels();
987  for (const auto& process : processes) {
988  const double xs = process->weight();
989  if (xs <= 0.0) {
990  continue;
991  }
992  if (!final_state) {
993  std::stringstream process_description_stream;
994  process_description_stream << *process;
995  const std::string& description = process_description_stream.str();
996  double m_tot = 0.0;
997  for (const auto& ptype : process->particle_types()) {
998  m_tot += ptype->mass();
999  }
1000  outgoing_total_mass[description] = m_tot;
1001  if (!xs_dump[description].empty() &&
1002  std::abs(xs_dump[description].back().first - sqrts) <
1003  really_small) {
1004  xs_dump[description].back().second += xs;
1005  } else {
1006  xs_dump[description].push_back(std::make_pair(sqrts, xs));
1007  }
1008  } else {
1009  std::stringstream process_description_stream;
1010  process_description_stream << *process;
1011  const std::string& description = process_description_stream.str();
1012  ParticleTypePtrList initial_particles = {&a, &b};
1013  ParticleTypePtrList final_particles = process->particle_types();
1014  auto& process_node =
1015  tree.add_action(description, xs, std::move(initial_particles),
1016  std::move(final_particles));
1017  decaytree::add_decays(process_node, sqrts);
1018  }
1019  }
1020  xs_dump["total"].push_back(std::make_pair(sqrts, act->cross_section()));
1021  // Total cross-section should be the first in the list -> negative mass
1022  outgoing_total_mass["total"] = -1.0;
1023  if (final_state) {
1024  // tree.print();
1025  auto final_state_xs = tree.final_state_cross_sections();
1026  deduplicate(final_state_xs);
1027  for (const auto& p : final_state_xs) {
1028  // Don't print empty columns.
1029  //
1030  // FIXME(steinberg): The better fix would be to not have them in the
1031  // first place.
1032  if (p.name_ == "") {
1033  continue;
1034  }
1035  outgoing_total_mass[p.name_] = p.mass_;
1036  xs_dump[p.name_].push_back(std::make_pair(sqrts, p.cross_section_));
1037  }
1038  }
1039  }
1040  // Get rid of cross sections that are zero.
1041  // (This only happens if their is a resonance in the final state that cannot
1042  // decay with our simplified assumptions.)
1043  for (auto it = begin(xs_dump); it != end(xs_dump);) {
1044  // Sum cross section over all energies.
1045  const xs_saver& xs = (*it).second;
1046  double sum = 0;
1047  for (const auto& p : xs) {
1048  sum += p.second;
1049  }
1050  if (sum == 0.) {
1051  it = xs_dump.erase(it);
1052  } else {
1053  ++it;
1054  }
1055  }
1056 
1057  // Nice ordering of channels by summed pole mass of products
1058  std::vector<std::string> all_channels;
1059  for (const auto& channel : xs_dump) {
1060  all_channels.push_back(channel.first);
1061  }
1062  std::sort(all_channels.begin(), all_channels.end(),
1063  [&](const std::string& str_a, const std::string& str_b) {
1064  return outgoing_total_mass[str_a] < outgoing_total_mass[str_b];
1065  });
1066 
1067  // Print header
1068  std::cout << "# Dumping partial " << a.name() << b.name()
1069  << " cross-sections in mb, energies in GeV" << std::endl;
1070  std::cout << " sqrt_s";
1071  // Align everything to 24 unicode characters.
1072  // This should be enough for the longest channel name: 7 final-state
1073  // particles, or 2 of the longest named resonances (currently Ds0*(2317)⁺).
1074  for (const auto& channel : all_channels) {
1075  std::cout << utf8::fill_left(channel, 24, ' ');
1076  }
1077  std::cout << std::endl;
1078 
1079  // Print out all partial cross-sections in mb
1080  for (int i = 0; i < n_momentum_points; i++) {
1081  double momentum;
1082  if (plab.size() > 0) {
1083  momentum = pCM_from_s(s_from_plab(plab.at(i), m_a, m_b), m_a, m_b);
1084  } else {
1085  momentum = momentum_step * (i + 1);
1086  }
1087  a_data.set_4momentum(m_a, momentum, 0.0, 0.0);
1088  b_data.set_4momentum(m_b, -momentum, 0.0, 0.0);
1089  const double sqrts = (a_data.momentum() + b_data.momentum()).abs();
1090  std::printf("%9.6f", sqrts);
1091  for (const auto& channel : all_channels) {
1092  const xs_saver energy_and_xs = xs_dump[channel];
1093  size_t j = 0;
1094  for (; j < energy_and_xs.size() && energy_and_xs[j].first < sqrts; j++) {
1095  }
1096  double xs = 0.0;
1097  if (j < energy_and_xs.size() &&
1098  std::abs(energy_and_xs[j].first - sqrts) < really_small) {
1099  xs = energy_and_xs[j].second;
1100  }
1101  std::printf("%16.6f", xs); // Same alignment as in the header.
1102  }
1103  std::printf("\n");
1104  }
1105 }
1106 
1107 } // namespace smash
Interface to the SMASH configuration files.
T take(const Key< T > &key)
The default interface for SMASH to read configuration values.
const DecayBranchList & decay_mode_list() const
Definition: decaymodes.h:63
double abs() const
calculate the lorentz invariant absolute value
Definition: fourvector.h:464
double x0() const
Definition: fourvector.h:313
IsoParticleType is a class to represent isospin multiplets.
static const IsoParticleTypeList & list_all()
Returns a list of all IsoParticleTypes.
ParticleData contains the dynamic information of a certain particle.
Definition: particledata.h:59
void set_4momentum(const FourVector &momentum_vector)
Set the particle's 4-momentum directly.
Definition: particledata.h:177
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.
uint32_t id_process() const
Get the id of the last action.
Definition: particledata.h:138
const FourVector & momentum() const
Get the particle's 4-momentum.
Definition: particledata.h:171
BelongsTo belongs_to() const
Getter for belongs_to label.
Definition: particledata.h:397
int32_t id() const
Get the id of the particle.
Definition: particledata.h:77
HistoryData get_history() const
Get history information.
Definition: particledata.h:143
double pole_mass() const
Get the particle's pole mass ("on-shell").
Definition: particledata.h:119
const FourVector & position() const
Get the particle's position in Minkowski space.
Definition: particledata.h:217
A pointer-like interface to global references to ParticleType objects.
Definition: particletype.h:682
Particle type contains the static properties of a particle species.
Definition: particletype.h:98
const DecayModes & decay_modes() const
static const ParticleTypePtr try_find(PdgCode pdgcode)
Returns the ParticleTypePtr for the given pdgcode.
Definition: particletype.cc:89
PdgCode pdgcode() const
Definition: particletype.h:157
const std::string & name() const
Definition: particletype.h:142
bool is_stable() const
Definition: particletype.h:249
double mass() const
Definition: particletype.h:145
The Particles class abstracts the storage and manipulation of particles.
Definition: particles.h:33
PdgCode stores a Particle Data Group Particle Numbering Scheme particle type number.
Definition: pdgcode.h:108
const bool only_warn_for_high_prob
Switch to turn off throwing an exception for collision probabilities larger than 1.
const int testparticles
Number of test particles.
ScatterActionsFinderParameters(Configuration &config, const ExperimentParameters &parameters)
Class constructor.
const ReactionsBitSet included_2to2
List of included 2<->2 reactions.
const double elastic_parameter
Elastic cross section parameter (in mb).
const bool strings_switch
Indicates whether string fragmentation is switched on.
const TotalCrossSectionStrategy total_xs_strategy
Method used to evaluate total cross sections for collision finding.
const double AQM_charm_suppression
Factor to reduce cross sections for charmed hadrons.
const MultiParticleReactionsBitSet included_multi
List of included multi-particle reactions.
const bool allow_collisions_within_nucleus
If particles within the same nucleus are allowed to collide for their first time.
const NNbarTreatment nnbar_treatment
Switch for NNbar reactions.
const double AQM_bottom_suppression
Factor to reduce cross sections for bottomed hadrons.
const CollisionCriterion coll_crit
Specifies which collision criterion is used.
const SpinInteractionType spin_interaction_type
Switch to control whether to include spin interactions.
ScatterActionsFinder(Configuration &config, const ExperimentParameters &parameters)
Constructor of the finder with the given parameters.
ScatterActionsFinderParameters finder_parameters_
Struct collecting several parameters.
ActionList find_actions_in_cell(const ParticleList &search_list, double dt, const double gcell_vol, const std::vector< FourVector > &beam_momentum) const override
Search for all the possible collisions within one cell.
void dump_cross_sections(const ParticleType &a, const ParticleType &b, double m_a, double m_b, bool final_state, std::vector< double > &plab) const
Print out partial cross-sections of all processes that can occur in the collision of a(mass = m_a) an...
const bool isotropic_
Do all collisions isotropically.
double max_transverse_distance_sqr(int testparticles) const
The maximal distance over which particles can interact in case of the geometric criterion,...
ActionPtr check_collision_two_part(const ParticleData &data_a, const ParticleData &data_b, double dt, const std::vector< FourVector > &beam_momentum={}, const double gcell_vol=0.0) const
Check for a single pair of particles (id_a, id_b) if a collision will happen in the next timestep and...
const double string_formation_time_
Parameter for formation time.
std::unique_ptr< StringProcess > string_process_interface_
Class that deals with strings, interfacing Pythia.
void dump_reactions() const
Prints out all the 2-> n (n > 1) reactions with non-zero cross-sections between all possible pairs of...
bool is_constant_elastic_isotropic() const
If there is only one particle sort, no decays (only elastic scatterings are possible),...
ActionPtr check_collision_multi_part(const ParticleList &plist, double dt, const double gcell_vol) const
Check for multiple i.e.
const double box_length_
Box length: needed to determine coordinates of collision correctly in case of collision through the w...
ActionList find_actions_with_surrounding_particles(const ParticleList &search_list, const Particles &surrounding_list, double dt, const std::vector< FourVector > &beam_momentum) const override
Search for all the possible secondary collisions between the outgoing particles and the rest.
ActionList find_actions_with_neighbors(const ParticleList &search_list, const ParticleList &neighbors_list, double dt, const std::vector< FourVector > &beam_momentum) const override
Search for all the possible collisions among the neighboring cells.
double collision_time(const ParticleData &p1, const ParticleData &p2, double dt, const std::vector< FourVector > &beam_momentum) const
Determine the collision time of the two particles.
Collection of useful constants that are known at compile time.
@ TwoToFive
Directly create 5 pions, use with multi-particle reactions.
@ Resonances
Use intermediate Resonances.
@ TopDownMeasured
Mix the two above, using the parametrizations only for measured processes, and summing up partials fo...
@ TopDown
Use parametrizations based on existing data, rescaling with AQM for unmeasured processes.
@ BottomUp
Sum the existing partial contributions.
@ NNbar_5to2
@ A3_Nuclei_4to2
@ Deuteron_3to2
@ Meson_3to1
@ Stochastic
Stochastic Criteiron.
@ Geometric
Geometric criterion.
@ Covariant
Covariant Criterion.
@ PiDeuteron_to_pidprime
@ NDeuteron_to_Ndprime
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > logg
An array that stores all pre-configured Logger objects.
Definition: logging.cc:40
static std::string make_decay_name(const std::string &res_name, const DecayBranchPtr &decay, ParticleTypePtrList &final_state)
Generate name for decay and update final state.
static void add_decays(Node &node, double sqrts)
Add nodes for all decays possible from the given node and all of its children.
constexpr int p
Proton.
constexpr int64_t dprime
Deuteron-prime resonance.
T uniform(T min, T max)
Definition: random.h:88
std::string fill_left(const std::string &s, size_t width, char fill=' ')
Fill string with characters to the left until the given width is reached.
Definition: action.h:24
bool parametrization_exists(const PdgCode &pdg_a, const PdgCode &pdg_b)
Checks if supplied codes have existing parametrizations of total cross sections.
static void deduplicate(std::vector< FinalStateCrossSection > &final_state_xs)
Deduplicate the final-state cross sections by summing.
@ TwoToTwo
See here for a short description.
@ Elastic
See here for a short description.
@ StringHard
See here for a short description.
bool all_of(Container &&c, UnaryPredicate &&p)
Convenience wrapper for std::all_of that operates on a complete container.
Definition: algorithms.h:80
static StringTransitionParameters create_string_transition_parameters(Configuration &config)
constexpr double nucleon_mass
Nucleon mass in GeV.
Definition: constants.h:62
constexpr double pion_mass
Pion mass in GeV.
Definition: constants.h:69
T pCM_from_s(const T s, const T mass_a, const T mass_b) noexcept
Definition: kinematics.h:66
constexpr double really_small
Numerical error tolerance.
Definition: constants.h:41
void isoclean(std::string &s)
Remove ⁺, ⁻, ⁰ from string.
static constexpr int LFindScatter
bool is_string_soft_process(ProcessType p)
Check if a given process type is a soft string excitation.
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
constexpr double fm2_mb
mb <-> fm^2 conversion factor.
Definition: constants.h:32
Helper structure for Experiment.
std::optional< bool > use_monash_tune_default
Bool for the default usage of the monash tune in the collider modus.
const ReactionsBitSet included_2to2
This indicates which two to two reactions are switched off.
Represent a final-state cross section.
FinalStateCrossSection(const std::string &name, double cross_section, double mass)
Construct a final-state cross section.
std::string name_
Name of the final state.
double cross_section_
Corresponding cross section in mb.
double mass_
Total mass of final state particles.
int32_t collisions_per_particle
Collision counter per particle, zero only for initially present particles.
Definition: particledata.h:33
A container to keep track of all ever existed input keys.
Definition: input_keys.h:1116
static const Key< double > collTerm_stringParam_probabilityPToDUU
See user guide description for more information.
Definition: input_keys.h:2959
static const Key< double > collTerm_stringTrans_lower
See user guide description for more information.
Definition: input_keys.h:2754
static const Key< std::pair< double, double > > collTerm_stringTrans_rangeNpi
See user guide description for more information.
Definition: input_keys.h:2789
static const Key< double > collTerm_stringTrans_range_width
See user guide description for more information.
Definition: input_keys.h:2805
static const Key< bool > collTerm_stringParam_useMonashTune
See user guide description for more information.
Definition: input_keys.h:3112
static const Key< double > collTerm_stringParam_sigmaPerp
See user guide description for more information.
Definition: input_keys.h:2994
static const Key< double > collTerm_stringParam_gluonPMin
See user guide description for more information.
Definition: input_keys.h:2870
static const Key< double > collTerm_stringParam_stringZALeading
See user guide description for more information.
Definition: input_keys.h:3067
static const Key< bool > collTerm_stringParam_mDependentFormationTimes
See user guide description for more information.
Definition: input_keys.h:2884
static const Key< double > collTerm_stringParam_diquarkSuppression
See user guide description for more information.
Definition: input_keys.h:2818
static const Key< double > collTerm_stringParam_formTimeFactor
See user guide description for more information.
Definition: input_keys.h:2831
static const Key< double > collTerm_stringParam_quarkAlpha
See user guide description for more information.
Definition: input_keys.h:2899
static const Key< double > collTerm_stringParam_stringZA
See user guide description for more information.
Definition: input_keys.h:3053
static const Key< double > collTerm_stringParam_strangeSuppression
See user guide description for more information.
Definition: input_keys.h:3012
static const Key< double > collTerm_stringParam_stringSigmaT
See user guide description for more information.
Definition: input_keys.h:3025
static const Key< double > collTerm_stringParam_stringZB
See user guide description for more information.
Definition: input_keys.h:3080
static const Key< double > collTerm_stringParam_quarkBeta
See user guide description for more information.
Definition: input_keys.h:2912
static const Key< double > collTerm_stringParam_popcornRate
See user guide description for more information.
Definition: input_keys.h:2927
static const Key< double > collTerm_stringParam_stringZBLeading
See user guide description for more information.
Definition: input_keys.h:3095
static const Key< double > collTerm_stringTrans_pipiOffset
See user guide description for more information.
Definition: input_keys.h:2741
static const Key< double > collTerm_stringParam_stringTension
See user guide description for more information.
Definition: input_keys.h:3040
static const Key< double > collTerm_stringTrans_KNOffset
See user guide description for more information.
Definition: input_keys.h:2726
static const Key< std::pair< double, double > > collTerm_stringTrans_rangeNN
See user guide description for more information.
Definition: input_keys.h:2772
static const Key< double > collTerm_stringParam_gluonBeta
See user guide description for more information.
Definition: input_keys.h:2856
static const Key< bool > collTerm_stringParam_separateFragmentBaryon
See user guide description for more information.
Definition: input_keys.h:2974
Constants related to transition between low and high collision energies.
Node of a decay tree, representing a possible action (2-to-2 or 1-to-2).
std::vector< Node > children_
Possible actions after this action.
void final_state_cross_sections_helper(uint64_t depth, std::vector< FinalStateCrossSection > &result, const std::string &name, double weight, bool show_intermediate_states=false) const
Internal helper function for final_state_cross_sections, to be called recursively to calculate all fi...
ParticleTypePtrList final_particles_
Final-state particle types in this action.
std::vector< FinalStateCrossSection > final_state_cross_sections() const
Node & add_action(const std::string &name, double weight, ParticleTypePtrList &&initial_particles, ParticleTypePtrList &&final_particles)
Add an action to the children of this node.
ParticleTypePtrList initial_particles_
Initial-state particle types in this action.
void print() const
Print the decay tree starting with this node.
void print_helper(uint64_t depth) const
Internal helper function for print, to be called recursively to print all nodes.
double weight_
Weight (cross section or branching ratio).
Node(const Node &)=delete
Cannot be copied.
Node(const std::string &name, double weight, ParticleTypePtrList &&initial_particles, ParticleTypePtrList &&final_particles, ParticleTypePtrList &&state, std::vector< Node > &&children)
Node(Node &&)=default
Move constructor.
ParticleTypePtrList state_
Particle types corresponding to the global state after this action.
std::string name_
Name for printing.