The Gaudi Framework  master (53dee381)
Loading...
Searching...
No Matches
precedence.py
Go to the documentation of this file.
11import json
12import os
13import random
14import sys
15
16# FIXME: workaround for the old version of networkx in LCG 100
17import warnings
18
19warnings.filterwarnings("ignore", message='"is" with a literal', category=SyntaxWarning)
20
21import networkx as nx
22from Configurables import CPUCruncher, Gaudi__Sequencer
23
24from Gaudi.Configuration import INFO
25
26
27def _buildFilePath(filePath):
28 if not os.path.exists(filePath):
29 __fullFilePath__ = os.path.realpath(
30 os.path.join(
31 os.environ.get("ENV_PROJECT_SOURCE_DIR", ""),
32 "GaudiHive",
33 "data",
34 filePath,
35 )
36 )
37 if not os.path.exists(__fullFilePath__):
38 __fullFilePath__ = os.path.realpath(
39 os.path.join(
40 os.environ.get("ENV_PROJECT_SOURCE_DIR", ""),
41 "Gaudi",
42 "GaudiHive",
43 "data",
44 filePath,
45 )
46 )
47 if not os.path.exists(__fullFilePath__):
48 print(
49 f"\nERROR: invalid file path '{filePath}'. "
50 "It must be either absolute, or relative to "
51 "'$ENV_PROJECT_SOURCE_DIR/GaudiHive/data/' or to "
52 "'$ENV_PROJECT_SOURCE_DIR/Gaudi/GaudiHive/data/'."
53 )
54 sys.exit(1)
55 else:
56 __fullFilePath__ = filePath
57
58 return __fullFilePath__
59
60
62 """A class to manage uniform algorithm timing"""
63
64 def __init__(self, avgRuntime, varRuntime=0):
65 self.avgRuntime = avgRuntime
66 self.varRuntime = varRuntime
67
68 def get(self, algoName=""):
69 """Get time and its variance (in a tuple) for a given algorithm name"""
70
71 return self.avgRuntime, self.varRuntime
72
73
75 """A class to manage real algorithm timing"""
76
77 def __init__(self, path, defaultTime, factor=1):
78 """
79 defaultTime -- run time, assigned to an algorithm if no time is found in provided timing library
80 (and it will also be scaled by the 'factor' argument)
81 """
82
83 self.path = os.path.realpath(_buildFilePath(path))
84 self.factor = factor
85 self.defaultTime = defaultTime # typically 0.05s
86 self.varRuntime = 0
87
88 self.file = open(self.path)
89 self.timings = json.load(self.file)
90
91 def get(self, algoName=""):
92 """Get time for a given algorithm name"""
93
94 if algoName in self.timings:
95 time = float(self.timings[algoName])
96 else:
97 capAlgoName = algoName[0].upper() + algoName[1 : len(algoName)]
98
99 if capAlgoName in self.timings:
100 time = float(self.timings[capAlgoName])
101 else:
102 time = self.defaultTime
103 print(
104 f"WARNING: Timing for {algoName} (or {capAlgoName}) not found in the provided library, using default one: {time}"
105 )
106
107 time = time * self.factor
108
109 return time, self.varRuntime
110
111
113 def __init__(self, value):
114 self.value = value
115
116 def get(self):
117 return self.value
118
119
121 """Provides randomly ordered set of boolean values with requested proportion of True and False."""
122
123 def __init__(self, pattern, seed=None):
124 """
125 Keyword arguments:
126 pattern -- either a dictionary describing proportion of True and False (e.g., {True:5,False:15}), or
127 a list/tuple containing a pattern to be used as-is (e.g., [False,True,True,False])
128 seed -- an int, long or other hashable object to initialize random number generator (passed to random.shuffle as-is)
129 """
130
131 if isinstance(pattern, dict):
132 proportion = pattern
133
134 length = proportion[True] + proportion[False]
135 if length <= 0:
136 raise ValueError(f"Wrong set length requested: {length}")
137
138 self.pattern = [False for i in range(proportion[False])] + [
139 True for i in range(proportion[True])
140 ]
141
142 if seed is not None:
143 random.seed(seed)
144
145 random.shuffle(self.pattern)
146
147 elif isinstance(pattern, (list, tuple)):
148 self.pattern = pattern
149 else:
150 raise "ERROR: unknown pattern type"
151
153
154 def _create_generator(self, pattern):
155 yield from pattern
156
157 def get(self):
158 return next(self.generator)
159
160 def get_pattern(self):
161 return self.pattern
162
163
165 """Constructs the sequence tree of CPUCrunchers with provided control flow and data flow precedence rules."""
166
167 unique_sequencers = []
168 dupl_seqs = {}
169 OR_sequencers = []
170 unique_algos = []
171 dupl_algos = {}
172
173 unique_data_objects = []
174
176 self,
177 timeValue,
178 BlockingBoolValue,
179 sleepFraction,
180 cfgPath,
181 dfgPath,
182 topSequencer,
183 showStat=False,
184 timeline=False,
185 outputLevel=INFO,
186 cardinality=1,
187 ):
188 """
189 Keyword arguments:
190 timeValue -- timeValue object to set algorithm execution time
191 BlockingBoolValue -- *BooleanValue object to set whether an algorithm has to experience CPU-blocking execution
192 cfgPath -- relative to $ENV_PROJECT_SOURCE_DIR/GaudiHive/data path to GRAPHML file with control flow dependencies
193 dfgPath -- relative to $ENV_PROJECT_SOURCE_DIR/GaudiHive/data path to GRAPHML file with data flow dependencies
194 showStat -- print out statistics on precedence graph
195 """
196
197 self.cardinality = cardinality
198 self.timeValue = timeValue
199 self.BlockingBoolValue = BlockingBoolValue
200 self.sleepFraction = sleepFraction
201
202 self.cfg = nx.read_graphml(_buildFilePath(cfgPath))
203 self.dfg = nx.read_graphml(_buildFilePath(dfgPath))
204
205 self.enableTimeline = timeline
206
207 self.outputLevel = outputLevel
208
209 # Generate control flow part
210 self.sequencer = self._generate_sequence(topSequencer)
211
212 if showStat:
213 print("\n===== Statistics on Algorithms =====")
214 print(
215 "Total number of algorithm nodes: ",
216 len(self.unique_algos)
217 + sum([self.dupl_algos[i] - 1 for i in self.dupl_algos]),
218 )
219 print("Number of unique algorithms: ", len(self.unique_algos))
220 print(
221 " -->",
222 len(self.dupl_algos),
223 "of them being re-used with the following distribution: ",
224 [self.dupl_algos[i] for i in self.dupl_algos],
225 )
226 # pprint.pprint(dupl_algos)
227
228 print("\n===== Statistics on Sequencers =====")
229 print(
230 "Total number of sequencers: ",
231 len(self.unique_sequencers)
232 + sum([self.dupl_seqs[i] - 1 for i in self.dupl_seqs]),
233 )
234 print("Number of unique sequencers: ", len(self.unique_sequencers))
235 print(
236 " -->",
237 len(self.dupl_seqs),
238 "of them being re-used with the following distribution: ",
239 [self.dupl_seqs[i] for i in self.dupl_seqs],
240 )
241 # pprint.pprint(dupl_seqs)
242 print("Number of OR-sequencers: ", len(self.OR_sequencers))
243
244 print("\n===== Statistics on DataObjects =====")
245 print("Number of unique DataObjects: ", len(self.unique_data_objects))
246 # pprint.pprint(self.unique_data_objects)
247 print()
248
249 def get(self):
250 return self.sequencer
251
252 def _declare_data_deps(self, algo_name, algo):
253 """Declare data inputs and outputs for a given algorithm."""
254
255 # Declare data inputs
256 for inNode, outNode in self.dfg.in_edges(algo_name):
257 dataName = inNode
258 if dataName not in self.unique_data_objects:
259 self.unique_data_objects.append(dataName)
260
261 if dataName not in algo.inpKeys:
262 algo.inpKeys.append(dataName)
263
264 # Declare data outputs
265 for inNode, outNode in self.dfg.out_edges(algo_name):
266 dataName = outNode
267 if dataName not in self.unique_data_objects:
268 self.unique_data_objects.append(dataName)
269
270 if dataName not in algo.outKeys:
271 algo.outKeys.append(dataName)
272
273 def _generate_sequence(self, name, seq=None):
274 """Assemble the tree of sequencers."""
275
276 if not seq:
277 seq = Gaudi__Sequencer(name, ShortCircuit=False)
278
279 for n in self.cfg[name]:
280 # extract entity name and type
281 algo_name = n.split("/")[1] if "/" in n else n
282
283 if "type" in self.cfg.nodes[n]:
284 # first rely on explicit type, if given
285 algo_type = self.cfg.nodes[n].get("type")
286 else:
287 # if the type is not given explicitly, try to extract it from entity name,
288 # and, if unsuccessful, assume it is an algorithm
289 algo_type = n.split("/")[0] if "/" in n else "Algorithm"
290
291 if algo_type in ["GaudiSequencer", "AthSequencer", "ProcessPhase"]:
292 if algo_name in ["RecoITSeq", "RecoOTSeq", "RecoTTSeq"]:
293 continue
294
295 if n not in self.unique_sequencers:
296 self.unique_sequencers.append(n)
297 else:
298 if n not in self.dupl_seqs:
299 self.dupl_seqs[n] = 2
300 else:
301 self.dupl_seqs[n] += 1
302
303 seq_daughter = Gaudi__Sequencer(algo_name, OutputLevel=INFO)
304 if self.cfg.nodes[n].get("ModeOR") == "True":
305 self.OR_sequencers.append(n)
306 seq_daughter.ModeOR = True
307 # if self.cfg.nodes[n].get('Lazy') == 'False':
308 # print "Non-Lazy - ", n
309 seq_daughter.ShortCircuit = False
310 if seq_daughter not in seq.Members:
311 seq.Members += [seq_daughter]
312 # iterate deeper
313 self._generate_sequence(n, seq_daughter)
314 else:
315 # rndname = ''.join(random.choice(string.lowercase) for i in range(5))
316 # if algo_name in unique_algos: algo_name = algo_name + "-" + rndname
317 if n not in self.unique_algos:
318 self.unique_algos.append(n)
319 else:
320 if n not in self.dupl_algos:
321 self.dupl_algos[n] = 2
322 else:
323 self.dupl_algos[n] += 1
324
325 avgRuntime, varRuntime = self.timeValue.get(algo_name)
326
327 algo_daughter = CPUCruncher(
328 algo_name,
329 Cardinality=self.cardinality,
330 OutputLevel=self.outputLevel,
331 varRuntime=varRuntime,
332 avgRuntime=avgRuntime,
333 SleepFraction=self.sleepFraction
334 if self.BlockingBoolValue.get()
335 else 0.0,
336 Timeline=self.enableTimeline,
337 )
338
339 self._declare_data_deps(algo_name, algo_daughter)
340
341 if algo_daughter not in seq.Members:
342 seq.Members += [algo_daughter]
343
344 return seq
A class that implements a search for prime numbers.
Definition CPUCruncher.h:30
__init__(self, timeValue, BlockingBoolValue, sleepFraction, cfgPath, dfgPath, topSequencer, showStat=False, timeline=False, outputLevel=INFO, cardinality=1)
_declare_data_deps(self, algo_name, algo)
_generate_sequence(self, name, seq=None)
__init__(self, path, defaultTime, factor=1)
Definition precedence.py:77
get(self, algoName="")
Definition precedence.py:91
__init__(self, pattern, seed=None)
get(self, algoName="")
Definition precedence.py:68
__init__(self, avgRuntime, varRuntime=0)
Definition precedence.py:64
_buildFilePath(filePath)
Definition precedence.py:27