Geant4 Cross Reference

Cross-Referencing   Geant4
Geant4/materials/src/G4DensityEffectCalculator.cc

Version: [ ReleaseNotes ] [ 1.0 ] [ 1.1 ] [ 2.0 ] [ 3.0 ] [ 3.1 ] [ 3.2 ] [ 4.0 ] [ 4.0.p1 ] [ 4.0.p2 ] [ 4.1 ] [ 4.1.p1 ] [ 5.0 ] [ 5.0.p1 ] [ 5.1 ] [ 5.1.p1 ] [ 5.2 ] [ 5.2.p1 ] [ 5.2.p2 ] [ 6.0 ] [ 6.0.p1 ] [ 6.1 ] [ 6.2 ] [ 6.2.p1 ] [ 6.2.p2 ] [ 7.0 ] [ 7.0.p1 ] [ 7.1 ] [ 7.1.p1 ] [ 8.0 ] [ 8.0.p1 ] [ 8.1 ] [ 8.1.p1 ] [ 8.1.p2 ] [ 8.2 ] [ 8.2.p1 ] [ 8.3 ] [ 8.3.p1 ] [ 8.3.p2 ] [ 9.0 ] [ 9.0.p1 ] [ 9.0.p2 ] [ 9.1 ] [ 9.1.p1 ] [ 9.1.p2 ] [ 9.1.p3 ] [ 9.2 ] [ 9.2.p1 ] [ 9.2.p2 ] [ 9.2.p3 ] [ 9.2.p4 ] [ 9.3 ] [ 9.3.p1 ] [ 9.3.p2 ] [ 9.4 ] [ 9.4.p1 ] [ 9.4.p2 ] [ 9.4.p3 ] [ 9.4.p4 ] [ 9.5 ] [ 9.5.p1 ] [ 9.5.p2 ] [ 9.6 ] [ 9.6.p1 ] [ 9.6.p2 ] [ 9.6.p3 ] [ 9.6.p4 ] [ 10.0 ] [ 10.0.p1 ] [ 10.0.p2 ] [ 10.0.p3 ] [ 10.0.p4 ] [ 10.1 ] [ 10.1.p1 ] [ 10.1.p2 ] [ 10.1.p3 ] [ 10.2 ] [ 10.2.p1 ] [ 10.2.p2 ] [ 10.2.p3 ] [ 10.3 ] [ 10.3.p1 ] [ 10.3.p2 ] [ 10.3.p3 ] [ 10.4 ] [ 10.4.p1 ] [ 10.4.p2 ] [ 10.4.p3 ] [ 10.5 ] [ 10.5.p1 ] [ 10.6 ] [ 10.6.p1 ] [ 10.6.p2 ] [ 10.6.p3 ] [ 10.7 ] [ 10.7.p1 ] [ 10.7.p2 ] [ 10.7.p3 ] [ 10.7.p4 ] [ 11.0 ] [ 11.0.p1 ] [ 11.0.p2 ] [ 11.0.p3, ] [ 11.0.p4 ] [ 11.1 ] [ 11.1.1 ] [ 11.1.2 ] [ 11.1.3 ] [ 11.2 ] [ 11.2.1 ] [ 11.2.2 ] [ 11.3.0 ]

  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  * Implements calculation of the Fermi density effect as per the method
 29  * described in:
 30  *
 31  *   R. M. Sternheimer, M. J. Berger, and S. M. Seltzer. Density
 32  *   effect for the ionization loss of charged particles in various sub-
 33  *   stances. Atom. Data Nucl. Data Tabl., 30:261, 1984.
 34  *
 35  * Which (among other Sternheimer references) builds on:
 36  *
 37  *   R. M. Sternheimer. The density effect for ionization loss in
 38  *   materials. Phys. Rev., 88:851­859, 1952.
 39  *
 40  * The returned values of delta are directly from the Sternheimer calculation,
 41  * and not Sternheimer's popular three-part approximate parameterization
 42  * introduced in the same paper.
 43  *
 44  * Author: Matthew Strait <straitm@umn.edu> 2019
 45  */
 46 
 47 #include "G4DensityEffectCalculator.hh"
 48 
 49 #include "G4AtomicShells.hh"
 50 #include "G4NistManager.hh"
 51 #include "G4Pow.hh"
 52 
 53 static G4Pow* gpow = G4Pow::GetInstance();
 54 
 55 const G4int maxWarnings = 20;
 56 
 57 G4DensityEffectCalculator::G4DensityEffectCalculator(const G4Material* mat, G4int n)
 58   : fMaterial(mat), nlev(n)
 59 {
 60   fVerbose = std::max(fVerbose, G4NistManager::Instance()->GetVerbose());
 61 
 62   sternf = new G4double[nlev];
 63   levE = new G4double[nlev];
 64   sternl = new G4double[nlev];
 65   sternEbar = new G4double[nlev];
 66   for (G4int i = 0; i < nlev; ++i) {
 67     sternf[i] = 0.0;
 68     levE[i] = 0.0;
 69     sternl[i] = 0.0;
 70     sternEbar[i] = 0.0;
 71   }
 72 
 73   fConductivity = sternx = 0.0;
 74   G4bool conductor = (fMaterial->GetFreeElectronDensity() > 0.0);
 75 
 76   G4int sh = 0;
 77   G4double sum = 0.;
 78   const G4double tot = fMaterial->GetTotNbOfAtomsPerVolume();
 79   for (size_t j = 0; j < fMaterial->GetNumberOfElements(); ++j) {
 80     // The last subshell is considered to contain the conduction
 81     // electrons. Sternheimer 1984 says "the lowest chemical valance of
 82     // the element" is used to set the number of conduction electrons.
 83     // I'm not sure if that means the highest subshell or the whole
 84     // shell, but in any case, he also says that the choice is arbitrary
 85     // and offers a possible alternative. This is one of the sources of
 86     // uncertainty in the model.
 87     const G4double frac = fMaterial->GetVecNbOfAtomsPerVolume()[j] / tot;
 88     const G4int Z = fMaterial->GetElement((G4int)j)->GetZasInt();
 89     const G4int nshell = G4AtomicShells::GetNumberOfShells(Z);
 90     for (G4int i = 0; i < nshell; ++i) {
 91       // For conductors, put *all* top shell electrons into the conduction
 92       // band, regardless of element.
 93       const G4double xx = frac * G4AtomicShells::GetNumberOfElectrons(Z, i);
 94       if (i < nshell - 1 || ! conductor) {
 95         sternf[sh] += xx;
 96       }
 97       else {
 98         fConductivity += xx;
 99       }
100       levE[sh] = G4AtomicShells::GetBindingEnergy(Z, i) / CLHEP::eV;
101       ++sh;
102     }
103   }
104   for (G4int i = 0; i < nlev; ++i) {
105     sum += sternf[i];
106   }
107   sum += fConductivity;
108 
109   const G4double invsum = (sum > 0.0) ? 1. / sum : 0.0;
110   for (G4int i = 0; i < nlev; ++i) {
111     sternf[i] *= invsum;
112   }
113   fConductivity *= invsum;
114   plasmaE = fMaterial->GetIonisation()->GetPlasmaEnergy() / CLHEP::eV;
115   meanexcite = fMaterial->GetIonisation()->GetMeanExcitationEnergy() / CLHEP::eV;
116 }
117 
118 G4DensityEffectCalculator::~G4DensityEffectCalculator()
119 {
120   delete[] sternf;
121   delete[] levE;
122   delete[] sternl;
123   delete[] sternEbar;
124 }
125 
126 G4double G4DensityEffectCalculator::ComputeDensityCorrection(G4double x)
127 {
128   if (fVerbose > 1) {
129     G4cout << "G4DensityEffectCalculator::ComputeDensityCorrection for " << fMaterial->GetName()
130            << ", x= " << x << G4endl;
131   }
132   const G4double approx = fMaterial->GetIonisation()->GetDensityCorrection(x);
133   const G4double exact = FermiDeltaCalculation(x);
134 
135   if (fVerbose > 1) {
136     G4cout << "   Delta: computed= " << exact << ", parametrized= " << approx << G4endl;
137   }
138   if (approx >= 0. && exact < 0.) {
139     if (fVerbose > 0) {
140       ++fWarnings;
141       if (fWarnings < maxWarnings) {
142         G4ExceptionDescription ed;
143         ed << "Sternheimer fit failed for " << fMaterial->GetName() << ", x = " << x
144            << ": Delta exact= " << exact << ", approx= " << approx;
145         G4Exception("G4DensityEffectCalculator::DensityCorrection", "mat008", JustWarning, ed);
146       }
147     }
148     return approx;
149   }
150   // Fall back to approx if exact and approx are very different, under the
151   // assumption that this means the exact calculation has gone haywire
152   // somehow, with the exception of the case where approx is negative.  I
153   // have seen this clearly-wrong result occur for substances with extremely
154   // low density (1e-25 g/cc).
155   if (approx >= 0. && std::abs(exact - approx) > 1.) {
156     if (fVerbose > 0) {
157       ++fWarnings;
158       if (fWarnings < maxWarnings) {
159         G4ExceptionDescription ed;
160         ed << "Sternheimer exact= " << exact << " and approx= " << approx
161            << " are too different for " << fMaterial->GetName() << ", x = " << x;
162         G4Exception("G4DensityEffectCalculator::DensityCorrection", "mat008", JustWarning, ed);
163       }
164     }
165     return approx;
166   }
167   return exact;
168 }
169 
170 G4double G4DensityEffectCalculator::FermiDeltaCalculation(G4double x)
171 {
172   // Above beta*gamma of 10^10, the exact treatment is within machine
173   // precision of the limiting case, for ordinary solids, at least. The
174   // convergence goes up as the density goes down, but even in a pretty
175   // hard vacuum it converges by 10^20. Also, it's hard to imagine how
176   // this energy is relevant (x = 20 -> 10^19 GeV for muons). So this
177   // is mostly not here for physical reasons, but rather to avoid ugly
178   // discontinuities in the return value.
179   if (x > 20.) {
180     return -1.;
181   }
182 
183   sternx = x;
184   G4double sternrho = Newton(1.5, true);
185 
186   // Negative values, and values much larger than unity are non-physical.
187   // Values between zero and one are also suspect, but not as clearly wrong.
188   if (sternrho <= 0. || sternrho > 100.) {
189     if (fVerbose > 0) {
190       ++fWarnings;
191       if (fWarnings < maxWarnings) {
192         G4ExceptionDescription ed;
193         ed << "Sternheimer computation failed for " << fMaterial->GetName() << ", x = " << x
194            << ":\n"
195            << "Could not solve for Sternheimer rho. Probably you have a \n"
196            << "mean ionization energy which is incompatible with your\n"
197            << "distribution of energy levels, or an unusually dense material.\n"
198            << "Number of levels: " << nlev << " Mean ionization energy(eV): " << meanexcite
199            << " Plasma energy(eV): " << plasmaE << "\n";
200         for (G4int i = 0; i < nlev; ++i) {
201           ed << "Level " << i << ": strength " << sternf[i] << ": energy(eV)= " << levE[i] << "\n";
202         }
203         G4Exception("G4DensityEffectCalculator::SetupFermiDeltaCalc", "mat008", JustWarning, ed);
204       }
205     }
206     return -1.;
207   }
208 
209   // Calculate the Sternheimer adjusted energy levels and parameters l_i given
210   // the Sternheimer parameter rho.
211   for (G4int i = 0; i < nlev; ++i) {
212     sternEbar[i] = levE[i] * (sternrho / plasmaE);
213     sternl[i] = std::sqrt(gpow->powN(sternEbar[i], 2) + (2. / 3.) * sternf[i]);
214   }
215   // The derivative of the function we are solving for is strictly
216   // negative for positive (physical) values, so if the value at
217   // zero is less than zero, it has no solution, and there is no
218   // density effect in the Sternheimer "exact" treatment (which is
219   // still an approximation).
220   //
221   // For conductors, this test is not needed, because Ell(L) contains
222   // the term fConductivity/(L*L), so the value at L=0 is always
223   // positive infinity. In the code we don't return inf, though, but
224   // rather set that term to zero, which means that if this test were
225   // used, it would give the wrong result for some materials.
226   if (fConductivity == 0 && Ell(0) <= 0) {
227     return 0;
228   }
229 
230   // Attempt to find the root from 40 starting points evenly distributed
231   // in log space.  Trying a single starting point is not sufficient for
232   // convergence in most cases.
233   for (G4int startLi = -10; startLi < 30; ++startLi) {
234     const G4double sternL = Newton(gpow->powN(2, startLi), false);
235     if (sternL != -1.) {
236       return DeltaOnceSolved(sternL);
237     }
238   }
239   return -1.;  // Signal the caller to use the Sternheimer approximation,
240                // because we have been unable to solve the exact form.
241 }
242 
243 /* Newton's method for finding roots.  Adapted from G4PolynominalSolver, but
244  * without the assumption that the input is a polynomial.  Also, here we
245  * always expect the roots to be positive, so return -1 as an error value. */
246 G4double G4DensityEffectCalculator::Newton(G4double start, G4bool first)
247 {
248   const G4int maxIter = 100;
249   G4int nbad = 0, ngood = 0;
250 
251   G4double lambda(start), value(0.), dvalue(0.);
252 
253   if (fVerbose > 2) {
254     G4cout << "G4DensityEffectCalculator::Newton: strat= " << start << " type: " << first << G4endl;
255   }
256   while (true) {
257     if (first) {
258       value = FRho(lambda);
259       dvalue = DFRho(lambda);
260     }
261     else {
262       value = Ell(lambda);
263       dvalue = DEll(lambda);
264     }
265     if (dvalue == 0.0) {
266       break;
267     }
268     const G4double del = value / dvalue;
269     lambda -= del;
270 
271     const G4double eps = std::abs(del / lambda);
272     if (eps <= 1.e-12) {
273       ++ngood;
274       if (ngood == 2) {
275         if (fVerbose > 2) {
276           G4cout << "  Converged with result= " << lambda << G4endl;
277         }
278         return lambda;
279       }
280     }
281     else {
282       ++nbad;
283     }
284     if (nbad > maxIter || std::isnan(value) || std::isinf(value)) {
285       break;
286     }
287   }
288   if (fVerbose > 2) {
289     G4cout << "  Failed to converge last value= " << value << " dvalue= " << dvalue
290            << " lambda= " << lambda << G4endl;
291   }
292   return -1.;
293 }
294 
295 /* Return the derivative of the equation used
296  * to solve for the Sternheimer parameter rho. */
297 G4double G4DensityEffectCalculator::DFRho(G4double rho)
298 {
299   G4double ans = 0.0;
300   for (G4int i = 0; i < nlev; ++i) {
301     if (sternf[i] > 0.) {
302       ans += sternf[i] * gpow->powN(levE[i], 2) * rho /
303              (gpow->powN(levE[i] * rho, 2) + 2. / 3. * sternf[i] * gpow->powN(plasmaE, 2));
304     }
305   }
306   return ans;
307 }
308 
309 /* Return the functional value for the equation used
310  * to solve for the Sternheimer parameter rho. */
311 G4double G4DensityEffectCalculator::FRho(G4double rho)
312 {
313   G4double ans = 0.0;
314   for (G4int i = 0; i < nlev; ++i) {
315     if (sternf[i] > 0.) {
316       ans += sternf[i] *
317              G4Log(gpow->powN(levE[i] * rho, 2) + 2. / 3. * sternf[i] * gpow->powN(plasmaE, 2));
318     }
319   }
320   ans *= 0.5;  // pulled out of loop for efficiency
321 
322   if (fConductivity > 0.) {
323     ans += fConductivity * G4Log(plasmaE * std::sqrt(fConductivity));
324   }
325   ans -= G4Log(meanexcite);
326   return ans;
327 }
328 
329 /* Return the derivative for the equation used to
330  * solve for the Sternheimer parameter l, called 'L' here. */
331 G4double G4DensityEffectCalculator::DEll(G4double L)
332 {
333   G4double ans = 0.;
334   for (G4int i = 0; i < nlev; ++i) {
335     if (sternf[i] > 0 && (sternEbar[i] > 0. || L != 0.)) {
336       const G4double y = gpow->powN(sternEbar[i], 2);
337       ans += sternf[i] / gpow->powN(y + L * L, 2);
338     }
339   }
340   ans += fConductivity / gpow->powN(L * L, 2);
341   ans *= (-2 * L);  // pulled out of the loop for efficiency
342   return ans;
343 }
344 
345 /* Return the functional value for the equation used to
346  * solve for the Sternheimer parameter l, called 'L' here. */
347 G4double G4DensityEffectCalculator::Ell(G4double L)
348 {
349   G4double ans = 0.;
350   for (G4int i = 0; i < nlev; ++i) {
351     if (sternf[i] > 0. && (sternEbar[i] > 0. || L != 0.)) {
352       ans += sternf[i] / (gpow->powN(sternEbar[i], 2) + L * L);
353     }
354   }
355   if (fConductivity > 0. && L != 0.) {
356     ans += fConductivity / (L * L);
357   }
358   ans -= gpow->powZ(10, -2 * sternx);
359   return ans;
360 }
361 
362 /**
363  * Given the Sternheimer parameter l (called 'sternL' here), and that
364  * the l_i and adjusted energies have been found with SetupFermiDeltaCalc(),
365  * return the value of delta.  Helper function for DoFermiDeltaCalc().
366  */
367 G4double G4DensityEffectCalculator::DeltaOnceSolved(G4double sternL)
368 {
369   G4double ans = 0.;
370   for (G4int i = 0; i < nlev; ++i) {
371     if (sternf[i] > 0.) {
372       ans += sternf[i] *
373              G4Log((gpow->powN(sternl[i], 2) + gpow->powN(sternL, 2)) / gpow->powN(sternl[i], 2));
374     }
375   }
376   // sternl for the conduction electrons is sqrt(fConductivity), with
377   // no factor of 2./3 as with the other levels.
378   if (fConductivity > 0) {
379     ans += fConductivity * G4Log((fConductivity + gpow->powN(sternL, 2)) / fConductivity);
380   }
381   ans -= gpow->powN(sternL, 2) / (1 + gpow->powZ(10, 2 * sternx));
382   return ans;
383 }
384