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