forked from jchelly/SOAP
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathparameter_file.py
291 lines (253 loc) · 11.3 KB
/
parameter_file.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#!/bin/env python
"""
parameter_file.py
Support for parameter files.
The parameter file object keeps track of the parameters that are requested,
and can output this information in the form of a ".used_parameters" file,
similar to the file produced by SWIFT.
"""
from typing import Dict, Union, List, Tuple
import yaml
import property_table
class ParameterFile:
"""
Internal representation of the parameter file.
Acts as a meaningful wrapper around the bare parameter dictionary.
"""
# parameter dictionary
parameters: Dict
def __init__(
self,
file_name: Union[None, str] = None,
parameter_dictionary: Union[None, Dict] = None,
snipshot: bool = False,
):
"""
Constructor.
Parameters:
- file_name: str or None
Name of the parameter file. If None, a parameter dictionary should be
provided instead.
- parameter_dictionary: Dict or None
Dictionary of parameters. Only used if file_name is None.
Useful for creating dummy parameter file objects in unit testing.
"""
if file_name is not None:
with open(file_name, "r") as handle:
self.parameters = yaml.safe_load(handle)
if self.calculate_missing_properties():
self.unregistered_parameters = set()
else:
self.unregistered_parameters = None
else:
self.unregistered_parameters = None
if parameter_dictionary is not None:
self.parameters = parameter_dictionary
else:
self.parameters = {}
self.snipshot = snipshot
def get_parameters(self) -> Dict:
"""
Get a copy of the parameter dictionary.
"""
return dict(self.parameters)
def write_parameters(self, file_name: str = "SOAP.used_parameters.yml"):
"""
Write the (used) parameters to a file.
Parameters:
- file_name: str
Name of the file to write.
"""
with open(file_name, "w") as handle:
yaml.safe_dump(self.parameters, handle)
def get_property_mask(self, halo_type: str, full_list: List[str]) -> Dict:
"""
Get a dictionary with True/False values indicating which properties
should actually be computed for the given halo type. The dictionary
keys are based on the contents of the given list of properties. If
a property in the list is missing from the parameter file, it is
assumed that this property needs to be calculated.
Note that we currently do not check for properties in the parameter
file that are not in the list.
Parameters:
- halo_type: str
Halo type identifier in the parameter file, can be one of
ApertureProperties, ProjectedApertureProperties, SOProperties
or SubhaloProperties.
- full_list: List[str]
List of all the properties that can be calculated by this
particular halo type (as defined in the corresponding HaloProperty
specialisation).
Returns a dictionary with True or False for each property in full_list.
"""
if not halo_type in self.parameters:
self.parameters[halo_type] = {}
if not "properties" in self.parameters[halo_type]:
self.parameters[halo_type]["properties"] = {}
for property in full_list:
self.parameters[halo_type]["properties"][
property
] = self.calculate_missing_properties()
mask = {}
for property in full_list:
# Property is listed in the parameter file for this halo_type
if property in self.parameters[halo_type]["properties"]:
should_calculate = self.parameters[halo_type]["properties"][property]
# should_calculate is a dict if we want different behaviour for snapshots/snipshots
if isinstance(should_calculate, dict):
if self.snipshot:
mask[property] = should_calculate["snipshot"]
else:
mask[property] = should_calculate["snapshot"]
# otherwise should_calculate is a bool
else:
mask[property] = should_calculate
# Property is not listed in the parameter file for this halo_type
else:
if self.calculate_missing_properties():
mask[property] = True
self.parameters[halo_type]["properties"][property] = True
if self.unregistered_parameters is not None:
self.unregistered_parameters.add((halo_type, property))
else:
mask[property] = False
assert isinstance(mask[property], bool)
return mask
def print_unregistered_properties(self) -> None:
"""
Prints a list of any properties that will be calculated that are not present in the parameter file
"""
if not self.calculate_missing_properties():
print("Properties not present in the parameter file will not be calculated")
elif (self.unregistered_parameters is not None) and (
len(self.unregistered_parameters) != 0
):
print(
"The following properties were not found in the parameter file, but will be calculated:"
)
for halo_type, property in self.unregistered_parameters:
print(f" {halo_type.ljust(30)}{property}")
def print_invalid_properties(self) -> None:
"""
Print a list of any properties in the parameter file that are not present in
the property table. This doesn't check if the property is defined for a specific
halo type.
"""
invalid_properties = []
full_property_list = property_table.PropertyTable.full_property_list
valid_properties = [prop[0] for prop in full_property_list.values()]
for key in self.parameters:
# Skip keys which aren't halo types
if "properties" not in self.parameters[key]:
continue
for prop in self.parameters[key]["properties"]:
if prop not in valid_properties:
invalid_properties.append(prop)
if len(invalid_properties):
print(
"The following properties were found in the parameter file, but are invalid:"
)
for prop in invalid_properties:
print(f" {prop}")
def get_halo_type_variations(
self, halo_type: str, default_variations: Dict
) -> Dict:
"""
Get a dictionary of variations for the given halo type.
Different variations are for example bound/unbound SubhaloProperties or
aperture properties with different aperture sizes.
If the given halo type is not found in the parameter file, or no
variations are specified, the default variations are used.
Parameters:
- halo_type: str
Halo type identifier in the parameter file, can be one of
ApertureProperties, ProjectedApertureProperties, SOProperties
or SubhaloProperties.
- default_variations: Dict
Dictionary with default variations that will be used if the
halo type variations are not provided in the parameter file.
Returns a dictionary from which different versions of the
corresponding HaloProperty specialisation can be constructed.
"""
if not halo_type in self.parameters:
self.parameters[halo_type] = {}
if not "variations" in self.parameters[halo_type]:
self.parameters[halo_type]["variations"] = {}
for variation in default_variations:
self.parameters[halo_type]["variations"][variation] = dict(
default_variations[variation]
)
return dict(self.parameters[halo_type]["variations"])
def get_particle_property(self, property_name: str) -> Tuple[str, str]:
"""
Get the particle type and name in the snapshot of the given generic
particle property name, taking into account aliases.
An alias is useful if a dataset has a different name than expected
internally. For example, in FLAMINGO the ElementMassFractions were
only output in their smoothed form, so the following alias is
required:
PartType0/ElementMassFractions: PartType0/SmoothedElementMassFractions
Parameters:
- property_name: str
(Full) path to a generic dataset in the snapshot.
Returns a tuple with the path of the actual dataset in the snapshot,
e.g. ("PartType4", "Masses").
"""
if "aliases" in self.parameters:
if property_name in self.parameters["aliases"]:
property_name = self.parameters["aliases"][property_name]
parts = property_name.split("/")
if not len(parts) == 2:
raise RuntimeError(
f'Unable to parse particle property name "{property_name}"!'
)
return parts[0], parts[1]
def get_aliases(self) -> Dict:
"""
Get all the aliases defined in the parameter file.
Returns the dictionary of aliases or an empty dictionary if no
aliases were defined (there are no default aliases).
"""
if "aliases" in self.parameters:
return dict(self.parameters["aliases"])
else:
return dict()
def get_filters(self, default_filters: Dict) -> Dict:
"""
Get a dictionary with filters to use for SOAP.
Parameters:
- default_filter: Dict
Dictionary with default filters, which are used
if no filters are found in the parameter file or if a particular
category is missing.
Returns a dictionary with a threshold value for each category present,
the properties to use for the filter, and how to combine the properties
if multiple are listed.
"""
filters = dict(default_filters)
if "filters" in self.parameters:
for category in default_filters:
if category in self.parameters["filters"]:
filters[category] = self.parameters["filters"][category]
else:
self.parameters["filters"][category] = filters[category]
else:
self.parameters["filters"] = dict(default_filters)
return filters
def get_defined_constants(self) -> Dict:
"""
Get the dictionary with defined constants from the parameter file.
Returns an empty dictionary if no defined constants are found in the
parameter file (there are no default constants).
"""
if "defined_constants" in self.parameters:
return dict(self.parameters["defined_constants"])
else:
return dict()
def calculate_missing_properties(self) -> bool:
"""
Returns a bool indicating if properties missing from parameter file
should be computed. Defaults to true.
"""
calculations = self.parameters.get("calculations", {})
return calculations.get("calculate_missing_properties", True)