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