Back to home page

EIC code displayed by LXR

 
 

    


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

0001 #include "G4HepEmElectronInteractionUMSC.hh"
0002 
0003 #include "G4HepEmRandomEngine.hh"
0004 
0005 #include "G4HepEmMSCTrackData.hh"
0006 
0007 #include "G4HepEmConstants.hh"
0008 #include "G4HepEmMath.hh"
0009 
0010 #include "G4HepEmData.hh"
0011 #include "G4HepEmParameters.hh"
0012 
0013 #include "G4HepEmMaterialData.hh"
0014 
0015 // The msc step limit will be written into the G4HepEmMSCTrackData::fTrueStepLength member of
0016 // of the input track. If msc limits the step, this is shorter than the track->GetPStepLength.
0017 // Note, that in all cases, the final physical step length will need to be coverted to geometrical
0018 // one that is done in the G4HepEmElectronManager.
0019 void G4HepEmElectronInteractionUMSC::StepLimit(G4HepEmData* hepEmData, G4HepEmParameters* hepEmPars,
0020     G4HepEmMSCTrackData* mscData, double ekin, int imat, int iregion, double range, double presafety,
0021     bool onBoundary, bool iselectron, G4HepEmRandomEngine* rnge) {
0022   // Initial values:
0023   //  - lengths are already initialised to the current minimum physics step  which is the true, minimum
0024   //    step length from all other physics
0025   //  - the first transport mean free path value, i.e. `mscData.fLambtr1` is also assumed to be up to date
0026   mscData->fIsNoScatteringInMSC = false;
0027   mscData->fIsDisplace          = true;
0028 
0029   // stop in case of:
0030   // - very small steps
0031   // - will never leave the current volume boundary (the correction on range accounts fluctuation)
0032   // and indicate no-dispalcement.
0033   const double kTLimitMinfix = 1.0E-8; // 0.01 [nm] 1.0E-8 [mm]
0034   const struct G4HepEmMatData& matData = hepEmData->fTheMaterialData->fMaterialData[imat];
0035   if (mscData->fTrueStepLength < kTLimitMinfix || range*(matData.fUMSCPar) < presafety) {
0036     mscData->fIsDisplace = false;
0037     return;
0038   }
0039   const double mscRangeFactor  =  hepEmPars->fParametersPerRegion[iregion].fMSCRangeFactor;
0040   const double mscSafetyFactor =  hepEmPars->fParametersPerRegion[iregion].fMSCSafetyFactor;
0041   const bool   mscIsUseSafety  = !hepEmPars->fParametersPerRegion[iregion].fIsMSCMinimalStepLimit;
0042 
0043   double tlimit = 0.0;
0044   if (mscIsUseSafety) {
0045       // The `fUseSafety` Urban MSC step limit (i.e. sdandard EM):
0046       // ---------------------------------------------------------
0047       // set the initial range, dynamic range factor, minimal step and true step values
0048       // if just enetring to a new volume or performing the very first step with this
0049       // particle (or more exactly, reaching this point first time)
0050       if (mscData->fIsFirstStep || onBoundary) {
0051         const double lambdaTr1 = mscData->fLambtr1;
0052         mscData->fInitialRange = G4HepEmMax(range, lambdaTr1);
0053         // note: the below is true only because `lambdaLimit = 1.0 [mm]`
0054         //const double kILambdaLimit   = 1.0; // 1/(kLambdaLimit = 1.0 [mm])
0055         mscData->fDynamicRangeFactor = lambdaTr1 > 1.0
0056                                        ? mscRangeFactor*(0.75 + 0.25*lambdaTr1)
0057                                        : mscRangeFactor;
0058         // note: `ekin` below is the kinetic energy in MeV;
0059         const double stepMin = lambdaTr1*1.0E-3/(2.0E-3 + ekin*(matData.fUMSCStepMinPars[0] + ekin*matData.fUMSCStepMinPars[1]));
0060 
0061         // there is some difference in the algorithm from G4.11.0 that (together with
0062         // the differences in the fUMSCPar, fUMSCStepMinPars[0,1] parameter values) gives
0063         // 2-3 % lower number of HITS (e.g. lower number of steps) in ATLAS when using
0064         // G4.11.0 compred to older G4 versions such as G4.10.6.p03.
0065         // We include this to ease validation though this is an improvment of G4 11.0
0066 #if G4VERSION_NUM < 1070
0067         // G4 version before 10.7
0068         mscData->fTlimitMin  = G4HepEmMax(0.70*matData.fZeffSqrt*stepMin, kTLimitMinfix);
0069 #else
0070         // this is like in Geant4.11.0
0071         const double dum0    = iselectron ? 0.87*matData.fZeff23 : 0.70*matData.fZeffSqrt;
0072         // note `tlow` = 5 [keV] ==> 5.0E-3 [MeV] ==> 1/tlow = 200 [1/MeV]
0073         const double dum1    = ekin > 5.0E-3 ? dum0*stepMin : dum0*stepMin*0.5*(1.0 + ekin*200.0);
0074         mscData->fTlimitMin  = G4HepEmMax(dum1, kTLimitMinfix);
0075 #endif
0076 
0077         // reset first step flag (if any)
0078         mscData->fIsFirstStep = false;
0079       }
0080       // the true step limit
0081       const double tlimitmin = mscData->fTlimitMin;
0082       tlimit    = range > presafety
0083                   ? G4HepEmMax(G4HepEmMax(mscData->fInitialRange*mscData->fDynamicRangeFactor, mscSafetyFactor*presafety), tlimitmin)
0084                   : G4HepEmMax(range, tlimitmin);
0085   } else {
0086       // The `fMinimal` Urban MSC step limit (i.e. sdandard EM):
0087       // ---------------------------------------------------------
0088       // NOTE: `tlimit` is updated only when moving to a new volume (see below).
0089       //  Its value is stored in `mscData->fInitialRange` instead of introducing a
0090       //  new memebr as `fInitialRange` is not used when the step limit is `fMinimal`
0091       //
0092       // NOTE: `tlimit` stays its initial value till the first boundary which is
0093       //  very large (1E+21 mm in our case while 1E+50 in G4UrbanMscModel) so
0094       //  no MSC step limit till the very first boundary for sure.
0095       if (onBoundary) {
0096         const double lambdaTr1 = mscData->fLambtr1;
0097         const double tmpTlimit = range > lambdaTr1
0098                                  ? mscRangeFactor*range
0099                                  : mscRangeFactor*lambdaTr1;
0100         // store `tlimit` in `mscData->fInitialRange` instead of introducing
0101         // a new member as `fInitialRange` is not used when the step limit is
0102         // `fMinimal`; note: tlimitmin = 10*kTLimitMinfix
0103         mscData->fInitialRange = G4HepEmMax(tmpTlimit,  10*kTLimitMinfix);
0104       }
0105       tlimit = mscData->fInitialRange; // initialised to 1E+21 mm at start tracking
0106   }
0107 
0108   // NOTE: on `tlimit` and `tlimitmin`
0109   // `fUseSafety`: `tlimit` is updated in each step while `tlimitmin` only at first of entering to a new volume
0110   // `fMinimal`  : `tlimit` is updated only when enetring to a new volume while `tlimitmin` is constant (10x kTLimitMinfix)
0111   const double tlimitmin = mscData->fTlimitMin;
0112   // randomise the true step limit but only if the step was determined by msc
0113   if (tlimit < mscData->fTrueStepLength) { // keep mscData->fTrueStepLength otherwise as teh current step-limit --> not msc limited this step
0114     const double dum0 = tlimit > tlimitmin
0115                         ? G4HepEmMax(rnge->Gauss(tlimit, 0.1*(tlimit - tlimitmin)), tlimitmin)
0116                         : tlimitmin;
0117     mscData->fTrueStepLength = G4HepEmMin(dum0, mscData->fTrueStepLength);
0118   }
0119 
0120   // msc step limit is done!
0121   //
0122   // convert the true (physics) step length to geometrical one (i.e. the pojection of the transport vector
0123   // along the original direction)
0124   // NOTE: this conversion is done in the G4HepEmElectronManager
0125   // ConvertTrueToGeometricLength(...)
0126 }
0127 
0128 
0129 void G4HepEmElectronInteractionUMSC::SampleScattering(G4HepEmData* hepEmData, G4HepEmMSCTrackData* mscData,
0130      double pStepLength, double preStepEkin, double preStepTr1mfp, double postStepEkin, double postStepTr1mfp,
0131      int imat, bool isElectron, bool isPosCor, bool isDisplace, G4HepEmRandomEngine* rnge) {
0132   const struct G4HepEmMatData& matData = hepEmData->fTheMaterialData->fMaterialData[imat];
0133   const double cost  = SampleCosineTheta(pStepLength, preStepEkin, preStepTr1mfp, postStepEkin, postStepTr1mfp, mscData->fTlimitMin,
0134                          matData.fRadiationLength, matData.fZeff, matData.fUMSCTailCoeff, matData.fUMSCThetaCoeff, isElectron, isPosCor, rnge);
0135   // no scattering so no dispacement in case cost = 1.0
0136   if (std::abs(cost) >= 1.0) {
0137     mscData->fIsNoScatteringInMSC = true;
0138     // mscData->fIsDisplace = false;
0139     return;
0140   }
0141   const double sth = std::sqrt((1.0 - cost)*(1.0 + cost));
0142   const double phi = k2Pi*rnge->flat();
0143   mscData->SetNewDirection(sth*std::cos(phi), sth*std::sin(phi), cost);
0144   // compute/sample dispacement
0145   mscData->fIsDisplace = mscData->fIsDisplace && isDisplace;
0146   if (mscData->fIsDisplace && pStepLength > mscData->fZPathLength) {
0147     SampleDisplacement(pStepLength, phi, mscData, rnge);
0148   }
0149 }
0150 
0151 
0152 double G4HepEmElectronInteractionUMSC::SampleCosineTheta(double pStepLength, double preStepEkin, double preStepTr1mfp,
0153        double postStepEkin, double postStepTr1mfp, double umscTlimitMin, double radLength, double zeff,
0154        const double* umscTailCoeff, const double* umscThetaCoeff, bool isElectron, bool isPosCor, G4HepEmRandomEngine* rnge) {
0155   // NOTE: since I already know the finalEnergy in the electron Manager and that is already set to the track,
0156   //       I could get the logFinalEnergy from the updated track which could save up one log call
0157   // compute the 1rst transport mfp at the post-step point energy
0158   const double iPreStepTr1mfp = 1.0/preStepTr1mfp;
0159   const double deltaR1mfp     = preStepTr1mfp - postStepTr1mfp;
0160   const double tau = std::abs(deltaR1mfp) > 0.01*preStepTr1mfp
0161                      ? pStepLength*G4HepEmLog(preStepTr1mfp/postStepTr1mfp)/deltaR1mfp
0162                      : pStepLength*iPreStepTr1mfp;
0163   //
0164   // Note:  `currentTau = tau` that is used in G4Urban before the displacement
0165   //        sampling and dispacement is done only if (currentTau >= tausmall)
0166   //        However, here if (tau < tausmall) we return `cost = 1` that will
0167   //        result an immediate stop in the caller SampleScattering since this
0168   //        means no scattering. So we never even try to sample dispacement if
0169   //        currentTau = tau < tausmall.
0170 
0171   //
0172   // isotropic case i.e. uniform cost
0173   const double kTauBig   = 8.0;
0174   if (tau > kTauBig) {
0175     return 2.0*rnge->flat() - 1.0;
0176   }
0177   //
0178   // zero scattering i.e. cost = 1.0
0179   const double kTauSmall = 1.0E-16;
0180   if (tau < kTauSmall) {
0181     return 1.0;
0182   }
0183   //
0184   // normal case: sample cost
0185   double xmeanth, x2meanth;
0186   if (tau < 0.01) {
0187     // taylor series approximation
0188     xmeanth  = 1.0 - tau*(1.0 - 0.5*tau);
0189     x2meanth = 1.0 - tau*(5.0 - 6.25*tau)*0.333333;
0190   } else {
0191     xmeanth  = G4HepEmExp(-tau);
0192     x2meanth = (1.0 + 2.0*G4HepEmExp(-2.5*tau))*0.333333;
0193   }
0194   //  - too large step of low-energy particle
0195   if (postStepEkin < 0.5*preStepEkin) {
0196     return SimpleScattering(xmeanth, x2meanth, rnge);
0197   }
0198   // - sample theta0 and check if the step is extreme small
0199   //   note: below `lambdaLimit = 1.0 [mm]`
0200   const double   tsmall    = G4HepEmMin(umscTlimitMin, 1.0);
0201   const bool stpNotExSmall = pStepLength > tsmall;
0202   const double theta0      = stpNotExSmall
0203                             ? ComputeTheta0(pStepLength/radLength, postStepEkin, preStepEkin, zeff, umscThetaCoeff, isElectron, isPosCor)
0204                             : ComputeTheta0(tsmall/radLength,      postStepEkin, preStepEkin, zeff, umscThetaCoeff, isElectron, isPosCor)*std::sqrt(pStepLength/tsmall);
0205   //
0206   if (theta0 > kPi*0.166666) {
0207     return SimpleScattering(xmeanth, x2meanth, rnge);
0208   }
0209   // protection for very small angles (cost = 1)
0210   const double theta2 = theta0*theta0;
0211   if (theta2 < kTauSmall) {
0212     // zero scattering i.e. cost = 1.0
0213     return 1.0;
0214   }
0215 
0216   // parameter for tail
0217   const double dumtau = stpNotExSmall ? tau : tsmall*iPreStepTr1mfp;
0218   const double parU   = G4HepEmPow(dumtau, 0.1666666);
0219   const double dumxsi = umscTailCoeff[0] + parU*(umscTailCoeff[1] + parU*umscTailCoeff[2])
0220                         + umscTailCoeff[3]*G4HepEmLog(pStepLength/(tau*radLength));
0221   // tail should not be too big
0222   const double parXsi = G4HepEmMax(dumxsi, 1.9);
0223   //
0224   const double   parC =   std::abs(parXsi - 3.) < 0.001 ? 3.001
0225                         : std::abs(parXsi - 2.) < 0.001 ? 2.001
0226                         : parXsi;
0227   const double dumC1  = parC - 1.;
0228   const double dumEa  = G4HepEmExp(-parXsi);
0229   const double dumEaa = 1./(1. - dumEa);
0230   double thex = theta2*(1.0 - theta2*0.0833333);
0231   if (theta2 > 0.01) {
0232     const double  dum = 2.0*std::sin(0.5*theta0);
0233     thex = dum*dum;
0234   }
0235   const double xmean1 = 1. - (1. - (1. + parXsi)*dumEa)*thex*dumEaa;
0236   //
0237   if (xmean1 <= 0.999*xmeanth) {
0238     return SimpleScattering(xmeanth, x2meanth, rnge);
0239   }
0240   //
0241   const double  x0 = 1. - parXsi*thex;
0242   const double  bx = parC*thex;
0243 
0244   const double   b = bx + x0;
0245   const double  b1 = b + 1.;
0246 
0247   const double eb1 = G4HepEmPow(b1, dumC1);
0248   const double ebx = G4HepEmPow(bx, dumC1);
0249   const double   d = ebx/eb1;
0250 
0251   const double xmean2 = (x0 + d - (bx - b1*d)/(parC - 2.))/(1. - d);
0252 
0253   const double f1x0 = dumEa*dumEaa;
0254   const double f2x0 = dumC1/(parC*(1. - d));
0255   const double prob = f2x0/(f1x0 + f2x0);
0256 
0257   const double qprb = xmeanth/(prob*xmean1 + (1. - prob)*xmean2);
0258   //
0259   // sampling of cost
0260   double rndArray[3];
0261   rnge->flatArray(3, rndArray);
0262   if (rndArray[0] < qprb) {
0263     if (rndArray[1] < prob) {
0264       return  1. + G4HepEmLog(dumEa + rndArray[2]/dumEaa)*thex;
0265     } else {
0266       const double var0 = (1.0 - d)*rndArray[2];
0267       if (var0 < 0.01*d) {
0268         const double var = var0/(d*dumC1);
0269         return -1.0 + var*(1.0 - var*0.5*parC)*b1;
0270       } else {
0271         return  1.0 + thex*(parC - parXsi - parC*G4HepEmPow(var0 + d, -1./dumC1));
0272       }
0273     }
0274   } else {
0275     return 2.0*rndArray[1] - 1.0;
0276   }
0277 }
0278 
0279 
0280 double G4HepEmElectronInteractionUMSC::SimpleScattering(double xmeanth, double x2meanth, G4HepEmRandomEngine* rnge) {
0281   // 'large angle scattering'
0282   // 2 model functions with correct xmean and x2mean
0283   const double dum0 = 3.*x2meanth - 1;
0284   const double dum1 = 2.*xmeanth  - dum0;
0285   const double    a = 1. + 4.*dum0/dum1;
0286   const double prob = (2. + a)*xmeanth/a;
0287   // sampling
0288   double rndArray[2];
0289   rnge->flatArray(2, rndArray);
0290   return (rndArray[0] < prob)
0291          ? -1. + 2.*G4HepEmPow(rndArray[1],1./(1. + a))
0292          : -1. + 2.*rndArray[1];
0293 }
0294 
0295 
0296 // totry: all these could probably computed in `float`
0297 double G4HepEmElectronInteractionUMSC::ComputeTheta0(double stepInRadLength, double postStepEkin, double preStepEkin, double zeff,
0298        const double* umscThetaCoeff, bool isElectron, bool isPosCor) {
0299   // ( Highland formula: Particle Physics Booklet, July 2002, eq. 26.10)
0300   const double     kHighland = 13.6; // note:: assumed to be in MeV
0301   const double postInvBetaPc = (postStepEkin + kElectronMassC2)/(postStepEkin*(postStepEkin + 2.*kElectronMassC2));
0302   const double invBetaPc     = preStepEkin != postStepEkin
0303                                ? std::sqrt(postInvBetaPc*(preStepEkin + kElectronMassC2)/(preStepEkin*(preStepEkin + 2.*kElectronMassC2)))
0304                                : postInvBetaPc;
0305   const double y = (isElectron || !isPosCor)
0306                    ? stepInRadLength
0307                    : stepInRadLength * Theta0PositronCorrection(preStepEkin*postStepEkin, zeff);
0308   return kHighland*std::sqrt(y)*invBetaPc*(umscThetaCoeff[0] + umscThetaCoeff[1]*G4HepEmLog(y));
0309 }
0310 
0311 
0312 // totry: all these could probably be computed in `float`
0313 double G4HepEmElectronInteractionUMSC::Theta0PositronCorrection(double eekin, double zeff) {
0314   const double ff = 1. + zeff*(1.84035E-4*zeff - 1.86427E-2) + 0.41125;
0315   const double  a = 0.994 - 4.08E-3*zeff;
0316   const double  b = 7.16 + (52.6 + 365./zeff)/zeff;
0317   const double tu = std::sqrt(eekin)*kInvElectronMassC2;
0318   const double  x = std::sqrt(tu*(tu + 2.)/((tu + 1.)*(tu + 1.)));
0319   //
0320   const double xl = 0.6;
0321   if (x < xl) {
0322     return ff*a*(1. - G4HepEmExp(-b*x));
0323   }
0324   //
0325   const double  c = 1.00 - 4.47E-3*zeff;
0326   const double  d = 1.21E-3*zeff;
0327   const double  e = 113.0;
0328   const double xh = 0.9;
0329   if(x > xh) {
0330     return ff*(c + d*G4HepEmExp(e*(x - 1.)));
0331   }
0332   //
0333   const double yl = a*(1. - G4HepEmExp(-b*xl));
0334   const double yh = c + d*G4HepEmExp(e*(xh -1.));
0335   const double y0 = (yh - yl)/(xh - xl);
0336   const double y1 = yl - y0*xl;
0337   return ff*(y0*x + y1);
0338 }
0339 
0340 
0341 // note: should only be called if tru-step-length != z-step-length
0342 void G4HepEmElectronInteractionUMSC::SampleDisplacement(double pStepLength, double thePhi, G4HepEmMSCTrackData* mscData, G4HepEmRandomEngine* rnge) {
0343   // simple and fast sampling
0344   // based on single scattering results
0345   // u = r/rmax : mean value (r = 0.73*rmax where rmax:= sqrt(t-g)*(t+g))
0346   const double r = 0.73*std::sqrt((pStepLength - mscData->fZPathLength)*(pStepLength + mscData->fZPathLength));
0347   // simple distribution for v=Phi-phi=psi ~exp(-beta*v)
0348   // beta determined from the requirement that distribution should give
0349   // the same mean value than that obtained from the ss simulation
0350   const double cbeta  = 2.16;
0351   const double cbeta1 = 1. - G4HepEmExp(-cbeta*kPi);
0352   double rndArray[2];
0353   rnge->flatArray(2, rndArray);
0354   const double psi = -G4HepEmLog(1. - rndArray[0]*cbeta1)/cbeta;
0355   const double phi = (rndArray[1] < 0.5) ? thePhi + psi : thePhi - psi;
0356   mscData->SetDisplacement(r*std::cos(phi), r*std::sin(phi), 0.0);
0357 }