Skip to content
4 changes: 3 additions & 1 deletion config/psyclone.cfg

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stevemullerworth apologies - you'll need to edit your PSyclone configuration file in the same way as is done here. We were only discussing today that we need to get rid of these settings (in favour of reading constants_mod.f90)...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this works - I tested changing one field to real64 and one to real32 and it all compiles (the real32 is used in a built-in so there is no clash with a kernel kind).

Also, I noticed that our build system can point to a different config using PSYCLONE_CONFIG_FILE (as opposed to PSYCLONE_CONFIG), and changing PSYCLONE_CONFIG_FILE to point to the centrally-installed version also worked. I will enquire as to why we still maintain a copy of this file in our source.

Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ precision_map = i_def: 4,
r_solver: 4,
r_tran: 8,
r_bl: 8,
r_um: 8
r_um: 8,
real64: 8,
real32: 4

# Specify whether we generate code to perform runtime correctness checks.
# Allowed values:
Expand Down
24 changes: 21 additions & 3 deletions doc/user_guide/lfric.rst
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ Mixed Precision

The LFRic API supports the ability to specify the precision required
by the model via precision variables. To make use of this, the code
developer must declare scalars, arrays, fields and operators in the algorithm
developer must declare scalars, arrays, fields and operators in the Algorithm
layer with the required LFRic-supported precision. In the current
implementation there are two supported precisions for ``REAL`` data and
one each for ``INTEGER`` and ``LOGICAL`` data. The actual precision used in
Expand Down Expand Up @@ -471,6 +471,10 @@ associated kernel metadata description and their precision:
+--------------------------+---------------------------------------+-----------+
| R_TRAN_FIELD_TYPE | GH_FIELD, GH_REAL | R_TRAN |
+--------------------------+---------------------------------------+-----------+
| FIELD_REAL32_TYPE | GH_FIELD, GH_REAL | REAL32 |
+--------------------------+---------------------------------------+-----------+
| FIELD_REAL64_TYPE | GH_FIELD, GH_REAL | REAL64 |
+--------------------------+---------------------------------------+-----------+
| INTEGER_FIELD_TYPE | GH_FIELD, GH_INTEGER | I_DEF |
+--------------------------+---------------------------------------+-----------+
| OPERATOR_TYPE | GH_OPERATOR, GH_REAL | R_DEF |
Expand All @@ -479,13 +483,18 @@ associated kernel metadata description and their precision:
+--------------------------+---------------------------------------+-----------+
| R_TRAN_OPERATOR_TYPE | GH_OPERATOR, GH_REAL | R_TRAN |
+--------------------------+---------------------------------------+-----------+
| OPERATOR_REAL32_TYPE | GH_OPERATOR, GH_REAL | REAL32 |
+--------------------------+---------------------------------------+-----------+
| OPERATOR_REAL64_TYPE | GH_OPERATOR, GH_REAL | REAL64 |
+--------------------------+---------------------------------------+-----------+
| COLUMNWISE_OPERATOR_TYPE | GH_COLUMNWISE_OPERATOR, GH_REAL | R_SOLVER |
+--------------------------+---------------------------------------+-----------+

As can be seen from the above table, the kernel metadata does not
capture all of the precision options. For example, from the metadata
it is not possible to determine whether a ``REAL`` scalar, ``REAL`` field
or ``REAL`` operator has precision ``R_DEF``, ``R_SOLVER`` or ``R_TRAN``.
or ``REAL`` operator has precision ``R_DEF``, ``R_SOLVER``, ``R_TRAN``
or one of the Fortran intrinsic precisions (``REAL32``, ``REAL64``).

If a scalar, array, field, or operator is specified with a particular
precision in the algorithm layer then any associated kernels that it
Expand Down Expand Up @@ -601,6 +610,10 @@ outlined in the table below:
+-------------------------+------------------+--------------+
| ``r_tran_field_type`` | ``real`` | ``r_tran`` |
+-------------------------+------------------+--------------+
| ``field_real32_type`` | ``real`` | ``real32`` |
+-------------------------+------------------+--------------+
| ``field_real64_type`` | ``real`` | ``real64`` |
+-------------------------+------------------+--------------+
| ``integer_field_type`` | ``integer`` | ``i_def`` |
+-------------------------+------------------+--------------+

Expand Down Expand Up @@ -701,7 +714,8 @@ a message that indicates the problem.
| Fortran Datatype | Supported Precision |
+==================+==========================+
| ``real`` | ``r_def``, ``r_bl``, |
| | ``r_solver``, ``r_tran`` |
| | ``r_solver``, ``r_tran``,|
| | ``real32``, ``real64`` |
+------------------+--------------------------+
| ``integer`` | ``i_def`` |
+------------------+--------------------------+
Expand Down Expand Up @@ -731,6 +745,10 @@ outlined in the table below:
+----------------------------+------------------+--------------+
| ``r_tran_operator_type`` | ``real`` | ``r_tran`` |
+----------------------------+------------------+--------------+
| ``operator_real32_type`` | ``real`` | ``real32`` |
+----------------------------+------------------+--------------+
| ``operator_real64_type`` | ``real`` | ``real64`` |
+----------------------------+------------------+--------------+

.. _lfric-mixed-precision-cma-operators:

Expand Down
36 changes: 36 additions & 0 deletions src/psyclone/domain/lfric/lfric_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,11 @@ def __init__(self) -> None:

# ---------- Infrastructure module maps -------------------------------

# Those kind symbols used in LFRic that are actually
# Fortran intrinsics (and thus don't come from constants_mod).
LFRicConstants.INTRINSIC_KINDS = ("real32", "real64")
LFRicConstants.FORTRAN_ISO_MOD_NAME = "iso_fortran_env"

# Dictionary allowing us to look-up the name of the Fortran module,
# type and proxy-type associated with each LFRic data structure type.
# Data structure type mandates its proxy name, Fortran intrinsic type
Expand Down Expand Up @@ -420,6 +425,18 @@ def __init__(self) -> None:
"proxy_type": "r_bl_field_proxy_type",
"intrinsic": "real",
"kind": "r_bl"},
# 'real'-valued field with explicit 32-bit precision
"r_32_field": {"module": "field_real32_mod",
"type": "field_real32_type",
"proxy_type": "field_real32_proxy_type",
"intrinsic": "real",
"kind": "real32"},
# 'real'-valued field with explicit 64-bit precision
"r_64_field": {"module": "field_real64_mod",
"type": "field_real64_type",
"proxy_type": "field_real64_proxy_type",
"intrinsic": "real",
"kind": "real64"},
# 'integer'-valued field with data of kind 'i_def'
"integer_field": {"module": "integer_field_mod",
"type": "integer_field_type",
Expand All @@ -432,6 +449,18 @@ def __init__(self) -> None:
"proxy_type": "operator_proxy_type",
"intrinsic": "real",
"kind": "r_def"},
# 'real'-valued operator with real32 data
"r_32_operator": {"module": "operator_real32_mod",
"type": "operator_real32_type",
"proxy_type": "operator_real32_proxy_type",
"intrinsic": "real",
"kind": "real32"},
# 'real'-valued operator with real32 data
"r_64_operator": {"module": "operator_real64_mod",
"type": "operator_real64_type",
"proxy_type": "operator_real64_proxy_type",
"intrinsic": "real",
"kind": "real64"},
# 'real'-valued operator with data of kind 'r_solver'
"r_solver_operator": {
"module": "r_solver_operator_mod",
Expand All @@ -454,6 +483,13 @@ def __init__(self) -> None:
"intrinsic": "real",
"kind": "r_solver"}}

# Construct a reverse map from type to the name of the LFRic
# data type for all real field types.
LFRicConstants.REAL_DATA_TYPE_RMAP = {}
for key, value in LFRicConstants.DATA_TYPE_MAP.items():
if value["intrinsic"] == "real" and "field" in key:
LFRicConstants.REAL_DATA_TYPE_RMAP[value["type"]] = key

# Mapping from a vector type used in the algorithm-layer to
# the actual type used in the PSy-layer.
LFRicConstants.FIELD_VECTOR_TO_FIELD_MAP = {
Expand Down
17 changes: 15 additions & 2 deletions src/psyclone/domain/lfric/lfric_driver_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ def __init__(self, region_name: Optional[tuple[str, str]] = None) -> None:
# -------------------------------------------------------------------------
def handle_precision_symbols(self, symbol_table: SymbolTable) -> None:
'''This function adds an import of the various precision
symbols used by LFRic from the constants_mod module.
symbols used by LFRic from the constants_mod module. It also adds
imports of the real32 and real64 intrinsic kinds.

:param symbol_table: the symbol table to which the precision symbols
must be added.
Expand All @@ -87,15 +88,27 @@ def handle_precision_symbols(self, symbol_table: SymbolTable) -> None:
# does not exist at all in LFRic, but is still in LFRic's psyclone.cfg
# file. TODO #2018 and
# https://code.metoffice.gov.uk/trac/lfric/ticket/4674
names_to_skip = ["r_quad", "r_phys"] + list(const.INTRINSIC_KINDS)
api_config = Config.get().api_conf("lfric")
all_precisions = [name for name in api_config.precision_map
if name not in ["r_quad", "r_phys"]]
if name not in names_to_skip]
for prec_name in all_precisions:
symbol_table.new_symbol(prec_name,
tag=f"{prec_name}@{mod_name}",
symbol_type=DataSymbol,
datatype=ScalarType.integer_type(),
interface=ImportInterface(constant_mod))
# Intrinsic kind symbols are imported into constants_mod but are
# private to it so have to be handled separately.
iso_mod = ContainerSymbol(const.FORTRAN_ISO_MOD_NAME,
is_intrinsic=True)
symbol_table.add(iso_mod)
for prec_name in const.INTRINSIC_KINDS:
symbol_table.new_symbol(prec_name,
tag=f"{prec_name}@{iso_mod.name}",
symbol_type=DataSymbol,
datatype=ScalarType.integer_type(),
interface=ImportInterface(iso_mod))

# -------------------------------------------------------------------------
def verify_and_cleanup_psyir(self, extract_region: Node) -> None:
Expand Down
14 changes: 12 additions & 2 deletions src/psyclone/domain/lfric/lfric_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,9 @@ def add_precision_symbol(table: SymbolTable,
table then add it. Also ensure that the Container symbol from which it
is imported is in the table.

Also supports Fortran intrinsic kinds imported from the
iso_fortran_env module.

:param table: the symbol table to use.
:param name: name of the LFRic precision symbol to add to table.

Expand All @@ -624,7 +627,13 @@ def add_precision_symbol(table: SymbolTable,
raise ValueError(f"'{name}' is not a recognised LFRic precision.")

const = LFRicConstants()
mod_name = const.UTILITIES_MOD_MAP["constants"]["module"]
if name in const.INTRINSIC_KINDS:
# This is an intrinsic precision (e.g. real64)
mod_name = const.FORTRAN_ISO_MOD_NAME
is_intrinsic = True
else:
mod_name = const.UTILITIES_MOD_MAP["constants"]["module"]
is_intrinsic = False

sym = table.lookup(name, otherwise=None)

Expand All @@ -638,7 +647,8 @@ def add_precision_symbol(table: SymbolTable,
return sym

constants_mod = table.find_or_create(mod_name,
symbol_type=ContainerSymbol)
symbol_type=ContainerSymbol,
is_intrinsic=is_intrinsic)
sym = DataSymbol(name, ScalarType.integer_type(),
interface=ImportInterface(constants_mod))
table.add(sym)
Expand Down
90 changes: 48 additions & 42 deletions src/psyclone/lfric.py
Original file line number Diff line number Diff line change
Expand Up @@ -5954,22 +5954,22 @@ def _init_scalar_properties(
self._precision = const.SCALAR_PRECISION_MAP[
self.intrinsic_type]

def _init_field_properties(self, alg_datatype, check=True):
def _init_field_properties(self,
alg_datatype: Optional[str],
check: bool = True) -> None:
'''Set up the properties of this field using algorithm datatype
information if it is available.

:param alg_datatype: the datatype of this argument as \
specified in the algorithm layer or None if it is not \
known.
:type alg_datatype: str or NoneType
:param bool check: whether to use the algorithm \
:param alg_datatype: the datatype of this argument as
specified in the algorithm layer or None if it is not known.
:param check: whether to use the algorithm
information. Optional argument that defaults to True.

:raises GenerationError: if the datatype for a gh_field \
:raises GenerationError: if the datatype for a gh_field
could not be found in the algorithm layer.
:raises GenerationError: if the datatype specified in the \
:raises GenerationError: if the datatype specified in the
algorithm layer is inconsistent with the kernel metadata.
:raises InternalError: if the intrinsic type of the field is \
:raises InternalError: if the intrinsic type of the field is
not supported (i.e. is not real or integer).

'''
Expand All @@ -5990,20 +5990,14 @@ def _init_field_properties(self, alg_datatype, check=True):
if not check:
# Use the default as we are ignoring any algorithm info
argtype = "field"
elif alg_datatype == "field_type":
argtype = "field"
elif alg_datatype == "r_bl_field_type":
argtype = "r_bl_field"
elif alg_datatype == "r_solver_field_type":
argtype = "r_solver_field"
elif alg_datatype == "r_tran_field_type":
argtype = "r_tran_field"
else:
raise GenerationError(
f"The metadata for argument '{self.name}' in kernel "
f"'{self._call.name}' specifies that this is a real "
f"field, however it is declared as a "
f"'{alg_datatype}' in the algorithm code.")
argtype = const.REAL_DATA_TYPE_RMAP.get(alg_datatype, None)
if not argtype:
raise GenerationError(
f"The metadata for argument '{self.name}' in kernel "
f"'{self._call.name}' specifies that this is a real "
f"field, however it is declared as a "
f"'{alg_datatype}' in the algorithm code.")

elif self.intrinsic_type == "integer":
if check and alg_datatype != "integer_field_type":
Expand All @@ -6023,20 +6017,21 @@ def _init_field_properties(self, alg_datatype, check=True):
self._proxy_data_type = const.DATA_TYPE_MAP[argtype]["proxy_type"]
self._module_name = const.DATA_TYPE_MAP[argtype]["module"]

def _init_operator_properties(self, alg_datatype, check=True):
def _init_operator_properties(self,
alg_datatype: Optional[str],
check: bool = True) -> None:
'''Set up the properties of this operator using algorithm datatype
information if it is available.

:param alg_datatype: the datatype of this argument as \
specified in the algorithm layer or None if it is not \
known.
:type alg_datatype: str or NoneType
:param bool check: whether to use the algorithm \
information. Optional argument that defaults to True.
:raises GenerationError: if the datatype for a gh_operator \
could not be found in the algorithm layer (and check is \
:param alg_datatype: the datatype of this argument as specified in
the algorithm layer or None if it is not known.
:param check: whether to use the algorithm information. Optional
argument that defaults to True.

:raises GenerationError: if the datatype for a gh_operator
could not be found in the algorithm layer (and check is
True).
:raises GenerationError: if the datatype specified in the \
:raises GenerationError: if the datatype specified in the
algorithm layer is inconsistent with the kernel metadata.
:raises InternalError: if this argument is not an operator.

Expand All @@ -6048,9 +6043,8 @@ def _init_operator_properties(self, alg_datatype, check=True):
# Use the default as we are ignoring any algorithm info
argtype = "operator"
elif not alg_datatype:
# Raise an exception as we require algorithm
# information to determine the precision of the
# operator
# Raise an exception as we require algorithm information
# to determine the precision of the operator
raise GenerationError(
f"It was not possible to determine the operator type "
f"from the algorithm layer for argument '{self.name}' "
Expand All @@ -6061,6 +6055,10 @@ def _init_operator_properties(self, alg_datatype, check=True):
argtype = "r_solver_operator"
elif alg_datatype == "r_tran_operator_type":
argtype = "r_tran_operator"
elif alg_datatype == "operator_real64_type":
argtype = "r_64_operator"
elif alg_datatype == "operator_real32_type":
argtype = "r_32_operator"
else:
raise GenerationError(
f"The metadata for argument '{self.name}' in kernel "
Expand Down Expand Up @@ -6480,17 +6478,25 @@ def _find_or_create_type(mod_name: str,
raise NotImplementedError(
f"Unsupported scalar type '{self.intrinsic_type}'")

const = LFRicConstants()
kind_name = self.precision
try:
kind_symbol = symtab.lookup(kind_name)
except KeyError:
mod_map = LFRicConstants().UTILITIES_MOD_MAP
const_mod = mod_map["constants"]["module"]
constants_container = symtab.find_or_create(
const_mod, symbol_type=ContainerSymbol)
kind_symbol = DataSymbol(
kind_name, ScalarType.integer_type(),
interface=ImportInterface(constants_container))
if kind_name.lower() in const.INTRINSIC_KINDS:
# This is an intrinsic kind (e.g. real32) so it comes
# from the intrinsic ISO module.
cntr_sym = symtab.find_or_create(
const.FORTRAN_ISO_MOD_NAME,
symbol_type=ContainerSymbol,
is_intrinsic=True)
else:
mod_map = const.UTILITIES_MOD_MAP
const_mod = mod_map["constants"]["module"]
cntr_sym = symtab.find_or_create(
const_mod, symbol_type=ContainerSymbol)
kind_symbol = DataSymbol(kind_name, ScalarType.integer_type(),
interface=ImportInterface(cntr_sym))
symtab.add(kind_symbol)
dts = ScalarType(prim_type, Reference(kind_symbol))
if self.is_scalar_array and self._array_ndims >= 1:
Expand Down
Loading
Loading