PyForTool
Python-fortran-tool
Loading...
Searching...
No Matches
applications.py
1"""
2This module implements the Applications class containing methods
3for high-to-moderate level transformation
4"""
5
6import copy
7import os
8import re
9
10from pyfortool.util import debugDecor, alltext, n2name, isStmt, PYFTError, tag, noParallel
11from pyfortool.expressions import createExpr, createExprPart, createElem, simplifyExpr
12from pyfortool.tree import updateTree
13from pyfortool.variables import updateVarList
14from pyfortool import NAMESPACE
16
17
18# pylint: disable-next=unused-argument
19def _loopVarPHYEX(lowerDecl, upperDecl, lowerUsed, upperUsed, name, index):
20 """
21 Try to guess the name of the variable to use for looping on indexes
22 :param lowerDecl, upperDecl: lower and upper bounds as defined in the declaration statement
23 :param lowerUsed, upperUsed: lower and upper bounds as given in the statement
24 :param name: name of the array
25 :param index: index of the rank
26 :return: the variable name of False to discard this statement
27 """
28 if lowerUsed is not None and lowerUsed.upper() == 'IIJB' and \
29 upperUsed is not None and upperUsed.upper() == 'IIJE':
30 varName = 'JIJ'
31 elif upperDecl is None or lowerDecl is None:
32 varName = False
33 elif upperDecl.upper() in ('KSIZE', 'KPROMA', 'KMICRO',
34 'IGRIM', 'IGACC', 'IGDRY', 'IGWET'):
35 varName = 'JL'
36 elif upperDecl.upper() in ('D%NIJT', 'IIJE') or lowerDecl.upper() in ('D%NIJT', 'IIJB') or \
37 'D%NIJT' in upperDecl.upper() + lowerDecl.upper():
38 # REAL, DIMENSION(MERGE(D%NIJT, 0, PARAMI%LDEPOSC)), INTENT(OUT) :: PINDEP
39 varName = 'JIJ'
40 elif upperDecl.upper() in ('IKB', 'IKE', 'IKT', 'D%NKT', 'KT') or \
41 'D%NKT' in upperDecl.upper():
42 # REAL, DIMENSION(MERGE(D%NIJT, 0, OCOMPUTE_SRC),
43 # MERGE(D%NKT, 0, OCOMPUTE_SRC)), INTENT(OUT) :: PSIGS
44 varName = 'JK'
45 elif upperDecl.upper() == 'KSV' or lowerDecl.upper() == 'KSV':
46 varName = 'JSV'
47 elif upperDecl.upper() == 'KRR':
48 varName = 'JRR'
49 elif upperDecl.upper() in ('D%NIT', 'IIE', 'IIU') or lowerDecl.upper() == 'IIB' or \
50 'D%NIT' in upperDecl.upper():
51 varName = 'JI'
52 elif upperDecl.upper() in ('D%NJT', 'IJE', 'IJU') or lowerDecl.upper() == 'IJB' or \
53 'D%NJT' in upperDecl.upper():
54 varName = 'JJ'
55 else:
56 varName = False
57 return varName
58
59
61 """
62 Methods for high-to-moderate level transformation
63 """
64
65 @debugDecor
67 """
68 Split all module and subroutine contain in a fortran file
69 Return a fortran file for each module and subroutine
70 """
71 for scope in self.getScopes(level=1, excludeContains=False, includeItself=True):
73 scope.path.split(":")[1].lower() + ".F90") as file:
74 file.extend(scope.findall('./{*}*'))
75 if file[-1].tail is None:
76 file[-1].tail = '\n'
77 file.write()
78
79 @debugDecor
80 def buildModi(self):
81 """
82 Build the modi_ file corresponding to the given scope
83 """
84 filename = self.getFileName()
85 fortran = 'MODULE MODI_' + os.path.splitext(os.path.basename(filename))[0].upper() + \
86 '\nEND MODULE'
88 os.path.join(os.path.dirname(filename), 'modi_' + os.path.basename(filename)), fortran)
89 module = modi.find('.//{*}program-unit')
90 module.insert(1, createElem('C', text='!Automatically generated by PyForTool', tail='\n'))
91 module.insert(2, createElem('implicit-none-stmt', text='IMPLICIT NONE', tail='\n'))
92 interface = createElem('interface-construct')
93 interface.append(createElem('interface-stmt', text='INTERFACE', tail='\n'))
94 for scope in self.getScopes(level=1):
95 prog = createElem('program-unit')
96 prog.append(copy.deepcopy(scope[0]))
97 for use in scope.findall('./use-stmt'):
98 prog.append(copy.deepcopy(use))
99 prog.append(createElem('implicit-none-stmt', text='IMPLICIT NONE', tail='\n'))
100 for var in [var for var in scope.varList if var['arg'] or var['result']]:
101 prog.append(createExpr(self.varSpec2stmt(var, True))[0])
102 for external in scope.findall('./{*}external-stmt'):
103 prog.append(copy.deepcopy(external))
104 end = copy.deepcopy(scope[-1])
105 end.tail = '\n'
106 prog.append(end)
107 interface.append(prog)
108 interface.append(createElem('end-interface-stmt', text='END INTERFACE', tail='\n'))
109 module.insert(3, interface)
110 modi.write()
111 modi.close()
112
113 @debugDecor
114 def deleteNonColumnCallsPHYEX(self, simplify=False):
115 """
116 Remove PHYEX routines that compute with different vertical columns not needed for AROME
117 MODE_ROTATE_WIND, UPDATE_ROTATE_WIND
118 If Simplify is True, also remove all variables only needed for these calls
119 :param simplify : if True, remove variables that are now unused
120 """
121 for subroutine in ('ROTATE_WIND', 'UPDATE_ROTATE_WIND', 'BL_DEPTH_DIAG_3D',
122 'TM06_H', 'TURB_HOR_SPLT'):
123 # Remove call statements
124 nb = self.removeCall(subroutine, simplify=simplify)
125 # Remove use statement
126 if nb > 0:
127 self.removeVar([(v['scopePath'], v['n']) for v in self.varList
128 if v['n'] == subroutine], simplify=simplify)
129
130 @debugDecor
132 """
133 in Meso-NH, !$mnh_do_concurrent directive works only if no DO/ENDDO is written within.
134 DO loop instructions have been written in PHYEX common code within these directives
135 to be able to be run in offline and IAL models without MNH_EXPAND scripts.
136 This function removes these DO and END DO.
137 It is used only when shipping the PHYEX version integrated into MesoNH.
138 """
139 scopes = self.getScopes()
140 for scope in scopes:
141 comments = scope.findall('.//{*}C')
142 for coms in comments:
143 if '!$mnh_do_concurrent' in coms.text:
144 par = scope.getParent(coms)
145 icom = list(par).index(coms)
146 mainDoConstruct = par[icom+1]
147 allDoConstructs = mainDoConstruct.findall('.//{*}do-construct')
148 allDoConstructs.append(mainDoConstruct)
149 for doConstruct in allDoConstructs:
150 for el in doConstruct:
151 if tag(el) == 'do-stmt' or tag(el) == 'end-do-stmt':
152 doConstruct.remove(el)
153
154 @debugDecor
156 """
157 Convert STR%VAR into single local variable contained in compute (a-stmt)
158 and in if-then-stmt, else-if-stmt, where-stmt
159 e.g.
160 ZA = 1 + CST%XG ==> ZA = 1 + XCST_G
161 ZA = 1 + PARAM_ICE%XRTMIN(3) ==> ZA = 1 + XPARAM_ICE_XRTMIN3
162 ZRSMIN(1:KRR) = ICED%XRTMIN(1:KRR) => ZRSMIN(1:KRR) = ICEDXRTMIN1KRR(1:KRR)
163 IF(TURBN%CSUBG_MF_PDF=='NONE')THEN => IF(CTURBNSUBG_MF_PDF=='NONE')THEN
164
165 RESTRICTION : works only if the r-component variable is contained in 1 parent structure.
166 Allowed for conversion : CST%XG
167 Not converted : TOTO%CST%XG (for now, recursion must be coded)
168 Not converted : TOTO%ARRAY(:) (shape of the array must be determined from E1)
169 Not converted : TOTO%TUTU(:) (type in a type)
170 """
171 def convertOneType(component, newVarList, scope):
172 # 1) Build the name of the new variable
173 objType = scope.getParent(component, 2) # The object STR%VAR
174 objTypeStr = alltext(objType).upper()
175 namedENn = objType.find('.//{*}N/{*}n')
176 structure = namedENn.text
177 variable = component.find('.//{*}ct').text.upper()
178 if variable[0] == "T":
179 return # Exclude variables of the type "Type"
180 # If the variable is an array with index selection
181 # such as ICED%XRTMIN(1:KRR)
182 arrayIndices = ''
183 arrayRall = objType.findall('.//{*}array-R')
184 if len(arrayRall) > 0:
185 arrayR = copy.deepcopy(arrayRall[0]) # Save for the declaration
186 txt = alltext(arrayR).replace(',', '')
187 txt = txt.replace(':', '')
188 txt = txt.replace('(', '')
189 txt = txt.replace(')', '')
190 arrayIndices = arrayIndices + txt
191 elif len(objType.findall('.//{*}element-LT')) > 0:
192 # Case with single element such as ICED%XRTMIN(1)
193 for elem in objType.findall('.//{*}element'):
194 arrayIndices = arrayIndices + alltext(elem)
195 newName = variable[0] + structure + variable[1:] + arrayIndices
196 newName = newName.upper()
197
198 # 2) Replace the namedE>N>n by the newName and delete R-LT
199 # except for array with index selection (R-LT is moved)
200 namedENn.text = newName
201 objType.remove(objType.find('.//{*}R-LT'))
202 if len(arrayRall) > 0:
203 objType.insert(1, arrayR)
204
205 # 3) Add to the list of not already present for declaration
206 if newName not in newVarList:
207 if len(arrayRall) == 0:
208 newVarList[newName] = (None, objTypeStr)
209 else:
210 newVarList[newName] = (arrayR, objTypeStr)
211
212 scopes = self.getScopes()
213 if scopes[0].path.split('/')[-1].split(':')[1][:4] == 'MODD':
214 return
215 for scope in [scope for scope in scopes
216 if 'sub:' in scope.path and 'interface' not in scope.path]:
217 newVarList = {}
218 for ifStmt in (scope.findall('.//{*}if-then-stmt') +
219 scope.findall('.//{*}else-if-stmt') +
220 scope.findall('.//{*}where-stmt')):
221 compo = ifStmt.findall('.//{*}component-R')
222 if len(compo) > 0:
223 for elcompo in compo:
224 convertOneType(elcompo, newVarList, scope)
225
226 for aStmt in scope.findall('.//{*}a-stmt'):
227 # Exclude statements in which the component-R is in E1
228 # (e.g. PARAMI%XRTMIN(4) = 2)
229 if len(aStmt[0].findall('.//{*}component-R')) == 0: # E1 is the first son of aStmt
230 compoE2 = aStmt.findall('.//{*}component-R')
231 if len(compoE2) > 0:
232 # Exclude stmt from which E2 has only 1 named-E/{*}N/{*}n e.g. IKB = D%NKB
233 # warning, it does not handle yet op in simple statement
234 # such as ZEXPL = 1.- TURBN%XIMPL
235 # Include stmt from which E2 has 1 named-E/{*}N/{*}n AND E1 is an array;
236 # e.g. ZDELTVPT(JIJ,JK)=CSTURB%XLINF
237 nbNamedEinE2 = len(aStmt.findall('.//{*}E-2')[0].findall('.//{*}named-E/' +
238 '{*}N/{*}n'))
239 if nbNamedEinE2 > 1 or nbNamedEinE2 == 1 and \
240 len(aStmt[0].findall('.//{*}R-LT')) == 1:
241 for elcompoE2 in compoE2:
242 convertOneType(elcompoE2, newVarList, scope)
243
244 # For converted variables (= newVarList), look for possible affection
245 # If found, add an extra affection for the new variables
246 for aStmt in scope.findall('.//{*}a-stmt'):
247 # statements in which the component-R is in E1
248 if len(aStmt[0].findall('.//{*}component-R')) > 0:
249 # E1 where the conversion will be applied
250 for el in newVarList.items():
251 if alltext(aStmt[0]) == el[1][1]:
252 stmtAffect = createExpr(el[0] + "=" + alltext(aStmt[0]))
253 par = scope.getParent(aStmt)
254 iExtra = 0
255 if tag(par[list(par).index(aStmt)+1]) == 'C':
256 iExtra = 1
257 par.insert(list(par).index(aStmt)+1+iExtra, stmtAffect[0])
258 break
259
260 # Add the declaration of the new variables and their affectation
261 for el, var in newVarList.items():
262 if el[0].upper() == 'X' or el[0].upper() == 'P' or el[0].upper() == 'Z':
263 varType = 'REAL'
264 elif el[0].upper() == 'L' or el[0].upper() == 'O':
265 varType = 'LOGICAL'
266 elif el[0].upper() == 'N' or el[0].upper() == 'I' or el[0].upper() == 'K':
267 varType = 'INTEGER'
268 elif el[0].upper() == 'C':
269 varType = 'CHARACTER(LEN=LEN(' + var[1] + '))'
270 else:
271 raise PYFTError('Case not implemented for the first letter of the newVarName ' +
272 el + ' in convertTypesInCompute')
273 varArray = ''
274 # Handle the case the variable is an array
275 if var[0]:
276 varArray = ', DIMENSION('
277 for i, sub in enumerate(var[0].findall('.//{*}section-subscript')):
278 if len(sub.findall('.//{*}upper-bound')) > 0:
279 dimSize = simplifyExpr(
280 alltext(sub.findall('.//{*}upper-bound')[0]) +
281 '-' + alltext(sub.findall('.//{*}lower-bound')[0]) +
282 ' + 1')
283 elif len(sub.findall('.//{*}lover-bound')) > 0:
284 dimSize = simplifyExpr(alltext(sub.findall('.//{*}lower-bound')[0]))
285 else: # Case XRTMIN(:)
286 dimSize = 'SIZE(' + var[1] + ',' + str(i+1) + ')'
287 varArray = ', DIMENSION(' + dimSize + ','
288 varArray = varArray[:-1] + ')'
289 scope.addVar([[scope.path, el, varType + varArray + ' :: ' + el, None]])
290
291 # Affectation
292 stmtAffect = createExpr(el + "=" + var[1])[0]
293 scope.insertStatement(scope.indent(stmtAffect), first=True)
294
295 @debugDecor
296 def deleteDrHook(self, simplify=False):
297 """
298 Remove DR_HOOK calls.
299 If Simplify is True, also remove all variables only needed for these calls (ZHOOK_HANDLE,
300 DR_HOOK, LHOOK, YOMHOOK, JPRB, PARKIND1)
301 :param simplify : if True, remove variables that are now unused
302 """
303 self.removeCall('DR_HOOK', simplify=simplify)
304
305 @debugDecor
306 def addDrHook(self):
307 """
308 Add DR_HOOK calls.
309 """
310 for scope in [scope for scope in self.getScopes()
311 if scope.path.split('/')[-1].split(':')[0] in ('func', 'sub') and
312 (len(scope.path.split('/')) == 1 or
313 scope.path.split('/')[-2].split(':')[0] != 'interface')]:
314 name = scope.path.split(':')[-1].upper()
315 # Add USE YOMHOOK, ONLY: LHOOK, DR_HOOK, JPHOOK
316 scope.addModuleVar([[scope.path, 'YOMHOOK', ['LHOOK', 'DR_HOOK', 'JPHOOK']]])
317 # REAL(KIND=JPHOOK) :: ZHOOK_HANDLE
318 scope.addVar([[scope.path, 'ZHOOK_HANDLE', 'REAL(KIND=JPHOOK) :: ZHOOK_HANDLE',
319 None]])
320 # Insert IF (LHOOK) CALL DR_HOOK('XXnameXX', 0, ZHOOK_HANDLE)
321 scope.insertStatement(createExpr(f"IF (LHOOK) CALL DR_HOOK('{name}', " +
322 "0, ZHOOK_HANDLE)")[0], True)
323 # Insert IF (LHOOK) CALL DR_HOOK('XXnameXX', 1, ZHOOK_HANDLE)
324 endStr = f"IF (LHOOK) CALL DR_HOOK('{name}', 1, ZHOOK_HANDLE)"
325 scope.insertStatement(createExpr(endStr)[0], False)
326 for ret in scope.findall('.//{*}return-stmt'):
327 par = scope.getParent(ret)
328 par.insert(list(par).index(ret), createExpr(endStr)[0])
329
330 @debugDecor
331 def deleteBudgetDDH(self, simplify=False):
332 """
333 Remove Budget calls.
334 If Simplify is True, also remove all variables only needed for these calls
335 :param simplify : if True, remove variables that are now unused
336 """
337 self.removeCall('BUDGET_STORE_INIT_PHY', simplify=simplify)
338 self.removeCall('BUDGET_STORE_END_PHY', simplify=simplify)
339 self.removeCall('BUDGET_STORE_ADD_PHY', simplify=simplify)
340 self.removeCall('TBUDGETS', simplify=simplify)
341 flagTorm = ['BUCONF%LBUDGET_SV', 'BUCONF%LBUDGET_TKE', 'BUCONF%LBUDGET_TH',
342 'BUCONF%LBUDGET_RI', 'BUCONF%LBUDGET_RV', 'BUCONF%LBUDGET_RG',
343 'BUCONF%LBUDGET_RS', 'BUCONF%LBUDGET_RH', 'BUCONF%LBUDGET_RR',
344 'BUCONF%LBUDGET_RC', 'BUCONF%LBUDGET_U', 'BUCONF%LBUDGET_V',
345 'BUCONF%LBUDGET_W']
346 self.setFalseIfStmt(flagTorm, simplify=simplify)
347
348 @debugDecor
349 def deleteRoutineCallsMesoNHGPU(self, simplify=True):
350 """
351 Remove Calls to routines not compatible with Méso-NH on GPU
352 e.g. CALL within a DO loop
353 e.g. OCND2 in condensation uses a CALL ICECLOUD and fonctions within computations
354 If Simplify is True, also remove all variables only needed for these calls
355 :param simplify : if True, remove variables that are now unused
356 """
357 self.setFalseIfStmt('OCND2', simplify=simplify)
358
359 @debugDecor
361 """
362 Convert MODULE to SUBMODULE statements and add INTERFACE of SUBROUTINEs of PHYEX
363 ==> Applied only on MODE_
364 ==> Not applied :
365 - if an INTERFACE already exists
366 - if no subroutine is present in the module
367 - to CONTAINS routines
368 1) Create interface statement if any
369 2) Add subroutines declaration (with MODULE statement)
370 3) Add SUBMODULE statements and convert SUBROUTINE to MODULE SUBROUTINE statements
371 """
372 scopes = self.getScopes()
373 modScope = scopes[0] # Module is the first scope
374 # Save the module for later duplications
375 oldModNode = self.find('.//{*}program-unit')
376 modNode = copy.deepcopy(self.find('.//{*}program-unit'))
377
378 interfaceStmt = self.findall('.//{*}interface-stmt')
379 subStmt = self.findall('.//{*}subroutine-stmt')
380
381 if modScope.path.split('/')[-1].split(':')[1][:4] == 'MODE' and \
382 len(interfaceStmt) == 0 and len(subStmt) > 0:
383 moduleName = modScope.path.split('/')[-1].split(':')[1][:]
384 # Creation of the module MODE_XXX with subroutines interfaces
385 newMod = createElem('program-unit', text='MODULE ' + moduleName, tail='\n')
386 newMod.text += '\n'
387 newMod.append(createElem('implicit-none-stmt', text='IMPLICIT NONE', tail='\n'))
388 interfaceStmt = createElem('interface-construct')
389 interfaceStmt.append(createElem('interface-stmt', text='INTERFACE', tail='\n'))
390 interfaceStmt.append(createElem('end-interface-stmt', text='END INTERFACE', tail='\n'))
391 newMod.append(interfaceStmt)
392
393 # For all subroutines/functions, copy the declaration into the interface construct
394 subsModified = []
395 for scope in scopes[1:]:
396 # exclude contained subroutines (sub:sub)
397 if sum('sub' in s for s in scope.path.split('/')) == 1:
398 subsModified.append(scope.path.split('/')[-1].split(':')[1][:])
399 subroutineDecl = createElem('module-unit')
400 # MODULE SUBROUTINE statement
401 subroutineStmt = copy.deepcopy(scope[0])
402 declType = subroutineStmt.text # FUNCTION or SUBROUTINE
403 prefix = createElem('prefix')
404 prefix.text = 'MODULE'
405 subroutineStmt.text = ''
406 subroutineStmt.insert(0, prefix)
407 prefix.tail = ' ' + declType
408 subroutineDecl.append(subroutineStmt)
409 # USE statements
410 for use in scope.findall('.//{*}use-stmt'):
411 subroutineDecl.append(copy.deepcopy(use))
412 subroutineDecl.append(createElem('implicit-none-stmt', text='IMPLICIT NONE',
413 tail='\n'))
414 # Variables declarations
415 for var in [var for var in scope.varList if var['arg'] or var['result']]:
416 subroutineDecl.append(createExpr(self.varSpec2stmt(var, True))[0])
417 for external in scope.findall('./{*}external-stmt'):
418 subroutineDecl.append(copy.deepcopy(external))
419 if 'SUBROUTINE' in declType:
420 endStmt = createElem('end-subroutine-stmt')
421 declName = subroutineStmt.find('./{*}subroutine-N/{*}N/{*}n').text
422 elif 'FUNCTION' in declType:
423 endStmt = createElem('end-function-stmt')
424 declName = subroutineStmt.find('./{*}function-N/{*}N/{*}n').text
425 else:
426 raise PYFTError('declType in addSubmodulePHYEX not handled')
427
428 endStmt.text = 'END ' + declType + declName + '\n'
429 subroutineDecl.append(endStmt)
430 interfaceStmt.insert(1, subroutineDecl)
431
432 # Add the new module with interfaces only
433 newMod.append(createElem('end-program-unit', text='END MODULE ' + moduleName,
434 tail='\n'))
435 self[0].insert(0, newMod) # pylint: disable=unsubscriptable-object
436
437 # Convert the old modules statement to SUBMODULE (ancestor) SubmoduleName
438 progUnit = createElem('program-unit')
439 # <f:submodule-stmt>SUBMODULE (
440 # <f:parent-identifier>
441 # <f:ancestor-module-N>
442 # <f:n>MODE_SHUMAN_PHY</f:n>
443 # </f:ancestor-module-N>
444 # </f:parent-identifier>)
445 # <f:submodule-module-N>
446 # <f:n>SMODE_SHUMAN_PHY</f:n>
447 # </f:submodule-module-N>
448 # </f:submodule-stmt>
449 submoduleStmt = createElem('submodule-stmt', text='SUBMODULE (')
450 parentId = createElem('parent-identifier')
451 parentId.tail = ') '
452 ancestorModule = createElem('ancestor-module-N')
453 ancestorModuleN = createElem('n', text=moduleName)
454 ancestorModule.append(ancestorModuleN)
455 parentId.append(ancestorModule)
456 submoduleStmt.append(parentId)
457 submoduleModule = createElem('submodule-module-N')
458 submoduleModuleN = createElem('n', text='S' + moduleName, tail='\n')
459 submoduleModule.append(submoduleModuleN)
460 submoduleStmt.append(submoduleModule)
461 progUnit.append(submoduleStmt)
462
463 # END SUBMODULE statement
464 endSubmoduleStmt = createElem('end-submodule-stmt', text='END SUBMODULE ')
465 submoduleN = createElem('submodule-N')
466 submoduleNN = createElem('N')
467 submoduleNNn = createElem('n', text='S' + moduleName, tail='\n')
468 submoduleNN.append(submoduleNNn)
469 submoduleN.append(submoduleNN)
470 endSubmoduleStmt.append(submoduleN)
471
472 progUnit.append(endSubmoduleStmt)
473 progUnit.append(createElem('end-program-unit'))
474
475 # Copy the module content into the submodules
476 # Remove end-module-stmt (and module-stmt is not)
477 modStmt = modNode.find('.//{*}module-stmt')
478 modEndStmt = modNode.find('.//{*}end-module-stmt')
479 modNode.remove(modStmt)
480 modNode.remove(modEndStmt)
481
482 # Remove possible PUBLIC and PRIVATE statements
483 publicStmts = modNode.findall('.//{*}public-stmt')
484 privateStmts = modNode.findall('.//{*}private-stmt')
485 if len(publicStmts) > 0:
486 for publicStmt in publicStmts:
487 modNode.remove(publicStmt)
488 if len(privateStmts) > 0:
489 for privateStmt in privateStmts:
490 modNode.remove(privateStmt)
491
492 # And Change the subroutine statements to module-subroutine statements
493 subroutines = modNode.findall('.//{*}subroutine-stmt')
494 for sub in subroutines:
495 if sub.find('.//{*}N/{*}n').text in subsModified:
496 prefix = createElem('prefix')
497 prefix.text = 'MODULE'
498 sub.text = ''
499 sub.insert(0, prefix)
500 prefix.tail = ' SUBROUTINE '
501 progUnit.insert(1, modNode)
502
503 self.insert(1, progUnit)
504
505 # Remove the old module
506 self.remove(oldModNode)
507
508 @debugDecor
509 def addMPPDB_CHECKS(self, printsMode=False):
510
511 """
512 Add MPPDB_CHEKS on all intent REAL arrays on subroutines.
513 ****** Not applied on modd_ routines. ********
514 Handle optional arguments.
515 Example, for a BL89 routine with 4 arguments, 1 INTENT(IN),
516 2 INTENT(INOUT), 1 INTENT(OUT), it produces :
517 IF (MPPDB_INITIALIZED) THEN
518 !Check all IN arrays
519 CALL MPPDB_CHECK(PZZ, "BL89 beg:PZZ")
520 !Check all INOUT arrays
521 CALL MPPDB_CHECK(PDZZ, "BL89 beg:PDZZ")
522 CALL MPPDB_CHECK(PTHVREF, "BL89 beg:PTHVREF")
523 END IF
524 ...
525 IF (MPPDB_INITIALIZED) THEN
526 !Check all INOUT arrays
527 CALL MPPDB_CHECK(PDZZ, "BL89 end:PDZZ")
528 CALL MPPDB_CHECK(PTHVREF, "BL89 end:PTHVREF")
529 !Check all OUT arrays
530 CALL MPPDB_CHECK(PLM, "BL89 end:PLM")
531 END IF
532 param printsMode: if True, instead of CALL MPPDB_CHECK, add fortran prints for debugging
533 """
534 def addPrints_statement(var, typeofPrints='minmax'):
535 ifBeg, ifEnd = '', ''
536 if var['as']: # If array
537 varName = var['n']
538 if typeofPrints == 'minmax':
539 strMSG = f'MINMAX {varName} = \",MINVAL({varName}), MAXVAL({varName})'
540 elif typeofPrints == 'shape':
541 strMSG = f'SHAPE {varName} = \",SHAPE({varName})'
542 else:
543 raise PYFTError('typeofPrints is either minmax or shape in addPrints_statement')
544 else:
545 strMSG = var['n'] + ' = \",' + var['n']
546 if var['opt']:
547 ifBeg = ifBeg + 'IF (PRESENT(' + var['n'] + ')) THEN\n '
548 ifEnd = ifEnd + '\nEND IF'
549 return createExpr(ifBeg + "print*,\"" + strMSG + ifEnd)[0]
550
551 def addMPPDB_CHECK_statement(var, subRoutineName, strMSG='beg:'):
552 ifBeg, ifEnd, addD, addLastDim, addSecondDimType = '', '', '', '', ''
553 # Test if the variable is declared with the PHYEX D% structure,
554 # in that case, use the PHYEX MPPDB_CHECK interface
555 if var['as'][0][1]: # If not NoneType
556 if 'D%NIJT' in var['as'][0][1]:
557 addD = 'D,'
558 if len(var['as']) == 2:
559 # This handle 2D arrays with the last dim either D%NKT or anything else.
560 addLastDim = ', ' + var['as'][1][1]
561 if len(var['as']) >= 2:
562 # This adds information on the type of the second dimension :
563 # is it the vertical one or not, to remove extra points
564 if 'D%NK' in var['as'][1][1]:
565 addSecondDimType = ',' + '''"VERTICAL"'''
566 else:
567 addSecondDimType = ',' + '''"OTHER"'''
568 if 'MERGE' in var['as'][-1][1]: # e.g. MERGE(D%NKT,0,OCLOUDMODIFLM)
569 keyDimMerge = var['as'][-1][1].split(',')[2][:-1] # e.g. OCLOUDMODIFLM
570 ifBeg = 'IF (' + keyDimMerge + ') THEN\n'
571 ifEnd = '\nEND IF\n'
572 if var['opt']:
573 ifBeg = ifBeg + 'IF (PRESENT(' + var['n'] + ')) THEN\n IF (SIZE(' + \
574 var['n'] + ',1) > 0) THEN\n'
575 ifEnd = ifEnd + '\nEND IF\nEND IF'
576 argsMPPDB = var['n'] + ", " + "\"" + subRoutineName + " " + strMSG+var['n'] + "\""
577 return createExpr(ifBeg + "CALL MPPDB_CHECK(" + addD + argsMPPDB +
578 addLastDim + addSecondDimType + ")" + ifEnd)[0]
579 scopes = self.getScopes()
580 if scopes[0].path.split('/')[-1].split(':')[1][:4] == 'MODD':
581 return
582 for scope in scopes:
583 # Do not add MPPDB_CHEKS to :
584 # - MODULE or FUNCTION object,
585 # - interface subroutine from a MODI
586 # but only to SUBROUTINES
587 if 'sub:' in scope.path and 'func' not in scope.path and 'interface' not in scope.path:
588 subRoutineName = scope.path.split('/')[-1].split(':')[1]
589
590 # Look for all intent arrays only
591 arraysIn, arraysInOut, arraysOut = [], [], []
592 if not printsMode:
593 for var in scope.varList:
594 if var['arg'] and var['as'] and 'TYPE' not in var['t'] and \
595 'REAL' in var['t'] and var['scopePath'] == scope.path:
596 if var['i'] == 'IN':
597 arraysIn.append(var)
598 if var['i'] == 'INOUT':
599 arraysInOut.append(var)
600 if var['i'] == 'OUT':
601 arraysOut.append(var)
602 else:
603 for var in scope.varList:
604 if not var['t'] or var['t'] and 'TYPE' not in var['t']:
605 if var['i'] == 'IN':
606 arraysIn.append(var)
607 if var['i'] == 'INOUT':
608 arraysInOut.append(var)
609 if var['i'] == 'OUT':
610 arraysOut.append(var)
611 # Check if there is any intent variables
612 if len(arraysIn) + len(arraysInOut) + len(arraysOut) == 0:
613 break
614
615 # Add necessary module
616 if not printsMode:
617 scope.addModuleVar([(scope.path, 'MODE_MPPDB', None)])
618 else:
619 scope.addModuleVar([(scope.path, 'MODD_BLANK_n', ['LDUMMY1'])])
620
621 # Prepare some FORTRAN comments
622 commentIN = createElem('C', text='!Check all IN arrays', tail='\n')
623 commentINOUT = createElem('C', text='!Check all INOUT arrays', tail='\n')
624 commentOUT = createElem('C', text='!Check all OUT arrays', tail='\n')
625
626 # 1) variables IN and INOUT block (beggining of the routine)
627 if len(arraysIn) + len(arraysInOut) > 0:
628 if not printsMode:
629 ifMPPDBinit = createExpr("IF (MPPDB_INITIALIZED) THEN\n END IF")[0]
630 else:
631 ifMPPDBinit = createExpr("IF (LDUMMY1) THEN\n END IF")[0]
632 ifMPPDB = ifMPPDBinit.find('.//{*}if-block')
633
634 # Variables IN
635 if len(arraysIn) > 0:
636 ifMPPDB.insert(1, commentIN)
637 for i, var in enumerate(arraysIn):
638 if not printsMode:
639 ifMPPDB.insert(2 + i, addMPPDB_CHECK_statement(var, subRoutineName,
640 strMSG='beg:'))
641 else:
642 ifMPPDB.insert(2 + i, addPrints_statement(var,
643 typeofPrints='minmax'))
644 ifMPPDB.insert(3 + i, addPrints_statement(var,
645 typeofPrints='shape'))
646
647 # Variables INOUT
648 if len(arraysInOut) > 0:
649 shiftLineNumber = 2 if len(arraysIn) > 0 else 1
650 if not printsMode:
651 ifMPPDB.insert(len(arraysIn) + shiftLineNumber, commentINOUT)
652 else:
653 ifMPPDB.insert(len(arraysIn)*2 + shiftLineNumber-1, commentINOUT)
654
655 for i, var in enumerate(arraysInOut):
656 if not printsMode:
657 ifMPPDB.insert(len(arraysIn) + shiftLineNumber + 1 + i,
658 addMPPDB_CHECK_statement(var, subRoutineName,
659 strMSG='beg:'))
660 else:
661 ifMPPDB.insert(len(arraysIn) + shiftLineNumber + 1 + i,
662 addPrints_statement(var, typeofPrints='minmax'))
663
664 # Add the new IN and INOUT block
665 scope.insertStatement(scope.indent(ifMPPDBinit), first=True)
666
667 # 2) variables INOUT and OUT block (end of the routine)
668 if len(arraysInOut) + len(arraysOut) > 0:
669 if not printsMode:
670 ifMPPDBend = createExpr("IF (MPPDB_INITIALIZED) THEN\n END IF")[0]
671 else:
672 ifMPPDBend = createExpr("IF (LDUMMY1) THEN\n END IF")[0]
673 ifMPPDB = ifMPPDBend.find('.//{*}if-block')
674
675 # Variables INOUT
676 if len(arraysInOut) > 0:
677 ifMPPDB.insert(1, commentINOUT)
678 for i, var in enumerate(arraysInOut):
679 if not printsMode:
680 ifMPPDB.insert(2 + i, addMPPDB_CHECK_statement(var, subRoutineName,
681 strMSG='end:'))
682 else:
683 ifMPPDB.insert(2 + i, addPrints_statement(var,
684 typeofPrints='minmax'))
685
686 # Variables OUT
687 if len(arraysOut) > 0:
688 shiftLineNumber = 2 if len(arraysInOut) > 0 else 1
689 if not printsMode:
690 ifMPPDB.insert(len(arraysInOut) + shiftLineNumber, commentOUT)
691 else:
692 ifMPPDB.insert(len(arraysInOut)*2 + shiftLineNumber-1, commentOUT)
693 for i, var in enumerate(arraysOut):
694 if not printsMode:
695 ifMPPDB.insert(len(arraysInOut) + shiftLineNumber + 1 + i,
696 addMPPDB_CHECK_statement(var, subRoutineName,
697 strMSG='end:'))
698 else:
699 ifMPPDB.insert(len(arraysInOut) + shiftLineNumber + 1 + i,
700 addPrints_statement(var, typeofPrints='minmax'))
701
702 # Add the new INOUT and OUT block
703 scope.insertStatement(scope.indent(ifMPPDBend), first=False)
704
705 @debugDecor
706 def addStack(self, model, stopScopes, parserOptions=None, wrapH=False):
707 """
708 Add specific allocations of local arrays on the fly for GPU
709 :param model : 'MESONH' or 'AROME' for specific objects related to the allocator or stack
710 :param stopScopes: scope paths where we stop to add stack
711 :param parserOptions, wrapH: see the PYFT class
712
713 Stacks are added to all routines called by the scopes listed in stopScopes
714 """
715 if model == 'AROME':
716 # The AROME transformation needs an additional parameter
717 # We apply the transformation only if the routine is called
718 # from a scope within stopScopes
719 for scope in [scope for scope in self.getScopes()
720 if scope.path in stopScopes or
721 self.tree.isUnderStopScopes(scope.path, stopScopes)]:
722 # Intermediate transformation, needs cpp to be completed
723 # This version would be OK if we didn't need to read again the files with fxtran
724 # after transformation
725 # nb = scope.modifyAutomaticArrays(
726 # declTemplate="temp({type}, {name}, ({shape}))",
727 # startTemplate="alloc({name})")
728
729 # Full transformation, using CRAY pointers
730 # In comparison with the original transformation of Philippe,
731 # we do not call SOF with __FILE__ and __LINE__ because it breaks
732 # future reading with fxtran
733 nb = scope.modifyAutomaticArrays(
734 declTemplate="{type}, DIMENSION({shape}) :: {name}; " +
735 "POINTER(IP_{name}_, {name})",
736 startTemplate="IP_{name}_=YLSTACK%L(KIND({name})/4);" +
737 "YLSTACK%L(KIND({name})/4)=" +
738 "YLSTACK%L(KIND({name})/4)+" +
739 "KIND({name})*SIZE({name});" +
740 "IF(YLSTACK%L(KIND({name})/4)>" +
741 "YLSTACK%U(KIND({name})/4))" +
742 "CALL SOF('" + scope.getFileName() + ":{name}', " +
743 "KIND({name}))")
744
745 if nb > 0:
746 # Some automatic arrays have been modified,
747 # we need to add an argument to the routine
748 scope.addArgInTree('YDSTACK', 'TYPE (STACK) :: YDSTACK', -1,
749 stopScopes, moduleVarList=[('STACK_MOD', ['STACK', 'SOF'])],
750 otherNames=['YLSTACK'],
751 parserOptions=parserOptions, wrapH=wrapH)
752
753 # Copy the stack to a local variable and use it for call statements
754 # this operation must be done after the call to addArgInTree
755 scope.addVar([[scope.path, 'YLSTACK', 'TYPE (STACK) :: YLSTACK', None]])
756 scope.insertStatement(createExpr('YLSTACK=YDSTACK')[0], True)
757 for argN in scope.findall('.//{*}call-stmt/{*}arg-spec/' +
758 '{*}arg/{*}arg-N/../{*}named-E/{*}N'):
759 if n2name(argN) == 'YDSTACK':
760 argN[0].text = 'YLSTACK'
761
762 elif model == 'MESONH':
763 for scope in self.getScopes():
764 # We apply the transformation only if the routine is called
765 # from a scope within stopScopes
766 if (not self.tree.isValid) or stopScopes is None or scope.path in stopScopes or \
767 self.tree.isUnderStopScopes(scope.path, stopScopes):
768 nb = scope.modifyAutomaticArrays(
769 declTemplate="{type}, DIMENSION({doubledotshape}), " +
770 "POINTER, CONTIGUOUS :: {name}",
771 startTemplate="CALL MNH_MEM_GET({name}, {lowUpList})")
772 if nb > 0:
773 # Some automatic arrays have been modified
774 # we need to add the stack module,
775 scope.addModuleVar([(scope.path, 'MODE_MNH_ZWORK',
776 ['MNH_MEM_GET', 'MNH_MEM_POSITION_PIN',
777 'MNH_MEM_RELEASE'])])
778 # to pin the memory position,
779 scope.insertStatement(
780 createExpr(f"CALL MNH_MEM_POSITION_PIN('{scope.path}')")[0], True)
781 # and to realease the memory
782 scope.insertStatement(
783 createExpr(f"CALL MNH_MEM_RELEASE('{scope.path}')")[0], False)
784 else:
785 raise PYFTError('Stack is implemented only for AROME and MESONH models')
786
787 @debugDecor
788 def inlineContainedSubroutinesPHYEX(self, simplify=False):
789 """
790 Inline all contained subroutines in the main subroutine
791 Steps :
792 - Identify contained subroutines
793 - Look for all CALL statements, check if it is a containted routines; if yes, inline
794 - Delete the containted routines
795 :param simplify: try to simplify code (construct or variables becoming useless)
796 :param loopVar: None to create new variable for each added DO loop
797 (around ELEMENTAL subroutine calls)
798 or a function that return the name of the variable to use for
799 the loop control. This function returns a string (name of the variable),
800 or True to create a new variable, or False to not transform this statement
801 The functions takes as arguments:
802 - lower and upper bounds as defined in the declaration statement
803 - lower and upper bounds as given in the statement
804 - name of the array
805 - index of the rank
806 """
807 return self.inlineContainedSubroutines(simplify=simplify, loopVar=_loopVarPHYEX)
808
809 @debugDecor
810 @updateVarList
811 def removeIJDim(self, stopScopes, parserOptions=None, wrapH=False, simplify=False):
812 """
813 Transform routines to be called in a loop on columns
814 :param stopScopes: scope paths where we stop to add the D argument (if needed)
815 :param parserOptions, wrapH: see the PYFT class
816 :param simplify: try to simplify code (remove useless dimensions in call)
817
818 ComputeInSingleColumn :
819 - Remove all Do loops on JI and JJ
820 - Initialize former indexes JI, JJ, JIJ to first array element:
821 JI=D%NIB, JJ=D%NJB, JIJ=D%NIJB
822 - If simplify is True, replace (:,*) on I/J/IJ dimension on argument
823 with explicit (:,*) on CALL statements:
824 e.g. CALL FOO(D, A(:,JK,1), B(:,:))
825 ==> CALL FOO(D, A(JIJ,JK,1), B(:,:)) only if the target argument is not an array
826 """
827
828 indexToCheck = {'JI': ('D%NIB', 'D%NIT'),
829 'JJ': ('D%NJB', 'D%NJT'),
830 'JIJ': ('D%NIJB', 'D%NIJT')}
831
832 def slice2index(namedE, scope):
833 """
834 Transform a slice on the horizontal dimension into an index
835 Eg.: X(1:D%NIJT, 1:D%NKT) => X(JIJ, 1:D%NKT) Be careful, this array is not contiguous.
836 X(1:D%NIJT, JK) => X(JIJ, JK)
837 :param namedE: array to transform
838 :param scope: scope where the array is
839 """
840 # Loop on all array dimensions
841 for isub, sub in enumerate(namedE.findall('./{*}R-LT/{*}array-R/' +
842 '{*}section-subscript-LT/' +
843 '{*}section-subscript')):
844 if ':' in alltext(sub):
845 loopIndex, _, _ = scope.findIndexArrayBounds(namedE, isub, _loopVarPHYEX)
846 if loopIndex in indexToCheck: # To be transformed
847 if sub.text == ':':
848 sub.text = None
849 lowerBound = createElem('lower-bound')
850 sub.insert(0, lowerBound)
851 else:
852 lowerBound = sub.find('./{*}lower-bound')
853 lowerBound.tail = ''
854 for item in lowerBound:
855 lowerBound.remove(item)
856 upperBound = sub.find('./{*}upper-bound')
857 if upperBound is not None:
858 sub.remove(upperBound)
859 lowerBound.append(createExprPart(loopIndex))
860 if loopIndex not in indexRemoved:
861 indexRemoved.append(loopIndex)
862 # Transform array-R/section-subscript-LT/section-subscript
863 # into parens-R>/element-LT/element if needed
864 if ':' not in alltext(namedE.find('./{*}R-LT/{*}array-R/{*}section-subscript-LT')):
865 namedE.find('./{*}R-LT/{*}array-R').tag = f'{{{NAMESPACE}}}parens-R'
866 namedE.find('./{*}R-LT/{*}parens-R/' +
867 '{*}section-subscript-LT').tag = f'{{{NAMESPACE}}}element-LT'
868 for ss in namedE.findall('./{*}R-LT/{*}parens-R/' +
869 '{*}element-LT/{*}section-subscript'):
870 ss.tag = f'{{{NAMESPACE}}}element'
871 lowerBound = ss.find('./{*}lower-bound')
872 for item in lowerBound:
873 ss.append(item)
874 ss.remove(lowerBound)
875
876 # 0 - Preparation
877 self.addArrayParentheses()
879 if simplify:
880 self.attachArraySpecToEntity()
881 hUupperBounds = [v[1] for v in indexToCheck.values()] # Upper bounds for horizontal dim
882
883 # Loop on all scopes (reversed order); except functions (in particular
884 # FWSED from ice4_sedimentation_stat)
885 for scope in [scope for scope in self.getScopes()[::-1]
886 if 'func:' not in scope.path and
887 (scope.path in stopScopes or
888 self.tree.isUnderStopScopes(scope.path, stopScopes,
889 includeInterfaces=True))]:
890 indexRemoved = []
891
892 # 1 - Remove all DO loops on JI and JJ for preparation to compute on KLEV only
893 # Look for all do-nodes, check if the loop-index is one of the authorized
894 # list (indexToCheck), if found, removes it
895 for doNode in scope.findall('.//{*}do-construct')[::-1]:
896 for loopI in doNode.findall('./{*}do-stmt/{*}do-V/{*}named-E/{*}N'):
897 loopIname = n2name(loopI).upper()
898 if loopIname in indexToCheck:
899 # Move the content of the doNode (except do-stmt and end_do_stmt)
900 # in parent node
901 par = scope.getParent(doNode)
902 index = list(par).index(doNode)
903 for item in doNode[1:-1][::-1]:
904 par.insert(index, item)
905 par.remove(doNode) # remove the do_construct
906 if loopIname not in indexRemoved:
907 indexRemoved.append(loopIname)
908
909 # 2 - Reduce horizontal dimensions for intrinsic array functions
910 # SUM(X(:,:)) => SUM(X(JI, X))
911 # In the simplify==True case, SUM(X(:,:)) becomes SUM(X(:)) by removing first dimension
912 for intr in scope.findall('.//{*}R-LT/{*}parens-R/../..'):
913 intrName = n2name(intr.find('./{*}N')).upper()
914 if intrName in ('PACK', 'UNPACK', 'COUNT', 'MAXVAL', 'MINVAL', 'ALL', 'ANY', 'SUM'):
915 # Is it part of an expression or of an affectation statement?
916 # eg: CALL(UNPACK(Y(:), MASK=G(:,:)) * Z(:))
917 # or X(:,:) = UNPACK(Y(:), MASK=G(:,:)) * Z(:)
918 # If yes, we also need to transform X and Z
919 # if not, only arrays inside the function are transformed
920 parToUse = intr
921 par = intr
922 while par is not None and not isStmt(par):
923 par = scope.getParent(par)
924 if tag(par) in ('a-stmt', 'op-E'):
925 parToUse = par
926
927 # 2.1 Loop on all arrays in the expression using this intrinsic function
928 # to replace horizontal dimensions by indexes
929 for namedE in parToUse.findall('.//{*}R-LT/{*}array-R/../..'):
930 slice2index(namedE, scope)
931
932 # 2.2 Replace intrinsic function when argument becomes a scalar
933 if intr.find('.//{*}R-LT/{*}array-R') is None:
934 if intrName in ('MAXVAL', 'MINVAL', 'SUM', 'ALL', 'ANY'):
935 # eg: MAXVAL(X(:)) => MAXVAL(X(JI)) => X(JI)
936 parens = intr.find('./{*}R-LT/{*}parens-R')
937 parens.tag = f'{{{NAMESPACE}}}parens-E'
938 intrPar = scope.getParent(intr)
939 intrPar.insert(list(intrPar).index(intr), parens)
940 intrPar.remove(intr)
941 elif intrName == 'COUNT':
942 # eg: COUNT(X(:)) => COUNT(X(JI)) => MERGE(1, 0., X(JI))
943 nodeN = intr.find('./{*}N')
944 for item in nodeN[1:]:
945 nodeN.remove(item)
946 nodeN.find('./{*}n').text = 'MERGE'
947 elementLT = intr.find('./{*}R-LT/{*}parens-R/{*}element-LT')
948 for val in (1, 0):
949 element = createElem('element', tail=', ')
950 element.append(createExprPart(val))
951 elementLT.insert(0, element)
952
953 if simplify:
954 # 3 - Remove useless dimensions
955 # Arrays only on horizontal dimensions are transformed into scalars
956 # - at declaration "REAL :: P(D%NIT)" => "REAL :: P"
957 # - during call "CALL FOO(P(:)" => "CALL FOO(P)"
958 # "CALL FOO(Z(:,IK)" => "CALL FOO(Z(JIJ,IK)"
959 # - but "CALL FOO(Z(:,:)" is kept untouched
960 # All arrays are transformed except IN/OUT arrays of the top subroutine (stopScopes)
961 # that cannot be transformed into scalar
962
963 # At least for rain_ice.F90, inlining must be performed before executing this code
964 assert scope.find('.//{*}include') is None and \
965 scope.find('.//{*}include-stmt') is None, \
966 "inlining must be performed before removing horizontal dimensions"
967
968 if scope.path in stopScopes:
969 # List of dummy arguments whose shape cannot be modified
970 preserveShape = [v['n'] for v in scope.varList if v['arg']]
971 else:
972 preserveShape = []
973
974 # 4 - For all subroutines or modi_ interface
975 if 'sub:' in scope.path:
976 # Remove suppressed dimensions "Z(JIJI)" => "Z"
977 # We cannot do this based upon declaration transformation because an array can
978 # be declared in one scope and used in another sub-scope
979 for namedE in scope.findall('.//{*}named-E/{*}R-LT/{*}parens-R/../..'):
980 if n2name(namedE.find('./{*}N')).upper() not in preserveShape:
981 var = scope.varList.findVar(n2name(namedE.find('./{*}N')).upper())
982 if var is not None and var['as'] is not None and len(var['as']) > 0:
983 subs = namedE.findall('./{*}R-LT/{*}parens-R/' +
984 '{*}element-LT/{*}element')
985 if (len(subs) == 1 and var['as'][0][1] in hUupperBounds) or \
986 (len(subs) == 2 and (var['as'][0][1] in hUupperBounds and
987 var['as'][1][1] in hUupperBounds)):
988 namedE.remove(namedE.find('./{*}R-LT'))
989
990 # Remove (:) or (:,:) for horizontal array in call-statement
991 # or replace ':' by index
992 for call in scope.findall('.//{*}call-stmt'):
993 for namedE in call.findall('./{*}arg-spec//{*}named-E'):
994 subs = namedE.findall('.//{*}section-subscript')
995 var = scope.varList.findVar(n2name(namedE.find('./{*}N')).upper())
996 if len(subs) > 0 and (var is None or var['as'] is None or
997 len(var['as']) < len(subs)):
998 # Before adding a warning, functions (especially unpack) must
999 # be recognised
1000 # logging.warning(("Don't know if first dimension of {name} must " +
1001 # "be modified or not -> kept untouched"
1002 # ).format(name=alltext(namedE)))
1003 remove = False # to remove completly the parentheses
1004 index = False # to transform ':' into index
1005 elif (len(subs) >= 2 and
1006 ':' in alltext(subs[0]) and var['as'][0][1] in hUupperBounds and
1007 ':' in alltext(subs[1]) and var['as'][1][1] in hUupperBounds):
1008 # eg: CALL(P(:, :)) with SIZE(P, 1) == D%NIT and SIZE(P, 2) == D%NJT
1009 remove = len(subs) == 2
1010 index = (len(subs) > 2 and
1011 len([sub for sub in subs if ':' in alltext(sub)]) == 2)
1012 elif (len(subs) >= 1 and
1013 ':' in alltext(subs[0]) and var['as'][0][1] in hUupperBounds):
1014 # eg: CALL(P(:)) with SIZE(P, 1) == D%NJT
1015 remove = len(subs) == 1
1016 index = (len(subs) > 1 and
1017 len([sub for sub in subs if ':' in alltext(sub)]) == 1)
1018 else:
1019 remove = False
1020 index = False
1021 if remove:
1022 if n2name(namedE.find('./{*}N')).upper() in preserveShape:
1023 slice2index(namedE, scope)
1024 else:
1025 nodeRLT = namedE.find('.//{*}R-LT')
1026 scope.getParent(nodeRLT).remove(nodeRLT)
1027 if index:
1028 slice2index(namedE, scope)
1029
1030 # Remove dimensions in variable declaration statements
1031 # This modification must be done after other modifications so that
1032 # the findVar method still return an array
1033 for decl in scope.findall('.//{*}T-decl-stmt/{*}EN-decl-LT/{*}EN-decl'):
1034 name = n2name(decl.find('./{*}EN-N/{*}N')).upper()
1035 if name not in preserveShape:
1036 varsShape = decl.findall('.//{*}shape-spec-LT')
1037 for varShape in varsShape:
1038 subs = varShape.findall('.//{*}shape-spec')
1039 if (len(subs) == 1 and alltext(subs[0]) in hUupperBounds) or \
1040 (len(subs) == 2 and (alltext(subs[0]) in hUupperBounds and
1041 alltext(subs[1]) in hUupperBounds)):
1042 # Transform array declaration into scalar declaration
1043 itemToRemove = scope.getParent(varShape)
1044 scope.getParent(itemToRemove).remove(itemToRemove)
1045 # We should set scope.varList to None here to clear the cache
1046 # but we don't to save some computational time
1047
1048 # 4 - Values for removed indexes
1049 for loopIndex in indexRemoved:
1050 # Initialize former indexes JI,JJ,JIJ to first array element:
1051 # JI=D%NIB, JJ=D%NJB, JIJ=D%NIJB
1052 scope.insertStatement(
1053 createExpr(loopIndex + " = " + indexToCheck[loopIndex][0])[0], True)
1054 if len(indexRemoved) > 0:
1055 scope.addArgInTree('D', 'TYPE(DIMPHYEX_t) :: D',
1056 0, stopScopes, moduleVarList=[('MODD_DIMPHYEX', ['DIMPHYEX_t'])],
1057 parserOptions=parserOptions, wrapH=wrapH)
1058 # Check loop index presence at declaration of the scope
1059 scope.addVar([[scope.path, loopIndex, 'INTEGER :: ' + loopIndex, None]
1060 for loopIndex in indexRemoved
1061 if scope.varList.findVar(loopIndex, exactScope=True) is None])
1062
1064 """
1065 :return: the list the variables needed by the mnh_expand directives
1066 """
1067
1068 result = []
1069 # Look for variables needed for the mnh_expand directives
1070 for node in self.findall('.//{*}C'):
1071 if node.text.startswith('!$mnh_expand_array') or \
1072 node.text.startswith('!$mnh_expand_where'):
1073 elems = node.text.split('(')[1].split(')')[0].split(',')
1074 result.extend([v.strip().upper() for v in [e.split('=')[0] for e in elems]])
1075 return result
1076
1077 @debugDecor
1078 def removePHYEXUnusedLocalVar(self, excludeList=None, simplify=False):
1079 """
1080 Remove unused local variables (dummy and module variables are not suppressed)
1081 This function is identical to variables.removeUnusedLocalVar except that this one
1082 is specific to the PHYEX code and take into account the mnh_expand directives.
1083 :param excludeList: list of variable names to exclude from removal (even if unused)
1084 :param simplify: try to simplify code (if we delete a declaration statement that used a
1085 variable as kind selector, and if this variable is not used else where,
1086 we also delete it)
1087 """
1088
1089 if excludeList is None:
1090 excludeList = []
1091 return self.removeUnusedLocalVar(excludeList=excludeList + self._mnh_expand_var(),
1092 simplify=simplify)
1093
1094 @debugDecor
1095 def checkPHYEXUnusedLocalVar(self, mustRaise=False, excludeList=None):
1096 """
1097 :param mustRaise: True to raise
1098 :param excludeList: list of variable names to exclude from the check
1099 Issue a logging.warning if there are unused local variables
1100 If mustRaise is True, issue a logging.error instead and raise an error
1101 """
1102
1103 if excludeList is None:
1104 excludeList = []
1105 return self.checkUnusedLocalVar(mustRaise=mustRaise,
1106 excludeList=excludeList + self._mnh_expand_var())
1107
1108 @debugDecor
1109 def expandAllArraysPHYEX(self, concurrent=False):
1110 """
1111 Transform array syntax into DO loops
1112 :param concurrent: use 'DO CONCURRENT' instead of simple 'DO' loops
1113 """
1114
1115 # For simplicity, all functions (not only array functions) have been searched
1116 # in the PHYEX source code
1117 funcList = ['AA2', 'AA2W', 'AF3', 'AM3', 'ARTH', 'BB3', 'BB3W', 'COEFJ', 'COLL_EFFI',
1118 'DELTA', 'DELTA_VEC', 'DESDTI', 'DESDTW', 'DQSATI_O_DT_1D',
1119 'DQSATI_O_DT_2D_MASK', 'DQSATI_O_DT_3D', 'DQSATW_O_DT_1D',
1120 'DQSATW_O_DT_2D_MASK', 'DQSATW_O_DT_3D', 'DSDD', 'DXF', 'DXM', 'DYF',
1121 'DYM', 'DZF', 'DZM', 'ESATI', 'ESATW', 'FUNCSMAX', 'GAMMA_INC', 'GAMMA_X0D',
1122 'GAMMA_X1D', 'GENERAL_GAMMA', 'GET_XKER_GWETH', 'GET_XKER_N_GWETH',
1123 'GET_XKER_N_RACCS', 'GET_XKER_N_RACCSS', 'GET_XKER_N_RDRYG',
1124 'GET_XKER_N_SACCRG', 'GET_XKER_N_SDRYG', 'GET_XKER_N_SWETH', 'GET_XKER_RACCS',
1125 'GET_XKER_RACCSS', 'GET_XKER_RDRYG', 'GET_XKER_SACCRG', 'GET_XKER_SDRYG',
1126 'GET_XKER_SWETH', 'GX_M_M', 'GX_M_U', 'GX_U_M', 'GX_V_UV', 'GX_W_UW', 'GY_M_M',
1127 'GY_M_V', 'GY_U_UV', 'GY_V_M', 'GY_W_VW', 'GZ_M_M', 'GZ_M_W', 'GZ_U_UW',
1128 'GZ_V_VW', 'GZ_W_M', 'HYPGEO', 'ICENUMBER2', 'LEAST_LL', 'LNORTH_LL',
1129 'LSOUTH_LL', 'LWEST_LL', 'MOMG', 'MXF', 'MXM', 'MYF', 'MYM', 'MZF', 'MZM',
1130 'QSATI_0D', 'QSATI_1D', 'QSATI_2D', 'QSATI_2D_MASK', 'QSATI_3D',
1131 'QSATMX_TAB', 'QSATW_0D', 'QSATW_1D', 'QSATW_2D', 'QSATW_2D_MASK',
1132 'QSATW_3D', 'RECT', 'REDIN', 'SINGL_FUNCSMAX', 'SM_FOES_0D', 'SM_FOES_1D',
1133 'SM_FOES_2D', 'SM_FOES_2D_MASK', 'SM_FOES_3D', 'SM_PMR_HU_1D', 'SM_PMR_HU_3D',
1134 'TIWMX_TAB', 'TO_UPPER', 'ZRIDDR', 'GAMMLN', 'COUNTJV2D', 'COUNTJV3D', 'UPCASE']
1135
1136 return self.removeArraySyntax(concurrent=concurrent, useMnhExpand=False,
1137 loopVar=_loopVarPHYEX, reuseLoop=False,
1138 funcList=funcList, updateMemSet=True, updateCopy=True)
1139
1140 @debugDecor
1142 """
1143 Convert intrinsic math functions **, LOG, ATAN, **2, **3, **4, EXP, COS, SIN, ATAN2
1144 into a self defined function BR_ for MesoNH CPU/GPU bit-reproductibility
1145 """
1146 # Power integer allowed for BR_Pn and functions converted (from modi_bitrep.f90)
1147 powerBRList = [2, 3, 4]
1148 mathBRList = ['ALOG', 'LOG', 'EXP', 'COS', 'SIN', 'ASIN', 'ATAN', 'ATAN2']
1149
1150 for scope in self.getScopes():
1151 # 1/2 Look for all operations and seek for power **
1152 # <f:op-E>
1153 # ... ==> leftOfPow
1154 # <f:op>
1155 # <f:o>**</f:o>
1156 # </f:op>
1157 # ... ==> rightOfPow
1158 # </f:op-E>
1159 for opo in scope.findall('.//{*}o'):
1160 if alltext(opo) == '**':
1161 op = scope.getParent(opo)
1162 opE = scope.getParent(opo, level=2)
1163 parOfopE = scope.getParent(opo, level=3)
1164 # Save the position of the opE that will be converted
1165 index = list(parOfopE).index(opE)
1166
1167 # Get the next/previous object after/before the ** operator which are
1168 # the siblings of the parent of <f:o>*</f:o>
1169 rightOfPow = scope.getSiblings(op, after=True, before=False)[0]
1170 leftOfPow = scope.getSiblings(op, after=False, before=True)[0]
1171
1172 # Prepare the object that will contain the left and right of Pow
1173 nodeRLT = createElem('R-LT')
1174 parensR = createElem('parens-R', text='(', tail=')')
1175 elementLT = createElem('element-LT')
1176
1177 # Regarding the right part of pow(), build a new node expression :
1178 # If it is a number and check only for 2, 3 and 4 (e.g. A**2, B**3, D**4 etc)
1179 if tag(rightOfPow) == 'literal-E':
1180 # Handle '2.' and '2.0'
1181 powerNumber = int(alltext(rightOfPow).replace('.', ''))
1182 if powerNumber in powerBRList:
1183 # <f:named-E>
1184 # <f:N>
1185 # <f:n>BR_Pn</f:n>
1186 # </f:N>
1187 # <f:R-LT>
1188 # <f:parens-R>(
1189 # <f:element-LT>
1190 # <f:element>
1191 # ... ==> leftOfPow
1192 # </f:element>,
1193 # </f:element-LT>
1194 # </f:parens-R>)
1195 # </f:R-LT>
1196 # </f:named-E>
1197 nodeBRP = createExprPart('BR_P' + str(powerNumber))
1198 element = createElem('element')
1199 element.append(leftOfPow)
1200 elementLT.append(element)
1201 # If the right part of pow() is not a number OR it is a number
1202 # except 2, 3 or 4 (powerBRList)
1203 if tag(rightOfPow) != 'literal-E' or \
1204 (tag(rightOfPow) == 'literal-E' and
1205 int(alltext(rightOfPow).replace('.', '')) not in powerBRList):
1206 # <f:named-E>
1207 # <f:N>
1208 # <f:n>BR_POW</f:n> or <f:n>BR_Pn</f:n>
1209 # </f:N>
1210 # <f:R-LT>
1211 # <f:parens-R>(
1212 # <f:element-LT>
1213 # <f:element>
1214 # ... ==> leftOfPow
1215 # </f:element>,
1216 # <f:element>
1217 # ... ==> rightOfPow
1218 # </f:element>
1219 # </f:element-LT>
1220 # </f:parens-R>)
1221 # </f:R-LT>
1222 # </f:named-E>
1223 nodeBRP = createExprPart('BR_POW')
1224 leftElement = createElem('element', tail=',')
1225 leftElement.append(leftOfPow)
1226 rightElement = createElem('element')
1227 rightElement.append(rightOfPow)
1228 elementLT.append(leftElement)
1229 elementLT.append(rightElement)
1230
1231 # Insert the RLT object as a sibling of the BR_ object,
1232 # e.g. instead of the old object
1233 parensR.append(elementLT)
1234 nodeRLT.append(parensR)
1235 nodeBRP.insert(1, nodeRLT)
1236 nodeBRP.tail = opE.tail
1237 parOfopE.remove(opE)
1238 parOfopE.insert(index, nodeBRP)
1239
1240 # Add necessary module in the current scope
1241 scope.addModuleVar([(scope.path, 'MODI_BITREP', None)])
1242
1243 # 2/2 Look for all specific functions LOG, ATAN, EXP,etc
1244 for nnn in scope.findall('.//{*}named-E/{*}N/{*}n'):
1245 if alltext(nnn).upper() in mathBRList:
1246 if alltext(nnn).upper() == 'ALOG':
1247 nnn.text = 'BR_LOG'
1248 else:
1249 nnn.text = 'BR_' + nnn.text
1250 # Add necessary module in the current scope
1251 scope.addModuleVar([(scope.path, 'MODI_BITREP', None)])
1252
1253 @debugDecor
1254 @updateVarList
1256 """
1257 Convert all calling of functions and gradient present in shumansGradients
1258 table into the use of subroutines
1259 and use mnh_expand_directives to handle intermediate computations
1260 """
1261 def getDimsAndMNHExpandIndexes(zshugradwkDim, dimWorkingVar=''):
1262 dimSuffRoutine = ''
1263 if zshugradwkDim == 1:
1264 dimSuffRoutine = '2D' # e.g. in turb_ver_dyn_flux : MZM(ZCOEFS(:,IKB))
1265 dimSuffVar = '1D'
1266 mnhExpandArrayIndexes = 'JIJ=IIJB:IIJE'
1267 localVariables = ['JIJ']
1268 elif zshugradwkDim == 2:
1269 dimSuffVar = '2D'
1270 if 'D%NKT' in dimWorkingVar:
1271 mnhExpandArrayIndexes = 'JIJ=IIJB:IIJE,JK=1:IKT'
1272 localVariables = ['JIJ', 'JK']
1273 elif 'D%NIT' in dimWorkingVar and 'D%NJT' in dimWorkingVar:
1274 # only found in turb_hor*
1275 mnhExpandArrayIndexes = 'JI=1:IIT,JJ=1:IJT'
1276 localVariables = ['JI', 'JJ']
1277 dimSuffRoutine = '2D' # e.g. in turb_hor : MZM(PRHODJ(:,:,IKB))
1278 else:
1279 # raise PYFTError('mnhExpandArrayIndexes construction case ' +
1280 # 'is not handled, case for zshugradwkDim == 2, ' +
1281 # "dimWorkingVar = ' + dimWorkingVar)
1282 dimSuffRoutine = ''
1283 mnhExpandArrayIndexes = 'JIJ=IIJB:IIJE,JK=1:IKT'
1284 localVariables = ['JIJ', 'JK']
1285 elif zshugradwkDim == 3: # case for turb_hor 3D variables
1286 dimSuffVar = '3D'
1287 mnhExpandArrayIndexes = 'JI=1:IIT,JJ=1:IJT,JK=1:IKT'
1288 localVariables = ['JI', 'JJ', 'JK']
1289 else:
1290 raise PYFTError('Shuman func to routine conversion not implemented ' +
1291 'for 4D+ dimensions variables')
1292 return dimSuffRoutine, dimSuffVar, mnhExpandArrayIndexes, localVariables
1293
1294 def FUNCtoROUTINE(scope, stmt, itemFuncN, localShumansCount, inComputeStmt,
1295 nbzshugradwk, zshugradwkDim, dimWorkingVar):
1296 """
1297 :param scope: node on which the calling function is present before transformation
1298 :param stmt: statement node (a-stmt or call-stmt) that contains the function(s) to be
1299 transformed
1300 :param itemFuncN: <n>FUNCTIONNAME</n> node
1301 :param localShumansCount: instance of the shumansGradients dictionnary
1302 for the given scope (which contains the number of times a
1303 function has been called within a transformation)
1304 :param dimWorkingVar: string of the declaration of a potential working variable
1305 depending on the array on wich the shuman is applied
1306 (e.g. MZM(PRHODJ(:,IKB));
1307 dimWorkingVar = 'REAL, DIMENSION(D%NIJT) :: ' )
1308 :return zshugradwk
1309 :return callStmt: the new CALL to the routines statement
1310 :return computeStmt: the a-stmt computation statement if there was an operation
1311 in the calling function in stmt
1312 :return localVariables: list of local variables needed for the mnh_expand directive
1313 """
1314 localVariables = []
1315 # Function name, parent and grandParent
1316 parStmt = scope.getParent(stmt)
1317 parItemFuncN = scope.getParent(itemFuncN) # <N><n>MZM</N></n>
1318 # <named-E><N><n>MZM</N></n> <R-LT><f:parens-R>(<f:element-LT><f:element>....
1319 grandparItemFuncN = scope.getParent(itemFuncN, level=2)
1320 funcName = alltext(itemFuncN)
1321
1322 # workingItem = Content of the function
1323 indexForCall = list(parStmt).index(stmt)
1324 if inComputeStmt:
1325 # one for !$mnh_expand, one for !$acc kernels added at the previous
1326 # call to FUNCtoROUTINE
1327 indexForCall -= 2
1328 siblsItemFuncN = scope.getSiblings(parItemFuncN, after=True, before=False)
1329 workingItem = siblsItemFuncN[0][0][0]
1330 # Case where & is present in the working item.
1331 # We must look for all contents until the last ')'
1332 if len(siblsItemFuncN[0][0]) > 1:
1333 # last [0] is to avoid getting the '( )' from the function
1334 workingItem = scope.updateContinuation(siblsItemFuncN[0][0], removeALL=True,
1335 align=False, addBegin=False)[0]
1336
1337 # Detect if the workingItem contains expressions, if so:
1338 # create a compute statement embedded by mnh_expand directives
1339 opE = workingItem.findall('.//{*}op-E')
1340 scope.removeArrayParenthesesInNode(workingItem)
1341 computeStmt, remaningArgsofFunc = [], ''
1342 dimSuffVar = str(zshugradwkDim) + 'D'
1343 dimSuffRoutine, dimSuffVar, mnhExpandArrayIndexes, _ = \
1344 getDimsAndMNHExpandIndexes(zshugradwkDim, dimWorkingVar)
1345 if len(opE) > 0:
1346 nbzshugradwk += 1
1347 computingVarName = 'ZSHUGRADWK'+str(nbzshugradwk)+'_'+str(zshugradwkDim)+'D'
1348 # Add the declaration of the new computing var and workingVar if not already present
1349 if not scope.varList.findVar(computingVarName):
1350 scope.addVar([[scope.path, computingVarName,
1351 dimWorkingVar + computingVarName, None]])
1352 else:
1353 # Case of nested shuman/gradients with a working variable already declared.
1354 # dimWorkingVar is only set again for mnhExpandArrayIndexes
1355 computeVar = scope.varList.findVar(computingVarName)
1356 dimWorkingVar = 'REAL, DIMENSION('
1357 for dims in computeVar['as'][:arrayDim]:
1358 dimWorkingVar += dims[1] + ','
1359 dimWorkingVar = dimWorkingVar[:-1] + ') ::'
1360
1361 dimSuffRoutine, dimSuffVar, mnhExpandArrayIndexes, localVariables = \
1362 getDimsAndMNHExpandIndexes(zshugradwkDim, dimWorkingVar)
1363
1364 # Insert the directives and the compute statement
1365 mnhOpenDir = "!$mnh_expand_array(" + mnhExpandArrayIndexes + ")"
1366 mnhCloseDir = "!$mnh_end_expand_array(" + mnhExpandArrayIndexes + ")"
1367 # workingItem[0] is to avoid getting elements unnecessary in gradient calls
1368 # such as , PDZZ in GZ_U_UW(PIMPL*ZRES + PEXPL*PUM, PDZZ)
1369 workingComputeItem = workingItem[0]
1370 # Only the first argument is saved; multiple arguments is not handled
1371 if len(workingItem) == 2:
1372 remaningArgsofFunc = ',' + alltext(workingItem[1])
1373 elif len(workingItem) > 2:
1374 raise PYFTError('ShumanFUNCtoCALL: expected maximum 1 argument in shuman ' +
1375 'function to transform')
1376 computeStmt = createExpr(computingVarName + " = " + alltext(workingComputeItem))[0]
1377 workingItem = computeStmt.find('.//{*}E-1')
1378
1379 parStmt.insert(indexForCall, createElem('C', text='!$acc kernels', tail='\n'))
1380 parStmt.insert(indexForCall + 1, createElem('C', text=mnhOpenDir, tail='\n'))
1381 parStmt.insert(indexForCall + 2, computeStmt)
1382 parStmt.insert(indexForCall + 3, createElem('C', text=mnhCloseDir, tail='\n'))
1383 parStmt.insert(indexForCall + 4, createElem('C',
1384 text='!$acc end kernels', tail='\n'))
1385 parStmt.insert(indexForCall + 5, createElem('C',
1386 text='!', tail='\n')) # To increase readibility
1387 indexForCall += 6
1388
1389 # Add the new CALL statement
1390 if zshugradwkDim == 1:
1391 dimSuffRoutine = '2D'
1392 workingVar = 'Z' + funcName + dimSuffVar + '_WORK' + str(localShumansCount[funcName])
1393 if funcName in ('GY_U_UV', 'GX_V_UV'):
1394 gpuGradientImplementation = '_DEVICE('
1395 newFuncName = funcName + dimSuffRoutine + '_DEVICE'
1396 else:
1397 gpuGradientImplementation = '_PHY(D, '
1398 newFuncName = funcName + dimSuffRoutine + '_PHY'
1399 callStmt = createExpr("CALL " + funcName + dimSuffRoutine + gpuGradientImplementation
1400 + alltext(workingItem) + remaningArgsofFunc +
1401 ", " + workingVar + ")")[0]
1402 parStmt.insert(indexForCall, callStmt)
1403
1404 # Remove the function/gradient from the original statement
1405 parOfgrandparItemFuncN = scope.getParent(grandparItemFuncN)
1406 indexWorkingVar = list(parOfgrandparItemFuncN).index(grandparItemFuncN)
1407 savedTail = grandparItemFuncN.tail
1408 parOfgrandparItemFuncN.remove(grandparItemFuncN)
1409
1410 # Add the working variable within the original statement
1411 xmlWorkingvar = createExprPart(workingVar)
1412 xmlWorkingvar.tail = savedTail
1413 parOfgrandparItemFuncN.insert(indexWorkingVar, xmlWorkingvar)
1414
1415 # Add the declaration of the shuman-gradient workingVar if not already present
1416 if not scope.varList.findVar(workingVar):
1417 scope.addVar([[scope.path, workingVar, dimWorkingVar + workingVar, None]])
1418
1419 return callStmt, computeStmt, nbzshugradwk, newFuncName, localVariables
1420
1421 shumansGradients = {'MZM': 0, 'MXM': 0, 'MYM': 0, 'MZF': 0, 'MXF': 0, 'MYF': 0,
1422 'DZM': 0, 'DXM': 0, 'DYM': 0, 'DZF': 0, 'DXF': 0, 'DYF': 0,
1423 'GZ_M_W': 0, 'GZ_W_M': 0, 'GZ_U_UW': 0, 'GZ_V_VW': 0,
1424 'GX_M_U': 0, 'GX_U_M': 0, 'GX_W_UW': 0, 'GX_M_M': 0,
1425 'GY_V_M': 0, 'GY_M_V': 0, 'GY_W_VW': 0, 'GY_M_M': 0,
1426 'GX_V_UV': 0, 'GY_U_UV': 0}
1427 scopes = self.getScopes()
1428 if len(scopes) == 0 or scopes[0].path.split('/')[-1].split(':')[1][:4] == 'MODD':
1429 return
1430 for scope in scopes:
1431 if 'sub:' in scope.path and 'func' not in scope.path \
1432 and 'interface' not in scope.path:
1433 # Init : look for all a-stmt and call-stmt which contains a shuman or
1434 # gradients function, and save it into a list foundStmtandCalls
1435 localVariablesToAdd = set()
1436 foundStmtandCalls, computeStmtforParenthesis = {}, []
1437 aStmt = scope.findall('.//{*}a-stmt')
1438 callStmts = scope.findall('.//{*}call-stmt')
1439 aStmtandCallStmts = aStmt + callStmts
1440 for stmt in aStmtandCallStmts:
1441 elemN = stmt.findall('.//{*}n')
1442 for el in elemN:
1443 if alltext(el) in list(shumansGradients):
1444 # Expand the single-line if-stmt necessary
1445 # to add all the new lines further.
1446 parStmt = scope.getParent(stmt)
1447 if tag(parStmt) == 'action-stmt':
1448 scope.changeIfStatementsInIfConstructs(
1449 singleItem=scope.getParent(parStmt))
1450
1451 if str(stmt) in foundStmtandCalls:
1452 foundStmtandCalls[str(stmt)][1] += 1
1453 else:
1454 foundStmtandCalls[str(stmt)] = [stmt, 1]
1455
1456 # For each a-stmt and call-stmt containing at least 1 shuman/gradient function
1457 subToInclude = set()
1458 for stmt in foundStmtandCalls:
1459 localShumansGradients = copy.deepcopy(shumansGradients)
1460 elemToLookFor = [foundStmtandCalls[stmt][0]]
1461 previousComputeStmt = []
1462 maxnbZshugradwk = 0
1463
1464 while len(elemToLookFor) > 0:
1465 nbzshugradwk = 0
1466 for elem in elemToLookFor:
1467 elemN = elem.findall('.//{*}n')
1468 for el in elemN:
1469 if alltext(el) in list(localShumansGradients.keys()):
1470 # Check the dimensions of the stmt objects in which the
1471 # function exist for handling selecting-index
1472 # shuman-function use
1473 # 1) if the stmt is from an a-astmt, check E1
1474 nodeE1var = foundStmtandCalls[stmt][0].findall(
1475 './/{*}E-1/{*}named-E/{*}N')
1476 if len(nodeE1var) > 0:
1477 var = scope.varList.findVar(alltext(nodeE1var[0]))
1478 allSubscripts = foundStmtandCalls[stmt][0].findall(
1479 './/{*}E-1//{*}named-E/{*}R-LT/' +
1480 '{*}array-R/{*}section-subscript-LT')
1481 # 2) if the stmt is from a call-stmt,
1482 # check the first <named-E><N> in the function
1483 else:
1484 elPar = scope.getParent(el, level=2) # MXM(...)
1485 callVar = elPar.findall('.//{*}named-E/{*}N')
1486 if alltext(el)[0] == 'G':
1487 # If it is a gradient, the array on which the gradient
1488 # is applied is the last argument
1489
1490 # callVar[-1] is array on which the gradient is applied
1491 var = scope.varList.findVar(alltext(callVar[-1]))
1492 shumanIsCalledOn = scope.getParent(callVar[-1])
1493 else:
1494 # Shumans
1495 var, inested = None, 0
1496 # pylint: disable-next=unsubscriptable-object
1497 while not var or len(var['as']) == 0:
1498 # While the var is not an array already declared
1499 # callVar[0] is the first array on which the
1500 # function is applied
1501 var = scope.varList.findVar(
1502 alltext(callVar[inested]))
1503 inested += 1
1504 shumanIsCalledOn = scope.getParent(callVar[inested-1])
1505 allSubscripts = shumanIsCalledOn.findall(
1506 './/{*}R-LT/{*}array-R/' +
1507 '{*}section-subscript-LT')
1508
1509 # if var: # Protection in case of nested functions,
1510 # var is not an array but None
1511 arrayDim = len(var['as'])
1512
1513 # Look for subscripts in case of array sub-selection
1514 # (such as 1 or IKB)
1515 if len(allSubscripts) > 0:
1516 for subLT in allSubscripts:
1517 for sub in subLT:
1518 lowerBound = sub.findall('.//{*}lower-bound')
1519 if len(lowerBound) > 0:
1520 if len(sub.findall('.//{*}upper-bound')) > 0:
1521 # For protection: not handled with
1522 # lower:upper bounds
1523 raise PYFTError('ShumanFUNCtoCALL does ' +
1524 'not handle conversion ' +
1525 'to routine of array ' +
1526 'subselection lower:upper' +
1527 ': how to set up the ' +
1528 'shape of intermediate ' +
1529 'arrays ?')
1530 # Handle change of dimensions for
1531 # selecting index for the working arrays
1532 arrayDim -= 1
1533
1534 # Build the dimensions declaration in case of
1535 # working/intermediate variable needed
1536 dimWorkingVar = ''
1537 if var:
1538 dimWorkingVar = 'REAL, DIMENSION('
1539 for dims in var['as'][:arrayDim]:
1540 dimWorkingVar += dims[1] + ','
1541 dimWorkingVar = dimWorkingVar[:-1] + ') ::'
1542
1543 # Add existing working variable with the name of the function
1544 localShumansGradients[alltext(el)] += 1
1545
1546 # To be sure that ending !comments after the statement is
1547 # not impacting the placement of the last !mnh_expand_array
1548 if foundStmtandCalls[stmt][0].tail:
1549 foundStmtandCalls[stmt][0].tail = \
1550 foundStmtandCalls[stmt][0].tail.replace('\n', '') + '\n'
1551 else:
1552 foundStmtandCalls[stmt][0].tail = '\n'
1553
1554 # Transform the function into a call statement
1555 result = FUNCtoROUTINE(scope, elem, el,
1556 localShumansGradients,
1557 elem in previousComputeStmt,
1558 nbzshugradwk, arrayDim,
1559 dimWorkingVar)
1560 (newCallStmt, newComputeStmt,
1561 nbzshugradwk, newFuncName, lv) = result
1562 localVariablesToAdd.update(lv)
1563 subToInclude.add(newFuncName)
1564 # Update the list of elements to check if there are still
1565 # remaining function to convert within the new call-stmt
1566 elemToLookFor.append(newCallStmt)
1567
1568 # If a new intermediate compute statement was created, it needs
1569 # to be checked and add Parenthesis to arrays for mnh_expand
1570 if len(newComputeStmt) > 0:
1571 elemToLookFor.append(newComputeStmt)
1572 computeStmtforParenthesis.append(newComputeStmt)
1573 # Allow to save that this newComputeStmt comes with 2
1574 # extra lines before and after
1575 # (mnh_expand and acc directives)
1576 previousComputeStmt.append(newComputeStmt)
1577 break
1578 # Check in old and new objects if there are still
1579 # remaining shuman/gradients functions
1580 elemToLookForNew = []
1581 for i in elemToLookFor:
1582 nodeNs = i.findall('.//{*}n')
1583 if len(nodeNs) > 0:
1584 for nnn in nodeNs:
1585 if alltext(nnn) in list(localShumansGradients):
1586 elemToLookForNew.append(i)
1587 break
1588 elemToLookFor = elemToLookForNew
1589 # Save the maximum number of necessary intermediate
1590 # computing variables ZSHUGRADWK
1591 if nbzshugradwk > maxnbZshugradwk:
1592 maxnbZshugradwk = nbzshugradwk
1593
1594 # Add parenthesis around all variables
1595 scope.addArrayParenthesesInNode(foundStmtandCalls[stmt][0])
1596
1597 # For the last compute statement, add mnh_expand and acc
1598 # kernels if not call statement
1599 if tag(foundStmtandCalls[stmt][0]) != 'call-stmt':
1600 # get mnhExpandArrayIndexes
1601 # Here dimSuffRoutine, dimSuffVar are not used
1602 dimSuffRoutine, dimSuffVar, mnhExpandArrayIndexes, lv = \
1603 getDimsAndMNHExpandIndexes(arrayDim, dimWorkingVar)
1604 localVariablesToAdd.update(lv)
1605
1606 parStmt = scope.getParent(foundStmtandCalls[stmt][0])
1607 indexForCall = list(parStmt).index(foundStmtandCalls[stmt][0])
1608 mnhOpenDir = "!$mnh_expand_array(" + mnhExpandArrayIndexes + ")"
1609 mnhCloseDir = "!$mnh_end_expand_array(" + mnhExpandArrayIndexes + ")"
1610 parStmt.insert(indexForCall,
1611 createElem('C', text="!$acc kernels", tail='\n'))
1612 parStmt.insert(indexForCall + 1,
1613 createElem('C', text=mnhOpenDir, tail='\n'))
1614 parStmt.insert(indexForCall + 3,
1615 createElem('C', text=mnhCloseDir, tail='\n'))
1616 parStmt.insert(indexForCall + 4,
1617 createElem('C', text="!$acc end kernels", tail='\n'))
1618 parStmt.insert(indexForCall + 5,
1619 createElem('C', text="!", tail='\n')) # For readibility
1620
1621 # For all saved intermediate newComputeStmt, add parenthesis around all variables
1622 for stmt in computeStmtforParenthesis:
1623 scope.addArrayParenthesesInNode(stmt)
1624
1625 # Add the use statements
1626 moduleVars = []
1627 for sub in sorted(subToInclude):
1628 if re.match(r'[MD][XYZ][MF](2D)?_PHY', sub):
1629 moduleVars.append((scope.path, 'MODE_SHUMAN_PHY', sub))
1630 else:
1631 for kind in ('M', 'U', 'V', 'W'):
1632 if re.match(r'G[XYZ]_' + kind + r'_[MUVW]{1,2}_PHY', sub):
1633 moduleVars.append((scope.path, f'MODE_GRADIENT_{kind}_PHY', sub))
1634 scope.addModuleVar(moduleVars)
1635
1636 # Add the missing local variables
1637 for varName in localVariablesToAdd:
1638 if not scope.varList.findVar(varName):
1639 var = {'as': [], 'asx': [],
1640 'n': varName, 'i': None, 't': 'INTEGER', 'arg': False,
1641 'use': False, 'opt': False, 'allocatable': False,
1642 'parameter': False, 'init': None, 'scopePath': scope.path}
1643 scope.addVar([[scope.path, var['n'], scope.varSpec2stmt(var), None]])
1644
1645 @debugDecor
1646 @noParallel
1647 @updateTree('signal')
1649 """
1650 build module files containing helpers to copy user type structures
1651 """
1652 for scope in self.getScopes():
1653 attribute = scope.find('./{*}T-stmt/{*}attribute')
1654 if scope.path.split('/')[-1].split(':')[0] == 'type' and \
1655 (attribute is None or alltext(attribute).upper() != 'ABSTRACT'):
1656 print(tag(scope), scope.find('./{*}T-stmt/{*}attribute'))
1657 typeName = scope.path.split('/')[-1].split(':')[1]
1658 filename = os.path.join(os.path.dirname(scope.getFileName()),
1659 "modd_util_{t}.F90".format(t=typeName.lower()))
1660 scope.tree.signal(filename)
1661 with open(filename, 'w', encoding="utf-8") as file:
1662 file.write("""
1663MODULE MODD_UTIL_{t}
1664USE {m}, ONLY: {t}
1665CONTAINS
1666SUBROUTINE COPY_{t} (YD, LDCREATED)""".format(t=typeName,
1667 m=scope.path.split('/')[-2].split(':')[1]))
1668
1669 for var in scope.varList:
1670 if 'TYPE(' in var['t'].replace(' ', '').upper():
1671 file.write("""
1672USE MODD_UTIL_{t}""".format(t=var['t'].replace(' ', '')[5:-1]))
1673
1674 file.write("""
1675IMPLICIT NONE
1676TYPE ({t}), INTENT(IN), TARGET :: YD
1677LOGICAL, OPTIONAL, INTENT(IN) :: LDCREATED
1678INTEGER :: I
1679LOGICAL :: LLCREATED
1680LLCREATED = .FALSE.
1681IF (PRESENT (LDCREATED)) THEN
1682 LLCREATED = LDCREATED
1683ENDIF
1684IF (.NOT. LLCREATED) THEN
1685 !$acc enter data create (YD)
1686 !$acc update device (YD)
1687ENDIF""".format(t=typeName))
1688
1689 for var in scope.varList:
1690 if var['allocatable']:
1691 file.write("""
1692IF (ALLOCATED (YD%{v})) THEN
1693 !$acc enter data create (YD%{v})
1694 !$acc update device (YD%{v})
1695 !$acc enter data attach (YD%{v})
1696ENDIF""".format(v=var['n']))
1697 if 'TYPE(' in var['t'].replace(' ', '').upper():
1698 if var['as'] is not None and len(var['as']) != 0:
1699 indexes = ['LBOUND(YD%{v}, 1) + I - 1'.format(v=var['n'])]
1700 for i in range(len(var['as']) - 1):
1701 indexes.append('LBOUND(YD%{v}, {i})'.format(v=var['n'],
1702 i=str(i + 2)))
1703 file.write("""
1704DO I=1, SIZE(YD%{v})
1705 CALL COPY_{t}(YD%{v}({i}), LDCREATED=.TRUE.)
1706ENDDO""".format(v=var['n'], t=var['t'].replace(' ', '')[5:-1], i=', '.join(indexes)))
1707 else:
1708 file.write("""
1709CALL COPY_{t}(YD%{v}, LDCREATED=.TRUE.)""".format(v=var['n'], t=var['t'].replace(' ', '')[5:-1]))
1710
1711 file.write("""
1712END SUBROUTINE COPY_{t}
1713
1714SUBROUTINE WIPE_{t} (YD, LDDELETED)""".format(t=typeName))
1715
1716 for var in scope.varList:
1717 if 'TYPE(' in var['t'].replace(' ', '').upper():
1718 file.write("""
1719USE MODD_UTIL_{t}""".format(t=var['t'].replace(' ', '')[5:-1]))
1720
1721 file.write("""
1722IMPLICIT NONE
1723TYPE ({t}), INTENT(IN), TARGET :: YD
1724LOGICAL, OPTIONAL, INTENT(IN) :: LDDELETED
1725INTEGER :: I
1726LOGICAL :: LLDELETED
1727LLDELETED = .FALSE.
1728IF (PRESENT (LDDELETED)) THEN
1729 LLDELETED = LDDELETED
1730ENDIF""".format(t=typeName))
1731
1732 for var in scope.varList:
1733 if 'TYPE(' in var['t'].replace(' ', '').upper():
1734 if var['as'] is not None and len(var['as']) != 0:
1735 indexes = ['LBOUND(YD%{v}, 1) + I - 1'.format(v=var['n'])]
1736 for i in range(len(var['as']) - 1):
1737 indexes.append('LBOUND(YD%{v}, {i})'.format(v=var['n'],
1738 i=str(i + 2)))
1739 file.write("""
1740DO I=1, SIZE(YD%{v})
1741 CALL WIPE_{t}(YD%{v}({i}), LDDELETED=.TRUE.)
1742ENDDO""".format(v=var['n'], t=var['t'].replace(' ', '')[5:-1], i=', '.join(indexes)))
1743 else:
1744 file.write("""
1745CALL WIPE_{t}(YD%{v}, LDDELETED=.TRUE.)""".format(v=var['n'], t=var['t'].replace(' ', '')[5:-1]))
1746 if var['allocatable']:
1747 file.write("""
1748IF (ALLOCATED (YD%{v})) THEN
1749 !$acc exit data detach (YD%{v})
1750 !$acc exit data delete (YD%{v})
1751ENDIF""".format(v=var['n']))
1752
1753 file.write("""
1754IF (.NOT. LLDELETED) THEN
1755 !$acc exit data delete (YD)
1756ENDIF
1757END SUBROUTINE WIPE_{t}
1758
1759END MODULE MODD_UTIL_{t}\n""".format(t=typeName))
addStack(self, model, stopScopes, parserOptions=None, wrapH=False)
deleteRoutineCallsMesoNHGPU(self, simplify=True)
removePHYEXUnusedLocalVar(self, excludeList=None, simplify=False)
removeIJDim(self, stopScopes, parserOptions=None, wrapH=False, simplify=False)
deleteBudgetDDH(self, simplify=False)
deleteNonColumnCallsPHYEX(self, simplify=False)
expandAllArraysPHYEX(self, concurrent=False)
checkPHYEXUnusedLocalVar(self, mustRaise=False, excludeList=None)
inlineContainedSubroutinesPHYEX(self, simplify=False)
deleteDrHook(self, simplify=False)
addMPPDB_CHECKS(self, printsMode=False)
_loopVarPHYEX(lowerDecl, upperDecl, lowerUsed, upperUsed, name, index)
generateEmptyPYFT(filename, fortran=None, **kwargs)
Definition pyfortool.py:42