Geant4 11.2.2
Toolkit for the simulation of the passage of particles through matter
Loading...
Searching...
No Matches
G4DensityEffectCalculator.cc
Go to the documentation of this file.
1//
2// ********************************************************************
3// * License and Disclaimer *
4// * *
5// * The Geant4 software is copyright of the Copyright Holders of *
6// * the Geant4 Collaboration. It is provided under the terms and *
7// * conditions of the Geant4 Software License, included in the file *
8// * LICENSE and available at http://cern.ch/geant4/license . These *
9// * include a list of copyright holders. *
10// * *
11// * Neither the authors of this software system, nor their employing *
12// * institutes,nor the agencies providing financial support for this *
13// * work make any representation or warranty, express or implied, *
14// * regarding this software system or assume any liability for its *
15// * use. Please see the license in the file LICENSE and URL above *
16// * for the full disclaimer and the limitation of liability. *
17// * *
18// * This code implementation is the result of the scientific and *
19// * technical work of the GEANT4 collaboration. *
20// * By using, copying, modifying or distributing the software (or *
21// * any work based on the software) you agree to acknowledge its *
22// * use in resulting scientific publications, and indicate your *
23// * acceptance of all terms of the Geant4 Software license. *
24// ********************************************************************
25//
26
27/*
28 * 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 <[email protected]> 2019
45 */
46
48
49#include "G4AtomicShells.hh"
50#include "G4NistManager.hh"
51#include "G4Pow.hh"
52
53static G4Pow* gpow = G4Pow::GetInstance();
54
55const G4int maxWarnings = 20;
56
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();
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
119{
120 delete[] sternf;
121 delete[] levE;
122 delete[] sternl;
123 delete[] sternEbar;
124}
125
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) {
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) {
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
170G4double 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) {
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. */
246G4double 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. */
297G4double 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. */
311G4double 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. */
331G4double 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. */
347G4double 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 */
367G4double 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}
const G4int maxWarnings
@ JustWarning
void G4Exception(const char *originOfException, const char *exceptionCode, G4ExceptionSeverity severity, const char *description)
std::ostringstream G4ExceptionDescription
G4double G4Log(G4double x)
Definition G4Log.hh:227
double G4double
Definition G4Types.hh:83
bool G4bool
Definition G4Types.hh:86
int G4int
Definition G4Types.hh:85
#define G4endl
Definition G4ios.hh:67
G4GLOB_DLL std::ostream G4cout
static G4int GetNumberOfElectrons(G4int Z, G4int SubshellNb)
static G4double GetBindingEnergy(G4int Z, G4int SubshellNb)
static G4int GetNumberOfShells(G4int Z)
G4DensityEffectCalculator(const G4Material *, G4int)
G4double ComputeDensityCorrection(G4double x)
G4int GetZasInt() const
Definition G4Element.hh:120
G4double GetMeanExcitationEnergy() const
G4double GetDensityCorrection(G4double x) const
G4double GetPlasmaEnergy() const
G4double GetTotNbOfAtomsPerVolume() const
const G4Element * GetElement(G4int iel) const
G4IonisParamMat * GetIonisation() const
G4double GetFreeElectronDensity() const
const G4double * GetVecNbOfAtomsPerVolume() const
std::size_t GetNumberOfElements() const
const G4String & GetName() const
static G4NistManager * Instance()
Definition G4Pow.hh:49
static G4Pow * GetInstance()
Definition G4Pow.cc:41
G4double powZ(G4int Z, G4double y) const
Definition G4Pow.hh:225
G4double powN(G4double x, G4int n) const
Definition G4Pow.cc:162