Coverage for yaptide/converter/converter/fluka/helper_parsers/material_parser.py: 77%

102 statements  

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

1from dataclasses import dataclass, field 

2from converter.fluka.predefined_materials import ( 

3 PREDEFINED_MATERIALS, 

4 PREDEFINED_COMPOUNDS, 

5 FLUKA_NAMES, 

6) 

7from converter.fluka.helper_parsers.region_parser import FlukaRegion 

8from copy import deepcopy 

9 

10BLACK_HOLE_ICRU = 0 

11VACUUM_ICRU = 1000 

12# Single elements that are sent from the UI 

13# have compatible icru with the atomic number 

14# only up to a certain number, which is 

15# SINGLE_ELEMENT_MAX_ICRU 

16SINGLE_ELEMENT_MAX_ICRU = 98 

17 

18 

19@dataclass 

20class FlukaMaterial: 

21 """Class representing a material in Fluka input""" 

22 

23 fluka_name: str = "" 

24 fluka_number: int = 0 # Not used 

25 common_name: str = "" # Not used 

26 A: float = 0 # Not used 

27 Z: int = 0 

28 density: float = 0 

29 icru: int = 0 

30 

31 

32@dataclass 

33class FlukaCompound: 

34 """Class representing a compound in Fluka input.""" 

35 

36 fluka_name: str = "" 

37 common_name: str = "" # Not used 

38 density: float = 0 

39 icru: int = 0 

40 # composition is a list of tuples (weight fraction, fluka name) 

41 composition: list[tuple[float, str]] = field(default_factory=list) 

42 

43 

44@dataclass 

45class FlukaLowMat: 

46 """ 

47 Class representing correspondence between Fluka materials and 

48 low-energy neutron cross sections in Fluka input. 

49 """ 

50 

51 material_name: str = "" 

52 low_energy_neutron_material: str = "" 

53 

54 

55@dataclass 

56class MaterialAssignment: 

57 """Class representing an assignment of a material to a region in Fluka input.""" 

58 

59 material_name: str = "" 

60 region_name: str = "" 

61 

62 

63@dataclass 

64class IonisationPotential: 

65 """Class representing an ionisation potential in Fluka input.""" 

66 

67 material_name: str = "" 

68 ionisation_potential: float = 0 

69 

70 

71def load_predefined_materials() -> ( 

72 dict[str, FlukaMaterial], 

73 dict[str, FlukaCompound], 

74 dict[str, str], 

75): 

76 """ 

77 Convert list of dicts of predefined materials and compounds to lists of 

78 FlukaMaterial and FlukaCompound objects. Also create a dict of icru -> fluka_name. 

79 """ 

80 predefined_materials = {material["icru"]: FlukaMaterial(**material) for material in PREDEFINED_MATERIALS} 

81 predefined_compounds = {compound["icru"]: FlukaCompound(**compound) for compound in PREDEFINED_COMPOUNDS} 

82 icru_to_fluka_name = {material["icru"]: material["fluka_name"] for material in FLUKA_NAMES} 

83 return predefined_materials, predefined_compounds, icru_to_fluka_name 

84 

85 

86def parse_materials(materials_json, zones_json: dict) -> (dict[str, FlukaMaterial], dict[str, FlukaCompound]): 

87 """Parse materials from json to FlukaMaterial and FlukaCompound objects.""" 

88 ( 

89 predefined_materials, 

90 predefined_compounds, 

91 icru_to_fluka_name, 

92 ) = load_predefined_materials() 

93 material_index = compound_index = 1 

94 # uuid -> material, uuid -> compound 

95 materials, compounds = {}, {} 

96 lowmats = [] 

97 zones = deepcopy(zones_json["zones"]) 

98 if "worldZone" in zones_json: 

99 zones.append(zones_json["worldZone"]) 

100 

101 materials_list = [] 

102 for material in materials_json: 

103 materials_list.append((material["uuid"], material["icru"], material["density"])) 

104 for zone in zones: 

105 if ("customMaterial" in zone and "density" in zone["materialPropertiesOverrides"]): 

106 materials_list.append(( 

107 zone["customMaterial"]["uuid"], 

108 zone["customMaterial"]["icru"], 

109 zone["materialPropertiesOverrides"]["density"], 

110 )) 

111 

112 for uuid, icru, density in materials_list: 

113 # Check if material is black hole or vacuum, we can't redefine them, 

114 # however they are defined in predefined materials 

115 if icru in [BLACK_HOLE_ICRU, VACUUM_ICRU]: 

116 materials[uuid] = predefined_materials[icru] 

117 # Check if material is already predefined, if density is different 

118 # define new material 

119 elif icru in predefined_materials: 

120 predefined_material = predefined_materials[icru] 

121 if density != predefined_material.icru: 

122 new_material = FlukaMaterial( 

123 fluka_name=f"MAT{material_index:0>5}", 

124 Z=icru, 

125 density=density, 

126 icru=icru, 

127 ) 

128 new_lowmat = FlukaLowMat( 

129 material_name=new_material.fluka_name, 

130 low_energy_neutron_material=icru_to_fluka_name[icru], 

131 ) 

132 lowmats.append(new_lowmat) 

133 material_index += 1 

134 materials[uuid] = new_material 

135 else: 

136 materials[uuid] = predefined_material 

137 # Check if material is not predefined single-element 

138 elif icru in icru_to_fluka_name: 

139 new_material = FlukaMaterial( 

140 fluka_name=f"MAT{material_index:0>5}", 

141 Z=icru, 

142 density=density, 

143 icru=icru, 

144 ) 

145 new_lowmat = FlukaLowMat( 

146 material_name=new_material.fluka_name, 

147 low_energy_neutron_material=icru_to_fluka_name[icru], 

148 ) 

149 lowmats.append(new_lowmat) 

150 material_index += 1 

151 materials[uuid] = new_material 

152 # Check if material is predefined compound, if density is different 

153 # define new compound 

154 elif icru in predefined_compounds: 

155 predefined_compound = predefined_compounds[icru] 

156 if density != predefined_compound.density: 

157 # To define new compound it is necessary to define new material 

158 new_material = FlukaMaterial( 

159 fluka_name=f"COM{compound_index:0>5}", 

160 Z=0, 

161 density=density, 

162 icru=icru, 

163 ) 

164 new_compound = FlukaCompound( 

165 fluka_name=f"COM{compound_index:0>5}", 

166 density=density, 

167 composition=[(-1.0, predefined_compound.fluka_name)], 

168 ) 

169 compound_index += 1 

170 materials[uuid] = new_material 

171 compounds[uuid] = new_compound 

172 else: 

173 materials[uuid] = predefined_compound 

174 # If icru is not used anywhere above, it means we are dealing 

175 # with not predefined compound or single-element material 

176 # with undefined cross section which is not supported for now 

177 else: 

178 raise NotImplementedError(f"Material with icru {icru} is not supported.") 

179 

180 return materials, compounds, lowmats 

181 

182 

183def assign_materials_to_regions( 

184 materials: dict[str, FlukaMaterial], 

185 regions: dict[str, FlukaRegion], 

186 zones_json: dict, 

187) -> list[MaterialAssignment]: 

188 """Assign materials to regions based on uuids in json.""" 

189 assignments = [] 

190 for zone in zones_json["zones"]: 

191 assignments.append( 

192 MaterialAssignment( 

193 material_name=materials[zone["materialUuid"]].fluka_name, 

194 region_name=regions[zone["uuid"]].name, 

195 )) 

196 if "worldZone" in zones_json: 

197 assignments.append( 

198 MaterialAssignment( 

199 material_name=materials[zones_json["worldZone"]["materialUuid"]].fluka_name, 

200 region_name=regions[zones_json["worldZone"]["uuid"]].name, 

201 )) 

202 assignments.append( 

203 MaterialAssignment( 

204 material_name="BLCKHOLE", 

205 region_name=regions[zones_json["worldZone"]["uuid"] + "boundary"].name, 

206 )) 

207 

208 return assignments 

209 

210 

211def set_custom_ionisation_potential(materials: dict[str, FlukaMaterial], zones_json: dict, 

212 materials_json: list[dict]) -> list[IonisationPotential]: 

213 """Set custom ionisation potential for materials that have it defined.""" 

214 ionisation_potentials = [] 

215 

216 for zone in zones_json["zones"]: 

217 if "averageIonisationPotential" in zone.get("materialPropertiesOverrides", []): 

218 ionisation_potentials.append( 

219 IonisationPotential( 

220 material_name=materials[zone["materialUuid"]].fluka_name, 

221 ionisation_potential=zone["materialPropertiesOverrides"]["averageIonisationPotential"], 

222 )) 

223 if "worldZone" in zones_json and "averageIonisationPotential" in zones_json["worldZone"].get( 

224 "materialPropertiesOverrides", []): 

225 ionisation_potentials.append( 

226 IonisationPotential( 

227 material_name=materials[zones_json["worldZone"]["materialUuid"]].fluka_name, 

228 ionisation_potential=zones_json["worldZone"]["materialPropertiesOverrides"] 

229 ["averageIonisationPotential"], 

230 )) 

231 for material in materials_json: 

232 if "averageIonisationPotential" in material: 

233 ionisation_potentials.append( 

234 IonisationPotential( 

235 material_name=materials[material["uuid"]].fluka_name, 

236 ionisation_potential=material["averageIonisationPotential"], 

237 )) 

238 return ionisation_potentials