source: trunk/examples/extended/electromagnetic/TestEm7/src/G4ScreenedNuclearRecoil.cc@ 1036

Last change on this file since 1036 was 807, checked in by garnier, 17 years ago

update

File size: 35.3 KB
Line 
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// $Id: G4ScreenedNuclearRecoil.cc,v 1.5 2008/01/14 12:11:39 vnivanch Exp $
28// GEANT4 tag $Name: $
29//
30//
31// Class Description
32// Process for screened electromagnetic nuclear elastic scattering;
33// Physics comes from:
34// Marcus H. Mendenhall and Robert A. Weller,
35// "Algorithms for the rapid computation of classical cross
36// sections for screened Coulomb collisions "
37// Nuclear Instruments and Methods in Physics Research B58 (1991) 11-17
38// The only input required is a screening function phi(r/a) which is the ratio
39// of the actual interatomic potential for two atoms with atomic numbers Z1 and Z2,
40// to the unscreened potential Z1*Z2*e^2/r where e^2 is elm_coupling in Geant4 units
41//
42// First version, April 2004, Marcus H. Mendenhall, Vanderbilt University
43//
44// 5 May, 2004, Marcus Mendenhall
45// Added an option for enhancing hard collisions statistically, to allow
46// backscattering calculations to be carried out with much improved event rates,
47// without distorting the multiple-scattering broadening too much.
48// the method SetCrossSectionHardening(G4double fraction, G4double HardeningFactor)
49// sets what fraction of the events will be randomly hardened,
50// and the factor by which the impact area is reduced for such selected events.
51//
52// 21 November, 2004, Marcus Mendenhall
53// added static_nucleus to IsApplicable
54//
55// 7 December, 2004, Marcus Mendenhall
56// changed mean free path of stopping particle from 0.0 to 1.0*nanometer
57// to avoid new verbose warning about 0 MFP in 4.6.2p02
58//
59// 17 December, 2004, Marcus Mendenhall
60// added code to permit screening out overly close collisions which are
61// expected to be hadronic, not Coulombic
62//
63// 19 December, 2004, Marcus Mendenhall
64// massive rewrite to add modular physics stages and plug-in cross section table
65// computation. This allows one to select (e.g.) between the normal external python
66// process and an embedded python interpreter (which is much faster) for generating
67// the tables.
68// It also allows one to switch between sub-sampled scattering (event biasing) and
69// normal scattering, and between non-relativistic kinematics and relativistic
70// kinematic approximations, without having a class for every combination. Further, one can
71// add extra stages to the scattering, which can implement various book-keeping processes.
72//
73// January 2007, Marcus Mendenhall
74// Reorganized heavily for inclusion in Geant4 Core. All modules merged into
75// one source and header, all historic code removed.
76//
77// Class Description - End
78
79
80#include <stdio.h>
81
82#include "globals.hh"
83
84#include "G4ScreenedNuclearRecoil.hh"
85
86#include "G4ParticleTypes.hh"
87#include "G4ParticleTable.hh"
88#include "G4VParticleChange.hh"
89#include "G4ParticleChange.hh"
90#include "G4DataVector.hh"
91#include "G4Track.hh"
92#include "G4Step.hh"
93
94#include "G4Material.hh"
95#include "G4Element.hh"
96#include "G4Isotope.hh"
97#include "G4MaterialCutsCouple.hh"
98#include "G4ElementVector.hh"
99#include "G4IsotopeVector.hh"
100
101#include "G4RangeTest.hh"
102#include "G4ParticleDefinition.hh"
103#include "G4DynamicParticle.hh"
104#include "G4ProcessManager.hh"
105#include "G4StableIsotopes.hh"
106
107#include "Randomize.hh"
108
109#include "CLHEP/Units/PhysicalConstants.h"
110
111#include <iostream>
112#include <iomanip>
113
114G4ScreenedCoulombCrossSection::~G4ScreenedCoulombCrossSection()
115{
116 ScreeningMap::iterator tables=screeningData.begin();
117 for (;tables != screeningData.end(); tables++) {
118 delete (*tables).second.EMphiData;
119 }
120 screeningData.clear();
121
122 std::map<G4int, c2_function<G4double> *>::iterator mfpit=MFPTables.begin();
123 for (;mfpit != MFPTables.end(); mfpit++) {
124 delete (*mfpit).second;
125 }
126 MFPTables.clear();
127
128}
129
130const G4double G4ScreenedCoulombCrossSection::massmap[nMassMapElements+1]={
131 0, 1.007940, 4.002602, 6.941000, 9.012182, 10.811000, 12.010700,
132 14.006700, 15.999400, 18.998403, 20.179700, 22.989770, 24.305000, 26.981538, 28.085500,
133 30.973761, 32.065000, 35.453000, 39.948000, 39.098300, 40.078000, 44.955910, 47.867000,
134 50.941500, 51.996100, 54.938049, 55.845000, 58.933200, 58.693400, 63.546000, 65.409000,
135 69.723000, 72.640000, 74.921600, 78.960000, 79.904000, 83.798000, 85.467800, 87.620000,
136 88.905850, 91.224000, 92.906380, 95.940000, 98.000000, 101.070000, 102.905500, 106.420000,
137 107.868200, 112.411000, 114.818000, 118.710000, 121.760000, 127.600000, 126.904470, 131.293000,
138 132.905450, 137.327000, 138.905500, 140.116000, 140.907650, 144.240000, 145.000000, 150.360000,
139 151.964000, 157.250000, 158.925340, 162.500000, 164.930320, 167.259000, 168.934210, 173.040000,
140 174.967000, 178.490000, 180.947900, 183.840000, 186.207000, 190.230000, 192.217000, 195.078000,
141 196.966550, 200.590000, 204.383300, 207.200000, 208.980380, 209.000000, 210.000000, 222.000000,
142 223.000000, 226.000000, 227.000000, 232.038100, 231.035880, 238.028910, 237.000000, 244.000000,
143 243.000000, 247.000000, 247.000000, 251.000000, 252.000000, 257.000000, 258.000000, 259.000000,
144 262.000000, 261.000000, 262.000000, 266.000000, 264.000000, 277.000000, 268.000000, 281.000000,
145 272.000000, 285.000000, 282.500000, 289.000000, 287.500000, 292.000000};
146
147G4ParticleDefinition* G4ScreenedCoulombCrossSection::SelectRandomUnweightedTarget(const G4MaterialCutsCouple* couple)
148{
149 // Select randomly an element within the material, according to number density only
150 const G4Material* material = couple->GetMaterial();
151 G4int nMatElements = material->GetNumberOfElements();
152 const G4ElementVector* elementVector = material->GetElementVector();
153 const G4Element *element=0;
154 G4ParticleDefinition*target=0;
155
156 // Special case: the material consists of one element
157 if (nMatElements == 1)
158 {
159 element= (*elementVector)[0];
160 }
161 else
162 {
163 // Composite material
164 G4double random = G4UniformRand() * material->GetTotNbOfAtomsPerVolume();
165 G4double nsum=0.0;
166 const G4double *atomDensities=material->GetVecNbOfAtomsPerVolume();
167
168 for (G4int k=0 ; k < nMatElements ; k++ )
169 {
170 nsum+=atomDensities[k];
171 element= (*elementVector)[k];
172 if (nsum >= random) break;
173 }
174 }
175
176 G4int N=0;
177 G4int Z=(G4int)std::floor(element->GetZ()+0.5);
178
179 G4int nIsotopes=element->GetNumberOfIsotopes();
180 if(!nIsotopes) {
181 if(Z<=92) {
182 // we have no detailed material isotopic info available,
183 // so use G4StableIsotopes table up to Z=92
184 static G4StableIsotopes theIso; // get a stable isotope table for default results
185 nIsotopes=theIso.GetNumberOfIsotopes(Z);
186 G4double random = 100.0*G4UniformRand(); // values are expressed as percent, sum is 100
187 G4int tablestart=theIso.GetFirstIsotope(Z);
188 G4double asum=0.0;
189 for(G4int i=0; i<nIsotopes; i++) {
190 asum+=theIso.GetAbundance(i+tablestart);
191 N=theIso.GetIsotopeNucleonCount(i+tablestart);
192 if(asum >= random) break;
193 }
194 } else {
195 // too heavy for stable isotope table, just use mean mass
196 N=(G4int)std::floor(element->GetN()+0.5);
197 }
198 } else {
199 G4int i;
200 const G4IsotopeVector *isoV=element->GetIsotopeVector();
201 G4double random = G4UniformRand();
202 G4double *abundance=element->GetRelativeAbundanceVector();
203 G4double asum=0.0;
204 for(i=0; i<nIsotopes; i++) {
205 asum+=abundance[i];
206 N=(*isoV)[i]->GetN();
207 if(asum >= random) break;
208 }
209 }
210
211 // get the official definition of this nucleus, to get the correct value of A
212 // note that GetIon is very slow, so we will cache ones we have already found ourselves.
213 ParticleCache::iterator p=targetMap.find(Z*1000+N);
214 if (p != targetMap.end()) {
215 target=(*p).second;
216 } else{
217 target=G4ParticleTable::GetParticleTable()->GetIon(Z, N, 0.0);
218 targetMap[Z*1000+N]=target;
219 }
220 return target;
221}
222
223void G4ScreenedCoulombCrossSection::BuildMFPTables()
224{
225 const G4int nmfpvals=200;
226
227 std::vector<G4double> evals(nmfpvals), mfpvals(nmfpvals);
228
229 // sum up inverse MFPs per element for each material
230 const G4MaterialTable* materialTable = G4Material::GetMaterialTable();
231 if (materialTable == 0)
232 G4Exception("G4ScreenedCoulombCrossSection::BuildMFPTables - no MaterialTable found)");
233
234 G4int nMaterials = G4Material::GetNumberOfMaterials();
235
236 for (G4int matidx=0; matidx < nMaterials; matidx++) {
237
238 const G4Material* material= (*materialTable)[matidx];
239 const G4ElementVector &elementVector = *(material->GetElementVector());
240 const G4int nMatElements = material->GetNumberOfElements();
241
242 const G4Element *element=0;
243 const G4double *atomDensities=material->GetVecNbOfAtomsPerVolume();
244
245 G4double emin=0, emax=0; // find innermost range of cross section functions
246 for (G4int kel=0 ; kel < nMatElements ; kel++ )
247 {
248 element=elementVector[kel];
249 G4int Z=(G4int)std::floor(element->GetZ()+0.5);
250 c2_function<G4double> &ifunc=*sigmaMap[Z];
251 if(!kel || ifunc.xmin() > emin) emin=ifunc.xmin();
252 if(!kel || ifunc.xmax() < emax) emax=ifunc.xmax();
253 }
254
255 G4double logint=std::log(emax/emin) / (nmfpvals-1) ; // logarithmic increment for tables
256
257 // compute energy scale for interpolator. Force exact values at both ends to avoid range errors
258 for (G4int i=1; i<nmfpvals-1; i++) evals[i]=emin*std::exp(logint*i);
259 evals.front()=emin;
260 evals.back()=emax;
261
262 // zero out the inverse mfp sums to start
263 for (G4int eidx=0; eidx < nmfpvals; eidx++) mfpvals[eidx] = 0.0;
264
265 // sum inverse mfp for each element in this material and for each energy
266 for (G4int kel=0 ; kel < nMatElements ; kel++ )
267 {
268 element=elementVector[kel];
269 G4int Z=(G4int)std::floor(element->GetZ()+0.5);
270 c2_function<G4double> &sigma=*sigmaMap[Z];
271 G4double ndens = atomDensities[kel]; // compute atom fraction for this element in this material
272
273 for (G4int eidx=0; eidx < nmfpvals; eidx++) {
274 mfpvals[eidx] += ndens*sigma(evals[eidx]);
275 }
276 }
277
278 // convert inverse mfp to regular mfp
279 for (G4int eidx=0; eidx < nmfpvals; eidx++) {
280 mfpvals[eidx] = 1.0/mfpvals[eidx];
281 }
282 // and make a new interpolating function out of the sum
283 MFPTables[matidx] = static_cast<c2_function<G4double> *>(new log_log_interpolating_function<G4double>(
284 evals, mfpvals));
285 }
286
287#ifdef DEBUG
288 for (G4int matidx=0; matidx < nMaterials; matidx++) {
289 const G4Material* material= (*materialTable)[matidx];
290 G4cout << "***** MFP (1MeV) ***** " << material->GetName() << " " << (*MFPTables[matidx])(1.0) << G4endl;
291 }
292#endif
293
294}
295
296G4ScreenedNuclearRecoil::
297G4ScreenedNuclearRecoil(const G4String& processName,
298 const G4String &ScreeningKey,
299 G4bool GenerateRecoils,
300 G4double RecoilCutoff, G4double PhysicsCutoff) :
301 G4VDiscreteProcess(processName),
302 screeningKey(ScreeningKey),
303 generateRecoils(GenerateRecoils), avoidReactions(1),
304 recoilCutoff(RecoilCutoff), physicsCutoff(PhysicsCutoff),
305 hardeningFraction(0.0), hardeningFactor(1.0),
306 externalCrossSectionConstructor(0)
307{
308 highEnergyLimit=100.0*MeV;
309 lowEnergyLimit=physicsCutoff;
310 registerDepositedEnergy=1; // by default, don't hide NIEL
311 MFPScale=1.0;
312 // SetVerboseLevel(2);
313 AddStage(new G4ScreenedCoulombClassicalKinematics);
314 AddStage(new G4SingleScatter);
315}
316
317void G4ScreenedNuclearRecoil::ResetTables()
318{
319 std::map<G4int, c2_function<G4double>*>::iterator xh=meanFreePathTables.begin();
320 for(;xh != meanFreePathTables.end(); xh++) {
321 delete (*xh).second;
322 }
323 meanFreePathTables.clear();
324
325 std::map<G4int, G4ScreenedCoulombCrossSection*>::iterator xt=crossSectionHandlers.begin();
326 for(;xt != crossSectionHandlers.end(); xt++) {
327 delete (*xt).second;
328 }
329 crossSectionHandlers.clear();
330}
331
332void G4ScreenedNuclearRecoil::ClearStages()
333{
334 // I don't think I like deleting the processes here... they are better abandoned
335 // if the creator doesn't get rid of them
336 // std::vector<G4ScreenedCollisionStage *>::iterator stage=collisionStages.begin();
337 //for(; stage != collisionStages.end(); stage++) delete (*stage);
338
339 collisionStages.clear();
340}
341
342G4ScreenedNuclearRecoil::~G4ScreenedNuclearRecoil()
343{
344 ResetTables();
345}
346
347// returns true if it appears the nuclei collided, and we are interested in checking
348G4bool G4ScreenedNuclearRecoil::CheckNuclearCollision(
349 G4double A, G4double a1, G4double apsis) {
350 return avoidReactions && (apsis < (1.1*(std::pow(A,1.0/3.0)+std::pow(a1,1.0/3.0)) + 1.4)*fermi);
351 // nuclei are within 1.4 fm (reduced pion Compton wavelength) of each other at apsis,
352 // this is hadronic, skip it
353}
354
355G4ScreenedCoulombCrossSection *G4ScreenedNuclearRecoil::GetNewCrossSectionHandler(void) {
356 G4ScreenedCoulombCrossSection *xc;
357 if(!externalCrossSectionConstructor) xc=new G4NativeScreenedCoulombCrossSection;
358 else xc=externalCrossSectionConstructor->create();
359 xc->SetVerbosity(verboseLevel);
360 return xc;
361}
362
363G4double G4ScreenedNuclearRecoil::GetMeanFreePath(const G4Track& track,
364 G4double,
365 G4ForceCondition*)
366{
367 const G4DynamicParticle* incoming = track.GetDynamicParticle();
368 G4double energy = incoming->GetKineticEnergy();
369 G4double a1=incoming->GetDefinition()->GetPDGMass()/amu_c2;
370
371 G4double meanFreePath;
372
373 if (energy < lowEnergyLimit || energy < recoilCutoff) return 1.0*nanometer; /* stop slow particles! */
374 else if (energy > highEnergyLimit*a1) energy=highEnergyLimit*a1; /* constant MFP at high energy */
375
376 G4double fz1=incoming->GetDefinition()->GetPDGCharge();
377 G4int z1=(G4int)(fz1/eplus + 0.5);
378
379 std::map<G4int, G4ScreenedCoulombCrossSection*>::iterator xh=
380 crossSectionHandlers.find(z1);
381 G4ScreenedCoulombCrossSection *xs;
382
383 if (xh==crossSectionHandlers.end()) {
384 xs =crossSectionHandlers[z1]=GetNewCrossSectionHandler();
385 xs->LoadData(screeningKey, z1, a1, physicsCutoff);
386 xs->BuildMFPTables();
387 } else xs=(*xh).second;
388
389 const G4MaterialCutsCouple* materialCouple = track.GetMaterialCutsCouple();
390 size_t materialIndex = materialCouple->GetMaterial()->GetIndex();
391
392 c2_function<G4double> &mfp=*(*xs)[materialIndex];
393
394 // make absolutely certain we don't get an out-of-range energy
395 meanFreePath = mfp(std::min(std::max(energy, mfp.xmin()), mfp.xmax()));
396
397 // G4cout << "MFP: " << meanFreePath << " index " << materialIndex << " energy " << energy << " MFPScale " << MFPScale << G4endl;
398
399 return meanFreePath*MFPScale;
400}
401
402G4VParticleChange* G4ScreenedNuclearRecoil::PostStepDoIt(const G4Track& aTrack, const G4Step& aStep)
403{
404 validCollision=1;
405 aParticleChange.Initialize(aTrack);
406 NIEL=0.0; // default is no NIEL deposited
407
408 // do universal setup
409
410 const G4DynamicParticle* incidentParticle = aTrack.GetDynamicParticle();
411 G4ParticleDefinition *baseParticle=aTrack.GetDefinition();
412
413 G4double fz1=baseParticle->GetPDGCharge()/eplus;
414 G4int z1=(G4int)(fz1+0.5);
415 G4double incidentEnergy = incidentParticle->GetKineticEnergy();
416
417 // Select randomly one element and (possibly) isotope in the current material.
418 const G4MaterialCutsCouple* couple = aTrack.GetMaterialCutsCouple();
419
420 if(incidentEnergy < GetRecoilCutoff()) { // check energy sanity on entry
421 if(!baseParticle->GetProcessManager()->
422 GetAtRestProcessVector()->size())
423 aParticleChange.ProposeTrackStatus(fStopAndKill);
424 else
425 aParticleChange.ProposeTrackStatus(fStopButAlive);
426
427 AddToNIEL(incidentEnergy);
428 aParticleChange.ProposeEnergy(0.0);
429 // stop the particle and bail out
430 validCollision=0;
431 }
432
433 const G4Material* mat = couple->GetMaterial();
434 G4double numberDensity=mat->GetTotNbOfAtomsPerVolume();
435 G4double lattice=0.5/std::pow(numberDensity,1.0/3.0); // typical lattice half-spacing
436 G4double length=GetCurrentInteractionLength();
437 G4double sigopi=1.0/(CLHEP::pi*numberDensity*length); // this is sigma0/pi
438
439 // compute the impact parameter very early, so if is rejected as too far away, little effort is wasted
440 // this is the TRIM method for determining an impact parameter based on the flight path
441 // this gives a cumulative distribution of N(P)= 1-exp(-pi P^2 n l)
442 // which says the probability of NOT hitting a disk of area sigma= pi P^2 =exp(-sigma N l)
443 // which may be reasonable
444 G4double P;
445 if(sigopi < lattice*lattice) {
446 // normal long-flight approximation
447 P = std::sqrt(-std::log(G4UniformRand()) *sigopi);
448 } else {
449 // short-flight limit
450 P = std::sqrt(G4UniformRand())*lattice;
451 }
452
453 G4double fraction=GetHardeningFraction();
454 if(fraction && G4UniformRand() < fraction) {
455 // pick out some events, and increase the central cross section
456 // by reducing the impact parameter
457 P /= std::sqrt(GetHardeningFactor());
458 }
459
460
461 // check if we are far enough away that the energy transfer must be below cutoff,
462 // and leave everything alone if so, saving a lot of time.
463 if(P*P > sigopi) {
464 if(GetVerboseLevel() > 1)
465 printf("ScreenedNuclear impact reject: length=%.3f P=%.4f limit=%.4f\n",
466 length/angstrom, P/angstrom,std::sqrt(sigopi)/angstrom);
467 // no collision, don't follow up with anything
468 validCollision=0;
469 }
470
471 // find out what we hit, and record it in our kinematics block.
472 if(validCollision) {
473 G4ScreenedCoulombCrossSection *xsect=GetCrossSectionHandlers()[z1];
474 G4ParticleDefinition *recoilIon=
475 xsect->SelectRandomUnweightedTarget(couple);
476 kinematics.crossSection=xsect;
477 kinematics.recoilIon=recoilIon;
478 kinematics.impactParameter=P;
479 kinematics.a1=baseParticle->GetPDGMass()/amu_c2;
480 kinematics.a2=recoilIon->GetPDGMass()/amu_c2;
481 } else {
482 kinematics.recoilIon=0;
483 kinematics.impactParameter=0;
484 kinematics.a1=baseParticle->GetPDGMass()/amu_c2;
485 kinematics.a2=0;
486 }
487
488 std::vector<G4ScreenedCollisionStage *>::iterator stage=collisionStages.begin();
489
490 for(; stage != collisionStages.end(); stage++)
491 (*stage)->DoCollisionStep(this,aTrack, aStep);
492
493 if(registerDepositedEnergy) {
494 aParticleChange.ProposeLocalEnergyDeposit(NIEL);
495 aParticleChange.ProposeNonIonizingEnergyDeposit(NIEL);
496 }
497 return G4VDiscreteProcess::PostStepDoIt( aTrack, aStep );
498}
499
500G4bool G4ScreenedCoulombClassicalKinematics::DoScreeningComputation(G4ScreenedNuclearRecoil *master,
501 const G4ScreeningTables *screen, G4double eps, G4double beta)
502{
503 G4double au=screen->au;
504 G4CoulombKinematicsInfo &kin=master->GetKinematics();
505 G4double A=kin.a2;
506 G4double a1=kin.a1;
507
508 G4double xx0; // first estimate of closest approach
509 if(eps < 5.0) {
510 G4double y=std::log(eps);
511 G4double mlrho4=((((3.517e-4*y+1.401e-2)*y+2.393e-1)*y+2.734)*y+2.220);
512 G4double rho4=std::exp(-mlrho4); // W&M eq. 18
513 G4double bb2=0.5*beta*beta;
514 xx0=std::sqrt(bb2+std::sqrt(bb2*bb2+rho4)); // W&M eq. 17
515 } else {
516 G4double ee=1.0/(2.0*eps);
517 xx0=ee+std::sqrt(ee*ee+beta*beta); // W&M eq. 15 (Rutherford value)
518 if(master->CheckNuclearCollision(A, a1, xx0*au)) return 0; // nuclei too close
519
520 }
521
522 c2_function<G4double> &phiData=*(screen->EMphiData);
523 // instantiate all the needed functions statically, so no allocation is done at run time
524 // we will be solving x^2 - x phi(x*au)/eps - beta^2 == 0.0
525 // or, for easier scaling, x'^2 - x' au phi(x')/eps - beta^2 au^2
526 static c2_plugin_function<G4double> phifunc;
527 static c2_quadratic<G4double> xsq(0., 0., 0., 1.); // x^2
528 static c2_linear<G4double> xovereps(0., 0., 0.); // will fill this in with the right slope at run time
529 static c2_function<G4double> &xphi=xovereps*phifunc;
530 static c2_function<G4double> &diff=xsq-xphi;
531
532 xovereps.reset(0., 0.0, au/eps); // slope of x*au/eps term
533 phifunc.set_function(phiData); // install interpolating table
534
535 G4double xx1, phip, phip2;
536 G4int root_error;
537
538 xx1=diff.find_root(phiData.xmin(), std::min(10*xx0*au,phiData.xmax()),
539 std::min(xx0*au, phiData.xmax()), beta*beta*au*au, &root_error, &phip, &phip2)/au;
540
541 if(root_error) {
542 G4cout << "Screened Coulomb Root Finder Error" << G4endl;
543 G4cout << "au " << au << " A " << A << " a1 " << a1 << " xx1 " << xx1 << " eps " << eps << " beta " << beta << G4endl;
544 G4cout << " xmin " << phiData.xmin() << " xmax " << std::min(10*xx0*au,phiData.xmax()) ;
545 G4cout << " f(xmin) " << phifunc(phiData.xmin()) << " f(xmax) " << phifunc(std::min(10*xx0*au,phiData.xmax())) ;
546 G4cout << " xstart " << std::min(xx0*au, phiData.xmax()) << " target " << beta*beta*au*au ;
547 G4cout << G4endl;
548 throw c2_exception("Failed root find");
549 }
550
551 phifunc.unset_function(); // throws an exception if used without setting again
552 // phiprime is scaled by one factor of au because phi is evaluated at (xx0*au),
553 G4double phiprime=phip*au;
554
555 //lambda0 is from W&M 19
556 G4double lambda0=1.0/std::sqrt(0.5+beta*beta/(2.0*xx1*xx1)-phiprime/(2.0*eps));
557
558 //compute the 6-term Lobatto integral alpha (per W&M 21, with different coefficients)
559 // this is probably completely un-needed but gives the highest quality results,
560 G4double alpha=(1.0+ lambda0)/30.0;
561 G4double xvals[]={0.98302349, 0.84652241, 0.53235309, 0.18347974};
562 G4double weights[]={0.03472124, 0.14769029, 0.23485003, 0.18602489};
563 for(G4int k=0; k<4; k++) {
564 G4double x, ff;
565 x=xx1/xvals[k];
566 ff=1.0/std::sqrt(1.0-phiData(x*au)/(x*eps)-beta*beta/(x*x));
567 alpha+=weights[k]*ff;
568 }
569
570 G4double thetac1=CLHEP::pi*beta*alpha/xx1; // complement of CM scattering angle
571 G4double sintheta=std::sin(thetac1); //note sin(pi-theta)=sin(theta)
572 G4double costheta=-std::cos(thetac1); // note cos(pi-theta)=-cos(theta)
573 // G4double psi=std::atan2(sintheta, costheta+a1/A); // lab scattering angle (M&T 3rd eq. 8.69)
574
575 // numerics note: because we checked above for reasonable values of beta which give real recoils,
576 // we don't have to look too closely for theta -> 0 here (which would cause sin(theta)
577 // and 1-cos(theta) to both vanish and make the atan2 ill behaved).
578 G4double zeta=std::atan2(sintheta, 1-costheta); // lab recoil angle (M&T 3rd eq. 8.73)
579 G4double coszeta=std::cos(zeta);
580 G4double sinzeta=std::sin(zeta);
581
582 kin.sinTheta=sintheta;
583 kin.cosTheta=costheta;
584 kin.sinZeta=sinzeta;
585 kin.cosZeta=coszeta;
586 return 1; // all OK, collision is valid
587}
588
589void G4ScreenedCoulombClassicalKinematics::DoCollisionStep(G4ScreenedNuclearRecoil *master,
590 const G4Track& aTrack, const G4Step&) {
591
592 if(!master->GetValidCollision()) return;
593
594 G4ParticleChange &aParticleChange=master->GetParticleChange();
595 G4CoulombKinematicsInfo &kin=master->GetKinematics();
596
597 const G4DynamicParticle* incidentParticle = aTrack.GetDynamicParticle();
598 G4ParticleDefinition *baseParticle=aTrack.GetDefinition();
599
600 G4double incidentEnergy = incidentParticle->GetKineticEnergy();
601
602 // this adjustment to a1 gives the right results for soft (constant gamma)
603 // relativistic collisions. Hard collisions are wrong anyway, since the
604 // Coulombic and hadronic terms interfere and cannot be added.
605 G4double gamma=(1.0+incidentEnergy/baseParticle->GetPDGMass());
606 G4double a1=kin.a1*gamma; // relativistic gamma correction
607
608 G4ParticleDefinition *recoilIon=kin.recoilIon;
609 G4double A=recoilIon->GetPDGMass()/amu_c2;
610 G4int Z=(G4int)((recoilIon->GetPDGCharge()/eplus)+0.5);
611
612 G4double Ec = incidentEnergy*(A/(A+a1)); // energy in CM frame (non-relativistic!)
613 const G4ScreeningTables *screen=kin.crossSection->GetScreening(Z);
614 G4double au=screen->au; // screening length
615
616 G4double beta = kin.impactParameter/au; // dimensionless impact parameter
617 G4double eps = Ec/(screen->z1*Z*elm_coupling/au); // dimensionless energy
618
619 G4bool ok=DoScreeningComputation(master, screen, eps, beta);
620 if(!ok) {
621 master->SetValidCollision(0); // flag bad collision
622 return; // just bail out without setting valid flag
623 }
624
625 G4double eRecoil=4*incidentEnergy*a1*A*kin.cosZeta*kin.cosZeta/((a1+A)*(a1+A));
626 kin.eRecoil=eRecoil;
627
628 if(incidentEnergy-eRecoil < master->GetRecoilCutoff()) {
629 if(!baseParticle->GetProcessManager()->
630 GetAtRestProcessVector()->size())
631 aParticleChange.ProposeTrackStatus(fStopAndKill);
632 else
633 aParticleChange.ProposeTrackStatus(fStopButAlive);
634 aParticleChange.ProposeEnergy(0.0);
635 master->AddToNIEL(incidentEnergy-eRecoil);
636 }
637
638 if(master->GetEnableRecoils() && eRecoil > master->GetRecoilCutoff()) {
639 kin.recoilIon=recoilIon;
640 } else {
641 kin.recoilIon=0; // this flags no recoil to be generated
642 master->AddToNIEL(eRecoil) ;
643 }
644}
645
646void G4SingleScatter::DoCollisionStep(G4ScreenedNuclearRecoil *master,
647 const G4Track& aTrack, const G4Step&) {
648
649 if(!master->GetValidCollision()) return;
650
651 G4CoulombKinematicsInfo &kin=master->GetKinematics();
652 G4ParticleChange &aParticleChange=master->GetParticleChange();
653
654 const G4DynamicParticle* incidentParticle = aTrack.GetDynamicParticle();
655 G4double incidentEnergy = incidentParticle->GetKineticEnergy();
656 G4double eRecoil=kin.eRecoil;
657
658 G4double azimuth=G4UniformRand()*(2.0*CLHEP::pi);
659 G4double sa=std::sin(azimuth);
660 G4double ca=std::cos(azimuth);
661
662 G4ThreeVector recoilMomentumDirection(kin.sinZeta*ca, kin.sinZeta*sa, kin.cosZeta);
663 G4ParticleMomentum incidentDirection = incidentParticle->GetMomentumDirection();
664 recoilMomentumDirection=recoilMomentumDirection.rotateUz(incidentDirection);
665 G4ThreeVector recoilMomentum=recoilMomentumDirection*std::sqrt(2.0*eRecoil*kin.a2*amu_c2);
666
667 if(aParticleChange.GetEnergy() != 0.0) { // DoKinematics hasn't stopped it!
668 G4ThreeVector beamMomentum=incidentParticle->GetMomentum()-recoilMomentum;
669 aParticleChange.ProposeMomentumDirection(beamMomentum.unit()) ;
670 aParticleChange.ProposeEnergy(incidentEnergy-eRecoil);
671 }
672
673 if(kin.recoilIon) {
674 G4DynamicParticle* recoil = new G4DynamicParticle (kin.recoilIon,
675 recoilMomentumDirection,eRecoil) ;
676
677 aParticleChange.SetNumberOfSecondaries(1);
678 aParticleChange.AddSecondary(recoil);
679 }
680}
681
682G4bool G4ScreenedNuclearRecoil::
683IsApplicable(const G4ParticleDefinition& aParticleType)
684{
685 return aParticleType == *(G4Proton::Proton()) ||
686 aParticleType.GetParticleType() == "nucleus" ||
687 aParticleType.GetParticleType() == "static_nucleus";
688}
689
690
691void
692G4ScreenedNuclearRecoil::
693DumpPhysicsTable(const G4ParticleDefinition&)
694{
695}
696
697// This used to be the file mhmScreenedNuclearRecoil_native.cc
698// it has been included here to collect this file into a smaller number of packages
699
700#include "G4DataVector.hh"
701#include "G4Material.hh"
702#include "G4Element.hh"
703#include "G4Isotope.hh"
704#include "G4MaterialCutsCouple.hh"
705#include "G4ElementVector.hh"
706#include <vector>
707
708static c2_function<G4double> &ZBLScreening(G4int z1, G4int z2, size_t npoints, G4double rMax, G4double *auval)
709{
710 static const size_t ncoef=4;
711 static G4double scales[ncoef]={-3.2, -0.9432, -0.4028, -0.2016};
712 static G4double coefs[ncoef]={0.1818,0.5099,0.2802,0.0281};
713
714 G4double au=0.8854*angstrom*0.529/(std::pow(z1, 0.23)+std::pow(z2,0.23));
715 std::vector<G4double> r(npoints), phi(npoints);
716
717 for(size_t i=0; i<npoints; i++) {
718 G4double rr=(float)i/(float)(npoints-1);
719 r[i]=rr*rr*rMax; // use quadratic r scale to make sampling fine near the center
720 G4double sum=0.0;
721 for(size_t j=0; j<ncoef; j++) sum+=coefs[j]*std::exp(scales[j]*r[i]/au);
722 phi[i]=sum;
723 }
724
725 // compute the derivative at the origin for the spline
726 G4double phiprime0=0.0;
727 for(size_t j=0; j<ncoef; j++) phiprime0+=scales[j]*coefs[j]*std::exp(scales[j]*r[0]/au);
728 phiprime0*=(1.0/au); // put back in natural units;
729
730 *auval=au;
731 return *static_cast<c2_function<G4double> *>(new lin_log_interpolating_function<G4double>(r, phi, false, phiprime0));
732}
733
734static c2_function<G4double> &MoliereScreening(G4int z1, G4int z2, size_t npoints, G4double rMax, G4double *auval)
735{
736 static const size_t ncoef=3;
737 static G4double scales[ncoef]={-6.0, -1.2, -0.3};
738 static G4double coefs[ncoef]={0.10, 0.55, 0.35};
739
740 G4double au=0.8853*0.529*angstrom/std::sqrt(std::pow(z1, 0.6667)+std::pow(z2,0.6667));
741 std::vector<G4double> r(npoints), phi(npoints);
742
743 for(size_t i=0; i<npoints; i++) {
744 G4double rr=(float)i/(float)(npoints-1);
745 r[i]=rr*rr*rMax; // use quadratic r scale to make sampling fine near the center
746 G4double sum=0.0;
747 for(size_t j=0; j<ncoef; j++) sum+=coefs[j]*std::exp(scales[j]*r[i]/au);
748 phi[i]=sum;
749 }
750
751 // compute the derivative at the origin for the spline
752 G4double phiprime0=0.0;
753 for(size_t j=0; j<ncoef; j++) phiprime0+=scales[j]*coefs[j]*std::exp(scales[j]*r[0]/au);
754 phiprime0*=(1.0/au); // put back in natural units;
755
756 *auval=au;
757 return *static_cast<c2_function<G4double> *>(new lin_log_interpolating_function<G4double>(r, phi, false, phiprime0));
758}
759
760static c2_function<G4double> &LJScreening(G4int z1, G4int z2, size_t npoints, G4double rMax, G4double *auval)
761{
762//from Loftager, Besenbacher, Jensen & Sorensen
763//PhysRev A20, 1443++, 1979
764 G4double au=0.8853*0.529*angstrom/std::sqrt(std::pow(z1, 0.6667)+std::pow(z2,0.6667));
765 std::vector<G4double> r(npoints), phi(npoints);
766
767 for(size_t i=0; i<npoints; i++) {
768 G4double rr=(float)i/(float)(npoints-1);
769 r[i]=rr*rr*rMax; // use quadratic r scale to make sampling fine near the center
770
771 G4double y=std::sqrt(9.67*r[i]/au);
772 G4double ysq=y*y;
773 G4double phipoly=1+y+0.3344*ysq+0.0485*y*ysq+0.002647*ysq*ysq;
774 phi[i]=phipoly*std::exp(-y);
775 // G4cout << r[i] << " " << phi[i] << G4endl;
776 }
777
778 // compute the derivative at the origin for the spline
779 G4double logphiprime0=(9.67/2.0)*(2*0.3344-1.0); // #avoid 0/0 on first element
780 logphiprime0 *= (1.0/au); // #put back in natural units
781
782 *auval=au;
783 return *static_cast<c2_function<G4double> *>(new lin_log_interpolating_function<G4double>(r, phi, false, logphiprime0*phi[0]));
784}
785
786G4NativeScreenedCoulombCrossSection::~G4NativeScreenedCoulombCrossSection() {
787}
788
789G4NativeScreenedCoulombCrossSection::G4NativeScreenedCoulombCrossSection() {
790 AddScreeningFunction("zbl", ZBLScreening);
791 AddScreeningFunction("lj", LJScreening);
792 AddScreeningFunction("mol", MoliereScreening);
793}
794
795std::vector<G4String> G4NativeScreenedCoulombCrossSection::GetScreeningKeys() const {
796 std::vector<G4String> keys;
797 // find the available screening keys
798 std::map<std::string, ScreeningFunc>::const_iterator sfunciter=phiMap.begin();
799 for(; sfunciter != phiMap.end(); sfunciter++) keys.push_back((*sfunciter).first);
800 return keys;
801}
802
803static inline G4double cm_energy(G4double a1, G4double a2, G4double t0) {
804 // "relativistically correct energy in CM frame"
805 G4double m1=a1*amu_c2, m2=a2*amu_c2;
806 G4double mc2=(m1+m2);
807 G4double f=2.0*m2*t0/(mc2*mc2);
808 // old way: return (f < 1e-6) ? 0.5*mc2*f : mc2*(std::sqrt(1.0+f)-1.0);
809 // formally equivalent to previous, but numerically stable for all f without conditional
810 // uses identity (sqrt(1+x) - 1)(sqrt(1+x) + 1) = x
811 return mc2*f/(std::sqrt(1.0+f)+1.0);
812}
813
814static inline G4double thetac(G4double m1, G4double m2, G4double eratio) {
815 G4double s2th2=eratio*( (m1+m2)*(m1+m2)/(4.0*m1*m2) );
816 G4double sth2=std::sqrt(s2th2);
817 return 2.0*std::asin(sth2);
818}
819
820void G4NativeScreenedCoulombCrossSection::LoadData(G4String screeningKey, G4int z1, G4double a1, G4double recoilCutoff)
821{
822 static const size_t sigLen=200; // since sigma doesn't matter much, a very coarse table will do
823 G4DataVector energies(sigLen);
824 G4DataVector data(sigLen);
825
826 a1=standardmass(z1); // use standardized values for mass for building tables
827
828 const G4MaterialTable* materialTable = G4Material::GetMaterialTable();
829 if (materialTable == 0)
830 G4Exception("mhmNativeCrossSection::LoadData - no MaterialTable found)");
831
832 G4int nMaterials = G4Material::GetNumberOfMaterials();
833
834 for (G4int m=0; m<nMaterials; m++)
835 {
836 const G4Material* material= (*materialTable)[m];
837 const G4ElementVector* elementVector = material->GetElementVector();
838 const G4int nMatElements = material->GetNumberOfElements();
839
840 for (G4int iEl=0; iEl<nMatElements; iEl++)
841 {
842 G4Element* element = (*elementVector)[iEl];
843 G4int Z = (G4int) element->GetZ();
844 G4double a2=element->GetA()*(mole/gram);
845
846 if(sigmaMap.find(Z)!=sigmaMap.end()) continue; // we've already got this element
847
848 // find the screening function generator we need
849 std::map<std::string, ScreeningFunc>::iterator sfunciter=phiMap.find(screeningKey);
850 if(sfunciter==phiMap.end()) {
851 G4cout << "no such screening key " << screeningKey << G4endl; // FIXME later
852 exit(1);
853 }
854 ScreeningFunc sfunc=(*sfunciter).second;
855
856 G4double au;
857 c2_function<G4double> &screen=sfunc(z1, Z, 200, 50.0*angstrom, &au); // generate the screening data
858
859 G4ScreeningTables st;
860 st.EMphiData=&screen; // this is our phi table
861 st.z1=z1; st.m1=a1; st.z2=Z; st.m2=a2; st.emin=recoilCutoff;
862 st.au=au;
863
864 // now comes the hard part... build the total cross section tables from the phi table
865 //based on (pi-thetac) = pi*beta*alpha/x0, but noting that alpha is very nearly unity, always
866 //so just solve it wth alpha=1, which makes the solution much easier
867 //this function returns an approximation to (beta/x0)^2=phi(x0)/(eps*x0)-1 ~ ((pi-thetac)/pi)^2
868 //Since we don't need exact sigma values, this is good enough (within a factor of 2 almost always)
869 //this rearranges to phi(x0)/(x0*eps) = 2*theta/pi - theta^2/pi^2
870
871 c2_linear<G4double> c2au(0.0, 0.0, au);
872 c2_composed_function<G4double> phiau(screen, c2au); // build phi(x*au) for dimensionless phi
873 c2_linear<G4double> c2eps(0.0, 0.0, 0.0); // will store an appropriate eps inside this in loop
874 c2_ratio<G4double> x0func(phiau, c2eps); // this will be phi(x)/(x*eps) when c2eps is correctly set
875
876 G4double m1c2=a1*amu_c2;
877 G4double escale=z1*Z*elm_coupling/au; // energy at screening distance
878 G4double emax=m1c2; // model is doubtful in very relativistic range
879 G4double eratkin=0.9999*(4*a1*a2)/((a1+a2)*(a1+a2)); // #maximum kinematic ratio possible at 180 degrees
880 G4double cmfact0=st.emin/cm_energy(a1, a2, st.emin);
881 G4double l1=std::log(emax);
882 G4double l0=std::log(st.emin*cmfact0/eratkin);
883
884 if(verbosity >=1)
885 G4cout << "Native Screening: " << screeningKey << " " << z1 << " " << a1 << " " <<
886 Z << " " << a2 << " " << recoilCutoff << G4endl;
887
888 for(size_t idx=0; idx<sigLen; idx++) {
889 G4double ee=std::exp(idx*((l1-l0)/sigLen)+l0);
890 G4double gamma=1.0+ee/m1c2;
891 G4double eratio=(cmfact0*st.emin)/ee; // factor by which ee needs to be reduced to get emin
892 G4double theta=thetac(gamma*a1, a2, eratio);
893
894 G4double eps=cm_energy(a1, a2, ee)/escale; // #make sure lab energy is converted to CM for these calculations
895 c2eps.reset(0.0, 0.0, eps); // set correct slope in this function
896
897 G4double q=theta/pi;
898 // G4cout << ee << " " << m1c2 << " " << gamma << " " << eps << " " << theta << " " << q << G4endl;
899 G4double x0= x0func.find_root(1e-6*angstrom/au, 0.9999*screen.xmax()/au, 1.0, 2*q-q*q);
900 G4double betasquared=x0*x0 - x0*phiau(x0)/eps;
901 G4double sigma=pi*betasquared*au*au;
902 energies[idx]=ee;
903 data[idx]=sigma;
904 }
905
906 screeningData[Z]=st;
907 sigmaMap[Z] = static_cast<c2_function<G4double> *>(new log_log_interpolating_function<G4double>(
908 energies, data));
909 }
910 }
911}
Note: See TracBrowser for help on using the repository browser.