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