Version: SMASH-3.4
scatteractionsfinder.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2014-2026
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
11 
12 #include <algorithm>
13 #include <map>
14 #include <stdexcept>
15 #include <vector>
16 
17 #include "smash/constants.h"
18 #include "smash/decaymodes.h"
20 #include "smash/input_keys.h"
21 #include "smash/logging.h"
22 #include "smash/parametrizations.h"
23 #include "smash/scatteraction.h"
26 #include "smash/stringfunctions.h"
27 
28 namespace smash {
29 static constexpr int LFindScatter = LogArea::FindScatter::id;
30 
32  Configuration& config, const ExperimentParameters& parameters)
33  : finder_parameters_(config, parameters),
34  isotropic_(config.take(InputKeys::collTerm_isotropic)),
35  box_length_(parameters.box_length),
36  string_formation_time_(
37  config.read(InputKeys::collTerm_stringParam_formationTime)) {
39  logg[LFindScatter].info(
40  "Constant elastic isotropic cross-section mode:", " using ",
41  finder_parameters_.elastic_parameter, " mb as maximal cross-section.");
42  }
45  throw std::invalid_argument(
46  "Multi-body reactions (like e.g. 3->1 or 3->2) are only possible with "
47  "the stochastic collision criterion. Change your config accordingly.");
48  }
49 
52  1 &&
54  .included_2to2[IncludedReactions::PiDeuteron_to_pidprime] == 1 ||
56  .included_2to2[IncludedReactions::NDeuteron_to_Ndprime] == 1)) {
57  throw std::invalid_argument(
58  "To prevent double counting it is not possible to enable deuteron 3->2 "
59  "reactions\nand reactions involving the d' at the same time\ni.e. to "
60  "include \"Deuteron_3to2\" in `Multi_Particle_Reactions` and\n "
61  "\"PiDeuteron_to_pidprime\" "
62  "or \"NDeuteron_to_Ndprime\" in `Included_2to2` at the same time.\n"
63  "Change your config accordingly.");
64  }
65 
68  1 &&
70  throw std::invalid_argument(
71  "Do not use the d' resonance and enable \"Deuteron_3to2\" "
72  "`Multi_Particle_Reactions` at the same time. Either use the direct "
73  "3-to-2 reactions or the d' together with \"PiDeuteron_to_pidprime\" "
74  "and \"NDeuteron_to_Ndprime\" in `Included_2to2`. Otherwise the "
75  "deuteron 3-to-2 reactions would be double counted.");
76  }
77 
81  1) ||
84  1 &&
86  throw std::invalid_argument(
87  "In order to conserve detailed balance, when \"NNbar_5to2\" is "
88  "included in\n`Multi_Particle_Reactions`, the `NNbarTreatment` has to "
89  "be set to \"two to five\" and vice versa.");
90  }
91 
94  throw std::invalid_argument(
95  "'NNbar' has to be in the list of allowed 2 to 2 processes "
96  "to enable annihilation to go through resonances");
97  }
98 
100  string_process_interface_ = std::make_unique<StringProcess>(config);
101  }
102 }
103 
105  Configuration& config) {
112 }
113 
115  Configuration& config, const ExperimentParameters& parameters)
116  : elastic_parameter(config.take(InputKeys::collTerm_elasticCrossSection)),
117  low_snn_cut(parameters.low_snn_cut),
118  scale_xs(parameters.scale_xs),
119  additional_el_xs(
120  config.take(InputKeys::collTerm_additionalElasticCrossSection)),
121  maximum_cross_section(parameters.maximum_cross_section),
122  coll_crit(parameters.coll_crit),
123  nnbar_treatment(parameters.nnbar_treatment),
124  included_2to2(parameters.included_2to2),
125  charm_rescattering(parameters.charm_rescattering),
126  included_multi(parameters.included_multi),
127  testparticles(parameters.testparticles),
128  two_to_one(parameters.two_to_one),
129  allow_collisions_within_nucleus(
130  config.take(InputKeys::modi_collider_collisionWithinNucleus)),
131  spin_interaction_type(parameters.spin_interaction_type),
132  strings_switch(parameters.strings_switch),
133  use_AQM(config.take(InputKeys::collTerm_useAQM)),
134  strings_with_probability(
135  config.take(InputKeys::collTerm_stringsWithProbability)),
136  only_warn_for_high_prob(
137  config.take(InputKeys::collTerm_onlyWarnForHighProbability)),
138  transition_high_energy{create_string_transition_parameters(config)},
139  total_xs_strategy(config.take(InputKeys::collTerm_totXsStrategy)),
140  pseudoresonance_method(config.take(InputKeys::collTerm_pseudoresonance)),
141  hard_string_transition_mode(
142  config.take(InputKeys::collTerm_hardStringTransition_mode)),
143  hard_string_transition_energy_range(
144  config.take(InputKeys::collTerm_hardStringTransition_energyRange)),
145  AQM_charm_suppression(
146  config.take(InputKeys::collTerm_HF_AQMcSuppression)),
147  AQM_bottom_suppression(
148  config.take(InputKeys::collTerm_HF_AQMbSuppression)) {
150  logg[LFindScatter].info(
151  "Evaluating total cross sections from partial processes.");
152  } else if (parameters.included_2to2[IncludedReactions::Elastic] == 1 &&
153  parameters.included_2to2.count() == 1) {
154  throw std::invalid_argument(
155  "The BottomUp strategy for total cross section evaluation is needed to "
156  "have only elastic interactions, please change the configuration "
157  "accordingly.");
159  logg[LFindScatter].info(
160  "Evaluating total cross sections from parametrizations.");
162  logg[LFindScatter].info(
163  "Evaluating total cross sections from parametrizations only for "
164  "measured processes.");
165  }
166 
168  logg[LFindScatter].warn(
169  "It is not possible to completely disable AQM when "
170  "Total_Cross_Section_Strategy is set to \"TopDown\" or "
171  "\"TopDownMeasured\".\n"
172  "AQM will be used for total parametrizations of cross sections.");
173  }
174 }
175 
177  const ParticleData& data_a, const ParticleData& data_b, double dt,
178  const std::vector<FourVector>& beam_momentum,
179  const double gcell_vol) const {
180  /* If the two particles
181  * 1) belong to one of the two colliding nuclei, and
182  * 2) both of them have never experienced any collisions,
183  * then the collisions between them are banned. */
185  assert(data_a.id() >= 0);
186  assert(data_b.id() >= 0);
187  bool in_same_nucleus = (data_a.belongs_to() == BelongsTo::Projectile &&
188  data_b.belongs_to() == BelongsTo::Projectile) ||
189  (data_a.belongs_to() == BelongsTo::Target &&
190  data_b.belongs_to() == BelongsTo::Target);
191  bool never_interacted_before =
192  data_a.get_history().collisions_per_particle == 0 &&
193  data_b.get_history().collisions_per_particle == 0;
194  if (in_same_nucleus && never_interacted_before) {
195  return nullptr;
196  }
197  }
198 
199  // No grid or search in cell means no collision for stochastic criterion
201  gcell_vol < really_small) {
202  return nullptr;
203  }
204 
205  // Determine time of collision.
206  const double time_until_collision =
207  collision_time(data_a, data_b, dt, beam_momentum);
208 
209  // Check that collision happens in this timestep.
210  if (time_until_collision < 0. || time_until_collision >= dt) {
211  return nullptr;
212  }
213 
214  // Determine which total cross section to use
215  bool incoming_parametrized = (finder_parameters_.total_xs_strategy ==
219  const PdgCode& pdg_a = data_a.type().pdgcode();
220  const PdgCode& pdg_b = data_b.type().pdgcode();
221  incoming_parametrized = parametrization_exists(pdg_a, pdg_b);
222  }
223 
224  // Create ScatterAction object.
225  ScatterActionPtr act = std::make_unique<ScatterAction>(
226  data_a, data_b, time_until_collision, isotropic_, string_formation_time_,
227  box_length_, incoming_parametrized,
229 
231  act->set_stochastic_pos_idx();
232  }
233 
235  act->set_string_interface(string_process_interface_.get());
236  }
237 
238  // Distance squared calculation not needed for stochastic criterion
239  const double distance_squared =
241  ? act->transverse_distance_sqr()
243  ? act->cov_transverse_distance_sqr()
244  : 0.0;
245 
246  // Don't calculate cross section if the particles are very far apart.
247  // Not needed for stochastic criterion because of cell structure.
249  distance_squared >=
251  return nullptr;
252  }
253 
254  if (incoming_parametrized) {
255  act->set_parametrized_total_cross_section(finder_parameters_);
256  } else {
257  // Add various subprocesses.
258  act->add_all_scatterings(finder_parameters_);
259  }
260 
261  double xs = act->cross_section() * fm2_mb /
262  static_cast<double>(finder_parameters_.testparticles);
263 
264  // Take cross section scaling factors into account
265  xs *= data_a.xsec_scaling_factor(time_until_collision);
266  xs *= data_b.xsec_scaling_factor(time_until_collision);
267 
269  const double v_rel = act->relative_velocity();
270  /* Collision probability for 2-particle scattering, see
271  * \iref{Staudenmaier:2021lrg}. */
272  const double prob = xs * v_rel * dt / gcell_vol;
273 
274  logg[LFindScatter].debug(
275  "Stochastic collison criterion parameters (2-particles):\nprob = ",
276  prob, ", xs = ", xs, ", v_rel = ", v_rel, ", dt = ", dt,
277  ", gcell_vol = ", gcell_vol,
278  ", testparticles = ", finder_parameters_.testparticles);
279 
280  if (prob > 1.) {
281  std::stringstream err;
282  err << "Probability larger than 1 for stochastic rates. ( P_22 = " << prob
283  << " )\n"
284  << data_a.type().name() << data_b.type().name() << " with masses "
285  << data_a.momentum().abs() << " and " << data_b.momentum().abs()
286  << " at sqrts[GeV] = " << act->sqrt_s()
287  << " with xs[fm^2]/Ntest = " << xs
288  << "\nConsider using smaller timesteps.";
290  logg[LFindScatter].warn(err.str());
291  } else {
292  throw std::runtime_error(err.str());
293  }
294  }
295 
296  // probability criterion
297  double random_no = random::uniform(0., 1.);
298  if (random_no > prob) {
299  return nullptr;
300  }
301 
304  // just collided with this particle
305  if (data_a.id_process() > 0 && data_a.id_process() == data_b.id_process()) {
306  logg[LFindScatter].debug("Skipping collided particles at time ",
307  data_a.position().x0(), " due to process ",
308  data_a.id_process(), "\n ", data_a, "\n<-> ",
309  data_b);
310 
311  return nullptr;
312  }
313 
314  // Cross section for collision criterion
315  const double cross_section_criterion = xs * M_1_PI;
316 
317  // distance criterion according to cross_section
318  if (distance_squared >= cross_section_criterion) {
319  return nullptr;
320  }
321 
322  logg[LFindScatter].debug("particle distance squared: ", distance_squared,
323  "\n ", data_a, "\n<-> ", data_b);
324  }
325 
326  // Include possible outgoing branches
327  if (incoming_parametrized) {
328  act->add_all_scatterings(finder_parameters_);
329  }
330 
331  return act;
332 }
333 
335  const ParticleList& plist, double dt, const double gcell_vol) const {
336  /* If all particles
337  * 1) belong to the two colliding nuclei
338  * 2) are within the same nucleus
339  * 3) have never experienced any collisons,
340  * then the collision between them are banned also for multi-particle
341  * interactions. */
343  bool all_projectile =
344  std::all_of(plist.begin(), plist.end(), [&](const ParticleData& data) {
345  return data.belongs_to() == BelongsTo::Projectile;
346  });
347  bool all_target =
348  std::all_of(plist.begin(), plist.end(), [&](const ParticleData& data) {
349  return data.belongs_to() == BelongsTo::Target;
350  });
351  bool none_collided =
352  std::all_of(plist.begin(), plist.end(), [&](const ParticleData& data) {
353  return data.get_history().collisions_per_particle == 0;
354  });
355  if ((all_projectile || all_target) && none_collided) {
356  return nullptr;
357  }
358  }
359  // No grid or search in cell
360  if (gcell_vol < really_small) {
361  return nullptr;
362  }
363 
364  /* Optimisation for later: Already check here at the beginning
365  * if collision with plist is possible before constructing actions. */
366 
367  // 1. Determine time of collision.
368  const double time_until_collision = dt * random::uniform(0., 1.);
369 
370  // 2. Create ScatterAction object.
371  ScatterActionMultiPtr act =
372  std::make_unique<ScatterActionMulti>(plist, time_until_collision);
373 
374  act->set_stochastic_pos_idx();
375 
376  // 3. Add possible final states (dt and gcell_vol for probability calculation)
377  act->add_possible_reactions(dt, gcell_vol, finder_parameters_.included_multi);
378 
379  /* 4. Return total collision probability
380  * Scales with 1 over the number of testpartciles to the power of the
381  * number of incoming particles - 1 */
382  const double prob =
383  act->get_total_weight() /
384  std::pow(finder_parameters_.testparticles, plist.size() - 1);
385 
386  // 5. Check that probability is smaller than one
387  if (prob > 1.) {
388  std::stringstream err;
389  err << "Probability " << prob << " larger than 1 for stochastic rates for ";
390  for (const ParticleData& data : plist) {
391  err << data.type().name();
392  }
393  err << " at sqrts[GeV] = " << act->sqrt_s()
394  << "\nConsider using smaller timesteps.";
396  logg[LFindScatter].warn(err.str());
397  } else {
398  throw std::runtime_error(err.str());
399  }
400  }
401 
402  // 6. Perform probability decisions
403  double random_no = random::uniform(0., 1.);
404  if (random_no > prob) {
405  return nullptr;
406  }
407 
408  return act;
409 }
410 
412  const ParticleList& search_list, double dt, const double gcell_vol,
413  const std::vector<FourVector>& beam_momentum) const {
414  std::vector<ActionPtr> actions;
415  for (const ParticleData& p1 : search_list) {
416  for (const ParticleData& p2 : search_list) {
417  // Check for 2 particle scattering
418  if (p1.id() < p2.id()) {
419  ActionPtr act =
420  check_collision_two_part(p1, p2, dt, beam_momentum, gcell_vol);
421  if (act) {
422  actions.push_back(std::move(act));
423  }
424  }
426  // Also, check for 3 particle scatterings with stochastic criterion
427  for (const ParticleData& p3 : search_list) {
432  if (p1.id() < p2.id() && p2.id() < p3.id()) {
433  ActionPtr act =
434  check_collision_multi_part({p1, p2, p3}, dt, gcell_vol);
435  if (act) {
436  actions.push_back(std::move(act));
437  }
438  }
439  }
440  for (const ParticleData& p4 : search_list) {
443  if (p1.id() < p2.id() && p2.id() < p3.id() && p3.id() < p4.id()) {
444  ActionPtr act =
445  check_collision_multi_part({p1, p2, p3, p4}, dt, gcell_vol);
446  if (act) {
447  actions.push_back(std::move(act));
448  }
449  }
450  }
453  search_list.size() >= 5) {
454  for (const ParticleData& p5 : search_list) {
455  if ((p1.id() < p2.id() && p2.id() < p3.id() &&
456  p3.id() < p4.id() && p4.id() < p5.id()) &&
457  (p1.is_pion() && p2.is_pion() && p3.is_pion() &&
458  p4.is_pion() && p5.is_pion())) {
459  // at the moment only pure pion 5-body reactions
460  ActionPtr act = check_collision_multi_part(
461  {p1, p2, p3, p4, p5}, dt, gcell_vol);
462  if (act) {
463  actions.push_back(std::move(act));
464  }
465  }
466  }
467  }
468  }
469  }
470  }
471  }
472  }
473  return actions;
474 }
475 
477  const ParticleList& search_list, const ParticleList& neighbors_list,
478  double dt, const std::vector<FourVector>& beam_momentum) const {
479  std::vector<ActionPtr> actions;
481  // Only search in cells
482  return actions;
483  }
484  for (const ParticleData& p1 : search_list) {
485  for (const ParticleData& p2 : neighbors_list) {
486  assert(p1.id() != p2.id());
487  // Check if a collision is possible.
488  ActionPtr act = check_collision_two_part(p1, p2, dt, beam_momentum);
489  if (act) {
490  actions.push_back(std::move(act));
491  }
492  }
493  }
494  return actions;
495 }
496 
498  const ParticleList& search_list, const Particles& surrounding_list,
499  double dt, const std::vector<FourVector>& beam_momentum) const {
500  std::vector<ActionPtr> actions;
502  // Only search in cells
503  return actions;
504  }
505  for (const ParticleData& p2 : surrounding_list) {
506  /* don't look for collisions if the particle from the surrounding list is
507  * also in the search list */
508  auto result = std::find_if(
509  search_list.begin(), search_list.end(),
510  [&p2](const ParticleData& p) { return p.id() == p2.id(); });
511  if (result != search_list.end()) {
512  continue;
513  }
514  for (const ParticleData& p1 : search_list) {
515  // Check if a collision is possible.
516  ActionPtr act = check_collision_two_part(p1, p2, dt, beam_momentum);
517  if (act) {
518  actions.push_back(std::move(act));
519  }
520  }
521  }
522  return actions;
523 }
524 
526  constexpr double time = 0.0;
527 
528  const size_t N_isotypes = IsoParticleType::list_all().size();
529  const size_t N_pairs = N_isotypes * (N_isotypes - 1) / 2;
530 
531  std::cout << N_isotypes << " iso-particle types." << std::endl;
532  std::cout << "They can make " << N_pairs << " pairs." << std::endl;
533  std::vector<double> momentum_scan_list = {0.1, 0.3, 0.5, 1.0,
534  2.0, 3.0, 5.0, 10.0};
535  for (const IsoParticleType& A_isotype : IsoParticleType::list_all()) {
536  for (const IsoParticleType& B_isotype : IsoParticleType::list_all()) {
537  if (&A_isotype > &B_isotype) {
538  continue;
539  }
540  bool any_nonzero_cs = false;
541  std::vector<std::string> r_list;
542  for (const ParticleTypePtr A_type : A_isotype.get_states()) {
543  for (const ParticleTypePtr B_type : B_isotype.get_states()) {
544  if (A_type > B_type) {
545  continue;
546  }
547  ParticleData A(*A_type), B(*B_type);
548  for (auto mom : momentum_scan_list) {
549  A.set_4momentum(A.pole_mass(), mom, 0.0, 0.0);
550  B.set_4momentum(B.pole_mass(), -mom, 0.0, 0.0);
551  ScatterActionPtr act = std::make_unique<ScatterAction>(
552  A, B, time, isotropic_, string_formation_time_, -1, false,
555  act->set_string_interface(string_process_interface_.get());
556  }
557  act->add_all_scatterings(finder_parameters_);
558  const double total_cs = act->cross_section();
559  if (total_cs <= 0.0) {
560  continue;
561  }
562  any_nonzero_cs = true;
563  for (const auto& channel : act->collision_channels()) {
564  const auto type = channel->get_type();
565  std::string r;
566  if (is_string_process(type)) {
567  r = A_type->name() + B_type->name() + std::string(" → strings");
568  } else {
569  std::string r_type =
570  (type == ProcessType::Elastic) ? std::string(" (el)")
571  : (channel->get_type() == ProcessType::TwoToTwo)
572  ? std::string(" (inel)")
573  : std::string(" (?)");
574  r = A_type->name() + B_type->name() + std::string(" → ") +
575  channel->particle_types()[0]->name() +
576  channel->particle_types()[1]->name() + r_type;
577  }
578  isoclean(r);
579  r_list.push_back(r);
580  }
581  }
582  }
583  }
584  std::sort(r_list.begin(), r_list.end());
585  r_list.erase(std::unique(r_list.begin(), r_list.end()), r_list.end());
586  if (any_nonzero_cs) {
587  for (auto r : r_list) {
588  std::cout << r;
589  if (r_list.back() != r) {
590  std::cout << ", ";
591  }
592  }
593  std::cout << std::endl;
594  }
595  }
596  }
597 }
598 
599 /// Represent a final-state cross section.
601  /// Name of the final state.
602  std::string name_;
603 
604  /// Corresponding cross section in mb.
606 
607  /// Total mass of final state particles.
608  double mass_;
609 
610  /**
611  * Construct a final-state cross section.
612  *
613  * \param name Name of the final state.
614  * \param cross_section Corresponding cross section in mb.
615  * \param mass Total mass of final state particles.
616  * \return Constructed object.
617  */
618  FinalStateCrossSection(const std::string& name, double cross_section,
619  double mass)
620  : name_(name), cross_section_(cross_section), mass_(mass) {}
621 };
622 
623 namespace decaytree {
624 
625 /**
626  * Node of a decay tree, representing a possible action (2-to-2 or 1-to-2).
627  *
628  * This data structure can be used to build a tree going from the initial
629  * state (a collision of two particles) to all possible final states by
630  * recursively performing all possible decays. The tree can be used to
631  * calculate the final state cross sections.
632  *
633  * The initial actions are 2-to-2 or 2-to-1 scatterings, all other actions are
634  * 1-to-2 decays.
635  */
636 struct Node {
637  public:
638  /// Name for printing.
639  std::string name_;
640 
641  /// Weight (cross section or branching ratio).
642  double weight_;
643 
644  /// Initial-state particle types in this action.
645  ParticleTypePtrList initial_particles_;
646 
647  /// Final-state particle types in this action.
648  ParticleTypePtrList final_particles_;
649 
650  /// Particle types corresponding to the global state after this action.
651  ParticleTypePtrList state_;
652 
653  /// Possible actions after this action.
654  std::vector<Node> children_;
655 
656  /// Cannot be copied
657  Node(const Node&) = delete;
658  /// Move constructor
659  Node(Node&&) = default;
660 
661  /**
662  * \return A new decay tree node.
663  *
664  * \param name Name for printing.
665  * \param weight Cross section or branching ratio.
666  * \param initial_particles Initial-state particle types in this node.
667  * \param final_particles Final-state particle types in this node.
668  * \param state Curent particle types of the system.
669  * \param children Possible actions after this action.
670  */
671  Node(const std::string& name, double weight,
672  ParticleTypePtrList&& initial_particles,
673  ParticleTypePtrList&& final_particles, ParticleTypePtrList&& state,
674  std::vector<Node>&& children)
675  : name_(name),
676  weight_(weight),
677  initial_particles_(std::move(initial_particles)),
678  final_particles_(std::move(final_particles)),
679  state_(std::move(state)),
680  children_(std::move(children)) {}
681 
682  /**
683  * Add an action to the children of this node.
684  *
685  * The current particle state of the new action is automatically calculated.
686  *
687  * \param name Name of the action used for output.
688  * \param weight Cross section/branching ratio of the action.
689  * \param initial_particles Initial-state particle types of the action.
690  * \param final_particles Final-state particle types of the action.
691  * \return Newly added node by reference.
692  */
693  Node& add_action(const std::string& name, double weight,
694  ParticleTypePtrList&& initial_particles,
695  ParticleTypePtrList&& final_particles) {
696  // Copy parent state and update it.
697  ParticleTypePtrList state(state_);
698  for (const auto& p : initial_particles) {
699  state.erase(std::find(state.begin(), state.end(), p));
700  }
701  for (const auto& p : final_particles) {
702  state.push_back(p);
703  }
704  // Sort the state to normalize the output.
705  std::sort(state.begin(), state.end(),
707  return a->name() < b->name();
708  });
709  // Push new node to children.
710  Node new_node(name, weight, std::move(initial_particles),
711  std::move(final_particles), std::move(state), {});
712  children_.emplace_back(std::move(new_node));
713  return children_.back();
714  }
715 
716  /// Print the decay tree starting with this node.
717  void print() const { print_helper(0); }
718 
719  /**
720  * \return Final-state cross sections.
721  */
722  std::vector<FinalStateCrossSection> final_state_cross_sections() const {
723  std::vector<FinalStateCrossSection> result;
724  final_state_cross_sections_helper(0, result, "", 1.);
725  return result;
726  }
727 
728  private:
729  /**
730  * Internal helper function for `print`, to be called recursively to print
731  * all nodes.
732  *
733  * \param depth Recursive call depth.
734  */
735  void print_helper(uint64_t depth) const {
736  for (uint64_t i = 0; i < depth; i++) {
737  std::cout << " ";
738  }
739  std::cout << name_ << " " << weight_ << std::endl;
740  for (const auto& child : children_) {
741  child.print_helper(depth + 1);
742  }
743  }
744 
745  /**
746  * Internal helper function for `final_state_cross_sections`, to be called
747  * recursively to calculate all final-state cross sections.
748  *
749  * \param depth Recursive call depth.
750  * \param result Pairs of process names and exclusive cross sections.
751  * \param name Current name.
752  * \param weight current Weight/cross section.
753  * \param show_intermediate_states Whether intermediate states should be
754  * shown.
755  */
757  uint64_t depth, std::vector<FinalStateCrossSection>& result,
758  const std::string& name, double weight,
759  bool show_intermediate_states = false) const {
760  // The first node corresponds to the total cross section and has to be
761  // ignored. The second node corresponds to the partial cross section. All
762  // further nodes correspond to branching ratios.
763  if (depth > 0) {
764  weight *= weight_;
765  }
766 
767  std::string new_name;
768  double mass = 0.;
769 
770  if (show_intermediate_states) {
771  new_name = name;
772  if (!new_name.empty()) {
773  new_name += "->";
774  }
775  new_name += name_;
776  new_name += "{";
777  } else {
778  new_name = "";
779  }
780  for (const auto& s : state_) {
781  new_name += s->name();
782  mass += s->mass();
783  }
784  if (show_intermediate_states) {
785  new_name += "}";
786  }
787 
788  if (children_.empty()) {
789  result.emplace_back(FinalStateCrossSection(new_name, weight, mass));
790  return;
791  }
792  for (const auto& child : children_) {
793  child.final_state_cross_sections_helper(depth + 1, result, new_name,
794  weight, show_intermediate_states);
795  }
796  }
797 };
798 
799 /**
800  * Generate name for decay and update final state.
801  *
802  * \param[in] res_name Name of resonance.
803  * \param[in] decay Decay branch.
804  * \param[out] final_state Final state of decay.
805  * \return Name of decay.
806  */
807 static std::string make_decay_name(const std::string& res_name,
808  const DecayBranchPtr& decay,
809  ParticleTypePtrList& final_state) {
810  std::stringstream name;
811  name << "[" << res_name << "->";
812  for (const auto& p : decay->particle_types()) {
813  name << p->name();
814  final_state.push_back(p);
815  }
816  name << "]";
817  return name.str();
818 }
819 
820 /**
821  * Add nodes for all decays possible from the given node and all of its
822  * children.
823  *
824  * \param node Starting node.
825  * \param[in] sqrts center-of-mass energy.
826  */
827 static void add_decays(Node& node, double sqrts) {
828  // If there is more than one unstable particle in the current state, then
829  // there will be redundant paths in the decay tree, corresponding to
830  // reorderings of the decays. To avoid double counting, we normalize by the
831  // number of possible decay orderings. Normalizing by the number of unstable
832  // particles recursively corresponds to normalizing by the factorial that
833  // gives the number of reorderings.
834  //
835  // Ideally, the redundant paths should never be added to the decay tree, but
836  // we never have more than two redundant paths, so it probably does not
837  // matter much.
838  uint32_t n_unstable = 0;
839  double sqrts_minus_masses = sqrts;
840  for (const ParticleTypePtr ptype : node.state_) {
841  if (!ptype->is_stable()) {
842  n_unstable += 1;
843  }
844  sqrts_minus_masses -= ptype->mass();
845  }
846  const double norm =
847  n_unstable != 0 ? 1. / static_cast<double>(n_unstable) : 1.;
848 
849  for (const ParticleTypePtr ptype : node.state_) {
850  if (!ptype->is_stable()) {
851  const double sqrts_decay = sqrts_minus_masses + ptype->mass();
852  bool can_decay = false;
853  for (const auto& decay : ptype->decay_modes().decay_mode_list()) {
854  // Make sure to skip kinematically impossible decays.
855  // In principle, we would have to integrate over the mass of the
856  // resonance, but as an approximation we just assume it at its pole.
857  double final_state_mass = 0.;
858  for (const auto& p : decay->particle_types()) {
859  final_state_mass += p->mass();
860  }
861  if (final_state_mass > sqrts_decay) {
862  continue;
863  }
864  can_decay = true;
865 
866  ParticleTypePtrList parts;
867  const auto name = make_decay_name(ptype->name(), decay, parts);
868  auto& new_node = node.add_action(name, norm * decay->weight(), {ptype},
869  std::move(parts));
870  add_decays(new_node, sqrts_decay);
871  }
872  if (!can_decay) {
873  // Remove final-state cross sections with resonances that cannot
874  // decay due to our "mass = pole mass" approximation.
875  node.weight_ = 0;
876  return;
877  }
878  }
879  }
880 }
881 
882 } // namespace decaytree
883 
884 /**
885  * Deduplicate the final-state cross sections by summing.
886  *
887  * \param[inout] final_state_xs Final-state cross sections.
888  */
889 static void deduplicate(std::vector<FinalStateCrossSection>& final_state_xs) {
890  std::sort(final_state_xs.begin(), final_state_xs.end(),
891  [](const FinalStateCrossSection& a,
892  const FinalStateCrossSection& b) { return a.name_ < b.name_; });
893  auto current = final_state_xs.begin();
894  while (current != final_state_xs.end()) {
895  auto adjacent = std::adjacent_find(
896  current, final_state_xs.end(),
897  [](const FinalStateCrossSection& a, const FinalStateCrossSection& b) {
898  return a.name_ == b.name_;
899  });
900  current = adjacent;
901  if (adjacent != final_state_xs.end()) {
902  adjacent->cross_section_ += (adjacent + 1)->cross_section_;
903  final_state_xs.erase(adjacent + 1);
904  }
905  }
906 }
907 
909  const ParticleType& a, const ParticleType& b, double m_a, double m_b,
910  bool final_state, std::vector<double>& plab) const {
911  std::vector<double> sqrts_values;
912  std::map<std::string, std::vector<double>> xs_dump;
913  std::map<std::string, double> outgoing_total_mass;
914 
915  ParticleData a_data(a), b_data(b);
916  int n_momentum_points = 200;
917  constexpr double momentum_step = 0.02;
918  if (plab.size() > 0) {
919  // Remove duplicates.
920  std::sort(plab.begin(), plab.end());
921  plab.erase(std::unique(plab.begin(), plab.end()), plab.end());
922  // Reset size of momentum points to the number of unique values.
923  n_momentum_points = plab.size();
924  }
925  sqrts_values.reserve(n_momentum_points);
926  const auto store_cross_section_into_map = [&](const std::string& channel,
927  double xs, int i) {
928  /* Store one cross-section value per energy point. Missing channels remain
929  * zero, so all channel vectors have the same indexing as sqrts_values. */
930  auto& xs_values = xs_dump[channel];
931  if (xs_values.empty()) {
932  xs_values.resize(n_momentum_points, 0.0);
933  }
934  /* NOTE: We add the value to the existing one (usually 0.0) in order to
935  * correctly accumulate cross-sections in case multiple channels have the
936  * same name. */
937  xs_values[i] += xs;
938  };
939  for (int i = 0; i < n_momentum_points; i++) {
940  double momentum;
941  if (plab.size() > 0) {
942  momentum = pCM_from_s(s_from_plab(plab.at(i), m_a, m_b), m_a, m_b);
943  } else {
944  momentum = momentum_step * (i + 1);
945  }
946  a_data.set_4momentum(m_a, momentum, 0.0, 0.0);
947  b_data.set_4momentum(m_b, -momentum, 0.0, 0.0);
948  const double sqrts = (a_data.momentum() + b_data.momentum()).abs();
949  sqrts_values.push_back(sqrts);
950  ScatterActionPtr act = std::make_unique<ScatterAction>(
951  a_data, b_data, 0., isotropic_, string_formation_time_, -1, false,
954  act->set_string_interface(string_process_interface_.get());
955  }
956  act->add_all_scatterings(finder_parameters_);
957  decaytree::Node tree(a.name() + b.name(), act->cross_section(), {&a, &b},
958  {&a, &b}, {&a, &b}, {});
959  const CollisionBranchList& processes = act->collision_channels();
960  for (const auto& process : processes) {
961  const double xs = process->weight();
962  if (xs <= 0.0) {
963  continue;
964  }
965  std::stringstream process_description_stream;
966  process_description_stream << *process;
967  const std::string& description = process_description_stream.str();
968  if (!final_state) {
969  double m_tot = 0.0;
970  for (const auto& ptype : process->particle_types()) {
971  m_tot += ptype->mass();
972  }
973  outgoing_total_mass[description] = m_tot;
974  store_cross_section_into_map(description, xs, i);
975  } else {
976  ParticleTypePtrList initial_particles = {&a, &b};
977  ParticleTypePtrList final_particles = process->particle_types();
978  auto& process_node =
979  tree.add_action(description, xs, std::move(initial_particles),
980  std::move(final_particles));
981  decaytree::add_decays(process_node, sqrts);
982  }
983  }
984  store_cross_section_into_map("total", act->cross_section(), i);
985  // Total cross-section should be the first in the list -> negative mass
986  outgoing_total_mass["total"] = -1.0;
987  if (final_state) {
988  // tree.print();
989  auto final_state_xs = tree.final_state_cross_sections();
990  deduplicate(final_state_xs);
991  for (const auto& p : final_state_xs) {
992  // Don't print empty columns.
993  //
994  // FIXME(steinberg): The better fix would be to not have them in the
995  // first place.
996  if (p.name_ == "") {
997  continue;
998  }
999  outgoing_total_mass[p.name_] = p.mass_;
1000  store_cross_section_into_map(p.name_, p.cross_section_, i);
1001  }
1002  }
1003  }
1004  // Get rid of cross sections that are zero.
1005  // This only happens if there is a resonance in the final state that cannot
1006  // decay with our simplified assumptions.
1007  for (auto it = begin(xs_dump); it != end(xs_dump);) {
1008  // Sum cross section over all energies.
1009  const auto& xs = (*it).second;
1010  const double sum = std::accumulate(xs.begin(), xs.end(), 0.0);
1011  if (sum == 0.) {
1012  it = xs_dump.erase(it);
1013  } else {
1014  ++it;
1015  }
1016  }
1017 
1018  // Nice ordering of channels by summed pole mass of products
1019  std::vector<std::string> all_channels;
1020  for (const auto& channel : xs_dump) {
1021  all_channels.push_back(channel.first);
1022  }
1023  std::sort(all_channels.begin(), all_channels.end(),
1024  [&](const std::string& str_a, const std::string& str_b) {
1025  return outgoing_total_mass[str_a] < outgoing_total_mass[str_b];
1026  });
1027 
1028  // Print header
1029  std::cout << "# Dumping partial " << a.name() << b.name()
1030  << " cross-sections in mb, energies in GeV" << std::endl;
1031  std::cout << " sqrt_s";
1032  // Align everything to 24 unicode characters.
1033  // This should be enough for the longest channel name: 7 final-state
1034  // particles, or 2 of the longest named resonances (currently Ds0*(2317)⁺).
1035  for (const auto& channel : all_channels) {
1036  std::cout << utf8::fill_left(channel, 24, ' ');
1037  }
1038  std::cout << std::endl;
1039 
1040  // Print out all partial cross-sections in mb
1041  for (int i = 0; i < n_momentum_points; i++) {
1042  std::printf("%9.6f", sqrts_values[i]);
1043  for (const auto& channel : all_channels) {
1044  std::printf("%24.6f", xs_dump.at(channel)[i]);
1045  }
1046  std::printf("\n");
1047  }
1048 }
1049 
1050 } // 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:731
Particle type contains the static properties of a particle species.
Definition: particletype.h:100
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:159
const std::string & name() const
Definition: particletype.h:144
bool is_stable() const
Definition: particletype.h:251
double mass() const
Definition: particletype.h:147
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 bool use_AQM
Switch to control whether to use AQM or not.
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 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 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.h:245
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:91
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.
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)
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
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:280
bool is_string_process(ProcessType p)
Check if a given process type is a string excitation.
constexpr double fm2_mb
mb <-> fm^2 conversion factor.
Definition: constants.h:32
Helper structure for Experiment.
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:1255
static const Key< double > collTerm_stringTrans_lower
See user guide description for more information.
Definition: input_keys.h:3333
static const Key< std::pair< double, double > > collTerm_stringTrans_rangeNpi
See user guide description for more information.
Definition: input_keys.h:3382
static const Key< double > collTerm_stringTrans_range_width
See user guide description for more information.
Definition: input_keys.h:3402
static const Key< double > collTerm_stringTrans_pipiOffset
See user guide description for more information.
Definition: input_keys.h:3317
static const Key< double > collTerm_stringTrans_KNOffset
See user guide description for more information.
Definition: input_keys.h:3299
static const Key< std::pair< double, double > > collTerm_stringTrans_rangeNN
See user guide description for more information.
Definition: input_keys.h:3354
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.