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