Skip to content

Commit ccfefcd

Browse files
authored
Merge pull request #552 from climbfuji/feature/prebuild_optarg_based_on_merge_feature_capgen_into_main_20240308
Add support (and tests) for optional arguments in ccpp_prebuild
2 parents 741212e + c298399 commit ccfefcd

28 files changed

+1055
-116
lines changed

.github/workflows/capgen_unit_tests.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ on:
77

88
jobs:
99
unit_tests:
10-
runs-on: ubuntu-latest
10+
strategy:
11+
matrix:
12+
os: [ubuntu-22.04]
13+
fortran-compiler: [gfortran-9, gfortran-10, gfortran-11, gfortran-12]
14+
runs-on: ${{ matrix.os }}
1115
steps:
1216
- uses: actions/checkout@v3
1317
- name: update repos and install dependencies
14-
run: sudo apt-get update && sudo apt-get install -y build-essential gfortran cmake python3 git
18+
run: sudo apt-get update && sudo apt-get install -y build-essential ${{matrix.fortran-compiler}} cmake python3 git
1519
- name: Run unit tests
1620
run: cd test && ./run_fortran_tests.sh
1721

scripts/ccpp_prebuild.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,21 @@ def compare_metadata(metadata_define, metadata_request):
471471
var.convert_from(metadata_define[var_name][0].units)
472472
elif var.intent=='out':
473473
var.convert_to(metadata_define[var_name][0].units)
474+
# If the host model variable is allocated based on a condition, i.e. has an active attribute other
475+
# than T (.true.), the scheme variable must be optional
476+
if not metadata_define[var_name][0].active == 'T':
477+
for var in metadata_request[var_name]:
478+
if var.optional == 'F':
479+
logging.error("Conditionally allocated host-model variable {0} is not optional in {1}".format(
480+
var_name, var.container))
481+
success = False
482+
# TEMPORARY CHECK - IF THE VARIABLE IS ALWAYS ALLOCATED, THE SCHEME VARIABLE SHOULDN'T BE OPTIONAL
483+
else:
484+
for var in metadata_request[var_name]:
485+
if var.optional == 'T':
486+
logging.warn("Unconditionally allocated host-model variable {0} is optional in {1}".format(
487+
var_name, var.container))
488+
474489
# Construct the actual target variable and list of modules to use from the information in 'container'
475490
var = metadata_define[var_name][0]
476491
target = ''
@@ -483,6 +498,7 @@ def compare_metadata(metadata_define, metadata_request):
483498
pass
484499
else:
485500
logging.error('Unknown identifier {0} in container value of defined variable {1}'.format(subitems[0], var_name))
501+
success = False
486502
target += var.local_name
487503
# Copy the length kind from the variable definition to update len=* in the variable requests
488504
if var.type == 'character':

scripts/common.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
CCPP_BLOCK_COUNT = 'ccpp_block_count'
3030
CCPP_BLOCK_SIZES = 'ccpp_block_sizes'
3131
CCPP_THREAD_NUMBER = 'ccpp_thread_number'
32+
CCPP_THREAD_COUNT = 'ccpp_thread_count'
3233

3334
CCPP_CHUNK_EXTENT = 'ccpp_chunk_extent'
3435
CCPP_HORIZONTAL_LOOP_BEGIN = 'horizontal_loop_begin'
@@ -60,6 +61,7 @@
6061
CCPP_LOOP_COUNTER : 'cdata%loop_cnt',
6162
CCPP_BLOCK_NUMBER : 'cdata%blk_no',
6263
CCPP_THREAD_NUMBER : 'cdata%thrd_no',
64+
CCPP_THREAD_COUNT : 'cdata%thrd_cnt',
6365
}
6466

6567
STANDARD_CHARACTER_TYPE = 'character'

scripts/metadata_parser.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,16 +198,28 @@ def read_new_metadata(filename, module_name, table_name, scheme_name = None, sub
198198
# *DH 2020-05-26
199199

200200
if not new_var.get_prop_value('active'):
201-
# If it doesn't have an active attribute, then the variable is always active (default)
202-
active = 'T'
201+
raise Exception("Unexpected result: no active attribute received from capgen metadata parser for {} / {}".format(standard_name,table_name))
202+
elif scheme_name and not new_var.get_prop_value('active').lower() == '.true.':
203+
raise Exception("Scheme variable {} in table {} has metadata attribute active={}, which is not allowed".format(
204+
standard_name, table_name, new_var.get_prop_value('active').lower()))
203205
elif new_var.get_prop_value('active').lower() == '.true.':
204206
active = 'T'
205-
elif new_var.get_prop_value('active') and new_var.get_prop_value('active').lower() == '.false.':
207+
elif new_var.get_prop_value('active').lower() == '.false.':
206208
active = 'F'
207209
else:
208210
# Replace multiple whitespaces, preserve case
209211
active = ' '.join(new_var.get_prop_value('active').split())
210212

213+
if not new_var.get_prop_value('optional') in [False, True]:
214+
raise Exception("Unexpected result: no optional attribute received from metadata parser for {} / {}".format(standard_name,table_name))
215+
elif not scheme_name and new_var.get_prop_value('optional'):
216+
raise Exception("Host variable {} in table {} has metadata attribute optional={}, which is not allowed".format(
217+
standard_name,table_name, new_var.get_prop_value('optional').lower()))
218+
elif new_var.get_prop_value('optional'):
219+
optional = 'T'
220+
else:
221+
optional = 'F'
222+
211223
# DH* 20210812
212224
# Workaround for Fortran DDTs incorrectly having the type of
213225
# the DDT copied into the kind attribute in parse_metadata_file
@@ -228,6 +240,7 @@ def read_new_metadata(filename, module_name, table_name, scheme_name = None, sub
228240
kind = kind,
229241
intent = new_var.get_prop_value('intent'),
230242
active = active,
243+
optional = optional,
231244
)
232245
# Check for duplicates in same table
233246
if standard_name in metadata.keys():

scripts/mkcap.py

Lines changed: 126 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def __init__(self, **kwargs):
3232
self._kind = None
3333
self._intent = None
3434
self._active = None
35+
self._optional = None
36+
self._pointer = False
3537
self._target = None
3638
self._actions = { 'in' : None, 'out' : None }
3739
for key, value in kwargs.items():
@@ -134,6 +136,30 @@ def active(self, value):
134136
raise ValueError('Invalid value {0} for variable property active, must be a string'.format(value))
135137
self._active = value
136138

139+
@property
140+
def optional(self):
141+
'''Get the optional attribute of the variable.'''
142+
return self._optional
143+
144+
@optional.setter
145+
def optional(self, value):
146+
if not isinstance(value, str):
147+
raise ValueError('Invalid value {0} for variable property optional, must be a string'.format(value))
148+
self._optional = value
149+
150+
# Pointer is not set by parsing metadata attributes, but by mkstatic.
151+
# This is a quick and dirty solution!
152+
@property
153+
def pointer(self):
154+
'''Get the pointer attribute of the variable.'''
155+
return self._pointer
156+
157+
@pointer.setter
158+
def pointer(self, value):
159+
if not isinstance(value, bool):
160+
raise ValueError('Invalid value {0} for variable property pointer, must be a logical'.format(value))
161+
self._pointer = value
162+
137163
@property
138164
def target(self):
139165
'''Get the target of the variable.'''
@@ -270,62 +296,128 @@ def print_def_intent(self, metadata):
270296
dictionary to resolve lower bounds for array dimensions.'''
271297
# Resolve dimensisons to local names using undefined upper bounds (assumed shape)
272298
dimstring = self.dimstring_local_names(metadata, assume_shape = True)
299+
# It is an error for host model variables to have the optional attribute in the metadata
300+
if self.optional == 'T':
301+
error_message = "This routine should only be called for host model variables" + \
302+
" that cannot be optional, but got self.optional=T"
303+
raise Exception(error_message)
304+
# If the host variable is potentially unallocated, add optional and target to variable declaration
305+
elif not self.active == 'T':
306+
optional = ', optional, target'
307+
else:
308+
optional = ''
273309
#
274310
if self.type in STANDARD_VARIABLE_TYPES:
275311
if self.kind:
276-
str = "{s.type}({s._kind}), intent({s.intent}) :: {s.local_name}{dimstring}"
312+
str = "{s.type}({s._kind}), intent({s.intent}){optional} :: {s.local_name}{dimstring}"
277313
else:
278-
str = "{s.type}, intent({s.intent}) :: {s.local_name}{dimstring}"
314+
str = "{s.type}, intent({s.intent}){optional} :: {s.local_name}{dimstring}"
279315
else:
280316
if self.kind:
281317
error_message = "Generating variable definition statements for derived types with" + \
282318
" kind attributes not implemented; variable: {0}".format(self.standard_name)
283319
raise Exception(error_message)
284320
else:
285-
str = "type({s.type}), intent({s.intent}) :: {s.local_name}{dimstring}"
286-
return str.format(s=self, dimstring=dimstring)
321+
str = "type({s.type}), intent({s.intent}){optional} :: {s.local_name}{dimstring}"
322+
return str.format(s=self, optional=optional, dimstring=dimstring)
287323

288324
def print_def_local(self, metadata):
289325
'''Print the definition line for the variable, assuming it is a local variable.'''
290-
if self.type in STANDARD_VARIABLE_TYPES:
291-
if self.kind:
292-
if self.rank:
293-
str = "{s.type}({s._kind}), dimension{s.rank}, allocatable :: {s.local_name}"
326+
# It is an error for local variables to have the active attribute
327+
if not self.active == 'T':
328+
error_message = "This routine should only be called for local variables" + \
329+
" that cannot have an active attribute other than the" +\
330+
" default T, but got self.active=T"
331+
raise Exception(error_message)
332+
333+
# If it is a pointer, everything is different!
334+
if self.pointer:
335+
if self.type in STANDARD_VARIABLE_TYPES:
336+
if self.kind:
337+
if self.rank:
338+
str = "{s.type}({s._kind}), dimension{s.rank}, pointer :: p => null()"
339+
else:
340+
str = "{s.type}({s._kind}), pointer :: p => null()"
294341
else:
295-
str = "{s.type}({s._kind}) :: {s.local_name}"
342+
if self.rank:
343+
str = "{s.type}, dimension{s.rank}, pointer :: p => null()"
344+
else:
345+
str = "{s.type}, pointer :: p => null()"
296346
else:
297-
if self.rank:
298-
str = "{s.type}, dimension{s.rank}, allocatable :: {s.local_name}"
347+
if self.kind:
348+
error_message = "Generating variable definition statements for derived types with" + \
349+
" kind attributes not implemented; variable: {0}".format(self.standard_name)
350+
raise Exception(error_message)
299351
else:
300-
str = "{s.type} :: {s.local_name}"
352+
if self.rank:
353+
str = "type({s.type}), dimension{s.rank}, pointer :: p => null()"
354+
else:
355+
str = "type({s.type}), pointer :: p => null()"
356+
return str.format(s=self)
301357
else:
302-
if self.kind:
303-
error_message = "Generating variable definition statements for derived types with" + \
304-
" kind attributes not implemented; variable: {0}".format(self.standard_name)
305-
raise Exception(error_message)
358+
# If the host variable is potentially unallocated, the active attribute is
359+
# also set accordingly for the local variable; add target to variable declaration
360+
if self.optional == 'T':
361+
target = ', target'
306362
else:
307-
if self.rank:
308-
str = "type({s.type}), dimension{s.rank}, allocatable :: {s.local_name}"
363+
target = ''
364+
if self.type in STANDARD_VARIABLE_TYPES:
365+
if self.kind:
366+
if self.rank:
367+
str = "{s.type}({s._kind}), dimension{s.rank}, allocatable{target} :: {s.local_name}"
368+
else:
369+
str = "{s.type}({s._kind}){target} :: {s.local_name}"
309370
else:
310-
str = "type({s.type}) :: {s.local_name}"
311-
return str.format(s=self)
371+
if self.rank:
372+
str = "{s.type}, dimension{s.rank}, allocatable{target} :: {s.local_name}"
373+
else:
374+
str = "{s.type}{target} :: {s.local_name}"
375+
else:
376+
if self.kind:
377+
error_message = "Generating variable definition statements for derived types with" + \
378+
" kind attributes not implemented; variable: {0}".format(self.standard_name)
379+
raise Exception(error_message)
380+
else:
381+
if self.rank:
382+
str = "type({s.type}), dimension{s.rank}, allocatable{target} :: {s.local_name}"
383+
else:
384+
str = "type({s.type}){target} :: {s.local_name}"
385+
return str.format(s=self, target=target)
312386

313387
def print_debug(self):
314388
'''Print the data retrieval line for the variable.'''
315-
str='''Contents of {s} (* = mandatory for compatibility):
316-
standard_name = {s.standard_name} *
317-
long_name = {s.long_name}
318-
units = {s.units} *
319-
local_name = {s.local_name}
320-
type = {s.type} *
321-
dimensions = {s.dimensions}
322-
rank = {s.rank} *
323-
kind = {s.kind} *
324-
intent = {s.intent}
325-
active = {s.active}
326-
target = {s.target}
327-
container = {s.container}
328-
actions = {s.actions}'''
389+
# Scheme variables don't have the active attribute
390+
if 'SCHEME' in self.container:
391+
str='''Contents of {s} (* = mandatory for compatibility):
392+
standard_name = {s.standard_name} *
393+
long_name = {s.long_name}
394+
units = {s.units} *
395+
local_name = {s.local_name}
396+
type = {s.type} *
397+
dimensions = {s.dimensions}
398+
rank = {s.rank} *
399+
kind = {s.kind} *
400+
intent = {s.intent}
401+
optional = {s.optional}
402+
target = {s.target}
403+
container = {s.container}
404+
actions = {s.actions}'''
405+
# Host model variables don't have the optional attribute
406+
else:
407+
str='''Contents of {s} (* = mandatory for compatibility):
408+
standard_name = {s.standard_name} *
409+
long_name = {s.long_name}
410+
units = {s.units} *
411+
local_name = {s.local_name}
412+
type = {s.type} *
413+
dimensions = {s.dimensions}
414+
rank = {s.rank} *
415+
kind = {s.kind} *
416+
intent = {s.intent}
417+
active = {s.active}
418+
target = {s.target}
419+
container = {s.container}
420+
actions = {s.actions}'''
329421
return str.format(s=self)
330422

331423
class CapsMakefile(object):

0 commit comments

Comments
 (0)