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