1
2 """
3 Class that implements pyFoamDecompose
4 """
5
6 from optparse import OptionGroup
7
8 from PyFoamApplication import PyFoamApplication
9 from PyFoam.Basics.FoamFileGenerator import FoamFileGenerator
10 from PyFoam.Error import error
11 from PyFoam.Basics.Utilities import writeDictionaryHeader,rmtree
12 from PyFoam.Execution.UtilityRunner import UtilityRunner
13 from PyFoam.RunDictionary.SolutionDirectory import SolutionDirectory
14 from PyFoam.RunDictionary.RegionCases import RegionCases
15 from PyFoam.RunDictionary.ParsedParameterFile import FoamStringParser
16 from PyFoam.FoamInformation import oldAppConvention as oldApp
17 from PyFoam.FoamInformation import foamVersion
18
19 from CommonMultiRegion import CommonMultiRegion
20 from CommonStandardOutput import CommonStandardOutput
21 from CommonServer import CommonServer
22 from CommonVCSCommit import CommonVCSCommit
23
24 from os import path,listdir,symlink
25 import sys,string
26
27 -class Decomposer(PyFoamApplication,
28 CommonStandardOutput,
29 CommonServer,
30 CommonMultiRegion,
31 CommonVCSCommit):
43
44 decomposeChoices=["metis","simple","hierarchical","manual"]
45 defaultMethod="metis"
46
48 if foamVersion()>=(1,6):
49 self.defaultMethod="scotch"
50 self.decomposeChoices+=[self.defaultMethod]
51 self.decomposeChoices+=["parMetis"]
52
53 spec=OptionGroup(self.parser,
54 "Decomposition Specification",
55 "How the case should be decomposed")
56 spec.add_option("--method",
57 type="choice",
58 default=self.defaultMethod,
59 dest="method",
60 action="store",
61 choices=self.decomposeChoices,
62 help="The method used for decomposing (Choices: "+string.join(self.decomposeChoices,", ")+") Default: %default")
63
64 spec.add_option("--n",
65 dest="n",
66 action="store",
67 default=None,
68 help="Number of subdivisions in coordinate directions. A python list or tuple (for simple and hierarchical)")
69
70 spec.add_option("--delta",
71 dest="delta",
72 action="store",
73 type="float",
74 default=None,
75 help="Cell skew factor (for simple and hierarchical)")
76
77 spec.add_option("--order",
78 dest="order",
79 action="store",
80 default=None,
81 help="Order of decomposition (for hierarchical)")
82
83 spec.add_option("--processorWeights",
84 dest="processorWeights",
85 action="store",
86 default=None,
87 help="The weights of the processors. A python list. Used for metis, scotch and parMetis")
88
89 spec.add_option("--globalFaceZones",
90 dest="globalFaceZones",
91 action="store",
92 default=None,
93 help="""Global face zones. A string with a python list or an OpenFOAM-list of words. Used for the GGI interface. Ex: '["GGI_Z1","GGI_Z2"]' or '(GGI_Z1 GGI_Z2)'""")
94
95 spec.add_option("--dataFile",
96 dest="dataFile",
97 action="store",
98 default=None,
99 help="File with the allocations. (for manual)")
100 self.parser.add_option_group(spec)
101
102 behave=OptionGroup(self.parser,
103 "Decomposition behaviour",
104 "How the program should behave during decomposition")
105 behave.add_option("--test",
106 dest="test",
107 action="store_true",
108 default=False,
109 help="Just print the resulting dictionary")
110
111 behave.add_option("--clear",
112 dest="clear",
113 action="store_true",
114 default=False,
115 help="Clear the case of previous processor directories")
116
117 behave.add_option("--no-decompose",
118 dest="doDecompose",
119 action="store_false",
120 default=True,
121 help="Don't run the decomposer (only writes the dictionary")
122
123 behave.add_option("--decomposer",
124 dest="decomposer",
125 action="store",
126 default="decomposePar",
127 help="The decompose Utility that should be used")
128 self.parser.add_option_group(behave)
129
130 work=OptionGroup(self.parser,
131 "Additional work",
132 "What else should be done in addition to decomposing")
133 work.add_option("--constant-link",
134 dest="doConstantLinks",
135 action="store_true",
136 default=False,
137 help="Add links to the contents of the constant directory to the constant directories of the processor-directories")
138 self.parser.add_option_group(work)
139
140 CommonMultiRegion.addOptions(self)
141 CommonStandardOutput.addOptions(self)
142 CommonServer.addOptions(self,False)
143 CommonVCSCommit.addOptions(self)
144
146 decomposeParWithRegion=(foamVersion()>=(1,6))
147
148 if self.opts.keeppseudo and (not self.opts.regions and self.opts.region==None):
149 warning("Option --keep-pseudocases only makes sense for multi-region-cases")
150
151 if decomposeParWithRegion and self.opts.keeppseudo:
152 warning("Option --keep-pseudocases doesn't make sense since OpenFOAM 1.6 because decomposePar supports regions")
153
154 nr=int(self.parser.getArgs()[1])
155 if nr<2:
156 error("Number of processors",nr,"too small (at least 2)")
157
158 case=path.abspath(self.parser.getArgs()[0])
159 method=self.opts.method
160
161 result={}
162 result["numberOfSubdomains"]=nr
163 result["method"]=method
164
165 coeff={}
166 result[method+"Coeffs"]=coeff
167
168 if self.opts.globalFaceZones!=None:
169 try:
170 fZones=eval(self.opts.globalFaceZones)
171 except SyntaxError:
172 fZones=FoamStringParser(
173 self.opts.globalFaceZones,
174 listDict=True
175 ).data
176
177 result["globalFaceZones"]=fZones
178
179 if method in ["metis","scotch","parMetis"]:
180 if self.opts.processorWeights!=None:
181 weigh=eval(self.opts.processorWeights)
182 if nr!=len(weigh):
183 error("Number of processors",nr,"and length of",weigh,"differ")
184 coeff["processorWeights"]=weigh
185 elif method=="manual":
186 if self.opts.dataFile==None:
187 error("Missing required option dataFile")
188 else:
189 coeff["dataFile"]="\""+self.opts.dataFile+"\""
190 elif method=="simple" or method=="hierarchical":
191 if self.opts.n==None or self.opts.delta==None:
192 error("Missing required option n or delta")
193 n=eval(self.opts.n)
194 if len(n)!=3:
195 error("Needs to be three elements, not",n)
196 if nr!=n[0]*n[1]*n[2]:
197 error("Subdomains",n,"inconsistent with processor number",nr)
198 coeff["n"]="(%d %d %d)" % (n[0],n[1],n[2])
199
200 coeff["delta"]=float(self.opts.delta)
201 if method=="hierarchical":
202 if self.opts.order==None:
203 error("Missing reuired option order")
204 if len(self.opts.order)!=3:
205 error("Order needs to be three characters")
206 coeff["order"]=self.opts.order
207 else:
208 error("Method",method,"not yet implementes")
209
210 gen=FoamFileGenerator(result)
211
212 if self.opts.test:
213 print str(gen)
214 return -1
215 else:
216 f=open(path.join(case,"system","decomposeParDict"),"w")
217 writeDictionaryHeader(f)
218 f.write(str(gen))
219 f.close()
220
221 if self.opts.clear:
222 rmtree(path.join(case,"processor*"),ignore_errors=True)
223
224 self.checkAndCommit(SolutionDirectory(case,archive=None))
225
226 if self.opts.doDecompose:
227 if self.opts.region:
228 regionNames=self.opts.region[:]
229 while True:
230 try:
231 i=regionNames.index("region0")
232 regionNames[i]=None
233 except ValueError:
234 break
235 else:
236 regionNames=[None]
237
238 regions=None
239
240 sol=SolutionDirectory(case)
241 if not decomposeParWithRegion:
242 if self.opts.regions or self.opts.region!=None:
243 print "Building Pseudocases"
244 regions=RegionCases(sol,clean=True,processorDirs=False)
245
246 if self.opts.regions:
247 regionNames=sol.getRegions(defaultRegion=True)
248
249 for theRegion in regionNames:
250 theCase=path.normpath(case)
251 if theRegion!=None and not decomposeParWithRegion:
252 theCase+="."+theRegion
253
254 if oldApp():
255 argv=[self.opts.decomposer,".",theCase]
256 else:
257 argv=[self.opts.decomposer,"-case",theCase]
258 if theRegion!=None and decomposeParWithRegion:
259 argv+=["-region",theRegion]
260
261 f=open(path.join(case,"system",theRegion,"decomposeParDict"),"w")
262 writeDictionaryHeader(f)
263 f.write(str(gen))
264 f.close()
265
266 self.setLogname(default="Decomposer",useApplication=False)
267
268 run=UtilityRunner(argv=argv,
269 silent=self.opts.progress or self.opts.silent,
270 logname=self.opts.logname,
271 compressLog=self.opts.compress,
272 server=self.opts.server,
273 noLog=self.opts.noLog,
274 logTail=self.opts.logTail,
275 jobId=self.opts.jobId)
276 run.start()
277
278 if theRegion!=None and not decomposeParWithRegion:
279 print "Syncing into master case"
280 regions.resync(theRegion)
281
282 if regions!=None and not decomposeParWithRegion:
283 if not self.opts.keeppseudo:
284 print "Removing pseudo-regions"
285 regions.cleanAll()
286 else:
287 for r in sol.getRegions():
288 if r not in regionNames:
289 regions.clean(r)
290
291 if self.opts.doConstantLinks:
292 print "Adding symlinks in the constant directories"
293 constPath=path.join(case,"constant")
294 for f in listdir(constPath):
295 srcExpr=path.join(path.pardir,path.pardir,"constant",f)
296 for p in range(nr):
297 dest=path.join(case,"processor%d"%p,"constant",f)
298 if not path.exists(dest):
299 symlink(srcExpr,dest)
300
301 self.addToCaseLog(case)
302