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
« 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
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
19@dataclass
20class FlukaMaterial:
21 """Class representing a material in Fluka input"""
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
32@dataclass
33class FlukaCompound:
34 """Class representing a compound in Fluka input."""
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)
44@dataclass
45class FlukaLowMat:
46 """
47 Class representing correspondence between Fluka materials and
48 low-energy neutron cross sections in Fluka input.
49 """
51 material_name: str = ""
52 low_energy_neutron_material: str = ""
55@dataclass
56class MaterialAssignment:
57 """Class representing an assignment of a material to a region in Fluka input."""
59 material_name: str = ""
60 region_name: str = ""
63@dataclass
64class IonisationPotential:
65 """Class representing an ionisation potential in Fluka input."""
67 material_name: str = ""
68 ionisation_potential: float = 0
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
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"])
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 ))
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.")
180 return materials, compounds, lowmats
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 ))
208 return assignments
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 = []
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