Belle II Software  release-08-01-10
__init__.py
1 # disable doxygen check for this file
2 # @cond
3 
4 
11 import basf2
12 from collections import namedtuple
13 import json
14 
15 prompt_script_package = "prompt.calibrations."
16 prompt_script_dir = "calibration/scripts/prompt/calibrations"
17 
18 prompt_validation_script_package = "prompt.validations."
19 prompt_validation_script_dir = "calibration/scripts/prompt/validations"
20 
21 INPUT_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 
55 class 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 
196 class 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