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