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

Last change on this file was 1230, checked in by garnier, 15 years ago

update to geant4.9.3

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