Version: SMASH-3.1
decaymodes.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2014-2020,2022
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
10 #include "smash/decaymodes.h"
11 
12 #include <vector>
13 
14 #include "smash/clebschgordan.h"
15 #include "smash/constants.h"
16 #include "smash/inputfunctions.h"
17 #include "smash/isoparticletype.h"
18 #include "smash/logging.h"
19 #include "smash/stringfunctions.h"
20 
21 namespace smash {
22 static constexpr int LDecayModes = LogArea::DecayModes::id;
23 
25 std::vector<DecayModes> *DecayModes::all_decay_modes = nullptr;
27 std::vector<DecayTypePtr> *all_decay_types = nullptr;
28 
29 void DecayModes::add_mode(ParticleTypePtr mother, double ratio, int L,
30  ParticleTypePtrList particle_types) {
31  DecayType *type = get_decay_type(mother, particle_types, L);
32  // Check if mode already exists: if yes, add weight.
33  for (auto &mode : decay_modes_) {
34  if (type == &mode->type()) {
35  mode->set_weight(mode->weight() + ratio);
36  return;
37  }
38  }
39  // Add new mode.
40  decay_modes_.push_back(std::make_unique<DecayBranch>(*type, ratio));
41 }
42 
44  ParticleTypePtrList particle_types,
45  int L) {
46  assert(all_decay_types != nullptr);
47 
48  // check if the decay type already exisits
49  for (const auto &type : *all_decay_types) {
50  if (type->has_mother(mother) && type->has_particles(particle_types) &&
51  type->angular_momentum() == L) {
52  return type.get();
53  }
54  }
55 
56  // if the type does not exist yet, create a new one
57  switch (particle_types.size()) {
58  case 2:
59  if (is_dilepton(particle_types[0]->pdgcode(),
60  particle_types[1]->pdgcode())) {
61  all_decay_types->emplace_back(
62  std::make_unique<TwoBodyDecayDilepton>(particle_types, L));
63  } else if (particle_types[0]->is_stable() &&
64  particle_types[1]->is_stable()) {
65  all_decay_types->emplace_back(
66  std::make_unique<TwoBodyDecayStable>(particle_types, L));
67  } else if (particle_types[0]->is_stable() ||
68  particle_types[1]->is_stable()) {
69  all_decay_types->emplace_back(
70  std::make_unique<TwoBodyDecaySemistable>(particle_types, L));
71  } else {
72  all_decay_types->emplace_back(
73  std::make_unique<TwoBodyDecayUnstable>(particle_types, L));
74  }
75  break;
76  case 3:
77  if (has_lepton_pair(particle_types[0]->pdgcode(),
78  particle_types[1]->pdgcode(),
79  particle_types[2]->pdgcode())) {
80  all_decay_types->emplace_back(std::make_unique<ThreeBodyDecayDilepton>(
81  mother, particle_types, L));
82  } else {
83  all_decay_types->emplace_back(
84  std::make_unique<ThreeBodyDecay>(particle_types, L));
85  }
86  break;
87  default:
88  throw InvalidDecay(
89  "DecayModes::get_decay_type was instructed to add a decay mode "
90  "with " +
91  std::to_string(particle_types.size()) +
92  " particles. This is an invalid input.");
93  }
94 
95  return all_decay_types->back().get();
96 }
97 
98 bool DecayModes::renormalize(const std::string &name) {
99  double sum = 0.;
100  bool is_large_renormalization = false;
101  for (auto &mode : decay_modes_) {
102  sum += mode->weight();
103  }
104  if (std::abs(sum - 1.) < really_small) {
105  logg[LDecayModes].debug("Particle ", name,
106  ": Extremely small renormalization constant: ", sum,
107  "\n=> Skipping the renormalization.");
108  } else {
109  is_large_renormalization = (std::abs(sum - 1.) > 0.01);
110  logg[LDecayModes].debug("Particle ", name,
111  ": Renormalizing decay modes with ", sum);
112  double new_sum = 0.0;
113  for (auto &mode : decay_modes_) {
114  mode->set_weight(mode->weight() / sum);
115  new_sum += mode->weight();
116  }
117  logg[LDecayModes].debug("After renormalization sum of ratios is ", new_sum);
118  }
119  return is_large_renormalization;
120 }
121 
122 /* unnamed namespace; its content is accessible only in this file.
123  * This is identically the same as have a static function, but if one
124  * wanted to specify a file-local type, this would be the way to go. */
125 namespace {
132 inline std::size_t find_offset(PdgCode pdg) {
133  return std::addressof(ParticleType::find(pdg)) -
134  std::addressof(ParticleType::list_all()[0]);
135 }
136 } // unnamed namespace
137 
138 static int min_angular_momentum(int s0, int s1, int s2) {
139  int min_L = std::min(std::abs(s0 - s1 - s2), std::abs(s0 - s1 + s2));
140  min_L = std::min(min_L, std::abs(s0 + s1 - s2));
141  if (min_L % 2 != 0) {
142  throw std::runtime_error(
143  "min_angular_momentum: sum of spins should be integer");
144  }
145  return min_L / 2.;
146 }
147 
148 static int min_angular_momentum(int s0, int s1, int s2, int s3) {
149  int min_L =
150  std::min(std::abs(s0 - s1 + s2 + s3), std::abs(s0 + s1 - s2 + s3));
151  min_L = std::min(min_L, std::abs(s0 + s1 + s2 - s3));
152  min_L = std::min(min_L, std::abs(s0 - s1 - s2 + s3));
153  min_L = std::min(min_L, std::abs(s0 - s1 + s2 - s3));
154  min_L = std::min(min_L, std::abs(s0 + s1 - s2 - s3));
155  min_L = std::min(min_L, std::abs(s0 - s1 - s2 - s3));
156  if (min_L % 2 != 0) {
157  throw std::runtime_error(
158  "min_angular_momentum: sum of spins should be integer");
159  }
160  return min_L / 2.;
161 }
162 
163 void DecayModes::load_decaymodes(const std::string &input) {
164  // create the DecayType vector first, then it outlives the DecayModes vector,
165  // which references the DecayType objects.
166  static std::vector<DecayTypePtr> decaytypes;
167  decaytypes.clear(); // in case an exception was thrown and should try again
168  // ten decay types per decay mode should be a good guess.
169  decaytypes.reserve(10 * ParticleType::list_all().size());
170  all_decay_types = &decaytypes;
171 
172  static std::vector<DecayModes> decaymodes;
173  decaymodes.clear(); // in case an exception was thrown and should try again
174  decaymodes.resize(ParticleType::list_all().size());
175  all_decay_modes = &decaymodes;
176 
177  const IsoParticleType *isotype_mother = nullptr;
178  ParticleTypePtrList mother_states;
179  std::vector<DecayModes> decay_modes_to_add; // one for each mother state
180  int total_large_renormalized = 0;
181 
182  const auto end_of_decaymodes = [&]() {
183  if (isotype_mother == nullptr) { // at the start of the file
184  return;
185  }
186  // Loop over all states in the mother multiplet and add modes
187  for (size_t m = 0; m < mother_states.size(); m++) {
188  if (decay_modes_to_add[m].is_empty() && !mother_states[m]->is_stable()) {
189  throw MissingDecays("No decay modes found for particle " +
190  mother_states[m]->name());
191  }
192  bool is_large_renorm =
193  decay_modes_to_add[m].renormalize(mother_states[m]->name());
194  total_large_renormalized += is_large_renorm;
195  PdgCode pdgcode = mother_states[m]->pdgcode();
196  /* Add the list of decay modes for this particle type */
197  decaymodes[find_offset(pdgcode)] = std::move(decay_modes_to_add[m]);
198  }
199  if (isotype_mother->has_anti_multiplet()) {
200  /* Construct the decay modes for the anti-multiplet. */
201  logg[LDecayModes].debug("generating decay modes for anti-multiplet: " +
202  isotype_mother->name());
203  for (const auto &state : mother_states) {
204  PdgCode pdg = state->pdgcode();
205  PdgCode pdg_anti = pdg.get_antiparticle();
206  const ParticleType &type_anti = ParticleType::find(pdg_anti);
207  DecayModes &decay_modes_orig = decaymodes[find_offset(pdg)];
208  DecayModes &decay_modes_anti = decaymodes[find_offset(pdg_anti)];
209  for (const auto &mode : decay_modes_orig.decay_mode_list()) {
210  ParticleTypePtrList list = mode->particle_types();
211  for (auto &type : list) {
212  if (type->has_antiparticle()) {
213  type = type->get_antiparticle();
214  }
215  }
216  decay_modes_anti.add_mode(&type_anti, mode->weight(),
217  mode->angular_momentum(), list);
218  }
219  }
220  }
221  };
222 
223  // Track the line number for better error messages.
224  // FIXME: At the moment this does not include comments and empty lines.
225  uint64_t linenumber = 1;
226  for (const Line &line : line_parser(input)) {
227  const auto trimmed = trim(line.text);
228  assert(!trimmed.empty()); // trim(line.text) is never empty,
229  // else line_parser is broken
230  if (trimmed.find_first_of(" \t") == std::string::npos) {
231  // a single record on one line signifies a new decay mode section
232  end_of_decaymodes();
233  std::string name = trim(line.text);
234  isotype_mother = &IsoParticleType::find(name);
235  mother_states = isotype_mother->get_states();
236  decay_modes_to_add.clear();
237  decay_modes_to_add.resize(mother_states.size());
238  logg[LDecayModes].debug("reading decay modes for " + name);
239  // check if any of the states have decay modes already
240  for (size_t m = 0; m < mother_states.size(); m++) {
241  PdgCode pdgcode = mother_states[m]->pdgcode();
242  if (!decaymodes[find_offset(pdgcode)].is_empty()) {
243  throw LoadFailure("Duplicate entry for " + name +
244  " in decaymodes.txt:" + std::to_string(linenumber));
245  }
246  }
247  } else {
248  std::istringstream lineinput(line.text);
249  std::vector<std::string> decay_particles;
250  decay_particles.reserve(3);
251  double ratio;
252  lineinput >> ratio;
253 
254  int L;
255  lineinput >> L;
256  if (L < 0) {
257  throw LoadFailure("Invalid angular momentum '" + std::to_string(L) +
258  "' in decaymodes.txt:" + std::to_string(line.number) +
259  ": '" + line.text + "'");
260  }
261 
262  std::string name;
263  lineinput >> name;
264  bool multi = true;
265  while (lineinput) {
266  decay_particles.emplace_back(name);
267  const auto isotype = IsoParticleType::try_find(name);
268  const bool is_multiplet = isotype;
269  const bool is_state = ParticleType::exists(name);
270  if (!is_multiplet && !is_state) {
271  throw InvalidDecay(
272  "Daughter " + name +
273  " is neither an isospin multiplet nor a particle." + " (line " +
274  std::to_string(linenumber) + ": \"" + trimmed + "\")");
275  }
276  const bool is_hadronic_multiplet =
277  is_multiplet && isotype->get_states()[0]->is_hadron();
278  multi &= is_hadronic_multiplet;
279  lineinput >> name;
280  }
281  Parity parity;
282  const int s0 = isotype_mother->spin();
283  int min_L = 0;
284  int max_L = 0;
285  if (multi) {
286  /* References to isospin multiplets: Automatically determine all valid
287  * combinations and calculate Clebsch-Gordan factors */
288  switch (decay_particles.size()) {
289  case 2: {
290  const IsoParticleType &isotype_daughter_1 =
291  IsoParticleType::find(decay_particles[0]);
292  const IsoParticleType &isotype_daughter_2 =
293  IsoParticleType::find(decay_particles[1]);
294  parity = isotype_daughter_1.parity() * isotype_daughter_2.parity();
295  const int s1 = isotype_daughter_1.spin();
296  const int s2 = isotype_daughter_2.spin();
297  min_L = min_angular_momentum(s0, s1, s2);
298  max_L = (s0 + s1 + s2) / 2;
299  // loop through multiplets
300  bool forbidden_by_isospin = true;
301  for (size_t m = 0; m < mother_states.size(); m++) {
302  for (const auto &daughter1 : isotype_daughter_1.get_states()) {
303  for (const auto &daughter2 : isotype_daughter_2.get_states()) {
304  // calculate Clebsch-Gordan factor
305  const double cg_sqr = isospin_clebsch_gordan_sqr_2to1(
306  *daughter1, *daughter2, *mother_states[m]);
307  if (cg_sqr > 0.) {
308  // add mode
309  logg[LDecayModes].debug(
310  "decay mode generated: " + mother_states[m]->name() +
311  " -> " + daughter1->name() + " " + daughter2->name() +
312  " (" + std::to_string(ratio * cg_sqr) + ")");
313  decay_modes_to_add[m].add_mode(mother_states[m],
314  ratio * cg_sqr, L,
315  {daughter1, daughter2});
316  forbidden_by_isospin = false;
317  }
318  }
319  }
320  }
321  if (forbidden_by_isospin) {
322  std::stringstream s;
323  s << ",\nwhere isospin mother: " << isotype_mother->isospin()
324  << ", daughters: " << isotype_daughter_1.isospin() << " "
325  << isotype_daughter_2.isospin();
326  throw InvalidDecay(isotype_mother->name() +
327  " decay mode is forbidden by isospin: \"" +
328  line.text + "\"" + s.str());
329  }
330  break;
331  }
332  case 3: {
333  const IsoParticleType &isotype_daughter_1 =
334  IsoParticleType::find(decay_particles[0]);
335  const IsoParticleType &isotype_daughter_2 =
336  IsoParticleType::find(decay_particles[1]);
337  const IsoParticleType &isotype_daughter_3 =
338  IsoParticleType::find(decay_particles[2]);
339  parity = isotype_daughter_1.parity() * isotype_daughter_2.parity() *
340  isotype_daughter_3.parity();
341  const int s1 = isotype_daughter_1.spin();
342  const int s2 = isotype_daughter_2.spin();
343  const int s3 = isotype_daughter_2.spin();
344  min_L = min_angular_momentum(s0, s1, s2, s3);
345  max_L = (s0 + s1 + s2 + s3) / 2;
346  // loop through multiplets
347  for (size_t m = 0; m < mother_states.size(); m++) {
348  for (const auto &daughter1 : isotype_daughter_1.get_states()) {
349  for (const auto &daughter2 : isotype_daughter_2.get_states()) {
350  for (const auto &daughter3 :
351  isotype_daughter_3.get_states()) {
352  const double cg_sqr = isospin_clebsch_gordan_sqr_3to1(
353  *daughter1, *daughter2, *daughter3, *mother_states[m]);
354  if (cg_sqr > 0.) {
355  // add mode
356  logg[LDecayModes].debug(
357  "decay mode generated: " + mother_states[m]->name() +
358  " -> " + daughter1->name() + " " + daughter2->name() +
359  " " + daughter3->name() + " (" +
360  std::to_string(ratio * cg_sqr) + ")");
361  decay_modes_to_add[m].add_mode(
362  mother_states[m], ratio * cg_sqr, L,
363  {daughter1, daughter2, daughter3});
364  }
365  }
366  }
367  }
368  }
369  break;
370  }
371  default:
372  throw std::runtime_error(
373  "References to isospin multiplets only "
374  "allowed in two-body or three-body decays: " +
375  line.text + " (line " + std::to_string(linenumber) + ": \"" +
376  trimmed + "\")");
377  }
378  } else {
379  /* References to specific states, not multiplets:
380  * Loop over all mother states and check charge conservation. */
381  ParticleTypePtrList types;
382  int charge = 0;
383  parity = Parity::Pos;
384  for (auto part : decay_particles) {
385  try {
386  types.push_back(IsoParticleType::find_state(part));
387  } catch (std::runtime_error &e) {
388  throw std::runtime_error(std::string() + e.what() + " (line " +
389  std::to_string(linenumber) + ": \"" +
390  trimmed + "\")");
391  }
392  charge += types.back()->charge();
393  parity *= types.back()->parity();
394  }
395  if (types.size() == 2) {
396  const int s1 = types[0]->spin();
397  const int s2 = types[1]->spin();
398  min_L = min_angular_momentum(s0, s1, s2);
399  max_L = (s0 + s1 + s2) / 2;
400  } else if (types.size() == 3) {
401  const int s1 = types[0]->spin();
402  const int s2 = types[1]->spin();
403  const int s3 = types[2]->spin();
404  min_L = min_angular_momentum(s0, s1, s2, s3);
405  max_L = (s0 + s1 + s2 + s3) / 2;
406  } else {
407  throw InvalidDecay(isotype_mother->name() +
408  " decay mode has an invalid number of particles"
409  " in the final state " +
410  "(line " + std::to_string(linenumber) + ": \"" +
411  trimmed + "\")");
412  }
413  bool no_decays = true;
414  for (size_t m = 0; m < mother_states.size(); m++) {
415  if (mother_states[m]->charge() == charge) {
416  logg[LDecayModes].debug(
417  "decay mode found: ", mother_states[m]->name(), " -> ",
418  decay_particles.size());
419  decay_modes_to_add[m].add_mode(mother_states[m], ratio, L, types);
420  no_decays = false;
421  }
422  }
423  if (no_decays) {
424  throw InvalidDecay(isotype_mother->name() +
425  " decay mode violates charge conservation " +
426  "(line " + std::to_string(linenumber) + ": \"" +
427  trimmed + "\")");
428  }
429  }
430  // Take angular momentum into account.
431  // FIXME: At the moment this is not correct for 3-body decays (see #517),
432  // therefore only check paritiy for 2-body decays below.
433  if (L % 2 == 1) {
434  parity = -parity;
435  }
436  // Make sure the decay has the correct parity for 2-body decays
437  if (decay_particles.size() == 2 && parity != mother_states[0]->parity()) {
438  throw InvalidDecay(mother_states[0]->name() +
439  " decay mode violates parity conservation " +
440  "(line " + std::to_string(linenumber) + ": \"" +
441  trimmed + "\")");
442  }
443  // Make sure the decay has a correct angular momentum.
444  if (L < min_L || L > max_L) {
445  throw InvalidDecay(
446  mother_states[0]->name() +
447  " decay mode violates angular momentum conservation: " +
448  std::to_string(L) + " not in [" + std::to_string(min_L) + ", " +
449  std::to_string(max_L) + "] (line " + std::to_string(linenumber) +
450  ": \"" + trimmed + "\")");
451  }
452  }
453  linenumber++;
454  }
455  end_of_decaymodes();
456 
457  // Check whether the mother's pole mass is strictly larger than the minimal
458  // masses of the daughters. This is required by the Manley-Saleski ansatz.
459  const auto &particles = ParticleType::list_all();
460  for (const auto &mother : particles) {
461  if (mother.is_stable()) {
462  continue;
463  }
464  const auto &decays = mother.decay_modes().decay_mode_list();
465  for (const auto &decay : decays) {
466  if (mother.mass() <= decay->threshold()) {
467  std::stringstream s;
468  s << mother.name() << " → ";
469  for (const auto &p : decay->particle_types()) {
470  s << p->name();
471  }
472  s << " with " << mother.mass() << " ≤ " << decay->threshold();
473  throw InvalidDecay(
474  "For all decays, the minimum mass of daughters"
475  "must be smaller\nthan the mother's pole mass "
476  "(Manley-Saleski Ansatz)\n"
477  "Violated by the following decay: " +
478  s.str());
479  }
480  }
481  }
482  if (total_large_renormalized > 0) {
483  logg[LDecayModes].warn(
484  "Branching ratios of ", total_large_renormalized,
485  " hadrons were renormalized by more than 1% to have sum 1.");
486  }
487 }
488 
489 } // namespace smash
The DecayModes class is used to store and update information about decay branches (i....
Definition: decaymodes.h:29
const DecayBranchList & decay_mode_list() const
Definition: decaymodes.h:63
bool is_empty() const
Definition: decaymodes.h:60
void add_mode(ParticleTypePtr mother, double ratio, int L, ParticleTypePtrList particle_types)
Add a decay mode using all necessary information.
Definition: decaymodes.cc:29
bool renormalize(const std::string &name)
Renormalize the branching ratios to add up to 1.
Definition: decaymodes.cc:98
static std::vector< DecayModes > * all_decay_modes
A list of all DecayModes objects using the same indexing as all_particle_types.
Definition: decaymodes.h:132
DecayBranchList decay_modes_
Vector of decay modes.
Definition: decaymodes.h:123
static DecayType * get_decay_type(ParticleTypePtr mother, ParticleTypePtrList particle_types, int L)
Retrieve a decay type.
Definition: decaymodes.cc:43
static void load_decaymodes(const std::string &input)
Loads the DecayModes map as described in the input string.
Definition: decaymodes.cc:163
DecayType is the abstract base class for all decay types.
Definition: decaytype.h:23
IsoParticleType is a class to represent isospin multiplets.
static const IsoParticleType * try_find(const std::string &name)
Returns the IsoParticleType pointer for the given name.
static const IsoParticleType & find(const std::string &name)
Returns the IsoParticleType object for the given name.
int isospin() const
Returns twice the total isospin of the multiplet.
bool has_anti_multiplet() const
Check if there is a multiplet of antiparticles, which is different from the original multiplet.
const std::string & name() const
Returns the name of the multiplet.
unsigned int spin() const
Returns twice the spin of the multiplet.
ParticleTypePtrList get_states() const
Returns list of states that form part of the multiplet.
static const ParticleTypePtr find_state(const std::string &name)
Returns the ParticleType object for the given name, by first finding the correct multiplet and then l...
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
static const ParticleType & find(PdgCode pdgcode)
Returns the ParticleType object for the given pdgcode.
Definition: particletype.cc:99
static bool exists(PdgCode pdgcode)
static const ParticleTypeList & list_all()
Definition: particletype.cc:51
PdgCode stores a Particle Data Group Particle Numbering Scheme particle type number.
Definition: pdgcode.h:111
PdgCode get_antiparticle() const
Construct the antiparticle to a given PDG code.
Definition: pdgcode.h:332
Collection of useful constants that are known at compile time.
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > logg
An array that stores all pre-configured Logger objects.
Definition: logging.cc:39
std::size_t find_offset(PdgCode pdg)
Passes back the address offset of a particletype in the list of all particles.
Definition: decaymodes.cc:132
constexpr int p
Proton.
Definition: action.h:24
bool is_dilepton(const PdgCode pdg1, const PdgCode pdg2)
Definition: pdgcode.h:1117
static int min_angular_momentum(int s0, int s1, int s2)
Definition: decaymodes.cc:138
double isospin_clebsch_gordan_sqr_3to1(const ParticleType &p_a, const ParticleType &p_b, const ParticleType &p_c, const ParticleType &Res)
Calculate the squared isospin Clebsch-Gordan coefficient for three particles p_a, p_b and p_c couplin...
build_vector_< Line > line_parser(const std::string &input)
Helper function for parsing particles.txt and decaymodes.txt.
std::string trim(const std::string &s)
Strip leading and trailing whitespaces.
Parity
Represent the parity of a particle type.
Definition: particletype.h:25
@ Pos
Positive parity.
bool has_lepton_pair(const PdgCode pdg1, const PdgCode pdg2, const PdgCode pdg3)
Definition: pdgcode.h:1129
std::vector< DecayTypePtr > * all_decay_types
Global pointer to the decay types list.
Definition: decaymodes.cc:27
constexpr double really_small
Numerical error tolerance.
Definition: constants.h:37
double isospin_clebsch_gordan_sqr_2to1(const ParticleType &p_a, const ParticleType &p_b, const ParticleType &Res)
Calculate the squared isospin Clebsch-Gordan coefficient for two particles p_a and p_b coupling to a ...
Definition: clebschgordan.h:26
static constexpr int LDecayModes
Definition: decayaction.cc:17
Line consists of a line number and the contents of that line.