Version: SMASH-3.1
scatteractionsfinder.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2014-2024
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_(create_finder_parameters(config, parameters)),
37  isotropic_(config.take({"Collision_Term", "Isotropic"}, false)),
38  box_length_(parameters.box_length),
39  string_formation_time_(config.take(
40  {"Collision_Term", "String_Parameters", "Formation_Time"}, 1.)) {
41  if (is_constant_elastic_isotropic()) {
42  logg[LFindScatter].info(
43  "Constant elastic isotropic cross-section mode:", " using ",
44  finder_parameters_.elastic_parameter, " mb as maximal cross-section.");
45  }
46  if (finder_parameters_.included_multi.any() &&
47  finder_parameters_.coll_crit != CollisionCriterion::Stochastic) {
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 
55  if (finder_parameters_
57  1 &&
58  (finder_parameters_
59  .included_2to2[IncludedReactions::PiDeuteron_to_pidprime] == 1 ||
60  finder_parameters_
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 
71  if (finder_parameters_
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 
83  if ((finder_parameters_.nnbar_treatment == NNbarTreatment::TwoToFive &&
84  finder_parameters_
86  1) ||
87  (finder_parameters_
89  1 &&
90  finder_parameters_.nnbar_treatment != NNbarTreatment::TwoToFive)) {
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 
97  if (finder_parameters_.nnbar_treatment == NNbarTreatment::Resonances &&
98  finder_parameters_.included_2to2[IncludedReactions::NNbar] != 1) {
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 
104  if (finder_parameters_.strings_switch) {
105  auto subconfig = config.extract_sub_configuration(
106  {"Collision_Term", "String_Parameters"}, Configuration::GetEmpty::Yes);
107  string_process_interface_ = std::make_unique<StringProcess>(
108  subconfig.take({"String_Tension"}, 1.0), string_formation_time_,
109  subconfig.take({"Gluon_Beta"}, 0.5),
110  subconfig.take({"Gluon_Pmin"}, 0.001),
111  subconfig.take({"Quark_Alpha"}, 2.0),
112  subconfig.take({"Quark_Beta"}, 7.0),
113  subconfig.take({"Strange_Supp"}, 0.16),
114  subconfig.take({"Diquark_Supp"}, 0.036),
115  subconfig.take({"Sigma_Perp"}, 0.42),
116  subconfig.take({"StringZ_A_Leading"}, 0.2),
117  subconfig.take({"StringZ_B_Leading"}, 2.0),
118  subconfig.take({"StringZ_A"}, 2.0), subconfig.take({"StringZ_B"}, 0.55),
119  subconfig.take({"String_Sigma_T"}, 0.5),
120  subconfig.take({"Form_Time_Factor"}, 1.0),
121  subconfig.take({"Mass_Dependent_Formation_Times"}, false),
122  subconfig.take({"Prob_proton_to_d_uu"}, 1. / 3.),
123  subconfig.take({"Separate_Fragment_Baryon"}, true),
124  subconfig.take({"Popcorn_Rate"}, 0.15),
125  subconfig.take({"Use_Monash_Tune"},
126  parameters.use_monash_tune_default.value()));
127  }
128 }
129 
131  Configuration& config, const ExperimentParameters& parameters) {
132  std::pair<double, double> sqrts_range_Npi =
133  config.take({"Collision_Term", "String_Transition", "Sqrts_Range_Npi"},
135  std::pair<double, double> sqrts_range_NN =
136  config.take({"Collision_Term", "String_Transition", "Sqrts_Range_NN"},
138  if (sqrts_range_Npi.first < nucleon_mass + pion_mass) {
139  sqrts_range_Npi.first = nucleon_mass + pion_mass;
140  if (sqrts_range_Npi.second < sqrts_range_Npi.first)
141  sqrts_range_Npi.second = sqrts_range_Npi.first;
142  logg[LFindScatter].warn(
143  "Lower bound of Sqrts_Range_Npi too small, setting it to mass "
144  "threshold. New range is [",
145  sqrts_range_Npi.first, ',', sqrts_range_Npi.second, "] GeV");
146  }
147  if (sqrts_range_NN.first < 2 * nucleon_mass) {
148  sqrts_range_NN.first = 2 * nucleon_mass;
149  if (sqrts_range_NN.second < sqrts_range_NN.first)
150  sqrts_range_NN.second = sqrts_range_NN.first;
151  logg[LFindScatter].warn(
152  "Lower bound of Sqrts_Range_NN too small, setting it to mass "
153  "threshold. New range is [",
154  sqrts_range_NN.first, ',', sqrts_range_NN.second, "] GeV.");
155  }
156  auto xs_strategy =
157  config.take({"Collision_Term", "Total_Cross_Section_Strategy"},
159  if (xs_strategy == TotalCrossSectionStrategy::BottomUp) {
160  logg[LFindScatter].info(
161  "Evaluating total cross sections from partial processes.");
162  } else if (parameters.included_2to2[IncludedReactions::Elastic] == 1 &&
163  parameters.included_2to2.count() == 1) {
164  throw std::invalid_argument(
165  "The BottomUp strategy for total cross section evaluation is needed to "
166  "have only elastic interactions, please change the configuration "
167  "accordingly.");
168  } else if (xs_strategy == TotalCrossSectionStrategy::TopDown) {
169  logg[LFindScatter].info(
170  "Evaluating total cross sections from parametrizations.");
171  } else if (xs_strategy == TotalCrossSectionStrategy::TopDownMeasured) {
172  logg[LFindScatter].info(
173  "Evaluating total cross sections from parametrizations only for "
174  "measured processes.");
175  }
176  return {
177  config.take({"Collision_Term", "Elastic_Cross_Section"}, -1.),
178  parameters.low_snn_cut,
179  parameters.scale_xs,
180  config.take({"Collision_Term", "Additional_Elastic_Cross_Section"}, 0.0),
181  parameters.maximum_cross_section,
182  parameters.coll_crit,
183  parameters.nnbar_treatment,
184  parameters.included_2to2,
185  parameters.included_multi,
186  parameters.testparticles,
187  parameters.two_to_one,
188  config.take({"Modi", "Collider", "Collisions_Within_Nucleus"}, false),
189  parameters.strings_switch,
190  config.take({"Collision_Term", "Use_AQM"}, true),
191  config.take({"Collision_Term", "Strings_with_Probability"}, true),
192  config.take({"Collision_Term", "Only_Warn_For_High_Probability"}, false),
194  sqrts_range_Npi, sqrts_range_NN,
195  config.take({"Collision_Term", "String_Transition", "Sqrts_Lower"},
197  config.take(
198  {"Collision_Term", "String_Transition", "Sqrts_Range_Width"},
200  config.take(
201  {"Collision_Term", "String_Transition", "PiPi_Offset"},
203  config.take(
204  {"Collision_Term", "String_Transition", "KN_Offset"},
206  xs_strategy,
207  config.take({"Collision_Term", "Pseudoresonance"},
209 }
210 
212  const ParticleData& data_a, const ParticleData& data_b, double dt,
213  const std::vector<FourVector>& beam_momentum,
214  const double gcell_vol) const {
215  /* If the two particles
216  * 1) belong to one of the two colliding nuclei, and
217  * 2) both of them have never experienced any collisions,
218  * then the collisions between them are banned. */
220  assert(data_a.id() >= 0);
221  assert(data_b.id() >= 0);
222  bool in_same_nucleus = (data_a.belongs_to() == BelongsTo::Projectile &&
223  data_b.belongs_to() == BelongsTo::Projectile) ||
224  (data_a.belongs_to() == BelongsTo::Target &&
225  data_b.belongs_to() == BelongsTo::Target);
226  bool never_interacted_before =
227  data_a.get_history().collisions_per_particle == 0 &&
228  data_b.get_history().collisions_per_particle == 0;
229  if (in_same_nucleus && never_interacted_before) {
230  return nullptr;
231  }
232  }
233 
234  // No grid or search in cell means no collision for stochastic criterion
236  gcell_vol < really_small) {
237  return nullptr;
238  }
239 
240  // Determine time of collision.
241  const double time_until_collision =
242  collision_time(data_a, data_b, dt, beam_momentum);
243 
244  // Check that collision happens in this timestep.
245  if (time_until_collision < 0. || time_until_collision >= dt) {
246  return nullptr;
247  }
248 
249  // Determine which total cross section to use
250  bool incoming_parametrized = (finder_parameters_.total_xs_strategy ==
254  const PdgCode& pdg_a = data_a.type().pdgcode();
255  const PdgCode& pdg_b = data_b.type().pdgcode();
256  incoming_parametrized = parametrization_exists(pdg_a, pdg_b);
257  }
258 
259  // Create ScatterAction object.
260  ScatterActionPtr act = std::make_unique<ScatterAction>(
261  data_a, data_b, time_until_collision, isotropic_, string_formation_time_,
262  box_length_, incoming_parametrized);
263 
265  act->set_stochastic_pos_idx();
266  }
267 
269  act->set_string_interface(string_process_interface_.get());
270  }
271 
272  // Distance squared calculation not needed for stochastic criterion
273  const double distance_squared =
275  ? act->transverse_distance_sqr()
277  ? act->cov_transverse_distance_sqr()
278  : 0.0;
279 
280  // Don't calculate cross section if the particles are very far apart.
281  // Not needed for stochastic criterion because of cell structure.
283  distance_squared >=
285  return nullptr;
286  }
287 
288  if (incoming_parametrized) {
289  act->set_parametrized_total_cross_section(finder_parameters_);
290  } else {
291  // Add various subprocesses.
292  act->add_all_scatterings(finder_parameters_);
293  }
294 
295  double xs = act->cross_section() * fm2_mb /
296  static_cast<double>(finder_parameters_.testparticles);
297 
298  // Take cross section scaling factors into account
299  xs *= data_a.xsec_scaling_factor(time_until_collision);
300  xs *= data_b.xsec_scaling_factor(time_until_collision);
301 
303  const double v_rel = act->relative_velocity();
304  /* Collision probability for 2-particle scattering, see
305  * \iref{Staudenmaier:2021lrg}. */
306  const double prob = xs * v_rel * dt / gcell_vol;
307 
308  logg[LFindScatter].debug(
309  "Stochastic collison criterion parameters (2-particles):\nprob = ",
310  prob, ", xs = ", xs, ", v_rel = ", v_rel, ", dt = ", dt,
311  ", gcell_vol = ", gcell_vol,
312  ", testparticles = ", finder_parameters_.testparticles);
313 
314  if (prob > 1.) {
315  std::stringstream err;
316  err << "Probability larger than 1 for stochastic rates. ( P_22 = " << prob
317  << " )\n"
318  << data_a.type().name() << data_b.type().name() << " with masses "
319  << data_a.momentum().abs() << " and " << data_b.momentum().abs()
320  << " at sqrts[GeV] = " << act->sqrt_s()
321  << " with xs[fm^2]/Ntest = " << xs
322  << "\nConsider using smaller timesteps.";
324  logg[LFindScatter].warn(err.str());
325  } else {
326  throw std::runtime_error(err.str());
327  }
328  }
329 
330  // probability criterion
331  double random_no = random::uniform(0., 1.);
332  if (random_no > prob) {
333  return nullptr;
334  }
335 
338  // just collided with this particle
339  if (data_a.id_process() > 0 && data_a.id_process() == data_b.id_process()) {
340  logg[LFindScatter].debug("Skipping collided particles at time ",
341  data_a.position().x0(), " due to process ",
342  data_a.id_process(), "\n ", data_a, "\n<-> ",
343  data_b);
344 
345  return nullptr;
346  }
347 
348  // Cross section for collision criterion
349  const double cross_section_criterion = xs * M_1_PI;
350 
351  // distance criterion according to cross_section
352  if (distance_squared >= cross_section_criterion) {
353  return nullptr;
354  }
355 
356  logg[LFindScatter].debug("particle distance squared: ", distance_squared,
357  "\n ", data_a, "\n<-> ", data_b);
358  }
359 
360  // Include possible outgoing branches
361  if (incoming_parametrized) {
362  act->add_all_scatterings(finder_parameters_);
363  }
364 
365  return act;
366 }
367 
369  const ParticleList& plist, double dt, const double gcell_vol) const {
370  /* If all particles
371  * 1) belong to the two colliding nuclei
372  * 2) are within the same nucleus
373  * 3) have never experienced any collisons,
374  * then the collision between them are banned also for multi-particle
375  * interactions. */
377  bool all_projectile =
378  std::all_of(plist.begin(), plist.end(), [&](const ParticleData& data) {
379  return data.belongs_to() == BelongsTo::Projectile;
380  });
381  bool all_target =
382  std::all_of(plist.begin(), plist.end(), [&](const ParticleData& data) {
383  return data.belongs_to() == BelongsTo::Target;
384  });
385  bool none_collided =
386  std::all_of(plist.begin(), plist.end(), [&](const ParticleData& data) {
387  return data.get_history().collisions_per_particle == 0;
388  });
389  if ((all_projectile || all_target) && none_collided) {
390  return nullptr;
391  }
392  }
393  // No grid or search in cell
394  if (gcell_vol < really_small) {
395  return nullptr;
396  }
397 
398  /* Optimisation for later: Already check here at the beginning
399  * if collision with plist is possible before constructing actions. */
400 
401  // 1. Determine time of collision.
402  const double time_until_collision = dt * random::uniform(0., 1.);
403 
404  // 2. Create ScatterAction object.
405  ScatterActionMultiPtr act =
406  std::make_unique<ScatterActionMulti>(plist, time_until_collision);
407 
408  act->set_stochastic_pos_idx();
409 
410  // 3. Add possible final states (dt and gcell_vol for probability calculation)
411  act->add_possible_reactions(dt, gcell_vol, finder_parameters_.included_multi);
412 
413  /* 4. Return total collision probability
414  * Scales with 1 over the number of testpartciles to the power of the
415  * number of incoming particles - 1 */
416  const double prob =
417  act->get_total_weight() /
418  std::pow(finder_parameters_.testparticles, plist.size() - 1);
419 
420  // 5. Check that probability is smaller than one
421  if (prob > 1.) {
422  std::stringstream err;
423  err << "Probability " << prob << " larger than 1 for stochastic rates for ";
424  for (const ParticleData& data : plist) {
425  err << data.type().name();
426  }
427  err << " at sqrts[GeV] = " << act->sqrt_s()
428  << "\nConsider using smaller timesteps.";
430  logg[LFindScatter].warn(err.str());
431  } else {
432  throw std::runtime_error(err.str());
433  }
434  }
435 
436  // 6. Perform probability decisions
437  double random_no = random::uniform(0., 1.);
438  if (random_no > prob) {
439  return nullptr;
440  }
441 
442  return act;
443 }
444 
446  const ParticleList& search_list, double dt, const double gcell_vol,
447  const std::vector<FourVector>& beam_momentum) const {
448  std::vector<ActionPtr> actions;
449  for (const ParticleData& p1 : search_list) {
450  for (const ParticleData& p2 : search_list) {
451  // Check for 2 particle scattering
452  if (p1.id() < p2.id()) {
453  ActionPtr act =
454  check_collision_two_part(p1, p2, dt, beam_momentum, gcell_vol);
455  if (act) {
456  actions.push_back(std::move(act));
457  }
458  }
460  // Also, check for 3 particle scatterings with stochastic criterion
461  for (const ParticleData& p3 : search_list) {
466  if (p1.id() < p2.id() && p2.id() < p3.id()) {
467  ActionPtr act =
468  check_collision_multi_part({p1, p2, p3}, dt, gcell_vol);
469  if (act) {
470  actions.push_back(std::move(act));
471  }
472  }
473  }
474  for (const ParticleData& p4 : search_list) {
477  if (p1.id() < p2.id() && p2.id() < p3.id() && p3.id() < p4.id()) {
478  ActionPtr act =
479  check_collision_multi_part({p1, p2, p3, p4}, dt, gcell_vol);
480  if (act) {
481  actions.push_back(std::move(act));
482  }
483  }
484  }
487  search_list.size() >= 5) {
488  for (const ParticleData& p5 : search_list) {
489  if ((p1.id() < p2.id() && p2.id() < p3.id() &&
490  p3.id() < p4.id() && p4.id() < p5.id()) &&
491  (p1.is_pion() && p2.is_pion() && p3.is_pion() &&
492  p4.is_pion() && p5.is_pion())) {
493  // at the moment only pure pion 5-body reactions
494  ActionPtr act = check_collision_multi_part(
495  {p1, p2, p3, p4, p5}, dt, gcell_vol);
496  if (act) {
497  actions.push_back(std::move(act));
498  }
499  }
500  }
501  }
502  }
503  }
504  }
505  }
506  }
507  return actions;
508 }
509 
511  const ParticleList& search_list, const ParticleList& neighbors_list,
512  double dt, const std::vector<FourVector>& beam_momentum) const {
513  std::vector<ActionPtr> actions;
515  // Only search in cells
516  return actions;
517  }
518  for (const ParticleData& p1 : search_list) {
519  for (const ParticleData& p2 : neighbors_list) {
520  assert(p1.id() != p2.id());
521  // Check if a collision is possible.
522  ActionPtr act = check_collision_two_part(p1, p2, dt, beam_momentum);
523  if (act) {
524  actions.push_back(std::move(act));
525  }
526  }
527  }
528  return actions;
529 }
530 
532  const ParticleList& search_list, const Particles& surrounding_list,
533  double dt, const std::vector<FourVector>& beam_momentum) const {
534  std::vector<ActionPtr> actions;
536  // Only search in cells
537  return actions;
538  }
539  for (const ParticleData& p2 : surrounding_list) {
540  /* don't look for collisions if the particle from the surrounding list is
541  * also in the search list */
542  auto result = std::find_if(
543  search_list.begin(), search_list.end(),
544  [&p2](const ParticleData& p) { return p.id() == p2.id(); });
545  if (result != search_list.end()) {
546  continue;
547  }
548  for (const ParticleData& p1 : search_list) {
549  // Check if a collision is possible.
550  ActionPtr act = check_collision_two_part(p1, p2, dt, beam_momentum);
551  if (act) {
552  actions.push_back(std::move(act));
553  }
554  }
555  }
556  return actions;
557 }
558 
560  constexpr double time = 0.0;
561 
562  const size_t N_isotypes = IsoParticleType::list_all().size();
563  const size_t N_pairs = N_isotypes * (N_isotypes - 1) / 2;
564 
565  std::cout << N_isotypes << " iso-particle types." << std::endl;
566  std::cout << "They can make " << N_pairs << " pairs." << std::endl;
567  std::vector<double> momentum_scan_list = {0.1, 0.3, 0.5, 1.0,
568  2.0, 3.0, 5.0, 10.0};
569  for (const IsoParticleType& A_isotype : IsoParticleType::list_all()) {
570  for (const IsoParticleType& B_isotype : IsoParticleType::list_all()) {
571  if (&A_isotype > &B_isotype) {
572  continue;
573  }
574  bool any_nonzero_cs = false;
575  std::vector<std::string> r_list;
576  for (const ParticleTypePtr A_type : A_isotype.get_states()) {
577  for (const ParticleTypePtr B_type : B_isotype.get_states()) {
578  if (A_type > B_type) {
579  continue;
580  }
581  ParticleData A(*A_type), B(*B_type);
582  for (auto mom : momentum_scan_list) {
583  A.set_4momentum(A.pole_mass(), mom, 0.0, 0.0);
584  B.set_4momentum(B.pole_mass(), -mom, 0.0, 0.0);
585  ScatterActionPtr act = std::make_unique<ScatterAction>(
586  A, B, time, isotropic_, string_formation_time_, -1, false);
588  act->set_string_interface(string_process_interface_.get());
589  }
590  act->add_all_scatterings(finder_parameters_);
591  const double total_cs = act->cross_section();
592  if (total_cs <= 0.0) {
593  continue;
594  }
595  any_nonzero_cs = true;
596  for (const auto& channel : act->collision_channels()) {
597  const auto type = channel->get_type();
598  std::string r;
599  if (is_string_soft_process(type) ||
600  type == ProcessType::StringHard) {
601  r = A_type->name() + B_type->name() + std::string(" → strings");
602  } else {
603  std::string r_type =
604  (type == ProcessType::Elastic) ? std::string(" (el)")
605  : (channel->get_type() == ProcessType::TwoToTwo)
606  ? std::string(" (inel)")
607  : std::string(" (?)");
608  r = A_type->name() + B_type->name() + std::string(" → ") +
609  channel->particle_types()[0]->name() +
610  channel->particle_types()[1]->name() + r_type;
611  }
612  isoclean(r);
613  r_list.push_back(r);
614  }
615  }
616  }
617  }
618  std::sort(r_list.begin(), r_list.end());
619  r_list.erase(std::unique(r_list.begin(), r_list.end()), r_list.end());
620  if (any_nonzero_cs) {
621  for (auto r : r_list) {
622  std::cout << r;
623  if (r_list.back() != r) {
624  std::cout << ", ";
625  }
626  }
627  std::cout << std::endl;
628  }
629  }
630  }
631 }
632 
636  std::string name_;
637 
640 
642  double mass_;
643 
652  FinalStateCrossSection(const std::string& name, double cross_section,
653  double mass)
654  : name_(name), cross_section_(cross_section), mass_(mass) {}
655 };
656 
657 namespace decaytree {
658 
670 struct Node {
671  public:
673  std::string name_;
674 
676  double weight_;
677 
679  ParticleTypePtrList initial_particles_;
680 
682  ParticleTypePtrList final_particles_;
683 
685  ParticleTypePtrList state_;
686 
688  std::vector<Node> children_;
689 
691  Node(const Node&) = delete;
693  Node(Node&&) = default;
694 
705  Node(const std::string& name, double weight,
706  ParticleTypePtrList&& initial_particles,
707  ParticleTypePtrList&& final_particles, ParticleTypePtrList&& state,
708  std::vector<Node>&& children)
709  : name_(name),
710  weight_(weight),
711  initial_particles_(std::move(initial_particles)),
712  final_particles_(std::move(final_particles)),
713  state_(std::move(state)),
714  children_(std::move(children)) {}
715 
727  Node& add_action(const std::string& name, double weight,
728  ParticleTypePtrList&& initial_particles,
729  ParticleTypePtrList&& final_particles) {
730  // Copy parent state and update it.
731  ParticleTypePtrList state(state_);
732  for (const auto& p : initial_particles) {
733  state.erase(std::find(state.begin(), state.end(), p));
734  }
735  for (const auto& p : final_particles) {
736  state.push_back(p);
737  }
738  // Sort the state to normalize the output.
739  std::sort(state.begin(), state.end(),
741  return a->name() < b->name();
742  });
743  // Push new node to children.
744  Node new_node(name, weight, std::move(initial_particles),
745  std::move(final_particles), std::move(state), {});
746  children_.emplace_back(std::move(new_node));
747  return children_.back();
748  }
749 
751  void print() const { print_helper(0); }
752 
756  std::vector<FinalStateCrossSection> final_state_cross_sections() const {
757  std::vector<FinalStateCrossSection> result;
758  final_state_cross_sections_helper(0, result, "", 1.);
759  return result;
760  }
761 
762  private:
769  void print_helper(uint64_t depth) const {
770  for (uint64_t i = 0; i < depth; i++) {
771  std::cout << " ";
772  }
773  std::cout << name_ << " " << weight_ << std::endl;
774  for (const auto& child : children_) {
775  child.print_helper(depth + 1);
776  }
777  }
778 
791  uint64_t depth, std::vector<FinalStateCrossSection>& result,
792  const std::string& name, double weight,
793  bool show_intermediate_states = false) const {
794  // The first node corresponds to the total cross section and has to be
795  // ignored. The second node corresponds to the partial cross section. All
796  // further nodes correspond to branching ratios.
797  if (depth > 0) {
798  weight *= weight_;
799  }
800 
801  std::string new_name;
802  double mass = 0.;
803 
804  if (show_intermediate_states) {
805  new_name = name;
806  if (!new_name.empty()) {
807  new_name += "->";
808  }
809  new_name += name_;
810  new_name += "{";
811  } else {
812  new_name = "";
813  }
814  for (const auto& s : state_) {
815  new_name += s->name();
816  mass += s->mass();
817  }
818  if (show_intermediate_states) {
819  new_name += "}";
820  }
821 
822  if (children_.empty()) {
823  result.emplace_back(FinalStateCrossSection(new_name, weight, mass));
824  return;
825  }
826  for (const auto& child : children_) {
827  child.final_state_cross_sections_helper(depth + 1, result, new_name,
828  weight, show_intermediate_states);
829  }
830  }
831 };
832 
841 static std::string make_decay_name(const std::string& res_name,
842  const DecayBranchPtr& decay,
843  ParticleTypePtrList& final_state) {
844  std::stringstream name;
845  name << "[" << res_name << "->";
846  for (const auto& p : decay->particle_types()) {
847  name << p->name();
848  final_state.push_back(p);
849  }
850  name << "]";
851  return name.str();
852 }
853 
861 static void add_decays(Node& node, double sqrts) {
862  // If there is more than one unstable particle in the current state, then
863  // there will be redundant paths in the decay tree, corresponding to
864  // reorderings of the decays. To avoid double counting, we normalize by the
865  // number of possible decay orderings. Normalizing by the number of unstable
866  // particles recursively corresponds to normalizing by the factorial that
867  // gives the number of reorderings.
868  //
869  // Ideally, the redundant paths should never be added to the decay tree, but
870  // we never have more than two redundant paths, so it probably does not
871  // matter much.
872  uint32_t n_unstable = 0;
873  double sqrts_minus_masses = sqrts;
874  for (const ParticleTypePtr ptype : node.state_) {
875  if (!ptype->is_stable()) {
876  n_unstable += 1;
877  }
878  sqrts_minus_masses -= ptype->mass();
879  }
880  const double norm =
881  n_unstable != 0 ? 1. / static_cast<double>(n_unstable) : 1.;
882 
883  for (const ParticleTypePtr ptype : node.state_) {
884  if (!ptype->is_stable()) {
885  const double sqrts_decay = sqrts_minus_masses + ptype->mass();
886  bool can_decay = false;
887  for (const auto& decay : ptype->decay_modes().decay_mode_list()) {
888  // Make sure to skip kinematically impossible decays.
889  // In principle, we would have to integrate over the mass of the
890  // resonance, but as an approximation we just assume it at its pole.
891  double final_state_mass = 0.;
892  for (const auto& p : decay->particle_types()) {
893  final_state_mass += p->mass();
894  }
895  if (final_state_mass > sqrts_decay) {
896  continue;
897  }
898  can_decay = true;
899 
900  ParticleTypePtrList parts;
901  const auto name = make_decay_name(ptype->name(), decay, parts);
902  auto& new_node = node.add_action(name, norm * decay->weight(), {ptype},
903  std::move(parts));
904  add_decays(new_node, sqrts_decay);
905  }
906  if (!can_decay) {
907  // Remove final-state cross sections with resonances that cannot
908  // decay due to our "mass = pole mass" approximation.
909  node.weight_ = 0;
910  return;
911  }
912  }
913  }
914 }
915 
916 } // namespace decaytree
917 
923 static void deduplicate(std::vector<FinalStateCrossSection>& final_state_xs) {
924  std::sort(final_state_xs.begin(), final_state_xs.end(),
925  [](const FinalStateCrossSection& a,
926  const FinalStateCrossSection& b) { return a.name_ < b.name_; });
927  auto current = final_state_xs.begin();
928  while (current != final_state_xs.end()) {
929  auto adjacent = std::adjacent_find(
930  current, final_state_xs.end(),
931  [](const FinalStateCrossSection& a, const FinalStateCrossSection& b) {
932  return a.name_ == b.name_;
933  });
934  current = adjacent;
935  if (adjacent != final_state_xs.end()) {
936  adjacent->cross_section_ += (adjacent + 1)->cross_section_;
937  final_state_xs.erase(adjacent + 1);
938  }
939  }
940 }
941 
943  const ParticleType& a, const ParticleType& b, double m_a, double m_b,
944  bool final_state, std::vector<double>& plab) const {
945  typedef std::vector<std::pair<double, double>> xs_saver;
946  std::map<std::string, xs_saver> xs_dump;
947  std::map<std::string, double> outgoing_total_mass;
948 
949  ParticleData a_data(a), b_data(b);
950  int n_momentum_points = 200;
951  constexpr double momentum_step = 0.02;
952  if (plab.size() > 0) {
953  n_momentum_points = plab.size();
954  // Remove duplicates.
955  std::sort(plab.begin(), plab.end());
956  plab.erase(std::unique(plab.begin(), plab.end()), plab.end());
957  }
958  for (int i = 0; i < n_momentum_points; i++) {
959  double momentum;
960  if (plab.size() > 0) {
961  momentum = pCM_from_s(s_from_plab(plab.at(i), m_a, m_b), m_a, m_b);
962  } else {
963  momentum = momentum_step * (i + 1);
964  }
965  a_data.set_4momentum(m_a, momentum, 0.0, 0.0);
966  b_data.set_4momentum(m_b, -momentum, 0.0, 0.0);
967  const double sqrts = (a_data.momentum() + b_data.momentum()).abs();
968  const ParticleList incoming = {a_data, b_data};
969  ScatterActionPtr act = std::make_unique<ScatterAction>(
970  a_data, b_data, 0., isotropic_, string_formation_time_, -1, false);
972  act->set_string_interface(string_process_interface_.get());
973  }
974  act->add_all_scatterings(finder_parameters_);
975  decaytree::Node tree(a.name() + b.name(), act->cross_section(), {&a, &b},
976  {&a, &b}, {&a, &b}, {});
977  const CollisionBranchList& processes = act->collision_channels();
978  for (const auto& process : processes) {
979  const double xs = process->weight();
980  if (xs <= 0.0) {
981  continue;
982  }
983  if (!final_state) {
984  std::stringstream process_description_stream;
985  process_description_stream << *process;
986  const std::string& description = process_description_stream.str();
987  double m_tot = 0.0;
988  for (const auto& ptype : process->particle_types()) {
989  m_tot += ptype->mass();
990  }
991  outgoing_total_mass[description] = m_tot;
992  if (!xs_dump[description].empty() &&
993  std::abs(xs_dump[description].back().first - sqrts) <
994  really_small) {
995  xs_dump[description].back().second += xs;
996  } else {
997  xs_dump[description].push_back(std::make_pair(sqrts, xs));
998  }
999  } else {
1000  std::stringstream process_description_stream;
1001  process_description_stream << *process;
1002  const std::string& description = process_description_stream.str();
1003  ParticleTypePtrList initial_particles = {&a, &b};
1004  ParticleTypePtrList final_particles = process->particle_types();
1005  auto& process_node =
1006  tree.add_action(description, xs, std::move(initial_particles),
1007  std::move(final_particles));
1008  decaytree::add_decays(process_node, sqrts);
1009  }
1010  }
1011  xs_dump["total"].push_back(std::make_pair(sqrts, act->cross_section()));
1012  // Total cross-section should be the first in the list -> negative mass
1013  outgoing_total_mass["total"] = -1.0;
1014  if (final_state) {
1015  // tree.print();
1016  auto final_state_xs = tree.final_state_cross_sections();
1017  deduplicate(final_state_xs);
1018  for (const auto& p : final_state_xs) {
1019  // Don't print empty columns.
1020  //
1021  // FIXME(steinberg): The better fix would be to not have them in the
1022  // first place.
1023  if (p.name_ == "") {
1024  continue;
1025  }
1026  outgoing_total_mass[p.name_] = p.mass_;
1027  xs_dump[p.name_].push_back(std::make_pair(sqrts, p.cross_section_));
1028  }
1029  }
1030  }
1031  // Get rid of cross sections that are zero.
1032  // (This only happens if their is a resonance in the final state that cannot
1033  // decay with our simplified assumptions.)
1034  for (auto it = begin(xs_dump); it != end(xs_dump);) {
1035  // Sum cross section over all energies.
1036  const xs_saver& xs = (*it).second;
1037  double sum = 0;
1038  for (const auto& p : xs) {
1039  sum += p.second;
1040  }
1041  if (sum == 0.) {
1042  it = xs_dump.erase(it);
1043  } else {
1044  ++it;
1045  }
1046  }
1047 
1048  // Nice ordering of channels by summed pole mass of products
1049  std::vector<std::string> all_channels;
1050  for (const auto& channel : xs_dump) {
1051  all_channels.push_back(channel.first);
1052  }
1053  std::sort(all_channels.begin(), all_channels.end(),
1054  [&](const std::string& str_a, const std::string& str_b) {
1055  return outgoing_total_mass[str_a] < outgoing_total_mass[str_b];
1056  });
1057 
1058  // Print header
1059  std::cout << "# Dumping partial " << a.name() << b.name()
1060  << " cross-sections in mb, energies in GeV" << std::endl;
1061  std::cout << " sqrt_s";
1062  // Align everything to 16 unicode characters.
1063  // This should be enough for the longest channel name (7 final-state
1064  // particles).
1065  for (const auto& channel : all_channels) {
1066  std::cout << utf8::fill_left(channel, 16, ' ');
1067  }
1068  std::cout << std::endl;
1069 
1070  // Print out all partial cross-sections in mb
1071  for (int i = 0; i < n_momentum_points; i++) {
1072  double momentum;
1073  if (plab.size() > 0) {
1074  momentum = pCM_from_s(s_from_plab(plab.at(i), m_a, m_b), m_a, m_b);
1075  } else {
1076  momentum = momentum_step * (i + 1);
1077  }
1078  a_data.set_4momentum(m_a, momentum, 0.0, 0.0);
1079  b_data.set_4momentum(m_b, -momentum, 0.0, 0.0);
1080  const double sqrts = (a_data.momentum() + b_data.momentum()).abs();
1081  std::printf("%9.6f", sqrts);
1082  for (const auto& channel : all_channels) {
1083  const xs_saver energy_and_xs = xs_dump[channel];
1084  size_t j = 0;
1085  for (; j < energy_and_xs.size() && energy_and_xs[j].first < sqrts; j++) {
1086  }
1087  double xs = 0.0;
1088  if (j < energy_and_xs.size() &&
1089  std::abs(energy_and_xs[j].first - sqrts) < really_small) {
1090  xs = energy_and_xs[j].second;
1091  }
1092  std::printf("%16.6f", xs); // Same alignment as in the header.
1093  }
1094  std::printf("\n");
1095  }
1096 }
1097 
1098 } // namespace smash
Interface to the SMASH configuration files.
Value take(std::initializer_list< const char * > keys)
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.
default_type default_value() const
Get the default value of the key.
Definition: input_keys.h:132
ParticleData contains the dynamic information of a certain particle.
Definition: particledata.h:58
void set_4momentum(const FourVector &momentum_vector)
Set the particle's 4-momentum directly.
Definition: particledata.h:164
const ParticleType & type() const
Get the type of the particle.
Definition: particledata.h:128
double xsec_scaling_factor(double delta_time=0.) const
Return the cross section scaling factor at a given time.
Definition: particledata.cc:86
uint32_t id_process() const
Get the id of the last action.
Definition: particledata.h:134
const FourVector & momentum() const
Get the particle's 4-momentum.
Definition: particledata.h:158
BelongsTo belongs_to() const
Getter for belongs_to label.
Definition: particledata.h:339
int32_t id() const
Get the id of the particle.
Definition: particledata.h:76
HistoryData get_history() const
Get history information.
Definition: particledata.h:139
double pole_mass() const
Get the particle's pole mass ("on-shell").
Definition: particledata.h:115
const FourVector & position() const
Get the particle's position in Minkowski space.
Definition: particledata.h:204
A pointer-like interface to global references to ParticleType objects.
Definition: particletype.h:676
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:246
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:111
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...
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:39
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.
ScatterActionsFinderParameters create_finder_parameters(Configuration &config, const ExperimentParameters &parameters)
Gather all relevant parameters for a ScatterActionsFinder either getting them from an ExperimentParam...
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
constexpr double nucleon_mass
Nucleon mass in GeV.
Definition: constants.h:58
constexpr double pion_mass
Pion mass in GeV.
Definition: constants.h:65
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:37
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:28
Helper structure for Experiment.
const CollisionCriterion coll_crit
Employed collision criterion.
NNbarTreatment nnbar_treatment
This indicates how NN̅ annihilation should be treated; options are to neglect it, make it conserve de...
double maximum_cross_section
The maximal cross section (in mb) for which it is guaranteed that all collisions with this cross sect...
bool strings_switch
This indicates whether string fragmentation is switched on.
const ReactionsBitSet included_2to2
This indicates which two to two reactions are switched off.
double scale_xs
Global factor which all cross sections are scaled with.
bool two_to_one
This indicates whether string fragmentation is switched on.
int testparticles
Number of test-particles.
double low_snn_cut
Elastic collisions between the nucleons with the square root s below low_snn_cut are excluded.
const MultiParticleReactionsBitSet included_multi
This indicates which multi-particle reactions are switched on.
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:32
static const Key< double > collTerm_stringTrans_lower
See user guide description for more information.
Definition: input_keys.h:2528
static const Key< std::pair< double, double > > collTerm_stringTrans_rangeNpi
See user guide description for more information.
Definition: input_keys.h:2563
static const Key< double > collTerm_stringTrans_range_width
See user guide description for more information.
Definition: input_keys.h:2579
static const Key< PseudoResonance > collTerm_pseudoresonance
See user guide description for more information.
Definition: input_keys.h:2093
static const Key< double > collTerm_stringTrans_pipiOffset
See user guide description for more information.
Definition: input_keys.h:2515
static const Key< TotalCrossSectionStrategy > collTerm_totXsStrategy
See user guide description for more information.
Definition: input_keys.h:2059
static const Key< double > collTerm_stringTrans_KNOffset
See user guide description for more information.
Definition: input_keys.h:2500
static const Key< std::pair< double, double > > collTerm_stringTrans_rangeNN
See user guide description for more information.
Definition: input_keys.h:2546
Helper structure for ScatterActionsFinder.
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.
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 CollisionCriterion coll_crit
Specifies which collision criterion is used.
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.