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