1 """
2 Application class that implements pyFoamComparator
3 """
4
5 import sys
6 import re
7 import string
8 from xml.dom.minidom import parse
9 import xml.dom
10 from os import path
11
12 from PyFoam.Error import error
13 from PyFoam.Basics.Utilities import execute
14 from PyFoam.Execution.AnalyzedRunner import AnalyzedRunner
15 from PyFoam.Execution.ConvergenceRunner import ConvergenceRunner
16 from PyFoam.Execution.BasicRunner import BasicRunner
17 from PyFoam.Execution.UtilityRunner import UtilityRunner
18 from PyFoam.RunDictionary.ParsedParameterFile import ParsedParameterFile
19 from PyFoam.LogAnalysis.BoundingLogAnalyzer import BoundingLogAnalyzer
20 from PyFoam.RunDictionary.SolutionDirectory import SolutionDirectory
21
22 from PyFoamApplication import PyFoamApplication
23
26 description="""
27 Reads an XML-file that specifies a base case and a parameter-variation
28 and executes all the variations of that case
29 """
30
31 PyFoamApplication.__init__(self,description=description,usage="%prog [options] <xmlfile>",nr=1,interspersed=True)
32
34 self.parser.add_option("--test",action="store_true",default=False,dest="test",help="Only does the preparation steps but does not execute the actual solver an the end")
35 self.parser.add_option("--removeOld",action="store_true",default=False,dest="removeOld",help="Remove the directories from an old run without asking")
36 self.parser.add_option("--purge",action="store_true",default=False,dest="purge",help="Remove the case directories after evaluating")
37 self.parser.add_option("--no-purge",action="store_true",default=False,dest="nopurge",help="Don't remove the case directories after evaluating")
38 self.parser.add_option("--steady",action="store_true",default=False,dest="steady",help="Only runs the solver until convergence")
39 self.parser.add_option("--showDictionary",action="store_true",default=False,dest="showDict",help="Shows the parameter-dictionary after the running of the solver")
40
42 fName=self.parser.getArgs()[0]
43
44 dom=parse(fName)
45 doc=dom.documentElement
46
47 if doc.tagName!='comparator':
48 error("Wrong root-element",doc.tagName,"Expected: 'comparator'")
49
50 self.data=ComparatorData(doc)
51
52 purge=False
53 if doc.hasAttribute('purge'):
54 purge=eval(doc.getAttribute('purge'))
55 if self.opts.purge:
56 purge=self.opts.purge
57 if self.opts.nopurge:
58 purge=False
59
60 steady=False
61 if doc.hasAttribute('steady'):
62 steady=eval(doc.getAttribute('steady'))
63 if self.opts.steady:
64 purge=self.opts.steady
65
66 print " Parameters read OK "
67 print
68
69 aLog=open(self.data.id+".overview","w")
70 rDir=self.data.id+".results"
71 execute("rm -rf "+rDir)
72 execute("mkdir "+rDir)
73
74 for i in range(len(self.data)):
75 runID=("%04d" % i)
76 print >>aLog,runID,
77
78 para=self.data[i]
79 para["template"]=self.data.template
80 para["extension"]=self.data.extension
81 para["id"]=self.data.id
82
83 print "Executing Variation",i+1,"of",len(self.data)
84 print "Parameters:",
85 for k,v in para.iteritems():
86 print "%s='%s' " % (k,v),
87 if v.find(" ")>=0 or v.find("\t")>=0:
88 v="'"+v+"'"
89 print >>aLog,v,
90
91 print
92
93 cName="%s.%04d" % (self.data.id, i)
94 log=open(cName+".log","w")
95
96 para["case"]=cName
97 print "Case-directory:",cName
98 para["results"]=path.join(rDir,runID)
99 print "Results directory:",para["results"]
100 execute("mkdir "+para["results"])
101
102 if path.exists(cName):
103 if self.opts.removeOld:
104 print " Removing old case-directory"
105 execute("rm -r "+cName)
106 else:
107 error("Case-directory",cName,"exists")
108
109 print " copying template"
110 out=execute("cp -r "+self.data.template+" "+cName)
111 print >>log,"---- Copying"
112 for l in out:
113 print >>log,l,
114
115 print " preparing"
116 ok,erg=self.data.prep.execute(para,log)
117 print >>aLog,ok,
118 for e in erg:
119 print >>aLog,e,
120 aLog.flush()
121
122 if self.opts.test:
123 print " Skipping execution"
124 else:
125 print " running the solver"
126 sys.stdout.flush()
127
128 if steady:
129 runnerClass=ConvergenceRunner
130 else:
131 runnerClass=AnalyzedRunner
132
133 run=runnerClass(BoundingLogAnalyzer(doTimelines=True,progress=True),
134 argv=[self.data.solver,".",cName],
135 silent=True,server=True)
136
137 run.start()
138 ok=run.runOK()
139 if ok:
140 print " executed OK"
141 else:
142 print " fatal error"
143
144 tEnd=-1
145
146 for aName in run.listAnalyzers():
147 a=run.getAnalyzer(aName)
148 if 'titles' in dir(a):
149 for tit in a.lines.getValueNames():
150 t,v=a.getTimeline(tit)
151 if len(v)>0:
152 para["result_"+aName+"_"+tit]=v[-1]
153 if t[-1]>tEnd:
154 tEnd=t[-1]
155
156 print >>aLog,run.runOK(),tEnd,run.run.wallTime(),
157
158 para["endTime"]=tEnd
159 para["runlog"]=run.logFile
160
161 if self.opts.showDict:
162 print para
163
164 print " evaluating results"
165
166 ok,erg=self.data.post.execute(para,log)
167
168 if ok:
169 print " Evaluation OK",
170 else:
171 print " Evaluation failed",
172
173 if len(erg)>0:
174 print ":",erg,
175 print
176
177 print >>aLog,ok,
178 for e in erg:
179 print >>aLog,e,
180
181 if purge:
182 print " removing the case-directory"
183 out=execute("rm -r "+cName)
184 print >>log,"---- Removing"
185 for l in out:
186 print >>log,l,
187
188 log.close()
189 print
190 print >>aLog
191 aLog.flush()
192
193 aLog.close()
194
196 """ The object that holds the actual data"""
197
199 """
200 @param doc: the parsed XML-data from which the object is constructed
201 """
202 self.name=doc.getAttribute("name")
203 if self.name=="":
204 error("No name for 'comparator' given")
205
206 base=doc.getElementsByTagName("base")
207 if base.length!=1:
208 error("One 'base'-element needed. Found",base.length)
209 self.__parseBase(base[0])
210
211 self.vList=[]
212 for v in doc.getElementsByTagName("variation"):
213 self.vList.append(Variation(v))
214
216 """@param e: The 'base'-element"""
217
218 self.template=e.getAttribute("template")
219 if self.template=="":
220 error("No template is given")
221 if not path.exists(self.template):
222 error("Template",self.template,"does not exist")
223 self.id=self.template
224 if e.hasAttribute('extension'):
225 self.extension=e.getAttribute('extension')
226 self.id+="."+self.extension
227 else:
228 self.extension=""
229 self.solver=e.getAttribute("solver")
230 if self.solver=="":
231 error("No solver is given")
232 prep=e.getElementsByTagName("preparation")
233 if prep.length!=1:
234 error("One 'preparation'-element needed. Found",prep.length)
235 self.prep=PreparationChain(prep[0])
236 post=e.getElementsByTagName("evaluation")
237 if post.length!=1:
238 error("One 'evaluation'-element needed. Found",post.length)
239 self.post=EvaluationChain(post[0])
240
242 """@return: The total number of variations"""
243 if len(self.vList)==0:
244 return 0
245 else:
246 nr=1l
247 for v in self.vList:
248 nr*=len(v)
249 return nr
250
252 """@param nr: Number of the variation
253 @return: dictionary with the variation"""
254 if nr>=len(self):
255 error("Index",nr,"of variation out of bounds: [0,",len(self)-1,"]")
256 result={}
257 tmp=nr
258 for v in self.vList:
259 k,val=v[tmp % len(v)]
260 result[k]=val
261 tmp/=len(v)
262 assert tmp==0
263
264 return result
265
267 """Abstract base class for a number of commands"""
269 """@param c: XML-Subtree that represents the chain"""
270 self.commands=[]
271 for e in c.childNodes:
272 if e.nodeType!=xml.dom.Node.ELEMENT_NODE:
273 continue
274 if not e.tagName in self.table.keys():
275 error("Tagname",e.tagName,"not in table of valid tags",self.table.keys())
276 self.commands.append(self.table[e.tagName](e))
277
279 """Executes the chain
280 @param para:A dictionary with the parameters
281 @param log: Logfile to write to"""
282
283 result=[]
284 status=True
285 for c in self.commands:
286 ok,erg=c.execute(para,log)
287 status=ok and status
288 if erg!=None:
289 if type(erg)==list:
290 result+=erg
291 else:
292 result.append(erg)
293
294 return status,result
295
297 """Chain of Preparation commands"""
308
310 """Chain of evaluation commands"""
320
322 """Abstract base class of all commands"""
324 pass
325
327 result=e.getAttribute(name)
328 if result=="":
329 error("Missing attribute",name,"in element",e.tagName)
330 return result
331
333 """@param vals: Dictionary with the keywords
334 @return: A boolean whether it completed successfully and a list with results (None if no results are generated)"""
335 error("Execute not implemented for",type(self))
336
338 """Replaces all strings enclosed by $$ with the parameters
339 @param orig: the original string
340 @param para: dictionary with the parameters"""
341
342 exp=re.compile("\$[^$]*\$")
343 tmp=orig
344
345 m=exp.search(tmp)
346 while m:
347 a,e=m.span()
348 pre=tmp[0:a]
349 post=tmp[e:]
350 mid=tmp[a+1:e-1]
351
352 if not mid in para.keys():
353 error("Key",mid,"not existing in keys",para.keys())
354
355 tmp=pre+para[mid]+post
356
357 m=exp.search(tmp)
358
359 return tmp
360
362 """Executes a shell command"""
366
368 cmd=self.replace(self.command,para)
369 print " Executing ",cmd,
370 sys.stdout.flush()
371 out=execute(cmd)
372
373 if len(out)>0:
374 print " -->",len(out),"lines output"
375 for l in out:
376 print >>log,"---- Command:",cmd
377 print >>log,l,
378 else:
379 print
380
381 return True,None
382
384 """Derives an additional value"""
389
391 tmp=self.replace(self.expression,para)
392 try:
393 val=eval(tmp)
394 except SyntaxError:
395 error("Syntax error in",tmp)
396 print " Setting",self.name,"to",val
397 para[self.name]=str(val)
398
399 return True,None
400
402 """Returns values from the chains dictionaries"""
406
408 if para.has_key(self.key):
409 return True,para[self.key]
410 else:
411 print "-----> ",self.key,"not in set of valid keys",para.keys()
412 print >>log,self.key,"not in set of valid keys of dictionary",para
413 return False,None
414
416 """Sets value in the chains dictionaries"""
421
423 para[self.key]=self.value
424 return True,None
425
427 """Executes a OpenFOAM-utility"""
432
434 argv=[self.utility,".",para['case']]+self.options.split()
435 print " Executing",string.join(argv),
436 sys.stdout.flush()
437 run=BasicRunner(argv,silent=True,logname=string.join(argv,"_"))
438 run.start()
439 if run.runOK():
440 print
441 else:
442 print "---> there was a problem"
443
444 return run.runOK(),None
445
447 """Executes a OpenFOAM-utility and extracts information"""
451
453 argv=[self.utility,".",para['case']]+self.options.split()
454 print " Executing and analyzing",string.join(argv),
455 sys.stdout.flush()
456 run=UtilityRunner(argv,silent=True,logname=string.join(argv,"_"))
457 run.add("data",self.regexp)
458 run.start()
459 data=run.analyzer.getData("data")
460 result=None
461 if data!=None:
462 result=[]
463 for a in data:
464 result.append(a)
465 if result==None:
466 print "no data",
467 else:
468 print result,
469
470 if run.runOK():
471 print
472 else:
473 print "---> there was a problem"
474
475 return run.runOK(),result
476
478 """Common class for commands that operate on dictionaries"""
482
484 f=self.replace(self.filename,para)
485 v=self.replace(self.value,para)
486 s=self.replace(self.subexpression,para)
487 k=self.replace(self.key,para)
488
489 try:
490 dictFile=ParsedParameterFile(f,backup=True)
491 val=dictFile[k]
492 except KeyError:
493 self.error("Key: ",k,"not existing in File",f)
494 except IOError,e:
495 self.error("Problem with file",k,":",e)
496
497 try:
498 exec "dictFile[k]"+s+"=v"
499 except Exception,e:
500 error("Problem with subexpression:",sys.exc_info()[0],":",e)
501
502 dictFile.writeFile()
503
504 return True,None
505
507 """Common class for commands that set values on fields"""
509 SetterCommand.__init__(self,c)
510 self.field=c.getAttribute("field")
511 self.filename=path.join("$case$","0",self.field)
512
514 """Sets an initial condition"""
519
523
525 """Sets a boundary condition"""
527 FieldSetterCommand.__init__(self,c)
528 self.patch=c.getAttribute("patch")
529 self.key="boundaryField"
530 self.subexpression="['"+self.patch+"']"
531 self.element=c.getAttribute("element")
532 if self.element=="":
533 self.element="value"
534 self.subexpression+="['"+self.element+"']"
535
539
541 """Writes a value to a dictionary"""
543 SetterCommand.__init__(self,c)
544 self.dir=c.getAttribute("directory")
545 self.dict=c.getAttribute("dictionary")
546 self.filename=path.join("$case$",self.dir,self.dict)
547 self.key=c.getAttribute("key")
548 self.subexpression=c.getAttribute("subexpression")
549 self.value=c.getAttribute("value")
550
554
556 """Copies the result of the last time-step to the resultsd directory"""
559
566
568 """Copies the log file to the results"""
571
573 print " Copy logfile"
574 execute("cp "+para["runlog"]+" "+para["results"])
575 return True,None
576
578 """Represenrs one variation"""
579
581 """@param e: the XML-data from which it is created"""
582
583 self.name=e.getAttribute("name")
584 if self.name=="":
585 error("No name for 'variation' given")
586 self.key=e.getAttribute("key")
587 if self.name=="":
588 error("No key for 'variation'",self.name," given")
589 self.values=[]
590 for v in e.getElementsByTagName("value"):
591 self.values.append(str(v.firstChild.data))
592
594 return "Variation "+self.name+" varies "+self.key+" over "+str(self.values)
595
597 """@return: number of values"""
598 return len(self.values)
599
601 return self.key,self.values[key]
602