1
2 """Collection of array of timelines"""
3
4 from PyFoam.Error import error
5 from math import ceil
6 from copy import deepcopy
7 from threading import Lock
8 import sys
9
10 from PyFoam.ThirdParty.six import print_,iteritems
11
12 transmissionLock=Lock()
13
15 """Mean value of a and b"""
16 return 0.5*(a+b)
17
19 """Absolute Maximum of a and b with the sign preserved"""
20 if a<0. or b<0.:
21 return min(a,b)
22 else:
23 return max(a,b)
24
26 """Collects references to TimeLineCollection objects"""
27
28 nr=1
29
32
36
37 - def add(self,line,nr=None):
48
54
56 """Makes sure that the data about the timelines is to be transfered via XMLRPC"""
57
58 transmissionLock.acquire()
59
60 lst={}
61 for i,p in iteritems(self.lines):
62 slaves=[]
63 for s in p.slaves:
64 slaves.append(s.lineNr)
65
66 lst[str(i)]={ "nr" : i,
67 "times" : deepcopy(p.times),
68 "values": deepcopy(p.values),
69 "lastValid" : deepcopy(p.lastValid),
70 "slaves": slaves }
71
72 transmissionLock.release()
73
74 return lst
75
77 """Looks through all the registered lines and replaces integers with
78 the actual registered line"""
79 for i,p in iteritems(self.lines):
80 if len(p.slaves)>0:
81 slaves=[]
82 for s in p.slaves:
83 if type(s)==int:
84 try:
85 slaves.append(self.lines[s])
86 except KeyError:
87 error(s,"not a known data set:",list(self.lines.keys()))
88 else:
89 slaves.append(s)
90 p.slaves=slaves
91
92 _allLines=TimeLinesRegistry()
93
96
98
99 possibleAccumulations=["first", "last", "min", "max", "average", "sum","count"]
100
101 - def __init__(self,
102 deflt=0.,
103 extendCopy=False,
104 splitThres=None,
105 splitFun=None,
106 noEmptyTime=True,
107 advancedSplit=False,
108 preloadData=None,
109 accumulation="first",
110 registry=None):
111 """@param deflt: default value for timelines if none has been defined before
112 @param extendCopy: Extends the timeline by cpying the last element
113 @param splitThres: Threshold after which the number of points is halved
114 @param splitFun: Function that is used for halving. If none is specified the mean function is used
115 @param noEmptyTime: if there is no valid entry no data is stored for this time
116 @param advancedSplit: Use another split algorithm than one that condenses two values into one
117 @param preloadData: a dictionary with a dictionary to initialize the values
118 @param accumulation: if more than one value is given at any time-step, how to accumulate them (possible values: "first", "last", "min", "max", "average", "sum","count")
119 """
120
121 self.cTime=None
122 self.times=[]
123 self.values={}
124 self.lastValid={}
125 self.setDefault(deflt)
126 self.setExtend(extendCopy)
127 self.thres=None
128 self.fun=None
129
130 if not (accumulation in TimeLineCollection.possibleAccumulations):
131 error("Value",accumulation,"not in list of possible values:",TimeLineCollection.possibleAccumulations)
132 self.accumulation=accumulation
133 self.accumulations={}
134 self.occured={}
135
136 self.slaves=[]
137
138 self.setSplitting(splitThres=splitThres,
139 splitFun=splitFun,
140 advancedSplit=advancedSplit,
141 noEmptyTime=noEmptyTime)
142
143 self.lineNr=None
144 if preloadData:
145 self.times=preloadData["times"]
146 self.values=preloadData["values"]
147 self.slaves=preloadData["slaves"]
148 self.lineNr=int(preloadData["nr"])
149 if "lastValid" in preloadData:
150 self.lastValid=preloadData["lastValid"]
151 else:
152 self.resetValid(val=True)
153
154 if registry==None:
155 registry=allLines()
156 self.lineNr=registry.add(self,self.lineNr)
157
159 """Helper function that resets the information whether the last entry is valid"""
160 self.lastValid={}
161 for n in self.values:
162 self.lastValid[n]=val
163 for s in self.slaves:
164 s.resetValid(val=val)
165
167 """Helper function that gets the number of valid values"""
168 nr=list(self.lastValid.values()).count(True)
169 for s in self.slaves:
170 nr+=s.nrValid()
171 return nr
172
174 """Adds a slave time-line-collection"""
175 self.slaves.append(slave)
176 slave.setSplitting(splitThres=self.thres,
177 splitFun=self.fun,
178 advancedSplit=self.advancedSplit,
179 noEmptyTime=self.noEmptyTime)
180
188
189 - def setSplitting(self,splitThres=None,splitFun=None,advancedSplit=False,noEmptyTime=True):
190 """Sets the parameters for splitting"""
191
192 self.advancedSplit = advancedSplit
193 if self.advancedSplit:
194 self.splitLevels = []
195 if splitThres:
196 self.thres=splitThres
197 if (self.thres % 2)==1:
198 self.thres+=1
199
200 if splitFun:
201 self.fun=splitFun
202 elif not self.fun:
203 self.fun=mean
204
205 for s in self.slaves:
206 s.setSplitting(splitThres=splitThres,splitFun=splitFun,advancedSplit=advancedSplit,noEmptyTime=noEmptyTime)
207
208 self.noEmptyTime=noEmptyTime
209
211 """@param deflt: default value to be used"""
212 self.defaultValue=float(deflt)
213
214 - def setExtend(self,mode):
215 """@param mode: whether or not to extend the timeline by copying or setting the default value"""
216 self.extendCopy=mode
217
219 """Number of elements in timelines"""
220 return len(self.times)
221
222 - def setTime(self,time,noLock=False,forceAppend=False):
223 """Sets the time. If time is new all the timelines are extended
224 @param time: the new current time
225 @param noLock: do not acquire the lock that ensures consistent data transmission"""
226
227 if not noLock:
228 transmissionLock.acquire()
229
230 dTime=float(time)
231
232 if dTime!=self.cTime:
233 self.cTime=dTime
234 append=True
235 if self.noEmptyTime and not forceAppend:
236 if self.nrValid()==0:
237 append=False
238 if append:
239 self.times.append(self.cTime)
240 for v in list(self.values.values()):
241 if len(v)>0 and self.extendCopy:
242 val=v[-1]
243 else:
244 val=self.defaultValue
245 v.append(val)
246 else:
247 if len(self.times)>0:
248 self.times[-1]=self.cTime
249
250 self.resetValid()
251
252 if self.thres and append:
253 try:
254 if len(self.times)>=self.thres:
255 if self.advancedSplit:
256
257
258 if len(self.splitLevels)<len(self.times):
259 self.splitLevels+=[0]*(len(self.times)-len(self.splitLevels))
260 splitTill=int(len(self.times)*0.75)
261 if self.splitLevels[splitTill]!=0:
262
263 splitTill=self.splitLevels.index(0)
264 splitFrom=0
265 maxLevel=self.splitLevels[0]
266 for l in range(maxLevel):
267 try:
268 li=self.splitLevels.index(l)
269 if li>=0 and li<splitTill/2:
270 splitFrom=li
271 break
272 except ValueError:
273 pass
274 window=4
275 if ((splitTill-splitFrom)/window)!=0:
276 splitTill=splitFrom+window*int(ceil((splitTill-splitFrom)/float(window)))
277
278
279 times=self.times[:splitFrom]
280 levels=self.splitLevels[:splitFrom]
281 values={}
282 for k in self.values:
283 values[k]=self.values[k][:splitFrom]
284
285 for start in range(splitFrom,splitTill,window):
286 end=start+window-1
287 sTime=self.times[start]
288 eTime=self.times[end]
289 times+=[sTime,(eTime-sTime)*(2./3)+sTime]
290 levels+=[self.splitLevels[start]+1,self.splitLevels[end]+1]
291 for k in self.values:
292 minV=self.values[k][start]
293 minI=0
294 maxV=self.values[k][start]
295 maxI=0
296 for j in range(1,window):
297 val=self.values[k][start+j]
298 if val>maxV:
299 maxV=val
300 maxI=j
301 if val<minV:
302 minV=val
303 minI=j
304 if minI<maxI:
305 values[k]+=[minV,maxV]
306 else:
307 values[k]+=[maxV,minV]
308 firstUnsplit=int(splitTill/window)*window
309 self.times=times+self.times[firstUnsplit:]
310 self.splitLevels=levels+self.splitLevels[firstUnsplit:]
311
312 for k in self.values:
313 self.values[k]=values[k]+self.values[k][firstUnsplit:]
314 assert len(self.times)==len(self.values[k])
315 else:
316 self.times=self.split(self.times,min)
317 for k in list(self.values.keys()):
318 self.values[k]=self.split(self.values[k],self.fun)
319 except Exception:
320 e = sys.exc_info()[1]
321 err, detail, tb = sys.exc_info()
322 print_(e)
323 error("Problem splitting",e)
324
325 self.occured={}
326
327 for s in self.slaves:
328 s.setTime(time,noLock=True,forceAppend=append)
329
330 if not noLock:
331 transmissionLock.release()
332
333 - def split(self,array,func):
334 """Makes the array smaller by joining every two points
335 @param array: the field to split
336 @param func: The function to use for joining two points"""
337
338 newLen=len(array)/2
339 newArray=[0.]*newLen
340
341 for i in range(newLen):
342 newArray[i]=func(array[2*i],array[2*i+1])
343
344 return newArray
345
347 """@return: A list of the time values"""
348 tm=None
349 if name in self.values or name==None:
350 tm=self.times
351 else:
352 for s in self.slaves:
353 if name in s.values:
354 tm=s.times
355 break
356 return tm
357
365
379
381 """Sets the value of the last element in a timeline
382 @param name: name of the timeline
383 @param value: the last element"""
384
385 val=float(value)
386
387 transmissionLock.acquire()
388
389 data=self.getValues(name)
390 if len(data)>0:
391 accu=self.accumulation
392 if name not in self.occured:
393 if accu=="count":
394 newValue=1
395 else:
396 newValue=val
397 self.occured[name]=1
398 else:
399 oldValue=data[-1]
400 n=self.occured[name]
401 self.occured[name]+=1
402 if name in self.accumulations:
403 accu=self.accumulations[name]
404 if accu=="first":
405 newValue=oldValue
406 elif accu=="last":
407 newValue=val
408 elif accu=="max":
409 newValue=max(val,oldValue)
410 elif accu=="min":
411 newValue=min(val,oldValue)
412 elif accu=="sum":
413 newValue=val+oldValue
414 elif accu=="average":
415 newValue=(n*oldValue+val)/(n+1)
416 elif accu=="count":
417 newValue=n+1
418 else:
419 error("Unimplemented accumulator",accu,"for",name)
420
421 data[-1]=newValue
422
423 self.lastValid[name]=True
424
425 transmissionLock.release()
426
446
448 """Return a dictionary with the latest values from all data sets"""
449
450 result={}
451
452 for n,d in iteritems(self.values):
453 if self.lastValid[n] or len(d)<2:
454 result[n]=d[-1]
455 else:
456 result[n]=d[-2]
457
458 return result
459
460
461