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