Skip to content

Commit

Permalink
Merge remote-tracking branch 'NCAR/main' into add_const_interface
Browse files Browse the repository at this point in the history
  • Loading branch information
Courtney Peverley committed May 28, 2024
2 parents 0b8f23a + ccfefcd commit c781edc
Show file tree
Hide file tree
Showing 28 changed files with 1,055 additions and 116 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/capgen_unit_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ on:

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

16 changes: 16 additions & 0 deletions scripts/ccpp_prebuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,21 @@ def compare_metadata(metadata_define, metadata_request):
var.convert_from(metadata_define[var_name][0].units)
elif var.intent=='out':
var.convert_to(metadata_define[var_name][0].units)
# If the host model variable is allocated based on a condition, i.e. has an active attribute other
# than T (.true.), the scheme variable must be optional
if not metadata_define[var_name][0].active == 'T':
for var in metadata_request[var_name]:
if var.optional == 'F':
logging.error("Conditionally allocated host-model variable {0} is not optional in {1}".format(
var_name, var.container))
success = False
# TEMPORARY CHECK - IF THE VARIABLE IS ALWAYS ALLOCATED, THE SCHEME VARIABLE SHOULDN'T BE OPTIONAL
else:
for var in metadata_request[var_name]:
if var.optional == 'T':
logging.warn("Unconditionally allocated host-model variable {0} is optional in {1}".format(
var_name, var.container))

# Construct the actual target variable and list of modules to use from the information in 'container'
var = metadata_define[var_name][0]
target = ''
Expand All @@ -483,6 +498,7 @@ def compare_metadata(metadata_define, metadata_request):
pass
else:
logging.error('Unknown identifier {0} in container value of defined variable {1}'.format(subitems[0], var_name))
success = False
target += var.local_name
# Copy the length kind from the variable definition to update len=* in the variable requests
if var.type == 'character':
Expand Down
2 changes: 2 additions & 0 deletions scripts/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
CCPP_BLOCK_COUNT = 'ccpp_block_count'
CCPP_BLOCK_SIZES = 'ccpp_block_sizes'
CCPP_THREAD_NUMBER = 'ccpp_thread_number'
CCPP_THREAD_COUNT = 'ccpp_thread_count'

CCPP_CHUNK_EXTENT = 'ccpp_chunk_extent'
CCPP_HORIZONTAL_LOOP_BEGIN = 'horizontal_loop_begin'
Expand Down Expand Up @@ -60,6 +61,7 @@
CCPP_LOOP_COUNTER : 'cdata%loop_cnt',
CCPP_BLOCK_NUMBER : 'cdata%blk_no',
CCPP_THREAD_NUMBER : 'cdata%thrd_no',
CCPP_THREAD_COUNT : 'cdata%thrd_cnt',
}

STANDARD_CHARACTER_TYPE = 'character'
Expand Down
19 changes: 16 additions & 3 deletions scripts/metadata_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,28 @@ def read_new_metadata(filename, module_name, table_name, scheme_name = None, sub
# *DH 2020-05-26

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

if not new_var.get_prop_value('optional') in [False, True]:
raise Exception("Unexpected result: no optional attribute received from metadata parser for {} / {}".format(standard_name,table_name))
elif not scheme_name and new_var.get_prop_value('optional'):
raise Exception("Host variable {} in table {} has metadata attribute optional={}, which is not allowed".format(
standard_name,table_name, new_var.get_prop_value('optional').lower()))
elif new_var.get_prop_value('optional'):
optional = 'T'
else:
optional = 'F'

# DH* 20210812
# Workaround for Fortran DDTs incorrectly having the type of
# the DDT copied into the kind attribute in parse_metadata_file
Expand All @@ -228,6 +240,7 @@ def read_new_metadata(filename, module_name, table_name, scheme_name = None, sub
kind = kind,
intent = new_var.get_prop_value('intent'),
active = active,
optional = optional,
)
# Check for duplicates in same table
if standard_name in metadata.keys():
Expand Down
160 changes: 126 additions & 34 deletions scripts/mkcap.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def __init__(self, **kwargs):
self._kind = None
self._intent = None
self._active = None
self._optional = None
self._pointer = False
self._target = None
self._actions = { 'in' : None, 'out' : None }
for key, value in kwargs.items():
Expand Down Expand Up @@ -134,6 +136,30 @@ def active(self, value):
raise ValueError('Invalid value {0} for variable property active, must be a string'.format(value))
self._active = value

@property
def optional(self):
'''Get the optional attribute of the variable.'''
return self._optional

@optional.setter
def optional(self, value):
if not isinstance(value, str):
raise ValueError('Invalid value {0} for variable property optional, must be a string'.format(value))
self._optional = value

# Pointer is not set by parsing metadata attributes, but by mkstatic.
# This is a quick and dirty solution!
@property
def pointer(self):
'''Get the pointer attribute of the variable.'''
return self._pointer

@pointer.setter
def pointer(self, value):
if not isinstance(value, bool):
raise ValueError('Invalid value {0} for variable property pointer, must be a logical'.format(value))
self._pointer = value

@property
def target(self):
'''Get the target of the variable.'''
Expand Down Expand Up @@ -270,62 +296,128 @@ def print_def_intent(self, metadata):
dictionary to resolve lower bounds for array dimensions.'''
# Resolve dimensisons to local names using undefined upper bounds (assumed shape)
dimstring = self.dimstring_local_names(metadata, assume_shape = True)
# It is an error for host model variables to have the optional attribute in the metadata
if self.optional == 'T':
error_message = "This routine should only be called for host model variables" + \
" that cannot be optional, but got self.optional=T"
raise Exception(error_message)
# If the host variable is potentially unallocated, add optional and target to variable declaration
elif not self.active == 'T':
optional = ', optional, target'
else:
optional = ''
#
if self.type in STANDARD_VARIABLE_TYPES:
if self.kind:
str = "{s.type}({s._kind}), intent({s.intent}) :: {s.local_name}{dimstring}"
str = "{s.type}({s._kind}), intent({s.intent}){optional} :: {s.local_name}{dimstring}"
else:
str = "{s.type}, intent({s.intent}) :: {s.local_name}{dimstring}"
str = "{s.type}, intent({s.intent}){optional} :: {s.local_name}{dimstring}"
else:
if self.kind:
error_message = "Generating variable definition statements for derived types with" + \
" kind attributes not implemented; variable: {0}".format(self.standard_name)
raise Exception(error_message)
else:
str = "type({s.type}), intent({s.intent}) :: {s.local_name}{dimstring}"
return str.format(s=self, dimstring=dimstring)
str = "type({s.type}), intent({s.intent}){optional} :: {s.local_name}{dimstring}"
return str.format(s=self, optional=optional, dimstring=dimstring)

def print_def_local(self, metadata):
'''Print the definition line for the variable, assuming it is a local variable.'''
if self.type in STANDARD_VARIABLE_TYPES:
if self.kind:
if self.rank:
str = "{s.type}({s._kind}), dimension{s.rank}, allocatable :: {s.local_name}"
# It is an error for local variables to have the active attribute
if not self.active == 'T':
error_message = "This routine should only be called for local variables" + \
" that cannot have an active attribute other than the" +\
" default T, but got self.active=T"
raise Exception(error_message)

# If it is a pointer, everything is different!
if self.pointer:
if self.type in STANDARD_VARIABLE_TYPES:
if self.kind:
if self.rank:
str = "{s.type}({s._kind}), dimension{s.rank}, pointer :: p => null()"
else:
str = "{s.type}({s._kind}), pointer :: p => null()"
else:
str = "{s.type}({s._kind}) :: {s.local_name}"
if self.rank:
str = "{s.type}, dimension{s.rank}, pointer :: p => null()"
else:
str = "{s.type}, pointer :: p => null()"
else:
if self.rank:
str = "{s.type}, dimension{s.rank}, allocatable :: {s.local_name}"
if self.kind:
error_message = "Generating variable definition statements for derived types with" + \
" kind attributes not implemented; variable: {0}".format(self.standard_name)
raise Exception(error_message)
else:
str = "{s.type} :: {s.local_name}"
if self.rank:
str = "type({s.type}), dimension{s.rank}, pointer :: p => null()"
else:
str = "type({s.type}), pointer :: p => null()"
return str.format(s=self)
else:
if self.kind:
error_message = "Generating variable definition statements for derived types with" + \
" kind attributes not implemented; variable: {0}".format(self.standard_name)
raise Exception(error_message)
# If the host variable is potentially unallocated, the active attribute is
# also set accordingly for the local variable; add target to variable declaration
if self.optional == 'T':
target = ', target'
else:
if self.rank:
str = "type({s.type}), dimension{s.rank}, allocatable :: {s.local_name}"
target = ''
if self.type in STANDARD_VARIABLE_TYPES:
if self.kind:
if self.rank:
str = "{s.type}({s._kind}), dimension{s.rank}, allocatable{target} :: {s.local_name}"
else:
str = "{s.type}({s._kind}){target} :: {s.local_name}"
else:
str = "type({s.type}) :: {s.local_name}"
return str.format(s=self)
if self.rank:
str = "{s.type}, dimension{s.rank}, allocatable{target} :: {s.local_name}"
else:
str = "{s.type}{target} :: {s.local_name}"
else:
if self.kind:
error_message = "Generating variable definition statements for derived types with" + \
" kind attributes not implemented; variable: {0}".format(self.standard_name)
raise Exception(error_message)
else:
if self.rank:
str = "type({s.type}), dimension{s.rank}, allocatable{target} :: {s.local_name}"
else:
str = "type({s.type}){target} :: {s.local_name}"
return str.format(s=self, target=target)

def print_debug(self):
'''Print the data retrieval line for the variable.'''
str='''Contents of {s} (* = mandatory for compatibility):
standard_name = {s.standard_name} *
long_name = {s.long_name}
units = {s.units} *
local_name = {s.local_name}
type = {s.type} *
dimensions = {s.dimensions}
rank = {s.rank} *
kind = {s.kind} *
intent = {s.intent}
active = {s.active}
target = {s.target}
container = {s.container}
actions = {s.actions}'''
# Scheme variables don't have the active attribute
if 'SCHEME' in self.container:
str='''Contents of {s} (* = mandatory for compatibility):
standard_name = {s.standard_name} *
long_name = {s.long_name}
units = {s.units} *
local_name = {s.local_name}
type = {s.type} *
dimensions = {s.dimensions}
rank = {s.rank} *
kind = {s.kind} *
intent = {s.intent}
optional = {s.optional}
target = {s.target}
container = {s.container}
actions = {s.actions}'''
# Host model variables don't have the optional attribute
else:
str='''Contents of {s} (* = mandatory for compatibility):
standard_name = {s.standard_name} *
long_name = {s.long_name}
units = {s.units} *
local_name = {s.local_name}
type = {s.type} *
dimensions = {s.dimensions}
rank = {s.rank} *
kind = {s.kind} *
intent = {s.intent}
active = {s.active}
target = {s.target}
container = {s.container}
actions = {s.actions}'''
return str.format(s=self)

class CapsMakefile(object):
Expand Down
Loading

0 comments on commit c781edc

Please sign in to comment.