StRoot  1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
NucleonExcitations.cc
1 // NucleonExcitations.cc is a part of the PYTHIA event generator.
2 // Copyright (C) 2020 Torbjorn Sjostrand.
3 // PYTHIA is licenced under the GNU GPL v2 or later, see COPYING for details.
4 // Please respect the MCnet Guidelines, see GUIDELINES for details.
5 
6 // Function definitions (not found in the header) for NucleonExcitations class.
7 
8 #include "Pythia8/NucleonExcitations.h"
9 
10 namespace Pythia8 {
11 
12 //==========================================================================
13 
14 // The NucleonExcitations class.
15 
16 //--------------------------------------------------------------------------
17 
18 static double pCMS(double eCM, double mA, double mB) {
19  if (eCM <= mA + mB) return 0;
20  double sCM = eCM * eCM;
21  return sqrt((sCM - pow2(mA + mB)) * (sCM - pow2(mA - mB))) / (2. * eCM);
22 }
23 
24 static string attributeValue(string line, string attribute) {
25  if (line.find(attribute) == string::npos) return "";
26  int iBegAttri = line.find(attribute);
27  int iBegQuote = line.find("\"", iBegAttri + 1);
28  int iEndQuote = line.find("\"", iBegQuote + 1);
29  return line.substr(iBegQuote + 1, iEndQuote - iBegQuote - 1);
30 
31 }
32 
33 static int intAttributeValue(string line, string attribute) {
34  string valString = attributeValue(line, attribute);
35  if (valString == "") return 0;
36  istringstream valStream(valString);
37  int intVal;
38  valStream >> intVal;
39  return intVal;
40 }
41 
42 static double doubleAttributeValue(string line, string attribute) {
43  string valString = attributeValue(line, attribute);
44  if (valString == "") return 0.;
45  istringstream valStream(valString);
46  double doubleVal;
47  valStream >> doubleVal;
48  return doubleVal;
49 }
50 
51 static void completeTag(istream& stream, string& line) {
52  while (line.find(">") == string::npos) {
53  string addLine;
54  if (!getline(stream, addLine)) break;
55  line += " " + addLine;
56  }
57 }
58 
59 //--------------------------------------------------------------------------
60 
61 // Read in excitation data from the specified file.
62 
63 bool NucleonExcitations::init(string path) {
64 
65  ifstream stream(path);
66  if (!stream.is_open()) {
67  infoPtr->errorMsg( "Error in NucleonExcitations::init: "
68  "unable to open file", path);
69  return false;
70  }
71 
72  return init(stream);
73 }
74 
75 //--------------------------------------------------------------------------
76 
77 // Read in excitation data from the specified stream.
78 
79 bool NucleonExcitations::init(istream& stream) {
80 
81  // Lower bound, needed for total cross section parameterization.
82  double eMin = INFINITY;
83 
84  // Read header info.
85  string line;
86  if (!getline(stream, line)) {
87  infoPtr->errorMsg("Error in NucleonExcitations::init: "
88  "unable to read file");
89  return false;
90  }
91 
92  string word1;
93  istringstream(line) >> word1;
94 
95  if (word1 != "<header") {
96  infoPtr->errorMsg("Error in NucleonExcitations::init: header missing");
97  return false;
98  }
99  completeTag(stream, line);
100 
101  // Configuration to use when parameterizing total cross section.
102  double highEnergyThreshold = doubleAttributeValue(line, "threshold");
103  int sigmaTotalPrecision = intAttributeValue(line, "sigmaTotalPrecision");
104 
105  // Process each line sequentially.
106  while (getline(stream, line)) {
107 
108  if (!(istringstream(line) >> word1))
109  continue;
110 
111  if (word1 == "<excitationChannel") {
112  completeTag(stream, line);
113 
114  // Read channel data.
115  int maskA = intAttributeValue(line, "maskA");
116  int maskB = intAttributeValue(line, "maskB");
117  double left = doubleAttributeValue(line, "left");
118  double right = doubleAttributeValue(line, "right");
119  double scaleFactor = doubleAttributeValue(line, "scaleFactor");
120 
121  istringstream dataStr(attributeValue(line, "data"));
122  vector<double> data;
123  double currentData;
124  while (dataStr >> currentData)
125  data.push_back(currentData);
126 
127  // Update eMin if needed.
128  if (eMin > left)
129  eMin = left;
130 
131  // Add channel to the list.
132  excitationChannels.push_back(ExcitationChannel {
133  LinearInterpolator(left, right, data), maskA, maskB, scaleFactor });
134  }
135  }
136 
137  // Pre-sum sigmas to create one parameterization for the total sigma.
138  vector<double> sigmaTotPts(sigmaTotalPrecision);
139  double dE = (highEnergyThreshold - eMin) / (sigmaTotalPrecision - 1);
140  for (int i = 0; i < sigmaTotalPrecision; ++i) {
141  double eCM = eMin + i * dE;
142  double sigma = 0.;
143  for (auto& channel : excitationChannels)
144  sigma += channel.sigma(eCM);
145  sigmaTotPts[i] = sigma;
146  }
147  sigmaTotal = LinearInterpolator(eMin, highEnergyThreshold, sigmaTotPts);
148 
149  // Done.
150  return true;
151 }
152 
153 //--------------------------------------------------------------------------
154 
155 // Validate that the loaded data makes sense.
156 
157 bool NucleonExcitations::check() {
158 
159  // Check that all excitations make sense.
160  for (auto excitationChannel : excitationChannels) {
161  // Check that ids actually correspond to particles.
162  for (int mask : { excitationChannel.maskA, excitationChannel.maskB })
163  for (int id : { mask + 2210, mask + 2110 })
164  if (!particleDataPtr->isParticle(id)) {
165  infoPtr->errorMsg("Error in HadronWidths::check: "
166  "excitation is not a particle", std::to_string(id));
167  return false;
168  }
169  }
170  return true;
171 }
172 
173 //--------------------------------------------------------------------------
174 
175 // Pick an excitation and mass distribution for the specified particles.
176 
177 bool NucleonExcitations::pickExcitation(int idA, int idB, double eCM,
178  int& idCOut, double& mCOut, int& idDOut, double& mDOut) {
179 
180  // Excitations are available only for nucleons.
181  if (!(abs(idA) == 2112 || abs(idA) == 2212)
182  || !(abs(idB) == 2112 || abs(idB) == 2212)) {
183  infoPtr->errorMsg("Error in NucleonExcitations:pickExcitation: "
184  "excitations are only available for NN collisions");
185  return false;
186  }
187 
188  // If antiparticles, flip signs and flip back at the end.
189  int signA = (idA > 0 ? 1 : -1), signB = (idB > 0 ? 1 : -1);
190  idA *= signA;
191  idB *= signB;
192 
193  // Pick an excitation channel.
194  vector<double> sigmas(excitationChannels.size());
195  for (int i = 0; i < int(sigmas.size()); ++i) {
196  // Below threshold, use parameterization.
197  if (eCM < excitationChannels[i].sigma.right())
198  sigmas[i] = excitationChannels[i].sigma(eCM);
199  // Above threshold, use approximation (ignoring incoming phase space).
200  else {
201  double mA = particleDataPtr->m0(2210 + excitationChannels[i].maskA);
202  double mB = particleDataPtr->m0(2210 + excitationChannels[i].maskB);
203  sigmas[i] = pCMS(eCM, mA, mB) * excitationChannels[i].scaleFactor;
204  }
205  }
206  auto& channel = excitationChannels[rndmPtr->pick(sigmas)];
207 
208  // The two nucleons have equal chance of becoming excited.
209  int maskA = channel.maskA, maskB = channel.maskB;
210  if (rndmPtr->flat() > 0.5)
211  swap(maskA, maskB);
212 
213  // Construct ids of resonances from masks plus incoming quark content.
214  int idCtmp = maskA + (idA - idA % 10);
215  int idDtmp = maskB + (idB - idB % 10);
216 
217  // Pick masses.
218  double mCtmp, mDtmp;
219  if (!hadronWidthsPtr->pickMasses(idCtmp, idDtmp, eCM, mCtmp, mDtmp)) {
220  infoPtr->errorMsg("Error in NucleonExcitations::pickExcitation: "
221  "failed picking masses",
222  "(for " + to_string(idA) + " + " + to_string(idB) + " --> "
223  + to_string(idCtmp) + " + " + to_string(idDtmp) + ")");
224  return false;
225  }
226 
227  // Set output values and return.
228  idCOut = signA * idCtmp;
229  idDOut = signB * idDtmp;
230  mCOut = mCtmp;
231  mDOut = mDtmp;
232  return true;
233 }
234 
235 //--------------------------------------------------------------------------
236 
237 // Get total excitation cross sections for NN at the specified energy.
238 
239 double NucleonExcitations::sigmaExTotal(double eCM) const {
240  // Below threshold, use parameterization.
241  if (eCM < sigmaTotal.right())
242  return sigmaTotal(eCM);
243  // Above threshold, sum approximated integrals.
244  else {
245  double sig = 0.;
246  for (auto channel : excitationChannels) {
247  double mA = particleDataPtr->m0(2210 + channel.maskA);
248  double mB = particleDataPtr->m0(2210 + channel.maskB);
249  sig += channel.scaleFactor * pCMS(eCM, mA, mB);
250  }
251 
252  // Average over incoming phase space.
253  return sig / pCMS(eCM, 0.938, 0.938) / pow2(eCM);
254  }
255 }
256 
257 //--------------------------------------------------------------------------
258 
259  // Get cross section for NN -> CD. Quark content in masks is ignored.
260 
261 double NucleonExcitations::sigmaExPartial(double eCM,
262  int maskC, int maskD) const {
263 
264  // Remove quark content from masks.
265  maskC = maskC - 10 * ((maskC / 10) % 1000);
266  maskD = maskD - 10 * ((maskD / 10) % 1000);
267 
268  // Ensure ordering is ND, NX* or DX*.
269  if (maskD == 0002 || (maskD == 0004 && maskC > 0004))
270  swap(maskC, maskD);
271 
272  // Find the corresponding channel.
273  for (auto& channel : excitationChannels)
274  if (channel.maskA == maskC && channel.maskB == maskD) {
275  // At low energy, use interpolation.
276  if (eCM < channel.sigma.right())
277  return channel.sigma(eCM);
278 
279  // At high energy, use parameterization.
280  double mA = particleDataPtr->m0(2210 + channel.maskA);
281  double mB = particleDataPtr->m0(2210 + channel.maskB);
282  return channel.scaleFactor / pow2(eCM)
283  * pCMS(eCM, mA, mB) / pCMS(eCM, 0.938, 0.938);
284  }
285 
286  // Cross section is zero if channel does not exist.
287  return 0.;
288 }
289 
290 //--------------------------------------------------------------------------
291 
292 // Get masks (ids without quark content) for all implemented cross sections.
293 
294 vector<pair<int, int>> NucleonExcitations::getChannels() const {
295  vector<pair<int, int>> result;
296  for (auto channel : excitationChannels)
297  result.push_back(make_pair(channel.maskA, channel.maskB));
298  return result;
299 }
300 
301 //--------------------------------------------------------------------------
302 
303 // Get all nucleon excitations from particle data.
304 
305 vector<int> NucleonExcitations::getExcitationMasks() const {
306 
307  vector<int> results;
308  for (auto& kvPair : *particleDataPtr) {
309  int id = kvPair.first;
310  int quarkContent = ((id / 10) % 1000);
311  int mask = id - 10 * quarkContent;
312 
313  // Check quark content to make sure each mask is included only once.
314  if ( ((mask == 0004) || (mask >= 10000 && mask < 1000000))
315  && quarkContent == 221 )
316  results.push_back(mask);
317  }
318 
319  return results;
320 }
321 
322 //--------------------------------------------------------------------------
323 
324 // Calculate partial excitation cross section without using interpolation.
325 
326 double NucleonExcitations::sigmaCalc(double eCM, int maskC, int maskD) const {
327 
328  // Convert masks to particle ids.
329  int quarkContentC = (maskC / 10) % 1000, quarkContentD = (maskD / 10) % 1000;
330  maskC -= 10 * quarkContentC;
331  maskD -= 10 * quarkContentD;
332  ParticleDataEntry* entryC = particleDataPtr->findParticle(2210 + maskC);
333  ParticleDataEntry* entryD = particleDataPtr->findParticle(2210 + maskD);
334 
335  // No cross section below threshold.
336  if (eCM < entryC->mMin() + entryD->mMin())
337  return 0.;
338 
339  // Calculate matrix element, based on method by UrQMD.
340  double matrixElement;
341  if (maskC == 0002 && maskD == 0004) {
342  constexpr double A = 40000, mD2 = pow2(1.232), GammaD2 = pow2(0.115);
343  matrixElement = A * mD2 * GammaD2 /
344  (pow2(eCM * eCM - mD2) + mD2 * GammaD2);
345  }
346  else if (maskC == 0004 && maskD == 0004)
347  matrixElement = 2.8;
348  else {
349  double mD = particleDataPtr->m0(2210 + maskD);
350  double mC, A;
351  if (maskC == 0002) {
352  mC = 0.938;
353  if (particleDataPtr->isParticle(2220 + maskD))
354  A = 12.0;
355  else
356  A = 6.3;
357  }
358  else {
359  mC = 1.232;
360  A = 3.5;
361  }
362 
363  matrixElement = A / (pow2(mD - mC) * pow2(mD + mC));
364  }
365 
366  // Return cross section.
367  return entryC->spinType() * entryD->spinType() * matrixElement
368  * psSize(eCM, *entryC, *entryD) / pCMS(eCM, 0.938, 0.938) / pow2(eCM);
369 }
370 
371 //--------------------------------------------------------------------------
372 
373 // Regenerate parameterization for all cross sections.
374 
375 bool NucleonExcitations::parameterizeAll(int precision, double threshold) {
376 
377  if (precision <= 1){
378  infoPtr->errorMsg("Error in NucleonExcitations::parameterizeAll: "
379  "precision must be at least 2");
380  return false;
381  }
382 
383  double mN = particleDataPtr->m0(2212), mD = particleDataPtr->m0(2214);
384 
385  // Calculate high energy scale factor for nucleons and Delta(1232).
386  double scaleFactorN = 2.;
387 
388  double scaleFactorD;
389  bool check = integrateGauss(scaleFactorD, [&](double m) {
390  return hadronWidthsPtr->mDistr(2214, m);
391  }, particleDataPtr->mMin(2214), particleDataPtr->mMax(2214));
392  if (!check) {
393  infoPtr->errorMsg("Abort from NucleonExcitations::parameterizeAll: "
394  "unable to integrate excitation mass distribution", "2214");
395  return false;
396  }
397  scaleFactorD *= 4;
398 
399  // Create new excitation channels.
400  excitationChannels.clear();
401  for (auto maskEx : getExcitationMasks()) {
402 
403  int idEx = 2210 + maskEx;
404  infoPtr->errorMsg("Info from NucleonExcitations::parameterizeAll: "
405  "parameterizing", to_string(idEx), true);
406 
407  // Define helpful variables for the current excitation.
408  ParticleDataEntry* entry = particleDataPtr->findParticle(idEx);
409  double mEx = entry->m0(), mMinEx = entry->mMin();
410  bool isDelta = particleDataPtr->isParticle(2220 + maskEx);
411 
412  // Calculate high energy scale factor.
413  double scaleFactorEx;
414  check = integrateGauss(scaleFactorEx, [&](double m) {
415  return hadronWidthsPtr->mDistr(idEx, m);
416  }, entry->mMin(), entry->mMax());
417 
418  if (!check) {
419  infoPtr->errorMsg("Abort from NucleonExcitations::parameterizeAll: "
420  "unable to integrate excitation mass distribution", to_string(idEx));
421  return false;
422  }
423  scaleFactorEx *= entry->spinType();
424 
425  // Generate N + X cross sections.
426  double eMin = mN + mMinEx;
427  double de = (threshold - eMin) / (precision - 1);
428  vector<double> dataPointsNX(precision);
429  for (int ie = 0; ie < precision; ++ie) {
430  double eNow = eMin + de * ie;
431  dataPointsNX[ie] = sigmaCalc(eNow, 0002, maskEx);
432  }
433 
434  double scaleN = (maskEx == 0004) ? 0.
435  : scaleFactorN * scaleFactorEx * (isDelta ? 12.0 : 6.3)
436  / (pow2(mN - mEx) * pow2(mN + mEx));
437  excitationChannels.push_back(ExcitationChannel {
438  LinearInterpolator(eMin, threshold, dataPointsNX),
439  0002, maskEx, scaleN
440  });
441 
442  // Generate Delta(1232) + X cross sections.
443  eMin = mD + mMinEx;
444  de = (threshold - eMin) / (precision - 1);
445  vector<double> dataPointsDX(precision);
446  for (int ie = 0; ie < precision; ++ie) {
447  double eNow = eMin + de * ie;
448  dataPointsDX[ie] = sigmaCalc(eNow, 0004, maskEx);
449  }
450 
451  double scaleD = scaleFactorD * scaleFactorEx *
452  (maskEx == 0004 ? 2.8 : 3.5 / (pow2(mD - mEx) * pow2(mD + mEx)));
453  excitationChannels.push_back(ExcitationChannel {
454  LinearInterpolator(eMin, threshold, dataPointsDX),
455  0004, maskEx, scaleD
456  });
457  }
458 
459  // Reparameterize total cross section.
460  vector<double> sigmaTotPts(precision);
461  double eMin = mN + mD;
462  double de = (threshold - eMin) / (precision - 1);
463  for (int ie = 0; ie < precision; ++ie) {
464  double eNow = eMin + de * ie;
465  sigmaTotPts[ie] = 0;
466  for (auto& channel : excitationChannels)
467  sigmaTotPts[ie] += channel.sigma(eNow);
468  }
469  sigmaTotal = LinearInterpolator(eMin, threshold, sigmaTotPts);
470 
471  // Done.
472  return true;
473 }
474 
475 //--------------------------------------------------------------------------
476 
477 // Get total available phase space, integrating over mass-dependent widths.
478 
479 double NucleonExcitations::psSize(double eCM, ParticleDataEntry& prodA,
480  ParticleDataEntry& prodB) const {
481 
482  // Store some important values.
483  int idA = prodA.id(), idB = prodB.id();
484  double m0A = prodA.m0(), m0B = prodB.m0();
485  double mMinA = prodA.mMin(), mMinB = prodB.mMin();
486  double mMaxA = prodA.mMax(), mMaxB = prodB.mMax();
487  bool varA = mMaxA > mMinA, varB = mMaxB > mMinB;
488 
489  if (eCM < mMinA + mMinB)
490  return 0.;
491 
492  double result;
493  bool success = true;
494 
495  // No resonances.
496  if (!varA && !varB)
497  return pCMS(eCM, m0A, m0B);
498 
499  // A is resonance.
500  else if (varA && !varB) {
501  if (eCM <= mMinA + m0B)
502  return 0.;
503 
504  // Integrate mass of A.
505  auto f = [=](double mA) {
506  return pCMS(eCM, mA, m0B) * hadronWidthsPtr->mDistr(idA, mA); };
507  if (!integrateGauss(result, f, mMinA, min(mMaxA, eCM - m0B)))
508  success = false;
509  }
510 
511  // B is resonance.
512  else if (!varA && varB) {
513  if (eCM <= m0A + mMinB)
514  return 0.;
515 
516  // Integrate mass of B.
517  auto f = [=](double mB) {
518  return pCMS(eCM, m0A, mB) * hadronWidthsPtr->mDistr(idB, mB); };
519  if (!integrateGauss(result, f, mMinB, min(mMaxB, eCM - m0A)))
520  success = false;
521  }
522 
523  // Both are resonances.
524  else {
525  if (eCM <= mMinA + mMinB)
526  return 0.;
527 
528  // Define integrand of outer integral.
529  auto I = [=, &success](double mA) {
530 
531  // Define integrand of inner integral.
532  auto f = [=](double mB) {
533  return pCMS(eCM, mA, mB)
534  * hadronWidthsPtr->mDistr(idA, mA)
535  * hadronWidthsPtr->mDistr(idB, mB); };
536  double res;
537 
538  // Integrate mass of B.
539  if (!integrateGauss(res, f, mMinB, min(mMaxB, eCM - mA)))
540  success = false;
541 
542  return res;
543  };
544 
545  // Integrate mass of A.
546  if (!integrateGauss(result, I, mMinA, min(mMaxA, eCM - mMinB)))
547  success = false;
548  }
549 
550  // Return result if successful.
551  if (success)
552  return result;
553  else {
554  infoPtr->errorMsg("Error in HadronWidths::psSize: Unable to integrate");
555  return NAN;
556  }
557 }
558 
559 //--------------------------------------------------------------------------
560 
561 // Write all cross section data to an xml file.
562 
563 bool NucleonExcitations::save(ostream& stream) const {
564 
565  if (!stream.good())
566  return false;
567 
568  // Write header
569  stream << "<header "
570  << "threshold=\"" << sigmaTotal.right() << "\" "
571  << "sigmaTotalPrecision=\"" << sigmaTotal.data().size() << "\" /> "
572  << endl << endl;
573 
574  // Write channels.
575  for (auto& channel : excitationChannels) {
576  stream << "<excitationChannel "
577  << "maskA=\"" << channel.maskA << "\" "
578  << "maskB=\"" << channel.maskB << "\" "
579  << "left=\"" << channel.sigma.left() << "\" "
580  << "right=\"" << channel.sigma.right() << "\" "
581  << "scaleFactor=\"" << channel.scaleFactor << "\" "
582  << "data=\" \n";
583 
584  for (double dataPoint : channel.sigma.data())
585  stream << dataPoint << " ";
586  stream << "\n /> \n \n";
587  }
588 
589  // Done.
590  return true;
591 }
592 
593 //==========================================================================
594 
595 }