1
0
Fork 0
mirror of https://github.com/gwm17/spspy.git synced 2025-04-22 07:08:50 -04:00
spspy/spspy/SPSTarget.py

171 lines
7.1 KiB
Python

from .data.NuclearData import global_nuclear_data
import pycatima as catima
from dataclasses import dataclass, field
from numpy import pi, cos
INVALID_RXN_LAYER: int = -1
ADAPTIVE_DEPTH_MAX: int = 100
ENERGY_PERCENT_STEP_MIN: float = 0.001
@dataclass
class TargetLayer:
compound_list: list[tuple[int, int , int]] = field(default_factory=list) #Z,A,S
thickness: float = 0.0 #ug/cm^2
def __str__(self) -> str:
return "".join([f"{global_nuclear_data.get_data(z, a,).prettyIsotopicSymbol}<sub>{s}</sub>" for z, a, s in self.compound_list])
#integrate energy loss starting from the final energy and running backwards to initial energy
#catima does not natively provide this type of method
#returns the total energy loss (really in this case energy gain) through the material
def get_reverse_energyloss(projectile: catima.Projectile, material: catima.Material) -> float:
depth = 0
e_out = projectile.T() #MeV/u
e_initial = e_out
x_step = 0.25*material.thickness() #g/cm^2
x_traversed = 0.0
e_step = catima.dedx(projectile, material)*x_step
A_recip = 1.0/projectile.A()
if material.thickness() <= 0.0:
return 0.0
#The integration step is adaptive, so use while(true)
while(True):
if e_step/e_initial > ENERGY_PERCENT_STEP_MIN and depth < ADAPTIVE_DEPTH_MAX:
depth += 1
x_step *= 0.5
e_step = catima.dedx(projectile, material)*x_step*A_recip
elif (x_step + x_traversed) >= material.thickness():
x_step = material.thickness() - x_traversed
e_step = catima.dedx(projectile, material)*x_step
e_initial += e_step*A_recip
projectile.T(e_initial)
return (e_initial - e_out)*projectile.A()
elif depth == ADAPTIVE_DEPTH_MAX:
return e_out*projectile.A()
else:
e_step = catima.dedx(projectile, material)*x_step
e_initial += e_step*A_recip
projectile.T(e_initial)
x_traversed += x_step
#integrate energy loss starting from the initial energy to final energy
#catima does not natively provide this type of method, only a calculate function which does a whole bunch of other stuff too
#returns the total energy loss through the material
def get_energyloss(projectile: catima.Projectile, material: catima.Material) -> float:
depth = 0
e_in = projectile.T() # MeV/u
e_final = e_in
x_step = 0.25*material.thickness() #g/cm^2
x_traversed = 0.0
e_step = catima.dedx(projectile, material)*x_step
A_recip = 1.0/projectile.A()
if material.thickness() <= 0.0:
return 0.0
while(True):
if e_step/e_final > ENERGY_PERCENT_STEP_MIN and depth < ADAPTIVE_DEPTH_MAX:
depth += 1
x_step *= 0.5
e_step = catima.dedx(projectile, material)*x_step*A_recip
elif (x_step + x_traversed) >= material.thickness():
x_step = material.thickness() - x_traversed
e_step = catima.dedx(projectile, material)*x_step*A_recip
e_final -= e_step
projectile.T(e_final)
return (e_in - e_final)*projectile.A()
elif depth == ADAPTIVE_DEPTH_MAX:
return e_in*projectile.A()
else:
e_step = catima.dedx(projectile, material)*x_step*A_recip
e_final -= e_step
projectile.T(e_final)
x_traversed += x_step
class SPSTarget:
UG2G: float = 1.0e-6 #convert ug to g
def __init__(self, layers: list[TargetLayer], name: str = "default"):
self.layer_details = layers
self.name = name
def __str__(self):
return self.name
def get_rxn_layer(self, zt: int, at: int) -> int:
for idx, layer in enumerate(self.layer_details):
for (z, a, s) in layer.compound_list:
if at == a and zt == z:
return idx
return INVALID_RXN_LAYER
#Calculate energy loss for a particle coming into the target, up to rxn layer (halfway through rxn layer)
def get_incoming_energyloss(self, zp: int, ap: float, e_initial: float, rxn_layer: int, angle: float) -> float:
if angle == pi*0.5:
return e_initial
projectile = catima.Projectile(ap, zp)
e_current = e_initial/ap
for (idx, layer) in enumerate(self.layer_details):
material = catima.Material([(global_nuclear_data.get_data(z, a).mass, z, float(s)) for (z, a, s) in layer.compound_list])
projectile.T(e_current) #catima wants MeV/u
if idx == rxn_layer:
material.thickness(self.layer_details[idx].thickness * self.UG2G / (2.0 * abs(cos(angle))))
e_current -= get_energyloss(projectile, material)
return e_initial - e_current*ap
else:
material.thickness(self.layer_details[idx].thickness * self.UG2G / abs(cos(angle)))
e_current -= get_energyloss(projectile, material)
return e_initial - e_current*ap
#Calculate energy loss for a particle leaving the target, from rxn layer (halfway through rxn layer) to end
def get_outgoing_energyloss(self, zp: int, ap: float, e_initial: float, rxn_layer: int, angle: float) -> float:
if angle == pi*0.5:
return e_initial
projectile = catima.Projectile(ap, zp)
e_current = e_initial/ap
for (idx, layer) in enumerate(self.layer_details[rxn_layer:], start=rxn_layer):
material = catima.Material([(global_nuclear_data.get_data(z, a).mass, z, float(s)) for (z, a, s) in layer.compound_list])
projectile.T(e_current) #catima wants MeV/u
if idx == rxn_layer:
material.thickness(self.layer_details[idx].thickness * self.UG2G / (2.0 * abs(cos(angle))))
else:
material.thickness(self.layer_details[idx].thickness * self.UG2G / abs(cos(angle)))
e_current -= get_energyloss(projectile, material)
return e_initial - e_current*ap
#Calculate reverse energy loss (energy gain) for a particle that left the target after a reaction (end -> rxn_layer)
def get_outgoing_reverse_energyloss(self, zp: int, ap: float, e_final: float, rxn_layer: int, angle: float) -> float:
if angle == pi*0.5:
return 0.0
projectile = catima.Projectile(ap, zp)
e_current = e_final/ap
sublist = self.layer_details[rxn_layer:] #only care about rxn_layer -> exit
reveresedRxnLayer = len(sublist) -1 #when reversed rxn_layer is the last layer
for (idx, layer) in reversed(list(enumerate(sublist))):
material = catima.Material([(global_nuclear_data.get_data(z, a).mass, z, float(s)) for (z, a, s) in layer.compound_list])
projectile.T(e_current) #catima wants MeV/u
if idx == reveresedRxnLayer:
material.thickness(self.layer_details[idx].thickness * self.UG2G / (2.0 * abs(cos(angle))))
else:
material.thickness(self.layer_details[idx].thickness * self.UG2G / abs(cos(angle)))
e_current += get_reverse_energyloss(projectile, material)
return e_current*ap - e_final