|
Line 0
Link Here
|
|
|
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:851859, 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 "G4ios.hh" |
| 48 |
#include "G4DensityEffectCalc.hh" |
| 49 |
#include <stdio.h> |
| 50 |
#include <stdlib.h> |
| 51 |
#include <math.h> |
| 52 |
|
| 53 |
/* Newton's method for finding roots. Adapted from G4PolynominalSolver, but |
| 54 |
* without the assumption that the input is a polynomial. Also, here we |
| 55 |
* always expect the roots to be positive, so return -1 as an error value. */ |
| 56 |
static double newton(const double start, |
| 57 |
double(*Function)(double, G4DensityEffectCalcData *), |
| 58 |
double(*Derivative)(double, G4DensityEffectCalcData *), |
| 59 |
G4DensityEffectCalcData * par) |
| 60 |
{ |
| 61 |
const int maxIter = 100; |
| 62 |
|
| 63 |
int nbad = 0, ngood = 0; |
| 64 |
|
| 65 |
double Lambda = start; |
| 66 |
|
| 67 |
while(true){ |
| 68 |
const double Value = Function(Lambda, par); |
| 69 |
const double Gradient = Derivative(Lambda, par); |
| 70 |
Lambda -= Value/Gradient; |
| 71 |
|
| 72 |
if(std::fabs((Value/Gradient)/Lambda) <= 1e-12) { |
| 73 |
ngood++; |
| 74 |
if(ngood == 2) return Lambda; |
| 75 |
} else { |
| 76 |
nbad++; |
| 77 |
if(nbad > maxIter || isnan(Value) || isinf(Value)) return -1; |
| 78 |
} |
| 79 |
} |
| 80 |
} |
| 81 |
|
| 82 |
/* Return the derivative of the equation used |
| 83 |
* to solve for the Sternheimer parameter rho. */ |
| 84 |
static double dfrho(double rho, G4DensityEffectCalcData * p) |
| 85 |
{ |
| 86 |
double ans = 0; |
| 87 |
for(int i = 0; i < p->nlev-1; i++) |
| 88 |
if(p->sternf[i] > 0) |
| 89 |
ans += p->sternf[i] * pow(p->levE[i], 2) * rho / |
| 90 |
(pow(p->levE[i] * rho, 2) + 2./3. * p->sternf[i] * pow(p->plasmaE, 2)); |
| 91 |
|
| 92 |
return ans; |
| 93 |
} |
| 94 |
|
| 95 |
/* Return the functional value for the equation used |
| 96 |
* to solve for the Sternheimer parameter rho. */ |
| 97 |
static double frho(double rho, G4DensityEffectCalcData * p) |
| 98 |
{ |
| 99 |
double ans = 0; |
| 100 |
for(int i = 0; i < p->nlev-1; i++) |
| 101 |
if(p->sternf[i] > 0) |
| 102 |
ans += p->sternf[i] * log(pow(p->levE[i]*rho, 2) + |
| 103 |
2./3. * p->sternf[i]*pow(p->plasmaE, 2)); |
| 104 |
|
| 105 |
ans *= 0.5; // pulled out of loop for efficiency |
| 106 |
|
| 107 |
if(p->sternf[p->nlev-1] > 0) |
| 108 |
ans += p->sternf[p->nlev-1] * log(p->plasmaE * sqrt(p->sternf[p->nlev-1])); |
| 109 |
|
| 110 |
ans -= log(p->meanexcite); |
| 111 |
|
| 112 |
return ans; |
| 113 |
} |
| 114 |
|
| 115 |
/* Return the derivative for the equation used to |
| 116 |
* solve for the Sternheimer parameter l, called 'L' here. */ |
| 117 |
static double dell(double L, G4DensityEffectCalcData * p) |
| 118 |
{ |
| 119 |
double ans = 0; |
| 120 |
for(int i = 0; i < p->nlev; i++) |
| 121 |
if(p->sternf[i] > 0 && (p->sternEbar[i] > 0 || L != 0)) |
| 122 |
ans += p->sternf[i]/pow(pow(p->sternEbar[i], 2) + L*L, 2); |
| 123 |
|
| 124 |
ans *= -2*L; // pulled out of the loop for efficiency |
| 125 |
|
| 126 |
return ans; |
| 127 |
} |
| 128 |
|
| 129 |
/* Return the functional value for the equation used to |
| 130 |
* solve for the Sternheimer parameter l, called 'L' here. */ |
| 131 |
static double ell(double L, G4DensityEffectCalcData * p) |
| 132 |
{ |
| 133 |
double ans = 0; |
| 134 |
for(int i = 0; i < p->nlev; i++) |
| 135 |
if(p->sternf[i] > 0 && (p->sternEbar[i] > 0 || L != 0)) |
| 136 |
ans += p->sternf[i]/(pow(p->sternEbar[i], 2) + L*L); |
| 137 |
|
| 138 |
ans -= pow(10, - 2 * p->sternx); |
| 139 |
|
| 140 |
return ans; |
| 141 |
} |
| 142 |
|
| 143 |
/** |
| 144 |
* Given the Sternheimer parameter l^2 (called 'sternL' here), and that |
| 145 |
* the l_i and adjusted energies have been found with SetupFermiDeltaCalc(), |
| 146 |
* return the value of delta. Helper function for DoFermiDeltaCalc(). |
| 147 |
*/ |
| 148 |
static double delta_once_solved(G4DensityEffectCalcData * p, |
| 149 |
const double sternL) |
| 150 |
{ |
| 151 |
double ans = 0; |
| 152 |
for(int i = 0; i < p->nlev; i++) |
| 153 |
if(p->sternf[i] > 0) |
| 154 |
ans += p->sternf[i] * |
| 155 |
log((pow(p->sternl[i], 2) + pow(sternL, 2))/pow(p->sternl[i], 2)); |
| 156 |
|
| 157 |
ans -= pow(sternL, 2)/(1 + pow(10, 2 * p->sternx)); |
| 158 |
return ans; |
| 159 |
} |
| 160 |
|
| 161 |
/** |
| 162 |
* Calculate the Sternheimer adjusted energy levels and parameters l_i given |
| 163 |
* the Sternheimer parameter rho. Helper function for SetupFermiDeltaCalc(). |
| 164 |
*/ |
| 165 |
static void calc_Ebarl(G4DensityEffectCalcData * par, |
| 166 |
const double sternrho) |
| 167 |
{ |
| 168 |
par->sternl = (double *)malloc(sizeof(double)*par->nlev); |
| 169 |
par->sternEbar = (double *)malloc(sizeof(double)*par->nlev); |
| 170 |
for(int i = 0; i < par->nlev; i++){ |
| 171 |
par->sternEbar[i] = par->levE[i] * sternrho/par->plasmaE; |
| 172 |
par->sternl[i] = i < par->nlev-1? |
| 173 |
sqrt(pow(par->sternEbar[i], 2) + 2./3. * par->sternf[i]) |
| 174 |
: sqrt(par->sternf[i]); |
| 175 |
} |
| 176 |
} |
| 177 |
|
| 178 |
/** |
| 179 |
* If the "oscillator strengths" par->sternf do not add up to 1, |
| 180 |
* normalize them so that they do. |
| 181 |
*/ |
| 182 |
static void normalize_sternf(G4DensityEffectCalcData * par) |
| 183 |
{ |
| 184 |
double sum = 0; |
| 185 |
for(int i = 0; i < par->nlev; i++) sum += par->sternf[i]; |
| 186 |
for(int i = 0; i < par->nlev; i++) par->sternf[i] /= sum; |
| 187 |
} |
| 188 |
|
| 189 |
/** |
| 190 |
* At sufficiently high energy, the density effect takes on a simple |
| 191 |
* form. Also at sufficiently high energy, we run into numerical problems |
| 192 |
* doing the full calculation. In that case, return this. |
| 193 |
*/ |
| 194 |
static double delta_limiting_case(G4DensityEffectCalcData * par, |
| 195 |
const double sternx) |
| 196 |
{ |
| 197 |
return 2 * (log(par->plasmaE/par->meanexcite) + log(10.)*sternx) - 1; |
| 198 |
} |
| 199 |
|
| 200 |
|
| 201 |
/********************/ |
| 202 |
/* Public functions */ |
| 203 |
/********************/ |
| 204 |
|
| 205 |
bool SetupFermiDeltaCalc(G4DensityEffectCalcData * par) |
| 206 |
{ |
| 207 |
normalize_sternf(par); |
| 208 |
|
| 209 |
const double sternrho = newton(1.5, frho, dfrho, par); |
| 210 |
|
| 211 |
// Negative values, and values much larger than unity are non-physical. |
| 212 |
// Values between zero and one are also suspect, but not as clearly wrong. |
| 213 |
if(sternrho <= 0 || sternrho > 100){ |
| 214 |
G4cerr << |
| 215 |
"*********************************************************************\n" |
| 216 |
"* Warning: Could not solve for Sternheimer rho. Probably you have a *\n" |
| 217 |
"* mean ionization energy which is incompatible with your *\n" |
| 218 |
"* distribution of energy levels. Falling back to parameterization. *\n" |
| 219 |
"*********************************************************************\n"; |
| 220 |
return false; |
| 221 |
} |
| 222 |
|
| 223 |
calc_Ebarl(par, sternrho); |
| 224 |
return true; |
| 225 |
} |
| 226 |
|
| 227 |
double DoFermiDeltaCalc(G4DensityEffectCalcData * par, |
| 228 |
const double sternx) |
| 229 |
{ |
| 230 |
// Above beta*gamma of 10^10, the exact treatment is within machine |
| 231 |
// precision of the limiting case, for ordinary solids, at least. The |
| 232 |
// convergence goes up as the density goes down, but even in a pretty |
| 233 |
// hard vacuum it converges by 10^20. Also, it's hard to imagine how |
| 234 |
// this energy is relevant (x = 20 -> 10^19 GeV for muons). So this |
| 235 |
// is mostly not here for physical reasons, but rather to avoid ugly |
| 236 |
// discontinuities in the return value. |
| 237 |
if(sternx > 20) return delta_limiting_case(par, sternx); |
| 238 |
|
| 239 |
par->sternx = sternx; |
| 240 |
|
| 241 |
// The derivative of the function we are solving for is strictly |
| 242 |
// negative for positive (physical) values, so if the value at |
| 243 |
// zero is less than zero, it has no solution, and there is no |
| 244 |
// density effect in the Sternheimer "exact" treatment (which is |
| 245 |
// still an approximation). |
| 246 |
if(ell(0, par) <= 0) return 0; |
| 247 |
|
| 248 |
// Increase initial guess until it converges. |
| 249 |
int startLi; |
| 250 |
for(startLi = -10; startLi < 30; startLi++){ |
| 251 |
const double sternL = newton(pow(2, startLi), &ell, &dell, par); |
| 252 |
if(sternL != -1) |
| 253 |
return delta_once_solved(par, sternL); |
| 254 |
} |
| 255 |
|
| 256 |
return -1; // Signal the caller to use the Sternheimer approximation, |
| 257 |
// because we have been unable to solve the exact form. |
| 258 |
} |