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