Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2024-09-28 07:02:50

0001 /**
0002  \file
0003  Implementations of kinematic calculation classes.
0004  
0005  \author    Thomas Burton
0006  \date      2011-07-07
0007  \copyright 2011 Brookhaven National Lab
0008  */
0009 
0010 #include "eicsmear/erhic/Kinematics.h"
0011 
0012 #include <algorithm>
0013 #include <cmath>
0014 #include <functional>
0015 #include <iostream>
0016 #include <list>
0017 #include <memory>
0018 #include <numeric>  // For std::accumulate
0019 #include <stdexcept>
0020 #include <utility>
0021 #include <vector>
0022 
0023 #include <TDatabasePDG.h>
0024 #include <TParticlePDG.h>
0025 #include <TVector3.h>
0026 
0027 #include "eicsmear/erhic/EventDis.h"
0028 #include "eicsmear/erhic/ParticleIdentifier.h"
0029 
0030 bool erhic::DisKinematics::BoundaryWarning=true;
0031 namespace {
0032 
0033 const double chargedPionMass =
0034 TDatabasePDG::Instance()->GetParticle(211)->Mass();
0035 
0036 typedef erhic::VirtualParticle Particle;
0037 
0038 // ==========================================================================
0039 // Helper function returning W^2 from x, Q^2 and mass.
0040 // ==========================================================================
0041 double computeW2FromXQ2M(double x, double Q2, double m) {
0042   if (x > 0.) {
0043     return std::pow(m, 2.) + (1. - x) * Q2 / x;
0044   }  // if
0045   return 0.;
0046 }
0047 
0048 // ==========================================================================
0049 // Returns the value x bounded by [minimum, maximum].
0050 // ==========================================================================
0051 double bounded(double x, double minimum, double maximum) {
0052   // KK 12/10/19: x>1 can be physically possible (in eA).
0053   // In general, silently cutting off values is dangerous practice.
0054   // Instead, we will optionally issue a warning but accept
0055   // Note: The warning is on by default, because this (local) function does
0056   // often get called with values for minimum and maximum,
0057   // and we shouldn't just silently ignore that.
0058   // It may be better to remove "bounded" entirely though.
0059   
0060   if ( erhic::DisKinematics::BoundaryWarning && ( x< minimum || x > maximum ) ){
0061     std::cerr << "Warning in Kinematics, bounded(): x (or y) = " << x
0062           << " is outside [" << minimum << "," << maximum << "]" << std::endl;
0063     std::cerr << "To disable this warning, set erhic::DisKinematics::BoundaryWarning=false;" << std::endl;
0064   }
0065   return x; 
0066   // return std::max(minimum, std::min(x, maximum));
0067 }
0068 
0069 /*
0070  Calculates kinematic information that is knowable by a detector.
0071  
0072  This takes account whether p, E and particle id are known or not, and
0073  computes assumed values for variables that aren't explicitly known.
0074  As a simple example: if p is not known, but E is, assume p == E.
0075  Returns a ParticleMC with these "best-guess" values, specifically p, E,
0076  ID, mass, pz, px, py, pt.
0077  */
0078 using erhic::ParticleMC;
0079 using erhic::VirtualParticle;
0080 typedef std::vector<const VirtualParticle*>::iterator VirtPartIter;
0081 typedef std::vector<const VirtualParticle*>::const_iterator cVirtPartIter;
0082 
0083 class MeasuredParticle {
0084  public:
0085   static ParticleMC* Create(const erhic::VirtualParticle* particle) {
0086     if (!particle) {
0087       throw std::invalid_argument("MeasuredParticle given NULL pointer");
0088     }  // if
0089     ParticleMC* measured = new ParticleMC;
0090     // Copy ID from the input particle or guess it if not known.
0091     measured->SetId(CalculateId(particle));
0092     // Set mass from known/guessed ID.
0093     TParticlePDG* pdg = measured->Id().Info();
0094     if (pdg) {
0095       measured->SetM(pdg->Mass());
0096     }  // if
0097     std::pair<double, double> ep =
0098       CalculateEnergyMomentum(particle, pdg->Mass());
0099     TLorentzVector vec(0., 0., ep.second, ep.first);
0100     vec.SetTheta(particle->GetTheta());
0101     vec.SetPhi(particle->GetPhi());
0102     measured->Set4Vector(vec);
0103     return measured;
0104   }
0105   /*
0106    Determine the particle ID.
0107    If the input particle ID is known, returns the same value.
0108    If the input ID is not known, return an assumed ID based on available
0109    information.
0110    */
0111   static int CalculateId(const erhic::VirtualParticle* particle) {
0112     int id(0);
0113     TParticlePDG* pdg = particle->Id().Info();
0114     // Skip pid of 0 (the default) as this is a dummy "ROOTino".
0115     if (pdg && particle->Id() != 0) {
0116       id = particle->Id();
0117     } else if (particle->GetP() > 0.) {
0118       // The particle ID is unknown.
0119       // If there is momentum information, it must be charged so
0120       // assume it is a pi+. We don't currently track whether charge info
0121       // is known so we can't distinguish pi+ and pi- here.
0122       // If there is no momentum information, assume it is a photon.
0123       // *This is not really correct, but currently we don't track
0124       // whether a particle is known to be EM or hadronic, so we can't
0125       // e.g. assume neutron for hadronic particles.
0126       id = 211;
0127     } else {
0128       id = 22;
0129     }  // if
0130     return id;
0131   }
0132   /*
0133    Calculate energy, using momentum and mass information if available.
0134    If mass is greater than zero, use this as the assumed mass when
0135    calculating via momentum, otherwise calculate
0136    mass from scratch via either the known or assumed ID returned by
0137    CalculateID(). Use this argument if you already know this mass and
0138    don't want to repeat the calculation.
0139    */
0140   static std::pair<double, double> CalculateEnergyMomentum(
0141       const erhic::VirtualParticle* particle, double mass = -1.) {
0142     if (mass < 0.) {
0143       int id = CalculateId(particle);
0144       TParticlePDG* pdg = TDatabasePDG::Instance()->GetParticle(id);
0145       if (pdg) {
0146         mass = pdg->Mass();
0147       } else {
0148         mass = 0.;
0149       }  // if
0150     }  // if
0151     // If momentum greater than zero, we assume the particle represents
0152     // data where tracking information was available, and we use the
0153     // momentum to compute energy.
0154     std::pair<double, double> ep(0., 0.);
0155     if (particle->GetP() > 0.) {
0156       ep.first = sqrt(pow(particle->GetP(), 2.) + pow(mass, 2.));
0157       ep.second = particle->GetP();
0158     } else if (particle->GetE() > 0.) {
0159       ep.first = particle->GetE();
0160       // need to catch cases where E was smeared < E
0161       // Assign P=0 in this case
0162       auto E = particle->GetE();
0163       if ( E >= mass ){
0164     ep.second = sqrt(pow(E, 2.) - pow(mass, 2.));
0165       } else {
0166     ep.second = 0;
0167       }
0168     }  // if
0169     return ep;
0170   }
0171 };
0172 
0173 }  // anonymous namespace
0174 
0175 namespace erhic {
0176 
0177 DisKinematics::DisKinematics()
0178 : mX(0.)
0179 , mQ2(0.)
0180 , mW2(0.)
0181 , mNu(0.)
0182 , mY(0) {
0183 }
0184 
0185 DisKinematics::DisKinematics(double x, double y, double nu,
0186                              double Q2, double W2)
0187 : mX(x)
0188 , mQ2(Q2)
0189 , mW2(W2)
0190 , mNu(nu)
0191 , mY(y) {
0192 }
0193 
0194 // ==========================================================================
0195 // ==========================================================================
0196 LeptonKinematicsComputer::LeptonKinematicsComputer(const EventDis& event) {
0197   //   ParticleIdentifier::IdentifyBeams(event, mBeams);
0198   mBeams.push_back(event.BeamLepton());
0199   mBeams.push_back(event.BeamHadron());
0200   mBeams.push_back(event.ExchangeBoson());
0201   mBeams.push_back(event.ScatteredLepton());
0202 }
0203 
0204 // ==========================================================================
0205 // ==========================================================================
0206 DisKinematics* LeptonKinematicsComputer::Calculate() {
0207   // Create kinematics with default values.
0208   DisKinematics* kin = new DisKinematics;
0209   try {
0210     // Use E, p and ID of scattered lepton to create "best-guess" kinematics.
0211     // MeasuredParticle::Create will throw an exception in case of a NULL
0212     // pointer argument.
0213     std::unique_ptr<const VirtualParticle> scattered( MeasuredParticle::Create(mBeams.at(3)));
0214     // If there is no measurement of theta of the scattered lepton we
0215     // cannot calculate kinematics. Note that via MeasuredParticle the momentum
0216     // may actually be derived from measured energy instead of momentum.
0217     if (scattered->GetTheta() > 0. && scattered->GetP() > 0.) {
0218       const TLorentzVector& l = mBeams.at(0)->Get4Vector();
0219       const TLorentzVector& h = mBeams.at(1)->Get4Vector();
0220       // Calculate kinematic quantities, making sure to bound
0221       // the results by physical limits.
0222       // First, Q-squared.
0223       // double Q2 = 2. * l.E() * scattered->GetE() * (1. + cos(scattered->GetTheta()));
0224       double Q2 = 2. * ( l.E() * scattered->GetE() +  scattered->GetP()*l.P()*cos(scattered->GetTheta()) - l.M2()*l.M2() );
0225       kin->mQ2 = std::max(0., Q2);
0226       // Find scattered lepton energy boosted to the rest
0227       // frame of the hadron beam. Calculate nu from this.
0228       double gamma = mBeams.at(1)->GetE() / mBeams.at(1)->GetM();
0229       double beta = mBeams.at(1)->GetP() / mBeams.at(1)->GetE();
0230       double ELeptonInNucl = gamma * (l.P() - beta * l.Pz());
0231       double ELeptonOutNucl = gamma *
0232       (scattered->GetP() - beta * scattered->GetPz());
0233       kin->mNu = std::max(0., ELeptonInNucl - ELeptonOutNucl);
0234       // Calculate y using the exchange boson.
0235       // Determine the exchange boson 4-vector from the scattered lepton, as
0236       // this will then be valid for smeared event input also (where the
0237       // exchange boson is not recorded).
0238       const TLorentzVector q = l - scattered->Get4Vector();
0239       const double ldoth = l.Dot(h);
0240       double y = q.Dot(h) / ldoth ;
0241       if ( y<0) y=0; // kk: catching unphysical negative values.
0242       kin->mY = bounded(y, 0., 1.);
0243       double x = 0;
0244       // Calculate x from Q^2 = sxy
0245       // double cme = (l + h).M2();
0246       // if ( y>0 && cme>0 ) x = kin->mQ2 / kin->mY / cme;
0247       // Calculate from x = Q^2 / ( 2 * h*q ), y = h*q / (l * h)
0248       if ( y>0 && std::abs(ldoth) > 0) x = kin->mQ2 / 2 / y / ldoth;        
0249       if ( x<0 ) x=0; // kk: catching unphysically negative values.
0250       kin->mX = bounded(x, 0., 1.);
0251       kin->mW2 = computeW2FromXQ2M(kin->mX, kin->mQ2, h.M());
0252       // if ( y > 1 ) std::cout << " NM y = " << y << std::endl;
0253       // if ( x > 1 ) std::cout << " NM x = " << x << "    y = " << y << "    Q2 = " << kin->mQ2 << std::endl;
0254     }  // if
0255   }  // try
0256   catch(std::invalid_argument& except) {
0257     // In case of no scattered lepton return default values.
0258     std::cerr << "No lepton for kinematic calculations" << std::endl;
0259   }  // catch
0260   return kin;
0261 }
0262 
0263 // ==========================================================================
0264 // ==========================================================================
0265 JacquetBlondelComputer::~JacquetBlondelComputer() {
0266   // Delete all "measureable" particles.
0267   for (VirtPartIter i = mParticles.begin(); i != mParticles.end(); ++i) {
0268     if (*i) {
0269       delete *i;
0270       *i = NULL;
0271     }  // if
0272   }  // for
0273   mParticles.clear();
0274 }
0275 
0276 // ==========================================================================
0277 // ==========================================================================
0278 JacquetBlondelComputer::JacquetBlondelComputer(const EventDis& event)
0279 : mEvent(event) {
0280   // Get the full list of final-state particles in the event.
0281   // including decay bosons and leptons 
0282   std::vector<const erhic::VirtualParticle*> final;
0283   mEvent.HadronicFinalState(final);
0284   // Populate the stored particle list with "measurable" versions of
0285   // each final-state particle.
0286   // std::transform(final.begin(), final.end(), std::back_inserter(mParticles),
0287   //                std::ptr_fun(&MeasuredParticle::Create));
0288   // transform applies the function MeasuredParticle::Create to each element of
0289   // final and stores the result in mParticles.
0290   // Easier, and compatible with C++98 and C++17:
0291   for (VirtPartIter it = final.begin() ; it != final.end(); ++it){
0292     mParticles.push_back ( MeasuredParticle::Create ( *it ) );
0293   }
0294 }
0295 
0296 // ==========================================================================
0297 // ==========================================================================
0298 DisKinematics* JacquetBlondelComputer::Calculate() {
0299   // Get all the final-state particles except the scattered lepton.
0300   DisKinematics* kin = new DisKinematics;
0301   kin->mY = ComputeY();
0302   kin->mQ2 = ComputeQSquared();
0303   kin->mX = ComputeX();
0304   kin->mW2 = computeW2FromXQ2M(kin->mX, kin->mQ2,
0305                                mEvent.BeamHadron()->GetM());
0306   return kin;
0307 }
0308 
0309 // ==========================================================================
0310 // ==========================================================================
0311 Double_t JacquetBlondelComputer::ComputeY() const {
0312   double y(0.);
0313   // Calculate y, as long as we have beam information.
0314   const VirtualParticle* hadron = mEvent.BeamHadron();
0315   const VirtualParticle* lepton = mEvent.BeamLepton();
0316   if (hadron && lepton) {
0317     // Sum the energies of the final-state hadrons
0318     std::vector<double> E;
0319     for (cVirtPartIter it = mParticles.begin() ; it != mParticles.end(); ++it){
0320       E.push_back ( (*it)->GetE() );
0321     }
0322     const double sumEh = std::accumulate(E.begin(), E.end(), 0.);
0323 
0324     // Sum the pz of the final-state hadrons
0325     std::vector<double> pz;
0326     for (cVirtPartIter it = mParticles.begin() ; it != mParticles.end(); ++it){
0327       pz.push_back ( (*it)->GetPz() );
0328     }
0329     const double sumPzh = std::accumulate(pz.begin(), pz.end(), 0.);
0330     // Compute y.
0331     // This expression seems more accurate at small y than the usual
0332     // (sumE - sumPz) / 2E_lepton.
0333     // Tested by Xiaoxuan Chu and Elke Aschenauer, this formula
0334     // is more appropriate in the presence of radiative effects.
0335     y = (hadron->GetE() * sumEh -
0336          hadron->GetPz() * sumPzh -
0337          pow(hadron->GetM(), 2.)) /
0338     (lepton->GetE() * hadron->GetE() - lepton->GetPz() * hadron->GetPz());
0339 
0340     // Can result in negative y. Catch that here since bounded() below is
0341     // for legacy reasons only and produces warnings instead of cutoffs
0342     if (y<0) y=0;
0343   }  // if
0344 
0345   // Return y, bounding it by the range [0, 1]
0346   // return bounded(y, 0., 1.);
0347   // changed: y_jb can be slightly >1
0348   // we're returning y as is and circumvent the warnning
0349   //std::cout << " JB y = " << y << std::endl;
0350   return y;
0351 }
0352 
0353 // ==========================================================================
0354 // We use the following exact expression:
0355 // 2(E_p * sum(E_h) - pz_p * sum(pz_h)) - sum(E_h)^2 + sum(p_h)^2 - M_p^2
0356 // ==========================================================================
0357 Double_t JacquetBlondelComputer::ComputeQSquared() const {
0358   double Q2(0.);
0359   // Calculate Q^2, as long as we have beam information.
0360   const VirtualParticle* hadron = mEvent.BeamHadron();
0361   if (hadron) {
0362     // Get the px of each particle:
0363     std::vector<double> px;
0364     for (cVirtPartIter it = mParticles.begin() ; it != mParticles.end(); ++it){
0365       px.push_back ( (*it)->GetPx() );
0366     }
0367 
0368     // Get the py of each particle:
0369     std::vector<double> py;
0370     for (cVirtPartIter it = mParticles.begin() ; it != mParticles.end(); ++it){
0371       py.push_back ( (*it)->GetPy() );
0372     }
0373 
0374     double sumPx = std::accumulate(px.begin(), px.end(), 0.);
0375     double sumPy = std::accumulate(py.begin(), py.end(), 0.);
0376     double y = ComputeY();
0377     if (y < 1.) {
0378       Q2 = (pow(sumPx, 2.) + pow(sumPy, 2.)) / (1. - y);
0379     }  // if
0380   }  // if
0381   return std::max(0., Q2);
0382 }
0383 
0384 // ==========================================================================
0385 // x_JB = Q2_JB / (y_JB * s)
0386 // ==========================================================================
0387 Double_t JacquetBlondelComputer::ComputeX() const {
0388   double x(0.);
0389   // Calculate x, as long as we have beam information.
0390   const VirtualParticle* hadron = mEvent.BeamHadron();
0391   const VirtualParticle* lepton = mEvent.BeamLepton();
0392   if (hadron && lepton) {
0393     double y = ComputeY();
0394     double s = (hadron->Get4Vector() + lepton->Get4Vector()).M2();
0395     // Use the approximate relation Q^2 = sxy to calculate x.
0396     if (y > 0.0) {
0397       x = ComputeQSquared() / y / s;
0398     }  // if
0399   }  // if
0400 
0401   // return bounded(x, 0., 1.);
0402   // changed: x_jb can be slightly >1 when y_jb is very small
0403   // we're returning x as is and circumvent the warning (but keep warning for x<0)
0404   // std::cout << " JB x = " << x << std::endl;
0405   return bounded(x, 0., 100.);
0406 }
0407 
0408 // ==========================================================================
0409 // ==========================================================================
0410 DoubleAngleComputer::~DoubleAngleComputer() {
0411   // Delete all "measureable" particles.
0412   typedef std::vector<const VirtualParticle*>::iterator Iter;
0413   for (Iter i = mParticles.begin(); i != mParticles.end(); ++i) {
0414     if (*i) {
0415       delete *i;
0416       *i = NULL;
0417     }  // if
0418   }  // for
0419   mParticles.clear();
0420 }
0421 
0422 // ==========================================================================
0423 // ==========================================================================
0424 DoubleAngleComputer::DoubleAngleComputer(const EventDis& event)
0425 : mEvent(event) {
0426   // Get the full list of final-state particles in the event.
0427   // including decay bosons and leptons 
0428   std::vector<const erhic::VirtualParticle*> final;
0429   mEvent.HadronicFinalState(final);
0430   // Populate the stored particle list with "measurable" versions of
0431   // each final-state particle.
0432   for (VirtPartIter it = final.begin() ; it != final.end(); ++it){
0433     mParticles.push_back ( MeasuredParticle::Create ( *it ) );
0434   }
0435 }
0436 
0437 // ==========================================================================
0438 // Formulae used are from F.D. Aaron et al., JHEP01 (2010) 109.
0439 // ==========================================================================
0440 DisKinematics* DoubleAngleComputer::Calculate() {
0441   mHasChanged = true;
0442   DisKinematics* kin = new DisKinematics;
0443   kin->mQ2 = ComputeQSquared();
0444   kin->mX = ComputeX();
0445   kin->mY = ComputeY();
0446   // Calculate W^2 from M^2 + (1 - x) * Q^2 / x
0447   kin->mW2 = computeW2FromXQ2M(kin->mX, kin->mQ2, mEvent.BeamHadron()->GetM());
0448   return kin;
0449 }
0450 
0451 // ==========================================================================
0452 // Scattering angle of struck quark
0453 // cos(angle) = A / B
0454 // where A = (sum of px_h)^2 + (sum of py_h)^2 - (sum of [E_h - pz_h])^2
0455 // and   B = (sum of px_h)^2 + (sum of py_h)^2 + (sum of [E_h - pz_h])^2
0456 // This is a computationally expensive operation that is called a lot,
0457 // so cashe the result until the particle list changes.
0458 // ==========================================================================
0459 Double_t DoubleAngleComputer::ComputeQuarkAngle() const {
0460   // Return the cached value if no changes have occurred since
0461   // the last call to computeQuarkAngle().
0462   if (!mHasChanged) {
0463     return mAngle;
0464   }  // if
0465   std::vector<TLorentzVector> hadrons;
0466   // std::transform(mParticles.begin(),
0467   //                mParticles.end(),
0468   //                std::back_inserter(hadrons),
0469   //                std::mem_fun(&VirtualParticle::Get4Vector));
0470   for (cVirtPartIter it = mParticles.begin() ; it != mParticles.end(); ++it){
0471     hadrons.push_back ( (*it)->Get4Vector() );
0472   }
0473   TLorentzVector h = std::accumulate(hadrons.begin(),
0474                                      hadrons.end(),
0475                                      TLorentzVector(0., 0., 0., 0.));
0476   mAngle = 2. * TMath::ATan((h.E() - h.Pz()) / h.Pt());
0477   mHasChanged = false;
0478   return mAngle;
0479 }
0480 
0481 // ==========================================================================
0482 // ==========================================================================
0483 Double_t DoubleAngleComputer::ComputeY() const {
0484   double y(0.);
0485   const VirtualParticle* scattered = mEvent.ScatteredLepton();
0486   if (scattered) {
0487     double theta = scattered->GetTheta();
0488     double gamma = ComputeQuarkAngle();
0489     double denominator = tan(theta / 2.) + tan(gamma / 2.);
0490     if (denominator > 0.) {
0491       y = tan(gamma / 2.) / denominator;
0492     }  // if
0493   }  // if
0494      // Return y bounded by the range [0, 1].
0495   return bounded(y, 0., 1.);
0496 }
0497 
0498 // ==========================================================================
0499 // ==========================================================================
0500 Double_t DoubleAngleComputer::ComputeQSquared() const {
0501   double Q2(0.);
0502   const VirtualParticle* lepton = mEvent.BeamLepton();
0503   const VirtualParticle* scattered = mEvent.ScatteredLepton();
0504   if (lepton && scattered) {
0505     double theta = scattered->GetTheta();
0506     double gamma = ComputeQuarkAngle();
0507     double denominator = tan(theta / 2.) + tan(gamma / 2.);
0508     // kk: could catch theta=0, but it's a canary for a faulty detector   if (denominator > 0. && tan(theta / 2.) !=0 ) {
0509     if (denominator > 0. ) {
0510       Q2 = 4. * pow(lepton->GetE(), 2.) / tan(theta / 2.) / denominator;
0511     }  // if
0512   }  // if
0513   // Return Q^2, requiring it to be positive.
0514   return std::max(0., Q2);
0515 }
0516 
0517 // ==========================================================================
0518 // ==========================================================================
0519 Double_t DoubleAngleComputer::ComputeX() const {
0520   double x(0.);
0521   const VirtualParticle* lepton = mEvent.BeamLepton();
0522   const VirtualParticle* hadron = mEvent.BeamHadron();
0523   if (lepton && hadron) {
0524     double s = (lepton->Get4Vector() + hadron->Get4Vector()).M2();
0525     double y = ComputeY();
0526     if (s > 0. && y > 0.) {
0527       x = ComputeQSquared() / y / s;
0528     }  // if
0529   }  // if
0530   // Return x bounded by the range [0, 1].
0531   return bounded(x, 0., 1.);
0532 }
0533 
0534 }  // namespace erhic