Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-13 08:21:04

0001 
0002 #include "G4HepEmElectronInteractionBrem.hh"
0003 
0004 #include "G4HepEmTLData.hh"
0005 #include "G4HepEmData.hh"
0006 #include "G4HepEmMatCutData.hh"
0007 #include "G4HepEmMaterialData.hh"
0008 #include "G4HepEmElementData.hh"
0009 #include "G4HepEmElectronData.hh"
0010 #include "G4HepEmSBTableData.hh"
0011 
0012 #include "G4HepEmElectronTrack.hh"
0013 #include "G4HepEmGammaTrack.hh"
0014 #include "G4HepEmConstants.hh"
0015 #include "G4HepEmRunUtils.hh"
0016 
0017 #include "G4HepEmInteractionUtils.hh"
0018 
0019 #include "G4HepEmMath.hh"
0020 
0021 #include <cmath>
0022 //#include <iostream>
0023 
0024 
0025 // Bremsstrahlung interaction based on:
0026 // 1. SB: - the numerical Seltzer-Berger DCS for the emitted photon energy.
0027 //        - used between 1 keV - 1 GeV primary e-/e+ kinetic energies.
0028 //        NOTE: the core part i.e. sampling the emitted photon energy is different than
0029 //          that in the G4SeltzerBergerModel. I implemented here my rejection free,
0030 //          memory effcicient (tables only per Z and not per mat-cuts) sampling.
0031 //          Rejection is used only to account dielectric supression and e+ correction.
0032 // 2. RB: - the Bethe-Heitler DCS with modifications such as screening and Coulomb
0033 //          corrections, emission in the field of the atomic electrons and LPM suppression.
0034 //          Used between 1 GeV - 100 TeV primary e-/e+ kinetic energies.
0035 void G4HepEmElectronInteractionBrem::Perform(G4HepEmTLData* tlData, struct G4HepEmData* hepEmData,
0036                                              bool iselectron, bool isSBmodel) {
0037   //
0038   G4HepEmElectronTrack* thePrimaryElTrack = tlData->GetPrimaryElectronTrack();
0039   G4HepEmTrack* thePrimaryTrack = thePrimaryElTrack->GetTrack();
0040   //
0041   double              thePrimEkin = thePrimaryTrack->GetEKin();
0042   const double        theLogEkin  = thePrimaryTrack->GetLogEKin();
0043   const int             theMCIndx = thePrimaryTrack->GetMCIndex();
0044   const G4HepEmMCCData& theMCData = hepEmData->fTheMatCutData->fMatCutData[theMCIndx];
0045   const double          theGamCut = theMCData.fSecGamProdCutE;
0046   // return if intercation is not possible (should not happen)
0047   if (thePrimEkin <= theGamCut) return;
0048   //
0049   // == Sampling of the emitted photon energy
0050   const double eGamma = isSBmodel
0051                         ? SampleETransferSB(hepEmData, thePrimEkin, theLogEkin, theMCIndx, tlData->GetRNGEngine(), iselectron)
0052                         : SampleETransferRB(hepEmData, thePrimEkin, theLogEkin, theMCIndx, tlData->GetRNGEngine(), iselectron);
0053   // get a secondary photon track and sample directions (all will be already in lab. frame)
0054   G4HepEmTrack* theSecTrack = tlData->AddSecondaryGammaTrack()->GetTrack();
0055   double*    theSecGammaDir = theSecTrack->GetDirection();
0056   double*    thePrimElecDir = thePrimaryTrack->GetDirection();
0057   //
0058   // == Sampling of the emitted photon and post interaction e-/e+ directions
0059   SampleDirections(thePrimEkin, eGamma, theSecGammaDir, thePrimElecDir, tlData->GetRNGEngine());
0060   //
0061   // == Update primary and set secondary gamma track properties
0062   //    note: the directions are already set in SampleDirections
0063   thePrimaryTrack->SetEKin(thePrimEkin - eGamma);
0064   theSecTrack->SetEKin(eGamma);
0065   theSecTrack->SetParentID(thePrimaryTrack->GetID());
0066   // NOTE: the following usually set to very high energy so I don't include this.
0067   // if secondary gamma energy is higher than threshold(very high by default)
0068   // then stop tracking the primary particle and create new secondary e-/e+
0069   // instead of the primary
0070 }
0071 
0072 
0073 double G4HepEmElectronInteractionBrem::SampleETransferSB(struct G4HepEmData* hepEmData, double thePrimEkin,
0074                                                          double theLogEkin, int theMCIndx,
0075                                                          G4HepEmRandomEngine* rnge, bool iselectron) {
0076   const G4HepEmMCCData& theMCData = hepEmData->fTheMatCutData->fMatCutData[theMCIndx];
0077   const double          theGamCut = theMCData.fSecGamProdCutE;
0078   const double       theLogGamCut = theMCData.fLogSecGamCutE;
0079   const G4HepEmMatData& theMData = hepEmData->fTheMaterialData->fMaterialData[theMCData.fHepEmMatIndex];
0080   // sample target element
0081   const G4HepEmElectronData* theElData = iselectron ? hepEmData->fTheElectronData : hepEmData->fThePositronData;
0082   const int elemIndx = (theMData.fNumOfElement > 1)
0083                        ? SelectTargetAtom(theElData, theMCIndx, thePrimEkin, theLogEkin, rnge->flat(), true)
0084                        : 0;
0085   const int     iZet = theMData.fElementVect[elemIndx];
0086   const double  dZet = (double)iZet;
0087   //
0088   // == Sampling of the emitted photon energy
0089   // get the G4HepEmSBTableData structure
0090   const G4HepEmSBTableData* theSBTables = hepEmData->fTheSBTableData;
0091   // get the start index of sampling tables for this Z
0092   const int iStart   = theSBTables->fSBTablesStartPerZ[iZet];
0093   // get the index of the gamma-cut cumulative in this Z data that corresponds to this mc
0094   const int iGamCut  = theSBTables->fGammaCutIndices[theSBTables->fGammaCutIndxStartIndexPerMC[theMCIndx]+elemIndx];
0095   // find the lower energy grid index i.e. `i` such that E_i <= E < E_{i+1}
0096   // find lower e- energy bin
0097   bool      isCorner = false; // indicate that the lower edge e- energy < gam-gut
0098   bool      isSimply = false; // simply sampling: isCorner+lower egde is selected
0099   int   elEnergyIndx = (int)(theSBTables->fSBTableData[iStart+2]);  // maxE-grid index for this Z
0100   // only if e- ekin is below the maximum value(use table at maximum otherwise)
0101   if (thePrimEkin < theSBTables->fElEnergyVect[elEnergyIndx]) {
0102     const double val = (theLogEkin-theSBTables->fLogMinElEnergy)*theSBTables->fILDeltaElEnergy;
0103     elEnergyIndx     = (int)val;
0104     double pIndxH    = val-elEnergyIndx;
0105     // check if we are at limiting case: lower edge e- energy < gam-gut
0106     if (theSBTables->fElEnergyVect[elEnergyIndx] <= theGamCut) {
0107       // recompute the probability of taking the higher e- energy bin()
0108       pIndxH   = (theLogEkin-theLogGamCut)/(theSBTables->fLElEnergyVect[elEnergyIndx+1]-theLogGamCut);
0109       isCorner = true;
0110     }
0111     //
0112     if (rnge->flat()<pIndxH) {
0113       ++elEnergyIndx;      // take the table at the higher e- energy bin
0114     } else if (isCorner) { // take the table at the lower  e- energy bin
0115       // special sampling need to be done if lower edge e- energy < gam-gut:
0116       // actually, we "sample" from a table "built" at the gamm-cut i.e. delta
0117       isSimply = true;
0118     }
0119   }
0120   // compute the start index of the sampling table data for this `elEnergyIndx`
0121   const int   minEIndx = (int)(theSBTables->fSBTableData[iStart+1]);
0122   const int numGamCuts = (int)(theSBTables->fSBTableData[iStart+3]);
0123   const int   sizeOneE = (int)(numGamCuts + 3*theSBTables->fNumKappa);
0124   const int   iSTStart = iStart + 4 + (elEnergyIndx-minEIndx)*sizeOneE;
0125   // the minimum value of the cumulative (that corresponds to the kappa-cut value)
0126   const double    minV = theSBTables->fSBTableData[iSTStart+iGamCut];
0127   // the start of the table with the 54 kappa-cumulative and par-A and par-B values.
0128   const double* stData = &(theSBTables->fSBTableData[iSTStart+numGamCuts]);
0129   // some transfomrmtion variables used in the looop
0130 //  const double lCurKappaC  = theLogGamCut-theLogEkin;
0131 //  const double lUsedKappaC = theLogGamCut-theSBTables->fLElEnergyVect[elEnergyIndx];
0132   const double lKTrans = (theLogGamCut-theLogEkin)/(theLogGamCut-theSBTables->fLElEnergyVect[elEnergyIndx]);
0133   // dielectric (always) and e+ correction suppressions (if the primary is e+)
0134   const double primETot = thePrimEkin + kElectronMassC2;
0135   const double dielSupConst = theMData.fDensityCorFactor*primETot*primETot;
0136   double suppression = 1.0;
0137   double rndm[2];
0138   // rejection loop starts here (rejection only for the diel-supression)
0139   double eGamma = 0.0;
0140   do {
0141     rnge->flatArray(2, rndm);
0142     double kappa = 1.0;
0143     if (!isSimply) {
0144       const double cumRV  = rndm[0]*(1.0-minV)+minV;
0145       // find lower index of the values in the Cumulative Function: use linear
0146       // instead of binary search because it's faster in our case
0147       // note: every 3rd value of `stData` is the cumulative for the corresponding kappa grid values
0148       const int cumLIndx3 = LinSearch(stData, theSBTables->fNumKappa, cumRV) - 3;
0149       const int  cumLIndx = cumLIndx3/3;
0150       const double   cumL = stData[cumLIndx3];
0151       const double     pA = stData[cumLIndx3+1];
0152       const double     pB = stData[cumLIndx3+2];
0153       const double   cumH = stData[cumLIndx3+3];
0154       const double    lKL = theSBTables->fLKappaVect[cumLIndx];
0155       const double    lKH = theSBTables->fLKappaVect[cumLIndx+1];
0156       const double    dm1 = (cumRV-cumL)/(cumH-cumL);
0157       const double    dm2 = (1.0+pA+pB)*dm1;
0158       const double    dm3 = 1.0+dm1*(pA+pB*dm1);
0159       // kappa sampled at E_i e- energy
0160       const double lKappa = lKL+dm2/dm3*(lKH-lKL);
0161       // transform lKappa to [log(gcut/ekin),0] form [log(gcut/E_i),0]
0162       kappa  = G4HepEmExp(lKappa*lKTrans);
0163      } else {
0164       kappa = 1.0-rndm[0]*(1.0-theGamCut/thePrimEkin);
0165      }
0166      // compute the emitted photon energy: k
0167      eGamma = kappa*thePrimEkin;
0168      const double invEGamma = 1.0/eGamma;
0169      // compute dielectric suppression: 1/(1+[gk_p/k]^2)
0170      suppression = 1.0/(1.0+dielSupConst*invEGamma*invEGamma);
0171      // add positron correction if particle is e+
0172      if (!iselectron) {
0173        const double     e1 = thePrimEkin - theGamCut;
0174        const double iBeta1 = (e1 + kElectronMassC2) / std::sqrt(e1*(e1 + 2.0*kElectronMassC2));
0175        const double     e2 = thePrimEkin - eGamma;
0176        const double iBeta2 = (e2 + kElectronMassC2) / std::sqrt(e2*(e2 + 2.0*kElectronMassC2));
0177        const double    dum = kAlpha*k2Pi*dZet*(iBeta1 - iBeta2);
0178        suppression = (dum > -12.) ? suppression*G4HepEmExp(dum) : 0.;
0179      }
0180    } while (rndm[1] > suppression);
0181    return eGamma;
0182 }
0183 
0184 double G4HepEmElectronInteractionBrem::SampleETransferRB(struct G4HepEmData* hepEmData, double thePrimEkin,
0185                                                              double theLogEkin, int theMCIndx,
0186                                                              G4HepEmRandomEngine* rnge, bool iselectron) {
0187   const G4HepEmMCCData& theMCData = hepEmData->fTheMatCutData->fMatCutData[theMCIndx];
0188   const double          theGamCut = theMCData.fSecGamProdCutE;
0189 //  const double       theLogGamCut = theMCData.fLogSecGamCutE;
0190 
0191   // get the material data
0192   const G4HepEmMatData&  theMData = hepEmData->fTheMaterialData->fMaterialData[theMCData.fHepEmMatIndex];
0193   // sample target element
0194   const G4HepEmElectronData*  theElData = iselectron ? hepEmData->fTheElectronData : hepEmData->fThePositronData;
0195   const int elemIndx = (theMData.fNumOfElement > 1)
0196                        ? SelectTargetAtom(theElData, theMCIndx, thePrimEkin, theLogEkin, rnge->flat(), false)
0197                        : 0;
0198   const int     iZet = theMData.fElementVect[elemIndx];
0199   const double  dZet = (double)iZet;
0200   const G4HepEmElemData& theElemData = hepEmData->fTheElementData->fElementData[G4HepEmMin(iZet, hepEmData->fTheElementData->fMaxZet)];
0201   //
0202   // == Sampling of the emitted photon energy
0203   // - compute lpm energy
0204   const double densityFactor = kMigdalConst * theMData.fElectronDensity;
0205   const double     lpmEnergy = kLPMconstant * theMData.fRadiationLength;
0206   // threshold for LPM effect (i.e. below which LPM hidden by density effect)
0207   const double  lpmEnergyLim = std::sqrt(densityFactor) * lpmEnergy;
0208   // compute the density, i.e. dielectric suppression correction factor
0209   const double thePrimTotalE = thePrimEkin + kElectronMassC2;
0210   const double   densityCorr = densityFactor * thePrimTotalE * thePrimTotalE;
0211   // LPM effect is turned off if thePrimTotalE < lpmEnergyLim
0212   const bool     isLPMActive = (thePrimTotalE > lpmEnergyLim) ;
0213   // compute/set auxiliary variables used in the energy transfer sampling
0214   const double      zFactor1 = theElemData.fZFactor1;
0215   const double      zFactor2 = (1.+1./dZet)/12.;
0216   const double    rejFuncMax = zFactor1 + zFactor2;
0217   // min and range of the transformed variable: x(k) = ln(k^2+k_p^2) that is in [ln(k_c^2+k_p^2), ln(E_k^2+k_p^2)]
0218   const double xmin   = G4HepEmLog( theGamCut*theGamCut     + densityCorr );
0219   const double xrange = G4HepEmLog( thePrimEkin*thePrimEkin + densityCorr ) - xmin;
0220   // sampling the emitted gamma energy
0221   double rndm[2];
0222   double eGamma, funcVal;
0223   do {
0224     rnge->flatArray(2, rndm);
0225     eGamma = std::sqrt( G4HepEmMax( G4HepEmExp( xmin + rndm[0] * xrange ) - densityCorr, 0.0 ) );
0226     // evaluate the DCS at this emitted gamma energy
0227     const double y     = eGamma / thePrimTotalE;
0228     const double onemy = 1.-y;
0229     const double dum0  = 0.25*y*y;
0230     if ( isLPMActive ) { // DCS: Bethe-Heitler in complete screening and LPM suppression
0231       // evaluate LPM functions (combined with the Ter-Mikaelian effect)
0232       double funcGS, funcPhiS, funcXiS;
0233       EvaluateLPMFunctions(funcXiS, funcGS, funcPhiS, eGamma, thePrimTotalE, lpmEnergy, theElemData.fZet23, theElemData.fILVarS1, theElemData.fILVarS1Cond, densityCorr, 1.0);
0234       const double term1 = funcXiS * ( dum0 * funcGS + (onemy+2.0*dum0) * funcPhiS );
0235       funcVal = term1*zFactor1 + onemy*zFactor2;
0236     } else {  // DCS: Bethe-Heitler without LPM suppression and complete screening only if Z<5 (becaue TF screening is not vaild for low Z)
0237       const double dum1 = onemy + 3.*dum0;
0238       if ( iZet < 5 ) { // DCS: complete screening
0239         funcVal = dum1 * zFactor1 + onemy * zFactor2;
0240       } else { // DCS: analytical approximations to the universal screening functions (based on TF model of atom)
0241         const double dum2 = y / ( thePrimTotalE - eGamma );
0242         const double gam  = dum2 * 100.*kElectronMassC2 / theElemData.fZet13;
0243         const double eps  = gam / theElemData.fZet13;
0244         // evaluate the screening functions (TF model of the atom, Tsai's aprx.):
0245 
0246         const double gam2 = gam*gam;
0247         const double phi1 = 16.863-2.0*G4HepEmLog(1.0+0.311877*gam2)+2.4*G4HepEmExp(-0.9*gam)+1.6*G4HepEmExp(-1.5*gam);
0248         const double phi2 = 2.0/(3.0+19.5*gam+18.0*gam2);    // phi1-phi2
0249         const double eps2 = eps*eps;
0250         const double psi1 = 24.34-2.0*G4HepEmLog(1.0+13.111641*eps2)+2.8*G4HepEmExp(-8.0*eps)+1.2*G4HepEmExp(-29.2*eps);
0251         const double psi2 = 2.0/(3.0+120.0*eps+1200.0*eps2); //psi1-psi2
0252         //
0253         const double logZ = theElemData.fLogZ;
0254         const double Fz   = logZ/3. + theElemData.fCoulomb;
0255         const double invZ = 1./dZet;
0256         funcVal = dum1*((0.25*phi1-Fz) + (0.25*psi1-2.*logZ/3.)*invZ) +  0.125*onemy*(phi2 + psi2*invZ);
0257       }
0258     }
0259     funcVal = G4HepEmMax( 0.0, funcVal);
0260   } while ( funcVal < rejFuncMax * rndm[1] );
0261   return eGamma;
0262 }
0263 
0264 
0265 // should be called only for mat-cuts with more than one elements in their material
0266 int G4HepEmElectronInteractionBrem::SelectTargetAtom(const struct G4HepEmElectronData* elData, const int imc,
0267                                                      const double ekin, const double lekin, const double urndn,
0268                                                      const bool isbremSB) {
0269   // start index for this mat-cut and this model (-1 is no elememnt selector i.e. single element material)
0270   const int   indxStart = isbremSB
0271                           ? elData->fElemSelectorBremSBStartIndexPerMatCut[imc]
0272                           : elData->fElemSelectorBremRBStartIndexPerMatCut[imc];
0273   const double* theData = isbremSB
0274                           ? &(elData->fElemSelectorBremSBData[indxStart])
0275                           : &(elData->fElemSelectorBremRBData[indxStart]);
0276   const int     numData = theData[0];
0277   const int     numElem = theData[1];
0278   const double    logE0 = theData[2];
0279   const double    invLD = theData[3];
0280   const double*   xdata = &(theData[4]);
0281   // make sure that $x \in  [x[0],x[ndata-1]]$
0282   const double   xv = G4HepEmMax(xdata[0], G4HepEmMin(xdata[numElem*(numData-1)], ekin));
0283   // compute the lowerindex of the x bin (idx \in [0,N-2] will be guaranted)
0284   const int idxEkin = G4HepEmMax(0.0, G4HepEmMin((lekin-logE0)*invLD, numData-2.0));
0285   // the real index position is idxEkin x numElem
0286   int   indx0 = idxEkin*numElem;
0287   int   indx1 = indx0+numElem;
0288   // linear interpolation
0289   const double   x1 = xdata[indx0++];
0290   const double   x2 = xdata[indx1++];
0291   const double   dl = x2-x1;
0292   const double    b = G4HepEmMax(0., G4HepEmMin(1., (xv - x1)/dl));
0293   int theElemIndex = 0;
0294   while (theElemIndex<numElem-1 && urndn > xdata[indx0+theElemIndex]+b*(xdata[indx1+theElemIndex]-xdata[indx0+theElemIndex])) { ++theElemIndex; }
0295   return theElemIndex;
0296 }
0297 
0298 
0299 void G4HepEmElectronInteractionBrem::SampleDirections(const double thePrimEkin, const double theSecGammaEkin,
0300                                                       double* theSecGammaDir, double* thePrimElecDir,
0301                                                       G4HepEmRandomEngine* rnge) {
0302   // sample photon direction (modified Tsai sampling):
0303   const double cost = SampleCostModifiedTsai(thePrimEkin, rnge);
0304   const double sint = std::sqrt((1.0-cost)*(1.0+cost));
0305   const double  phi = k2Pi*rnge->flat();
0306   theSecGammaDir[0] = sint * std::cos(phi);
0307   theSecGammaDir[1] = sint * std::sin(phi);
0308   theSecGammaDir[2] = cost;
0309   // rotate to refernce frame (G4HepEmRunUtils function) to get it in lab. frame
0310   RotateToReferenceFrame(theSecGammaDir, thePrimElecDir);
0311   // go for the post-interaction primary electron/positiorn direction in lab. farme
0312   const double primETot = thePrimEkin + kElectronMassC2;
0313   const double primPTot = std::sqrt(thePrimEkin * (primETot + kElectronMassC2));
0314   thePrimElecDir[0] = primPTot * thePrimElecDir[0] - theSecGammaEkin * theSecGammaDir[0];
0315   thePrimElecDir[1] = primPTot * thePrimElecDir[1] - theSecGammaEkin * theSecGammaDir[1];
0316   thePrimElecDir[2] = primPTot * thePrimElecDir[2] - theSecGammaEkin * theSecGammaDir[2];
0317   // normalisation
0318   const double  norm = 1.0 / std::sqrt(thePrimElecDir[0] * thePrimElecDir[0] + thePrimElecDir[1] * thePrimElecDir[1] + thePrimElecDir[2] * thePrimElecDir[2]);
0319   thePrimElecDir[0] *= norm;
0320   thePrimElecDir[1] *= norm;
0321   thePrimElecDir[2] *= norm;
0322 }
0323 
0324 
0325 // find lower bin index of value: used in acse of CDF values i.e. val in [0,1)
0326 // while vector elements in [0,1]
0327 // note: every 3rd value of the vect contains the kappa-cumulutaive values
0328 int G4HepEmElectronInteractionBrem::LinSearch(const double* vect, const int size, const double val) {
0329   int i = 0;
0330   const int size3 = 3*size;
0331   while (i + 9 < size3) {
0332     if (vect [i + 0] > val) return i + 0;
0333     if (vect [i + 3] > val) return i + 3;
0334     if (vect [i + 6] > val) return i + 6;
0335     if (vect [i + 9] > val) return i + 9;
0336     i += 12;
0337   }
0338   while (i < size3) {
0339     if (vect [i] > val)
0340       break;
0341     i += 3;
0342   }
0343   return i;
0344 }