Geant4 Cross Reference |
1 // 2 // ******************************************************************** 3 // * License and Disclaimer * 4 // * * 5 // * The Geant4 software is copyright of the Copyright Holders of * 6 // * the Geant4 Collaboration. It is provided under the terms and * 7 // * conditions of the Geant4 Software License, included in the file * 8 // * LICENSE and available at http://cern.ch/geant4/license . These * 9 // * include a list of copyright holders. * 10 // * * 11 // * Neither the authors of this software system, nor their employing * 12 // * institutes,nor the agencies providing financial support for this * 13 // * work make any representation or warranty, express or implied, * 14 // * regarding this software system or assume any liability for its * 15 // * use. Please see the license in the file LICENSE and URL above * 16 // * for the full disclaimer and the limitation of liability. * 17 // * * 18 // * This code implementation is the result of the scientific and * 19 // * technical work of the GEANT4 collaboration. * 20 // * By using, copying, modifying or distributing the software (or * 21 // * any work based on the software) you agree to acknowledge its * 22 // * use in resulting scientific publications, and indicate your * 23 // * acceptance of all terms of the Geant4 Software license. * 24 // ******************************************************************** 25 // 26 // 27 // ------------------------------------------------------------------- 28 // 29 // GEANT4 Class file 30 // 31 // 32 // File name: G4eBremsstrahlungRelModel 33 // 34 // Author: Andreas Schaelicke 35 // 36 // Creation date: 12.08.2008 37 // 38 // Modifications: 39 // 40 // 13.11.08 add SetLPMflag and SetLPMconstant methods 41 // 13.11.08 change default LPMconstant value 42 // 13.10.10 add angular distributon interface (VI) 43 // 31.05.16 change LPMconstant such that it gives suppression variable 's' 44 // that consistent to Migdal's one; fix a small bug in 'logTS1' 45 // computation; better agreement with exp.(M.Novak) 46 // 15.07.18 improved LPM suppression function approximation (no artificial 47 // steps), code cleanup and optimizations,more implementation and 48 // model related comments, consistent variable naming (M.Novak) 49 // 50 // Main References: 51 // Y.-S.Tsai, Rev. Mod. Phys. 46 (1974) 815; Rev. Mod. Phys. 49 (1977) 421. 52 // S.Klein, Rev. Mod. Phys. 71 (1999) 1501. 53 // T.Stanev et.al., Phys. Rev. D25 (1982) 1291. 54 // M.L.Ter-Mikaelian, High-energy Electromagnetic Processes in Condensed Media, 55 // Wiley, 1972. 56 // 57 // ------------------------------------------------------------------- 58 // 59 60 #include "G4eBremsstrahlungRelModel.hh" 61 #include "G4PhysicalConstants.hh" 62 #include "G4SystemOfUnits.hh" 63 #include "G4Electron.hh" 64 #include "G4Gamma.hh" 65 #include "Randomize.hh" 66 #include "G4Material.hh" 67 #include "G4Element.hh" 68 #include "G4ElementVector.hh" 69 #include "G4ParticleChangeForLoss.hh" 70 #include "G4ModifiedTsai.hh" 71 #include "G4Exp.hh" 72 #include "G4Log.hh" 73 #include "G4Pow.hh" 74 #include "G4EmParameters.hh" 75 #include "G4AutoLock.hh" 76 #include <thread> 77 78 const G4int G4eBremsstrahlungRelModel::gMaxZet = 120; 79 80 // constant DCS factor: 16\alpha r_0^2/3 81 const G4double G4eBremsstrahlungRelModel::gBremFactor 82 = 16. * CLHEP::fine_structure_const * CLHEP::classic_electr_radius 83 * CLHEP::classic_electr_radius/3.; 84 85 // Migdal's constant: 4\pi r_0*electron_reduced_compton_wavelength^2 86 const G4double G4eBremsstrahlungRelModel::gMigdalConstant 87 = 4. * CLHEP::pi * CLHEP::classic_electr_radius 88 * CLHEP::electron_Compton_length * CLHEP::electron_Compton_length; 89 90 // LPM constant: \alpha(mc^2)^2/(4\pi*\hbar c) 91 const G4double G4eBremsstrahlungRelModel::gLPMconstant 92 = CLHEP::fine_structure_const * CLHEP::electron_mass_c2 93 * CLHEP::electron_mass_c2 / (4. * CLHEP::pi * CLHEP::hbarc); 94 95 // abscissas and weights of an 8 point Gauss-Legendre quadrature 96 // for numerical integration on [0,1] 97 const G4double G4eBremsstrahlungRelModel::gXGL[] = { 98 1.98550718e-02, 1.01666761e-01, 2.37233795e-01, 4.08282679e-01, 99 5.91717321e-01, 7.62766205e-01, 8.98333239e-01, 9.80144928e-01 100 }; 101 const G4double G4eBremsstrahlungRelModel::gWGL[] = { 102 5.06142681e-02, 1.11190517e-01, 1.56853323e-01, 1.81341892e-01, 103 1.81341892e-01, 1.56853323e-01, 1.11190517e-01, 5.06142681e-02 104 }; 105 106 // elastic and inelatic radiation logarithms for light elements (where the 107 // Thomas-Fermi model doesn't work): computed by using Dirac-Fock model of atom. 108 const G4double G4eBremsstrahlungRelModel::gFelLowZet [] = { 109 0.0, 5.3104, 4.7935, 4.7402, 4.7112, 4.6694, 4.6134, 4.5520 110 }; 111 const G4double G4eBremsstrahlungRelModel::gFinelLowZet[] = { 112 0.0, 5.9173, 5.6125, 5.5377, 5.4728, 5.4174, 5.3688, 5.3236 113 }; 114 115 // LPM supression functions evaluated at initialisation time 116 std::shared_ptr<G4eBremsstrahlungRelModel::LPMFuncs> G4eBremsstrahlungRelModel::gLPMFuncs() 117 { 118 // We have to use shared pointer for the LPMFuncs as it is manipulated (content deleted) 119 // by the G4eBremsstrahlungRelModel used in the main thread and this 120 // model is owned (well deleted) by (at least in some cases) 121 // a G4SeltzerBergerModel which is owned by the G4LossTableManager 122 // which owned by a G4ThreadLocalSingleton<G4LossTableManager> 123 // which is a static global and thus deleted after this instance 124 // is deleted. 125 static auto _instance = std::make_shared<G4eBremsstrahlungRelModel::LPMFuncs>(); 126 return _instance; 127 } 128 129 // special data structure per element i.e. per Z 130 std::shared_ptr<std::vector<G4eBremsstrahlungRelModel::ElementData*>> G4eBremsstrahlungRelModel::gElementData() 131 { 132 // Same code comment as for gLPMFuncs. 133 static auto _instance = std::make_shared<std::vector<G4eBremsstrahlungRelModel::ElementData*>>(); 134 return _instance; 135 } 136 137 static std::once_flag applyOnce; 138 139 namespace 140 { 141 G4Mutex theBremRelMutex = G4MUTEX_INITIALIZER; 142 } 143 144 G4eBremsstrahlungRelModel::G4eBremsstrahlungRelModel(const G4ParticleDefinition* p, 145 const G4String& nam) 146 : G4VEmModel(nam), fLPMFuncs(gLPMFuncs()), fElementData(gElementData()) 147 { 148 fGammaParticle = G4Gamma::Gamma(); 149 // 150 fLowestKinEnergy = 1.0*CLHEP::MeV; 151 SetLowEnergyLimit(fLowestKinEnergy); 152 // 153 fLPMEnergyThreshold = 1.e+39; 154 fLPMEnergy = 0.; 155 SetAngularDistribution(new G4ModifiedTsai()); 156 // 157 if (nullptr != p) { 158 SetParticle(p); 159 } 160 } 161 162 G4eBremsstrahlungRelModel::~G4eBremsstrahlungRelModel() 163 { 164 if (fIsInitializer) { 165 // clear ElementData container 166 for (auto const & ptr : *fElementData) { delete ptr; } 167 fElementData->clear(); 168 // clear LPMFunctions (if any) 169 if (fLPMFuncs->fIsInitialized) { 170 fLPMFuncs->fLPMFuncG.clear(); 171 fLPMFuncs->fLPMFuncPhi.clear(); 172 fLPMFuncs->fIsInitialized = false; 173 } 174 } 175 } 176 177 void G4eBremsstrahlungRelModel::Initialise(const G4ParticleDefinition* p, 178 const G4DataVector& cuts) 179 { 180 // parameters in each thread 181 if (fPrimaryParticle != p) { 182 SetParticle(p); 183 } 184 fUseLPM = G4EmParameters::Instance()->LPM(); 185 fCurrentIZ = 0; 186 187 // init static element data and precompute LPM functions only once 188 std::call_once(applyOnce, [this]() { fIsInitializer = true; }); 189 190 // for all treads and derived classes 191 if (fIsInitializer || fElementData->empty()) { 192 G4AutoLock l(&theBremRelMutex); 193 if (fElementData->empty()) { 194 fElementData->resize(gMaxZet+1, nullptr); 195 } 196 InitialiseElementData(); 197 InitLPMFunctions(); 198 l.unlock(); 199 } 200 201 // element selectors are initialized in the master thread 202 if (IsMaster()) { 203 InitialiseElementSelectors(p, cuts); 204 } 205 // initialisation in all threads 206 if (nullptr == fParticleChange) { 207 fParticleChange = GetParticleChangeForLoss(); 208 } 209 if (GetTripletModel()) { 210 GetTripletModel()->Initialise(p, cuts); 211 fIsScatOffElectron = true; 212 } 213 } 214 215 void G4eBremsstrahlungRelModel::InitialiseLocal(const G4ParticleDefinition*, 216 G4VEmModel* masterModel) 217 { 218 SetElementSelectors(masterModel->GetElementSelectors()); 219 } 220 221 void G4eBremsstrahlungRelModel::SetParticle(const G4ParticleDefinition* p) 222 { 223 fPrimaryParticle = p; 224 fPrimaryParticleMass = p->GetPDGMass(); 225 fIsElectron = (p==G4Electron::Electron()); 226 } 227 228 // Sets kinematical variables like E_kin, E_t and some material dependent 229 // variables like LPM energy and characteristic photon energy k_p (more exactly 230 // k_p^2) for the Ter-Mikaelian suppression effect. 231 void G4eBremsstrahlungRelModel::SetupForMaterial(const G4ParticleDefinition*, 232 const G4Material* mat, 233 G4double kineticEnergy) 234 { 235 fDensityFactor = gMigdalConstant*mat->GetElectronDensity(); 236 fLPMEnergy = gLPMconstant*mat->GetRadlen(); 237 // threshold for LPM effect (i.e. below which LPM hidden by density effect) 238 if (fUseLPM) { 239 fLPMEnergyThreshold = std::sqrt(fDensityFactor)*fLPMEnergy; 240 } else { 241 fLPMEnergyThreshold = 1.e+39; // i.e. do not use LPM effect 242 } 243 // calculate threshold for density effect: k_p = sqrt(fDensityCorr) 244 fPrimaryKinEnergy = kineticEnergy; 245 fPrimaryTotalEnergy = kineticEnergy+fPrimaryParticleMass; 246 fDensityCorr = fDensityFactor*fPrimaryTotalEnergy*fPrimaryTotalEnergy; 247 // set activation flag for LPM effects in the DCS 248 fIsLPMActive = (fPrimaryTotalEnergy>fLPMEnergyThreshold); 249 } 250 251 // minimum primary (e-/e+) energy at which discrete interaction is possible 252 G4double G4eBremsstrahlungRelModel::MinPrimaryEnergy(const G4Material*, 253 const G4ParticleDefinition*, 254 G4double cut) 255 { 256 return std::max(fLowestKinEnergy, cut); 257 } 258 259 // Computes the restricted dE/dx as the appropriate weight of the individual 260 // element contributions that are computed by numerically integrating the DCS. 261 G4double 262 G4eBremsstrahlungRelModel::ComputeDEDXPerVolume(const G4Material* material, 263 const G4ParticleDefinition* p, 264 G4double kineticEnergy, 265 G4double cutEnergy) 266 { 267 G4double dedx = 0.0; 268 if (nullptr == fPrimaryParticle) { 269 SetParticle(p); 270 } 271 if (kineticEnergy < LowEnergyLimit()) { 272 return dedx; 273 } 274 // maximum value of the dE/dx integral (the minimum is 0 of course) 275 G4double tmax = std::min(cutEnergy, kineticEnergy); 276 if (tmax == 0.0) { 277 return dedx; 278 } 279 // sets kinematical and material related variables 280 SetupForMaterial(fPrimaryParticle, material,kineticEnergy); 281 // get element compositions of the material 282 const G4ElementVector* theElemVector = material->GetElementVector(); 283 const G4double* theAtomNumDensVector = material->GetAtomicNumDensityVector(); 284 const std::size_t numberOfElements = theElemVector->size(); 285 // loop over the elements of the material and compute their contributions to 286 // the restricted dE/dx by numerical integration of the dependent part of DCS 287 for (std::size_t ie = 0; ie < numberOfElements; ++ie) { 288 G4VEmModel::SetCurrentElement((*theElemVector)[ie]); 289 G4int zet = (*theElemVector)[ie]->GetZasInt(); 290 fCurrentIZ = std::min(zet, gMaxZet); 291 dedx += (zet*zet)*theAtomNumDensVector[ie]*ComputeBremLoss(tmax); 292 } 293 // apply the constant factor C/Z = 16\alpha r_0^2/3 294 dedx *= gBremFactor; 295 return std::max(dedx,0.); 296 } 297 298 // Computes the integral part of the restricted dE/dx contribution from a given 299 // element (Z) by numerically integrating the k dependent part of the DCS between 300 // k_min=0 and k_max = tmax = min[gamma-cut, electron-kinetic-eenrgy]. 301 // The numerical integration is done by dividing the integration range into 'n' 302 // subintervals and an 8 pint GL integral (on [0,1]) is performed on each sub- 303 // inteval by tranforming k to alpha=k/E_t (E_t is the total energy of the e-) 304 // and each sub-interavl is transformed to [0,1]. So the integrastion is done 305 // in xi(alpha) = xi(k) = [k/E_t-alpha_i]/delta where alpha_i=(i-1)*delta for 306 // the i = 1,2,..,n-th sub-interval so xi(k) in [0,1] on each sub-intevals. 307 // This transformation from 'k' to 'xi(k)' results in a multiplicative factor 308 // of E_t*delta at each step. 309 // The restricted dE/dx = N int_{0}^{k_max} k*ds/dk dk. There are 2 DCS model 310 // one with LPM and one without LPM effects (see them below). In both case not 311 // the ds/dk(Z,k) but ds/dk(Z,k)*[F*k/C] is computed since: 312 // (i) what we need here is ds/dk*k and not k so this multiplication is done 313 // (ii) the Ter-Mikaelian suppression i.e. F related factor is done here 314 // (iii) the constant factor C (includes Z^2 as well)is accounted in the caller 315 G4double G4eBremsstrahlungRelModel::ComputeBremLoss(G4double tmax) 316 { 317 // number of intervals and integration step 318 const G4double alphaMax = tmax/fPrimaryTotalEnergy; 319 const G4int nSub = (G4int)(20*alphaMax)+3; 320 const G4double delta = alphaMax/((G4double)nSub); 321 // set minimum value of the first sub-inteval 322 G4double alpha_i = 0.0; 323 G4double dedxInteg = 0.0; 324 for (G4int l = 0; l < nSub; ++l) { 325 for (G4int igl = 0; igl < 8; ++igl) { 326 // compute the emitted photon energy k 327 const G4double k = (alpha_i+gXGL[igl]*delta)*fPrimaryTotalEnergy; 328 // compute the DCS value at k (without the constant, the 1/k, 1/F factors) 329 const G4double dcs = fIsLPMActive 330 ? ComputeRelDXSectionPerAtom(k) // DCS WITHOUT LPM 331 : ComputeDXSectionPerAtom(k); // DCS WITH LPM 332 // account Ter-Mikaelian suppression: times 1/F with F = 1+(k_p/k)^2 333 dedxInteg += gWGL[igl]*dcs/(1.0+fDensityCorr/(k*k)); 334 } 335 // update sub-interval minimum value 336 alpha_i += delta; 337 } 338 // apply corrections due to variable transformation i.e. E_t*delta 339 dedxInteg *= delta*fPrimaryTotalEnergy; 340 return std::max(dedxInteg,0.); 341 } 342 343 // Computes restrected atomic cross section by numerically integrating the 344 // DCS between the proper kinematical limits accounting the gamma production cut 345 G4double G4eBremsstrahlungRelModel::ComputeCrossSectionPerAtom( 346 const G4ParticleDefinition* p, 347 G4double kineticEnergy, 348 G4double Z, 349 G4double, 350 G4double cut, 351 G4double maxEnergy) 352 { 353 G4double crossSection = 0.0; 354 if (nullptr == fPrimaryParticle) { 355 SetParticle(p); 356 } 357 if (kineticEnergy < LowEnergyLimit()) { 358 return crossSection; 359 } 360 // min/max kinetic energy limits of the DCS integration: 361 const G4double tmin = std::min(cut, kineticEnergy); 362 const G4double tmax = std::min(maxEnergy, kineticEnergy); 363 // zero restricted x-section if e- kinetic energy is below gamma cut 364 if (tmin >= tmax) { 365 return crossSection; 366 } 367 fCurrentIZ = std::min(G4lrint(Z), gMaxZet); 368 // integrate numerically (dependent part of) the DCS between the kin. limits: 369 // a. integrate between tmin and kineticEnergy of the e- 370 crossSection = ComputeXSectionPerAtom(tmin); 371 // allow partial integration: only if maxEnergy < kineticEnergy 372 // b. integrate between tmax and kineticEnergy (tmax=maxEnergy in this case) 373 // (so the result in this case is the integral of DCS between tmin and 374 // maxEnergy) 375 if (tmax < kineticEnergy) { 376 crossSection -= ComputeXSectionPerAtom(tmax); 377 } 378 // multiply with the constant factors: 16\alpha r_0^2/3 Z^2 379 crossSection *= Z*Z*gBremFactor; 380 return std::max(crossSection, 0.); 381 } 382 383 // Numerical integral of the (k dependent part of) DCS between k_min=tmin and 384 // k_max = E_k (where E_k is the kinetic energy of the e- and tmin is the 385 // minimum of energy of the emitted photon). The integration is done in the 386 // transformed alpha(k) = ln(k/E_t) variable (with E_t being the total energy of 387 // the primary e-). The integration range is divided into n sub-intervals with 388 // delta = [ln(k_min/E_t)-ln(k_max/E_t)]/n width each. An 8 point GL integral 389 // on [0,1] is applied on each sub-inteval so alpha is transformed to 390 // xi(alpha) = xi(k) = [ln(k/E_t)-alpha_i]/delta where alpha_i = ln(k_min/E_t) + 391 // (i-1)*delta for the i = 1,2,..,n-th sub-interval and xi(k) in [0,1] on each 392 // sub-intevals. From the transformed xi, k(xi) = E_t exp[xi*delta+alpha_i]. 393 // Since the integration is done in variable xi instead of k this 394 // transformation results in a multiplicative factor of k*delta at each step. 395 // However, DCS differential in k is ~1/k so the multiplicative factor is simple 396 // becomes delta and the 1/k factor is dropped from the DCS computation. 397 // NOTE: 398 // - LPM suppression is accounted above threshold e- energy (corresponidng 399 // flag is set in SetUpForMaterial() => 2 DCS with/without LPM 400 // - Ter-Mikaelian suppression is always accounted 401 G4double G4eBremsstrahlungRelModel::ComputeXSectionPerAtom(G4double tmin) 402 { 403 G4double xSection = 0.0; 404 const G4double alphaMin = G4Log(tmin/fPrimaryTotalEnergy); 405 const G4double alphaMax = G4Log(fPrimaryKinEnergy/tmin); 406 const G4int nSub = std::max((G4int)(0.45*alphaMax), 0) + 4; 407 const G4double delta = alphaMax/((G4double)nSub); 408 // set minimum value of the first sub-inteval 409 G4double alpha_i = alphaMin; 410 for (G4int l = 0; l < nSub; ++l) { 411 for (G4int igl = 0; igl < 8; ++igl) { 412 // compute the emitted photon energy k 413 const G4double k = G4Exp(alpha_i+gXGL[igl]*delta)*fPrimaryTotalEnergy; 414 // compute the DCS value at k (without the constant, the 1/k, 1/F factors) 415 const G4double dcs = fIsLPMActive 416 ? ComputeRelDXSectionPerAtom(k) // DCS WITHOUT LPM 417 : ComputeDXSectionPerAtom(k); // DCS WITH LPM 418 // account Ter-Mikaelian suppression: times 1/F with F = 1+(k_p/k)^2 419 xSection += gWGL[igl]*dcs/(1.0+fDensityCorr/(k*k)); 420 } 421 // update sub-interval minimum value 422 alpha_i += delta; 423 } 424 // apply corrections due to variable transformation 425 xSection *= delta; 426 // final check 427 return std::max(xSection, 0.); 428 } 429 430 // DCS WITH LPM EFFECT: complete screening aprx. and includes LPM suppression 431 // ds/dk(Z,k) = C/[F*k]*{ Xi(s*F)*[y^2*G/4 +(1-y+y^2/3)Phi]*[L_el-f_c+L_inel/Z] 432 // +(1-y)*[1+1/Z]/12} with C = 16\alpha r_0^2/3 Z^2 and 433 // Xi(s),G(s), Phi(s) are LPM suppression functions: 434 // 435 // LPM SUPPRESSION: The 's' is the suppression variable and F = F(k,k_p) = 436 // 1+(k_p/k)^2 with k_p = hbar*w_p*E/(m*c^2) is a material (e- density) 437 // dependent constant. F accounts the Ter-Mikaelian suppression with a smooth 438 // transition in the emitted photon energy. Also, the LPM suppression functions 439 // goes to 0 when s goes to 0 and goes to 1 when s is increasing (=1 at s=~2) 440 // So evaluating the LPM suppression functions at 'sF' instead of 's' ensures a 441 // smooth transition depending on the emitted photon energy 'k': LPM effect is 442 // smoothly turned off i.e. Xi(sF)=G(sF)=Phi(sF)=1 when k << k_p because F >> 1 443 // and sF ~ s when k >> k_p since F ~ 1 in that case. 444 // HERE, ds/dk(Z,k)*[F*k/C] is computed since: 445 // (i) DCS ~ 1/k factor will disappear due to the variable transformation 446 // v(k)=ln(k/E_t) -> dk/dv=E_t*e^v=k -> ds/dv= ds/dk*dk/dv=ds/dk*k so it 447 // would cnacell out the 1/k factor => 1/k don't included here 448 // (ii) the constant factor C and Z don't depend on 'k' => not included here 449 // (iii) the 1/F(k) factor is accounted in the callers: explicitly (cross sec- 450 // tion computation) or implicitly through further variable transformaton 451 // (in the final state sampling algorithm) 452 // COMPLETE SCREENING: see more at the DCS without LPM effect below. 453 G4double 454 G4eBremsstrahlungRelModel::ComputeRelDXSectionPerAtom(G4double gammaEnergy) 455 { 456 G4double dxsec = 0.0; 457 if (gammaEnergy < 0.) { 458 return dxsec; 459 } 460 const G4double y = gammaEnergy/fPrimaryTotalEnergy; 461 const G4double onemy = 1.-y; 462 const G4double dum0 = 0.25*y*y; 463 // evaluate LPM functions (combined with the Ter-Mikaelian effect) 464 G4double funcGS, funcPhiS, funcXiS; 465 ComputeLPMfunctions(funcXiS, funcGS, funcPhiS, gammaEnergy); 466 const ElementData* elDat = (*fElementData)[fCurrentIZ]; 467 const G4double term1 = funcXiS*(dum0*funcGS+(onemy+2.0*dum0)*funcPhiS); 468 dxsec = term1*elDat->fZFactor1+onemy*elDat->fZFactor2; 469 // 470 if (fIsScatOffElectron) { 471 fSumTerm = dxsec; 472 fNucTerm = term1*elDat->fZFactor11 + onemy/12.; 473 } 474 return std::max(dxsec,0.0); 475 } 476 477 // DCS WITHOUT LPM EFFECT: DCS with sceening (Z>5) and Coulomb cor. no LPM 478 // ds/dk(Z,k)=C/[F*k]*{(1-y+3*y^2/4)*[(0.25*phi1(g)-ln(Z)/3-f_c)+(0.25*psi1(e) 479 // -2*ln(Z)/3)/Z]+ (1-y)*[(phi1(g)-phi2(g))+(psi1(e)-psi2(e))/Z]/8} 480 // where f_c(Z) is the Coulomb correction factor and phi1(g),phi2(g) and psi1(e), 481 // psi2(e) are coherent and incoherent screening functions. In the Thomas-Fermi 482 // model of the atom, the screening functions will have a form that do not 483 // depend on Z (not explicitly). These numerical screening functions can be 484 // approximated as Tsai Eqs. [3.38-3.41] with the variables g=gamma and 485 // e=epsilon given by Tsai Eqs. [3.30 and 3.31] (see more details at the method 486 // ComputeScreeningFunctions()). Note, that in case of complete screening i.e. 487 // g = e = 0 => 0.25*phi1(0)-ln(Z)/3 = ln(184.149/Z^(1/3)) = L_el and 488 // 0.25*psi1(0)-2*ln(Z)/3=ln(1193.923/Z^(2/3))=L_inel and phi1(0)-phi2(0) = 489 // psi1(0)-psi2(0) = 2/3 so the DCS in complete screening => 490 // COMPLETE SCREENING: 491 // ds/dk(Z,k)=C/k*{(1-y+3*y^2/4)*[L_el-f_c+L_inel/Z] + (1-y)*[1+1/Z]/12} that is 492 // used in case of DCS with LPM above (if all the suprression functions are 493 // absent i.e. their value = 1). 494 // Since the Thomas-Fermi model of the atom is not accurate at low Z, the DCS in 495 // complete screening is used here at low Z(<5) with L_el(Z), L_inel(Z) values 496 // computed by using the Dirac-Fock model of the atom. 497 // NOTE: that the Ter-Mikaelian suppression is accounted in the DCS through the 498 // 1/F factor but it is included in the caller and not considered here. 499 // HERE, ds/dk(Z,k)*[F*k/C] is computed exactly like in the DCS with LPM case. 500 G4double 501 G4eBremsstrahlungRelModel::ComputeDXSectionPerAtom(G4double gammaEnergy) 502 { 503 G4double dxsec = 0.0; 504 if (gammaEnergy < 0.) { 505 return dxsec; 506 } 507 const G4double y = gammaEnergy/fPrimaryTotalEnergy; 508 const G4double onemy = 1.-y; 509 const G4double dum0 = onemy+0.75*y*y; 510 const ElementData* elDat = (*fElementData)[fCurrentIZ]; 511 // use complete screening and L_el, L_inel from Dirac-Fock model instead of TF 512 if (fCurrentIZ < 5 || fIsUseCompleteScreening) { 513 dxsec = dum0*elDat->fZFactor1; 514 dxsec += onemy*elDat->fZFactor2; 515 if (fIsScatOffElectron) { 516 fSumTerm = dxsec; 517 fNucTerm = dum0*elDat->fZFactor11+onemy/12.; 518 } 519 } else { 520 // use Tsai's analytical approx. (Tsai Eqs. [3.38-3.41]) to the 'universal' 521 // numerical screening functions computed by using the TF model of the atom 522 const G4double invZ = 1./(G4double)fCurrentIZ; 523 const G4double Fz = elDat->fFz; 524 const G4double logZ = elDat->fLogZ; 525 const G4double dum1 = y/(fPrimaryTotalEnergy-gammaEnergy); 526 const G4double gamma = dum1*elDat->fGammaFactor; 527 const G4double epsilon = dum1*elDat->fEpsilonFactor; 528 // evaluate the screening functions 529 G4double phi1, phi1m2, psi1, psi1m2; 530 ComputeScreeningFunctions(phi1, phi1m2, psi1, psi1m2, gamma, epsilon); 531 dxsec = dum0*((0.25*phi1-Fz) + (0.25*psi1-2.*logZ/3.)*invZ); 532 dxsec += 0.125*onemy*(phi1m2 + psi1m2*invZ); 533 if (fIsScatOffElectron) { 534 fSumTerm = dxsec; 535 fNucTerm = dum0*(0.25*phi1-Fz) + 0.125*onemy*phi1m2; 536 } 537 } 538 return std::max(dxsec,0.0); 539 } 540 541 // Coherent and incoherent screening function approximations (see Tsai 542 // Eqs.[3.38-3.41]). Tsai's analytical approximations to the numerical screening 543 // functions computed by using the Thomas-Fermi model of atom (Moliere's appro- 544 // ximation to the numerical TF screening function). In the TF-model, these 545 // screening functions can be expressed in a 'universal' i.e. Z (directly) inde- 546 // pendent variable (see Tsai Eqs. Eqs. [3.30 and 3.31]). 547 void G4eBremsstrahlungRelModel::ComputeScreeningFunctions(G4double& phi1, 548 G4double& phi1m2, 549 G4double& psi1, 550 G4double& psi1m2, 551 const G4double gam, 552 const G4double eps) 553 { 554 const G4double gam2 = gam*gam; 555 phi1 = 16.863-2.0*G4Log(1.0+0.311877*gam2)+2.4*G4Exp(-0.9*gam) 556 +1.6*G4Exp(-1.5*gam); 557 phi1m2 = 2.0/(3.0+19.5*gam+18.0*gam2); // phi1-phi2 558 const G4double eps2 = eps*eps; 559 psi1 = 24.34-2.0*G4Log(1.0+13.111641*eps2)+2.8*G4Exp(-8.0*eps) 560 +1.2*G4Exp(-29.2*eps); 561 psi1m2 = 2.0/(3.0+120.0*eps+1200.0*eps2); //psi1-psi2 562 } 563 564 void 565 G4eBremsstrahlungRelModel::SampleSecondaries(std::vector<G4DynamicParticle*>* vdp, 566 const G4MaterialCutsCouple* couple, 567 const G4DynamicParticle* dp, 568 G4double cutEnergy, 569 G4double maxEnergy) 570 { 571 const G4double kineticEnergy = dp->GetKineticEnergy(); 572 if (kineticEnergy < LowEnergyLimit()) { 573 return; 574 } 575 // min, max kinetic energy limits 576 const G4double tmin = std::min(cutEnergy, kineticEnergy); 577 const G4double tmax = std::min(maxEnergy, kineticEnergy); 578 if (tmin >= tmax) { 579 return; 580 } 581 // 582 SetupForMaterial(fPrimaryParticle, couple->GetMaterial(), kineticEnergy); 583 const G4Element* elm = SelectTargetAtom(couple,fPrimaryParticle,kineticEnergy, 584 dp->GetLogKineticEnergy(),tmin,tmax); 585 // 586 fCurrentIZ = elm->GetZasInt(); 587 const ElementData* elDat = (*fElementData)[fCurrentIZ]; 588 const G4double funcMax = elDat->fZFactor1+elDat->fZFactor2; 589 // get the random engine 590 G4double rndm[2]; 591 CLHEP::HepRandomEngine* rndmEngine = G4Random::getTheEngine(); 592 // min max 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)] 593 const G4double xmin = G4Log(tmin*tmin+fDensityCorr); 594 const G4double xrange = G4Log(tmax*tmax+fDensityCorr)-xmin; 595 G4double gammaEnergy, funcVal; 596 do { 597 rndmEngine->flatArray(2, rndm); 598 gammaEnergy = std::sqrt(std::max(G4Exp(xmin+rndm[0]*xrange)-fDensityCorr, 0.0)); 599 funcVal = fIsLPMActive 600 ? ComputeRelDXSectionPerAtom(gammaEnergy) 601 : ComputeDXSectionPerAtom(gammaEnergy); 602 // cross-check of proper function maximum in the rejection 603 // if (funcVal > funcMax) { 604 // G4cout << "### G4eBremsstrahlungRelModel Warning: Majoranta exceeded! " 605 // << funcVal << " > " << funcMax 606 // << " Egamma(MeV)= " << gammaEnergy 607 // << " Ee(MeV)= " << kineticEnergy 608 // << " " << GetName() 609 // << G4endl; 610 // } 611 // Loop checking, 03-Aug-2015, Vladimir Ivanchenko 612 } while (funcVal < funcMax*rndm[1]); 613 // 614 // scattering off nucleus or off e- by triplet model 615 if (fIsScatOffElectron && rndmEngine->flat()*fSumTerm>fNucTerm) { 616 GetTripletModel()->SampleSecondaries(vdp, couple, dp, cutEnergy, maxEnergy); 617 return; 618 } 619 // 620 // angles of the emitted gamma. ( Z - axis along the parent particle) 621 // use general interface 622 G4ThreeVector gamDir = 623 GetAngularDistribution()->SampleDirection(dp,fPrimaryTotalEnergy-gammaEnergy, 624 fCurrentIZ, couple->GetMaterial()); 625 // create G4DynamicParticle object for the Gamma 626 auto gamma = new G4DynamicParticle(fGammaParticle, gamDir, gammaEnergy); 627 vdp->push_back(gamma); 628 // compute post-interaction kinematics of primary e-/e+ based on 629 // energy-momentum conservation 630 const G4double totMomentum = std::sqrt(kineticEnergy*( 631 fPrimaryTotalEnergy + CLHEP::electron_mass_c2)); 632 G4ThreeVector dir = 633 (totMomentum*dp->GetMomentumDirection()-gammaEnergy*gamDir).unit(); 634 const G4double finalE = kineticEnergy-gammaEnergy; 635 // if secondary gamma energy is higher than threshold(very high by default) 636 // then stop tracking the primary particle and create new secondary e-/e+ 637 // instead of the primary one 638 if (gammaEnergy > SecondaryThreshold()) { 639 fParticleChange->ProposeTrackStatus(fStopAndKill); 640 fParticleChange->SetProposedKineticEnergy(0.0); 641 auto el = new G4DynamicParticle( 642 const_cast<G4ParticleDefinition*>(fPrimaryParticle), dir, finalE); 643 vdp->push_back(el); 644 } else { // continue tracking the primary e-/e+ otherwise 645 fParticleChange->SetProposedMomentumDirection(dir); 646 fParticleChange->SetProposedKineticEnergy(finalE); 647 } 648 } 649 650 void G4eBremsstrahlungRelModel::InitialiseElementData() 651 { 652 // create for all elements that are in the detector 653 auto elemTable = G4Element::GetElementTable(); 654 for (auto const & elem : *elemTable) { 655 const G4double zet = elem->GetZ(); 656 const G4int izet = std::min(elem->GetZasInt(), gMaxZet); 657 if (nullptr == (*fElementData)[izet]) { 658 auto elemData = new ElementData(); 659 const G4double fc = elem->GetfCoulomb(); 660 G4double Fel = 1.; 661 G4double Finel = 1.; 662 elemData->fLogZ = G4Log(zet); 663 elemData->fFz = elemData->fLogZ/3.+fc; 664 if (izet < 5) { 665 Fel = gFelLowZet[izet]; 666 Finel = gFinelLowZet[izet]; 667 } else { 668 Fel = G4Log(184.15) - elemData->fLogZ/3.; 669 Finel = G4Log(1194) - 2.*elemData->fLogZ/3.; 670 } 671 const G4double z13 = G4Pow::GetInstance()->Z13(izet); 672 const G4double z23 = z13*z13; 673 elemData->fZFactor1 = (Fel-fc)+Finel/zet; 674 elemData->fZFactor11 = (Fel-fc); // used only for the triplet 675 elemData->fZFactor2 = (1.+1./zet)/12.; 676 elemData->fVarS1 = z23/(184.15*184.15); 677 elemData->fILVarS1Cond = 1./(G4Log(std::sqrt(2.0)*elemData->fVarS1)); 678 elemData->fILVarS1 = 1./G4Log(elemData->fVarS1); 679 elemData->fGammaFactor = 100.0*electron_mass_c2/z13; 680 elemData->fEpsilonFactor = 100.0*electron_mass_c2/z23; 681 (*fElementData)[izet] = elemData; 682 } 683 } 684 } 685 686 void G4eBremsstrahlungRelModel::ComputeLPMfunctions(G4double& funcXiS, 687 G4double& funcGS, 688 G4double& funcPhiS, 689 const G4double egamma) 690 { 691 static const G4double sqrt2 = std::sqrt(2.); 692 const G4double redegamma = egamma/fPrimaryTotalEnergy; 693 const G4double varSprime = std::sqrt(0.125*redegamma*fLPMEnergy/ 694 ((1.0-redegamma)*fPrimaryTotalEnergy)); 695 const ElementData* elDat = (*fElementData)[fCurrentIZ]; 696 const G4double varS1 = elDat->fVarS1; 697 const G4double condition = sqrt2*varS1; 698 G4double funcXiSprime = 2.0; 699 if (varSprime > 1.0) { 700 funcXiSprime = 1.0; 701 } else if (varSprime > condition) { 702 const G4double ilVarS1Cond = elDat->fILVarS1Cond; 703 const G4double funcHSprime = G4Log(varSprime)*ilVarS1Cond; 704 funcXiSprime = 1.0 + funcHSprime - 0.08*(1.0-funcHSprime)*funcHSprime 705 *(2.0-funcHSprime)*ilVarS1Cond; 706 } 707 const G4double varS = varSprime/std::sqrt(funcXiSprime); 708 // - include dielectric suppression effect into s according to Migdal 709 const G4double varShat = varS*(1.0+fDensityCorr/(egamma*egamma)); 710 funcXiS = 2.0; 711 if (varShat > 1.0) { 712 funcXiS = 1.0; 713 } else if (varShat > varS1) { 714 funcXiS = 1.0+G4Log(varShat)*elDat->fILVarS1; 715 } 716 GetLPMFunctions(funcGS, funcPhiS, varShat); 717 //ComputeLPMGsPhis(funcGS, funcPhiS, varShat); 718 // 719 //MAKE SURE SUPPRESSION IS SMALLER THAN 1: due to Migdal's approximation on xi 720 if (funcXiS*funcPhiS > 1. || varShat > 0.57) { 721 funcXiS=1./funcPhiS; 722 } 723 } 724 725 void G4eBremsstrahlungRelModel::ComputeLPMGsPhis(G4double& funcGS, 726 G4double& funcPhiS, 727 const G4double varShat) 728 { 729 if (varShat < 0.01) { 730 funcPhiS = 6.0*varShat*(1.0-CLHEP::pi*varShat); 731 funcGS = 12.0*varShat-2.0*funcPhiS; 732 } else { 733 const G4double varShat2 = varShat*varShat; 734 const G4double varShat3 = varShat*varShat2; 735 const G4double varShat4 = varShat2*varShat2; 736 // use Stanev approximation: for \psi(s) and compute G(s) 737 if (varShat < 0.415827) { 738 funcPhiS = 1.0-G4Exp(-6.0*varShat*(1.0+varShat*(3.0-CLHEP::pi)) 739 + varShat3/(0.623+0.796*varShat+0.658*varShat2)); 740 // 1-\exp \left\{-4s-\frac{8s^2}{1+3.936s+4.97s^2-0.05s^3+7.5s^4} \right\} 741 const G4double funcPsiS = 1.0 - G4Exp(-4.0*varShat 742 - 8.0*varShat2/(1.0+3.936*varShat+4.97*varShat2 743 - 0.05*varShat3 + 7.5*varShat4)); 744 // G(s) = 3 \psi(s) - 2 \phi(s) 745 funcGS = 3.0*funcPsiS - 2.0*funcPhiS; 746 } else if (varShat<1.55) { 747 funcPhiS = 1.0-G4Exp(-6.0*varShat*(1.0+varShat*(3.0-CLHEP::pi)) 748 + varShat3/(0.623+0.796*varShat+0.658*varShat2)); 749 const G4double dum0 = -0.160723 + 3.755030*varShat 750 -1.798138*varShat2 + 0.672827*varShat3 751 -0.120772*varShat4; 752 funcGS = std::tanh(dum0); 753 } else { 754 funcPhiS = 1.0-0.011905/varShat4; 755 if (varShat<1.9156) { 756 const G4double dum0 = -0.160723 + 3.755030*varShat 757 -1.798138*varShat2 + 0.672827*varShat3 758 -0.120772*varShat4; 759 funcGS = std::tanh(dum0); 760 } else { 761 funcGS = 1.0-0.023065/varShat4; 762 } 763 } 764 } 765 } 766 767 // s goes up to 2 with ds = 0.01 to be the default bining 768 void G4eBremsstrahlungRelModel::InitLPMFunctions() 769 { 770 if (!fLPMFuncs->fIsInitialized) { 771 const G4int num = fLPMFuncs->fSLimit*fLPMFuncs->fISDelta+1; 772 fLPMFuncs->fLPMFuncG.resize(num); 773 fLPMFuncs->fLPMFuncPhi.resize(num); 774 for (G4int i = 0; i < num; ++i) { 775 const G4double sval=i/fLPMFuncs->fISDelta; 776 ComputeLPMGsPhis(fLPMFuncs->fLPMFuncG[i],fLPMFuncs->fLPMFuncPhi[i],sval); 777 } 778 fLPMFuncs->fIsInitialized = true; 779 } 780 } 781 782 void G4eBremsstrahlungRelModel::GetLPMFunctions(G4double& lpmGs, 783 G4double& lpmPhis, 784 const G4double sval) 785 { 786 if (sval < fLPMFuncs->fSLimit) { 787 G4double val = sval*fLPMFuncs->fISDelta; 788 const G4int ilow = (G4int)val; 789 val -= ilow; 790 lpmGs = (fLPMFuncs->fLPMFuncG[ilow+1]-fLPMFuncs->fLPMFuncG[ilow])*val 791 + fLPMFuncs->fLPMFuncG[ilow]; 792 lpmPhis = (fLPMFuncs->fLPMFuncPhi[ilow+1]-fLPMFuncs->fLPMFuncPhi[ilow])*val 793 + fLPMFuncs->fLPMFuncPhi[ilow]; 794 } else { 795 G4double ss = sval*sval; 796 ss *= ss; 797 lpmPhis = 1.0-0.01190476/ss; 798 lpmGs = 1.0-0.0230655/ss; 799 } 800 } 801 802