Belle II Software development
__init__.py
1# disable doxygen check for this file
2# @cond
3
4
11import basf2
12from collections import namedtuple
13import json
14
15prompt_script_package = "prompt.calibrations."
16prompt_script_dir = "calibration/scripts/prompt/calibrations"
17
18prompt_validation_script_package = "prompt.validations."
19prompt_validation_script_dir = "calibration/scripts/prompt/validations"
20
21INPUT_DATA_FILTERS = {"Magnet": {"On": "On",
22 "Off": "Off",
23 "Either": "Either"},
24 "Beam Energy": {"No Beam": "No Beam",
25 "4S": "4S",
26 "Continuum": "Continuum",
27 "": "",
28 "Scan": "Scan"},
29 "Run Type": {"beam": "beam",
30 "cosmic": "cosmic",
31 "debug": "debug", "null": "null",
32 "physics": "physics"},
33 "Data Tag": {"bhabha_all_calib": "bhabha_all_calib",
34 "bhabha_combined_calib": "bhabha_combined_calib",
35 "cosmic_calib": "cosmic_calib",
36 "gamma_gamma_calib": "gamma_gamma_calib",
37 "hadron_calib": "hadron_calib",
38 "btocharm_calib": "btocharm_calib",
39 "mumu_tight_or_highm_calib": "mumu_tight_or_highm_calib",
40 "offip_calib": "offip_calib",
41 "radmumu_calib": "radmumu_calib",
42 "random_calib": "random_calib",
43 "delayedbhabha_calib": "delayedbhabha_calib",
44 "single_gamma_mc": "single_gamma_mc"},
45 "Data Quality Tag": {">=30 Minute Run": ">=30 Minute Run",
46 "Bad For Alignment": "Bad For Alignment",
47 "Good": "Good",
48 "Good Shifter": "Good Shifter",
49 "Good For PXD": "Good For PXD",
50 "Good Or Recoverable": "Good Or Recoverable",
51 "Good Or Recoverable Shifter": "Good Or Recoverable Shifter"}
52 }
53
54
55class CalibrationSettings(namedtuple('CalSet_Factory',
56 ["name",
57 "expert_username",
58 "subsystem",
59 "description",
60 "input_data_formats",
61 "input_data_names",
62 "input_data_filters",
63 "depends_on",
64 "expert_config",
65 "produced_payloads"])):
66 """
67 Simple class to hold and display required information for a prompt calibration script (process).
68
69 Parameters:
70 name (str): The unique calibration name, not longer than 64 characters.
71
72 expert_username (str): The GitLab username of the expert to contact about this script.
73 This username will be used to assign the default responsible person for submitting and checking prompt
74 calibration jobs.
75
76 subsystem (str): The name of the subsystem that this calibration is for.
77 e.g. "cdc", "ecl", "klm", "svd", "top", "beam", etc.
78
79 description (str): Long form description of the calibration and what it does. Feel free to make this as long as you need.
80
81 input_data_formats (frozenset(str)): The data formats {'raw', 'cdst', 'mdst', 'udst'} of the input files
82 that should be used as input to the process. Used to figure out if this calibration should occur
83 before the relevant data production e.g. before cDST files are created.
84
85 input_data_names (frozenset(str)): The names that you will use when accessing the input data given to the
86 prompt calibration process i.e. Use these in the ``get_calibrations`` function to access the correct input
87 data files. e.g. input_data_names=["all_events", "offres_photon_events"]
88
89 input_data_filters (dict): The data selection for the data input names, used for automated calibration.
90 The keys should correspond to one of the ``input_data_names`` with the values being a list of the various data
91 filters, e.g. Data Tag, Beam Energy, Run Type, Run Quality Tag and Magnet. All available filters can be found in the
92 input_data_filters dictionary e.g. from prompt import input_data_filters with details about data tags and run quality
93 tags found at: https://calibration.belle2.org/belle2/data_tags/list/.
94 To exclude specific filters, pre-append with *NOT* e.g.
95 {"all_events": ["mumu_tight_or_highm_calib", "hadron_calib", "Good", "On"],
96 "offres_photon_events": ["gamma_gamma_calib", "Good", "NOT On"]}.
97 Not selecting a specific filters (e.g. Magnet) is equivalent to not having any requirements, e.g. (Either)
98
99 depends_on (list(CalibrationSettings)): The settings variables of the other prompt calibrations that you want
100 want to depend on. This will allow the external automatic system to understand the overall ordering of
101 scripts to run. If you encounter an import error when trying to run your prompt calibration script, it is
102 likely that you have introduced a circular dependency.
103
104 expert_config (dict): Default expert configuration for this calibration script. This is an optional dictionary
105 (which must be JSON compliant) of configuration options for your get_calibrations(...) function.
106 This is supposed to be used as a catch-all place to send in options for your calibration setup. For example,
107 you may want to have an optional list of IoV boundaries so that your prompt script knows that it should split the
108 input data between different IoV ranges. Or you might want to send if options like the maximum events per
109 input file to process. The value in your settings object will be the *default*, but you can override the value via
110 the caf_config.json sent into ``b2caf-prompt-run``.
111
112 produced_payloads (list(str)): The names of the payloads that this calibration script produces.
113 """
114
115
118 allowed_data_formats = frozenset({"raw", "cdst", "mdst", "udst"})
119
120 def __new__(cls, name, expert_username, subsystem, description,
121 input_data_formats=None, input_data_names=None, input_data_filters=None,
122 depends_on=None, expert_config=None, produced_payloads=None):
123 """
124 The special method to create the tuple instance. Returning the instance
125 calls the __init__ method
126 """
127 if len(name) > 64:
128 raise ValueError("name cannot be longer than 64 characters!")
129 if not input_data_formats:
130 raise ValueError("You must specify at least one input data format")
131 input_data_formats = frozenset(map(lambda x: x.lower(), input_data_formats))
132 if input_data_formats.difference(cls.allowed_data_formats):
133 raise ValueError("There was a data format that is not in the allowed_data_formats attribute.")
134 if not input_data_names:
135 raise ValueError("You must specify at least one input data name")
136 input_data_names = frozenset(input_data_names)
137
138 # The input data names in the filters MUST correspond to the input data names for the calibration.
139 if input_data_filters:
140 if set(input_data_filters.keys()) != input_data_names:
141 raise ValueError("The 'input_data_filters' keys don't match the 'input_data_names'!")
142 # Requested input data filters MUST exist in the ones we defined in the global dictionary.
143 allowed_filters = {filter_name for category in INPUT_DATA_FILTERS.values() for filter_name in category}
144 requested_filters = {filter_name.replace("NOT", "", 1).lstrip() for filters in input_data_filters.values()
145 for filter_name in filters}
146 if not allowed_filters.issuperset(requested_filters):
147 raise ValueError("The 'input_data_filters' contains unknown filter names:"
148 f"{requested_filters.difference(allowed_filters)}")
149 else:
150 input_data_filters = {}
151
152 # Check that the subsystem is among the allowed ones
153 allowed_subsystems = {"pxd", "svd", "cdc", "ecl", "top", "arich",
154 "klm", "trigger", "tracking", "alignment", "beam", "example"}
155 if subsystem not in allowed_subsystems:
156 raise ValueError(f"subsystem must be one of {allowed_subsystems}, but got '{subsystem}'")
157
158 if expert_config:
159 # Check that it's a dictionary and not some other valid JSON object
160 if not isinstance(expert_config, dict):
161 raise TypeError("expert_config must be a dictionary")
162 # Check if it is JSONable since people might put objects in there by mistake
163 try:
164 json.dumps(expert_config)
165 except TypeError as e:
166 basf2.B2ERROR("expert_config could not be serialised to JSON. "
167 "Most likely you used a non-supported type e.g. datetime.")
168 raise e
169 else:
170 expert_config = {}
171
172 if depends_on:
173 for calibration_settings in depends_on:
174 if not isinstance(calibration_settings, cls):
175 raise TypeError(f"A list of {str(cls)} object is required when setting the 'depends_on' keyword.")
176 else:
177 depends_on = []
178
179 return super().__new__(cls, name, expert_username, subsystem, description,
180 input_data_formats, input_data_names, input_data_filters,
181 depends_on, expert_config, produced_payloads)
182
183 def json_dumps(self):
184 """
185 Returns:
186 str: A valid JSON format string of the attributes.
187 """
188 depends_on_names = [calibration_settings.name for calibration_settings in self.depends_on]
189 return json.dumps({"name": self.name,
190 "expert_username": self.expert_username,
191 "subsystem": self.subsystem,
192 "input_data_formats": list(self.input_data_formats),
193 "input_data_names": list(self.input_data_names),
194 "input_data_filters": self.input_data_filters,
195 "depends_on": list(depends_on_names),
196 "description": self.description,
197 "expert_config": self.expert_config,
198 "produced_payloads": self.produced_payloads
199 })
200
201 def __str__(self):
202 depends_on_names = [calibration_settings.name for calibration_settings in self.depends_on]
203 output_str = str(self.__class__.__name__) + f"(name='{self.name}'):\n"
204 output_str += f" expert_username='{self.expert_username}'\n"
205 output_str += f" subsystem='{self.subsystem}'\n"
206 output_str += f" input_data_formats={list(self.input_data_formats)}\n"
207 output_str += f" input_data_names={list(self.input_data_names)}\n"
208 output_str += f" input_data_filters={list(self.input_data_filters)}\n"
209 output_str += f" depends_on={list(depends_on_names)}\n"
210 output_str += f" description='{self.description}'\n"
211 output_str += f" expert_config={self.expert_config}\n"
212 output_str += f" produced_payloads={self.produced_payloads}"
213 return output_str
214
215
216class ValidationSettings(namedtuple('ValSet_Factory', ["name", "description", "download_files", "expert_config"])):
217 """
218 Simple class to hold and display required information for a validation calibration script (process).
219
220 Parameters:
221 name (str): The unique name that must match the corresponding calibration, not longer than 64 characters.
222
223 description (str): Long form description of the validation and what it does. Feel free to make this as long as you need.
224
225 download_files (list): The names of the files you want downloaded, e.g. mycalibration_stdout. If multiple files of
226 the same name are found, all files are downloaded and appended with the folder they were in.
227
228 expert_config (dict): Default expert configuration for this validation script. This is an optional dictionary
229 (which must be JSON compliant) of configuration options for validation script.
230 This is supposed to be used as a catch-all place to send in options for your calibration setup. For example,
231 you may want to have an optional list of IoV boundaries so that your validation script knows that it should split the
232 input data between different IoV ranges. Or you might want to send if options like the maximum events per
233 input file to process. The value in your settings object will be the *default*, but you can override the value via
234 the caf_config.json sent into ``b2caf-prompt-run``.
235 """
236
237 def __new__(cls, name, description, download_files=None, expert_config=None):
238 """
239 The special method to create the tuple instance. Returning the instance
240 calls the __init__ method
241 """
242 if len(name) > 64:
243 raise ValueError("name cannot be longer than 64 characters!")
244
245 if expert_config:
246 # Check that it's a dictionary and not some other valid JSON object
247 if not isinstance(expert_config, dict):
248 raise TypeError("expert_config must be a dictionary")
249 # Check if it is JSONable since people might put objects in there by mistake
250 try:
251 json.dumps(expert_config)
252 except TypeError as e:
253 basf2.B2ERROR("expert_config could not be serialised to JSON. "
254 "Most likely you used a non-supported type e.g. datetime.")
255 raise e
256 else:
257 expert_config = {}
258
259 return super().__new__(cls, name, description, download_files, expert_config)
260
261 def json_dumps(self):
262 """
263 Returns:
264 str: A valid JSON format string of the attributes.
265 """
266 return json.dumps({"name": self.name,
267 "description": self.description,
268 "download_files": self.download_files,
269 "expert_config": self.expert_config
270 })
271
272 def __str__(self):
273 output_str = str(self.__class__.__name__) + f"(name='{self.name}'):\n"
274 output_str += f" description='{self.description}'\n"
275 output_str += f" download_files='{self.download_files}'\n"
276 output_str += f" expert_config={self.expert_config}"
277 return output_str
278
279# @endcond
STL class.