Coverage for yaptide/converter/converter/shieldhit/beam.py: 91%

110 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-07-01 12:55 +0000

1import math as m 

2from dataclasses import dataclass 

3from enum import IntEnum, unique 

4from typing import Optional, TypeVar, Type 

5 

6 

7T = TypeVar("T", bound="LabelledEnum") 

8 

9 

10class LabelledEnum(IntEnum): 

11 """Base class for enums with a label attribute""" 

12 

13 label: str 

14 

15 def __new__(cls, value, label): 

16 

17 if not isinstance(value, int): 

18 raise TypeError("Value must be an integer") 

19 obj = int.__new__(cls, value) 

20 obj._value_ = value 

21 obj.label = label 

22 return obj 

23 

24 @classmethod 

25 def from_str(cls: Type[T], value: str) -> T: 

26 """Converts a string to a LabelledEnum""" 

27 for method in cls: 

28 if method.label == value: 

29 return method 

30 

31 raise ValueError(f"{cls.__name__} has no value matching {value}") 

32 

33 

34@unique 

35class BeamSourceType(LabelledEnum): 

36 """Beam source type""" 

37 

38 SIMPLE = (0, "simple") 

39 FILE = (1, "file") 

40 

41 

42@unique 

43class ModulatorSimulationMethod(LabelledEnum): 

44 """Modulator simulation method for beam.dat file""" 

45 

46 MODULUS = (0, "modulus") 

47 SAMPLING = (1, "sampling") 

48 

49 

50@unique 

51class ModulatorInterpretationMode(LabelledEnum): 

52 """Modulator interpretation mode of data in the input files loaded with the USEBMOD card""" 

53 

54 MATERIAL = (0, "material") 

55 VACUMM = (1, "vacumm") 

56 

57 

58@unique 

59class StragglingModel(LabelledEnum): 

60 """Straggle model""" 

61 

62 GAUSSIAN = (1, "Gaussian") 

63 VAVILOV = (2, "Vavilov") 

64 NO_STRAGGLING = (0, "no straggling") 

65 

66 

67@unique 

68class MultipleScatteringMode(LabelledEnum): 

69 """Multiple scattering mode""" 

70 

71 GAUSSIAN = (1, "Gaussian") 

72 MOLIERE = (2, "Moliere") 

73 NO_SCATTERING = (0, "no scattering") 

74 

75 

76@dataclass(frozen=True) 

77class BeamModulator(): 

78 """Beam modulator card dataclass used in BeamConfig.""" 

79 

80 filename: str 

81 file_content: str 

82 zone_id: int 

83 simulation: ModulatorSimulationMethod = ModulatorSimulationMethod.MODULUS 

84 mode: ModulatorInterpretationMode = ModulatorInterpretationMode.MATERIAL 

85 

86 def __str__(self) -> str: 

87 """Returns the string representation of the beam modulator card""" 

88 modulator_template = """USEBMOD {zone} {filename} ! Zone# and file name for beam modulator 

89BMODMC {simulation} ! Simulation method for beam modulator (0-Modulus, 1-Monte Carlo sampling) 

90BMODTRANS {mode} ! Interpretation of thicknesses data in the config file (0-Material, 1-Vacuum)""" 

91 return modulator_template.format( 

92 zone=self.zone_id, 

93 filename=self.filename, 

94 simulation=self.simulation, 

95 mode=self.mode) 

96 

97 

98@dataclass 

99class BeamConfig: 

100 """Class mapping of the beam.dat config file.""" 

101 

102 particle: int = 2 

103 particle_name: Optional[str] = None 

104 heavy_ion_a: int = 1 

105 heavy_ion_z: int = 1 

106 energy: float = 150. # [MeV] 

107 energy_spread: float = 1.5 # [MeV] 

108 energy_low_cutoff: Optional[float] = None # [MeV] 

109 energy_high_cutoff: Optional[float] = None # [MeV] 

110 beam_ext_x: float = -0.1 # [cm] 

111 beam_ext_y: float = 0.1 # [cm] 

112 sad_x: Optional[float] = None # [cm] 

113 sad_y: Optional[float] = None # [cm] 

114 n_stat: int = 10000 

115 beam_pos: tuple[float, float, float] = (0, 0, 0) # [cm] 

116 beam_dir: tuple[float, float, float] = (0, 0, 1) # [cm] 

117 delta_e: float = 0.03 # [a.u.] 

118 nuclear_reactions: bool = True 

119 

120 modulator: Optional[BeamModulator] = None 

121 

122 straggling: StragglingModel = StragglingModel.VAVILOV 

123 multiple_scattering: MultipleScatteringMode = MultipleScatteringMode.MOLIERE 

124 

125 heavy_ion_template = "HIPROJ {a} {z} ! A and Z of the heavy ion" 

126 energy_cutoff_template = "TCUT0 {energy_low_cutoff} {energy_high_cutoff} ! energy cutoffs [MeV]" 

127 sad_template = "BEAMSAD {sad_x} {sad_y} ! BEAMSAD value [cm]" 

128 beam_source_type: BeamSourceType = BeamSourceType.SIMPLE 

129 beam_source_filename: Optional[str] = None 

130 beam_source_file_content: Optional[str] = None 

131 

132 beam_dat_template: str = """ 

133RNDSEED 89736501 ! Random seed 

134JPART0 {particle} ! Incident particle type{particle_optional_comment} 

135{optional_heavy_ion_line} 

136TMAX0 {energy} {energy_spread} ! Incident energy and energy spread; both in (MeV/nucl) 

137{optional_energy_cut_off_line} 

138NSTAT {n_stat:d} 0 ! NSTAT, Step of saving 

139STRAGG {straggling} ! Straggling: 0-Off 1-Gauss, 2-Vavilov 

140MSCAT {multiple_scattering} ! Mult. scatt 0-Off 1-Gauss, 2-Moliere 

141NUCRE {nuclear_reactions} ! Nucl.Reac. switcher: 1-ON, 0-OFF 

142{optional_beam_modulator_lines} 

143BEAMPOS {pos_x} {pos_y} {pos_z} ! Position of the beam 

144BEAMDIR {theta} {phi} ! Direction of the beam 

145BEAMSIGMA {beam_ext_x} {beam_ext_y} ! Beam extension 

146{optional_sad_parameter_line} 

147DELTAE {delta_e} ! relative mean energy loss per transportation step 

148""" 

149 

150 @staticmethod 

151 def cartesian2spherical(vector: tuple[float, float, float]) -> tuple[float, float, float]: 

152 """ 

153 Transform cartesian coordinates to spherical coordinates. 

154 

155 :param vector: cartesian coordinates 

156 :return: spherical coordinates 

157 """ 

158 x, y, z = vector 

159 r = m.sqrt(x**2 + y**2 + z**2) 

160 # acos returns the angle in radians between 0 and pi 

161 theta = m.degrees(m.acos(z / r)) 

162 # atan2 returns the angle in radians between -pi and pi 

163 phi = m.degrees(m.atan2(y, x)) 

164 # lets ensure the angle in degrees is always between 0 and 360, as SHIELD-HIT12A requires 

165 if phi < 0.: 

166 phi += 360. 

167 return theta, phi, r 

168 

169 def __str__(self) -> str: 

170 """Return the beam.dat config file as a string.""" 

171 theta, phi, _ = BeamConfig.cartesian2spherical(self.beam_dir) 

172 

173 # if particle name is defined, add the comment to the template 

174 particle_optional_comment = "" 

175 if self.particle_name: 

176 particle_optional_comment = f" ({self.particle_name})" 

177 

178 # if particle is heavy ion, add the heavy ion line to the template 

179 heavy_ion_line = "! no heavy ion" 

180 if self.particle == 25: 

181 heavy_ion_line = BeamConfig.heavy_ion_template.format(a=self.heavy_ion_a, z=self.heavy_ion_z) 

182 

183 # if energy cutoffs are defined, add them to the template 

184 cutoff_line = "! no energy cutoffs" 

185 if self.energy_low_cutoff is not None and self.energy_high_cutoff is not None: 

186 cutoff_line = BeamConfig.energy_cutoff_template.format( 

187 energy_low_cutoff=self.energy_low_cutoff, 

188 energy_high_cutoff=self.energy_high_cutoff 

189 ) 

190 

191 # if sad was defined, add it to the template 

192 sad_line = "! no BEAMSAD value" 

193 if self.sad_x is not None or self.sad_y is not None: 

194 sad_y_value = self.sad_y if self.sad_y is not None else "" 

195 sad_line = BeamConfig.sad_template.format( 

196 sad_x=self.sad_x, 

197 sad_y=sad_y_value) 

198 

199 # if beam modulator was defined, add it to the template 

200 mod_lines = str(self.modulator) if self.modulator is not None else '! no beam modulator' 

201 

202 # prepare main template 

203 result = self.beam_dat_template.format( 

204 particle=self.particle, 

205 particle_optional_comment=particle_optional_comment, 

206 optional_heavy_ion_line=heavy_ion_line, 

207 energy=float(self.energy), 

208 energy_spread=float(self.energy_spread), 

209 optional_energy_cut_off_line=cutoff_line, 

210 optional_sad_parameter_line=sad_line, 

211 optional_beam_modulator_lines=mod_lines, 

212 n_stat=self.n_stat, 

213 pos_x=self.beam_pos[0], 

214 pos_y=self.beam_pos[1], 

215 pos_z=self.beam_pos[2], 

216 beam_ext_x=self.beam_ext_x, 

217 beam_ext_y=self.beam_ext_y, 

218 theta=theta, 

219 phi=phi, 

220 delta_e=self.delta_e, 

221 nuclear_reactions=1 if self.nuclear_reactions else 0, 

222 straggling=self.straggling.value, 

223 multiple_scattering=self.multiple_scattering.value 

224 ) 

225 

226 # if beam source type is file, add the file name to the template 

227 if self.beam_source_type == BeamSourceType.FILE: 

228 result += "USECBEAM sobp.dat ! Use custom beam source file" 

229 

230 return result