EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Kinematics.cxx
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file Kinematics.cxx
1 
11 
12 #include <algorithm>
13 #include <cmath>
14 #include <functional>
15 #include <iostream>
16 #include <list>
17 #include <memory>
18 #include <numeric> // For std::accumulate
19 #include <stdexcept>
20 #include <utility>
21 #include <vector>
22 
23 #include <TDatabasePDG.h>
24 #include <TParticlePDG.h>
25 #include <TVector3.h>
26 
29 
31 namespace {
32 
33 const double chargedPionMass =
34 TDatabasePDG::Instance()->GetParticle(211)->Mass();
35 
37 
38 // ==========================================================================
39 // Helper function returning W^2 from x, Q^2 and mass.
40 // ==========================================================================
41 double computeW2FromXQ2M(double x, double Q2, double m) {
42  if (x > 0.) {
43  return std::pow(m, 2.) + (1. - x) * Q2 / x;
44  } // if
45  return 0.;
46 }
47 
48 // ==========================================================================
49 // Returns the value x bounded by [minimum, maximum].
50 // ==========================================================================
51 double bounded(double x, double minimum, double maximum) {
52  // KK 12/10/19: x>1 can be physically possible (in eA).
53  // In general, silently cutting off values is dangerous practice.
54  // Instead, we will optionally issue a warning but accept
55  // Note: The warning is on by default, because this (local) function does
56  // often get called with values for minimum and maximum,
57  // and we shouldn't just silently ignore that.
58  // It may be better to remove "bounded" entirely though.
59 
60  if ( erhic::DisKinematics::BoundaryWarning && ( x< minimum || x > maximum ) ){
61  std::cerr << "Warning in Kinematics, bounded(): x (or y) = " << x
62  << " is outside [" << minimum << "," << maximum << "]" << std::endl;
63  std::cerr << "To disable this warning, set erhic::DisKinematics::BoundaryWarning=false;" << std::endl;
64  }
65  return x;
66  // return std::max(minimum, std::min(x, maximum));
67 }
68 
69 /*
70  Calculates kinematic information that is knowable by a detector.
71 
72  This takes account whether p, E and particle id are known or not, and
73  computes assumed values for variables that aren't explicitly known.
74  As a simple example: if p is not known, but E is, assume p == E.
75  Returns a ParticleMC with these "best-guess" values, specifically p, E,
76  ID, mass, pz, px, py, pt.
77  */
78 using erhic::ParticleMC;
80 typedef std::vector<const VirtualParticle*>::iterator VirtPartIter;
81 typedef std::vector<const VirtualParticle*>::const_iterator cVirtPartIter;
82 
83 class MeasuredParticle {
84  public:
85  static ParticleMC* Create(const erhic::VirtualParticle* particle) {
86  if (!particle) {
87  throw std::invalid_argument("MeasuredParticle given NULL pointer");
88  } // if
89  ParticleMC* measured = new ParticleMC;
90  // Copy ID from the input particle or guess it if not known.
91  measured->SetId(CalculateId(particle));
92  // Set mass from known/guessed ID.
93  TParticlePDG* pdg = measured->Id().Info();
94  if (pdg) {
95  measured->SetM(pdg->Mass());
96  } // if
97  std::pair<double, double> ep =
98  CalculateEnergyMomentum(particle, pdg->Mass());
99  TLorentzVector vec(0., 0., ep.second, ep.first);
100  vec.SetTheta(particle->GetTheta());
101  vec.SetPhi(particle->GetPhi());
102  measured->Set4Vector(vec);
103  return measured;
104  }
105  /*
106  Determine the particle ID.
107  If the input particle ID is known, returns the same value.
108  If the input ID is not known, return an assumed ID based on available
109  information.
110  */
111  static int CalculateId(const erhic::VirtualParticle* particle) {
112  int id(0);
113  TParticlePDG* pdg = particle->Id().Info();
114  // Skip pid of 0 (the default) as this is a dummy "ROOTino".
115  if (pdg && particle->Id() != 0) {
116  id = particle->Id();
117  } else if (particle->GetP() > 0.) {
118  // The particle ID is unknown.
119  // If there is momentum information, it must be charged so
120  // assume it is a pi+. We don't currently track whether charge info
121  // is known so we can't distinguish pi+ and pi- here.
122  // If there is no momentum information, assume it is a photon.
123  // *This is not really correct, but currently we don't track
124  // whether a particle is known to be EM or hadronic, so we can't
125  // e.g. assume neutron for hadronic particles.
126  id = 211;
127  } else {
128  id = 22;
129  } // if
130  return id;
131  }
132  /*
133  Calculate energy, using momentum and mass information if available.
134  If mass is greater than zero, use this as the assumed mass when
135  calculating via momentum, otherwise calculate
136  mass from scratch via either the known or assumed ID returned by
137  CalculateID(). Use this argument if you already know this mass and
138  don't want to repeat the calculation.
139  */
140  static std::pair<double, double> CalculateEnergyMomentum(
141  const erhic::VirtualParticle* particle, double mass = -1.) {
142  if (mass < 0.) {
143  int id = CalculateId(particle);
144  TParticlePDG* pdg = TDatabasePDG::Instance()->GetParticle(id);
145  if (pdg) {
146  mass = pdg->Mass();
147  } else {
148  mass = 0.;
149  } // if
150  } // if
151  // If momentum greater than zero, we assume the particle represents
152  // data where tracking information was available, and we use the
153  // momentum to compute energy.
154  std::pair<double, double> ep(0., 0.);
155  if (particle->GetP() > 0.) {
156  ep.first = sqrt(pow(particle->GetP(), 2.) + pow(mass, 2.));
157  ep.second = particle->GetP();
158  } else if (particle->GetE() > 0.) {
159  ep.first = particle->GetE();
160  // need to catch cases where E was smeared < E
161  // Assign P=0 in this case
162  auto E = particle->GetE();
163  if ( E >= mass ){
164  ep.second = sqrt(pow(E, 2.) - pow(mass, 2.));
165  } else {
166  ep.second = 0;
167  }
168  } // if
169  return ep;
170  }
171 };
172 
173 } // anonymous namespace
174 
175 namespace erhic {
176 
178 : mX(0.)
179 , mQ2(0.)
180 , mW2(0.)
181 , mNu(0.)
182 , mY(0) {
183 }
184 
185 DisKinematics::DisKinematics(double x, double y, double nu,
186  double Q2, double W2)
187 : mX(x)
188 , mQ2(Q2)
189 , mW2(W2)
190 , mNu(nu)
191 , mY(y) {
192 }
193 
194 // ==========================================================================
195 // ==========================================================================
197  // ParticleIdentifier::IdentifyBeams(event, mBeams);
198  mBeams.push_back(event.BeamLepton());
199  mBeams.push_back(event.BeamHadron());
200  mBeams.push_back(event.ExchangeBoson());
201  mBeams.push_back(event.ScatteredLepton());
202 }
203 
204 // ==========================================================================
205 // ==========================================================================
207  // Create kinematics with default values.
208  DisKinematics* kin = new DisKinematics;
209  try {
210  // Use E, p and ID of scattered lepton to create "best-guess" kinematics.
211  // MeasuredParticle::Create will throw an exception in case of a NULL
212  // pointer argument.
213  std::unique_ptr<const VirtualParticle> scattered( MeasuredParticle::Create(mBeams.at(3)));
214  // If there is no measurement of theta of the scattered lepton we
215  // cannot calculate kinematics. Note that via MeasuredParticle the momentum
216  // may actually be derived from measured energy instead of momentum.
217  if (scattered->GetTheta() > 0. && scattered->GetP() > 0.) {
218  const TLorentzVector& l = mBeams.at(0)->Get4Vector();
219  const TLorentzVector& h = mBeams.at(1)->Get4Vector();
220  // Calculate kinematic quantities, making sure to bound
221  // the results by physical limits.
222  // First, Q-squared.
223  // double Q2 = 2. * l.E() * scattered->GetE() * (1. + cos(scattered->GetTheta()));
224  double Q2 = 2. * ( l.E() * scattered->GetE() + scattered->GetP()*l.P()*cos(scattered->GetTheta()) - l.M2()*l.M2() );
225  kin->mQ2 = std::max(0., Q2);
226  // Find scattered lepton energy boosted to the rest
227  // frame of the hadron beam. Calculate nu from this.
228  double gamma = mBeams.at(1)->GetE() / mBeams.at(1)->GetM();
229  double beta = mBeams.at(1)->GetP() / mBeams.at(1)->GetE();
230  double ELeptonInNucl = gamma * (l.P() - beta * l.Pz());
231  double ELeptonOutNucl = gamma *
232  (scattered->GetP() - beta * scattered->GetPz());
233  kin->mNu = std::max(0., ELeptonInNucl - ELeptonOutNucl);
234  // Calculate y using the exchange boson.
235  // Determine the exchange boson 4-vector from the scattered lepton, as
236  // this will then be valid for smeared event input also (where the
237  // exchange boson is not recorded).
238  const TLorentzVector q = l - scattered->Get4Vector();
239  const double ldoth = l.Dot(h);
240  double y = q.Dot(h) / ldoth ;
241  if ( y<0) y=0; // kk: catching unphysical negative values.
242  kin->mY = bounded(y, 0., 1.);
243  double x = 0;
244  // Calculate x from Q^2 = sxy
245  // double cme = (l + h).M2();
246  // if ( y>0 && cme>0 ) x = kin->mQ2 / kin->mY / cme;
247  // Calculate from x = Q^2 / ( 2 * h*q ), y = h*q / (l * h)
248  if ( y>0 && std::abs(ldoth) > 0) x = kin->mQ2 / 2 / y / ldoth;
249  if ( x<0 ) x=0; // kk: catching unphysically negative values.
250  kin->mX = bounded(x, 0., 1.);
251  kin->mW2 = computeW2FromXQ2M(kin->mX, kin->mQ2, h.M());
252  // if ( y > 1 ) std::cout << " NM y = " << y << std::endl;
253  // if ( x > 1 ) std::cout << " NM x = " << x << " y = " << y << " Q2 = " << kin->mQ2 << std::endl;
254  } // if
255  } // try
256  catch(std::invalid_argument& except) {
257  // In case of no scattered lepton return default values.
258  std::cerr << "No lepton for kinematic calculations" << std::endl;
259  } // catch
260  return kin;
261 }
262 
263 // ==========================================================================
264 // ==========================================================================
266  // Delete all "measureable" particles.
267  for (VirtPartIter i = mParticles.begin(); i != mParticles.end(); ++i) {
268  if (*i) {
269  delete *i;
270  *i = NULL;
271  } // if
272  } // for
273  mParticles.clear();
274 }
275 
276 // ==========================================================================
277 // ==========================================================================
279 : mEvent(event) {
280  // Get the full list of final-state particles in the event.
281  // including decay bosons and leptons
282  std::vector<const erhic::VirtualParticle*> final;
283  mEvent.HadronicFinalState(final);
284  // Populate the stored particle list with "measurable" versions of
285  // each final-state particle.
286  // std::transform(final.begin(), final.end(), std::back_inserter(mParticles),
287  // std::ptr_fun(&MeasuredParticle::Create));
288  // transform applies the function MeasuredParticle::Create to each element of
289  // final and stores the result in mParticles.
290  // Easier, and compatible with C++98 and C++17:
291  for (VirtPartIter it = final.begin() ; it != final.end(); ++it){
292  mParticles.push_back ( MeasuredParticle::Create ( *it ) );
293  }
294 }
295 
296 // ==========================================================================
297 // ==========================================================================
299  // Get all the final-state particles except the scattered lepton.
300  DisKinematics* kin = new DisKinematics;
301  kin->mY = ComputeY();
302  kin->mQ2 = ComputeQSquared();
303  kin->mX = ComputeX();
304  kin->mW2 = computeW2FromXQ2M(kin->mX, kin->mQ2,
305  mEvent.BeamHadron()->GetM());
306  return kin;
307 }
308 
309 // ==========================================================================
310 // ==========================================================================
312  double y(0.);
313  // Calculate y, as long as we have beam information.
314  const VirtualParticle* hadron = mEvent.BeamHadron();
315  const VirtualParticle* lepton = mEvent.BeamLepton();
316  if (hadron && lepton) {
317  // Sum the energies of the final-state hadrons
318  std::vector<double> E;
319  for (cVirtPartIter it = mParticles.begin() ; it != mParticles.end(); ++it){
320  E.push_back ( (*it)->GetE() );
321  }
322  const double sumEh = std::accumulate(E.begin(), E.end(), 0.);
323 
324  // Sum the pz of the final-state hadrons
325  std::vector<double> pz;
326  for (cVirtPartIter it = mParticles.begin() ; it != mParticles.end(); ++it){
327  pz.push_back ( (*it)->GetPz() );
328  }
329  const double sumPzh = std::accumulate(pz.begin(), pz.end(), 0.);
330  // Compute y.
331  // This expression seems more accurate at small y than the usual
332  // (sumE - sumPz) / 2E_lepton.
333  // Tested by Xiaoxuan Chu and Elke Aschenauer, this formula
334  // is more appropriate in the presence of radiative effects.
335  y = (hadron->GetE() * sumEh -
336  hadron->GetPz() * sumPzh -
337  pow(hadron->GetM(), 2.)) /
338  (lepton->GetE() * hadron->GetE() - lepton->GetPz() * hadron->GetPz());
339 
340  // Can result in negative y. Catch that here since bounded() below is
341  // for legacy reasons only and produces warnings instead of cutoffs
342  if (y<0) y=0;
343  } // if
344 
345  // Return y, bounding it by the range [0, 1]
346  // return bounded(y, 0., 1.);
347  // changed: y_jb can be slightly >1
348  // we're returning y as is and circumvent the warnning
349  //std::cout << " JB y = " << y << std::endl;
350  return y;
351 }
352 
353 // ==========================================================================
354 // We use the following exact expression:
355 // 2(E_p * sum(E_h) - pz_p * sum(pz_h)) - sum(E_h)^2 + sum(p_h)^2 - M_p^2
356 // ==========================================================================
358  double Q2(0.);
359  // Calculate Q^2, as long as we have beam information.
360  const VirtualParticle* hadron = mEvent.BeamHadron();
361  if (hadron) {
362  // Get the px of each particle:
363  std::vector<double> px;
364  for (cVirtPartIter it = mParticles.begin() ; it != mParticles.end(); ++it){
365  px.push_back ( (*it)->GetPx() );
366  }
367 
368  // Get the py of each particle:
369  std::vector<double> py;
370  for (cVirtPartIter it = mParticles.begin() ; it != mParticles.end(); ++it){
371  py.push_back ( (*it)->GetPy() );
372  }
373 
374  double sumPx = std::accumulate(px.begin(), px.end(), 0.);
375  double sumPy = std::accumulate(py.begin(), py.end(), 0.);
376  double y = ComputeY();
377  if (y < 1.) {
378  Q2 = (pow(sumPx, 2.) + pow(sumPy, 2.)) / (1. - y);
379  } // if
380  } // if
381  return std::max(0., Q2);
382 }
383 
384 // ==========================================================================
385 // x_JB = Q2_JB / (y_JB * s)
386 // ==========================================================================
388  double x(0.);
389  // Calculate x, as long as we have beam information.
390  const VirtualParticle* hadron = mEvent.BeamHadron();
391  const VirtualParticle* lepton = mEvent.BeamLepton();
392  if (hadron && lepton) {
393  double y = ComputeY();
394  double s = (hadron->Get4Vector() + lepton->Get4Vector()).M2();
395  // Use the approximate relation Q^2 = sxy to calculate x.
396  if (y > 0.0) {
397  x = ComputeQSquared() / y / s;
398  } // if
399  } // if
400 
401  // return bounded(x, 0., 1.);
402  // changed: x_jb can be slightly >1 when y_jb is very small
403  // we're returning x as is and circumvent the warning (but keep warning for x<0)
404  // std::cout << " JB x = " << x << std::endl;
405  return bounded(x, 0., 100.);
406 }
407 
408 // ==========================================================================
409 // ==========================================================================
411  // Delete all "measureable" particles.
412  typedef std::vector<const VirtualParticle*>::iterator Iter;
413  for (Iter i = mParticles.begin(); i != mParticles.end(); ++i) {
414  if (*i) {
415  delete *i;
416  *i = NULL;
417  } // if
418  } // for
419  mParticles.clear();
420 }
421 
422 // ==========================================================================
423 // ==========================================================================
425 : mEvent(event) {
426  // Get the full list of final-state particles in the event.
427  // including decay bosons and leptons
428  std::vector<const erhic::VirtualParticle*> final;
429  mEvent.HadronicFinalState(final);
430  // Populate the stored particle list with "measurable" versions of
431  // each final-state particle.
432  for (VirtPartIter it = final.begin() ; it != final.end(); ++it){
433  mParticles.push_back ( MeasuredParticle::Create ( *it ) );
434  }
435 }
436 
437 // ==========================================================================
438 // Formulae used are from F.D. Aaron et al., JHEP01 (2010) 109.
439 // ==========================================================================
441  mHasChanged = true;
442  DisKinematics* kin = new DisKinematics;
443  kin->mQ2 = ComputeQSquared();
444  kin->mX = ComputeX();
445  kin->mY = ComputeY();
446  // Calculate W^2 from M^2 + (1 - x) * Q^2 / x
447  kin->mW2 = computeW2FromXQ2M(kin->mX, kin->mQ2, mEvent.BeamHadron()->GetM());
448  return kin;
449 }
450 
451 // ==========================================================================
452 // Scattering angle of struck quark
453 // cos(angle) = A / B
454 // where A = (sum of px_h)^2 + (sum of py_h)^2 - (sum of [E_h - pz_h])^2
455 // and B = (sum of px_h)^2 + (sum of py_h)^2 + (sum of [E_h - pz_h])^2
456 // This is a computationally expensive operation that is called a lot,
457 // so cashe the result until the particle list changes.
458 // ==========================================================================
460  // Return the cached value if no changes have occurred since
461  // the last call to computeQuarkAngle().
462  if (!mHasChanged) {
463  return mAngle;
464  } // if
465  std::vector<TLorentzVector> hadrons;
466  // std::transform(mParticles.begin(),
467  // mParticles.end(),
468  // std::back_inserter(hadrons),
469  // std::mem_fun(&VirtualParticle::Get4Vector));
470  for (cVirtPartIter it = mParticles.begin() ; it != mParticles.end(); ++it){
471  hadrons.push_back ( (*it)->Get4Vector() );
472  }
473  TLorentzVector h = std::accumulate(hadrons.begin(),
474  hadrons.end(),
475  TLorentzVector(0., 0., 0., 0.));
476  mAngle = 2. * TMath::ATan((h.E() - h.Pz()) / h.Pt());
477  mHasChanged = false;
478  return mAngle;
479 }
480 
481 // ==========================================================================
482 // ==========================================================================
484  double y(0.);
485  const VirtualParticle* scattered = mEvent.ScatteredLepton();
486  if (scattered) {
487  double theta = scattered->GetTheta();
488  double gamma = ComputeQuarkAngle();
489  double denominator = tan(theta / 2.) + tan(gamma / 2.);
490  if (denominator > 0.) {
491  y = tan(gamma / 2.) / denominator;
492  } // if
493  } // if
494  // Return y bounded by the range [0, 1].
495  return bounded(y, 0., 1.);
496 }
497 
498 // ==========================================================================
499 // ==========================================================================
501  double Q2(0.);
502  const VirtualParticle* lepton = mEvent.BeamLepton();
503  const VirtualParticle* scattered = mEvent.ScatteredLepton();
504  if (lepton && scattered) {
505  double theta = scattered->GetTheta();
506  double gamma = ComputeQuarkAngle();
507  double denominator = tan(theta / 2.) + tan(gamma / 2.);
508  // kk: could catch theta=0, but it's a canary for a faulty detector if (denominator > 0. && tan(theta / 2.) !=0 ) {
509  if (denominator > 0. ) {
510  Q2 = 4. * pow(lepton->GetE(), 2.) / tan(theta / 2.) / denominator;
511  } // if
512  } // if
513  // Return Q^2, requiring it to be positive.
514  return std::max(0., Q2);
515 }
516 
517 // ==========================================================================
518 // ==========================================================================
520  double x(0.);
521  const VirtualParticle* lepton = mEvent.BeamLepton();
522  const VirtualParticle* hadron = mEvent.BeamHadron();
523  if (lepton && hadron) {
524  double s = (lepton->Get4Vector() + hadron->Get4Vector()).M2();
525  double y = ComputeY();
526  if (s > 0. && y > 0.) {
527  x = ComputeQSquared() / y / s;
528  } // if
529  } // if
530  // Return x bounded by the range [0, 1].
531  return bounded(x, 0., 1.);
532 }
533 
534 } // namespace erhic