Version: SMASH-3.4
scatteraction.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 
10 #include "smash/scatteraction.h"
11 
12 #include <cmath>
13 
14 #include "Pythia8/Pythia.h"
15 
16 #include "smash/angles.h"
17 #include "smash/constants.h"
18 #include "smash/crosssections.h"
19 #include "smash/fpenvironment.h"
20 #include "smash/logging.h"
21 #include "smash/pdgcode.h"
22 #include "smash/pow.h"
23 #include "smash/processbranch.h"
24 #include "smash/random.h"
25 
26 namespace smash {
27 static constexpr int LScatterAction = LogArea::ScatterAction::id;
28 
30  const ParticleData &in_part_b, const double time,
31  const bool isotropic,
32  const double string_formation_time,
33  const double box_length,
34  const bool is_total_parametrized,
35  const SpinInteractionType spin_interaction_type)
36  : Action({in_part_a, in_part_b}, time),
37  sum_of_partial_cross_sections_(0.),
38  isotropic_(isotropic),
39  string_formation_time_(string_formation_time),
40  is_total_parametrized_(is_total_parametrized),
41  spin_interaction_type_(spin_interaction_type) {
42  box_length_ = box_length;
43  if (is_total_parametrized_) {
44  parametrized_total_cross_section_ = smash_NaN<double>;
45  }
46 }
47 
48 void ScatterAction::add_collision(CollisionBranchPtr p) {
49  add_process<CollisionBranch>(p, collision_channels_,
51 }
52 
53 void ScatterAction::add_collisions(CollisionBranchList pv) {
54  add_processes<CollisionBranch>(std::move(pv), collision_channels_,
56 }
57 
59  logg[LScatterAction].debug("Incoming particles: ", incoming_particles_);
60 
61  const CollisionBranch *proc = choose_channel<CollisionBranch>(
65  process_type_ = proc->get_type();
68 
69  logg[LScatterAction].debug("Chosen channel: ", process_type_,
71 
72  /* The production point of the new particles. */
73  FourVector middle_point = get_interaction_point();
74 
75  switch (process_type_) {
77  /* 2->2 elastic scattering */
80  break;
82  /* resonance formation */
85  break;
87  /* 2->2 inelastic scattering */
88  /* Sample the particle momenta in CM system. */
90  break;
94  /* 2->m scattering */
96  break;
108  break;
109  default:
110  throw InvalidScatterAction(
111  "ScatterAction::generate_final_state: Invalid process type " +
112  std::to_string(static_cast<int>(process_type_)) + " was requested. " +
113  "(PDGcode1=" + incoming_particles_[0].pdgcode().string() +
114  ", PDGcode2=" + incoming_particles_[1].pdgcode().string() + ")");
115  }
116 
117  const bool core_in_incoming =
118  std::any_of(incoming_particles_.begin(), incoming_particles_.end(),
119  [](const ParticleData &p) { return p.is_core(); });
120  for (ParticleData &new_particle : outgoing_particles_) {
121  // Boost to the computational frame
123  new_particle.boost_momentum(
125  }
126  /* Set positions of the outgoing particles */
127  if (proc->get_type() != ProcessType::Elastic) {
128  new_particle.set_4position(middle_point);
129  if (core_in_incoming) {
130  new_particle.fluidize();
131  }
132  if (new_particle.type().pdgcode().is_heavy_flavor()) {
133  // Particle weight is the product of incoming weights
134  const double perturbative_weight = std::accumulate(
135  incoming_particles_.begin(), incoming_particles_.end(), 1.0,
136  [](double w, const ParticleData &p) {
137  return w * p.perturbative_weight();
138  });
139  new_particle.set_perturbative_weight(perturbative_weight);
140  }
141  }
142  }
143 }
144 
146  const ScatterActionsFinderParameters &finder_parameters) {
147  if (were_processes_added_) {
148  logg[LScatterAction].fatal() << "Trying to add processes again.";
149  throw std::logic_error(
150  "add_all_scatterings should be called only once per ScatterAction "
151  "instance");
152  } else {
153  were_processes_added_ = true;
154  }
155 
156  // Prevent charm interactions if CharmRescattering::None is set
157  const PdgCode &pdg_a = incoming_particles_[0].type().pdgcode();
158  const PdgCode &pdg_b = incoming_particles_[1].type().pdgcode();
159  if (finder_parameters.charm_rescattering == CharmRescattering::None &&
160  (pdg_a.frac_charm() != 0 || pdg_b.frac_charm() != 0)) {
161  return;
162  }
163 
166  CollisionBranchList processes =
167  xs.generate_collision_list(finder_parameters, string_process_);
168 
169  // Add various subprocesses.
170  add_collisions(std::move(processes));
171 
172  /* If the string processes are not triggered by a probability, then they
173  * always happen as long as the parametrized total cross section is larger
174  * than the sum of the cross sections of the non-string processes, and the
175  * square root s exceeds the threshold by at least 0.9 GeV. The cross section
176  * of the string processes are counted by taking the difference between the
177  * parametrized total and the sum of the non-strings. */
178  if (!finder_parameters.strings_with_probability &&
179  xs.string_probability(finder_parameters) > 0) {
180  const double xs_diff =
181  xs.high_energy(finder_parameters) - sum_of_partial_cross_sections_;
182  if (xs_diff > 0.) {
184  xs.string_excitation(xs_diff, string_process_, finder_parameters));
185  }
186  }
187 
188  // Prevent pseudoresonances for charmed hadrons in T-matrix approach
189  bool suppress_pseudoresonances_for_T_matrix_channels = false;
190  if (finder_parameters.charm_rescattering == CharmRescattering::T_Matrix &&
191  ((pdg_a.is_Dmeson() || pdg_b.is_Dmeson()) ||
192  (pdg_a.is_Dstar2007() || pdg_b.is_Dstar2007()))) {
193  if ((pdg_a.is_pion() || pdg_b.is_pion()) ||
194  (pdg_a.is_eta() || pdg_b.is_eta()) ||
195  (pdg_a.is_kaon() || pdg_b.is_kaon())) {
196  suppress_pseudoresonances_for_T_matrix_channels = true;
197  }
198  }
199 
200  ParticleTypePtr pseudoresonance =
202  finder_parameters.transition_high_energy);
203  if (pseudoresonance && finder_parameters.two_to_one &&
204  !suppress_pseudoresonances_for_T_matrix_channels) {
205  const double xs_total = is_total_parametrized_
207  : xs.high_energy(finder_parameters);
208  const double xs_gap = xs_total - sum_of_partial_cross_sections_;
209  /* The pseudo-resonance is only created if there is a (positive) cross
210  * section gap */
211  if (xs_gap > really_small) {
212  auto pseudoresonance_branch = std::make_unique<CollisionBranch>(
213  *pseudoresonance, xs_gap, ProcessType::TwoToOne);
214  add_collision(std::move(pseudoresonance_branch));
215  logg[LScatterAction].debug()
216  << "Pseudoresonance between " << incoming_particles_[0].type().name()
217  << " and " << incoming_particles_[1].type().name() << " is "
218  << pseudoresonance->name() << " with cross section " << xs_gap
219  << " mb.";
220  }
221  }
222  // Rescale the branches so that their sum matches the parametrization
225  }
226 }
227 
229  if (!were_processes_added_) {
230  logg[LScatterAction].fatal()
231  << "Trying to rescale branches before adding processes.";
232  throw std::logic_error(
233  "This function can only be called after having added processes.");
234  }
236  const ParticleTypePtr type_a = &incoming_particles_[0].type();
237  const ParticleTypePtr type_b = &incoming_particles_[1].type();
238  // This is a std::set instead of std::pair because the order of particles
239  // does not matter here
240  const std::set<ParticleTypePtr> pair{type_a, type_b};
241  if (!warned_no_rescaling_available.count(pair)) {
242  logg[LScatterAction].warn()
243  << "Total cross section between " << type_a->name() << " and "
244  << type_b->name() << " is roughly zero at sqrt(s) = " << sqrt_s()
245  << " GeV, and no rescaling to match the parametrized value will be "
246  "done.\nAn elastic process will be added, instead, to match the "
247  "total cross section.\nFor this pair of particles, this warning "
248  "will be subsequently suppressed.";
249  warned_no_rescaling_available.insert(pair);
250  }
251  auto elastic_branch = std::make_unique<CollisionBranch>(
252  *type_a, *type_b, *parametrized_total_cross_section_,
254  add_collision(std::move(elastic_branch));
255  } else {
256  const double reweight =
258  logg[LScatterAction].debug("Reweighting ", sum_of_partial_cross_sections_,
260  for (auto &proc : collision_channels_) {
261  proc->set_weight(proc->weight() * reweight);
262  }
263  }
264 }
265 
267  const PseudoResonance method,
268  const StringTransitionParameters &transition) const {
269  const ParticleTypePtr type_a = &incoming_particles_[0].type();
270  const ParticleTypePtr type_b = &incoming_particles_[1].type();
271  const double desired_mass = sqrt_s();
272 
273  double string_offset = 0.5 * transition.sqrts_add_lower;
274  const bool nucleon_and_pion = (type_a->is_nucleon() && type_b->is_pion()) ||
275  (type_a->is_pion() && type_b->is_nucleon());
276  const bool two_nucleons = type_a->is_nucleon() && type_b->is_nucleon();
277  const bool two_pions = type_a->is_pion() && type_b->is_pion();
278  const bool nucleon_and_kaon = (type_a->is_nucleon() && type_b->is_kaon()) ||
279  (type_a->is_kaon() && type_b->is_nucleon());
280  if (nucleon_and_pion) {
281  string_offset = transition.sqrts_range_Npi.first - pion_mass - nucleon_mass;
282  } else if (two_nucleons) {
283  string_offset = transition.sqrts_range_NN.first - 2 * nucleon_mass;
284  } else if (two_pions) {
285  string_offset = transition.pipi_offset;
286  } else if (nucleon_and_kaon) {
287  string_offset = transition.KN_offset;
288  }
289  /*
290  * Artificial cutoff to create a pseudo-resonance only close to the string
291  * transition, where data or first-principle models are unhelpful
292  */
293  if (desired_mass < incoming_particles_[0].effective_mass() +
294  incoming_particles_[1].effective_mass() +
295  string_offset) {
296  return {};
297  }
298 
299  if (method == PseudoResonance::None) {
300  return {};
301  } else if (method == PseudoResonance::LargestFromUnstable ||
303  if (type_a->is_stable() && type_b->is_stable()) {
304  return {};
305  }
306  }
307 
308  // If this list is empty, there are no possible pseudo-resonances.
309  ParticleTypePtrList list = list_possible_resonances(type_a, type_b);
310  if (std::empty(list)) {
311  return {};
312  }
313 
314  if (method == PseudoResonance::Largest ||
316  auto largest = *std::max_element(list.begin(), list.end(),
318  return a->mass() < b->mass();
319  });
320  return largest;
321  } else if (method == PseudoResonance::Closest ||
323  auto comparison = [&desired_mass](ParticleTypePtr a, ParticleTypePtr b) {
324  return std::abs(a->mass() - desired_mass) <
325  std::abs(b->mass() - desired_mass);
326  };
327  auto closest = *std::min_element(list.begin(), list.end(), comparison);
328  return closest;
329  } else {
330  throw std::logic_error("Unknown method for selecting pseudoresonance.");
331  }
332 }
333 
335  const ScatterActionsFinderParameters &finder_parameters) {
338 
341  xs.parametrized_total(finder_parameters);
342  } else {
343  logg[LScatterAction].fatal()
344  << "Trying to parametrize total cross section when it shouldn't be.";
345  throw std::logic_error(
346  "This function can only be called on ScatterAction objects with "
347  "parametrized cross section.");
348  }
349 }
350 
353  incoming_particles_[0].xsec_scaling_factor() *
354  incoming_particles_[1].xsec_scaling_factor();
355 }
356 
358  return partial_cross_section_ * incoming_particles_[0].xsec_scaling_factor() *
359  incoming_particles_[1].xsec_scaling_factor();
360 }
361 
363  return total_momentum().velocity();
364 }
365 
366 double ScatterAction::gamma_cm() const {
367  return (1. / std::sqrt(1.0 - beta_cm().sqr()));
368 }
369 
370 double ScatterAction::mandelstam_s() const { return total_momentum().sqr(); }
371 
373  const double m1 = incoming_particles_[0].effective_mass();
374  const double m2 = incoming_particles_[1].effective_mass();
375  return pCM(sqrt_s(), m1, m2);
376 }
377 
379  const double m1 = incoming_particles_[0].effective_mass();
380  const double m2 = incoming_particles_[1].effective_mass();
381  return pCM_sqr(sqrt_s(), m1, m2);
382 }
383 
385  const double m1 = incoming_particles()[0].effective_mass();
386  const double m2 = incoming_particles()[1].effective_mass();
387  const double m_s = mandelstam_s();
388  const double lamb = lambda_tilde(m_s, m1 * m1, m2 * m2);
389  return std::sqrt(lamb) / (2. * incoming_particles()[0].momentum().x0() *
390  incoming_particles()[1].momentum().x0());
391 }
392 
394  // local copy of particles (since we need to boost them)
397  /* Boost particles to center-of-momentum frame. */
398  const ThreeVector velocity = beta_cm();
399  p_a.boost(velocity);
400  p_b.boost(velocity);
401  const ThreeVector pos_diff =
402  p_a.position().threevec() - p_b.position().threevec();
403  const ThreeVector mom_diff =
404  p_a.momentum().threevec() - p_b.momentum().threevec();
405 
406  logg[LScatterAction].debug("Particle ", incoming_particles_,
407  " position difference [fm]: ", pos_diff,
408  ", momentum difference [GeV]: ", mom_diff);
409 
410  const double dp2 = mom_diff.sqr();
411  const double dr2 = pos_diff.sqr();
412  /* Zero momentum leads to infite distance. */
413  if (dp2 < really_small) {
414  return dr2;
415  }
416  const double dpdr = pos_diff * mom_diff;
417 
418  /* UrQMD squared distance criterion, in the center of momentum frame:
419  * position of particle a: x_a
420  * position of particle b: x_b
421  * momentum of particle a: p_a
422  * momentum of particle b: p_b
423  * d^2_{coll} = (x_a - x_b)^2 - ((x_a - x_b) . (p_a - p_b))^2 / (p_a - p_b)^2
424  */
425  const double result = dr2 - dpdr * dpdr / dp2;
426  return result > 0.0 ? result : 0.0;
427 }
428 
430  // local copy of particles (since we need to boost them)
433 
434  const FourVector delta_x = p_a.position() - p_b.position();
435  const double mom_diff_sqr =
436  (p_a.momentum().threevec() - p_b.momentum().threevec()).sqr();
437  const double x_sqr = delta_x.sqr();
438 
439  if (mom_diff_sqr < really_small) {
440  return -x_sqr;
441  }
442 
443  const double p_a_sqr = p_a.momentum().sqr();
444  const double p_b_sqr = p_b.momentum().sqr();
445  const double p_a_dot_x = p_a.momentum().Dot(delta_x);
446  const double p_b_dot_x = p_b.momentum().Dot(delta_x);
447  const double p_a_dot_p_b = p_a.momentum().Dot(p_b.momentum());
448 
449  const double b_sqr =
450  -x_sqr -
451  (p_a_sqr * std::pow(p_b_dot_x, 2) + p_b_sqr * std::pow(p_a_dot_x, 2) -
452  2 * p_a_dot_p_b * p_a_dot_x * p_b_dot_x) /
453  (std::pow(p_a_dot_p_b, 2) - p_a_sqr * p_b_sqr);
454  return b_sqr > 0.0 ? b_sqr : 0.0;
455 }
456 
457 /**
458  * Computes the B coefficients from the STAR fit, see fig. (6) in
459  * \iref{STAR:2020phn}.
460  *
461  * \param[in] plab Lab momentum in GeV.
462  *
463  * \return B coefficients of high-energy elastic proton-proton scatterings.
464  */
465 static double high_energy_bpp(double plab) {
466  double mandelstam_s = s_from_plab(plab, nucleon_mass, nucleon_mass);
467  return 7.6 + 0.66 * std::log(mandelstam_s);
468 }
469 
470 /**
471  * Computes the B coefficients from the Cugnon parametrization of the angular
472  * distribution in elastic pp scattering.
473  *
474  * See equation (8) in \iref{Cugnon:1996kh}.
475  * Note: The original Cugnon parametrization is only applicable for
476  * plab < 6 GeV and keeps rising above that.
477  *
478  * \param[in] plab Lab momentum in GeV.
479  *
480  * \return Cugnon B coefficient for elastic proton-proton scatterings.
481  */
482 static double Cugnon_bpp(double plab) {
483  if (plab < 2.) {
484  double p8 = pow_int(plab, 8);
485  return 5.5 * p8 / (7.7 + p8);
486  } else {
487  return std::min(high_energy_bpp(plab), 5.334 + 0.67 * (plab - 2.));
488  }
489 }
490 
491 /**
492  * Computes the B coefficients from the Cugnon parametrization of the angular
493  * distribution in elastic np scattering.
494  *
495  * See equation (10) in \iref{Cugnon:1996kh}.
496  *
497  * \param[in] plab Lab momentum in GeV.
498  *
499  * \return Cugnon B coefficient for elastic proton-neutron scatterings.
500  */
501 static double Cugnon_bnp(double plab) {
502  if (plab < 0.225) {
503  return 0.;
504  } else if (plab < 0.6) {
505  return 16.53 * (plab - 0.225);
506  } else if (plab < 1.6) {
507  return -1.63 * plab + 7.16;
508  } else {
509  return Cugnon_bpp(plab);
510  }
511 }
512 
513 void ScatterAction::sample_angles(std::pair<double, double> masses,
514  double kinetic_energy_cm) {
516  // We potentially have more than two particles, so the following angular
517  // distributions don't work. Instead we just keep the angular
518  // distributions generated by string fragmentation.
519  return;
520  }
521  assert(outgoing_particles_.size() == 2);
522 
523  // NN scattering is anisotropic currently
524  const bool nn_scattering = incoming_particles_[0].type().is_nucleon() &&
525  incoming_particles_[1].type().is_nucleon();
526  /* Elastic process is anisotropic and
527  * the angular distribution is based on the NN elastic scattering. */
528  const bool el_scattering = process_type_ == ProcessType::Elastic;
529 
530  const double mass_in_a = incoming_particles_[0].effective_mass();
531  const double mass_in_b = incoming_particles_[1].effective_mass();
532 
535 
536  const double mass_a = masses.first;
537  const double mass_b = masses.second;
538 
539  const std::array<double, 2> t_range = get_t_range<double>(
540  kinetic_energy_cm, mass_in_a, mass_in_b, mass_a, mass_b);
541  Angles phitheta;
542  if (el_scattering && !isotropic_) {
543  /** NN → NN: Choose angular distribution according to Cugnon
544  * parametrization,
545  * see \iref{Cugnon:1996kh}. */
546  double mandelstam_s_new = 0.;
547  if (nn_scattering) {
548  mandelstam_s_new = mandelstam_s();
549  } else {
550  /* In the case of elastic collisions other than NN collisions,
551  * there is an ambiguity on how to get the lab-frame momentum (plab),
552  * since the incoming particles can have different masses.
553  * Right now, we first obtain the center-of-mass momentum
554  * of the collision (pcom_now).
555  * Then, the lab-frame momentum is evaluated from the mandelstam s,
556  * which yields the original center-of-mass momentum
557  * when nucleon mass is assumed. */
558  const double pcm_now = pCM_from_s(mandelstam_s(), mass_in_a, mass_in_b);
559  mandelstam_s_new =
560  4. * std::sqrt(pcm_now * pcm_now + nucleon_mass * nucleon_mass);
561  }
562  double bb, a, plab = plab_from_s(mandelstam_s_new);
563  if (nn_scattering &&
564  p_a->pdgcode().antiparticle_sign() ==
565  p_b->pdgcode().antiparticle_sign() &&
566  std::abs(p_a->type().charge() + p_b->type().charge()) == 1) {
567  // proton-neutron and antiproton-antineutron
568  bb = std::max(Cugnon_bnp(plab), really_small);
569  a = (plab < 0.8) ? 1. : 0.64 / (plab * plab);
570  } else {
571  /* all others including pp, nn and AQM elastic processes
572  * This is applied for all particle pairs, which are allowed to
573  * interact elastically. */
574  bb = std::max(Cugnon_bpp(plab), really_small);
575  a = 1.;
576  }
577  double t = random::expo(bb, t_range[0], t_range[1]);
578  if (random::canonical() > 1. / (1. + a)) {
579  t = t_range[0] + t_range[1] - t;
580  }
581  // determine scattering angles in center-of-mass frame
582  phitheta = Angles(2. * M_PI * random::canonical(),
583  1. - 2. * (t - t_range[0]) / (t_range[1] - t_range[0]));
584  } else if (nn_scattering && p_a->pdgcode().is_Delta() &&
585  p_b->pdgcode().is_nucleon() &&
586  p_a->pdgcode().antiparticle_sign() ==
587  p_b->pdgcode().antiparticle_sign() &&
588  !isotropic_) {
589  /** NN → NΔ: Sample scattering angles in center-of-mass frame from an
590  * anisotropic angular distribution, using the same distribution as for
591  * elastic pp scattering, as suggested in \iref{Cugnon:1996kh}. */
592  const double plab = plab_from_s(mandelstam_s());
593  const double bb = std::max(Cugnon_bpp(plab), really_small);
594  double t = random::expo(bb, t_range[0], t_range[1]);
595  if (random::canonical() > 0.5) {
596  t = t_range[0] + t_range[1] - t; // symmetrize
597  }
598  phitheta = Angles(2. * M_PI * random::canonical(),
599  1. - 2. * (t - t_range[0]) / (t_range[1] - t_range[0]));
600  } else if (nn_scattering && p_b->pdgcode().is_nucleon() && !isotropic_ &&
601  (p_a->type().is_Nstar() || p_a->type().is_Deltastar())) {
602  /** NN → NR: Fit to HADES data, see \iref{Agakishiev:2014wqa}. */
603  const std::array<double, 4> p{1.46434, 5.80311, -6.89358, 1.94302};
604  const double a = p[0] + mass_a * (p[1] + mass_a * (p[2] + mass_a * p[3]));
605  /* If the resonance is so heavy that the index "a" exceeds 30,
606  * the power function turns out to be too sharp. Take t directly to be
607  * t_0 in such a case. */
608  double t = t_range[0];
609  if (a < 30) {
610  t = random::power(-a, t_range[0], t_range[1]);
611  }
612  if (random::canonical() > 0.5) {
613  t = t_range[0] + t_range[1] - t; // symmetrize
614  }
615  phitheta = Angles(2. * M_PI * random::canonical(),
616  1. - 2. * (t - t_range[0]) / (t_range[1] - t_range[0]));
617  } else {
618  /* isotropic angular distribution */
619  phitheta.distribute_isotropically();
620  }
621 
622  ThreeVector pscatt = phitheta.threevec();
623  // 3-momentum of first incoming particle in center-of-mass frame
624  ThreeVector pcm =
625  incoming_particles_[0].momentum().lorentz_boost(beta_cm()).threevec();
626  pscatt.rotate_z_axis_to(pcm);
627 
628  // final-state CM momentum
629  const double p_f = pCM(kinetic_energy_cm, mass_a, mass_b);
630  if (!(p_f > 0.0)) {
631  logg[LScatterAction].warn("Particle: ", p_a->pdgcode(),
632  " radial momentum: ", p_f);
633  logg[LScatterAction].warn("Etot: ", kinetic_energy_cm, " m_a: ", mass_a,
634  " m_b: ", mass_b);
635  }
636  p_a->set_4momentum(mass_a, pscatt * p_f);
637  p_b->set_4momentum(mass_b, -pscatt * p_f);
638 
639  /* Debug message is printed before boost, so that p_a and p_b are
640  * the momenta in the center of mass frame and thus opposite to
641  * each other.*/
642  logg[LScatterAction].debug("p_a: ", *p_a, "\np_b: ", *p_b);
643 }
644 
646  // copy initial particles into final state
649  // resample momenta
650  sample_angles({outgoing_particles_[0].effective_mass(),
651  outgoing_particles_[1].effective_mass()},
652  sqrt_s());
653 }
654 
656  // create new particles
661  }
662 }
663 
669  }
670  logg[LScatterAction].debug("2->", outgoing_particles_.size(),
671  " scattering:", incoming_particles_, " -> ",
673 }
674 
676  if (outgoing_particles_.size() != 1) {
677  std::string s =
678  "resonance_formation: "
679  "Incorrect number of particles in final state: ";
680  s += std::to_string(outgoing_particles_.size()) + " (";
681  s += incoming_particles_[0].pdgcode().string() + " + ";
682  s += incoming_particles_[1].pdgcode().string() + ")";
683  throw InvalidResonanceFormation(s);
684  }
685  // Set the momentum of the formed resonance in its rest frame.
686  outgoing_particles_[0].set_4momentum(
687  total_momentum_of_outgoing_particles().abs(), 0., 0., 0.);
691  }
692  /* this momentum is evaluated in the computational frame. */
693  logg[LScatterAction].debug("Momentum of the new particle: ",
694  outgoing_particles_[0].momentum());
695 }
696 
697 /* This function generates the outgoing state when
698  * ScatterAction::string_excitation() is used */
699 
703  /* Check momentum difference for debugging */
704  FourVector out_mom;
705  for (ParticleData data : outgoing_particles_) {
706  out_mom += data.momentum();
707  }
708  logg[LPythia].debug("Incoming momenta string:", total_momentum());
709  logg[LPythia].debug("Outgoing momenta string:", out_mom);
710 }
711 
712 /* This function will generate outgoing particles in computational frame
713  * from a hard process.
714  * The way to excite soft strings is based on the UrQMD model */
715 
717  assert(incoming_particles_.size() == 2);
718  // Disable floating point exception trap for Pythia
719  {
720  DisableFloatTraps guard;
721  /* initialize the string_process_ object for this particular collision */
723  /* implement collision */
724  bool success = false;
725  int ntry = 0;
726  const int ntry_max = 10000;
727  while (!success && ntry < ntry_max) {
728  ntry++;
729  success = string_process_->next(process_type_);
730  }
731 
732  if (ntry == ntry_max) {
733  /* If pythia fails to form a string, it is usually because the energy
734  * is not large enough. In this case, annihilation is then enforced. If
735  * this process still does not not produce any results, it defaults to
736  * an elastic collision. */
737  bool success_newtry = false;
738 
739  /* Check if the initial state is a baryon-antibaryon state.*/
740  PdgCode part1 = incoming_particles_[0].pdgcode(),
741  part2 = incoming_particles_[1].pdgcode();
742  bool is_BBbar_Pair = (part1.baryon_number() != 0) &&
743  (part1.baryon_number() == -part2.baryon_number());
744 
745  /* Decide on the new process .*/
746  if (is_BBbar_Pair) {
748  } else {
750  }
751  /* Perform the new process*/
752  int ntry_new = 0;
753  while (!success_newtry && ntry_new < ntry_max) {
754  ntry_new++;
755  success_newtry = string_process_->next(process_type_);
756  }
757 
758  if (success_newtry) {
760  }
761 
762  if (!success_newtry) {
763  /* If annihilation fails:
764  * Particles are normally added after process selection for
765  * strings, outgoing_particles is still uninitialized, and memory
766  * needs to be allocated. We also shift the process_type_ to elastic
767  * so that sample_angles does a proper treatment. */
768  outgoing_particles_.reserve(2);
773  }
774  } else {
776  }
777  }
778 }
780  ParticleData &outgoing_particle_a, ParticleData &outgoing_particle_b) {
781  // Boost spin vectors
782  outgoing_particle_a.set_spin_vector(
783  outgoing_particle_a.spin_vector().lorentz_boost(
784  outgoing_particle_a.velocity()));
785  outgoing_particle_b.set_spin_vector(
786  outgoing_particle_b.spin_vector().lorentz_boost(
787  outgoing_particle_b.velocity()));
788 }
789 
792  /* 2->2 elastic scattering */
794  // Final boost to the outgoing particle momenta
797  }
798 
799  /* 2->1 resonance formation */
801  /*
802  * @brief Λ+π → Σ* resonance formation with Λ–spin bookkeeping.
803  *
804  * We do not simulate a direct inelastic Λ+π scattering; instead we form a
805  * Σ* resonance and let it decay later. To preserve Λ polarization per
806  * arXiv:2404.15890v2, we treat the Σ* spin vector as a proxy for the
807  * would-be outgoing Λ spin: at formation, we set the Σ* spin to the
808  * incoming Λ spin and apply a possible spin flip according to the
809  * Λ–flip/non-flip fractions extracted from the paper. On Σ* → Λ+π decay,
810  * the Σ* spin vector is copied to the Λ, thus transporting Λ polarization
811  * through the resonance stage.
812  */
813  // Identify if the outgoing resonance is a Σ*
814  if (outgoing_particles_[0].is_sigmastar()) {
815  // Check that one of the incoming particles is a Λ and the other a π
816  const bool has_lambda = incoming_particles_[0].pdgcode().is_Lambda() ||
817  incoming_particles_[1].pdgcode().is_Lambda();
818  const bool has_pion = incoming_particles_[0].is_pion() ||
819  incoming_particles_[1].is_pion();
820  if (has_lambda && has_pion) {
821  auto &lambda = (incoming_particles_[0].pdgcode().is_Lambda())
823  : incoming_particles_[1];
824  auto &sigma_star = outgoing_particles_[0];
825 
826  // Perform spin flip with probability of 2/9
827  int random_int = random::uniform_int(1, 9);
828  FourVector final_spin_vector = lambda.spin_vector();
829 
830  if (random_int <= 7) {
831  // No spin flip
832  final_spin_vector = final_spin_vector.lorentz_boost(
833  outgoing_particles_[0].velocity());
834  outgoing_particles_[0].set_spin_vector(final_spin_vector);
835  } else {
836  // Spin flip in Lambda rest frame
837  ThreeVector lambda_velocity = lambda.velocity();
838  final_spin_vector =
839  final_spin_vector.lorentz_boost(lambda_velocity);
840 
841  // Flip the spatial spin vector components
842  final_spin_vector[1] = -final_spin_vector[1];
843  final_spin_vector[2] = -final_spin_vector[2];
844  final_spin_vector[3] = -final_spin_vector[3];
845 
846  // Boost back to computational frame and to Sigma* frame
847  final_spin_vector =
848  final_spin_vector.lorentz_boost(-lambda_velocity);
849  final_spin_vector =
850  final_spin_vector.lorentz_boost(sigma_star.velocity());
851  sigma_star.set_spin_vector(final_spin_vector);
852  }
853  }
854  }
855  }
856  }
857 }
858 
860  // Check if spin interaction is disabled
862  return;
863  }
864  const bool is_AB_to_AX =
866  const bool is_AB_to_XB =
868 
869  /* This logic relies on the assumption that the surviving hadron is
870  * always appended as the final element in the outgoing particle list.
871  * This ordering is guaranteed by StringProcess::next_SDiff(bool
872  * is_AB_to_AX). If that implementation changes, the behavior here must
873  * be re-evaluated. */
874  if (is_AB_to_AX || is_AB_to_XB) {
875  const std::size_t idx_hadron_in = is_AB_to_AX ? 0 : 1;
876 
877  // Boost spin vector of surviving hadron to outgoing frame
878  const FourVector final_spin_vector =
879  incoming_particles_[idx_hadron_in].spin_vector().lorentz_boost(
880  outgoing_particles_.back().velocity());
881 
882  outgoing_particles_.back().set_spin_vector(final_spin_vector);
883 
884  /* Set unpolarized spin vector for all newly created particles (all but
885  * the last one) */
886  for (auto it = outgoing_particles_.begin();
887  it != outgoing_particles_.end() - 1; ++it) {
888  it->set_unpolarized_spin_vector();
889  }
890  } else {
891  for (auto &particle : outgoing_particles_) {
892  particle.set_unpolarized_spin_vector();
893  }
894  }
895 }
896 
897 void ScatterAction::format_debug_output(std::ostream &out) const {
898  out << "Scatter of " << incoming_particles_;
899  if (outgoing_particles_.empty()) {
900  out << " (not performed)";
901  } else {
902  out << " to " << outgoing_particles_;
903  }
904 }
905 
906 } // namespace smash
Thrown for example when ScatterAction is called to perform with a wrong number of final-state particl...
Definition: action.h:330
Action is the base class for a generic process that takes a number of incoming particles and transfor...
Definition: action.h:35
virtual void sample_2body_phasespace()
Sample the full 2-body phase-space (masses, momenta, angles) in the center-of-mass frame for the fina...
Definition: action.cc:308
FourVector total_momentum_of_outgoing_particles() const
Calculate the total kinetic momentum of the outgoing particles.
Definition: action.cc:163
void assign_formation_time_to_outgoing_particles()
Assign the formation time to the outgoing particles.
Definition: action.cc:194
std::pair< FourVector, FourVector > get_potential_at_interaction_point() const
Get the skyrme and asymmetry potential at the interaction point.
Definition: action.cc:115
ParticleList outgoing_particles_
Initially this stores only the PDG codes of final-state particles.
Definition: action.h:363
FourVector total_momentum() const
Sum of 4-momenta of incoming particles.
Definition: action.h:389
virtual void sample_manybody_phasespace()
Sample the full n-body phase-space (masses, momenta, angles) in the center-of-mass frame for the fina...
Definition: action.cc:319
const double time_of_execution_
Time at which the action is supposed to be performed (absolute time in the lab frame in fm).
Definition: action.h:369
void assign_unpolarized_spin_vector_to_outgoing_particles()
Assign an unpolarized spin vector to all outgoing particles.
Definition: action.cc:339
const ParticleList & incoming_particles() const
Get the list of particles that go into the action.
Definition: action.cc:61
double sqrt_s() const
Determine the total energy in the center-of-mass frame [GeV].
Definition: action.h:271
ParticleList incoming_particles_
List with data of incoming particles.
Definition: action.h:355
FourVector get_interaction_point() const
Get the interaction point.
Definition: action.cc:71
static double lambda_tilde(double a, double b, double c)
Little helper function that calculates the lambda function (sometimes written with a tilde to better ...
Definition: action.h:315
ProcessType process_type_
type of process
Definition: action.h:372
Angles provides a common interface for generating directions: i.e., two angles that should be interpr...
Definition: angles.h:59
ThreeVector threevec() const
Definition: angles.h:288
void distribute_isotropically()
Populate the object with a new direction.
Definition: angles.h:199
CollisionBranch is a derivative of ProcessBranch, which is used to represent particular final-state c...
ProcessType get_type() const override
The CrossSections class assembles everything that is needed to calculate cross sections and gathers a...
Definition: crosssections.h:31
double high_energy(const ScatterActionsFinderParameters &finder_parameters) const
Determine the parametrized total cross section at high energies for the given collision,...
double string_probability(const ScatterActionsFinderParameters &finder_parameters) const
CollisionBranchList string_excitation(double total_string_xs, StringProcess *string_process, const ScatterActionsFinderParameters &finder_parameters) const
Determine the cross section for string excitations, which is given by the difference between the para...
double parametrized_total(const ScatterActionsFinderParameters &finder_parameters) const
Select the parametrization for the total cross section, given the types of incoming particles.
CollisionBranchList generate_collision_list(const ScatterActionsFinderParameters &finder_parameters, StringProcess *string_process) const
Generate a list of all possible collisions between the incoming particles with the given c....
Guard type that safely disables floating point traps for the scope in which it is placed.
Definition: fpenvironment.h:79
The FourVector class holds relevant values in Minkowski spacetime with (+, −, −, −) metric signature.
Definition: fourvector.h:33
double sqr() const
calculate the square of the vector (which is a scalar)
Definition: fourvector.h:460
FourVector lorentz_boost(const ThreeVector &v) const
Returns the FourVector boosted with velocity v.
Definition: fourvector.cc:17
ThreeVector threevec() const
Definition: fourvector.h:329
double Dot(const FourVector &a) const
calculate the scalar product with another four-vector
Definition: fourvector.h:456
ThreeVector velocity() const
Get the velocity (3-vector divided by zero component).
Definition: fourvector.h:333
ParticleData contains the dynamic information of a certain particle.
Definition: particledata.h:59
PdgCode pdgcode() const
Get the pdgcode of the particle.
Definition: particledata.h:88
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
const FourVector & momentum() const
Get the particle's 4-momentum.
Definition: particledata.h:171
ThreeVector velocity() const
Get the velocity 3-vector.
Definition: particledata.h:321
const FourVector & spin_vector() const
Get the mean spin 4-vector (Pauli–Lubanski vector) of the particle (const reference,...
Definition: particledata.h:365
void set_spin_vector(const FourVector &s)
Set the mean spin 4-vector (Pauli–Lubanski vector) of the particle.
Definition: particledata.h:375
void boost(const ThreeVector &v)
Apply a full Lorentz boost of momentum and position.
Definition: particledata.h:343
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
bool is_pion() const
Definition: particletype.h:221
bool is_Nstar() const
Definition: particletype.h:233
const std::string & name() const
Definition: particletype.h:144
int32_t charge() const
The charge of the particle.
Definition: particletype.h:191
bool is_stable() const
Definition: particletype.h:251
bool is_nucleon() const
Definition: particletype.h:218
double mass() const
Definition: particletype.h:147
bool is_kaon() const
Definition: particletype.h:224
bool is_Deltastar() const
Definition: particletype.h:245
PdgCode stores a Particle Data Group Particle Numbering Scheme particle type number.
Definition: pdgcode.h:108
bool is_Dmeson() const
Definition: pdgcode.h:489
int antiparticle_sign() const
Definition: pdgcode.h:734
int baryon_number() const
Definition: pdgcode.h:388
bool is_Dstar2007() const
Definition: pdgcode.h:498
bool is_pion() const
Definition: pdgcode.h:471
bool is_kaon() const
Definition: pdgcode.h:465
double frac_charm() const
Definition: pdgcode.h:573
bool is_nucleon() const
Definition: pdgcode.h:404
bool is_eta() const
Definition: pdgcode.h:477
bool is_Delta() const
Definition: pdgcode.h:428
ParticleList particle_list() const
double weight() const
Thrown when ScatterAction is called to perform with unknown ProcessType.
SpinInteractionType spin_interaction_type_
What kind of spin interaction to use.
bool isotropic_
Do this collision isotropically?
std::optional< double > parametrized_total_cross_section_
If cross section is parametrized, store the value.
void string_spin_interaction()
Perform spin interaction in string excitations.
void rescale_outgoing_branches()
Loop over the possible branches and rescales their weight according to the desired total cross sectio...
void add_collision(CollisionBranchPtr p)
Add a new collision channel.
ScatterAction(const ParticleData &in_part1, const ParticleData &in_part2, double time, bool isotropic=false, double string_formation_time=1.0, double box_length=-1.0, bool is_total_parametrized=false, const SpinInteractionType spin_interaction_type=SpinInteractionType::Off)
Construct a ScatterAction object.
void resonance_formation()
Perform a 2->1 resonance-formation process.
ThreeVector beta_cm() const
Get the velocity of the center of mass of the scattering/incoming particles in the calculation frame.
double partial_cross_section_
Partial cross-section to the chosen outgoing channel.
double relative_velocity() const
Get the relative velocity of the two incoming particles.
double mandelstam_s() const
Determine the Mandelstam s variable,.
void set_parametrized_total_cross_section(const ScatterActionsFinderParameters &finder_parameters)
Given the incoming particles, assigns the correct parametrization of the total cross section.
double get_partial_weight() const override
Get the partial cross section of the chosen channel.
StringProcess * string_process_
Pointer to interface class for strings.
bool is_total_parametrized_
Whether the total cross section is parametrized.
void create_string_final_state()
Creates the final states for string-processes after they are performed.
double sum_of_partial_cross_sections_
Current sum of partial hadronic cross sections.
double cm_momentum_squared() const
Get the squared momentum of the center of mass of the incoming particles in the calculation frame.
void generate_final_state() override
Generate the final-state of the scattering process.
void add_all_scatterings(const ScatterActionsFinderParameters &finder_parameters)
Add all possible scattering subprocesses for this action object.
void two_to_many_scattering()
Perform an inelastic two-to-many-body scattering (more than 2)
double get_total_weight() const override
Get the total cross section of scattering particles.
void string_excitation()
Todo(ryu): document better - it is not really UrQMD-based, isn't it? Perform the UrQMD-based string e...
void elastic_scattering()
Perform an elastic two-body scattering, i.e. just exchange momentum.
void inelastic_scattering()
Perform an inelastic two-body scattering, i.e. new particles are formed.
void spin_interaction()
Perform spin interaction in binary interactions.
void sample_angles(std::pair< double, double > masses, double kinetic_energy_cm) override
Sample final-state angles in a 2->2 collision (possibly anisotropic).
void add_collisions(CollisionBranchList pv)
Add several new collision channels at once.
double gamma_cm() const
Get the gamma factor corresponding to a boost to the center of mass frame of the colliding particles.
double cm_momentum() const
Get the momentum of the center of mass of the incoming particles in the calculation frame.
bool were_processes_added_
Lock for calling add_all_scatterings only once.
static std::set< std::set< ParticleTypePtr > > warned_no_rescaling_available
Warn about zero cross section only once per particle type pair.
double cov_transverse_distance_sqr() const
Calculate the transverse distance of the two incoming particles in their local rest frame written in ...
ParticleTypePtr try_find_pseudoresonance(const PseudoResonance method, const StringTransitionParameters &transition) const
Try to find a pseudo-resonance that can be created from the incoming particles using a given method.
CollisionBranchList collision_channels_
List of possible collisions.
double transverse_distance_sqr() const
Calculate the transverse distance of the two incoming particles in their local rest frame.
Helper class for ScatterActionsFinder.
const bool strings_with_probability
This indicates whether the string fragmentation is swiched on with a probability smoothly increasing ...
const StringTransitionParameters transition_high_energy
Constants related to transition between low collision energies - mediated via resonances - and high c...
const CharmRescattering charm_rescattering
Specifies kind of charm rescattering.
const PseudoResonance pseudoresonance_method
Which pseudo-resonance to choose.
const bool two_to_one
Enables resonance production.
ParticleList get_final_state()
bool next(ProcessType type)
Generate the next string process for a given process type.
void init(const ParticleList &incoming, double tcoll)
initialization feed intial particles, time of collision and gamma factor of the center of mass.
The ThreeVector class represents a physical three-vector with the components .
Definition: threevector.h:31
double sqr() const
Definition: threevector.h:275
void rotate_z_axis_to(ThreeVector &r)
Rotate the z-axis onto the vector r.
Definition: threevector.h:335
Collection of useful constants that are known at compile time.
@ T_Matrix
Charm interactions via T-matrix approach.
@ None
Disable charm interactions.
PseudoResonance
Which pseudo-resonance fills the inelastic gap in the transition to string region of cross sections.
@ Closest
Resonance with the pole mass closest from the invariant mass of incoming particles for all processes.
@ ClosestFromUnstable
Closest resonance for a given mass from processes with at least one resonance in the incoming particl...
@ None
No pseudo-resonance is created.
@ LargestFromUnstable
Heaviest possible resonance from processes with at least one resonance in the incoming particles.
@ Largest
Resonance of largest mass for all processes.
SpinInteractionType
Possible spin interaction types.
@ Off
No spin interactions.
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > & logg
An array that stores all pre-configured Logger objects.
Definition: logging.h:245
void format_debug_output(std::ostream &out) const override
Writes information about this scatter action to the out stream.
constexpr int p
Proton.
T power(T n, T xMin, T xMax)
Sample from a power-law probability density proportional to |x|^n.
Definition: random.h:229
T expo(T A, T x1, T x2)
Draws a random number x from an exponential distribution exp(A*x), where A is assumed to be positive,...
Definition: random.h:178
T uniform_int(T min, T max)
Definition: random.h:106
T canonical()
Definition: random.h:122
Definition: action.h:24
double plab_from_s(double mandelstam_s, double mass)
Convert Mandelstam-s to p_lab in a fixed-target collision.
Definition: kinematics.h:157
static double Cugnon_bnp(double plab)
Computes the B coefficients from the Cugnon parametrization of the angular distribution in elastic np...
T pCM(const T sqrts, const T mass_a, const T mass_b) noexcept
Definition: kinematics.h:79
ParticleTypePtrList list_possible_resonances(const ParticleTypePtr type_a, const ParticleTypePtr type_b)
Lists the possible resonances that decay into two particles.
T pCM_sqr(const T sqrts, const T mass_a, const T mass_b) noexcept
Definition: kinematics.h:91
@ FailedString
See here for a short description.
@ TwoToOne
See here for a short description.
@ StringHardSingleDiffractiveAX
See here for a short description.
@ StringSoftDoubleDiffractive
See here for a short description.
@ TwoToFive
See here for a short description.
@ StringSoftSingleDiffractiveXB
See here for a short description.
@ TwoToTwo
See here for a short description.
@ Elastic
See here for a short description.
@ TwoToFour
See here for a short description.
@ StringHardNonDiffractive
See here for a short description.
@ StringSoftAnnihilation
See here for a short description.
@ StringSoftNonDiffractive
See here for a short description.
@ StringSoftSingleDiffractiveAX
See here for a short description.
@ StringHardSingleDiffractiveXB
See here for a short description.
@ StringHardDoubleDiffractive
See here for a short description.
@ TwoToThree
See here for a short description.
static double high_energy_bpp(double plab)
Computes the B coefficients from the STAR fit, see fig.
std::string to_string(ThermodynamicQuantity quantity)
Convert a ThermodynamicQuantity enum value to its corresponding string.
Definition: stringify.cc:26
constexpr double nucleon_mass
Nucleon mass in GeV.
Definition: constants.h:69
constexpr T pow_int(const T base, unsigned const exponent)
Efficient template for calculating integer powers using squaring.
Definition: pow.h:23
constexpr double pion_mass
Pion mass in GeV.
Definition: constants.h:76
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
static constexpr int LScatterAction
static constexpr int LPythia
Definition: stringprocess.h:27
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.
static void boost_spin_vectors_after_elastic_scattering(ParticleData &outgoing_particle_a, ParticleData &outgoing_particle_b)
static double Cugnon_bpp(double plab)
Computes the B coefficients from the Cugnon parametrization of the angular distribution in elastic pp...
Constants related to transition between low and high collision energies.
const std::pair< double, double > sqrts_range_Npi
Transition range in N collisions.
const double pipi_offset
Constant offset as to where to turn on the strings and elastic processes for reactions (this is an e...
const double sqrts_add_lower
Constant for the lower end of transition region in the case of AQM this is added to the sum of masses...
const double KN_offset
Constant offset as to where to shift from 2to2 to string processes (in GeV) in the case of KN reactio...
const std::pair< double, double > sqrts_range_NN
Transition range in NN collisions.