mirror of
https://github.com/gwm17/spspy.git
synced 2024-09-21 15:07:25 -04:00
214 lines
7.0 KiB
Python
Executable File
214 lines
7.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import numpy as np
|
|
import EnergyLossData as edata
|
|
from NucData import Masses
|
|
|
|
class EnergyLoss:
|
|
MAX_FRACTIONAL_STEP=0.001
|
|
MAX_H_E_PER_U=100000.0
|
|
AVOGADRO=0.60221367
|
|
MEV2U=1.0/931.4940954
|
|
def __init__(self):
|
|
self.ZP = 0
|
|
self.AP = 0
|
|
self.MP = 0
|
|
self.ZT = np.zeros(0)
|
|
self.AT = np.zeros(0)
|
|
self.Stoich = np.zeros(0)
|
|
self.illegalFlag = True
|
|
|
|
def SetTargetData(self, zt, at, stoich):
|
|
self.ZT = zt
|
|
self.AT = at
|
|
total = np.sum(stoich)
|
|
self.Stoich = stoich/total
|
|
for z in self.ZT:
|
|
if z >= edata.MaxZ:
|
|
self.illegalFlag = True
|
|
return
|
|
self.illegalFlag = False
|
|
|
|
def GetEnergyLoss(self, zp, ap, e_initial, thickness):
|
|
if self.illegalFlag:
|
|
print("Unable to get energy loss with unset target data... returning 0")
|
|
return 0.0
|
|
|
|
if self.ZP != zp:
|
|
self.ZP = zp
|
|
self.AP = ap
|
|
self.MP = Masses.GetMass(self.ZP, self.AP)*self.MEV2U
|
|
|
|
e_final = e_initial
|
|
x_traversed = 0
|
|
x_step = 0.25*thickness
|
|
e_step = self.GetTotalStoppingPower(e_final)*x_step/1000.0
|
|
|
|
if thickness == 0.0:
|
|
return 0.0
|
|
go = True
|
|
while go:
|
|
if e_step/e_final > self.MAX_FRACTIONAL_STEP:
|
|
x_step *= 0.5
|
|
e_step = self.GetTotalStoppingPower(e_final)*x_step/1000.0
|
|
elif x_step+x_traversed >= thickness:
|
|
go = False
|
|
x_step = thickness - x_traversed #get valid portion of last chunk
|
|
e_final -= self.GetTotalStoppingPower(e_final)*x_step/1000.0
|
|
if e_final <= 0.0:
|
|
return e_initial
|
|
else:
|
|
x_traversed += x_step
|
|
e_step = self.GetTotalStoppingPower(e_final)*x_step/1000.0
|
|
e_final -= e_step
|
|
if e_final <= 0.0:
|
|
return e_initial
|
|
return e_initial - e_final
|
|
|
|
def GetReverseEnergyLoss(self, zp, ap, e_final, thickness):
|
|
if self.illegalFlag:
|
|
print("Unable to get energy loss with unset target data... returning 0")
|
|
return 0.0
|
|
|
|
if self.ZP != zp:
|
|
self.ZP = zp
|
|
self.AP = ap
|
|
self.MP = Masses.GetMass(self.ZP, self.AP)*self.MEV2U
|
|
|
|
e_initial = e_final
|
|
x_traversed = 0
|
|
x_step = 0.25*thickness
|
|
e_step = self.GetTotalStoppingPower(e_initial)*x_step/1000.0
|
|
|
|
if thickness == 0.0:
|
|
return 0.0
|
|
go = True
|
|
while go:
|
|
if e_step/e_final > self.MAX_FRACTIONAL_STEP:
|
|
x_step *= 0.5
|
|
e_step = self.GetTotalStoppingPower(e_initial)*x_step/1000.0
|
|
elif x_step+x_traversed >= thickness:
|
|
go = False
|
|
x_step = thickness - x_traversed #get valid portion of last chunk
|
|
e_initial += self.GetTotalStoppingPower(e_initial)*x_step/1000.0
|
|
else:
|
|
x_traversed += x_step
|
|
e_step = self.GetTotalStoppingPower(e_initial)*x_step/1000.0
|
|
e_final += e_step
|
|
if e_final <= 0.0:
|
|
return e_initial
|
|
return abs(e_initial - e_final)
|
|
|
|
def GetTotalStoppingPower(self, energy):
|
|
return self.GetElectronicStoppingPower(energy)+self.GetNuclearStoppingPower(energy)
|
|
|
|
def GetElectronicStoppingPower(self, energy):
|
|
e_per_u = energy*1000.0/self.MP
|
|
values = np.zeros(len(self.ZT))
|
|
if e_per_u > self.MAX_H_E_PER_U:
|
|
print("Bombarding energy exceeds maximum allowed value for energy loss! Returning 0")
|
|
return 0.0
|
|
elif e_per_u > 1000.0:
|
|
for i in range(len(self.ZT)):
|
|
values[i] = self.Hydrogen_dEdx_High(e_per_u, energy, self.ZT[i])
|
|
elif e_per_u > 10.0:
|
|
for i in range(len(self.ZT)):
|
|
values[i] = self.Hydrogen_dEdx_Med(e_per_u, self.ZT[i])
|
|
elif e_per_u > 0.0:
|
|
for i in range(len(self.ZT)):
|
|
values[i] = self.Hydrogen_dEdx_Low(e_per_u, self.ZT[i])
|
|
else:
|
|
print("Negative energy encountered at EnergyLoss::GetElectronicStoppingPower! Returning 0")
|
|
return 0.0
|
|
|
|
if self.ZP > 1:
|
|
for i in range(len(values)):
|
|
values[i] *= self.CalculateEffectiveChargeRatio(e_per_u, self.ZT[i])
|
|
|
|
stopping_total = np.sum(values*self.Stoich)
|
|
conv_factor = 0.0
|
|
for i in range(len(self.ZT)):
|
|
conv_factor += self.Stoich[i]*edata.NaturalMass[self.ZT[i]]
|
|
stopping_total *= self.AVOGADRO/conv_factor
|
|
return stopping_total
|
|
|
|
def GetNuclearStoppingPower(self, energy):
|
|
e = energy*1000.0
|
|
stopping_total = 0.0
|
|
for i in range(len(self.ZT)):
|
|
zt = self.ZT[i]
|
|
mt = edata.NaturalMass[self.ZT[i]]
|
|
x = (self.MP + mt) * np.sqrt(self.ZP**(2.0/3.0) + zt**(2.0/3.0))
|
|
epsilon = 32.53*mt*e/(self.ZP*zt*x)
|
|
sn = 8.462*(0.5*np.log(1.0+epsilon)/(epsilon+0.10718*(epsilon**0.37544)))*self.ZP*zt*self.MP/x
|
|
conversion_factor = self.AVOGADRO/mt
|
|
stopping_total += sn*conversion_factor*self.Stoich[i]
|
|
return stopping_total
|
|
|
|
def Hydrogen_dEdx_Low(self, e_per_u, zt):
|
|
return np.sqrt(e_per_u)*edata.HydrogenCoeff[zt][0]
|
|
|
|
def Hydrogen_dEdx_Med(self, e_per_u, zt):
|
|
x = edata.HydrogenCoeff[zt][1]*e_per_u**0.45
|
|
y = edata.HydrogenCoeff[zt][2]/e_per_u * np.log(1.0 + edata.HydrogenCoeff[zt][3]/e_per_u + edata.HydrogenCoeff[zt][4]*e_per_u)
|
|
return x*y/(x+y)
|
|
|
|
def Hydrogen_dEdx_High(self, e_per_u, energy, zt):
|
|
beta_sq = energy * (energy + 2.0*self.MP/self.MEV2U)/((energy + self.MP/self.MEV2U)**2.0)
|
|
alpha = edata.HydrogenCoeff[zt][5]/beta_sq
|
|
epsilon = edata.HydrogenCoeff[zt][6]*beta_sq/(1.0 - beta_sq) - beta_sq - edata.HydrogenCoeff[zt][7]
|
|
for i in range(1,5):
|
|
epsilon += edata.HydrogenCoeff[zt][7+i]*(np.log(e_per_u))**float(i)
|
|
return alpha * np.log(epsilon)
|
|
|
|
def CalculateEffectiveChargeRatio(self, e_per_u, zt):
|
|
z_ratio=0
|
|
if self.ZP == 2:
|
|
ln_epu = np.log(e_per_u)
|
|
gamma = 1.0+(0.007+0.00005*zt)*np.exp(-1.0*(7.6-ln_epu)**2.0)
|
|
alpha = 0.7446 + 0.1429*ln_epu + 0.01562*ln_epu**2.0 - 0.00267*ln_epu**3.0 + 1.338e-6*ln_epu**8.0
|
|
z_ratio = gamma*(1.0-np.exp(-alpha))*2.0
|
|
elif self.ZP == 3:
|
|
ln_epu = np.log(e_per_u)
|
|
gamma = 1.0+(0.007+0.00005*zt)*np.exp(-1.0*(7.6-ln_epu)**2.0)
|
|
alpha = 0.7138+0.002797*e_per_u+1.348e-6*e_per_u**2.0
|
|
z_ratio = gamma*(1-np.exp(-alpha))*3.0
|
|
else:
|
|
B = 0.886*(e_per_u/25.0)**0.5/(self.ZP**(2.0/3.0))
|
|
A = B + 0.0378*np.sin(np.pi/2.0*B)
|
|
z_ratio = (1.0 - np.exp(-A)*(1.034-0.1777*np.exp(-0.08114*self.ZP)))*self.ZP
|
|
return z_ratio*z_ratio
|
|
|
|
|
|
def main():
|
|
targetA = np.array([12])
|
|
targetZ = np.array([6])
|
|
targetS = np.array([1])
|
|
beamKE = 16.0
|
|
thickness = 20.0
|
|
|
|
eloss = EnergyLoss()
|
|
eloss.SetTargetData(targetZ, targetA, targetS)
|
|
print("Testing various cases for energy loss. Using 12C target with 20 ug/cm^2 thickness. Compare to values given by LISE++ or SRIM")
|
|
result = eloss.GetEnergyLoss(1, 1, beamKE, 20.0)
|
|
print("Case 1: ZP = 1, AP=1, Beam energy = 16 MeV -> Resulting energy loss = ", result, " MeV")
|
|
beamKE = 1.0
|
|
result = eloss.GetEnergyLoss(1, 1, beamKE, 20.0)
|
|
print("Case 2: ZP = 1, AP=1, Beam energy = 1.0 MeV -> Resulting energy loss = ", result, " MeV")
|
|
beamKE = 0.1
|
|
result = eloss.GetEnergyLoss(1, 1, beamKE, 20.0)
|
|
print("Case 3: ZP = 1, AP=1, Beam energy = 0.1 MeV -> Resulting energy loss = ", result, " MeV")
|
|
beamKE = 0.01
|
|
result = eloss.GetEnergyLoss(1, 1, beamKE, 20.0)
|
|
print("Case 4: ZP = 1, AP=1, Beam energy = 0.01 MeV -> Resulting energy loss = ", result, " MeV")
|
|
beamKE = 24.0
|
|
result = eloss.GetEnergyLoss(2, 4, beamKE, 20.0)
|
|
print("Case 5: ZP = 2, AP=4, Beam energy = 24.0 MeV -> Resulting energy loss = ", result, " MeV")
|
|
beamKE = 24.0
|
|
result = eloss.GetEnergyLoss(3, 6, beamKE, 20.0)
|
|
print("Case 6: ZP = 3, AP=6, Beam energy = 24 MeV -> Resulting energy loss = ", result, " MeV")
|
|
print("Finished.")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|