Geant4 11.4.0
Toolkit for the simulation of the passage of particles through matter
Loading...
Searching...
No Matches
G4GoudsmitSaundersonMscModel.cc
Go to the documentation of this file.
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 implementation file
30//
31// File name: G4GoudsmitSaundersonMscModel
32//
33// Author: Mihaly Novak / (Omrane Kadri)
34//
35// Creation date: 20.02.2009
36//
37// Modifications:
38// 04.03.2009 V.Ivanchenko cleanup and format according to Geant4 EM style
39// 12.05.2010 O.Kadri: adding Qn1 and Qn12 as private doubles
40// 18.05.2015 M. Novak provide PRELIMINARY verison of the revised class
41// This class has been revised and updated, new methods added.
42// A new version of Kawrakow-Bielajew Goudsmit-Saunderson MSC model
43// based on the screened Rutherford DCS for elastic scattering of
44// electrons/positrons has been introduced[1,2]. The corresponding MSC
45// angular distributions over a 2D parameter grid have been recomputed
46// and the CDFs are now stored in a variable transformed (smooth) form[2,3]
47// together with the corresponding rational interpolation parameters.
48// These angular distributions are handled by the new
49// G4GoudsmitSaundersonTable class that is responsible to sample if
50// it was no, single, few or multiple scattering case and delivers the
51// angular deflection (i.e. cos(theta) and sin(theta)).
52// Two screening options are provided:
53// - if fIsUsePWATotalXsecData=TRUE i.e. SetOptionPWAScreening(TRUE)
54// was called before initialisation: screening parameter value A is
55// determined such that the first transport coefficient G1(A)
56// computed according to the screened Rutherford DCS for elastic
57// scattering will reproduce the one computed from the PWA elastic
58// and first transport mean free paths[4].
59// - if fIsUsePWATotalXsecData=FALSE i.e. default value or
60// SetOptionPWAScreening(FALSE) was called before initialisation:
61// screening parameter value A is computed according to Moliere's
62// formula (by using material dependent parameters \chi_cc2 and b_c
63// precomputed for each material used at initialization in
64// G4GoudsmitSaundersonTable) [3]
65// Elastic and first trasport mean free paths are used consistently.
66// The new version is self-consistent, several times faster, more
67// robust and accurate compared to the earlier version.
68// Spin effects as well as a more accurate energy loss correction and
69// computations of Lewis moments will be implemented later on.
70// [1] A.F.Bielajew, NIMB 111 (1996) 195-208
71// [2] I.Kawrakow, A.F.Bielajew, NIMB 134(1998) 325-336
72// [3] I.Kawrakow, E.Mainegra-Hing, D.W.O.Rogers, F.Tessier,B.R.B.Walters,
73// NRCC Report PIRS-701 (2013)
74// [4] F.Salvat, A.Jablonski, C.J. Powell, CPC 165(2005) 157-190
75// 02.09.2015 M. Novak: first version of new step limit is provided.
76// fUseSafetyPlus corresponds to Urban fUseSafety (default)
77// fUseDistanceToBoundary corresponds to Urban fUseDistanceToBoundary
78// fUseSafety corresponds to EGSnrc error-free stepping algorithm
79// Range factor can be significantly higher at each case than in Urban.
80// 23.08.2017 M. Novak: added corrections to account spin effects (Mott-correction).
81// It can be activated by setting the fIsMottCorrection flag to be true
82// before initialization using the SetOptionMottCorrection() public method.
83// The fMottCorrection member is responsible to handle pre-computed Mott
84// correction (rejection) functions obtained by numerically computing
85// Goudsmit-Saunderson agnular distributions based on a DCS accounting spin
86// effects and screening corrections. The DCS used to compute the accurate
87// GS angular distributions is: DCS_{cor} = DCS_{SR}x[ DCS_{R}/DCS_{Mott}] where :
88// # DCS_{SR} is the relativistic Screened-Rutherford DCS (first Born approximate
89// solution of the Klein-Gordon i.e. relativistic Schrodinger equation =>
90// scattering of spinless e- on exponentially screened Coulomb potential)
91// note: the default (without using Mott-correction) GS angular distributions
92// are based on this DCS_{SR} with Moliere's screening parameter!
93// # DCS_{R} is the Rutherford DCS which is the same as above but without
94// screening
95// # DCS_{Mott} is the Mott DCS i.e. solution of the Dirac equation with a bare
96// Coulomb potential i.e. scattering of particles with spin (e- or e+) on a
97// point-like unscreened Coulomb potential
98// # moreover, the screening parameter of the DCS_{cor} was determined such that
99// the DCS_{cor} with this corrected screening parameter reproduce the first
100// transport cross sections obtained from the corresponding most accurate DCS
101// (i.e. from elsepa [4])
102// Unlike the default GS, the Mott-corrected angular distributions are particle type
103// (different for e- and e+ <= the DCS_{Mott} and the screening correction) and target
104// (Z and material) dependent.
105// 27.10.2017 M. Novak:
106// - Mott-correction flag is set now through the G4EmParameters
107// - new form of PWA correction to integrated quantities and screening (default)
108// - changed step limit flag conventions:
109// # fUseSafety corresponds to Urban's fUseSafety
110// # fUseDistanceToBoundary corresponds to Urban's fUseDistanceToBoundary
111// # fUseSafetyPlus corresponds to the error-free stepping algorithm
112// 02.02.2018 M. Novak: implemented CrossSectionPerVolume interface method (used only for testing)
113// 26.10.2025 M. Novak: the model has only its accurate stepping and boundary crossing algorithms
114// left as the only option that ensures the expected precision, especially when activating
115// its Mott correction option (that also activates the screeing and scattering power
116// corrections). The model has been used for describing e-/e+ MSC (below 100 MeV kinetic)
117// energy in the option4, Penelope and Livermore EM physics constructors since Geant4 10.6.
118//
119// References:
120// M. Novak: https://arxiv.org/abs/2410.13361
121// -----------------------------------------------------------------------------
122
123
125
127#include "G4GSPWACorrections.hh"
128
129#include "G4PhysicalConstants.hh"
130#include "G4SystemOfUnits.hh"
131
133#include "G4DynamicParticle.hh"
134#include "G4Electron.hh"
135#include "G4Positron.hh"
136
137#include "G4LossTableManager.hh"
138#include "G4EmParameters.hh"
139#include "G4Track.hh"
140#include "G4PhysicsTable.hh"
141#include "Randomize.hh"
142#include "G4Log.hh"
143#include "G4Exp.hh"
144#include "G4Pow.hh"
145#include <fstream>
146
147
149 : G4VMscModel(nam) {
150 currentMaterialIndex = -1;
151 presafety = 0.*mm;
152 //
153 particle = nullptr;
154 currentKinEnergy = 0.0;
155 currentRange = 0.0;
156 currentCouple = nullptr;
157 fParticleChange = nullptr;
158 //
159 // Moliere screeing parameter will be used and (by default) corrections are
160 // appalied to the integrated quantities (screeing parameter, elastic mfp, first
161 // and second moments) derived from the corresponding PWA quantities
162 // this PWA correction is ignored if Mott-correction is set to true because
163 // Mott-correction contains all these corrections as well
164 fIsUsePWACorrection = true;
165 fIsUseMottCorrection = false;
166 // Use the "range-rejection" like optimisation?
167 fIsUseOptimisation = true;
168 //
169 fLambda0 = 0.0; // elastic mean free path
170 fLambda1 = 0.0; // first transport mean free path
171 fScrA = 0.0; // screening parameter
172 fG1 = 0.0; // first transport coef.
173 //
174 fMCtoScrA = 1.0;
175 fMCtoQ1 = 1.0;
176 fMCtoG2PerG1 = 1.0;
177 //
178 fTheTrueStepLenght = 0.;
179 fTheZPathLenght = 0.;
180
181 fTheDisplacementVector.set(0.,0.,0.);
182 fTheNewDirection.set(0.,0.,1.);
183
184 fIsEndedUpOnBoundary = false;
185 fIsMultipleScattering = false;
186 fIsSingleScattering = false;
187 fIsNoScatteringInMSC = false;
188 fIsSimplified = false;
189
190 fGSTable = nullptr;
191 fPWACorrection = nullptr;
192}
193
194
196 if (IsMaster()) {
197 if (fGSTable) {
198 delete fGSTable;
199 fGSTable = nullptr;
200 }
201 if (fPWACorrection) {
202 delete fPWACorrection;
203 fPWACorrection = nullptr;
204 }
205 }
206}
207
208
210 SetParticle(p);
212 // -create GoudsmitSaundersonTable and init its Mott-correction member if
213 // Mott-correction was required
214 if (IsMaster()) {
215 // get the Mott-correction flag from EmParameters
216 if (G4EmParameters::Instance()->UseMottCorrection()) {
217 fIsUseMottCorrection = true;
218 }
219 // Mott-correction includes other way of PWA x-section corrections so deactivate it even if it was true
220 // when Mott-correction is activated by the user
221 if (fIsUseMottCorrection) {
222 fIsUsePWACorrection = false;
223 }
224 // clear GS-table
225 if (fGSTable) {
226 delete fGSTable;
227 fGSTable = nullptr;
228 }
229 // clear PWA corrections table if any
230 if (fPWACorrection) {
231 delete fPWACorrection;
232 fPWACorrection = nullptr;
233 }
234 // create GS-table
235 G4bool isElectron = true;
236 if (p->GetPDGCharge()>0.) {
237 isElectron = false;
238 }
239 fGSTable = new G4GoudsmitSaundersonTable(isElectron);
240 // G4GSTable will be initialised:
241 // - Screened-Rutherford DCS based GS angular distributions will be loaded only if they are not there yet
242 // - Mott-correction will be initialised if Mott-correction was requested to be used
243 fGSTable->SetOptionMottCorrection(fIsUseMottCorrection);
244 // - set PWA correction (correction to integrated quantites from Dirac-PWA)
245 fGSTable->SetOptionPWACorrection(fIsUsePWACorrection);
246 // init
247 fGSTable->Initialise(LowEnergyLimit(),HighEnergyLimit());
248 // create PWA corrections table if it was requested (and not deactivated because active Mott-correction)
249 if (fIsUsePWACorrection) {
250 fPWACorrection = new G4GSPWACorrections(isElectron);
251 fPWACorrection->Initialise();
252 }
253 }
254 fParticleChange = GetParticleChangeForMSC(p);
255}
256
257
259 fGSTable = static_cast<G4GoudsmitSaundersonMscModel*>(masterModel)->GetGSTable();
260 fIsUseMottCorrection = static_cast<G4GoudsmitSaundersonMscModel*>(masterModel)->GetOptionMottCorrection();
261 fIsUsePWACorrection = static_cast<G4GoudsmitSaundersonMscModel*>(masterModel)->GetOptionPWACorrection();
262 fPWACorrection = static_cast<G4GoudsmitSaundersonMscModel*>(masterModel)->GetPWACorrection();
263}
264
265
266// computes macroscopic first transport cross section: used only in testing not during mc transport
269 G4double kineticEnergy,
270 G4double,
271 G4double) {
272 // use Moliere's screening (with Mott-corretion if it was requested)
273 kineticEnergy = std::max(kineticEnergy, 10.*CLHEP::eV);
274 // // total mometum square, beta2
275 const G4double pt2 = kineticEnergy*(kineticEnergy + 2.0*electron_mass_c2);
276 const G4double beta2 = pt2/(pt2 + electron_mass_c2*electron_mass_c2);
277 const G4int matindx = static_cast<G4int>(mat->GetIndex());
278 // get the Mott-correcton factors if Mott-correcton was requested by the user
279 fMCtoScrA = 1.0;
280 fMCtoQ1 = 1.0;
281 fMCtoG2PerG1 = 1.0;
282 G4double scpCor = 1.0; // keep this for consistency (this method is not used in normal runs)
283 if (fIsUseMottCorrection) {
284 fGSTable->GetMottCorrectionFactors(G4Log(kineticEnergy), beta2, matindx, fMCtoScrA, fMCtoQ1, fMCtoG2PerG1);
285 // ! no scattering power correction since the current couple is not set before this interface method is called
286 // scpCor = fGSTable->ComputeScatteringPowerCorrection(currentCouple, kineticEnergy);
287 } else if (fIsUsePWACorrection) {
288 fPWACorrection->GetPWACorrectionFactors(G4Log(kineticEnergy), beta2, matindx, fMCtoScrA, fMCtoQ1, fMCtoG2PerG1);
289 // scpCor = fGSTable->ComputeScatteringPowerCorrection(currentCouple, kineticEnergy);
290 }
291 // screening parameter:
292 // - if Mott-corretioncorrection: the Screened-Rutherford times Mott-corretion DCS with this
293 // screening parameter gives back the (elsepa) PWA first transport cross section
294 // - if PWA correction: he Screened-Rutherford DCS with this screening parameter
295 // gives back the (elsepa) PWA first transport cross section
296 const G4double bc = fGSTable->GetMoliereBc(matindx);
297 fScrA = fGSTable->GetMoliereXc2(matindx)/(4.0*pt2*bc)*fMCtoScrA;
298 // elastic mean free path in Geant4 internal lenght units: the neglected (1+screening parameter) term is corrected
299 // (if Mott-corretion: the corrected screening parameter is used for this (1+A) correction + Moliere b_c is also
300 // corrected with the screening parameter correction)
301 fLambda0 = beta2*(1.+fScrA)*fMCtoScrA/(bc*scpCor);
302 // first transport coefficient (if Mott-corretion: the corrected screening parameter is used (it will be fully
303 // consistent with the one used during the pre-computation of the Mott-correted GS angular distributions))
304 fG1 = 2.0*fScrA*((1.0+fScrA)*G4Log(1.0/fScrA+1.0)-1.0);
305 // first transport mean free path
306 fLambda1 = fLambda0/fG1;
307 // return with the macroscopic first transport cross section
308 return fLambda1 > 0.0 ? 1.0/fLambda1 : 1.0E+20;
309}
310
311
312// gives back the first transport mean free path in internal G4 units
315 G4double kineticEnergy) {
316 // use Moliere's screening (with Mott-corretion if it was requested)
317 kineticEnergy = std::max(kineticEnergy, 10.0*CLHEP::eV);
318 // total mometum square, beta2
319 const G4double pt2 = kineticEnergy*(kineticEnergy + 2.0*electron_mass_c2);
320 const G4double beta2 = pt2/(pt2 + electron_mass_c2*electron_mass_c2);
321 const G4int matindx = static_cast<G4int>(currentCouple->GetMaterial()->GetIndex());
322 // get the Mott and scattering power correcton factors
323 // (if Mott-correcton was activated)
324 fMCtoScrA = 1.0;
325 fMCtoQ1 = 1.0;
326 fMCtoG2PerG1 = 1.0;
327 G4double scpCor = 1.0;
328 if (fIsUseMottCorrection) {
329 fGSTable->GetMottCorrectionFactors(G4Log(kineticEnergy), beta2, matindx, fMCtoScrA, fMCtoQ1, fMCtoG2PerG1);
330 scpCor = fGSTable->ComputeScatteringPowerCorrection(currentCouple, kineticEnergy);
331 } else if (fIsUsePWACorrection) {
332 fPWACorrection->GetPWACorrectionFactors(G4Log(kineticEnergy), beta2, matindx, fMCtoScrA, fMCtoQ1, fMCtoG2PerG1);
333 // scpCor = fGSTable->ComputeScatteringPowerCorrection(currentCouple, kineticEnergy);
334 }
335 // screening parameter:
336 // - if Mott-corretioncorrection: the Screened-Rutherford times Mott-corretion DCS with this
337 // screening parameter gives back the (elsepa) PWA first transport cross section
338 // - if PWA correction: he Screened-Rutherford DCS with this screening parameter
339 // gives back the (elsepa) PWA first transport cross section
340 const G4double bc = fGSTable->GetMoliereBc(matindx);
341 fScrA = fGSTable->GetMoliereXc2(matindx)/(4.0*pt2*bc)*fMCtoScrA;
342 // elastic mean free path in Geant4 internal lenght units: the neglected (1+screening parameter) term is corrected
343 // (if Mott-corretion: the corrected screening parameter is used for this (1+A) correction + Moliere b_c is also
344 // corrected with the screening parameter correction)
345 fLambda0 = beta2*(1.+fScrA)*fMCtoScrA/(bc*scpCor);
346 // first transport coefficient (if Mott-corretion: the corrected screening parameter is used (it will be fully
347 // consistent with the one used during the pre-computation of the Mott-correted GS angular distributions))
348 fG1 = 2.0*fScrA*((1.0+fScrA)*G4Log(1.0/fScrA+1.0)-1.0);
349 // first transport mean free path
350 fLambda1 = fLambda0/fG1;
351
352 return fLambda1;
353}
354
355
357G4GoudsmitSaundersonMscModel::GetTransportMeanFreePathOnly(const G4ParticleDefinition* /*partdef*/,
358 G4double kineticEnergy) {
359 // use Moliere's screening (with Mott-corretion if it was requested)
360 kineticEnergy = std::max(kineticEnergy, 10.0*CLHEP::eV);
361 // total mometum square, beta2
362 const G4double pt2 = kineticEnergy*(kineticEnergy + 2.0*electron_mass_c2);
363 const G4double beta2 = pt2/(pt2 + electron_mass_c2*electron_mass_c2);
364 const G4int matindx = static_cast<G4int>(currentCouple->GetMaterial()->GetIndex());
365 // get the Mott and scattering power correcton factors
366 // (if Mott-correcton was activated)
367 G4double mctoScrA = 1.0;
368 G4double mctoQ1 = 1.0;
369 G4double mctoG2PerG1 = 1.0;
370 G4double scpCor = 1.0;
371 if (fIsUseMottCorrection) {
372 fGSTable->GetMottCorrectionFactors(G4Log(kineticEnergy), beta2, matindx, mctoScrA, mctoQ1, mctoG2PerG1);
373 scpCor = fGSTable->ComputeScatteringPowerCorrection(currentCouple, kineticEnergy);
374 } else if (fIsUsePWACorrection) {
375 fPWACorrection->GetPWACorrectionFactors(G4Log(kineticEnergy), beta2, matindx, mctoScrA, mctoQ1, mctoG2PerG1);
376 // scpCor = fGSTable->ComputeScatteringPowerCorrection(currentCouple, kineticEnergy);
377 }
378 // screening parameter (with corrections)
379 const G4double bc = fGSTable->GetMoliereBc(matindx);
380 const G4double scrA = fGSTable->GetMoliereXc2(matindx)/(4.0*pt2*bc)*mctoScrA;
381 // elastic mean free path (under the SR DCS with Mott and scattering power corrections)
382 const G4double lambda0 = beta2*(1.+scrA)*mctoScrA/(bc*scpCor);
383 // first transport coefficient (under the SR DCS)
384 const G4double g1 = 2.0*scrA*((1.0+scrA)*G4Log(1.0/scrA+1.0)-1.0);
385 // first transport mean free path
386 const G4double lambda1 = lambda0/g1;
387
388 return lambda1;
389}
390
391
395
396
398 G4double& currentMinimalStep) {
399 const G4DynamicParticle* dp = track.GetDynamicParticle();
400 G4StepPoint* sp = track.GetStep()->GetPreStepPoint();
401 G4StepStatus stepStatus = sp->GetStepStatus();
402 currentCouple = track.GetMaterialCutsCouple();
403 SetCurrentCouple(currentCouple);
404 currentMaterialIndex = (G4int)currentCouple->GetMaterial()->GetIndex();
405 currentKinEnergy = dp->GetKineticEnergy();
406 currentRange = GetRange(particle,currentKinEnergy,currentCouple,
407 dp->GetLogKineticEnergy());
408 // elastic and first transport mfp, screening parameter and G1 are also set
409 // (Mott-correction will be used if it was requested by the user)
410 fLambda1 = GetTransportMeanFreePath(particle,currentKinEnergy);
411 // Set initial values:
412 // : lengths are initialised to currentMinimalStep which is the true, minimum
413 // step length from all other physics
414 fTheTrueStepLenght = currentMinimalStep;
415 fTheZPathLenght = currentMinimalStep; // will need to be converted
416 fTheDisplacementVector.set(0.0, 0.0, 0.0);
417 fTheNewDirection.set(0.0, 0.0, 1.0);
418
419 // Multiple scattering needs to be sampled ?
420 fIsMultipleScattering = false;
421 // Single scattering needs to be sampled ?
422 fIsSingleScattering = false;
423 // Was zero deflection in multiple scattering sampling ?
424 fIsNoScatteringInMSC = false;
425
426 // === The error-free stepping and boundary crossing
427 presafety = ComputeSafety(sp->GetPosition(), fTheTrueStepLenght);
428 // "range-rejection" like optimisation (i.e. deep inside the volume)
429 fIsSimplified = false;
430 if (fIsUseOptimisation && currentRange < presafety ) {
431 // it's assumed that the e-/e+ never leaves the volume
432 // --> simplified tracking, no MSC step limit, no displacement only deflection
433 fIsSimplified = true;
434 SampleMSC();
435 return ConvertTrueToGeom(fTheTrueStepLenght, currentMinimalStep);
436 } else {
437 // the step limit that corresponds to the accurate stepping and boundary
438 // crossing algorithm
439 // Set skin depth = skin x elastic_mean_free_path
440 const G4double skindepth = skin*fLambda0;
441 // Check if we can try Single Scattering because we are within skindepth
442 // distance from/to a boundary OR the current minimum true-step-length is
443 // shorter than skindepth. NOTICE: the latest has only efficieny reasons
444 // because the MSC angular sampling is fine for any short steps but much
445 // faster to try single scattering in case of short steps.
446 if ((stepStatus == fGeomBoundary) || (presafety < skindepth) || (fTheTrueStepLenght < skindepth)) {
447 //Try single scattering:
448 // - sample distance to next single scattering interaction (sslimit)
449 // - compare to current minimum length
450 // == if sslimit is the shorter:
451 // - set the step length to sslimit
452 // - indicate that single scattering needs to be done
453 // == else : nothing to do
454 //- in both cases, the step length was very short so geometrical and
455 // true path length are the same
456 const G4double sslimit = -1.*fLambda0*G4Log(G4UniformRand());
457 // compare to current minimum step length
458 if (sslimit < fTheTrueStepLenght) {
459 fTheTrueStepLenght = sslimit;
460 fIsSingleScattering = true;
461 }
462 // short step -> true step length equal to geometrical path length
463 fTheZPathLenght = fTheTrueStepLenght;
464 // Set that everything was done in step-limit phase (so no MSC call later)
465 // We will check if we need to perform the single-scattering angular
466 // sampling i.e. if single elastic scattering was the winer!
467 } else {
468 // After checking we know that we cannot try single scattering so we will
469 // need to make an MSC step
470 // Indicate that we need to make and MSC step.
471 fIsMultipleScattering = true;
472 // limit from range factor (keep this to allow stricter control)
473 fTheTrueStepLenght = std::min(fTheTrueStepLenght, facrange*currentRange);
474 // never let the particle go further than the safety if we are out of the skin
475 // if we are here we are out of the skin, presafety > 0.
476 if (fTheTrueStepLenght > presafety) {
477 fTheTrueStepLenght = std::min(fTheTrueStepLenght, presafety);
478 }
479 // make sure that we are still within the aplicability of condensed histry model
480 // i.e. true step length is not longer than 1/2 first transport mean free path.
481 // We schould take into account energy loss along the 1/2*lambda_transport1
482 // step length as well but we don't. So let it just 0.5*lambda_transport1
483 fTheTrueStepLenght = std::min(fTheTrueStepLenght, fLambda1*0.5);
484 }
485 }
486 // === end of step limit
487 // performe single scattering/multiple scattering
488 if (fIsMultipleScattering) {
489 // sample multiple scattering (including zero, one, few and multiple)
490 // the final position will also be calculated using the accurate LLCA
491 // leading to the transport vector, its projection to the current direction
492 // (`fTheZPathLenght`) and the perpendicular plane (`fTheDisplacementVector`)
493 // The MSC scattering able is also used to set the `fTheNewDirection`
494 SampleMSC();
495 } else if (fIsSingleScattering) {
496 // Sample single scattering angular deflection and claculate the new
497 // direction that is set in `fTheNewDirection`
498 // Note: `fTheZPathLenght` set to `true-step-length` while `fTheDisplacementVector`
499 // stays `(0, 0, 0)` vector in this case.
500 const G4double lekin = G4Log(currentKinEnergy);
501 const G4double pt2 = currentKinEnergy*(currentKinEnergy+2.0*CLHEP::electron_mass_c2);
502 const G4double beta2 = pt2/(pt2+CLHEP::electron_mass_c2*CLHEP::electron_mass_c2);
503 G4double cost = fGSTable->SingleScattering(1.0, fScrA, lekin, beta2, currentMaterialIndex);
504 cost = std::max(-1.0, std::min(cost, 1.0));
505 // compute sint
506 const G4double dum = 1.0 - cost;
507 const G4double sint = std::sqrt(dum*(2.0 - dum));
508 const G4double phi = CLHEP::twopi*G4UniformRand();
509 const G4double sinPhi = std::sin(phi);
510 const G4double cosPhi = std::cos(phi);
511 fTheNewDirection.set(sint*cosPhi, sint*sinPhi, cost);
512 } // otherwise: single scattering case because within skin but not elastic
513 // scattering proposed the shortest step (but most likely geometry)
514 return ConvertTrueToGeom(fTheTrueStepLenght, currentMinimalStep);
515}
516
518 // convert true -> geom (before transportation still at the pre-step point)
519 // (called from the step limitation ComputeTruePathLengthLimit)
520 // "range-rejection" like optimisation (just give whatever geometrical length)
521 if (fIsSimplified) {
522 fTheZPathLenght = std::min(fTheTrueStepLenght, fLambda1);
523 }
524 // Note: as we computed the post-step point, transport vector and its projection
525 // along the pre-step point direction at the step limit phase in
526 // `ComputeTruePathLengthLimit` above, we just return that projection here
527 return fTheZPathLenght;
528}
529
531 // convert geom -> true (after transportation already at the post-step point)
532 // Note: we computed the post-step point, transport vector and its projection
533 // along the pre-step point direction at the step limit phase in
534 // `ComputeTruePathLengthLimit` above. That computation was bsed on the
535 // actual true step length that was stored. Morover, we ensured that
536 // that post-step point will be either within the safety or used single
537 // scattering within the skin. Therefore, the post-step point is either
538 // exactly as we expected (`geomStepLength == fTheZPathLenght` as
539 // transportation could move the track with `fTheZPathLenght`) or the
540 // boundary was reached (`geomStepLength < fTheZPathLenght`) but then
541 // it was a single scattering step. The true step length is the one
542 // stored in the first case while equal to the geometrical one in the
543 // second case (as it was a single scattering step anyway).
544 fIsEndedUpOnBoundary = false;
545 if (geomStepLength < fTheZPathLenght) {
546 // reached boundary (or very last step) otherwise
547 fIsEndedUpOnBoundary = true;
548 fTheZPathLenght = geomStepLength;
549 fTheTrueStepLenght = geomStepLength;
550 }
551 return fTheTrueStepLenght;
552}
553
556 // do nothing on the boundary (reached that in single scattering mode)
557 if (!fIsEndedUpOnBoundary) {
558 // "range-rejection" like optimisation
559 if (fIsSimplified && !fIsNoScatteringInMSC) {
560 fTheNewDirection.rotateUz(oldDirection);
561 fParticleChange->ProposeMomentumDirection(fTheNewDirection);
562 return fTheDisplacementVector;
563 }
564 // if single scattering mode (i.e. within skin) and it's actually happened
565 if (fIsSingleScattering) {
566 fTheNewDirection.rotateUz(oldDirection);
567 fParticleChange->ProposeMomentumDirection(fTheNewDirection);
568 return fTheDisplacementVector;
569 }
570 // if multiple scattering mode (i.e. out of skin) and it's actually happened
571 if (fIsMultipleScattering && !fIsNoScatteringInMSC) {
572 fTheNewDirection.rotateUz(oldDirection);
573 fTheDisplacementVector.rotateUz(oldDirection);
574 fParticleChange->ProposeMomentumDirection(fTheNewDirection);
575 }
576 }
577 // on the boundary: reached in single scattering (or optimisation) -> displacement is (0,0,0))
578 return fTheDisplacementVector;
579}
580
582 fIsNoScatteringInMSC = false;
583 // Energy loss correction: calculate effective energy and step length
584 // (Eqs.(77) in my notes)
585 //
586 // `currentKinEnergy` is the pre-step point kinetic energy `E_0`
587 // (mean) energy loss (estimate only assuming a step length = `fTheTrueStepLenght`)
588 const G4double eloss = currentKinEnergy - GetEnergy(particle, currentRange - fTheTrueStepLenght, currentCouple);
589 const G4double midEkin = currentKinEnergy - 0.5*eloss; // mid-step energy `\tilde{E} := E_0 - 0.5*\Delta E`
590 const G4double tau = midEkin/electron_mass_c2; // `\tau := \tilde{E}/(mc^2)`
591 const G4double tau2 = tau*tau;
592 const G4double eps0 = eloss/currentKinEnergy; // energy loss fraction to pre-step energy: `\epsilon := \Delta E/E_0`
593 const G4double epsm = eloss/midEkin; // energy loss fraction to the mid-step energy: `\epsilon := \Delta E/\tilde{E}`
594 const G4double epsm2 = epsm*epsm;
595 const G4double effEner = midEkin*(1.0 - epsm2*(6.0 + 10.0*tau + 5.0*tau2)/(24.0*tau2 + 72.0*tau + 48.0));
596 const G4double dum = 1.0/((tau + 1.0)*(tau + 2.0));
597 const G4double effStep = fTheTrueStepLenght*(1.0 - 0.166666*epsm2*dum*dum*(4.0 + tau*(6.0 + tau*(7.0 + tau*(4.0 + tau)))));
598 // compute elastic mfp, first transport mfp, screening parameter, and G1 at this `E_eff`
599 // (with Mott-correction if it was requested by the user)
600 fLambda1 = GetTransportMeanFreePath(particle, effEner);
601 // s/lambda_el i.e. mean number elastic scattering along the step
602 // (using the effective step length and energy)
603 const G4double lambdan = fLambda0 > 0.0 ? effStep/fLambda0 : 0.0;
604 // do nothing in case of very small mean elastic scattering
605 if (lambdan <= 1.0E-12) {
606 fTheZPathLenght = fTheTrueStepLenght;
607 fIsNoScatteringInMSC = true;
608 return;
609 }
610 // first moment: 2.* lambdan *scrA*((1.+scrA)*log(1.+1./scrA)-1.);
611 // (which is equal to Q1 = s*G1/\lambda = s/\lambda_1)
612 G4double Qn1 = lambdan *fG1;
613 // sample scattering angles: divide the step into two and combine at the end
614 // NOTE: this shouldn't happen in normal case as we limit the step such that
615 // `s_max <= 0.5*\lambda_1` leading to `Q1_max = s/|lambda_1 = 0.5`.
616 // But we can see `Q1 > 0.5` in some corner case (e.g. due to the energy
617 // loss along the step as we assumed `\lambda_1` to be constant).
618 // Anyway, our first GS angular distribution table covers the [0.001,0.99]
619 // which is already well above the validity of the MSC theory as well as
620 // the LLCA algorithm that gives accurate post-step positions only up to
621 // Q1 < ~ 0.5. This is why we have that `s_max = 0.5*\lambda_1` step limit.
622 // (while we have a second GS angular distribution table up to Q1=7.99
623 // that is a smooth convergence to isotropic angular distributions)
624 G4double cosTheta1 = 1.0, sinTheta1 = 0.0, cosTheta2 = 1.0, sinTheta2 = 0.0;
625 if (0.5*Qn1 < 7.0) {
626 // (expected to have Qn1 that are usually < ~0.5)
627 // sample 2 scattering cost1, sint1, cost2 and sint2 for half of the step
628 const G4double lekin = G4Log(effEner);
629 const G4double pt2 = effEner*(effEner + 2.0*CLHEP::electron_mass_c2);
630 const G4double beta2 = pt2/(pt2 + CLHEP::electron_mass_c2*CLHEP::electron_mass_c2);
631 // backup GS angular dtr pointer (kinetic energy and delta index in case of Mott-correction)
632 // if the first was an msc sampling (the same will be used if the second is also an msc step)
634 G4int mcEkinIdx = -1;
635 G4int mcDeltIdx = -1;
636 G4double transfPar = 0.0;
637 G4bool isMsc = fGSTable->Sampling(0.5*lambdan, 0.5*Qn1, fScrA, cosTheta1, sinTheta1, lekin, beta2,
638 currentMaterialIndex, &gsDtr, mcEkinIdx, mcDeltIdx, transfPar,
639 true);
640 fGSTable->Sampling(0.5*lambdan, 0.5*Qn1, fScrA, cosTheta2, sinTheta2, lekin, beta2,
641 currentMaterialIndex, &gsDtr, mcEkinIdx, mcDeltIdx, transfPar, !isMsc);
642 if (cosTheta1 + cosTheta2 == 2.0) { // no scattering happened
643 fTheZPathLenght = fTheTrueStepLenght;
644 fIsNoScatteringInMSC = true;
645 return;
646 }
647 } else {
648 // corner case e.g. last step or "range-rejection" like opt.--> isotropic scattering
649 cosTheta1 = 1.-2.*G4UniformRand();
650 sinTheta1 = std::sqrt((1.-cosTheta1)*(1.+cosTheta1));
651 cosTheta2 = 1.-2.*G4UniformRand();
652 sinTheta2 = std::sqrt((1.-cosTheta2)*(1.+cosTheta2));
653 }
654 // sample 2 azimuthal angles
655 const G4double phi1 = CLHEP::twopi*G4UniformRand();
656 const G4double sinPhi1 = std::sin(phi1);
657 const G4double cosPhi1 = std::cos(phi1);
658 const G4double phi2 = CLHEP::twopi*G4UniformRand();
659 const G4double sinPhi2 = std::sin(phi2);
660 const G4double cosPhi2 = std::cos(phi2);
661 // compute final direction after the two scatterings (from the initial 0,0,1 dir)
662 const G4double u2 = sinTheta2*cosPhi2;
663 const G4double v2 = sinTheta2*sinPhi2;
664 const G4double tm = cosTheta1*u2 + sinTheta1*cosTheta2;
665 const G4double uf = tm*cosPhi1 - v2*sinPhi1;
666 const G4double vf = tm*sinPhi1 + v2*cosPhi1;
667 const G4double wf = cosTheta1*cosTheta2 - sinTheta1*u2;
668 // set the new direction (still in the scattering frame that is 0,0,1)
669 fTheNewDirection.set(uf, vf, wf);
670 //
671 // "range-rejection" like optimisation --> no dispalcement
672 if (fIsSimplified) {
673 return;
674 }
675 //
676 // == Compute final position (using the accurate LLCA)
677 // [NIMB 142(1998) Eq.76-80 with energy loss correction from Med.Phys. 27 (2000))
678 // apply correction to Q1
679 Qn1 *= fMCtoQ1;
680 // energy loss correction (gamma, delta, eta with alpha1 and alpha2 for eloss cor.)
681 // note: quantities are based on the SR DCS, gamma and delta with (Mott, screening, DPWA) corrections
682 // while the energy loss correction (alpha1, alpha2) is pure SR DCS based.
683 // compute alpha1, delta + alpha2 and gamma for energy loss correction
684 const G4double tp2 = tau + 2.0;
685 const G4double tp1 = tau + 1.0;
686 const G4double loga = G4Log(1.0 + 1.0/fScrA);
687 const G4double lgf1 = loga*(1.0 + fScrA) - 1.0;
688 const G4double lgf2 = loga*(1.0 + 2.0*fScrA) - 2.0;
689 // energy loss correction factors \alpha_1 and 1.0-alpha1
690 const G4double alpha1 = ((2.0 + tau*tp2)/tp1 - tp1/lgf1)*epsm/tp2;
691 // \alpha2 (plus the -0.25.. contrib from higer order)
692 const G4double alpha2 = 0.4082483*(eps0*tp1/(tp2*lgf1*lgf2) - 0.25*alpha1*alpha1);
693 // then gamma and delta (with energy loss correction: \delta -> \delta + \alpha_2)
694 const G4double gamma = (6.0*fScrA*lgf2*(1.0 + fScrA)/fG1)*fMCtoG2PerG1;
695 // calculate \delta --> \delta + alpha_2
696 const G4double delta = 0.9082483 - (0.1020621 - 0.0263747*gamma)*Qn1 + alpha2;
697 //
698 // ready to calculate the final position:
699 // sample \eta from p(eta)=2*eta i.e. P(eta) = eta_square ;-> P(eta) = rand --> eta = sqrt(rand)
700 const G4double eta = std::sqrt(G4UniformRand());
701 const G4double eta0 = 0.5*(1 - eta)*(1.0 + alpha1); // \eta_0 --> \eta_0(1 + \alpha_1)
702 const G4double eta1 = eta*delta; // \eta_1
703 const G4double eta2 = eta*(1.0 - delta); // \eta_2
704 // calculate the post-step point: xf, yf, zf are the final position coordinates
705 // divided by the (true) step length `s` and as given in NIMB 142 (1998)
706 // with the energy loss correction derived in Med.Phys. 27 (2000)
707 const G4double a1 = 0.5*(1 - eta)*(1.0 - alpha1);
708 const G4double w1v2 = cosTheta1*v2;
709 const G4double dum1 = eta1*sinTheta1;
710 const G4double xf = dum1*cosPhi1 + eta2*(cosPhi1*u2 - sinPhi1*w1v2) + a1*uf;
711 const G4double yf = dum1*sinPhi1 + eta2*(sinPhi1*u2 + cosPhi1*w1v2) + a1*vf;
712 const G4double zf = eta0 + eta1*cosTheta1 + eta2*cosTheta2 + a1*wf;
713 // final position relative to the pre-step point in the scattering frame
714 // (rx, ry, rz) = (xf, yf, zf)*s
715 const G4double rx = xf*fTheTrueStepLenght;
716 const G4double ry = yf*fTheTrueStepLenght;
717 const G4double rz = zf*fTheTrueStepLenght;
718 // calculate the transport distance (with protection: tr_distance <= true_step_length)
719 const G4double transportDistance = std::min(std::sqrt(rx*rx + ry*ry + rz*rz), fTheTrueStepLenght);
720 // set the z-path length i.e. the geometrical length to be the transport
721 // distance the displacement such that the final post-step point will
722 // eventually be at (rx, ry, rz) relative to the pre-step point (with rotations)
723 fTheZPathLenght = transportDistance;
724 fTheDisplacementVector.set(rx, ry, rz - fTheZPathLenght);
725}
G4double G4Log(G4double x)
Definition G4Log.hh:169
G4StepStatus
@ fGeomBoundary
CLHEP::Hep3Vector G4ThreeVector
double G4double
Definition G4Types.hh:83
bool G4bool
Definition G4Types.hh:86
int G4int
Definition G4Types.hh:85
const G4double alpha2
#define G4UniformRand()
Definition Randomize.hh:52
G4double GetLogKineticEnergy() const
G4ParticleDefinition * GetDefinition() const
G4double GetKineticEnergy() const
static G4EmParameters * Instance()
G4double ComputeTruePathLengthLimit(const G4Track &track, G4double &currentMinimalStep) override
G4ThreeVector & SampleScattering(const G4ThreeVector &, G4double safety) override
G4double ComputeGeomPathLength(G4double truePathLength) override
G4GoudsmitSaundersonMscModel(const G4String &nam="GoudsmitSaunderson")
void Initialise(const G4ParticleDefinition *, const G4DataVector &) override
G4GoudsmitSaundersonTable * GetGSTable()
G4double ComputeTrueStepLength(G4double geomStepLength) override
void InitialiseLocal(const G4ParticleDefinition *p, G4VEmModel *masterModel) override
G4double GetTransportMeanFreePath(const G4ParticleDefinition *, G4double)
G4double CrossSectionPerVolume(const G4Material *, const G4ParticleDefinition *, G4double kineticEnergy, G4double cutEnergy=0.0, G4double maxEnergy=DBL_MAX) override
G4double ComputeScatteringPowerCorrection(const G4MaterialCutsCouple *matcut, G4double ekin)
void GetMottCorrectionFactors(G4double logekin, G4double beta2, G4int matindx, G4double &mcToScr, G4double &mcToQ1, G4double &mcToG2PerG1)
const G4Material * GetMaterial() const
std::size_t GetIndex() const
G4StepPoint * GetPreStepPoint() const
const G4DynamicParticle * GetDynamicParticle() const
const G4MaterialCutsCouple * GetMaterialCutsCouple() const
const G4Step * GetStep() const
G4double LowEnergyLimit() const
G4bool IsMaster() const
void SetCurrentCouple(const G4MaterialCutsCouple *)
G4double HighEnergyLimit() const
G4VEmModel(const G4String &nam)
Definition G4VEmModel.cc:67
G4double facrange
G4double skin
G4VMscModel(const G4String &nam)
G4ParticleChangeForMSC * GetParticleChangeForMSC(const G4ParticleDefinition *p=nullptr)
G4double GetEnergy(const G4ParticleDefinition *part, G4double range, const G4MaterialCutsCouple *couple)
G4double GetRange(const G4ParticleDefinition *part, G4double kineticEnergy, const G4MaterialCutsCouple *couple)
G4double ConvertTrueToGeom(G4double &tLength, G4double &gLength)
G4double ComputeSafety(const G4ThreeVector &position, G4double limit=DBL_MAX)
void InitialiseParameters(const G4ParticleDefinition *)