ATLAS Offline Software
Loading...
Searching...
No Matches
L1MenuRun4.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
3from __future__ import annotations
4from typing import TYPE_CHECKING
5
6
7from enum import Enum
8
9from .GlobalAlgorithmsCollection import GlobalAlgorithmsCollection
10from .CTP import CTP
11from .Items import MenuItem, MenuItemsCollection
12from .Thresholds import MenuThresholdsCollection, Threshold
13from .Boards import MenuBoardsCollection
14from .Connectors import MenuConnectorsCollection, CType
15from .MenuUtils import get_smk_psk_Name
16from .Limits import Limits
17from .ThresholdType import ThrType
18from .CTPCondition import ThrCondition
19
20from AthenaCommon.Logging import logging
21log = logging.getLogger(__name__)
22
23if TYPE_CHECKING:
24 from AthenaConfiguration.AthConfigFlags import AthConfigFlags
25 from ..Base.GlobalAlgorithm import GlobalAlgorithm
26
27
29 """Class to bundle all L1 menu related infomation into a single object
30 (ideally this should be done for L1 and HLT combined, but for now we only do it for L1)
31 This information includes run, menuName, menuFile, menuInputFile, menuLegacyInputFile
32 and input files.
33 """
34
35 class MenuType(Enum):
36 """purpose of the menu: Physics, MC, Dev, HI, AllCTPIN"""
37 PHYSICS = "Physics"
38 MC = "MC"
39 DEV = "Dev"
40 HI = "HI"
41 ALLCTPIN = "AllCTPIn"
42
43 def __init__(self, menuFullName: str, *,
44 menuPurpose: MenuType, menuBaseName: str, menuFile: str,
45 menuInputFile: str, run: int = 4):
46 self.menuFullName: str = menuFullName
47 self.menuBaseName: str = menuBaseName
48 self.menuFile: str = menuFile
49 self.menuInputFile: str = menuInputFile
51 self.run: int = run
52
53 def __str__(self):
54 return f"menuName = {self.menuFullName}, menuFile={self.menuFile}, menuInputFile={self.menuInputFile}, menuPurpose={self.menuPurpose}"
55
56 def __repr__(self):
57 return f"MenuMetaInfo(menuName={self.menuFullName}, menuFile={self.menuFile}, menuInputFile={self.menuInputFile}, menuPurpose={self.menuPurpose})"
58
59
61 """
62 This class holds everything that is needed to define the menu
63 """
64
65 def __init__(self, *, menuInfo: MenuMetaInfo, flags: AthConfigFlags):
66
67 self.menuInfo: MenuMetaInfo = menuInfo
68
69 # items in menu
70 self.items: MenuItemsCollection = MenuItemsCollection()
71
72 # store cached flags
73 self.flags = flags
74
75 # all thresholds that are in menu (new and legacy)
77
78 # all thresholds that are in menu (new and legacy)
80
81 # all connectors between legacyCalo, muctpi, topo and the CTPIN/CTPCORE
83
84 # board definition
86
87 # CTP Info in the menu
88 self.ctp = CTP(4)
89
90 smk_psk_Name: dict[str, str] = get_smk_psk_Name(self.menuName)
91 self.items.menuName = smk_psk_Name["smkName"]
92 self.items.pssName = smk_psk_Name["pskName"]
93
94 @property
95 def menuName(self) -> str:
96 """The name of the menu, e.g. Physics_pp_run4"""
97 return self.menuInfo.menuBaseName
98
99 @property
100 def menuFullName(self) -> str:
101 """The full name of the menu, e.g. Physics_pp_run4_bulk"""
102 return self.menuInfo.menuFullName
103
104 def addItem(self, item: MenuItem):
105 self.items += item
106
107 def getItem(self, name: str) -> MenuItem:
108 return self.items[name]
109
110 def addThreshold(self, threshold: Threshold):
111 self.thresholds += threshold
112
113 def addGlobalAlgo(self, algo: GlobalAlgorithm):
114 self.globalAlgos.addAlgo(algo)
115
116 def addBoard(self, boardDef):
117 return self.boards.addBoard(boardDef)
118
119 def addConnector(self, connDef):
120 self.connectors.addConnector(connDef)
121
123 self.ctp.setupMonitoring(self.menuInfo.menuBaseName, self.items, self.thresholds,
124 self.connectors, self.menuInfo.menuFullName)
125
126 def check(self):
127 log.info("Doing L1 Menu checks")
128 from collections import defaultdict as dd
129 missing = dd(list)
130 allThresholds = set([thr.name for thr in self.thresholds])
131 allUsedThresholds = set()
132 for item in self.items:
133 for thrName in item.thresholdNames():
134 if 'SPARE' in thrName:
135 raise RuntimeError("CTP input %s is used by %s but SPARE thresholds are not to be used!" % (thrName, item))
136 if thrName not in allThresholds:
137 missing[thrName].append(item.name)
138 else:
139 allUsedThresholds.add(thrName)
140
141 for thrName in sorted(missing.keys()):
142 log.warning("Threshold %s (used by %s) is not defined in the menu", thrName, ",".join(missing[thrName]))
143
144 if len(allThresholds) - len(allUsedThresholds) > 0:
145 unusedThresholds = allThresholds.difference(allUsedThresholds)
146 log.debug("The following thresholds are unused")
147 log.debug("MU: %s", ", ".join([thr for thr in unusedThresholds if thr.startswith("MU")]))
148 log.debug("EM: %s", ", ".join([thr for thr in unusedThresholds if thr.startswith("EM")]))
149 log.debug("HA: %s", ", ".join([thr for thr in unusedThresholds if thr.startswith("HA")]))
150 log.debug("J: %s", ", ".join([thr for thr in unusedThresholds if thr.startswith("J")]))
151 log.debug("eFEX: %s", ", ".join([thr for thr in unusedThresholds if thr.startswith("e")]))
152 log.debug("jFEX: %s", ", ".join([thr for thr in unusedThresholds if thr.startswith("j")]))
153 log.debug("cTAU: %s", ", ".join([thr for thr in unusedThresholds if thr.startswith("cTAU")]))
154 log.debug("gFEX: %s", ", ".join([thr for thr in unusedThresholds if thr.startswith("g")]))
155
157 from collections import defaultdict as dd
158 from ..Menu.LegacyMenuThresholds import legacyThresholds
159 extraThresholds = dd(list)
160 for item in self.items:
161 for thrName in item.thresholdNames():
162 if thrName[:3] == 'ZB_':
163 thrName = thrName[3:]
164 if thrName[0] not in ('e', 'j', 'g', 'c') and thrName[:2] not in ["MU"] and "TOPO" not in thrName[:4]:
165 if thrName not in legacyThresholds:
166 extraThresholds[thrName].append(item.name)
167
168 for thrName in sorted(extraThresholds.keys()):
169 log.warning("Threshold %s (used by %s) should not be used!", thrName, ",".join(extraThresholds[thrName]))
170
172 if 'MC' not in self.menuInfo.menuBaseName:
173 from collections import defaultdict as dd
174 perfThresholds = dd(list)
175 for item in self.items:
176 for thrName in item.thresholdNames():
177 if 'Perf' in thrName:
178 perfThresholds[thrName].append(item.name)
179 for thrName in sorted(perfThresholds.keys()):
180 raise RuntimeError("Threshold %s (used by %s) should not be used!", thrName, ",".join(perfThresholds[thrName]))
181
182 def checkBoardInputs(self, algo, connDefName, fpgaName):
183 if 'MuCTPi' in connDefName or 'Legacy' in connDefName:
184 return
185
186 boardName = connDefName + fpgaName
187
188 allowedInputs = {}
189 allowedInputs['Topo1Opt0'] = ['MU', 'eEM', 'eTAU', 'gJ', 'gLJ', 'ZeroBiasA'] # TOPO1A, fiber 1 # noqa: E201
190 allowedInputs['Topo1Opt1'] = ['MU', 'eEM', 'eTAU', 'gJ', 'gLJ', ] # TOPO1A, fiber 2 # noqa: E202
191 allowedInputs['Topo1Opt2'] = ['MU', 'eTAU', 'cTAU', 'j', 'cXE', 'gXE', 'gTE', 'gMHT', 'gESPRESSO', 'LArSaturation', 'ZeroBiasB'] # TOPO1B, fiber 1
192 allowedInputs['Topo1Opt3'] = ['MU', 'eTAU', 'cTAU', 'j', 'cXE', 'gXE', 'gTE', 'gMHT', 'gESPRESSO', 'gRISTRETTO', 'LArSaturation', 'ZeroBiasB'] # TOPO1B, fiber 2
193 allowedInputs['Topo2El0'] = ['MU', 'eTAU', 'j', ] # TOPO2, FPGA1 # noqa: E202
194 allowedInputs['Topo2El1'] = [ 'eEM', 'j', ] # TOPO2, FPGA2 # noqa: E201,E202
195 allowedInputs['Topo3El0'] = [ 'eEM', 'eTAU', 'j', ] # noqa: E201,E202 # TOPO3, FPGA1
196 allowedInputs['Topo3El1'] = ['MU', 'eEM', 'eTAU', 'g', ] # TOPO3, FPGA2 noqa: E221
197
198 if boardName not in allowedInputs.keys():
199 raise RuntimeError("Connector name %s not found" % boardName)
200
201 if 'Mult_' in algo.name:
202 if not (any(x in algo.threshold for x in allowedInputs[boardName])):
203 raise RuntimeError("Algorithm %s in board %s with threshold %s not allowed" % (algo.name, boardName, algo.threshold))
204
205 if 'Mult_' not in algo.name:
206 for algoInput in algo.inputs:
207 if not (any(x in algoInput for x in allowedInputs[boardName])):
208 raise RuntimeError("Algorithm %s in board %s with input %s not allowed" % (algo.name, boardName, algoInput))
209
210 def checkL1CaloThresholds(self, thresholds, boardName, connName):
211 fullName = boardName + connName
212
213 allowedInputs = {}
214 allowedInputs['Ctpin7EM1'] = 8 * ['EM']
215 allowedInputs['Ctpin7EM2'] = 8 * ['EM']
216 allowedInputs['Ctpin7TAU1'] = 8 * ['HA']
217 allowedInputs['Ctpin7TAU2'] = 8 * ['HA']
218
219 allowedInputs['Ctpin8JET1'] = 10 * ['J'] # 3-bit JET
220 allowedInputs['Ctpin8JET2'] = 15 * ['J'] # 2-bit JET
221 allowedInputs['Ctpin8EN1'] = 8 * ['TE'] + 8 * ['XE'] + 8 * ['XS']
222 allowedInputs['Ctpin8EN2'] = 8 * ['TE'] + 8 * ['XE']
223
224 invalidThresholds = False
225 for ithr, thr in enumerate(thresholds):
226 try:
227 allowedThr = allowedInputs[fullName][ithr]
228 if thr[:len(allowedThr)] != allowedThr:
229 log.error(f"Threshold {thr} does not match expected type {allowedThr} at position {ithr} in connector {fullName}")
230 invalidThresholds = True
231 except IndexError:
232 log.error(f"Too many thresholds ({len(thresholds)}) provided for connector {fullName}, which only supports {len(allowedInputs[fullName])} thresholds")
233 invalidThresholds = True
234 if invalidThresholds:
235 raise RuntimeError("Incorrect specification of legacy L1Calo thresholds")
236
238 for conn in self.connectors:
239 if conn.ctype == CType.CTPIN:
240 if len(conn.triggerLines) > 31:
241 raise RuntimeError("Too many CTP inputs in %s: %i but a max of 31 are allowed" % (conn.name, len(conn.triggerLines)))
242
243 def checkTOPObits(self):
244 import re
245 pattern = r"topo[1-3](e|$)"
246 for conn in self.connectors:
247 flatindexs = []
248 if re.search(pattern, conn.name.lower()):
249 for tl in conn.triggerLines:
250 for thr in conn.triggerLines[tl]:
251 for thrname in conn.triggerLines[tl][thr]:
252 if thrname.flatindex not in flatindexs:
253 flatindexs.append(thrname.flatindex)
254 else:
255 log.error("TOPO connector %s has duplicate flatindex %i", conn.name, thrname.flatindex)
256 raise RuntimeError("Duplicate flatindex found in TOPO algorithm")
257
259 from collections import namedtuple
260 ctpInput = namedtuple('ctpInput', "name, conn, nbit")
261 ctpInputs = []
262 ctpUnusedInputs = []
263 ctpOutputs = []
264 thrNames = []
265 ctpInputBitSets = dict()
266 ctpInputNameSets = dict()
267 ctpUnusedInputBitSets = dict()
268 ctpUnusedInputNameSets = dict()
269 for item in self.items:
270 ctpOutputs.append(item.name)
271 for thrName in item.thresholdNames():
272 if thrName[:3] == 'ZB_':
273 thrName = thrName[3:]
274 if thrName not in thrNames:
275 thrNames.append(thrName)
276 thrNames_notFound = []
277 for thrName in thrNames:
278 thrName_found = False
279 for conn in self.connectors:
280 if conn.ctype != CType.ELEC:
281 for tl in conn.triggerLines:
282 if thrName == tl.name:
283 ctpInputs.append(ctpInput(name=thrName, conn=conn.name, nbit=tl.nbits))
284 thrName_found = True
285 else:
286 for fpga in conn.triggerLines:
287 for clock in conn.triggerLines[fpga]:
288 for tl in conn.triggerLines[fpga][clock]:
289 if thrName == tl.name:
290 ctpInputs.append(ctpInput(name=thrName, conn=conn.name, nbit=tl.nbits))
291 thrName_found = True
292 if not thrName_found:
293 thrNames_notFound.append(thrName)
294
295 for conn in self.connectors:
296 if conn.ctype != CType.ELEC:
297 for tl in conn.triggerLines:
298 thrName = tl.name
299 if thrName[:3] == 'ZB_':
300 thrName = thrName[3:]
301 usedInput = False
302 for ctpIn in ctpInputs:
303 if thrName == ctpIn.name:
304 usedInput = True
305 if not usedInput:
306 ctpUnusedInputs.append(ctpInput(name=thrName, conn=conn.name, nbit=tl.nbits))
307 else:
308 for fpga in conn.triggerLines:
309 for clock in conn.triggerLines[fpga]:
310 for tl in conn.triggerLines[fpga][clock]:
311 thrName = tl.name
312 if thrName[:3] == 'ZB_':
313 thrName = thrName[3:]
314 usedInput = False
315 for ctpIn in ctpInputs:
316 if thrName == ctpIn.name:
317 usedInput = True
318 if not usedInput:
319 ctpUnusedInputs.append(ctpInput(name=thrName, conn=conn.name, nbit=tl.nbits))
320
321 if len(thrNames_notFound) > 0:
322 log.error("Thresholds [%s] are not found", ",".join(thrNames_notFound))
323 log.error("Input thresholds are [%s]", ",".join([input.name for input in ctpInputs]))
324 raise RuntimeError("Not all input thresholds found!")
325
326 for ctpIn in ctpInputs:
327 thrset = None
328 thrName = ctpIn.name
329 if thrName[:2] in ['EM', 'HA', 'XE', 'TE', 'XS']:
330 thrset = 'legacyCalo'
331 elif thrName[:1] == 'J':
332 thrset = 'legacyCalo'
333 elif thrName[:2] == 'MU':
334 thrset = 'muon'
335 elif thrName[:3] in ['MBT', 'AFP', 'BCM', 'CAL', 'NIM', 'ZDC', 'BPT', 'LUC', 'BMA']:
336 thrset = 'detector'
337 elif thrName[:6] == 'R2TOPO':
338 thrset = 'legacyTopo'
339 elif thrName[:1] in ['e', 'j', 'c', 'g']:
340 thrset = 'topo1'
341 elif thrName[:4] == 'TOPO':
342 if 'Topo2' in ctpIn.conn:
343 thrset = 'topo2'
344 elif 'Topo3' in ctpIn.conn:
345 thrset = 'topo3'
346
347 if thrset not in ctpInputBitSets:
348 ctpInputBitSets[thrset] = 0
349 ctpInputNameSets[thrset] = []
350 if thrName not in ctpInputNameSets[thrset]:
351 ctpInputNameSets[thrset].append(thrName)
352 ctpInputBitSets[thrset] += ctpIn.nbit
353
354 for ctpIn in ctpUnusedInputs:
355 thrset = None
356 thrName = ctpIn.name
357 if thrName[:2] in ['EM', 'HA', 'XE', 'TE', 'XS']:
358 thrset = 'legacyCalo'
359 elif thrName[:1] == 'J':
360 thrset = 'legacyCalo'
361 elif thrName[:2] == 'MU':
362 thrset = 'muon'
363 elif thrName[:3] in ['MBT', 'AFP', 'BCM', 'CAL', 'NIM', 'ZDC', 'BPT', 'LUC']:
364 thrset = 'detector'
365 elif thrName[:6] == 'R2TOPO':
366 thrset = 'legacyTopo'
367 elif thrName[:1] in ['e', 'j', 'c', 'g']:
368 thrset = 'topo1'
369 elif thrName[:4] == 'TOPO':
370 if 'Topo2' in ctpIn.conn:
371 thrset = 'topo2'
372 elif 'Topo3' in ctpIn.conn:
373 thrset = 'topo3'
374
375 if thrset not in ctpUnusedInputBitSets:
376 ctpUnusedInputBitSets[thrset] = 0
377 ctpUnusedInputNameSets[thrset] = []
378 if thrName not in ctpUnusedInputNameSets[thrset]:
379 ctpUnusedInputNameSets[thrset].append(thrName)
380 ctpUnusedInputBitSets[thrset] += ctpIn.nbit
381
382 totalInputs = 0
383 log.info("Check total number of CTP input and output bits:")
384 log.info("Number of output bits: %i", len(ctpOutputs))
385 for thrset in ctpInputBitSets:
386 log.info("Used inputs in %s: %i thresholds and %i bits", thrset, len(ctpInputNameSets[thrset]), ctpInputBitSets[thrset])
387 if thrset is not None:
388 log.debug("Threshold set %s: %s", thrset, ",".join(ctpInputNameSets[thrset]))
389 else:
390 log.warning("Unrecognised CTP input bits: %s", ",".join(ctpInputNameSets[thrset]))
391 totalInputs += ctpInputBitSets[thrset]
392 log.info("Number of used inputs bits: %i", totalInputs)
393 totalUnusedInputs = 0
394 for thrset in ctpUnusedInputBitSets:
395 log.debug("Unused thresholds in %s: %i thresholds and %i bits", thrset, len(ctpUnusedInputNameSets[thrset]), ctpUnusedInputBitSets[thrset])
396 if thrset is not None:
397 log.debug("Unused threshold set %s: %s", thrset, ",".join(ctpUnusedInputNameSets[thrset]))
398 else:
399 log.debug("Unrecognised CTP input bits: %s", ",".join(ctpUnusedInputNameSets[thrset]))
400 totalUnusedInputs += ctpUnusedInputBitSets[thrset]
401 log.debug("Number of un-used inputs bits: %i", totalUnusedInputs)
402
403 # Fail menu generation for menus going to P1:
404 if (totalInputs > Limits.MaxTrigItems or len(ctpOutputs) > Limits.MaxTrigItems):
405 if 'AllCTPIn' in self.menuInfo.menuBaseName:
406 log.warning(f"Input or output bits limit of {Limits.MaxTrigItems} exceeded in the dummy CTP menu -- OK")
407 else:
408 raise RuntimeError("Both the numbers of inputs and outputs need to be not greater than %i in a physics menu!" % Limits.MaxTrigItems)
409
410 # Avoid that L1 item is defined only for BGRP0 as this include also the CALREQ BGRP2 (ATR-24781)
411 def checkBGRP(self):
412 for item in self.items:
413 if len(item.bunchGroups) == 1 and item.bunchGroups[0] == 'BGRP0':
414 raise RuntimeError("L1 item %s is defined with only BGRP0, ie it can trigger also in the CALREQ BGRP2 bunches. Please add another bunch group (ATR-24781)" % item.name)
415 if 'BGRP2' in item.bunchGroups:
416 if isinstance(item.logic, ThrCondition):
417 thrtype = item.logic.threshold().ttype
418 if thrtype in ThrType.CaloTypes():
419 # The LAr Digital Trigger sends an "align frame" to the FEXes in BCID 3500 (in BGRP2)
420 # No trigger can be sent during this align frame, so we block all calo triggers from this BGRP
421 raise RuntimeError(f"L1 item {item.name} with threshold type {thrtype} is in CALREQ BGRP2. This is not allowed!")
422
424 all_ctpin = []
425 connected_boards = []
426 for layout, connectors in self.ctp.inputConnectors.items():
427 for _, contents in connectors.items():
428 if layout == 'ctpin':
429 # Ignore empty
430 connected_boards += [board for board in contents.values() if board]
431 else:
432 connected_boards.append(contents)
433
434 for conn_name in connected_boards:
435 conn = self.connectors[conn_name]
436 if conn.ctype != CType.ELEC:
437 for tl in conn.triggerLines:
438 thrName = tl.name
439 if thrName[:3] == 'ZB_':
440 thrName = thrName[3:]
441 all_ctpin.append(thrName)
442 else:
443 for fpga in conn.triggerLines:
444 for clock in conn.triggerLines[fpga]:
445 for tl in conn.triggerLines[fpga][clock]:
446 thrName = tl.name
447 if thrName[:3] == 'ZB_':
448 thrName = thrName[3:]
449 all_ctpin.append(thrName)
450
451 for item in self.items:
452 for thrName in item.thresholdNames():
453 if thrName[:3] == 'ZB_':
454 thrName = thrName[3:]
455 if thrName not in all_ctpin:
456 raise RuntimeError(
457 f'checkItemsHaveInputs: Threshold {thrName} used by {item.name} is not on a board connected to CTP')
458
460 requiredItems = [
461 "L1_BCM_2A_FIRSTINTRAIN",
462 "L1_BCM_2C_FIRSTINTRAIN"
463 ]
464 missingItems = [item for item in requiredItems if self.items.items[item].monitorsHF == 0]
465 if missingItems:
466 raise RuntimeError(
467 f'The HF monitoring flags for {", ".join(missingItems)} are missing')
MenuItem getItem(self, str name)
addGlobalAlgo(self, GlobalAlgorithm algo)
checkL1CaloThresholds(self, thresholds, boardName, connName)
addThreshold(self, Threshold threshold)
checkBoardInputs(self, algo, connDefName, fpgaName)
__init__(self, *, MenuMetaInfo menuInfo, AthConfigFlags flags)
Definition L1MenuRun4.py:65
MenuMetaInfo.MenuType menuPurpose
Definition L1MenuRun4.py:50
__init__(self, str menuFullName, *, MenuType menuPurpose, str menuBaseName, str menuFile, str menuInputFile, int run=4)
Definition L1MenuRun4.py:45
STL class.