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