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