Belle II Software development
__init__.py
1#!/usr/bin/env python3
2
3
10
11"""
12PXD calibration APIs for CAF
13Author: qingyuan.liu@desy.de
14"""
15
16from basf2 import register_module, create_path
17from ROOT import Belle2 # noqa: make Belle2 namespace available
18from ROOT.Belle2 import PXDHotPixelMaskCalibrationAlgorithm, PXDAnalyticGainCalibrationAlgorithm
19from ROOT.Belle2 import PXDValidationAlgorithm
20from caf.framework import Calibration
21from caf.strategies import SequentialRunByRun, SimpleRunByRun
22
23run_types = ['beam', 'physics', 'cosmic', 'all']
24gain_methods = ['analytic', 'generic_mc', 'cluster_sim', '']
25
26
27def hot_pixel_mask_calibration(
28 input_files,
29 cal_name="PXDHotPixelMaskCalibration",
30 run_type=None,
31 global_tags=None,
32 local_dbs=None,
33 **kwargs):
34 """
35 Parameters:
36 input_files (list): A list of input files to be assigned to calibration.input_files
37
38 run_type ( str): The run type which will define different calibration parameters.
39 Should be in ['beam', 'physics', 'cosmic', 'all']
40
41 global_tags (list): A list of global tags
42
43 local_dbs (list): A list of local databases
44
45 **kwargs: Additional configuration to support extentions without changing scripts in calibration folder.
46 Supported options are listed below:
47
48 "activate_masking" is a boolean to activate existing masking in the payload.
49 PXDHotPixelMaskCollector will be used then instead of PXDRawHotPixelMaskCollector
50
51 "debug_hist" is the flag to save a ROOT file for debug histograms.
52 "inefficientPixelMultiplier" is the multiplier on median occupancy to define an inefficient pixel.
53 0 means only dead pixels are marked as dead.
54 A value > 0 means any pixels with occupancy below the threshold will be marked as dead.
55 "minInefficientPixels" is the minimum number of pixels to define a dead or inefficient row.
56 The rows with inefficient pixels >= this value will be marked as dead rows for now.
57 "deadPixelPayloadName" is the payload name used for more defective pixel types. The default is PXDDeadPixelPar.
58
59 Return:
60 A caf.framework.Calibration obj.
61 """
62
63 if global_tags is None:
64 global_tags = []
65 if local_dbs is None:
66 local_dbs = []
67 if run_type is None:
68 run_type = 'all'
69 if not isinstance(run_type, str) or run_type.lower() not in run_types:
70 raise ValueError(f"run_type not found in run_types : {run_type}")
71 activate_masking = kwargs.get("activate_masking", False)
72 if not isinstance(activate_masking, bool):
73 raise ValueError("activate_masking is not a boolean!")
74 debug_hist = kwargs.get("debug_hist", True)
75 if not isinstance(debug_hist, bool):
76 raise ValueError("debug_hist is not a boolean!")
77 inefficientPixelMultiplier = kwargs.get("inefficientPixelMultiplier", 0.)
78 if not isinstance(inefficientPixelMultiplier, float):
79 raise ValueError("inefficientPixelMultiplier is not a float!")
80 minInefficientPixels = kwargs.get("minInefficientPixels", 250)
81 if not isinstance(minInefficientPixels, int):
82 raise ValueError("minInefficientPixels is not an int!")
83 deadPixelPayloadName = kwargs.get("deadPixelPayloadName", "PXDDeadPixelPar")
84 if not isinstance(deadPixelPayloadName, str):
85 raise ValueError("deadPixelPayloadName is not a str!")
86
87 # Create basf2 path
88
89 gearbox = register_module('Gearbox')
90 geometry = register_module('Geometry')
91 geometry.param('components', ['PXD'])
92 pxdunpacker = register_module('PXDUnpacker')
93 # pxdunpacker.param('ContinueOnError', True)
94 checker = register_module('PXDPostErrorChecker')
95 # checker.param("CriticalErrorMask", 0)
96
97 main = create_path()
98 main.add_module(gearbox)
99 main.add_module(geometry)
100 main.add_module(pxdunpacker)
101 main.add_module(checker)
102 if activate_masking:
103 main.add_module("ActivatePXDPixelMasker")
104 main.add_module("PXDRawHitSorter")
105
106 # Collector setup
107 collector_name = "PXDRawHotPixelMaskCollector"
108 if activate_masking:
109 collector_name = "PXDHotPixelMaskCollector"
110 collector = register_module(collector_name)
111 collector.param("granularity", "run")
112
113 # Algorithm setup
114
115 algorithm = PXDHotPixelMaskCalibrationAlgorithm() # Getting a calibration algorithm instanced
116 algorithm.forceContinueMasking = False # Continue masking even when few/no events were collected
117 algorithm.minEvents = 30000 # Minimum number of collected events for masking
118 algorithm.minHits = 15 # Do dead pixel masking when median number of hits per pixel is higher
119 algorithm.pixelMultiplier = 7 # Occupancy threshold is median occupancy x multiplier
120 algorithm.maskDrains = True # Set True to allow masking of hot drain lines
121 algorithm.drainMultiplier = 7 # Occupancy threshold is median occupancy x multiplier
122 algorithm.maskRows = True # Set True to allow masking of hot rows
123 algorithm.rowMultiplier = 7 # Occupancy threshold is median occupancy x multiplier
124 # Occupancy threshold is median occupancy x multiplier
125 algorithm.inefficientPixelMultiplier = inefficientPixelMultiplier
126 # Minimum number of inefficient pixels in a dead/inefficient row
127 algorithm.minInefficientPixels = minInefficientPixels
128 algorithm.deadPixelPayloadName = deadPixelPayloadName
129 algorithm.setDebugHisto(debug_hist)
130 algorithm.setPrefix(collector_name)
131 if run_type.lower() == 'cosmic':
132 algorithm.forceContinueMasking = True
133
134 # Create the calibration instance
135
136 cal = Calibration(
137 name=cal_name,
138 collector=collector,
139 algorithms=[algorithm],
140 input_files=input_files)
141 for global_tag in global_tags:
142 cal.use_central_database(global_tag)
143 for local_db in local_dbs:
144 cal.use_local_database(local_db)
145 cal.pre_collector_path = main
146 cal.strategies = SequentialRunByRun
147
148 # Run type dependent configurations
149
150 if run_type.lower() == 'cosmic':
151 cal.strategies = SimpleRunByRun
152
153 return cal
154
155
156def gain_calibration(input_files, cal_name="PXDGainCalibration",
157 boundaries=None, global_tags=None, local_dbs=None,
158 gain_method="Analytic", validation=True, **kwargs):
159 """
160 Parameters:
161 input_files (list): A list of input files to be assigned to calibration.input_files
162
163 boundaries (c++ std::vector): boundaries for iov creation
164
165 global_tags (list): A list of global tags
166
167 local_dbs (list): A list of local databases
168
169 gain_method (str): A string of gain algorithm in
170 ['analytic', 'generic_mc', 'cluster_sim', '']. Empty str means to skip gain
171 calibration and validation has to be True otherwise no algorithm will be used.
172 Caveat: Only the analytic method is now implemented.
173
174 validation (bool): Adding validation algorithm if True (default)
175
176 **kwargs: Additional configuration to support extentions without changing scripts in calibration folder.
177 Supported options are listed below:
178
179 "collector_prefix": a string indicating which collector to be used for gain calibration. The supported
180 collectors are:
181 PXDPerformanceVariablesCollector (default),
182 PXDPerformanceCollector(using RAVE package for vertexing, obsolete)
183 "useClusterPosition": Flag to use cluster postion rather than track point to group pixels for calibration.
184 "particle_type": Particle type assigned to tracks. "e" by default.
185 "track_cuts_4gain": Track cuts used for gain calibration.
186 "track_cuts_4eff": Track cuts used for efficiency study.
187 "track_cuts_4res": Track cuts used for resolution study.
188
189 Return:
190 A caf.framework.Calibration obj.
191 """
192
193 if global_tags is None:
194 global_tags = []
195 if local_dbs is None:
196 local_dbs = []
197 if gain_method is None:
198 gain_method = 'analytic'
199 if not isinstance(gain_method, str) or gain_method.lower() not in gain_methods:
200 raise ValueError(f"gain_method not found in gain_methods : {gain_method}")
201 collector_prefix = kwargs.get("collector_prefix", "PXDPerformanceVariablesCollector")
202 supported_collectors = ["PXDPerformanceVariablesCollector", "PXDPerformanceCollector"]
203 if not isinstance(collector_prefix, str) or collector_prefix not in supported_collectors:
204 raise ValueError(f"collector_prefix not found in {supported_collectors}")
205 useClusterPosition = kwargs.get("useClusterPosition", False)
206 if not isinstance(useClusterPosition, bool):
207 raise ValueError("useClusterPosition has to be a boolean!")
208 particle_type = kwargs.get("particle_type", "e") # rely on modular analysis for value check
209 track_cuts_4gain = kwargs.get("track_cuts_4gain", "p > 1.0") # see above
210 track_cuts_4eff = kwargs.get("track_cuts_4eff", "pt > 2.0") # see above
211 track_cuts_4res = kwargs.get("track_cuts_4res", "Note2019") # NOTE-TE-2019-018
212
213 # Create basf2 path
214
215 gearbox = register_module('Gearbox')
216 geometry = register_module('Geometry', useDB=True)
217 genFitExtrapolation = register_module('SetupGenfitExtrapolation')
218 roiFinder = register_module('PXDROIFinder')
219
220 main = create_path()
221 main.add_module(gearbox)
222 main.add_module(geometry)
223 main.add_module(genFitExtrapolation)
224 main.add_module(roiFinder) # for PXDIntercepts
225 main.add_module("ActivatePXDPixelMasker")
226
227 # Collector setup
228 collector = register_module(collector_prefix)
229 if collector_prefix == "PXDPerformanceCollector":
230 main.add_module("PXDPerformance")
231 collector.param("fillEventTree", False)
232 else: # the default PXDPerformanceVariablesCollector
233 import modularAnalysis as ana
234 import vertex
235 # Particle list for gain calibration
236 p = particle_type
237 ana.fillParticleList(f'{p}+:gain', track_cuts_4gain, path=main)
238
239 # Particle list for event selection in efficiency monitoring
240 # nSVDHits > 5 doesn't help, firstSVDLayer == 3 for < 0.1% improvement?
241 ana.fillParticleList(f'{p}+:eff', track_cuts_4eff, path=main)
242 # Mass cut (9.5 < M < 11.5) below can improve efficiency by ~ 1%
243 ana.reconstructDecay(f'vpho:eff -> {p}+:eff {p}-:eff', '9.5<M<11.5', path=main)
244 # < 0.1% improvement by using kfit pvalue >= 0.01 after mass cut
245 # vertex.kFit('vpho:eff', conf_level=0.01, fit_type="fourC", daughtersUpdate=False, path=main)
246
247 # Particle list for studying impact parameter resolution
248 # Alias dosn't work with airflow implementation
249 # from variables import variables as vm
250 # vm.addAlias("pBetaSinTheta3o2", "formula(pt * (1./(1. + tanLambda**2)**0.5)**0.5)")
251 # vm.addAlias("absLambda", "abs(atan(tanLambda))")
252 track_cuts_4res_note2019 = 'pt>1.0 and abs(dz)<1.0 and dr<0.3'
253 track_cuts_4res_note2019 += ' and nCDCHits>20 and nSVDHits>=8 and nPXDHits>=1'
254 track_cuts_4res_note2019 += ' and [abs(atan(tanLambda)) < 0.5]'
255 track_cuts_4res_note2019 += ' and [formula(pt * (1./(1. + tanLambda**2)**0.5)**0.5) > 2.0]'
256 # track_cuts_4res_note2019 += ' and [absLambda<0.5]'
257 # track_cuts_4res_note2019 += ' and [pBetaSinTheta3o2>2.0]'
258 if track_cuts_4res == "Note2019":
259 track_cuts_4res = track_cuts_4res_note2019
260 ana.fillParticleList(f'{p}+:res', track_cuts_4res, path=main)
261 ana.reconstructDecay(f'vpho:res -> {p}+:res {p}-:res', '9.5<M<11.5', path=main)
262 # Remove multiple candidate events
263 ana.applyCuts('vpho:res', 'nParticlesInList(vpho:res)==1', path=main)
264 vertex.kFit('vpho:res', conf_level=0.0, path=main)
265
266 collector.param("PList4GainName", f"{p}+:gain")
267 collector.param("PList4EffName", "vpho:eff")
268 collector.param("PList4ResName", "vpho:res")
269 collector.param("maskedDistance", 3)
270 collector.param("useClusterPosition", useClusterPosition)
271
272 collector.param("granularity", "run")
273 collector.param("minClusterCharge", 8)
274 collector.param("minClusterSize", 1)
275 collector.param("maxClusterSize", 10)
276 collector.param("nBinsU", 4)
277 collector.param("nBinsV", 6)
278 collector.param("fillChargeRatioHistogram", True)
279
280 # Algorithm setup
281 algorithms = []
282 if validation:
283 validation_alg = PXDValidationAlgorithm()
284 validation_alg.setPrefix(collector_prefix)
285 validation_alg.minTrackPoints = 10 # Minimum number of track points
286 validation_alg.save2DHists = True # Flag to save 2D histogram for efficiency on layer 1 or 2 in Z-phi plane
287 validation_alg.saveD0Z0 = kwargs.get("saveD0Z0", False) # Flag to save corrected d0 (z0) difference
288 algorithms.append(validation_alg)
289
290 # validation_alg.setBoundaries(boundaries) # This takes boundaries from the expert_config
291 if (gain_method != ''):
292 algorithm = PXDAnalyticGainCalibrationAlgorithm() # Getting a calibration algorithm instanced
293 algorithm.minClusters = 200 # Minimum number of clusters in each region
294 algorithm.safetyFactor = 11 # Safety factor x minClusters/region -> <minClusters> for module
295 algorithm.forceContinue = False # Force continue the algorithm instead of return c_notEnoughData
296 algorithm.strategy = 0 # 0: medians, 1: landau fit
297 algorithm.correctForward = True # Correct default gains in forward regions due to low statistics
298 algorithm.useChargeRatioHistogram = True # Use Charge ratio histograms for the calibration
299 algorithm.setPrefix(collector_prefix)
300 # algorithm.setBoundaries(boundaries) # This takes boundaries from the expert_config
301 algorithms.append(algorithm)
302
303 # Create the calibration instance
304
305 cal = Calibration(
306 name=cal_name,
307 collector=collector,
308 algorithms=algorithms,
309 input_files=input_files)
310 for global_tag in global_tags:
311 cal.use_central_database(global_tag)
312 for local_db in local_dbs:
313 cal.use_local_database(local_db)
314 cal.pre_collector_path = main
315 # cal.strategies = SequentialBoundaries
316 cal.strategies = SequentialRunByRun
317 # cal.strategies = SimpleRunByRun
318
319 return cal
def kFit(list_name, conf_level, fit_type='vertex', constraint='', daughtersUpdate=False, decay_string='', massConstraint=[], recoilMass=0, smearing=0, path=None)
Definition: vertex.py:129