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