Version: SMASH-3.4
stringprocess.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2017-2020,2022,2024-2026
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
10 #include "smash/stringprocess.h"
11 
12 #include <array>
13 #include <cmath>
14 #include <cstdlib>
15 #include <limits>
16 #include <string>
17 
18 #include "smash/configuration.h"
19 #include "smash/constants.h"
21 #include "smash/input_keys.h"
22 #include "smash/kinematics.h"
23 #include "smash/pow.h"
24 #include "smash/processbranch.h"
25 #include "smash/random.h"
26 
27 namespace smash {
28 
30  : pmin_gluon_lightcone_(
31  config.take(InputKeys::collTerm_stringParam_gluonPMin)),
32  pow_fgluon_beta_(config.take(InputKeys::collTerm_stringParam_gluonBeta)),
33  pow_fquark_alpha_(
34  config.take(InputKeys::collTerm_stringParam_quarkAlpha)),
35  pow_fquark_beta_(config.take(InputKeys::collTerm_stringParam_quarkBeta)),
36  sigma_qperp_(config.take(InputKeys::collTerm_stringParam_sigmaPerp)),
37  stringz_a_leading_(
38  config.take(InputKeys::collTerm_stringParam_stringZALeading)),
39  stringz_b_leading_(
40  config.take(InputKeys::collTerm_stringParam_stringZBLeading)),
41  stringz_a_produce_(config.take(InputKeys::collTerm_stringParam_stringZA)),
42  stringz_b_produce_(config.take(InputKeys::collTerm_stringParam_stringZB)),
43  strange_supp_(
44  config.take(InputKeys::collTerm_stringParam_strangeSuppression)),
45  diquark_supp_(
46  config.take(InputKeys::collTerm_stringParam_diquarkSuppression)),
47  popcorn_rate_(config.take(InputKeys::collTerm_stringParam_popcornRate)),
48  damp_popcorn_(config.take(InputKeys::collTerm_stringParam_dampPopcorn)),
49  string_sigma_T_(
50  config.take(InputKeys::collTerm_stringParam_stringSigmaT)),
51  kappa_tension_string_(
52  config.take(InputKeys::collTerm_stringParam_stringTension)),
53  time_formation_const_(
54  config.take(InputKeys::collTerm_stringParam_formationTime)),
55  soft_t_form_(config.take(InputKeys::collTerm_stringParam_formTimeFactor)),
56  mass_dependent_formation_times_(config.take(
57  InputKeys::collTerm_stringParam_mDependentFormationTimes)),
58  prob_proton_to_d_uu_(
59  config.take(InputKeys::collTerm_stringParam_probabilityPToDUU)),
60  separate_fragment_baryon_(
61  config.take(InputKeys::collTerm_stringParam_separateFragmentBaryon)),
62  use_monash_tune_(
63  config.take(InputKeys::collTerm_stringParam_useMonashTune)),
64  additional_xsec_supp_(
65  config.take(InputKeys::collTerm_stringParam_unformedXsecSuppression)),
66  pythia_settings_(
67  config.take(InputKeys::collTerm_stringParam_pythiaSettings)) {
68  // setup and initialize pythia for fragmentation
69  pythia_hadron_ = std::make_unique<Pythia8::Pythia>(PYTHIA_XML_DIR, false);
70  /* turn off all parton-level processes to implement only hadronization */
71  pythia_hadron_->readString("ProcessLevel:all = off");
75  pythia_hadron_->init();
76  /*
77  * The const_cast<type>() function is used to obtain the reference of the
78  * PrivateInfo object in the pythia_hadron_.
79  * This cast is needed since Pythia 8.302 which included a major architecture
80  * change. The Info object of a Pythia object is now private, only a const
81  * reference can be obtained.
82  * In order to reference the PrivateInfo object during initialization, we
83  * cast the const reference to obtain the stored address.
84  */
85  pythia_sigmatot_.initInfoPtr(
86  const_cast<Pythia8::Info&>(pythia_hadron_->info));
87  pythia_sigmatot_.init();
88 
89  pythia_stringflav_.initInfoPtr(
90  const_cast<Pythia8::Info&>(pythia_hadron_->info));
91  pythia_stringflav_.init();
92 
93  event_intermediate_.init("intermediate partons",
94  &pythia_hadron_->particleData);
95 
96  for (int imu = 0; imu < 3; imu++) {
97  evecBasisAB_[imu] = ThreeVector(0., 0., 0.);
98  }
99 
100  final_state_.clear();
101 }
102 void StringProcess::common_setup_pythia(Pythia8::Pythia* pythia_in,
103  double strange_supp,
104  double diquark_supp,
105  double popcorn_rate, double stringz_a,
106  double stringz_b,
107  double string_sigma_T) {
108  // choose parametrization for mass-dependent width
109  pythia_in->readString("ParticleData:modeBreitWigner = 4");
110 
111  // Global Lund fragmentation
112  pythia_in->readString("StringZ:aLund = " + std::to_string(stringz_a));
113  pythia_in->readString("StringZ:bLund = " + std::to_string(stringz_b));
114  pythia_in->readString("BeamRemnants:dampPopcorn = " +
117  pythia_in->readString("BeamRemnants:hardRemnantBaryon = on");
118  pythia_in->readString("BeamRemnants:aRemnantBaryon = " +
120  pythia_in->readString("BeamRemnants:bRemnantBaryon = " +
122  }
123  // transverse momentum spread in string fragmentation
124  pythia_in->readString("StringPT:sigma = " + std::to_string(string_sigma_T));
125  // diquark suppression factor in string fragmentation
126  pythia_in->readString("StringFlav:probQQtoQ = " +
127  std::to_string(diquark_supp));
128  // strangeness suppression factor in string fragmentation
129  pythia_in->readString("StringFlav:probStoUD = " +
130  std::to_string(strange_supp));
131  pythia_in->readString("StringFlav:popcornRate = " +
132  std::to_string(popcorn_rate));
133 
134  // manually set the parton distribution function
135  pythia_in->readString("PDF:pSet = 13");
136  pythia_in->readString("PDF:pSetB = 13");
137  pythia_in->readString("PDF:piSet = 1");
138  pythia_in->readString("PDF:piSetB = 1");
139  pythia_in->readString("Beams:idA = 2212");
140  pythia_in->readString("Beams:idB = 2212");
141  pythia_in->readString("Beams:eCM = 10.");
142 
143  // set PYTHIA random seed from outside
144  pythia_in->readString("Random:setSeed = on");
145  // suppress unnecessary output
146  pythia_in->readString("Print:quiet = on");
147  // No resonance decays, since the resonances will be handled by SMASH
148  pythia_in->readString("HadronLevel:Decay = off");
149  // set particle masses and widths in PYTHIA to be same with those in SMASH
150  for (auto& ptype : ParticleType::list_all()) {
151  int pdgid = ptype.pdgcode().get_decimal();
152  double mass_pole = ptype.mass();
153  double width_pole = ptype.width_at_pole();
154  // check if the particle species is in PYTHIA
155  if (pythia_in->particleData.isParticle(pdgid)) {
156  // set mass and width in PYTHIA
157  pythia_in->particleData.m0(pdgid, mass_pole);
158  pythia_in->particleData.mWidth(pdgid, width_pole);
159  } else if (pdgid == 310 || pdgid == 130) {
160  // set mass and width of Kaon-L and Kaon-S
161  pythia_in->particleData.m0(pdgid, kaon_mass);
162  pythia_in->particleData.mWidth(pdgid, 0.);
163  }
164  }
165 
166  // make energy-momentum conservation in PYTHIA more precise
167  pythia_in->readString("Check:epTolErr = 1e-6");
168  pythia_in->readString("Check:epTolWarn = 1e-8");
169 
170  if (use_monash_tune_) {
171  pythia_in->readString("Tune:ee = 7");
172  pythia_in->readString("Tune:pp = 14");
173  }
174 
175  for (const auto& setting : pythia_settings_) {
176  if (!pythia_in->readString(setting)) {
177  throw std::runtime_error(
178  "Failed to apply Pythia setting \"" + setting +
179  "\". Please check that it is a valid Pythia 8 setting.");
180  }
181  }
182 }
183 
185  ParticleList& intermediate_particles, const FourVector& pString,
186  const ThreeVector& evecLong, double additional_xsec_supp,
187  bool find_and_scale_leading) {
188  assert(intermediate_particles.size() > 0);
189 
190  int bstring = 0;
191  for (const ParticleData& data : intermediate_particles) {
192  bstring += data.pdgcode().baryon_number();
193  }
194 
195  const ThreeVector vstring = pString.velocity();
196  if (find_and_scale_leading) {
197  assign_all_scaling_factors(bstring, intermediate_particles, evecLong,
198  additional_xsec_supp);
199  }
200 
201  for (ParticleData& particle : intermediate_particles) {
202  const FourVector mom_in_string_restframe = particle.momentum();
203  const ThreeVector velocity_in_string_restframe =
204  mom_in_string_restframe.velocity();
205  const double gamma_string = 1.0 / particle.inverse_gamma();
206 
207  const FourVector p_com = mom_in_string_restframe.lorentz_boost(-vstring);
208  particle.set_4momentum(p_com);
209  const double tau_prod =
211  ? M_SQRT2 * particle.effective_mass() / kappa_tension_string_
213 
214  const double t_prod_string = tau_prod * gamma_string;
215 
216  FourVector fragment_position(t_prod_string,
217  t_prod_string * velocity_in_string_restframe);
218 
219  fragment_position = fragment_position.lorentz_boost(-vstring);
220 
221  particle.set_slow_formation_times(
223  soft_t_form_ * fragment_position.x0() + time_collision_);
224  }
225 }
226 void StringProcess::init(const ParticleList& incoming, double tcoll) {
227  PDGcodes_[0] = incoming[0].pdgcode();
228  PDGcodes_[1] = incoming[1].pdgcode();
229  massA_ = incoming[0].effective_mass();
230  massB_ = incoming[1].effective_mass();
231 
232  plab_[0] = incoming[0].momentum();
233  plab_[1] = incoming[1].momentum();
234 
235  sqrtsAB_ = (plab_[0] + plab_[1]).abs();
236  ucomAB_ = (plab_[0] + plab_[1]) / sqrtsAB_;
238 
239  const Pythia8::Vec4 pA_lab = make_pythia_4vec(plab_[0]);
240  const Pythia8::Vec4 pB_lab = make_pythia_4vec(plab_[1]);
241 
242  to_cm_.reset();
243  to_cm_.toCMframe(pA_lab, pB_lab);
244 
245  const Pythia8::Vec4 pA_cm = to_cm_ * pA_lab;
246  const Pythia8::Vec4 pB_cm = to_cm_ * pB_lab;
247 
248  pcom_[0] = make_smash_4vec(pA_cm);
249  pcom_[1] = make_smash_4vec(pB_cm);
250  ThreeVector evec_polar(pA_cm.px(), pA_cm.py(), pA_cm.pz());
251  evec_polar /= std::sqrt(evec_polar.sqr());
252 
255 
256  time_collision_ = tcoll;
257 }
258 
260  string_parton_events_.clear();
261  final_state_.clear();
262 
263  bool parton_level_success = false;
264 
265  switch (type) {
267  parton_level_success = next_NDiffSoft();
268  break;
270  parton_level_success = next_SDiff(true);
271  break;
273  parton_level_success = next_SDiff(false);
274  break;
276  parton_level_success = next_DDiff();
277  break;
279  parton_level_success = next_BBbarAnn();
280  break;
285  parton_level_success = next_Hard(type);
286  break;
287  default:
288  logg[LPythia].error("Unknown string process required.");
289  return false;
290  }
291 
292  if (!parton_level_success) {
293  return false;
294  }
295 
296  for (const Pythia8::Event& string_event : string_parton_events_) {
297  auto hadrons = hadronize(string_event);
298 
299  if (!hadrons) {
300  final_state_.clear();
301  return false;
302  }
303 
304  final_state_.insert(final_state_.end(), hadrons->begin(), hadrons->end());
305  }
306 
307  return true;
308 }
309 
310 std::optional<ParticleList> StringProcess::hadronize(
311  const Pythia8::Event& string_evt) {
312  ParticleList intermediate_particles;
313  // usually 0 is "system", so need at least indices 1 and 2
314  if (string_evt.size() < 3) {
315  logg[LPythia].error("String event too small to hadronize.");
316  return std::nullopt;
317  }
318 
319  bool has_leading_parton = false;
320 
321  const bool has_junction = string_evt.sizeJunction() > 0;
322 
323  for (const Pythia8::Particle& particle : string_evt) {
324  if (is_leading_parton(particle)) {
325  has_leading_parton = true;
326  break;
327  }
328  }
329 
330  const bool has_leading_and_is_open = has_leading_parton && !has_junction;
331 
332  pythia_hadron_->event.reset();
333  pythia_hadron_->event[0].p(string_evt[0].p());
334  pythia_hadron_->event[0].m(string_evt[0].p().mCalc());
335  for (int i = 1; i < string_evt.size(); ++i) {
336  pythia_hadron_->event.append(string_evt[i]);
337  }
338 
339  pythia_hadron_->event.clearJunctions();
340  for (int i = 0; i < string_evt.sizeJunction(); ++i) {
341  pythia_hadron_->event.appendJunction(
342  string_evt.kindJunction(i), string_evt.colJunction(i, 0),
343  string_evt.colJunction(i, 1), string_evt.colJunction(i, 2));
344  }
345 
346  const Pythia8::Vec4 p_str = string_evt[0].p();
347 
348  Pythia8::RotBstMatrix to_string_rest;
349  to_string_rest.bstback(p_str);
350 
351  pythia_hadron_->event.rotbst(to_string_rest);
352 
353  if (!pythia_hadron_->forceHadronLevel(false)) {
354  pythia_hadron_->event.list();
355  logg[LPythia].error("Pythia fragmentation failed for one string.");
356  return std::nullopt;
357  }
358 
359  if (has_leading_and_is_open) {
361  }
362 
363  const FourVector pString_smash(
364  p_str.e(), ThreeVector(p_str.px(), p_str.py(), p_str.pz()));
365 
366  const double m2 = pString_smash.sqr();
367 
368  if (m2 <= 0.0) {
369  logg[LPythia].error("String has non-positive invariant mass.");
370  return std::nullopt;
371  }
372 
373  // evecLong: unit vector along string momentum
374  // fallback if |p| ~ 0
375  ThreeVector evecLong(p_str.px(), p_str.py(), p_str.pz());
376 
377  const double pabs = evecLong.abs();
378 
379  if (pabs > 1e-12) {
380  evecLong = evecLong / pabs;
381  } else {
382  evecLong = ThreeVector(0.0, 0.0, 1.0);
383  }
384 
385  // Build intermediate SMASH particles from final hadrons
386  intermediate_particles.reserve(pythia_hadron_->event.size());
387  for (int i = 0; i < pythia_hadron_->event.size(); ++i) {
388  const auto& particle = pythia_hadron_->event[i];
389 
390  if (!(particle.isFinal() && particle.isHadron())) {
391  continue;
392  }
393 
394  const FourVector mom_smash = make_smash_4vec(particle.p());
395 
396  int particle_id = particle.id();
397  convert_KaonLS(particle_id);
398 
399  if (!append_intermediate_list(particle_id, mom_smash,
400  intermediate_particles)) {
401  logg[LPythia].error("Unknown hadron in SMASH during hadronization: PDG=",
402  particle.id());
403 
404  return std::nullopt;
405  }
406 
407  if (is_leading_from_diquark(particle)) {
408  intermediate_particles.back().set_cross_section_scaling_factor(
409  2.0 / 3.0 * additional_xsec_supp_);
410 
411  } else if (is_leading_from_quark(particle)) {
412  intermediate_particles.back().set_cross_section_scaling_factor(
413  (pythia_hadron_->particleData.isBaryon(particle.id()) ? 1.0 / 3.0
414  : 0.5) *
416  } else {
417  intermediate_particles.back().set_cross_section_scaling_factor(0.0);
418  }
419  }
420 
421  if (intermediate_particles.empty()) {
422  logg[LPythia].error("Hadronization produced no final hadrons.");
423  return std::nullopt;
424  }
425 
426  const bool should_assign_scaling = has_junction && has_leading_parton;
427 
428  form_intermediate_particles(intermediate_particles, pString_smash, evecLong,
429  additional_xsec_supp_, should_assign_scaling);
430 
431  return intermediate_particles;
432 }
433 bool StringProcess::append_string(const Pythia8::Vec4& p_str,
434  const std::array<int, 2>& ends, int color_tag,
435  bool use_projectile_axis,
436  bool random_flip_of_endpoints) {
437  const bool flip_endpoints =
438  random_flip_of_endpoints && random::canonical() > 0.5;
439 
440  const int id1 = flip_endpoints ? ends[1] : ends[0];
441  const int id2 = flip_endpoints ? ends[0] : ends[1];
442  const auto& pd = pythia_hadron_->particleData;
443 
444  const double m_string = p_str.mCalc();
445 
446  if (m_string < estimate_string_threshold(id1, id2))
447  return false;
448 
449  const double m1 = pd.m0(id1);
450  const double m2 = pd.m0(id2);
451 
452  const double p_cm = pCM(m_string, m1, m2);
453  if (p_cm < really_small)
454  return false;
455 
456  const double E1 = std::sqrt(p_cm * p_cm + m1 * m1);
457  const double E2 = std::sqrt(p_cm * p_cm + m2 * m2);
458 
459  const int ibeam = use_projectile_axis ? 0 : 1;
460 
461  const ThreeVector mom = pcom_[ibeam].threevec();
462  Pythia8::Vec4 beam_axis_cm(mom.x1(), mom.x2(), mom.x3(), mom.abs());
463 
464  Pythia8::RotBstMatrix to_string_rest;
465  to_string_rest.bstback(p_str);
466 
467  Pythia8::Vec4 beam_axis_rest = to_string_rest * beam_axis_cm;
468 
469  const double norm = std::sqrt(beam_axis_rest.px() * beam_axis_rest.px() +
470  beam_axis_rest.py() * beam_axis_rest.py() +
471  beam_axis_rest.pz() * beam_axis_rest.pz());
472 
473  if (norm < really_small)
474  return false;
475 
476  const double nx = beam_axis_rest.px() / norm;
477  const double ny = beam_axis_rest.py() / norm;
478  const double nz = beam_axis_rest.pz() / norm;
479 
480  const Pythia8::Vec4 p1_rest(nx * p_cm, ny * p_cm, nz * p_cm, E1);
481  const Pythia8::Vec4 p2_rest(-nx * p_cm, -ny * p_cm, -nz * p_cm, E2);
482 
483  Pythia8::RotBstMatrix boost;
484  boost.bst(p_str);
485 
486  const Pythia8::Vec4 p1_cm = boost * p1_rest;
487  const Pythia8::Vec4 p2_cm = boost * p2_rest;
488 
489  // Create event for this string.
490  string_parton_events_.emplace_back();
491  Pythia8::Event& evt = string_parton_events_.back();
492  evt.init("Soft SMASH string", &pythia_hadron_->particleData);
493 
494  evt.append(90, -11, 0, 0, p1_cm + p2_cm, (p1_cm + p2_cm).mCalc());
495 
496  const int status_quark = static_cast<int>(LeadingStatus::LeadingQuark);
497  const int status_diquark = static_cast<int>(LeadingStatus::LeadingDiquark);
498 
499  const bool id1_is_quark = pythia_hadron_->particleData.isQuark(id1);
500  const bool id2_is_quark = pythia_hadron_->particleData.isQuark(id2);
501 
502  const int i1 = evt.append(id1, id1_is_quark ? status_quark : status_diquark,
503  0, 0, p1_cm, m1);
504  const int i2 = evt.append(id2, id2_is_quark ? status_quark : status_diquark,
505  0, 0, p2_cm, m2);
506 
507  set_color_by_type(evt[i1], color_tag);
508  set_color_by_type(evt[i2], color_tag);
509  evt.rotbst(to_cm_.inverse());
510  return true;
511 }
512 
513 bool StringProcess::next_SDiff(bool is_AB_to_AX) {
514  double massH = is_AB_to_AX ? massA_ : massB_;
515  double mstrMin = is_AB_to_AX ? massB_ : massA_;
516  double mstrMax = sqrtsAB_ - massH;
517 
518  int idqX1, idqX2;
519  double QTrn, QTrx, QTry;
520  double pabscomHX_sqr, massX;
521 
522  // decompose hadron into quarks
523  make_string_ends(is_AB_to_AX ? PDGcodes_[1] : PDGcodes_[0], idqX1, idqX2,
525  // string mass must be larger than threshold set by PYTHIA.
526  mstrMin = pythia_hadron_->particleData.m0(idqX1) +
527  pythia_hadron_->particleData.m0(idqX2);
528  // this threshold cannot be larger than maximum of allowed string mass.
529  if (mstrMin > mstrMax) {
530  return false;
531  }
532  // sample the transverse momentum transfer
533  QTrx = random::normal(0., sigma_qperp_ * M_SQRT1_2);
534  QTry = random::normal(0., sigma_qperp_ * M_SQRT1_2);
535  QTrn = std::sqrt(QTrx * QTrx + QTry * QTry);
536  /* sample the string mass and
537  * evaluate the three-momenta of hadron and string. */
538  massX = random::power(-1.0, mstrMin, mstrMax);
539  pabscomHX_sqr = pCM_sqr(sqrtsAB_, massH, massX);
540  /* magnitude of the three momentum must be larger
541  * than the transverse momentum. */
542  const bool foundPabsX = pabscomHX_sqr > QTrn * QTrn;
543 
544  if (!foundPabsX) {
545  return false;
546  }
547 
548  // determine three momentum of the final state hadron
549  double sign_direction = is_AB_to_AX ? 1. : -1.;
550 
551  // longitudinal component (along +z by convention)
552  const double pL = std::sqrt(pabscomHX_sqr - QTrn * QTrn);
553 
554  // momentum of the outgoing hadron H in the CM frame
555  const ThreeVector cm_momentum(sign_direction * QTrx, sign_direction * QTry,
556  sign_direction * pL);
557 
558  const FourVector pstrHcom(std::sqrt(pabscomHX_sqr + massH * massH),
559  cm_momentum);
560  const FourVector pstrXcom(std::sqrt(pabscomHX_sqr + massX * massX),
561  -cm_momentum);
562 
563  if (!append_string(make_pythia_4vec(pstrXcom), {idqX2, idqX1}, 101,
564  !is_AB_to_AX)) {
565  return false;
566  }
567  PdgCode hadron_code = is_AB_to_AX ? PDGcodes_[0] : PDGcodes_[1];
568  ParticleData new_particle(ParticleType::find(hadron_code));
569 
570  auto had_mom = make_pythia_4vec(pstrHcom);
571  had_mom.rotbst(to_cm_.inverse());
572 
573  new_particle.set_4momentum(make_smash_4vec(had_mom));
574  new_particle.set_cross_section_scaling_factor(1.);
575  new_particle.set_formation_time(time_collision_);
576  final_state_.push_back(new_particle);
577 
578  return true;
579 }
580 
582  std::array<std::array<int, 2>, 2> quarks;
583 
584  // decompose hadron into quark (and diquark) contents
585  make_string_ends(PDGcodes_[0], quarks[0][1], quarks[0][0],
587  make_string_ends(PDGcodes_[1], quarks[1][1], quarks[1][0],
589  // sample the lightcone momentum fraction carried by gluons
590  const double xmin_gluon_fraction = pmin_gluon_lightcone_ / sqrtsAB_;
591  const double xfracA =
592  random::beta_a0(xmin_gluon_fraction, pow_fgluon_beta_ + 1.);
593  const double xfracB =
594  random::beta_a0(xmin_gluon_fraction, pow_fgluon_beta_ + 1.);
595  // sample the transverse momentum transfer
596  const double QTrx = random::normal(0., sigma_qperp_ * M_SQRT1_2);
597  const double QTry = random::normal(0., sigma_qperp_ * M_SQRT1_2);
598  const double QTrn = std::sqrt(QTrx * QTrx + QTry * QTry);
599  // evaluate the lightcone momentum transfer
600  const double QPos = -QTrn * QTrn / (2. * xfracB * PNegB_);
601  const double QNeg = QTrn * QTrn / (2. * xfracA * PPosA_);
602 
603  const double pz1 = ((PPosA_ + QPos) - (PNegA_ + QNeg)) * M_SQRT1_2;
604  const double E1 = ((PPosA_ + QPos) + (PNegA_ + QNeg)) * M_SQRT1_2;
605  Pythia8::Vec4 p_str1(QTrx, QTry, pz1, E1);
606 
607  const double pz2 = ((PPosB_ - QPos) - (PNegB_ - QNeg)) * M_SQRT1_2;
608  const double E2 = ((PPosB_ - QPos) + (PNegB_ - QNeg)) * M_SQRT1_2;
609  Pythia8::Vec4 p_str2(-QTrx, -QTry, pz2, E2);
610  if (random::uniform_int(0, 1) == 0) {
611  std::swap(quarks[0][0], quarks[0][1]);
612  }
613  if (random::uniform_int(0, 1) == 0) {
614  std::swap(quarks[1][0], quarks[1][1]);
615  }
616  return append_string(p_str1, quarks[0], 101, true) &&
617  append_string(p_str2, quarks[1], 102, false);
618 }
619 
621  logg[LPythia].debug("Annihilation occurs between ", PDGcodes_[0], "+",
622  PDGcodes_[1], " at CM energy [GeV] ", sqrtsAB_);
623 
624  // check if the initial state is baryon-antibaryon pair.
625  PdgCode baryon = PDGcodes_[0], antibaryon = PDGcodes_[1];
626  if (baryon.baryon_number() == -1) {
627  std::swap(baryon, antibaryon);
628  }
629  if (baryon.baryon_number() != 1 || antibaryon.baryon_number() != -1) {
630  throw std::invalid_argument("Expected baryon-antibaryon pair.");
631  }
632 
633  // Count how many qqbar combinations are possible for each quark type
634  constexpr int n_q_types = 5; // u, d, s, c, b
635  std::vector<int> qcount_bar, qcount_antibar;
636  std::vector<int> n_combinations;
637  bool no_combinations = true;
638  for (int i = 0; i < n_q_types; i++) {
639  qcount_bar.push_back(baryon.net_quark_number(i + 1));
640  qcount_antibar.push_back(-antibaryon.net_quark_number(i + 1));
641  const int n_i = qcount_bar[i] * qcount_antibar[i];
642  n_combinations.push_back(n_i);
643  if (n_i > 0) {
644  no_combinations = false;
645  }
646  }
647 
648  /* if it is a BBbar pair but there is no qqbar pair to annihilate,
649  * nothing happens */
650  if (no_combinations) {
651  for (int i = 0; i < 2; i++) {
652  ParticleData new_particle(ParticleType::find(PDGcodes_[i]));
653  Pythia8::Vec4 pcom_pyth = make_pythia_4vec(pcom_[i]);
654  pcom_pyth.rotbst(to_cm_.inverse());
655  new_particle.set_4momentum(make_smash_4vec(pcom_pyth));
656  new_particle.set_cross_section_scaling_factor(1.);
657  new_particle.set_formation_time(time_collision_);
658  final_state_.push_back(new_particle);
659  }
660  return true;
661  }
662 
663  // Select qqbar pair to annihilate and remove it away
664  auto discrete_distr = random::discrete_dist<int>(n_combinations);
665  const int q_annihilate = discrete_distr() + 1;
666  qcount_bar[q_annihilate - 1]--;
667  qcount_antibar[q_annihilate - 1]--;
668 
669  // Get the remaining quarks and antiquarks
670  std::vector<int> remaining_quarks, remaining_antiquarks;
671  for (int i = 0; i < n_q_types; i++) {
672  for (int j = 0; j < qcount_bar[i]; j++) {
673  remaining_quarks.push_back(i + 1);
674  }
675  for (int j = 0; j < qcount_antibar[i]; j++) {
676  remaining_antiquarks.push_back(-(i + 1));
677  }
678  }
679  assert(remaining_quarks.size() == 2);
680  assert(remaining_antiquarks.size() == 2);
681 
682  const std::array<double, 2> mstr = {0.5 * sqrtsAB_, 0.5 * sqrtsAB_};
683 
684  // randomly select two quark-antiquark pairs
685  if (random::uniform_int(0, 1) == 0) {
686  std::swap(remaining_quarks[0], remaining_quarks[1]);
687  }
688  if (random::uniform_int(0, 1) == 0) {
689  std::swap(remaining_antiquarks[0], remaining_antiquarks[1]);
690  }
691  // Make sure it satisfies kinematical threshold constraint
692  bool kin_threshold_satisfied = true;
693  for (int i = 0; i < 2; i++) {
694  const double mstr_min =
695  pythia_hadron_->particleData.m0(remaining_quarks[i]) +
696  pythia_hadron_->particleData.m0(remaining_antiquarks[i]);
697  if (mstr_min > mstr[i]) {
698  kin_threshold_satisfied = false;
699  }
700  }
701  if (!kin_threshold_satisfied) {
702  return false;
703  }
704 
705  for (int i = 0; i < 2; i++) {
707  {remaining_quarks[i], remaining_antiquarks[i]}, 101 + i,
708  i == 0, true)) {
709  return false;
710  }
711  }
712  return true;
713 }
714 
716  // ids of the two ends of each string
717  std::array<int, 2> endsA; // string built with p_strA
718  std::array<int, 2> endsB; // string built with p_strB
719 
720  // decompose hadron into quark (and diquark) contents
721  int idqA1, idqA2, idqB1, idqB2;
724 
725  const int bar_a = PDGcodes_[0].baryon_number();
726  const int bar_b = PDGcodes_[1].baryon_number();
727 
728  if (bar_a == 1 || // baryon-*
729  (bar_a == 0 && bar_b == 1) || // meson-baryon
730  (bar_a == 0 && bar_b == 0)) { // meson-meson
731  endsA = {idqA2, idqB1};
732  endsB = {idqB2, idqA1};
733  } else if ((bar_a == 0 && bar_b == -1) || // meson-antibaryon
734  (bar_a == -1)) { // antibaryon-*
735  endsA = {idqB2, idqA1};
736  endsB = {idqA2, idqB1};
737  } else {
738  std::stringstream ss;
739  ss << "StringProcess::next_NDiffSoft: baryonA = " << bar_a
740  << ", baryonB = " << bar_b;
741  throw std::runtime_error(ss.str());
742  }
743 
744  // CM frame with A along +z and B along -z; PPos/PNeg are lightcone components
745  // in this frame.
746 
747  // sample the lightcone momentum fraction carried by quarks
748  const double xfracA = random::beta(pow_fquark_alpha_, pow_fquark_beta_);
749  const double xfracB = random::beta(pow_fquark_alpha_, pow_fquark_beta_);
750 
751  // sample the transverse momentum transfer
752  const double qx = random::normal(0., sigma_qperp_ * M_SQRT1_2);
753  const double qy = random::normal(0., sigma_qperp_ * M_SQRT1_2);
754  const double qT2 = qx * qx + qy * qy;
755 
756  // evaluate the lightcone momentum transfer
757  const double QPos = -qT2 / (2. * xfracB * PNegB_);
758  const double QNeg = qT2 / (2. * xfracA * PPosA_);
759  const double dPPos = -xfracA * PPosA_ - QPos;
760  const double dPNeg = xfracB * PNegB_ - QNeg;
761 
762  // string from hadron A side (with +qT)
763  const double pzA = ((PPosA_ + dPPos) - (PNegA_ + dPNeg)) * M_SQRT1_2;
764  const double EA = ((PPosA_ + dPPos) + (PNegA_ + dPNeg)) * M_SQRT1_2;
765  Pythia8::Vec4 p_strA(qx, qy, pzA, EA);
766 
767  // string from hadron B side (with -qT)
768  const double pzB = ((PPosB_ - dPPos) - (PNegB_ - dPNeg)) * M_SQRT1_2;
769  const double EB = ((PPosB_ - dPPos) + (PNegB_ - dPNeg)) * M_SQRT1_2;
770  Pythia8::Vec4 p_strB(-qx, -qy, pzB, EB);
771  return append_string(p_strA, endsA, 101, true) &&
772  append_string(p_strB, endsB, 102, false);
773 }
774 
776  Pythia8::Pythia& pythia) {
777  const Pythia8::Event& event = pythia.event;
778 
779  auto find_final_copy = [&](int iPos) -> int {
780  if (iPos <= 0 || iPos >= event.size())
781  return -1;
782  if (event[iPos].isFinal())
783  return iPos;
784 
785  const int id = event[iPos].id();
786  const auto ds = event[iPos].daughterListRecursive();
787  for (int j : ds) {
788  if (j > 0 && j < event.size() && event[j].isFinal() &&
789  event[j].id() == id)
790  return j;
791  }
792  return -1;
793  };
794 
795  auto tag_from_beam = [&](const Pythia8::BeamParticle& beam,
796  std::vector<bool>& isValenceFinal) -> void {
797  for (int i = 0; i < beam.size(); ++i) {
798  if (!beam[i].isValence()) {
799  continue;
800  }
801 
802  const int j = find_final_copy(beam[i].iPos());
803 
804  if (j >= 0 && (event[j].isQuark() || event[j].isDiquark())) {
805  isValenceFinal[j] = true;
806  }
807  }
808  };
809  auto tag_from_unresolved_diff_systems =
810  [&](std::vector<bool>& isValenceFinal) -> void {
811  for (int i = 1; i < event.size(); ++i) {
812  if (event[i].statusAbs() != 15) {
813  continue;
814  }
815 
816  std::vector<int> endpoint_daughters;
817  bool resolved_system = false;
818 
819  for (int d : event[i].daughterList()) {
820  if (d <= 0 || d >= event.size()) {
821  continue;
822  }
823 
824  if (event[d].isQuark() || event[d].isDiquark()) {
825  endpoint_daughters.push_back(d);
826  continue;
827  }
828 
829  if (!event[d].isGluon()) {
830  resolved_system = true;
831  }
832  }
833 
834  if (resolved_system || endpoint_daughters.empty()) {
835  continue;
836  }
837 
838  bool already_tagged = false;
839  for (int j : endpoint_daughters) {
840  if (isValenceFinal[j]) {
841  already_tagged = true;
842  break;
843  }
844  }
845 
846  if (already_tagged) {
847  continue;
848  }
849 
850  for (int j : endpoint_daughters) {
851  isValenceFinal[j] = true;
852  }
853  }
854  };
855 
856  std::vector<bool> isValenceFinal(event.size(), false);
857  tag_from_beam(pythia.beamA, isValenceFinal);
858  tag_from_beam(pythia.beamB, isValenceFinal);
859 
860  tag_from_unresolved_diff_systems(isValenceFinal);
861 
862  return isValenceFinal;
863 }
864 
865 void StringProcess::tag_leading_hadrons(Pythia8::Event& event) {
866  // Find the two string endpoint partons.
867  int idx1 = -1;
868  int idx2 = -1;
869  const Pythia8::ParticleData& pd = pythia_hadron_->particleData;
870 
871  for (const auto& part : event) {
872  if (part.isQuark() || part.isDiquark()) {
873  if (idx1 < 0) {
874  idx1 = part.index();
875  } else {
876  idx2 = part.index();
877  break;
878  }
879  }
880  }
881 
882  if (idx1 < 0 || idx2 < 0) {
883  return;
884  }
885 
886  // Work in the string rest frame. This makes the leading hadrons the ones
887  // closest to the two string endpoints in longitudinal momentum.
888  Pythia8::RotBstMatrix toRest;
889  toRest.toCMframe(event[idx1].p(), event[idx2].p());
890  event.rotbst(toRest);
891 
892  // Collect all final-state hadrons and order them from backward to forward.
893  std::vector<int> hadrons;
894  hadrons.reserve(event.size());
895 
896  for (int i = 0; i < event.size(); ++i) {
897  if (event[i].isFinal() && event[i].isHadron()) {
898  hadrons.push_back(i);
899  }
900  }
901 
902  std::sort(hadrons.begin(), hadrons.end(),
903  [&](int a, int b) { return event[a].pz() < event[b].pz(); });
904 
905  // Identify the forward and backward string endpoints in the rest frame.
906  const int forward_endpoint =
907  event[idx1].pz() > event[idx2].pz() ? idx1 : idx2;
908  const int backward_endpoint = forward_endpoint == idx1 ? idx2 : idx1;
909 
910  auto matches_endpoint = [&](int endpoint, int hadron) {
911  const auto& end = event[endpoint];
912  const auto& h = event[hadron];
913 
914  // Do not allow the same hadron to be tagged by both endpoints.
915  if (is_leading(h)) {
916  return false;
917  }
918 
919  // A diquark endpoint should tag a leading baryon with the same
920  // baryon-number sign.
921  if (end.isDiquark()) {
922  return pd.isBaryon(h.id()) && h.id() * end.id() > 0;
923  }
924 
925  // If a quark endpoint tags a baryon, require a compatible baryon sign.
926  if (pd.isBaryon(h.id())) {
927  return h.id() * end.id() > 0;
928  }
929 
930  // Mesons are compatible with quark endpoints.
931  return true;
932  };
933 
934  auto find_edge_hadron = [&](int endpoint, bool forward) -> int {
935  // Search from the edge corresponding to this endpoint: largest pz for
936  // the forward endpoint, smallest pz for the backward endpoint.
937  if (forward) {
938  for (auto it = hadrons.rbegin(); it != hadrons.rend(); ++it) {
939  if (matches_endpoint(endpoint, *it)) {
940  return *it;
941  }
942  }
943  } else {
944  for (int h : hadrons) {
945  if (matches_endpoint(endpoint, h)) {
946  return h;
947  }
948  }
949  }
950 
951  return -1;
952  };
953 
954  auto tag_endpoint = [&](int endpoint, bool forward) {
955  // Only original leading partons should define leading hadrons.
956  if (!is_leading_parton(event[endpoint])) {
957  return;
958  }
959 
960  const int hadron = find_edge_hadron(endpoint, forward);
961 
962  if (hadron >= 0) {
963  event[hadron].status(
964  leading_hadron_status_from_endpoint(event[endpoint]));
965  }
966  };
967 
968  auto tag_if_diquark = [&](int endpoint, bool forward) {
969  if (event[endpoint].isDiquark()) {
970  tag_endpoint(endpoint, forward);
971  }
972  };
973 
974  auto tag_if_quark = [&](int endpoint, bool forward) {
975  if (!event[endpoint].isDiquark()) {
976  tag_endpoint(endpoint, forward);
977  }
978  };
979 
980  // Tag diquark endpoints first. This lets baryonic leading hadrons claim the
981  // appropriate edge hadrons before quark endpoints are considered.
982  tag_if_diquark(backward_endpoint, false);
983  tag_if_diquark(forward_endpoint, true);
984 
985  // Tag the remaining quark endpoints.
986  tag_if_quark(backward_endpoint, false);
987  tag_if_quark(forward_endpoint, true);
988 
989  // Restore the original event frame.
990  event.rotbst(toRest.inverse());
991 }
992 
994  logg[LPythia].debug("Hard non-diff. with ", PDGcodes_[0], " + ", PDGcodes_[1],
995  " at CM energy [GeV] ", sqrtsAB_);
996 
997  std::array<int, 2> pdg_for_pythia;
998  std::array<std::array<int, 5>, 2> excess_quark;
999  std::array<std::array<int, 5>, 2> excess_antiq;
1000  for (int i = 0; i < 2; i++) {
1001  for (int j = 0; j < 5; j++) {
1002  excess_quark[i][j] = 0;
1003  excess_antiq[i][j] = 0;
1004  }
1005 
1006  // get PDG id used in PYTHIA event generation
1007  pdg_for_pythia[i] = pdg_map_for_pythia(PDGcodes_[i]);
1008  logg[LPythia].debug(" incoming particle ", i, " : ", PDGcodes_[i],
1009  " is mapped onto ", pdg_for_pythia[i]);
1010 
1011  PdgCode pdgcode_for_pythia(std::to_string(pdg_for_pythia[i]));
1012  /* evaluate how many more constituents incoming hadron has
1013  * compared to the mapped one. */
1014  find_excess_constituent(PDGcodes_[i], pdgcode_for_pythia, excess_quark[i],
1015  excess_antiq[i]);
1016  logg[LPythia].debug(" excess_quark[", i, "] = (", excess_quark[i][0],
1017  ", ", excess_quark[i][1], ", ", excess_quark[i][2],
1018  ", ", excess_quark[i][3], ", ", excess_quark[i][4],
1019  ")");
1020  logg[LPythia].debug(" excess_antiq[", i, "] = (", excess_antiq[i][0],
1021  ", ", excess_antiq[i][1], ", ", excess_antiq[i][2],
1022  ", ", excess_antiq[i][3], ", ", excess_antiq[i][4],
1023  ")");
1024  }
1025 
1026  std::pair<int, int> idAB{pdg_for_pythia[0], pdg_for_pythia[1]};
1027 
1028  // If an entry for the calculated particle IDs does not exist, create one
1029  // and initialize it accordingly
1030  if (hard_map_.count(idAB) == 0) {
1031  hard_map_[idAB] = std::make_unique<Pythia8::Pythia>(PYTHIA_XML_DIR, false);
1032  hard_map_[idAB]->readString("SoftQCD:nonDiffractive = on");
1033  hard_map_[idAB]->readString("SoftQCD:singleDiffractiveXB = on");
1034  hard_map_[idAB]->readString("SoftQCD:singleDiffractiveAX = on");
1035  hard_map_[idAB]->readString("SoftQCD:doubleDiffractive = on");
1036  hard_map_[idAB]->readString("HadronLevel:all = off");
1039  string_sigma_T_);
1040  hard_map_[idAB]->settings.flag("Beams:allowVariableEnergy", true);
1041  hard_map_[idAB]->settings.mode("Beams:idA", idAB.first);
1042  hard_map_[idAB]->settings.mode("Beams:idB", idAB.second);
1043  hard_map_[idAB]->settings.parm(
1044  "Beams:eCM", mpi_initialization_sqrts_.value_or(sqrtsAB_));
1045  logg[LPythia].debug("Pythia object initialized with ", pdg_for_pythia[0],
1046  " + ", pdg_for_pythia[1], " at CM energy [GeV] ",
1048  if (!hard_map_[idAB]->init()) {
1049  throw std::runtime_error("Pythia failed to initialize.");
1050  }
1051  }
1052  const int seed_new = random::uniform_int(1, maximum_rndm_seed_in_pythia);
1053  hard_map_[idAB]->rndm.init(seed_new);
1054  logg[LPythia].debug("hard_map_[", idAB.first, "][", idAB.second,
1055  "] : rndm is initialized with seed ", seed_new);
1056 
1057  // Change the energy using the Pythia 8.302+ feature
1058  hard_map_[idAB]->setKinematics(sqrtsAB_);
1059 
1060  bool final_state_success = false;
1061  /* Hard-process codes follow the Pythia convention:
1062  * https://www.pythia.org/latest-manual/QCDSoftProcesses.html
1063  *
1064  * Note: Pythia effectively classifies processes by the last digit
1065  * of the code (e.g. 101 -> 1, 102 -> 2, ...).
1066  *
1067  * The terminology "soft" vs "hard" is somewhat misleading:
1068  * Pythia's SoftQCD corresponds to a semi-perturbative (semi-hard)
1069  * model valid down to low pT, while HardQCD represents purely
1070  * perturbative processes and is not applicable over the full pT range.
1071  */
1072 
1073  switch (type) {
1075  final_state_success = hard_map_[idAB]->next(1);
1076  break;
1078  final_state_success = hard_map_[idAB]->next(5);
1079  break;
1081  final_state_success = hard_map_[idAB]->next(4);
1082  break;
1084  final_state_success = hard_map_[idAB]->next(3);
1085  break;
1086  default:
1087  logg[LPythia].error("Unknown string process required.");
1088  final_state_success = false;
1089  break;
1090  }
1091 
1092  logg[LPythia].debug("Pythia final state computed, success = ",
1093  final_state_success);
1094  if (!final_state_success) {
1095  return false;
1096  }
1097 
1098  auto valence_tags = compute_beam_valence_flags(*hard_map_[idAB]);
1099 
1100  for (auto& p : hard_map_[idAB]->event) {
1101  if (!p.isQuark() && !p.isDiquark() && !p.isGluon()) {
1102  continue;
1103  }
1104 
1105  if (valence_tags[p.index()] && (p.isQuark() || p.isDiquark())) {
1106  p.statusCode(static_cast<int>(p.isDiquark()
1109  } else {
1110  p.statusCode(static_cast<int>(LeadingStatus::NonLeadingParton));
1111  }
1112  }
1113  ParticleList new_intermediate_particles;
1114  ParticleList new_non_hadron_particles;
1115  Pythia8::Vec4 pSum = 0.;
1116  event_intermediate_.reset();
1117  /* Update the partonic intermediate state from PYTHIA output.
1118  * Note that hadronization will be performed separately,
1119  * after identification of strings and replacement of constituents. */
1120  for (int i = 0; i < hard_map_[idAB]->event.size(); i++) {
1121  if (hard_map_[idAB]->event[i].isFinal()) {
1122  const int pdgid = hard_map_[idAB]->event[i].id();
1123  Pythia8::Vec4 pquark = hard_map_[idAB]->event[i].p();
1124  const double mass = pquark.mCalc();
1125  const int status = hard_map_[idAB]->event[i].status();
1126  const int color = hard_map_[idAB]->event[i].col();
1127  const int anticolor = hard_map_[idAB]->event[i].acol();
1128 
1129  pSum += pquark;
1130  event_intermediate_.append(pdgid, status, color, anticolor, pquark, mass);
1131  }
1132  }
1133  // add junctions to the intermediate state if there is any.
1134  event_intermediate_.clearJunctions();
1135  for (int i = 0; i < hard_map_[idAB]->event.sizeJunction(); i++) {
1136  const int kind = hard_map_[idAB]->event.kindJunction(i);
1137  std::array<int, 3> col;
1138  for (int j = 0; j < 3; j++) {
1139  col[j] = hard_map_[idAB]->event.colJunction(i, j);
1140  }
1141  event_intermediate_.appendJunction(kind, col[0], col[1], col[2]);
1142  }
1143  /* The zeroth entry of event record is supposed to have the information
1144  * on the whole system. Specify the total momentum and invariant mass. */
1145  event_intermediate_[0].p(pSum);
1146  event_intermediate_[0].m(pSum.mCalc());
1147 
1148  const int elastic_side =
1151  : -1;
1152  if (elastic_side >= 0 &&
1153  PDGcodes_[elastic_side].get_decimal() != pdg_for_pythia[elastic_side]) {
1154  const int mapped_id = pdg_for_pythia[elastic_side];
1155  const int actual_id = PDGcodes_[elastic_side].get_decimal();
1156 
1157  int elastic_index = -1;
1158 
1159  for (int i = 1; i < event_intermediate_.size(); ++i) {
1160  const auto& p = event_intermediate_[i];
1161 
1162  if (p.status() == 14 && p.id() == mapped_id) {
1163  elastic_index = i;
1164  break;
1165  }
1166  }
1167 
1168  if (elastic_index < 0) {
1169  logg[LPythia].warn("Could not find elastic mapped hadron ", mapped_id,
1170  " to replace by ", actual_id);
1171  return false;
1172  }
1173 
1174  auto& elastic = event_intermediate_[elastic_index];
1175 
1176  std::vector<int> recoil_indices;
1177  Pythia8::Vec4 p_recoil_old;
1178 
1179  for (int i = 1; i < event_intermediate_.size(); ++i) {
1180  if (i == elastic_index)
1181  continue;
1182 
1183  const auto& p = event_intermediate_[i];
1184 
1185  recoil_indices.push_back(i);
1186  p_recoil_old += p.p();
1187  }
1188 
1189  if (recoil_indices.empty()) {
1190  event_intermediate_.list();
1191  logg[LPythia].warn(
1192  "Could not find recoil system for elastic mapped hadron ", mapped_id);
1193  return false;
1194  }
1195 
1196  Pythia8::Vec4 p_elastic_new = elastic.p();
1197  Pythia8::Vec4 p_recoil_new = p_recoil_old;
1198 
1199  const double m_elastic_new =
1201 
1202  const double m_recoil_system = p_recoil_old.mCalc();
1203 
1204  if (!Pythia8::pShift(p_elastic_new, p_recoil_new, m_elastic_new,
1205  m_recoil_system)) {
1206  logg[LPythia].warn(
1207  "Could not shift momenta when remapping elastic hadron from ",
1208  mapped_id, " to ", actual_id, ".");
1209  return false;
1210  }
1211 
1212  Pythia8::RotBstMatrix recoil_bst;
1213  recoil_bst.bstback(p_recoil_old);
1214  recoil_bst.bst(p_recoil_new);
1215 
1216  elastic.id(actual_id);
1217  elastic.m(m_elastic_new);
1218  elastic.p(p_elastic_new);
1219 
1220  for (const int idx : recoil_indices) {
1221  auto& recoiler = event_intermediate_[idx];
1222 
1223  Pythia8::Vec4 p_new = recoiler.p();
1224  p_new.rotbst(recoil_bst);
1225  recoiler.p(p_new);
1226  }
1227  excess_quark[elastic_side] = {0, 0, 0, 0, 0};
1228  excess_antiq[elastic_side] = {0, 0, 0, 0, 0};
1229  }
1230  /* Replace quark constituents according to the excess of valence quarks
1231  * and then rescale momenta of partons by constant factor
1232  * to fulfill the energy-momentum conservation. */
1233  bool correct_constituents =
1234  restore_constituent(event_intermediate_, excess_quark, excess_antiq);
1235  if (!correct_constituents) {
1236  logg[LPythia].debug("failed to find correct partonic constituents.");
1237  return false;
1238  }
1239  event_intermediate_.rotbst(to_cm_.inverse());
1240  int npart = event_intermediate_.size();
1241  int ipart = 0;
1242  while (ipart < npart) {
1243  const int pdgid = event_intermediate_[ipart].id();
1244  if (event_intermediate_[ipart].isFinal() &&
1245  !event_intermediate_[ipart].isParton() &&
1246  !hard_map_[idAB]->particleData.isOctetHadron(pdgid)) {
1247  logg[LPythia].debug("PDG ID from Pythia: ", pdgid);
1248  Pythia8::Vec4 momentum_pythia = event_intermediate_[ipart].p();
1249  FourVector momentum = make_smash_4vec(momentum_pythia);
1250 
1251  logg[LPythia].debug("4-momentum from Pythia: ", momentum);
1252  bool found_ptype =
1253  append_intermediate_list(pdgid, momentum, new_non_hadron_particles);
1254  if (!found_ptype) {
1255  logg[LPythia].warn("PDG ID ", pdgid,
1256  " does not exist in ParticleType - start over.");
1257  return false;
1258  }
1259  event_intermediate_.remove(ipart, ipart);
1260  npart -= 1;
1261  } else {
1262  ipart += 1;
1263  }
1264  }
1265 
1266  for (ParticleData& non_hadron : new_non_hadron_particles) {
1267  non_hadron.set_cross_section_scaling_factor(1.);
1268  non_hadron.set_formation_time(time_collision_);
1269  final_state_.push_back(non_hadron);
1270  }
1271  logg[LPythia].debug("Hard non-diff: partonic process gives ",
1272  event_intermediate_.size(), " partons.");
1273  bool find_forward_string = true;
1274  while (event_intermediate_.size() > 1) {
1275  Pythia8::Event string_event;
1276  string_event.init("Hard string", &pythia_hadron_->particleData);
1277  if (event_intermediate_.sizeJunction() > 0) {
1278  // identify string from a junction if there is any.
1279  compose_string_junction(find_forward_string, event_intermediate_,
1280  string_event);
1281  } else {
1282  /* identify string from a most forward or backward parton.
1283  * if there is no junction. */
1284  compose_string_parton(find_forward_string, event_intermediate_,
1285  string_event);
1286  }
1287  if (!string_above_threshold(string_event)) {
1288  return false;
1289  }
1290  string_parton_events_.push_back(string_event);
1291  find_forward_string = !find_forward_string;
1292  }
1293 
1294  return true;
1295 }
1296 
1297 double StringProcess::estimate_string_threshold(int left_endpoint_id,
1298  int right_endpoint_id) {
1299  constexpr double fragmentation_margin = 2.0 * pion_mass; // GeV
1300 
1301  auto& particle_data = pythia_hadron_->particleData;
1302 
1303  const bool left_is_quark = particle_data.isQuark(left_endpoint_id);
1304  const bool right_is_quark = particle_data.isQuark(right_endpoint_id);
1305  const bool left_is_diquark = particle_data.isDiquark(left_endpoint_id);
1306  const bool right_is_diquark = particle_data.isDiquark(right_endpoint_id);
1307 
1308  if (!(left_is_quark || left_is_diquark) ||
1309  !(right_is_quark || right_is_diquark)) {
1310  throw std::invalid_argument(
1311  "String threshold estimate requires quark or diquark endpoints.");
1312  }
1313 
1314  if (left_is_diquark && right_is_diquark) {
1315  const int lightest_left_baryon =
1316  pythia_stringflav_.combineToLightest(left_endpoint_id, 2); // u
1317  const int lightest_right_baryon =
1318  pythia_stringflav_.combineToLightest(right_endpoint_id, -2); // anti-u
1319 
1320  return particle_data.m0(lightest_left_baryon) +
1321  particle_data.m0(lightest_right_baryon) + fragmentation_margin;
1322  }
1323 
1324  const int lightest_hadron =
1325  pythia_stringflav_.combineToLightest(left_endpoint_id, right_endpoint_id);
1326 
1327  const PdgCode pdg = PdgCode::from_decimal(lightest_hadron);
1328  return ParticleType::find(pdg).mass() + fragmentation_margin;
1329 }
1330 
1331 void StringProcess::set_color_by_type(Pythia8::Particle& p, int color) {
1332  const int id = p.id();
1333  const bool is_anti = (id < 0);
1334 
1335  const auto& pd = pythia_hadron_->particleData;
1336  const bool is_quark = pd.isQuark(id);
1337  const bool is_diquark = pd.isDiquark(id);
1338 
1339  if ((is_quark && !is_anti) || (is_diquark && is_anti)) {
1340  // quark or antidiquark: color flows out
1341  p.cols(color, 0);
1342  } else if ((is_quark && is_anti) || (is_diquark && !is_anti)) {
1343  // antiquark or diquark: color flows in
1344  p.cols(0, color);
1345  } else {
1346  throw std::runtime_error("Cannot set color: not a quark or diquark (id=" +
1347  std::to_string(id) + ")");
1348  }
1349 }
1350 bool StringProcess::string_above_threshold(const Pythia8::Event& event) {
1351  const double string_mass = event[0].mCalc();
1352  const auto& particle_data = pythia_hadron_->particleData;
1353 
1354  std::vector<int> endpoints;
1355  endpoints.reserve(event.size());
1356 
1357  for (const Pythia8::Particle& p : event) {
1358  if (!p.isFinal() || p.isGluon() || !p.isParton()) {
1359  continue;
1360  }
1361 
1362  const int id = p.id();
1363 
1364  if (!particle_data.isQuark(id) && !particle_data.isDiquark(id)) {
1365  logg[LPythia].error("Colored non-quark/diquark in threshold check: id=",
1366  id, ", col=", p.col(), ", acol=", p.acol());
1367  return false;
1368  }
1369 
1370  endpoints.push_back(id);
1371  }
1372 
1373  // Closed gluon string.
1374  if (endpoints.size() < 2) {
1375  return string_mass > pion_mass * 2.0;
1376  }
1377 
1378  // Ordinary open string.
1379  if (endpoints.size() == 2) {
1380  return string_mass > estimate_string_threshold(endpoints[0], endpoints[1]);
1381  }
1382 
1383  // Baryonic/antibaryonic endpoint: combine two same-sign quarks.
1384  if (endpoints.size() == 3) {
1385  for (std::size_t i = 0; i < endpoints.size(); ++i) {
1386  for (std::size_t j = i + 1; j < endpoints.size(); ++j) {
1387  const int id_i = endpoints[i];
1388  const int id_j = endpoints[j];
1389 
1390  if (!particle_data.isQuark(id_i) || !particle_data.isQuark(id_j)) {
1391  continue;
1392  }
1393 
1394  if (id_i * id_j < 0) {
1395  continue;
1396  }
1397 
1398  const int diquark_id = diquark_from_quarks(id_i, id_j);
1399 
1400  const std::size_t k = 3 - i - j;
1401  const int remaining_id = endpoints[k];
1402 
1403  return string_mass >
1404  estimate_string_threshold(diquark_id, remaining_id);
1405  }
1406  }
1407 
1408  logg[LPythia].error(
1409  "Cannot reduce 3 string endpoints to quark-diquark "
1410  "threshold: ",
1411  endpoints[0], ", ", endpoints[1], ", ", endpoints[2]);
1412  return false;
1413  }
1414 
1415  logg[LPythia].error(
1416  "Unexpected number of string endpoints in threshold check: ",
1417  endpoints.size());
1418  return false;
1419 }
1421  PdgCode& pdg_mapped,
1422  std::array<int, 5>& excess_quark,
1423  std::array<int, 5>& excess_antiq) {
1424  /* decompose PDG id of the actual hadron and mapped one
1425  * to get the valence quark constituents */
1426  std::array<int, 3> qcontent_actual = pdg_actual.quark_content();
1427  std::array<int, 3> qcontent_mapped = pdg_mapped.quark_content();
1428 
1429  excess_quark = {0, 0, 0, 0, 0};
1430  excess_antiq = {0, 0, 0, 0, 0};
1431  for (int i = 0; i < 3; i++) {
1432  if (qcontent_actual[i] > 0) { // quark content of the actual hadron
1433  int j = qcontent_actual[i] - 1;
1434  excess_quark[j] += 1;
1435  }
1436 
1437  if (qcontent_mapped[i] > 0) { // quark content of the mapped hadron
1438  int j = qcontent_mapped[i] - 1;
1439  excess_quark[j] -= 1;
1440  }
1441 
1442  if (qcontent_actual[i] < 0) { // antiquark content of the actual hadron
1443  int j = std::abs(qcontent_actual[i]) - 1;
1444  excess_antiq[j] += 1;
1445  }
1446 
1447  if (qcontent_mapped[i] < 0) { // antiquark content of the mapped hadron
1448  int j = std::abs(qcontent_mapped[i]) - 1;
1449  excess_antiq[j] -= 1;
1450  }
1451  }
1452 }
1453 
1455  Pythia8::Particle& particle, std::array<int, 5>& excess_constituent) {
1456  // If the particle is neither quark nor diquark, nothing to do.
1457  if (!particle.isQuark() && !particle.isDiquark()) {
1458  return;
1459  }
1460 
1461  // If there is no excess of constituents, nothing to do.
1462  const std::array<int, 5> excess_null = {0, 0, 0, 0, 0};
1463  if (excess_constituent == excess_null) {
1464  return;
1465  }
1466 
1467  int nq = 0;
1468  std::array<int, 2> pdgid = {0, 0};
1469  int spin_deg = 0;
1470  int pdgid_new = 0;
1471  if (particle.isQuark()) {
1472  nq = 1;
1473  pdgid[0] = particle.id();
1474  } else if (particle.isDiquark()) {
1475  nq = 2;
1476  quarks_from_diquark(particle.id(), pdgid[0], pdgid[1], spin_deg);
1477  }
1478 
1479  for (int iq = 0; iq < nq; iq++) {
1480  int jq = std::abs(pdgid[iq]) - 1;
1481  int k_select = 0;
1482  std::vector<int> k_found;
1483  k_found.clear();
1484  // check if the constituent needs to be converted.
1485  if (excess_constituent[jq] < 0) {
1486  for (int k = 0; k < 5; k++) {
1487  // check which specie it can be converted into.
1488  if (k != jq && excess_constituent[k] > 0) {
1489  k_found.push_back(k);
1490  }
1491  }
1492  }
1493 
1494  // make a random selection of specie and update the excess of constituent.
1495  if (k_found.size() > 0) {
1496  const int l =
1497  random::uniform_int(0, static_cast<int>(k_found.size()) - 1);
1498  k_select = k_found[l];
1499  /* flavor jq + 1 is converted into k_select + 1
1500  * and excess_constituent is updated. */
1501  pdgid[iq] = pdgid[iq] > 0 ? k_select + 1 : -(k_select + 1);
1502  excess_constituent[jq] += 1;
1503  excess_constituent[k_select] -= 1;
1504  }
1505  }
1506 
1507  // determine PDG id of the converted parton.
1508  if (particle.isQuark()) {
1509  pdgid_new = pdgid[0];
1510  } else if (particle.isDiquark()) {
1511  if (std::abs(pdgid[0]) < std::abs(pdgid[1])) {
1512  std::swap(pdgid[0], pdgid[1]);
1513  }
1514 
1515  pdgid_new = std::abs(pdgid[0]) * 1000 + std::abs(pdgid[1]) * 100;
1516  if (std::abs(pdgid[0]) == std::abs(pdgid[1])) {
1517  pdgid_new += 3;
1518  } else {
1519  pdgid_new += spin_deg;
1520  }
1521 
1522  if (particle.id() < 0) {
1523  pdgid_new *= -1;
1524  }
1525  }
1526  logg[LPythia].debug(" parton id = ", particle.id(), " is converted to ",
1527  pdgid_new);
1528 
1529  // update the constituent mass and energy.
1530  Pythia8::Vec4 pquark = particle.p();
1531  double mass_new = pythia_hadron_->particleData.m0(pdgid_new);
1532  double e_new = std::sqrt(mass_new * mass_new + pquark.pAbs() * pquark.pAbs());
1533  // update the particle object.
1534  particle.id(pdgid_new);
1535  particle.e(e_new);
1536  particle.m(mass_new);
1537 }
1538 
1540  Pythia8::Event& event_intermediate, std::array<int, 5>& nquark_total,
1541  std::array<int, 5>& nantiq_total) {
1542  for (int iflav = 0; iflav < 5; iflav++) {
1543  nquark_total[iflav] = 0;
1544  nantiq_total[iflav] = 0;
1545  }
1546 
1547  for (int ip = 1; ip < event_intermediate.size(); ip++) {
1548  if (!event_intermediate[ip].isFinal()) {
1549  continue;
1550  }
1551  const int pdgid = event_intermediate[ip].id();
1552  if (pdgid > 0) {
1553  // quarks
1554  for (int iflav = 0; iflav < 5; iflav++) {
1555  nquark_total[iflav] +=
1556  pythia_hadron_->particleData.nQuarksInCode(pdgid, iflav + 1);
1557  }
1558  } else {
1559  // antiquarks
1560  for (int iflav = 0; iflav < 5; iflav++) {
1561  nantiq_total[iflav] += pythia_hadron_->particleData.nQuarksInCode(
1562  std::abs(pdgid), iflav + 1);
1563  }
1564  }
1565  }
1566 }
1567 
1569  Pythia8::Event& event_intermediate, std::array<int, 5>& nquark_total,
1570  std::array<int, 5>& nantiq_total, bool sign_constituent,
1571  std::array<std::array<int, 5>, 2>& excess_constituent) {
1572  Pythia8::Vec4 pSum = event_intermediate[0].p();
1573 
1574  /* compute total number of quark and antiquark constituents
1575  * in the whole system. */
1576  find_total_number_constituent(event_intermediate, nquark_total, nantiq_total);
1577 
1578  for (int iflav = 0; iflav < 5; iflav++) {
1579  /* Find how many constituent will be in the system after
1580  * changing the flavors.
1581  * Note that nquark_total is number of constituent right after
1582  * the pythia event (with mapped incoming hadrons), while the excess
1583  * shows how many constituents we have more or less that nquark_total. */
1584  int nquark_final =
1585  excess_constituent[0][iflav] + excess_constituent[1][iflav];
1586  if (sign_constituent) {
1587  nquark_final += nquark_total[iflav];
1588  } else {
1589  nquark_final += nantiq_total[iflav];
1590  }
1591  /* Therefore, nquark_final should not be negative.
1592  * negative nquark_final means that it will not be possible to
1593  * find a constituent to change the flavor. */
1594  bool enough_quark = nquark_final >= 0;
1595  /* If that is the case, a gluon will be splitted into
1596  * a quark-antiquark pair with the desired flavor. */
1597  if (!enough_quark) {
1598  logg[LPythia].debug(" not enough constituents with flavor ", iflav + 1,
1599  " : try to split a gluon to qqbar.");
1600  for (int ic = 0; ic < std::abs(nquark_final); ic++) {
1601  /* Since each incoming hadron has its own count of the excess,
1602  * it is necessary to find which one is problematic. */
1603  int ih_mod = -1;
1604  if (excess_constituent[0][iflav] < 0) {
1605  ih_mod = 0;
1606  } else {
1607  ih_mod = 1;
1608  }
1609 
1610  /* find the most forward or backward gluon
1611  * depending on which incoming hadron is found to be an issue. */
1612  int iforward = 1;
1613  for (int ip = 2; ip < event_intermediate.size(); ip++) {
1614  if (!event_intermediate[ip].isFinal() ||
1615  !event_intermediate[ip].isGluon()) {
1616  continue;
1617  }
1618 
1619  const double y_gluon_current = event_intermediate[ip].y();
1620  const double y_gluon_forward = event_intermediate[iforward].y();
1621  if ((ih_mod == 0 && y_gluon_current > y_gluon_forward) ||
1622  (ih_mod == 1 && y_gluon_current < y_gluon_forward)) {
1623  iforward = ip;
1624  }
1625  }
1626 
1627  if (!event_intermediate[iforward].isGluon()) {
1628  logg[LPythia].debug("There is no gluon to split into qqbar.");
1629  return false;
1630  }
1631 
1632  // four momentum of the original gluon
1633  Pythia8::Vec4 pgluon = event_intermediate[iforward].p();
1634 
1635  const int pdgid = iflav + 1;
1636  const double mass = pythia_hadron_->particleData.m0(pdgid);
1637  const int status = event_intermediate[iforward].status();
1638  /* color and anticolor indices.
1639  * the color index of gluon goes to the quark, while
1640  * the anticolor index goes to the antiquark */
1641  const int col = event_intermediate[iforward].col();
1642  const int acol = event_intermediate[iforward].acol();
1643 
1644  // three momenta of quark and antiquark
1645  std::array<double, 2> px_quark;
1646  std::array<double, 2> py_quark;
1647  std::array<double, 2> pz_quark;
1648  // energies of quark and antiquark
1649  std::array<double, 2> e_quark;
1650  // four momenta of quark and antiquark
1651  std::array<Pythia8::Vec4, 2> pquark;
1652  // transverse momentum scale of string fragmentation
1653  const double sigma_qt_frag = pythia_hadron_->parm("StringPT:sigma");
1654  // sample relative transverse momentum between quark and antiquark
1655  const double qx = random::normal(0., sigma_qt_frag * M_SQRT1_2);
1656  const double qy = random::normal(0., sigma_qt_frag * M_SQRT1_2);
1657  // setup kinematics
1658  for (int isign = 0; isign < 2; isign++) {
1659  /* As the simplest assumption, the three momentum of gluon
1660  * is equally distributed to quark and antiquark.
1661  * Then, they have a relative transverse momentum. */
1662  px_quark[isign] = 0.5 * pgluon.px() + (isign == 0 ? 1. : -1.) * qx;
1663  py_quark[isign] = 0.5 * pgluon.py() + (isign == 0 ? 1. : -1.) * qy;
1664  pz_quark[isign] = 0.5 * pgluon.pz();
1665  e_quark[isign] =
1666  std::sqrt(mass * mass + px_quark[isign] * px_quark[isign] +
1667  py_quark[isign] * py_quark[isign] +
1668  pz_quark[isign] * pz_quark[isign]);
1669  pquark[isign] = Pythia8::Vec4(px_quark[isign], py_quark[isign],
1670  pz_quark[isign], e_quark[isign]);
1671  }
1672 
1673  /* Total energy is not conserved at this point,
1674  * but this will be cured later. */
1675  pSum += pquark[0] + pquark[1] - pgluon;
1676  // add quark and antiquark to the event record
1677  event_intermediate.append(pdgid, status, col, 0, pquark[0], mass);
1678  event_intermediate.append(-pdgid, status, 0, acol, pquark[1], mass);
1679  // then remove the gluon from the record
1680  event_intermediate.remove(iforward, iforward);
1681 
1682  logg[LPythia].debug(" gluon at iforward = ", iforward,
1683  " is splitted into ", pdgid, ",", -pdgid,
1684  " qqbar pair.");
1685  /* Increase the total number of quarks and antiquarks by 1,
1686  * as we have extra ones from a gluon. */
1687  nquark_total[iflav] += 1;
1688  nantiq_total[iflav] += 1;
1689  }
1690  }
1691  }
1692 
1693  /* The zeroth entry of event record is supposed to have the information
1694  * on the whole system. Specify the total momentum and invariant mass. */
1695  event_intermediate[0].p(pSum);
1696  event_intermediate[0].m(pSum.mCalc());
1697 
1698  return true;
1699 }
1700 
1702  std::array<int, 5>& nquark_total,
1703  std::array<std::array<int, 5>, 2>& excess_quark,
1704  std::array<std::array<int, 5>, 2>& excess_antiq) {
1705  for (int iflav = 0; iflav < 5; iflav++) {
1706  /* Find how many constituent will be in the system after
1707  * changing the flavors.
1708  * Note that nquark_total is number of constituent right after
1709  * the pythia event (with mapped incoming hadrons), while the excess
1710  * shows how many constituents we have more or less that nquark_total. */
1711  int nquark_final =
1712  nquark_total[iflav] + excess_quark[0][iflav] + excess_quark[1][iflav];
1713  /* Therefore, nquark_final should not be negative.
1714  * negative nquark_final means that it will not be possible to
1715  * find a constituent to change the flavor. */
1716  bool enough_quark = nquark_final >= 0;
1717  // If that is the case, excess of constituents will be modified
1718  if (!enough_quark) {
1719  logg[LPythia].debug(" not enough constituents with flavor ", iflav + 1,
1720  " : try to modify excess of constituents.");
1721  for (int ic = 0; ic < std::abs(nquark_final); ic++) {
1722  /* Since each incoming hadron has its own count of the excess,
1723  * it is necessary to find which one is problematic. */
1724  int ih_mod = -1;
1725  if (excess_quark[0][iflav] < 0) {
1726  ih_mod = 0;
1727  } else {
1728  ih_mod = 1;
1729  }
1730  /* Increase the excess of both quark and antiquark
1731  * with corresponding flavor (iflav + 1) by 1.
1732  * This is for conservation of the net quark number. */
1733  excess_quark[ih_mod][iflav] += 1;
1734  excess_antiq[ih_mod][iflav] += 1;
1735 
1736  /* Since incoming hadrons are mapped onto ones with
1737  * the same baryon number (or quark number),
1738  * summation of the excesses over all flavors should be zero.
1739  * Therefore, we need to find another flavor which has
1740  * a positive excess and subtract by 1. */
1741  for (int jflav = 0; jflav < 5; jflav++) {
1742  // another flavor with positive excess of constituents
1743  if (jflav != iflav && excess_quark[ih_mod][jflav] > 0) {
1744  /* Decrease the excess of both quark and antiquark
1745  * with corresponding flavor (jflav + 1) by 1. */
1746  excess_quark[ih_mod][jflav] -= 1;
1747  excess_antiq[ih_mod][jflav] -= 1;
1748  /* We only need to find one (another) flavor to subtract.
1749  * No more! */
1750  break;
1751  }
1752  }
1753  }
1754  }
1755  }
1756 }
1757 
1759  Pythia8::Event& event_intermediate,
1760  std::array<std::array<int, 5>, 2>& excess_quark,
1761  std::array<std::array<int, 5>, 2>& excess_antiq) {
1762  Pythia8::Vec4 pSum = event_intermediate[0].p();
1763  const double energy_init = pSum.e();
1764  logg[LPythia].debug(" initial total energy [GeV] : ", energy_init);
1765 
1766  // Total number of quarks and antiquarks, respectively.
1767  std::array<int, 5> nquark_total;
1768  std::array<int, 5> nantiq_total;
1769 
1770  /* Split a gluon into qqbar if we do not have enough constituents
1771  * to be converted in the system. */
1772  bool split_for_quark = splitting_gluon_qqbar(
1773  event_intermediate, nquark_total, nantiq_total, true, excess_quark);
1774  bool split_for_antiq = splitting_gluon_qqbar(
1775  event_intermediate, nquark_total, nantiq_total, false, excess_antiq);
1776 
1777  /* Modify excess_quark and excess_antiq if we do not have enough
1778  * constituents to be converted in the system. */
1779  if (!split_for_quark || !split_for_antiq) {
1780  rearrange_excess(nquark_total, excess_quark, excess_antiq);
1781  rearrange_excess(nantiq_total, excess_antiq, excess_quark);
1782  }
1783 
1784  // Final check if there are enough constituents.
1785  for (int iflav = 0; iflav < 5; iflav++) {
1786  if (nquark_total[iflav] + excess_quark[0][iflav] + excess_quark[1][iflav] <
1787  0) {
1788  logg[LPythia].debug("Not enough quark constituents of flavor ",
1789  iflav + 1);
1790  return false;
1791  }
1792 
1793  if (nantiq_total[iflav] + excess_antiq[0][iflav] + excess_antiq[1][iflav] <
1794  0) {
1795  logg[LPythia].debug("Not enough antiquark constituents of flavor ",
1796  -(iflav + 1));
1797  return false;
1798  }
1799  }
1800 
1801  for (int ih = 0; ih < 2; ih++) {
1802  logg[LPythia].debug(" initial excess_quark[", ih, "] = (",
1803  excess_quark[ih][0], ", ", excess_quark[ih][1], ", ",
1804  excess_quark[ih][2], ", ", excess_quark[ih][3], ", ",
1805  excess_quark[ih][4], ")");
1806  logg[LPythia].debug(" initial excess_antiq[", ih, "] = (",
1807  excess_antiq[ih][0], ", ", excess_antiq[ih][1], ", ",
1808  excess_antiq[ih][2], ", ", excess_antiq[ih][3], ", ",
1809  excess_antiq[ih][4], ")");
1810  }
1811 
1812  bool recovered_quarks = false;
1813  while (!recovered_quarks) {
1814  /* Flavor conversion begins with the most forward and backward parton
1815  * respectively for incoming_particles_[0] and incoming_particles_[1]. */
1816  std::array<bool, 2> find_forward = {true, false};
1817  const std::array<int, 5> excess_null = {0, 0, 0, 0, 0};
1818  std::array<int, 5> excess_total = excess_null;
1819 
1820  for (int ih = 0; ih < 2; ih++) { // loop over incoming hadrons
1821  int nfrag = event_intermediate.size();
1822  for (int np_end = 0; np_end < nfrag - 1; np_end++) { // constituent loop
1823  /* select the np_end-th most forward or backward parton and
1824  * change its specie.
1825  * np_end = 0 corresponds to the most forward,
1826  * np_end = 1 corresponds to the second most forward and so on. */
1827  int iforward =
1828  get_index_forward(find_forward[ih], np_end, event_intermediate);
1829  pSum -= event_intermediate[iforward].p();
1830 
1831  if (event_intermediate[iforward].id() > 0) { // quark and diquark
1832  replace_constituent(event_intermediate[iforward], excess_quark[ih]);
1833  logg[LPythia].debug(
1834  " excess_quark[", ih, "] = (", excess_quark[ih][0], ", ",
1835  excess_quark[ih][1], ", ", excess_quark[ih][2], ", ",
1836  excess_quark[ih][3], ", ", excess_quark[ih][4], ")");
1837  } else { // antiquark and anti-diquark
1838  replace_constituent(event_intermediate[iforward], excess_antiq[ih]);
1839  logg[LPythia].debug(
1840  " excess_antiq[", ih, "] = (", excess_antiq[ih][0], ", ",
1841  excess_antiq[ih][1], ", ", excess_antiq[ih][2], ", ",
1842  excess_antiq[ih][3], ", ", excess_antiq[ih][4], ")");
1843  }
1844 
1845  const int pdgid = event_intermediate[iforward].id();
1846  Pythia8::Vec4 pquark = event_intermediate[iforward].p();
1847  const double mass = pquark.mCalc();
1848 
1849  const int status = event_intermediate[iforward].status();
1850  const int color = event_intermediate[iforward].col();
1851  const int anticolor = event_intermediate[iforward].acol();
1852 
1853  pSum += pquark;
1854  event_intermediate.append(pdgid, status, color, anticolor, pquark,
1855  mass);
1856 
1857  event_intermediate.remove(iforward, iforward);
1858  /* Now the last np_end + 1 entries in event_intermediate
1859  * are np_end + 1 most forward (or backward) partons. */
1860  }
1861 
1862  // Compute the excess of net quark numbers.
1863  for (int j = 0; j < 5; j++) {
1864  excess_total[j] += (excess_quark[ih][j] - excess_antiq[ih][j]);
1865  }
1866  }
1867 
1868  /* If there is no excess of net quark numbers,
1869  * quark content is considered to be correct. */
1870  recovered_quarks = excess_total == excess_null;
1871  }
1872  logg[LPythia].debug(" valence quark contents of hadons are recovered.");
1873 
1874  logg[LPythia].debug(" current total energy [GeV] : ", pSum.e());
1875  /* rescale momenta of all partons by a constant factor
1876  * to conserve the total energy. */
1877  while (true) {
1878  if (std::abs(pSum.e() - energy_init) <=
1879  std::abs(really_small * energy_init)) {
1880  break;
1881  }
1882 
1883  double energy_current = pSum.e();
1884  double slope = 0.;
1885  for (int i = 1; i < event_intermediate.size(); i++) {
1886  slope += event_intermediate[i].pAbs2() / event_intermediate[i].e();
1887  }
1888 
1889  const double rescale_factor = 1. + (energy_init - energy_current) / slope;
1890  pSum = 0.;
1891  for (int i = 1; i < event_intermediate.size(); i++) {
1892  const double px = rescale_factor * event_intermediate[i].px();
1893  const double py = rescale_factor * event_intermediate[i].py();
1894  const double pz = rescale_factor * event_intermediate[i].pz();
1895  const double pabs = rescale_factor * event_intermediate[i].pAbs();
1896  const double mass = event_intermediate[i].m();
1897 
1898  event_intermediate[i].px(px);
1899  event_intermediate[i].py(py);
1900  event_intermediate[i].pz(pz);
1901  event_intermediate[i].e(std::sqrt(mass * mass + pabs * pabs));
1902  pSum += event_intermediate[i].p();
1903  }
1904  logg[LPythia].debug(" parton momenta are rescaled by factor of ",
1905  rescale_factor);
1906  }
1907 
1908  logg[LPythia].debug(" final total energy [GeV] : ", pSum.e());
1909  /* The zeroth entry of event record is supposed to have the information
1910  * on the whole system. Specify the total momentum and invariant mass. */
1911  event_intermediate[0].p(pSum);
1912  event_intermediate[0].m(pSum.mCalc());
1913 
1914  return true;
1915 }
1916 
1917 void StringProcess::compose_string_parton(bool find_forward_string,
1918  Pythia8::Event& event_intermediate,
1919  Pythia8::Event& event_hadronize) {
1920  Pythia8::Vec4 pSum = 0.;
1921  event_hadronize.reset();
1922 
1923  // select the most forward or backward parton.
1924  int iforward = get_index_forward(find_forward_string, 0, event_intermediate);
1925  logg[LPythia].debug("Hard non-diff: iforward = ", iforward, "(",
1926  event_intermediate[iforward].id(), ")");
1927 
1928  pSum += event_intermediate[iforward].p();
1929  event_hadronize.append(event_intermediate[iforward]);
1930 
1931  int col_to_find = event_intermediate[iforward].acol();
1932  int acol_to_find = event_intermediate[iforward].col();
1933  event_intermediate.remove(iforward, iforward);
1934  logg[LPythia].debug("Hard non-diff: event_intermediate reduces in size to ",
1935  event_intermediate.size());
1936 
1937  // trace color and anti-color indices and find corresponding partons.
1938  while (col_to_find != 0 || acol_to_find != 0) {
1939  logg[LPythia].debug(" col_to_find = ", col_to_find,
1940  ", acol_to_find = ", acol_to_find);
1941 
1942  int ifound = -1;
1943  for (int i = 1; i < event_intermediate.size(); i++) {
1944  const int pdgid = event_intermediate[i].id();
1945  bool found_col =
1946  col_to_find != 0 && col_to_find == event_intermediate[i].col();
1947  bool found_acol =
1948  acol_to_find != 0 && acol_to_find == event_intermediate[i].acol();
1949  if (found_col) {
1950  logg[LPythia].debug(" col_to_find ", col_to_find, " from i ", i, "(",
1951  pdgid, ") found");
1952  }
1953  if (found_acol) {
1954  logg[LPythia].debug(" acol_to_find ", acol_to_find, " from i ", i, "(",
1955  pdgid, ") found");
1956  }
1957 
1958  if (found_col && !found_acol) {
1959  ifound = i;
1960  col_to_find = event_intermediate[i].acol();
1961  break;
1962  } else if (!found_col && found_acol) {
1963  ifound = i;
1964  acol_to_find = event_intermediate[i].col();
1965  break;
1966  } else if (found_col && found_acol) {
1967  ifound = i;
1968  col_to_find = 0;
1969  acol_to_find = 0;
1970  break;
1971  }
1972  }
1973 
1974  if (ifound < 0) {
1975  event_intermediate.list();
1976  event_intermediate.listJunctions();
1977  event_hadronize.list();
1978  event_hadronize.listJunctions();
1979  if (col_to_find != 0) {
1980  logg[LPythia].error("No parton with col = ", col_to_find);
1981  }
1982  if (acol_to_find != 0) {
1983  logg[LPythia].error("No parton with acol = ", acol_to_find);
1984  }
1985  throw std::runtime_error("Hard string could not be identified.");
1986  } else {
1987  pSum += event_intermediate[ifound].p();
1988  // add a parton to the new event record.
1989  event_hadronize.append(event_intermediate[ifound]);
1990 
1991  // then remove from the original event record.
1992  event_intermediate.remove(ifound, ifound);
1993  logg[LPythia].debug(
1994  "Hard non-diff: event_intermediate reduces in size to ",
1995  event_intermediate.size());
1996  }
1997  }
1998 
1999  /* The zeroth entry of event record is supposed to have the information
2000  * on the whole system. Specify the total momentum and invariant mass. */
2001  event_hadronize[0].p(pSum);
2002  event_hadronize[0].m(pSum.mCalc());
2003 }
2004 
2005 void StringProcess::compose_string_junction(bool& find_forward_string,
2006  Pythia8::Event& event_intermediate,
2007  Pythia8::Event& event_hadronize) {
2008  event_hadronize.reset();
2009 
2010  /* Move the first junction to the event record for hadronization
2011  * and specify color or anti-color indices to be found.
2012  * If junction kind is an odd number, it connects three quarks
2013  * to make a color-neutral baryonic configuration.
2014  * Otherwise, it connects three antiquarks
2015  * to make a color-neutral anti-baryonic configuration. */
2016  const int kind = event_intermediate.kindJunction(0);
2017  bool sign_color = kind % 2 == 1;
2018  std::vector<int> col; // color or anti-color indices of the junction legs
2019  for (int j = 0; j < 3; j++) {
2020  col.push_back(event_intermediate.colJunction(0, j));
2021  }
2022  event_hadronize.appendJunction(kind, col[0], col[1], col[2]);
2023  event_intermediate.eraseJunction(0);
2024  logg[LPythia].debug("junction (", col[0], ", ", col[1], ", ", col[2],
2025  ") with kind ", kind, " will be handled.");
2026 
2027  bool found_string = false;
2028  while (!found_string) {
2029  // trace color or anti-color indices and find corresponding partons.
2030  find_junction_leg(sign_color, col, event_intermediate, event_hadronize);
2031  found_string = true;
2032  for (unsigned int j = 0; j < col.size(); j++) {
2033  found_string = found_string && col[j] == 0;
2034  }
2035  if (!found_string) {
2036  /* if there is any leg which is not closed with parton,
2037  * look over junctions and find connected ones. */
2038  logg[LPythia].debug(" still has leg(s) unfinished.");
2039  sign_color = !sign_color;
2040  std::vector<int> junction_to_move;
2041  for (int i = 0; i < event_intermediate.sizeJunction(); i++) {
2042  const int kind_new = event_intermediate.kindJunction(i);
2043  /* If the original junction is associated with positive baryon number,
2044  * it looks for anti-junctions whose legs are connected with
2045  * anti-quarks (anti-colors in general). */
2046  if (sign_color != (kind_new % 2 == 1)) {
2047  continue;
2048  }
2049 
2050  std::array<int, 3> col_new;
2051  for (int k = 0; k < 3; k++) {
2052  col_new[k] = event_intermediate.colJunction(i, k);
2053  }
2054 
2055  int n_legs_connected = 0;
2056  // loop over remaining legs
2057  for (unsigned int j = 0; j < col.size(); j++) {
2058  if (col[j] == 0) {
2059  continue;
2060  }
2061  for (int k = 0; k < 3; k++) {
2062  if (col[j] == col_new[k]) {
2063  n_legs_connected += 1;
2064  col[j] = 0;
2065  col_new[k] = 0;
2066  }
2067  }
2068  }
2069 
2070  // specify which junction is connected to the original one.
2071  if (n_legs_connected > 0) {
2072  for (int k = 0; k < 3; k++) {
2073  if (col_new[k] != 0) {
2074  col.push_back(col_new[k]);
2075  }
2076  }
2077  logg[LPythia].debug(" junction ", i, " (",
2078  event_intermediate.colJunction(i, 0), ", ",
2079  event_intermediate.colJunction(i, 1), ", ",
2080  event_intermediate.colJunction(i, 2),
2081  ") with kind ", kind_new, " will be added.");
2082  junction_to_move.push_back(i);
2083  }
2084  }
2085 
2086  /* If there is any connected junction,
2087  * move it to the event record for hadronization. */
2088  for (unsigned int i = 0; i < junction_to_move.size(); i++) {
2089  unsigned int imove = junction_to_move[i] - i;
2090  const int kind_add = event_intermediate.kindJunction(imove);
2091  std::array<int, 3> col_add;
2092  for (int k = 0; k < 3; k++) {
2093  col_add[k] = event_intermediate.colJunction(imove, k);
2094  }
2095  // add a junction to the new event record.
2096  event_hadronize.appendJunction(kind_add, col_add[0], col_add[1],
2097  col_add[2]);
2098  // then remove from the original event record.
2099  event_intermediate.eraseJunction(imove);
2100  }
2101  }
2102  }
2103 
2104  Pythia8::Vec4 pSum = event_hadronize[0].p();
2105  find_forward_string = pSum.pz() > 0.;
2106 }
2107 
2108 void StringProcess::find_junction_leg(bool sign_color, std::vector<int>& col,
2109  Pythia8::Event& event_intermediate,
2110  Pythia8::Event& event_hadronize) {
2111  Pythia8::Vec4 pSum = event_hadronize[0].p();
2112  for (unsigned int j = 0; j < col.size(); j++) {
2113  if (col[j] == 0) {
2114  continue;
2115  }
2116  bool found_leg = false;
2117  while (!found_leg) {
2118  int ifound = -1;
2119  for (int i = 1; i < event_intermediate.size(); i++) {
2120  const int pdgid = event_intermediate[i].id();
2121  if (sign_color && col[j] == event_intermediate[i].col()) {
2122  logg[LPythia].debug(" col[", j, "] = ", col[j], " from i ", i, "(",
2123  pdgid, ") found");
2124  ifound = i;
2125  col[j] = event_intermediate[i].acol();
2126  break;
2127  } else if (!sign_color && col[j] == event_intermediate[i].acol()) {
2128  logg[LPythia].debug(" acol[", j, "] = ", col[j], " from i ", i, "(",
2129  pdgid, ") found");
2130  ifound = i;
2131  col[j] = event_intermediate[i].col();
2132  break;
2133  }
2134  }
2135 
2136  if (ifound < 0) {
2137  found_leg = true;
2138  if (event_intermediate.sizeJunction() == 0) {
2139  event_intermediate.list();
2140  event_intermediate.listJunctions();
2141  event_hadronize.list();
2142  event_hadronize.listJunctions();
2143  logg[LPythia].error("No parton with col = ", col[j],
2144  " connected with junction leg ", j);
2145  throw std::runtime_error("Hard string could not be identified.");
2146  }
2147  } else {
2148  pSum += event_intermediate[ifound].p();
2149  // add a parton to the new event record.
2150  event_hadronize.append(event_intermediate[ifound]);
2151  // then remove from the original event record.
2152  event_intermediate.remove(ifound, ifound);
2153  logg[LPythia].debug(
2154  "Hard non-diff: event_intermediate reduces in size to ",
2155  event_intermediate.size());
2156  if (col[j] == 0) {
2157  found_leg = true;
2158  }
2159  }
2160  }
2161  }
2162 
2163  /* The zeroth entry of event record is supposed to have the information
2164  * on the whole system. Specify the total momentum and invariant mass. */
2165  event_hadronize[0].p(pSum);
2166  event_hadronize[0].m(pSum.mCalc());
2167 }
2168 
2170  ThreeVector& evec_polar, std::array<ThreeVector, 3>& evec_basis) {
2171  assert(std::fabs(evec_polar.sqr() - 1.) < really_small);
2172 
2173  if (std::abs(evec_polar.x3()) < (1. - 1.0e-8)) {
2174  double ex, ey, et;
2175  double theta, phi;
2176 
2177  // evec_basis[0] is set to be longitudinal direction
2178  evec_basis[0] = evec_polar;
2179 
2180  theta = std::acos(evec_basis[0].x3());
2181 
2182  ex = evec_basis[0].x1();
2183  ey = evec_basis[0].x2();
2184  et = std::sqrt(ex * ex + ey * ey);
2185  if (ey > 0.) {
2186  phi = std::acos(ex / et);
2187  } else {
2188  phi = -std::acos(ex / et);
2189  }
2190 
2191  /* The transverse plane is spanned
2192  * by evec_basis[1] and evec_basis[2]. */
2193  evec_basis[1].set_x1(std::cos(theta) * std::cos(phi));
2194  evec_basis[1].set_x2(std::cos(theta) * std::sin(phi));
2195  evec_basis[1].set_x3(-std::sin(theta));
2196 
2197  evec_basis[2].set_x1(-std::sin(phi));
2198  evec_basis[2].set_x2(std::cos(phi));
2199  evec_basis[2].set_x3(0.);
2200  } else {
2201  // if evec_polar is very close to the z axis
2202  if (evec_polar.x3() > 0.) {
2203  evec_basis[1] = ThreeVector(1., 0., 0.);
2204  evec_basis[2] = ThreeVector(0., 1., 0.);
2205  evec_basis[0] = ThreeVector(0., 0., 1.);
2206  } else {
2207  evec_basis[1] = ThreeVector(0., 1., 0.);
2208  evec_basis[2] = ThreeVector(1., 0., 0.);
2209  evec_basis[0] = ThreeVector(0., 0., -1.);
2210  }
2211  }
2212 
2213  assert(std::fabs(evec_basis[1] * evec_basis[2]) < really_small);
2214  assert(std::fabs(evec_basis[2] * evec_basis[0]) < really_small);
2215  assert(std::fabs(evec_basis[0] * evec_basis[1]) < really_small);
2216 }
2217 
2219  PPosA_ = (pcom_[0].x0() + evecBasisAB_[0] * pcom_[0].threevec()) * M_SQRT1_2;
2220  PNegA_ = (pcom_[0].x0() - evecBasisAB_[0] * pcom_[0].threevec()) * M_SQRT1_2;
2221  PPosB_ = (pcom_[1].x0() + evecBasisAB_[0] * pcom_[1].threevec()) * M_SQRT1_2;
2222  PNegB_ = (pcom_[1].x0() - evecBasisAB_[0] * pcom_[1].threevec()) * M_SQRT1_2;
2223 }
2224 
2225 void StringProcess::quarks_from_diquark(int diquark, int& q1, int& q2,
2226  int& deg_spin) {
2227  // The 4-digit pdg id should be diquark.
2228  assert((std::abs(diquark) > 1000) && (std::abs(diquark) < 5510) &&
2229  (std::abs(diquark) % 100 < 10));
2230 
2231  // The fourth digit corresponds to the spin degeneracy.
2232  deg_spin = std::abs(diquark) % 10;
2233  // Diquark (anti-diquark) is decomposed into two quarks (antiquarks).
2234  const int sign_anti = diquark > 0 ? 1 : -1;
2235 
2236  // Obtain two quarks (or antiquarks) from the first and second digit.
2237  q1 = sign_anti * (std::abs(diquark) - (std::abs(diquark) % 1000)) / 1000;
2238  q2 = sign_anti * (std::abs(diquark) % 1000 - deg_spin) / 100;
2239 }
2240 
2242  assert((q1 > 0 && q2 > 0) || (q1 < 0 && q2 < 0));
2243  if (std::abs(q1) < std::abs(q2)) {
2244  std::swap(q1, q2);
2245  }
2246  int diquark = std::abs(q1 * 1000 + q2 * 100);
2247  /* Adding spin degeneracy = 2S+1. For identical quarks spin cannot be 0
2248  * because of Pauli exclusion principle, so spin 1 is assumed. Otherwise
2249  * S = 0 with probability 1/4 and S = 1 with probability 3/4. */
2250  diquark += (q1 != q2 && random::uniform_int(0, 3) == 0) ? 1 : 3;
2251  return (q1 < 0) ? -diquark : diquark;
2252 }
2253 
2254 void StringProcess::make_string_ends(const PdgCode& pdg, int& idq1, int& idq2,
2255  double xi) {
2256  std::array<int, 3> quarks = pdg.quark_content();
2257  if (pdg.is_nucleon()) {
2258  // protons and neutrons treated seperately since single quarks is at a
2259  // different position in the PDG code
2260  if (pdg.charge() == 0) { // (anti)neutron
2261  if (random::uniform(0., 1.) < xi) {
2262  idq1 = quarks[0];
2263  idq2 = diquark_from_quarks(quarks[1], quarks[2]);
2264  } else {
2265  idq1 = quarks[1];
2266  idq2 = diquark_from_quarks(quarks[0], quarks[2]);
2267  }
2268  } else { // (anti)proton
2269  if (random::uniform(0., 1.) < xi) {
2270  idq1 = quarks[2];
2271  idq2 = diquark_from_quarks(quarks[0], quarks[1]);
2272  } else {
2273  idq1 = quarks[0];
2274  idq2 = diquark_from_quarks(quarks[1], quarks[2]);
2275  }
2276  }
2277  } else {
2278  if (pdg.is_meson()) {
2279  idq1 = quarks[1];
2280  idq2 = quarks[2];
2281  /* Some mesons with PDG id 11X are actually mixed state of uubar and
2282  * ddbar. have a random selection whether we have uubar or ddbar in this
2283  * case. */
2284  if (idq1 == 1 && idq2 == -1 && random::uniform_int(0, 1) == 0) {
2285  idq1 = 2;
2286  idq2 = -2;
2287  }
2288  } else {
2289  assert(pdg.is_baryon());
2290  // Get random quark to position 0
2291  std::swap(quarks[random::uniform_int(0, 2)], quarks[0]);
2292  idq1 = quarks[0];
2293  idq2 = diquark_from_quarks(quarks[1], quarks[2]);
2294  }
2295  }
2296  // Fulfil the convention: idq1 should be quark or anti-diquark
2297  if (idq1 < 0) {
2298  std::swap(idq1, idq2);
2299  }
2300 }
2301 
2303  double suppression_factor) {
2304  int nbaryon = data.pdgcode().baryon_number();
2305  if (nbaryon == 0) {
2306  // Mesons always get a scaling factor of 1/2 since there is never
2307  // a q-qbar pair at the end of a string so nquark is always 1
2308  data.set_cross_section_scaling_factor(0.5 * suppression_factor);
2309  } else if (data.is_baryon()) {
2310  // Leading baryons get a factor of 2/3 if they carry 2
2311  // and 1/3 if they carry 1 of the strings valence quarks
2312  data.set_cross_section_scaling_factor(suppression_factor * nquark /
2313  (3.0 * nbaryon));
2314  }
2315 }
2316 
2317 std::pair<int, int> StringProcess::find_leading(int nq1, int nq2,
2318  ParticleList& list) {
2319  assert(list.size() >= 2);
2320  int end = list.size() - 1;
2321  int i1, i2;
2322  for (i1 = 0;
2323  i1 <= end && !list[i1].pdgcode().contains_enough_valence_quarks(nq1);
2324  i1++) {
2325  }
2326  for (i2 = end;
2327  i2 >= 0 && !list[i2].pdgcode().contains_enough_valence_quarks(nq2);
2328  i2--) {
2329  }
2330  std::pair<int, int> indices(i1, i2);
2331  return indices;
2332 }
2333 
2335  ParticleList& outgoing_particles,
2336  const ThreeVector& evecLong,
2337  double suppression_factor) {
2338  // Set each particle's cross section scaling factor to 0 first
2339  for (ParticleData& data : outgoing_particles) {
2340  data.set_cross_section_scaling_factor(0.0);
2341  }
2342  // sort outgoing particles according to the longitudinal velocity
2343  std::sort(outgoing_particles.begin(), outgoing_particles.end(),
2344  [&](ParticleData i, ParticleData j) {
2345  return i.momentum().velocity() * evecLong >
2346  j.momentum().velocity() * evecLong;
2347  });
2348  int nq1, nq2; // number of quarks at both ends of the string
2349  switch (baryon_string) {
2350  case 0:
2351  nq1 = -1;
2352  nq2 = 1;
2353  break;
2354  case 1:
2355  nq1 = 2;
2356  nq2 = 1;
2357  break;
2358  case -1:
2359  nq1 = -2;
2360  nq2 = -1;
2361  break;
2362  default:
2363  throw std::runtime_error("string is neither mesonic nor baryonic");
2364  }
2365  // Try to find nq1 on one string end and nq2 on the other string end and the
2366  // other way around. When the leading particles are close to the string
2367  // ends, the quarks are assumed to be distributed this way.
2368  std::pair<int, int> i = find_leading(nq1, nq2, outgoing_particles);
2369  std::pair<int, int> j = find_leading(nq2, nq1, outgoing_particles);
2370  if (baryon_string == 0 && i.second - i.first < j.second - j.first) {
2371  assign_scaling_factor(nq2, outgoing_particles[j.first], suppression_factor);
2372  assign_scaling_factor(nq1, outgoing_particles[j.second],
2373  suppression_factor);
2374  } else {
2375  assign_scaling_factor(nq1, outgoing_particles[i.first], suppression_factor);
2376  assign_scaling_factor(nq2, outgoing_particles[i.second],
2377  suppression_factor);
2378  }
2379 }
2380 
2382  PdgCode pdg_mapped(0x0);
2383 
2384  if (pdg.baryon_number() == 1) { // baryon
2385  pdg_mapped = pdg.charge() > 0 ? PdgCode(pdg::p) : PdgCode(pdg::n);
2386  } else if (pdg.baryon_number() == -1) { // antibaryon
2387  pdg_mapped = pdg.charge() < 0 ? PdgCode(-pdg::p) : PdgCode(-pdg::n);
2388  } else if (pdg.is_hadron()) { // meson
2389  if (pdg.charge() >= 0) {
2390  pdg_mapped = PdgCode(pdg::pi_p);
2391  } else {
2392  pdg_mapped = PdgCode(pdg::pi_m);
2393  }
2394  } else if (pdg.is_lepton()) { // lepton
2395  pdg_mapped = pdg.charge() < 0 ? PdgCode(0x11) : PdgCode(-0x11);
2396  } else {
2397  throw std::runtime_error("StringProcess::pdg_map_for_pythia failed.");
2398  }
2399 
2400  return pdg_mapped.get_decimal();
2401 }
2402 
2403 } // namespace smash
Interface to the SMASH configuration files.
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
double x0() const
Definition: fourvector.h:313
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
bool is_baryon() const
Definition: particledata.h:95
void set_formation_time(double form_time)
Set the absolute formation time.
Definition: particledata.h:264
void set_cross_section_scaling_factor(const double &xsec_scal)
Set the particle's initial cross_section_scaling_factor.
Definition: particledata.h:313
static const ParticleType & find(PdgCode pdgcode)
Returns the ParticleType object for the given pdgcode.
Definition: particletype.cc:99
static const ParticleTypeList & list_all()
Definition: particletype.cc:51
double mass() const
Definition: particletype.h:147
PdgCode stores a Particle Data Group Particle Numbering Scheme particle type number.
Definition: pdgcode.h:108
int baryon_number() const
Definition: pdgcode.h:388
bool is_meson() const
Definition: pdgcode.h:401
int net_quark_number(const int quark) const
Returns the net number of quarks with given flavour number For public use, see strangeness(),...
Definition: pdgcode.cc:62
std::array< int, 3 > quark_content() const
The return is always an array of three numbers, which are pdgcodes of quarks: 1 - d,...
Definition: pdgcode.h:754
bool is_lepton() const
Definition: pdgcode.h:372
static PdgCode from_decimal(const int pdgcode_decimal)
Construct PDG code from decimal number.
Definition: pdgcode.h:339
int32_t get_decimal() const
Definition: pdgcode.h:852
bool is_baryon() const
Definition: pdgcode.h:398
bool is_nucleon() const
Definition: pdgcode.h:404
bool is_hadron() const
Definition: pdgcode.h:367
int charge() const
The charge of the particle.
Definition: pdgcode.h:650
Pythia8::StringFlav pythia_stringflav_
An object for the flavor selection in string fragmentation in the case of separate fragmentation func...
bool next_SDiff(bool is_AB_to_AX)
Single-diffractive process is based on single pomeron exchange described in Ingelman:1984ns .
pythia_map hard_map_
Map object to contain the different pythia objects.
Pythia8::SigmaTotal pythia_sigmatot_
An object to compute cross-sections.
double pow_fgluon_beta_
parameter for the gluon distribution function
Definition: stringprocess.h:88
@ LeadingQuark
Custom status assigned to leading (valence) quarks.
@ NonLeadingParton
Standard PYTHIA status for non-leading partons that should be hadronized.
@ LeadingDiquark
Standard PYTHIA beam-remnant status used for leading diquarks.
std::vector< Pythia8::Event > string_parton_events_
PYTHIA event records containing string partons to be hadronized.
Pythia8::Event event_intermediate_
event record for intermediate partonic state in the hard string routine
std::array< PdgCode, 2 > PDGcodes_
PdgCodes of incoming particles.
Definition: stringprocess.h:68
double time_formation_const_
constant proper time in the case of constant formation time [fm]
double PNegB_
backward lightcone momentum p^{-} of incoming particle B in CM-frame [GeV]
Definition: stringprocess.h:60
static FourVector make_smash_4vec(const Pythia8::Vec4 &p)
Convert a PYTHIA four-vector into a SMASH four-vector.
void find_junction_leg(bool sign_color, std::vector< int > &col, Pythia8::Event &event_intermediate, Pythia8::Event &event_hadronize)
Identify partons, which are associated with junction legs, from a given PYTHIA event record.
double pow_fquark_beta_
parameter for the quark distribution function
Definition: stringprocess.h:98
bool next_NDiffSoft()
Soft Non-diffractive process is modelled in accordance with dual-topological approach Capella:1978ig ...
bool next_DDiff()
Double-diffractive process ( A + B -> X + X ) is similar to the single-diffractive process,...
static int pdg_map_for_pythia(PdgCode &pdg)
Take pdg code and map onto particle specie which can be handled by PYTHIA.
double prob_proton_to_d_uu_
Probability of splitting a nucleon into the quark flavour it has only once and a diquark it has twice...
Pythia8::RotBstMatrix to_cm_
Rotation/boost matrix to transform particles to the center-of-mass frame.
FourVector ucomAB_
velocity four vector of the center of mass in the lab frame
Definition: stringprocess.h:74
ThreeVector vcomAB_
velocity three vector of the center of mass in the lab frame
Definition: stringprocess.h:76
void compute_incoming_lightcone_momenta()
compute the lightcone momenta of incoming particles where the longitudinal direction is set to be sam...
double popcorn_rate_
popcorn rate
double pmin_gluon_lightcone_
the minimum lightcone momentum scale carried by a gluon [GeV]
Definition: stringprocess.h:83
bool splitting_gluon_qqbar(Pythia8::Event &event_intermediate, std::array< int, 5 > &nquark_total, std::array< int, 5 > &nantiq_total, bool sign_constituent, std::array< std::array< int, 5 >, 2 > &excess_constituent)
Take total number of quarks and check if the system has enough constituents that need to be converted...
static std::pair< int, int > find_leading(int nq1, int nq2, ParticleList &list)
Find the leading string fragments.
double PPosA_
forward lightcone momentum p^{+} of incoming particle A in CM-frame [GeV]
Definition: stringprocess.h:51
std::optional< ParticleList > hadronize(const Pythia8::Event &string_evt)
Hadronize a single partonic string configuration using Pythia8 and convert the produced hadrons into ...
double sqrtsAB_
sqrt of Mandelstam variable s of collision [GeV]
Definition: stringprocess.h:66
ParticleList final_state_
final state array which must be accessed after the collision
bool is_leading_from_quark(const Pythia8::Particle &p)
Check whether a particle is tagged as originating from a leading quark.
double PNegA_
backward lightcone momentum p^{-} of incoming particle A in CM-frame [GeV]
Definition: stringprocess.h:57
bool append_string(const Pythia8::Vec4 &p_str, const std::array< int, 2 > &ends, int color_tag, bool use_projectile_axis, bool random_flip_of_endpoints=false)
Append a single two-endpoint string as an independent PYTHIA event.
bool use_monash_tune_
Whether to use the monash tune Skands:2014pea for all string processes.
double soft_t_form_
factor to be multiplied to formation times in soft strings
static void make_string_ends(const PdgCode &pdgcode_in, int &idq1, int &idq2, double xi)
make a random selection to determine partonic contents at the string ends.
std::unique_ptr< Pythia8::Pythia > pythia_hadron_
PYTHIA object used in fragmentation.
double massA_
mass of incoming particle A [GeV]
Definition: stringprocess.h:62
bool next_Hard(ProcessType type)
Hard Non-diffractive process is based on PYTHIA 8 with partonic showers and interactions.
void common_setup_pythia(Pythia8::Pythia *pythia_in, double strange_supp, double diquark_supp, double popcorn_rate, double stringz_a, double stringz_b, double string_sigma_T)
Common setup of PYTHIA objects for soft and hard string routines.
double PPosB_
forward lightcone momentum p^{+} of incoming particle B in CM-frame [GeV]
Definition: stringprocess.h:54
std::array< ThreeVector, 3 > evecBasisAB_
Orthonormal basis vectors in the center of mass frame, where the 0th one is parallel to momentum of i...
Definition: stringprocess.h:81
std::vector< std::string > pythia_settings_
Additional Pythia 8 settings passed to each internal Pythia instance.
std::optional< double > mpi_initialization_sqrts_
Optional center-of-mass energy used to initialize MPI-capable Pythia objects.
bool restore_constituent(Pythia8::Event &event_intermediate, std::array< std::array< int, 5 >, 2 > &excess_quark, std::array< std::array< int, 5 >, 2 > &excess_antiq)
Take the intermediate partonic state from PYTHIA event with mapped hadrons and convert constituents i...
double string_sigma_T_
transverse momentum spread in string fragmentation
static void make_orthonormal_basis(ThreeVector &evec_polar, std::array< ThreeVector, 3 > &evec_basis)
compute three orthonormal basis vectors from unit vector in the longitudinal direction
int get_index_forward(bool find_forward, int np_end, Pythia8::Event &event)
Obtain index of the most forward or backward particle in a given PYTHIA event record.
bool is_leading_parton(const Pythia8::Particle &p)
Check whether a particle is tagged as a leading parton.
static void convert_KaonLS(int &pythia_id)
convert Kaon-L or Kaon-S into K0 or Anti-K0
double additional_xsec_supp_
additional cross-section suppression factor to take coherence effect into account.
static void assign_all_scaling_factors(int baryon_string, ParticleList &outgoing_particles, const ThreeVector &evecLong, double suppression_factor)
Assign a cross section scaling factor to all outgoing particles.
double damp_popcorn_
damp popcorn meson from diquark remnant endpoint rate
double strange_supp_
strange quark suppression factor
bool string_above_threshold(const Pythia8::Event &event)
Check whether all strings in a PYTHIA event are above fragmentation threshold.
static int diquark_from_quarks(int q1, int q2)
Construct diquark from two quarks.
std::vector< bool > compute_beam_valence_flags(Pythia8::Pythia &pythia)
Compute flags identifying beam valence partons (quarks or diquarks) that act as leading partons after...
void replace_constituent(Pythia8::Particle &particle, std::array< int, 5 > &excess_constituent)
Convert a partonic PYTHIA particle into the desired species and update the excess of constituents.
double kappa_tension_string_
string tension [GeV/fm]
double pow_fquark_alpha_
parameter for the quark distribution function
Definition: stringprocess.h:93
bool is_leading(const Pythia8::Particle &p)
Check whether a particle is tagged as originating from a leading endpoint.
double stringz_b_leading_
parameter (StringZ:bLund) for the fragmentation function of leading baryon in soft non-diffractive st...
static bool append_intermediate_list(int pdgid, FourVector momentum, ParticleList &intermediate_particles)
append new particle from PYTHIA to a specific particle list
int leading_hadron_status_from_endpoint(const Pythia8::Particle &end)
Determine the custom leading-hadron status code from a string endpoint.
bool next(ProcessType type)
Generate the next string process for a given process type.
bool mass_dependent_formation_times_
Whether the formation time should depend on the mass of the fragment according to Andersson:1983ia e...
double stringz_b_produce_
parameter (StringZ:bLund) for the fragmentation function of other (produced) hadrons in soft non-diff...
bool next_BBbarAnn()
Baryon-antibaryon annihilation process Based on what UrQMD Bass:1998ca , Bleicher:1999xi does,...
void set_color_by_type(Pythia8::Particle &p, int color)
Set the color or anticolor index of a particle according to its type.
StringProcess(Configuration &config)
Constructor, initializes PYTHIA.
void tag_leading_hadrons(Pythia8::Event &event)
Tag leading hadrons in a hadronized string.
double stringz_a_leading_
parameter (StringZ:aLund) for the fragmentation function of leading baryon in soft non-diffractive st...
void init(const ParticleList &incoming, double tcoll)
initialization feed intial particles, time of collision and gamma factor of the center of mass.
bool is_leading_from_diquark(const Pythia8::Particle &p)
Check whether a particle is tagged as originating from a leading diquark.
double sigma_qperp_
Transverse momentum spread of the excited strings.
void rearrange_excess(std::array< int, 5 > &nquark_total, std::array< std::array< int, 5 >, 2 > &excess_quark, std::array< std::array< int, 5 >, 2 > &excess_antiq)
Take total number of quarks and check if the system has enough constituents that need to be converted...
static Pythia8::Vec4 make_pythia_4vec(const FourVector &p)
Convert a SMASH four-vector into a PYTHIA four-vector.
bool separate_fragment_baryon_
Whether to use a separate fragmentation function for leading baryons.
std::array< FourVector, 2 > plab_
momenta of incoming particles in the lab frame [GeV]
Definition: stringprocess.h:70
std::array< FourVector, 2 > pcom_
momenta of incoming particles in the center of mass frame [GeV]
Definition: stringprocess.h:72
double time_collision_
time of collision in the computational frame [fm]
void form_intermediate_particles(ParticleList &intermediate_particles, const FourVector &pString, const ThreeVector &evecLong, double additional_xsec_supp=1.0, bool find_and_scale_leading=true)
Set formation times and cross-section scaling factors for fragmented hadrons as described in Andersso...
double diquark_supp_
diquark suppression factor
static void find_excess_constituent(PdgCode &pdg_actual, PdgCode &pdg_mapped, std::array< int, 5 > &excess_quark, std::array< int, 5 > &excess_antiq)
Compare the valence quark contents of the actual and mapped hadrons and evaluate how many more consti...
double estimate_string_threshold(int p_left, int p_right)
Estimate the minimum invariant mass required for a string to fragment.
double massB_
mass of incoming particle B [GeV]
Definition: stringprocess.h:64
void compose_string_parton(bool find_forward_string, Pythia8::Event &event_intermediate, Pythia8::Event &event_hadronize)
Identify a set of partons, which are connected to form a color-neutral string, from a given PYTHIA ev...
void find_total_number_constituent(Pythia8::Event &event_intermediate, std::array< int, 5 > &nquark_total, std::array< int, 5 > &nantiq_total)
Compute how many quarks and antiquarks we have in the system, and update the correspoing arrays with ...
double stringz_a_produce_
parameter (StringZ:aLund) for the fragmentation function of other (produced) hadrons in soft non-diff...
static void assign_scaling_factor(int nquark, ParticleData &data, double suppression_factor)
Assign a cross section scaling factor to the given particle.
void compose_string_junction(bool &find_forward_string, Pythia8::Event &event_intermediate, Pythia8::Event &event_hadronize)
Identify a set of partons and junction(s), which are connected to form a color-neutral string,...
static void quarks_from_diquark(int diquark, int &q1, int &q2, int &deg_spin)
find two quarks from a diquark.
The ThreeVector class represents a physical three-vector with the components .
Definition: threevector.h:31
double abs() const
Definition: threevector.h:277
double sqr() const
Definition: threevector.h:275
double x3() const
Definition: threevector.h:194
double x2() const
Definition: threevector.h:190
double x1() const
Definition: threevector.h:186
Discrete distribution with weight given by probability vector.
Definition: random.h:302
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.h:245
constexpr int pi_p
π⁺.
constexpr int p
Proton.
constexpr int n
Neutron.
constexpr int pi_m
π⁻.
T power(T n, T xMin, T xMax)
Sample from a power-law probability density proportional to |x|^n.
Definition: random.h:229
T beta_a0(T xmin, T b)
Draws a random number from a beta-distribution with a = 0.
Definition: random.h:395
T beta(T a, T b)
Draws a random number from a beta-distribution, where probability density of is .
Definition: random.h:373
double normal(const T &mean, const T &sigma)
Returns a random number drawn from a normal distribution.
Definition: random.h:294
T uniform_int(T min, T max)
Definition: random.h:106
T uniform(T min, T max)
Definition: random.h:91
T canonical()
Definition: random.h:122
Definition: action.h:24
T pCM(const T sqrts, const T mass_a, const T mass_b) noexcept
Definition: kinematics.h:79
T pCM_sqr(const T sqrts, const T mass_a, const T mass_b) noexcept
Definition: kinematics.h:91
constexpr int maximum_rndm_seed_in_pythia
The maximum value of the random seed used in PYTHIA.
Definition: constants.h:114
ProcessType
ProcessTypes are used to identify the type of the process.
Definition: processbranch.h:39
@ StringHardSingleDiffractiveAX
See here for a short description.
@ StringSoftDoubleDiffractive
See here for a short description.
@ StringSoftSingleDiffractiveXB
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.
std::string to_string(ThermodynamicQuantity quantity)
Convert a ThermodynamicQuantity enum value to its corresponding string.
Definition: stringify.cc:26
constexpr double pion_mass
Pion mass in GeV.
Definition: constants.h:76
constexpr double really_small
Numerical error tolerance.
Definition: constants.h:41
constexpr double kaon_mass
Kaon mass in GeV.
Definition: constants.h:83
static constexpr int LPythia
Definition: stringprocess.h:27
A container to keep track of all ever existed input keys.
Definition: input_keys.h:1255