1
2 """Working with a directory of samples"""
3
4 from os import path,listdir
5 from PyFoam.Error import error
6 import math
7 import re
8
9 from PyFoam.Basics.SpreadsheetData import SpreadsheetData
10
12 """A directory of sampled times"""
13
14 - def __init__(self,
15 case,
16 dirName="samples",
17 postfixes=[],
18 prefixes=[],
19 valueNames=None,
20 linePattern=None,
21 namesFromFirstLine=False,
22 needsExtension=True):
23 """@param case: The case directory
24 @param dirName: Name of the directory with the samples
25 @param postfixes: list of possible extensions to a field name of the form
26 name_postfix to help splitting such field names.
27 @param prefixes: list of possible extensions to a field name of the form
28 prefix_name to help splitting such field names
29 @param valueNames: List of value names. If specified then the classes do
30 not try to determine the names automatically
31 @param linePattern: Regular expression to determine the name of the line
32 from the filename. The first group in the expression is the name. If unset
33 the linename is determined automatically
34 @param needsExtension: whether a file needs an extension"""
35
36 self.dir=path.join(case,dirName)
37 self.times=[]
38
39 self.__defaultNames=valueNames
40 self.__linePattern=linePattern
41 self.__needsExtension=needsExtension
42 self.__namesFromFirstLine=namesFromFirstLine
43
44 self.prefixes=prefixes
45 self.postfixes=postfixes
46
47 for d in listdir(self.dir):
48 if path.isdir(path.join(self.dir,d)):
49 try:
50 float(d)
51 self.times.append(d)
52 except ValueError:
53 pass
54
55 self.times.sort(key=float)
56
58 return len(self.times)
59
61 for t in self.times:
62 yield SampleTime(self.dir,
63 t,
64 prefixes=self.prefixes,
65 postfixes=self.postfixes,
66 valueNames=self.__defaultNames,
67 namesFromFirstLine=self.__namesFromFirstLine,
68 linePattern=self.__linePattern,
69 needsExtension=self.__needsExtension)
70
72 if time in self:
73 return SampleTime(self.dir,
74 time,
75 prefixes=self.prefixes,
76 postfixes=self.postfixes,
77 valueNames=self.__defaultNames,
78 namesFromFirstLine=self.__namesFromFirstLine,
79 linePattern=self.__linePattern,
80 needsExtension=self.__needsExtension)
81 else:
82 raise KeyError(time)
83
85 return time in self.times
86
99
112
113 - def getData(self,
114 line=None,
115 value=None,
116 time=None,
117 note="",
118 scale=(1,1),
119 offset=(0,0)):
120 """Get Sample sets
121 @param line: name of the line. All
122 if unspecified
123 @param value: name of the sampled value. All
124 if unspecified
125 @param time: times for which the samples are to be got. All
126 if unspecified
127 @param note: A short annotation (for plots)
128 @param scale: pair of factors with which the data is scaled when being plotted
129 @param offset: pair of offsets"""
130
131 if line==None:
132 line=self.lines()
133 if value==None:
134 value=list(self.values())
135 if time==None:
136 time=self.times
137
138 sets=[]
139
140 for t in time:
141 for l in line:
142 for v in value:
143 try:
144 d=self[t][(l,v)]
145 if d==None:
146 continue
147 d.note=note
148 d.scale=scale
149 d.offset=offset
150 sets.append(d)
151 except KeyError:
152 pass
153
154 return sets
155
157 """A directory with one sampled time"""
158
159 - def __init__(self,
160 sDir,
161 time,
162 postfixes=[],
163 prefixes=[],
164 valueNames=None,
165 namesFromFirstLine=False,
166 linePattern=None,
167 needsExtension=True):
168 """@param sDir: The sample-dir
169 @param time: the timename
170 @param postfixes: list of possible extensions to a field name of the form
171 name_postfix to help splitting such field names.
172 @param prefixes: list of possible extensions to a field name of the form
173 prefix_name to help splitting such field names"""
174
175 self.dir=path.join(sDir,time)
176 self.lines=[]
177 self.values=[]
178
179 self.prefixes=prefixes
180 self.postfixes=postfixes
181
182 self.__valueNames=None
183 self.__defaultValueNames=valueNames
184 self.__linePattern=linePattern
185 self.__namesFromFirstLine=namesFromFirstLine
186
187 for f in listdir(self.dir):
188 if f[0]=='.' or f[-1]=='~' or (f.find(".")<0 and needsExtension):
189 continue
190 nm=self.extractLine(f)
191 if nm==None:
192 continue
193 vals=self.extractValues(f)
194 if nm not in self.lines:
195 self.lines.append(nm)
196 for v in vals:
197 if v not in self.values:
198 self.values.append(v)
199
200 self.lines.sort()
201 self.values.sort()
202
203 self.cache={}
204
206 """Extract the name of the line from a filename"""
207 if self.__linePattern:
208 expr=re.compile(self.__linePattern)
209 try:
210 return expr.match(fName).groups(1)[0]
211 except AttributeError:
212 return None
213 else:
214 return fName.split("_")[0]
215
217 """Extracts the names of the contained Values from a filename"""
218
219 if self.__defaultValueNames:
220 self.__valueNames=self.__defaultValueNames[:]
221 return self.__valueNames
222
223 if self.__namesFromFirstLine:
224 line=open(path.join(self.dir,fName)).readline().split()
225 if line[0]!="#":
226 error("First line of",path.join(self.dir,fName),
227 "does not start with a '#'")
228 return line[2:]
229
230 def preUnder(m):
231 return "&"+m.group(1)+m.group(2)
232 def postUnder(m):
233 return m.group(1)+m.group(2)+"&"
234
235 for p in self.prefixes:
236 fName=re.sub("([_&.]|^)("+p+")_",postUnder,fName)
237 for p in self.postfixes:
238 fName=re.sub("_("+p+")([_&.]|$)",preUnder,fName)
239
240 self.__valueNames=[]
241 try:
242 tmp=fName.split("_")[1:]
243 tmp[-1]=tmp[-1].split(".")[0]
244
245 for t in tmp:
246 self.__valueNames.append(t.replace("&","_"))
247 except IndexError:
248 pass
249
250 return self.__valueNames
251
253 """Get the data for a value on a specific line
254 @param key: A tuple with the line-name and the value-name
255 @returns: A SampleData-object"""
256
257 if key in self.cache:
258 return self.cache[key]
259
260 line,val=key
261 if line not in self.lines or val not in self.values:
262 raise KeyError(key)
263
264 fName=None
265
266 for f in listdir(self.dir):
267 if line==self.extractLine(f) and val in self.extractValues(f):
268 fName=f
269 break
270
271 if fName==None:
272 error("Can't find a file for the line",line,"and the value",val,"in the directory",self.dir)
273
274 first=True
275 coord=[]
276 data=[]
277 index=None
278
279 for l in open(path.join(self.dir,fName)).readlines():
280 if l.strip()[0]=='#':
281 continue
282
283 tmp=l.split()
284 if self.__defaultValueNames:
285 if len(tmp)!=len(self.__defaultValueNames)+1:
286 error("Number of items in line",l,
287 "is not consistent with predefined name",
288 self.__defaultValueNames)
289 if first:
290 first=False
291 vector,index=self.determineIndex(fName,val,tmp)
292
293 coord.append(float(tmp[0]))
294 try:
295 if vector:
296 data.append(tuple(map(float,tmp[index:index+3])))
297 else:
298 data.append(float(tmp[index]))
299 except IndexError:
300 raise KeyError(key)
301
302 if index!=None:
303 self.cache[key]=SampleData(fName=path.join(self.dir,fName),
304 name=val,
305 line=self.extractLine(fName),
306 index=index,
307 coord=coord,
308 data=data)
309
310 return self.cache[key]
311 else:
312 return None
313
315 """Determines the index of the data from the filename and a dataset
316 @param fName: name of the file
317 @param vName: Name of the quantity
318 @param data: A list with the data
319 @returns: A tuple of a boolean (whether the data is supposed to be
320 a vector or a scalar) and an integer (the index of the data set -
321 or the first component of the vector"""
322
323 vals=self.extractValues(fName)
324
325 if len(vals)+1==len(data):
326 vector=False
327 elif len(vals)*3+1==len(data):
328 vector=True
329 else:
330 error("The data in file",fName,"is neither vector nor scalar:",data)
331
332 index=vals.index(vName)
333 if vector:
334 index=index*3+1
335 else:
336 index=index+1
337
338 return vector,index
339
341 """Data from a sample-set"""
342
343 - def __init__(self,
344 fName,
345 name,
346 line,
347 index,
348 coord,
349 data,
350 note="",
351 scale=(1,1),
352 offset=(0,0)):
353 """@param fName: Name of the file
354 @param name: Name of the value
355 @param index: Index of the data in the file
356 @param coord: Values that identify the data (the location)
357 @param data: The actual data
358 @param scale: pair of factors with which the data is scaled when being plotted
359 @param offset: pair of offsets"""
360
361 self.file=fName
362 self.coord=coord
363 self.data=data
364 self.name=name
365 self.__line=line
366 self.index=index
367 self.note=note
368 self.scale=scale
369 self.offset=offset
370
372 if self.isVector():
373 vect=" (vector)"
374 else:
375 vect=""
376
377 return "SampleData of %s%s on %s at t=%s " % (self.name,vect,self.line(),self.time())
378
380 """Get the line of the sample"""
381 return self.__line
382
384 """Get the time of the sample (as a string)"""
385 return path.basename(path.dirname(self.file))
386
388 """Is this vector or scalar data?"""
389 if type(self.data[0])==tuple:
390 return True
391 else:
392 return False
393
394 - def range(self,component=None):
399
401 """Range of the data domain"""
402 return (min(self.coord),max(self.coord))
403
405 """Return the data as a number of single scalars.
406 @param component: If None for vectors the absolute value is taken.
407 else the number of the component"""
408
409 if self.isVector():
410 data=[]
411 if component==None:
412 for d in self.data:
413 data.append(math.sqrt(d[0]*d[0]+d[1]*d[1]+d[2]*d[2]))
414 else:
415 if component<0 or component>=len(self.data[0]):
416 error("Requested component",component,"does not fit the size of the data",len(self.data[0]))
417 for d in self.data:
418 data.append(d[component])
419 return data
420 else:
421 return self.data
422
423 - def __call__(self,
424 scaleX=1.,
425 scaleData=1,
426 offsetData=0,
427 offsetX=0):
428 """Return the data as SpreadsheetData-object"""
429
430 data=[]
431 if self.isVector():
432 for i,c in enumerate(self.coord):
433 data.append([scaleX*c+offsetX]+[scaleData*v+offsetData for v in self.data[i]])
434 else:
435 for i,c in enumerate(self.coord):
436 data.append([scaleX*c+offsetX,scaleData*self.data[i]+offsetData])
437
438 names=["coord"]
439 if self.isVector():
440 names+=[self.name+"_x",self.name+"_y",self.name+"_z"]
441 else:
442 names.append(self.name)
443
444 return SpreadsheetData(data=data,
445 names=names,
446 title="%s_t=%s" % (self.line(),self.time()))
447
448
449