diff --git a/.github/workflows/lfric_test.yml b/.github/workflows/lfric_test.yml index 8d336e2ead..2838470d16 100644 --- a/.github/workflows/lfric_test.yml +++ b/.github/workflows/lfric_test.yml @@ -73,7 +73,6 @@ jobs: # than the latest release from pypi. # pip install external/fparser pip install .[test] - pip install jinja2 # PSyclone, compile and run MetOffice gungho_model on GPU - name: LFRic GungHo with OpenMP offload diff --git a/changelog b/changelog index 895ca4ebf6..4991ac0d34 100644 --- a/changelog +++ b/changelog @@ -255,7 +255,16 @@ 88) PR #2753 towards #2717. Adds DataNode.is_character method. 89) PR #2712 for #2711. Adds support for user-supplied Kernels that - operate on dofs (in the LFRic API). + operate on dofs (in the LFRic API). + + 99) PR #2685 for #2027. Improves datatype and shape inference. + + 100) PR #2694 for #2690. Extends the PSyData NaN-checking tooling to + perform value verification. + + 101) PR #2749 for #2311. Alters test-harness generation for the LFRic + API in PSyAD to base the generated test-code filenames on the name of + the supplied LFRic Algorithm file. release 2.5.0 14th of February 2024 diff --git a/config/psyclone.cfg b/config/psyclone.cfg index 37277579b3..ba51267b02 100644 --- a/config/psyclone.cfg +++ b/config/psyclone.cfg @@ -46,7 +46,7 @@ REPRODUCIBLE_REDUCTIONS = false # Amount to pad the local summation array when REPRODUCIBLE_REDUCTIONS is true REPROD_PAD_SIZE = 8 PSYIR_ROOT_NAME = psyir_tmp -VALID_PSY_DATA_PREFIXES = profile, extract, read_only_verify, nan_test +VALID_PSY_DATA_PREFIXES = profile, extract, read_only_verify, value_range_check # Specify number of OpenCL devices per node. When combining OpenCL with MPI, # the mpirun/mpiexec ranks_per_node parameter must match this number. diff --git a/doc/developer_guide/system_specific_setup.rst b/doc/developer_guide/system_specific_setup.rst index a072b13148..c76360f8a4 100644 --- a/doc/developer_guide/system_specific_setup.rst +++ b/doc/developer_guide/system_specific_setup.rst @@ -180,19 +180,3 @@ Verifying the pylint standards is done with:: OK, you're all set up. - -Installing Tools for PSyData Wrapper Libraries -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you intend to compile the PSyData wrapper libraries or develop new libraries, -you might need to install Jinja2 (most wrapper libraries require Jinja2 though -some, like the NVIDIA GPU profiling wrapper, do not need it). You can install -the necessary dependencies to create all PSyData wrapper libraries with:: - - > pip install psyclone[psydata] - -or when using the git version:: - - > pip install .[psydata] - - -Check :ref:`psy_data` and especially the section :ref:`jinja` for more details. diff --git a/doc/user_guide/examples.rst b/doc/user_guide/examples.rst index d313c13a14..aa3bd61ec7 100644 --- a/doc/user_guide/examples.rst +++ b/doc/user_guide/examples.rst @@ -315,33 +315,53 @@ read-only variables: New value: 123.00000000000000 -------------------------------------- -.. _gocean_example_nan: +.. _gocean_example_value_range_check: -Example 5.4: Valid Number Verification (NaN Test) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Example 5.4: Value Range Check +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This example shows the use of valid number verification with PSyclone. It instruments each of the two invokes in the example program -with the PSyData-based NaN-verification code. -It uses the dl_esm_inf-specific nan_test library -(``lib/nan_test/dl_esm_inf/``). +with the PSyData-based Value-Range-Check code. +It uses the dl_esm_inf-specific value range check library +(``lib/value_range_check/dl_esm_inf/``). .. note:: The ``update_field_mod`` subroutine contains code that will trigger a division by 0 to create NaNs. If - the compiler should add floating point exception handling - code, this will take effect before the NaN testing is done - by the PSyData-based verification code. + the compiler happens to add code that handles floating point + exceptions , this will take effect before the value testing + is done by the PSyData-based verification code. The ``Makefile`` in this example will link with the compiled -nan_test library. You can execute the created -binary and it will print five warnings about invalid numbers -at the indices 1 1, ..., 5 5: +value_range_check library. You can then execute the binary +and enable the value range check by setting environments +(see :ref:`value range check` for +details). -.. code-block:: none +.. code-block:: shell - PSyData: Variable a_fld has the invalid value - Infinity at index/indices 1 1 - mainupdate + PSYVERIFY__main__init__b_fld=2:3 ./value_range_check + ... + PSyData: Variable b_fld has the value 0.0000000000000000 at index/indices 6 1 in module 'main', region 'init', which is not between '2.0000000000000000' and '3.0000000000000000'. ... + PSyData: Variable a_fld has the invalid value 'Inf' at index/indices 1 1 in module 'main', region 'update'. + +As indicated in :ref:`value range check`, you can +also check a variable in all kernels of a module, or in any instrumented +code region (since the example has only one module, both settings below +will create the same warnings): + +.. code-block:: shell + + PSYVERIFY__main__b_fld=2:3 ./value_range_check + PSYVERIFY__b_fld=2:3 ./value_range_check + ... + PSyData: Variable b_fld has the value 0.0000000000000000 at index/indices 6 1 in module 'main', region 'init', which is not between '2.0000000000000000' and '3.0000000000000000'. + ... + PSyData: Variable b_fld has the value 0.0000000000000000 at index/indices 6 1 in module 'main', region 'update', which is not between '2.0000000000000000' and '3.0000000000000000'. + +Notice that now a warning is created for both kernels: ``init`` and ``update``. + +Support for checking arbitrary Fortran code is tracked as issue #2741. Example 6: PSy-layer Code Creation using PSyIR diff --git a/doc/user_guide/libraries.rst b/doc/user_guide/libraries.rst index a9fc70c756..06d82d9c3e 100644 --- a/doc/user_guide/libraries.rst +++ b/doc/user_guide/libraries.rst @@ -108,17 +108,18 @@ the ``lib/read_only`` `directory For detailed instructions on how to build and use these libraries please refer to their specific ``README.md`` documentation. -NAN Test -^^^^^^^^ +Value Range Check +^^^^^^^^^^^^^^^^^ -These libraries test all input and output parameters of a kernel to -make sure they are not ``NaN`` or infinite. More information can be -found in the :ref:`NAN Test ` section. +These libraries can test if user-defined variables are within a +specified range. Additionally, they also verify that they are +not ``NaN`` or infinite. More information can be +found in the :ref:`Value Range Check ` section. The libraries for :ref:`LFRic ` and :ref:`GOcean ` APIs are included with PSyclone in -the ``lib/nan_test`` `directory -`__. +the ``lib/value_range_check`` `directory +`__. For detailed instructions on how to build and use these libraries please refer to their specific ``README.md`` documentation. @@ -137,9 +138,9 @@ The majority of wrapper libraries use `Jinja classes (please refer to :ref:`dev_guide:psy_data` and :ref:`dev_guide:jinja` for full details about the PSyData API). -Compilation of ``extract``, ``nan_test``, ``read_only`` and some of the +Compilation of ``extract``, ``value_range_check``, ``read_only`` and some of the profiling wrapper libraries depends on infrastructure libraries relevant -to the API they are used for. :ref:`LFRic API ` uses the +to the API they are used for. The :ref:`LFRic API ` uses the LFRic infrastructure and :ref:`GOcean ` uses the dl_esm_inf library. The LFRic infrastructure can be obtained from the LFRic `code repository `_, diff --git a/doc/user_guide/psy_data.rst b/doc/user_guide/psy_data.rst index cf3a1004f7..133f21bcb0 100644 --- a/doc/user_guide/psy_data.rst +++ b/doc/user_guide/psy_data.rst @@ -72,10 +72,12 @@ Access Verification: to a field (and scalar values). See :ref:`psydata_read_verification` for details. -NAN Test: +Value Range Check: The callbacks can be used to make sure that all floating point input - and output parameters of a kernel are not a ``NaN`` (not-a-number) or - infinite. See :ref:`psydata_nan_test` for the full description. + and output parameters of a kernel are within a user-specified range. + Additionally, it will also verify that the values are not a ``NaN`` + (not-a-number) or infinite. See :ref:`psydata_value_range_check` for + the full description. In-situ Visualisation: By giving access to output fields of a kernel, an in-situ visualisation @@ -99,7 +101,7 @@ Read-Only Verification ---------------------- The PSyData interface is being used to verify that read-only variables -in a kernel are not overwritten. The ``ReadOnlyVerifyTrans`` (in +in a kernel are not overwritten. The ``ReadOnlyVerifyTrans`` (in ``psyir.transformations.read_only_verify_trans``, or the :ref_guide:`Transformation Reference Guide psyclone.psyir.transformations.html#classes`) uses the dependency analysis to determine all read-only variables (i.e. arguments declared @@ -145,11 +147,12 @@ Both libraries support the environment variable ``PSYDATA_VERBOSE``. This can be used to control how much output is generated by the read-only-verification library at runtime. If the variable is not specified or has the value '0', warnings will only -be printed if checksums change. If it is set to '1', a message will be +be printed if checksums change. If it is set to '1', a message will be printed before and after each kernel call that is checked. If the variable is set to '2', it will additionally print the name of each variable that is checked. + Read-Only Verification Library for LFRic ++++++++++++++++++++++++++++++++++++++++ @@ -185,6 +188,13 @@ the required variables: This will create a library called ``lib_read_only.a``. +An executable example for using the LFRic read-only-verification library is +included in ``tutorial/practicals/LFRic/building_code/4_psydata`` directory, +see `this link for more information +`_. + + + Read-Only-Verification Library for GOcean +++++++++++++++++++++++++++++++++++++++++ @@ -224,39 +234,87 @@ An executable example for using the GOcean read-only-verification library is included in ``examples/gocean/eg5/readonly``, see :ref:`gocean_example_readonly`. -.. _psydata_nan_test: +.. _psydata_value_range_check: -NAN Test --------- +Value Range Check +----------------- This transformation can be used for both LFRic and GOcean APIs. It will -test all input and output parameters of a kernel to make sure they are not -``NaN`` or infinite. If they are, an error message like the following -is printed, but the program is not aborted:: +test all input and output parameters of a kernel to make sure they are +within a user-specified range. Additionally, it will also verify that floating +point values are not ``NaN`` or infinite. + +At runtime, environment variables must be specified to indicate which variables +are within what expected range, and optionally also at which location. +The range is specified as a ``:`` separated tuple:: + + 1.1:3.3 A value between 1.1 and 3.3 (inclusive). + :3.3 A value less than or equal to 3.3 + 1.1: A value greater than or equal to 1.1 + +The syntax for the environment variable is one of: + +``PSYVERIFY__module__kernel__variable`` + The specified variable is tested when calling the specified kernel in the + specified module. + +``PSYVERIFY__module__variable`` + The specified variable name is tested in all kernel calls of the + specified module that are instrumented with the ValueRangeCheckTrans + transformation. + +``PSYVERIFY__variable`` + The specified variable name is tested in any instrumented code region. - PSyData: Variable a_fld has the invalid value Inf at index/indices 1 1 in module 'main' region 'update'. +If the module name or kernel name contains a `-` (which can be inserted +by PSyclone, e.g. `invoke_compute-r1`), it needs to be replaced with an +underscore character in the environment variable (`_`) -Is uses the function ``IEEE_IS_FINITE`` from the ieee_arithmetic module -for this test. Note that only floating point numbers will be tested. -Integer numbers do not have a bit pattern for 'infinity' or ``NaN``. +An example taken from the LFric tutorial (note that values greater than +4000 are actually valid, the upper limit was just chosen to show +a few warnings raised by the value range checker):: + + PSYVERIFY__time_evolution__invoke_initialise_perturbation__perturbation_data=0.0:4000 + PSYVERIFY__time_evolution__perturbation_data=0.0:4000 + PSYVERIFY__perturbation_data=0.0:4000 + +.. warning:: Note that while the field variable is called `perturbation`, PSyclone will + append `_data` when the LFRic domain is used, so the name becomes + `perturbation_data`. You have to use + this name in LFRic in order to trigger the value range check. To verify + that the tests are done as expected, set the environment variable + `PSYDATA_VERBOSE` to 1, which will print which data is taken from the + environment variables: + + .. code-block:: bash + + PSyData: checking 'time_evolution' region 'invoke_initialise_perturbation' : 0.0000000000000000 <= perturbation_data <= 4000.0000000000000 + + +If values outside the specified range are found, appropriate warnings are printed, +but the program is not aborted:: + + PSyData: Variable 'perturbation_data' has the value 4227.3587826606408 at index/indices 27051 in module 'time_evolution', region 'invoke_initialise_perturbation', which is not between '0.0000000000000000' and '4000.0000000000000'. + + +The library uses the function ``IEEE_IS_FINITE`` from the ieee_arithmetic module +for additionally verifying that values are not ``NAN`` or ``infinity`` +for any floating point variable, even if no ``PSY_VERIFY...`` environment +variable is set for this variable. Integer numbers do not have a bit pattern +for 'infinity' or ``NaN``, so they will only be tested for valid range +if a corresponding environment variable is specified. The runtime libraries for GOcean and LFRic are based on a jinja-template -contained in the directory ``/lib/nan_test``. +contained in the directory ``/lib/value_range_check``. The respective API-specific libraries map the internal field structures to Fortran basic types and call the functions from the base class to handle those. The relevant libraries for the LFRic and GOcean APIs are contained in -the ``lib/nan_test/lfric`` and``lib/nan_test/dl_esm_inf`` subdirectories, +the ``lib/value_range_check/lfric`` and ``lib/value_range_check/dl_esm_inf`` subdirectories, respectively. For more information on how to build and link these libraries, please refer to the relevant ``README.md`` files. -An executable example for using the LFRic read-only-verification library is -included in ``tutorial/practicals/LFRic/building_code/4_psydata`` directory, -see `this link for more information -`_. - - .. _integrating_psy_data_lfric: Integrating PSyData Libraries into the LFRic Build Environment diff --git a/examples/gocean/README.md b/examples/gocean/README.md index 349a6ed239..6eb0096924 100644 --- a/examples/gocean/README.md +++ b/examples/gocean/README.md @@ -30,22 +30,29 @@ Examples of the application of kernel transforms to kernels that access data and/or routines from other Fortran modules. Note that this is not yet fully supported and is the subject of Issue #342. -## Example 5a +## Example 5a (profile) Illustrates the use of the profiling support in PSyclone. The resulting code may be compiled and executed. -## Example 5b +## Example 5b (extract) Illustrates the use of the kernel-data extraction support in PSyclone. The resulting code may be compiled and executed (requires a netcdf installation). -## Example 5c +## Example 5c (readonly) Illustrates the use of the read-only verification in PSyclone. The resulting code may be compiled and executed to show warnings printed by the read-only verification. +## Example 5d (value_range_check) + +Illustrates the use of the value range check in PSyclone. The +resulting code may be compiled and executed to show warnings printed. +Note that certain environment variables need to be defined to enable +the value range check, see the README.md in that directory for details. + ## Example 6 Informs the development of the code generation of PSy-layer code using the diff --git a/examples/gocean/eg5/Makefile b/examples/gocean/eg5/Makefile index 47957c07a5..062f17a4c9 100644 --- a/examples/gocean/eg5/Makefile +++ b/examples/gocean/eg5/Makefile @@ -33,6 +33,6 @@ # ------------------------------------------------------------------------------ # Author: J. Henrichs, Bureau of Meteorology -EXAMPLES=extract nan profile readonly +EXAMPLES=extract value_range_check profile readonly include ../../top_level.mk diff --git a/examples/gocean/eg5/nan/.gitignore b/examples/gocean/eg5/nan/.gitignore deleted file mode 100644 index 93c2ab5779..0000000000 --- a/examples/gocean/eg5/nan/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -alg.f90 -psy.f90 -nan_test - diff --git a/examples/gocean/eg5/value_range_check/.gitignore b/examples/gocean/eg5/value_range_check/.gitignore new file mode 100644 index 0000000000..642042bb2b --- /dev/null +++ b/examples/gocean/eg5/value_range_check/.gitignore @@ -0,0 +1,3 @@ +alg.f90 +psy.f90 +value_range_check diff --git a/examples/gocean/eg5/nan/Makefile b/examples/gocean/eg5/value_range_check/Makefile similarity index 73% rename from examples/gocean/eg5/nan/Makefile rename to examples/gocean/eg5/value_range_check/Makefile index eea9a89b95..0819780229 100644 --- a/examples/gocean/eg5/nan/Makefile +++ b/examples/gocean/eg5/value_range_check/Makefile @@ -39,13 +39,13 @@ # export F90FLAGS="-O3" # The dl_esm_inf infrastructure library: # export INF_DIR=../../../external/dl_esm_inf/finite_difference/src -# The nan-test wrapper library: -# export NAN_TEST_DIR = ../../../lib/nan_test/dl_esm_inf +# The value-range-check wrapper library: +# export VALUE_RANGE_CHECK_DIR=../../../lib/value_range_check/dl_esm_inf PSYROOT=../../../.. include $(PSYROOT)/examples/common.mk -GENERATED_FILES += *.o *.mod nan_test alg.f90 psy.f90 +GENERATED_FILES += *.o *.mod value_range_check alg.f90 psy.f90 # Location of the infrastucture code (which is a submodule of the # PSyclone git repo). @@ -53,41 +53,41 @@ SHARED_DIR ?= $(PSYROOT)/external INF_DIR ?= $(SHARED_DIR)/dl_esm_inf/finite_difference INF_INC = $(INF_DIR)/src INF_LIB = $(INF_DIR)/src/lib_fd.a -NAN_TEST_DIR = $(PSYROOT)/lib/nan_test/dl_esm_inf -F90FLAGS += -I$(INF_INC) -I $(NAN_TEST_DIR) -LIB_NAME = lib_nan_test.a +VALUE_RANGE_CHECK_DIR = $(PSYROOT)/lib/value_range_check/dl_esm_inf +F90FLAGS += -I$(INF_INC) -I $(VALUE_RANGE_CHECK_DIR) +LIB_NAME = lib_value_range_check.a # The two kernels used in the application. KERNELS = init_field_mod.o update_field_mod.o # The name of the executable -NAME = nan_test +NAME = value_range_check -.PHONY: default $(NAN_TEST_DIR)/$(LIB_NAME) $(INF_LIB) +.PHONY: default $(VALUE_RANGE_CHECK_DIR)/$(LIB_NAME) $(INF_LIB) compile: transform $(NAME) alg.f90 psy.f90: transform -transform: test.x90 test_nan_transform.py - ${PSYCLONE} -nodm -api "gocean" -s ./test_nan_transform.py \ +transform: test.x90 value_range_check_transformation.py + ${PSYCLONE} -nodm -api "gocean" -s ./value_range_check_transformation.py\ -opsy psy.f90 -oalg alg.f90 test.x90 run: compile # Note that if the output line is not found, the make will abort with error - ./nan_test 2>&1| grep "PSyData: Variable a_fld has the invalid value" + ./value_range_check 2>&1| grep "PSyData: Variable a_fld has the invalid value" -$(NAME): $(INF_LIB) $(NAN_TEST_DIR)/$(LIB_NAME) $(KERNELS) alg.o psy.o +$(NAME): $(INF_LIB) $(VALUE_RANGE_CHECK_DIR)/$(LIB_NAME) $(KERNELS) alg.o psy.o $(F90) $(F90FLAGS) $(KERNELS) alg.o psy.o -o $(NAME) \ - -L$(NAN_TEST_DIR) -l_nan_test $(INF_LIB) - -# The nan-test library -$(NAN_TEST_DIR)/$(LIB_NAME): - @echo "Making dl_esm_inf nan-test wrapper library" - @echo "==========================================" - $(MAKE) -C $(NAN_TEST_DIR) F90=$(F90) - @echo "Finished making dl_esm_inf nan-test wrapper library" + -L$(VALUE_RANGE_CHECK_DIR) -l_value_range_check $(INF_LIB) + +# The value_range_check library +$(VALUE_RANGE_CHECK_DIR)/$(LIB_NAME): + @echo "Making dl_esm_inf value-range-check wrapper library" @echo "===================================================" + $(MAKE) -C $(VALUE_RANGE_CHECK_DIR) F90=$(F90) + @echo "Finished making dl_esm_inf value-range-check wrapper library" + @echo "============================================================" @echo # The dl_esm_inf library @@ -110,5 +110,5 @@ psy.o: $(KERNELS) $(F90) $(F90FLAGS) -c $< allclean: clean - $(MAKE) -C $(NAN_TEST_DIR) allclean + $(MAKE) -C $(VALUE_RANGE_CHECK_DIR) allclean $(MAKE) -C $(INF_DIR) allclean diff --git a/examples/gocean/eg5/nan/README.md b/examples/gocean/eg5/value_range_check/README.md similarity index 58% rename from examples/gocean/eg5/nan/README.md rename to examples/gocean/eg5/value_range_check/README.md index 10220514f2..a7b76d8b7d 100644 --- a/examples/gocean/eg5/nan/README.md +++ b/examples/gocean/eg5/value_range_check/README.md @@ -1,16 +1,16 @@ -# PSyclone GOcean PSyData NaN-check Example +# PSyclone GOcean PSyData Value Range Check Example **Author:** J. Henrichs, Bureau of Meteorology ## Introduction -This is a simple example that shows how to use the NaN verification +This is a simple example that shows how to use the value range check support in PSyclone. It is a stand-alone program that can be compiled and run. ## Compilation A makefile is provided to compile this example. If required, -it will compile the dl_esm_inf library and the nan_test +it will compile the dl_esm_inf library and the value_range_check wrapper library. By default, the compilation uses the version of the dl_esm_inf library provided as a git submodule (under ``../../../external/dl_esm_inf/finite_difference``- see @@ -18,25 +18,43 @@ https://psyclone-dev.readthedocs.io/en/latest/working_practises.html) within the PSyclone repository. You can set the environment variable ``INF_DIR`` for the ``make`` command to pick a different version. -The makefile here invokes psyclone with the script ``test_nan_transform.py``. -This script uses PSyclone's ``NanTestTrans`` to instrument the two -invokes in the ``test.x90`` source file. +The makefile here invokes psyclone with the script +``value_range_check_transformation.py.py``. +This script uses PSyclone's ``ValueRangeCheck`` transformation to +instrument the two invokes in the ``test.x90`` source file. The source code computes divisions by 0 on the diagonals, resulting in invalid numbers (Infinity). ## Running +In order to activate the value range checking, you need to +specify the value range for variables as outlined here: +https://psyclone.readthedocs.io/en/latest/psy_data.html#value-range-check + ``` -$ ./nan_test +$ PSYVERIFY__main__init__b_fld=2:3 ./value_range_check ... Allocating C-T field with bounds: (1: 6,1: 6), internal region is (2: 4,2: 4) -PSyData: Variable a_fld has the invalid value Inf at index/indices 1 1 in module 'main' region 'update'. -PSyData: Variable a_fld has the invalid value Inf at index/indices 2 2 in module 'main' region 'update'. +PSyData: Variable 'b_fld' has the value 0.0000000000000000 at index/indices 6 1 in module 'main', region 'init', which is not between '2.0000000000000000' and '3.0000000000000000'. +... +PSyData: Variable 'a_fld' has the invalid value 'Inf' at index/indices 1 1 in module 'main', region 'update'. +... + +``` +Several warnings will be printed - the first set caused by having values not between +2 and 3 in the `init` kernel, then the warnings about Infinity being the result of +the kernel computations. + +Note that you do not need to specify a kernel name and module name if your variable +name is unique. You can remove the module and kernel name: +``` +$ PSYVERIFY__b_fld=2:3 ./value_range_check +PSyData: Variable 'b_fld' has the value 0.0000000000000000 at index/indices 6 1 in module 'main' region 'init', which is not between '2.0000000000000000' and '3.0000000000000000'. +PSyData: Variable 'b_fld' has the value 0.0000000000000000 at index/indices 6 1 in module 'main' region 'update', which is not between '2.0000000000000000' and '3.0000000000000000'. ``` -After calling the kernel ``mainupdate``, five warnings are printed, -indicating that the field ``a_fld`` has a value of infinity on the diagonals -(i.e. indices 1 1, ..., 5 5). +Now that the kernel and module names are not being specified, warnings are also printed +for the update kernel. ## Licence diff --git a/examples/gocean/eg5/nan/init_field_mod.f90 b/examples/gocean/eg5/value_range_check/init_field_mod.f90 similarity index 100% rename from examples/gocean/eg5/nan/init_field_mod.f90 rename to examples/gocean/eg5/value_range_check/init_field_mod.f90 diff --git a/examples/gocean/eg5/nan/test.x90 b/examples/gocean/eg5/value_range_check/test.x90 similarity index 93% rename from examples/gocean/eg5/nan/test.x90 rename to examples/gocean/eg5/value_range_check/test.x90 index 4ced9e302c..7bfda41c4b 100644 --- a/examples/gocean/eg5/nan/test.x90 +++ b/examples/gocean/eg5/value_range_check/test.x90 @@ -45,18 +45,17 @@ Program test use init_field_mod, only : init_field use update_field_mod, only : update_field - use nan_test_psy_data_mod, only: nan_test_PSyDataInit, & - nan_test_PSyDataShutdown, nan_test_PSyDataStart + use value_range_check_psy_data_mod, only: value_range_check_PSyDataInit, & + value_range_check_PSyDataShutdown, value_range_check_PSyDataStart TYPE(r2d_field) :: a_fld, b_fld TYPE(grid_type), target :: grid ! Initialise dl_esm_inf and create a grid: call parallel_init() - call nan_test_PSyDataInit() ! Just to show how to use the start function. It is not ! actually required, since the previous call enables it anyway. - call nan_test_PSyDataStart() + call value_range_check_PSyDataStart() grid = grid_type(GO_ARAKAWA_C, & (/GO_BC_PERIODIC,GO_BC_PERIODIC,GO_BC_NONE/), & GO_OFFSET_SW) @@ -79,6 +78,5 @@ Program test ! the output field `a_fld`. call invoke (update_field(a_fld, b_fld) ) print *,"a_fld is", a_fld%data - call nan_test_PSyDataShutdown() end program test diff --git a/examples/gocean/eg5/nan/update_field_mod.f90 b/examples/gocean/eg5/value_range_check/update_field_mod.f90 similarity index 100% rename from examples/gocean/eg5/nan/update_field_mod.f90 rename to examples/gocean/eg5/value_range_check/update_field_mod.f90 diff --git a/examples/gocean/eg5/nan/test_nan_transform.py b/examples/gocean/eg5/value_range_check/value_range_check_transformation.py similarity index 88% rename from examples/gocean/eg5/nan/test_nan_transform.py rename to examples/gocean/eg5/value_range_check/value_range_check_transformation.py index 18edcf01d1..a5ac6b62d4 100644 --- a/examples/gocean/eg5/nan/test_nan_transform.py +++ b/examples/gocean/eg5/value_range_check/value_range_check_transformation.py @@ -41,9 +41,7 @@ not infinity or NAN. ''' -from __future__ import print_function - -from psyclone.psyir.transformations import NanTestTrans +from psyclone.psyir.transformations import ValueRangeCheckTrans def trans(psy): @@ -58,7 +56,7 @@ def trans(psy): :rtype: :py:class:`psyclone.gocean1p0.GOPSy` ''' - nan_test = NanTestTrans() + value_range_check = ValueRangeCheckTrans() invoke = psy.invokes.get("invoke_0") schedule = invoke.schedule @@ -66,13 +64,15 @@ def trans(psy): # You could just apply the transform for all elements of # psy.invokes.invoke_list. But in this case we also # want to give the regions a friendlier name: - nan_test.apply(schedule.children, {"region_name": ("main", "init")}) + value_range_check.apply(schedule.children, + {"region_name": ("main", "init")}) invoke = psy.invokes.get("invoke_1_update_field") schedule = invoke.schedule - # Enclose everything in a nan_test region - nan_test.apply(schedule.children, {"region_name": ("main", "update")}) + # Enclose everything in a value_range_check region + value_range_check.apply(schedule.children, + {"region_name": ("main", "update")}) # print(schedule.view()) return psy diff --git a/examples/nemo/scripts/omp_cpu_trans.py b/examples/nemo/scripts/omp_cpu_trans.py index aa4258ba69..ceba2eadf6 100755 --- a/examples/nemo/scripts/omp_cpu_trans.py +++ b/examples/nemo/scripts/omp_cpu_trans.py @@ -47,7 +47,6 @@ # List of all files that psyclone will skip processing FILES_TO_SKIP = NOT_PERFORMANT + [ - "lbclnk.f90", # TODO #2685: effective shape bug "asminc.f90", "trosk.f90", "vremap.f90", diff --git a/examples/nemo/scripts/utils.py b/examples/nemo/scripts/utils.py index af1b5be162..39c98d9128 100755 --- a/examples/nemo/scripts/utils.py +++ b/examples/nemo/scripts/utils.py @@ -37,8 +37,8 @@ from psyclone.domain.common.transformations import KernelModuleInlineTrans from psyclone.psyir.nodes import ( - Loop, Assignment, Directive, Container, Reference, CodeBlock, Call, - Return, IfBlock, Routine, IntrinsicCall) + Assignment, Loop, Directive, Container, Reference, CodeBlock, + Call, Return, IfBlock, Routine, IntrinsicCall) from psyclone.psyir.symbols import ( DataSymbol, INTEGER_TYPE, REAL_TYPE, ArrayType, ScalarType, RoutineSymbol, ImportInterface) @@ -248,7 +248,7 @@ def normalise_loops( if convert_array_notation: # Make sure all array dimensions are explicit - for reference in schedule.walk(Reference, stop_type=Reference): + for reference in schedule.walk(Reference): part_of_the_call = reference.ancestor(Call) if part_of_the_call: if not part_of_the_call.is_elemental: diff --git a/examples/psyad/eg2/Makefile b/examples/psyad/eg2/Makefile index 508047e5c4..73f90ea521 100644 --- a/examples/psyad/eg2/Makefile +++ b/examples/psyad/eg2/Makefile @@ -64,8 +64,8 @@ TL_KERNEL_TYPE = ${TL_KERNEL_NAME}_type ADJ_KERNEL_TYPE = $(subst tl_,adj_,${TL_KERNEL_TYPE}) # The name of the file that will contain the generated test harness. -HARNESS_X90_FILE = adjoint_test_mod.x90 -HARNESS_F90_FILE = adjoint_test_mod.F90 +HARNESS_X90_FILE = adjt_hydrostatic_alg_mod.x90 +HARNESS_F90_FILE = adjt_hydrostatic_alg_mod.F90 GENERATED_FILES += adj_*.[fF]90 test_harness.exe \ *.x90 ${HARNESS_F90_FILE} psy.f90 *.mod *.o diff --git a/examples/psyad/eg2/README.md b/examples/psyad/eg2/README.md index b65f6155d8..65ba294967 100644 --- a/examples/psyad/eg2/README.md +++ b/examples/psyad/eg2/README.md @@ -18,9 +18,9 @@ make This will construct the adjoint of the kernel (written to `adj_hydrostatic_kernel_mod.x90`) and a test harness in the form of LFRic -Algorithm-layer code (`adjoint_test_mod.x90`). The Makefile then proceeds to +Algorithm-layer code (`adjt_hydrostatic_alg_mod.x90`). The Makefile then proceeds to use PSyclone to process the test harness Algorithm code to generate algorithm -(`adjoint_test_mod.F90`) and PSy layer (`psy.f90`) code. +(`adjt_hydrostatic_alg_mod.F90`) and PSy layer (`psy.f90`) code. There is no `compile` target for this example because the generated code requires the full LFRic infrastructure. However, it is straightforward @@ -36,9 +36,9 @@ psyad tl_hydrostatic_kernel_mod.F90 -a r_u exner theta moist_dyn_gas moist_dyn_t In this case, the adjoint of the tangent-linear kernel is written to `stdout`. -## Using the generated test harness in the LFRic skeleton mini-app +## Using the generated test harness in the LFRic core skeleton application -These instructions assume that you have a local, compiled version of LFRic +These instructions assume that you have a local, compiled version of LFRic core in `` and that the directory containing this file is ``. 1. Create the adjoint kernel and test harness code: @@ -51,7 +51,7 @@ make test-harness code and the TL and adjoint kernels: ```sh cd /miniapps/skeleton -cp /adjoint_test_mod.x90 source/algorithm/. +cp /adjt_hydrostatic_alg_mod.x90 source/algorithm/. cp /tl_hydrostatic_kernel_mod.F90 source/kernel/. cp /adj_hydrostatic_kernel_mod.F90 source/kernel/. ``` @@ -61,12 +61,12 @@ cp /adj_hydrostatic_kernel_mod.F90 source/kernel/. example - if the skeleton mini-app is modified on LFRic trunk then it will need to be updated): ```sh -sed -e 's/ subroutine run()/ subroutine run()\n use adjoint_test_mod, only: adjoint_test/' -e 's/call skeleton_alg(field_1)/call adjoint_test(mesh, chi, panel_id)/' source/driver/skeleton_driver_mod.f90 > new_driver.f90 +sed -e 's/ subroutine run()/ subroutine run()\n use adjt_hydrostatic_alg_mod, only: adjt_hydrostatic_alg/' -e 's/call skeleton_alg(field_1)/call adjt_hydrostatic_alg(mesh, chi, panel_id)/' source/driver/skeleton_driver_mod.f90 > new_driver.f90 mv source/driver/skeleton_driver_mod.f90{,.bak} mv new_driver.f90 source/driver/skeleton_driver_mod.f90 ``` -4. Build the modified miniapp: +4. Build the modified application: ```sh make clean make @@ -75,7 +75,7 @@ make Note that at this stage you may get errors relating to the LFRic unit-test framework. These can be ignored. -5. Run the miniapp: +5. Run the application: ```sh cd example ../bin/skeleton ./configuration.nml diff --git a/lib/Makefile b/lib/Makefile index 8ea3bfb4e3..95496615bb 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -59,6 +59,7 @@ all: $(MAKE) -C extract all $(MAKE) -C profiling all $(MAKE) -C read_only all + $(MAKE) -C value_range_check all %.f90: %.jinja process.py $$($(PSYDATA_LIB_DIR)/get_python.sh) $(PSYDATA_LIB_DIR)/process.py $< > $*.f90 diff --git a/lib/nan_test/.gitignore b/lib/nan_test/.gitignore deleted file mode 100644 index 1de9d777ee..0000000000 --- a/lib/nan_test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -nan_test_base.f90 diff --git a/lib/nan_test/lfric/.gitignore b/lib/nan_test/lfric/.gitignore deleted file mode 100644 index 911b06380e..0000000000 --- a/lib/nan_test/lfric/.gitignore +++ /dev/null @@ -1 +0,0 @@ -nan_test.f90 diff --git a/lib/nan_test/nan_test_base.jinja b/lib/nan_test/nan_test_base.jinja deleted file mode 100644 index f70c3c9cff..0000000000 --- a/lib/nan_test/nan_test_base.jinja +++ /dev/null @@ -1,229 +0,0 @@ -{# Added this as Jinja code so that it is understood that the - comment does not apply to THIS file. #} -{{ "! ================================================== !" }} -{{ "! THIS FILE IS CREATED FROM THE JINJA TEMPLATE FILE. !" }} -{{ "! DO NOT MODIFY DIRECTLY! !" }} -{{ "! ================================================== !" }} - -{# This jinja template file creates a base class for a not-NAN - verification library. It produces the required ProvideVariable() - functions for the specified Fortran basic types. Any library - using this base class can provide the required Fortran basic - types (see ALL_TYPES below) and the list of array dimensions - (see ALL_DIMS) that need to be supported when processing this - template. - - This NANTest base class depends on the PSyData base - class, which will provide the other Fortran-type-specific - functions for PreDeclarVariable(). Any function can obviously - be overwritten by a derived class. -#} - -{% if ALL_DIMS is not defined %} - {# Support 1 to 4 dimensional arrays if not specified #} - {% set ALL_DIMS = [1, 2, 3, 4] %} -{% endif %} - -{# The types that are supported. The first entry of each tuple - is the name used when naming subroutines and in user messages. - The second entry is the Fortran declaration. The third entry - is the number of bits. There is slightly different code - required for 32 and 64 bit values (due to the fact that the - Fortran transfer(value, mould) function leaves undefined bits - when mould is larger than value.) #} - -{% if ALL_TYPES is not defined %} - {% set ALL_TYPES = [ ("Double", "real(kind=real64)", 64), - ("Real", "real(kind=real32)", 32), - ("Int", "integer(kind=int32)", 32) ] %} -{% endif %} - -! ----------------------------------------------------------------------------- -! BSD 3-Clause License -! -! Copyright (c) 2020-2021, Science and Technology Facilities Council. -! All rights reserved. -! -! Redistribution and use in source and binary forms, with or without -! modification, are permitted provided that the following conditions are met: -! -! * Redistributions of source code must retain the above copyright notice, this -! list of conditions and the following disclaimer. -! -! * Redistributions in binary form must reproduce the above copyright notice, -! this list of conditions and the following disclaimer in the documentation -! and/or other materials provided with the distribution. -! -! * Neither the name of the copyright holder nor the names of its -! contributors may be used to endorse or promote products derived from -! this software without specific prior written permission. -! -! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -! FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -! COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -! INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -! BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -! ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -! POSSIBILITY OF SUCH DAMAGE. -! ----------------------------------------------------------------------------- -! Author J. Henrichs, Bureau of Meteorology -! Modified I. Kavcic, Met Office - -!> This module implements a PSyData-based verification that floating point -!! input and output parameters are not NAN and not infinite. -!! - -module nan_test_base_mod - - use, intrinsic :: iso_fortran_env, only : int64, int32, & - real32, real64, & - stderr => Error_Unit - use psy_data_base_mod, only : PSyDataBaseType, & - nan_test_PSyDataShutdown, & - nan_test_PSyDataInit, is_enabled, & - nan_test_PSyDataStart, nan_test_PSyDataStop - - implicit none - - type, extends(PSyDataBaseType), public :: NANTestBaseType - - contains - - ! The various procedures used - - {# Collect and declare the various procedures for the same generic interface -#} - {# ------------------------------------------------------------------------- -#} - {% set all_declares=[] -%} - {% set all_provides=[] -%} - {% for name, type, bits in ALL_TYPES %} - procedure :: ProvideScalar{{name}} - {{- all_provides.append("ProvideScalar"~name) or "" }} - {% for dim in ALL_DIMS %} - procedure :: ProvideArray{{dim}}d{{name}} - {{- all_provides.append("ProvideArray"~dim~"d"~name) or "" }} - {% endfor %} - {% endfor %} - - {% set indent=" " %} - {% if GENERIC_PROVIDE %} - !> The generic interface for providing the value of variables: - generic, public :: ProvideVariable => & - {{all_provides|join(", &\n"+indent) }} - {% endif %} - - end type NANTestBaseType - -contains - - ! ========================================================================= - ! Jinja created code. - ! ========================================================================= - -{% for name, type, bits in ALL_TYPES %} - - ! ========================================================================= - ! Implementation for all {{type}} types - ! ========================================================================= - ! ------------------------------------------------------------------------- - !> @brief This subroutine checks if a floating point value is NAN or infinite - !! using the IEEE_IS_FINIT function (and does nothing for integer types). - !! @param[in,out] this The instance of the NANTestBaseType. - !! @param[in] name The name of the variable (string). - !! @param[in] value The value of the variable. - subroutine ProvideScalar{{name}}(this, name, value) - - use, intrinsic :: ieee_arithmetic - - implicit none - - class(NANTestBaseType), intent(inout), target :: this - character(*), intent(in) :: name - {{type}}, intent(in) :: value - - {% if name not in ["Int", "Logical"] %} - if (.not. is_enabled) return - - if (IEEE_SUPPORT_DATATYPE(value)) then - if (.not. IEEE_IS_FINITE(value)) then - write(stderr, '(8G0)') "PSyData: Variable ", name," has invalid value ", & - value, " in module '", trim(this%module_name), & - "' region '", trim(this%region_name),"'." - endif - endif - {% else %} - ! Variables of type {{type}} do not have NANs - ! So nothing to do here. - {% endif %} - - call this%PSyDataBaseType%ProvideScalar{{name}}(name, value) - - end subroutine ProvideScalar{{name}} - - {# Now provide the array implementations #} - {# ------------------------------------- #} - {% for dim in ALL_DIMS %} - {# Create the ':,:,:,:' string - We repeat the list [":"] DIM-times, which is then joined #} - {% set DIMENSION=([":"]*dim)|join(",") %} - - {# Create list of variables: "i1, i2, i3, i4" #} - {% set vars = "i"~range(1,dim+1)|join(", i") %} - {% set indent = " "*3*dim %} - - ! ------------------------------------------------------------------------- - !> @brief This method checks if an array contains NAN or infinite IEEE values (it - !! does nothing in case that the array is an integer type). - !! @param[in,out] this The instance of the NANTestBaseType. - !! @param[in] name The name of the variable (string). - !! @param[in] value The value of the variable. - subroutine ProvideArray{{dim}}d{{name}}(this, name, value) - - use, intrinsic :: ieee_arithmetic - - implicit none - - class(NANTestBaseType), intent(inout), target :: this - character(*), intent(in) :: name - {{type}}, dimension({{DIMENSION}}), intent(in) :: value - - {# IEEE_SUPPORT_DATATYPE does not even compile for int data types #} - {% if name not in ["Int", "Logical"] %} - integer :: {{vars}} - - if (.not. is_enabled) return - if (IEEE_SUPPORT_DATATYPE(value)) then - {# The spaces take care of proper indentation #} - {% for j in range(dim, 0, -1) %} - {{ " "*3*(dim-j)}}do i{{j}}=1, size(value, {{j}}) - {% endfor %} - {{indent}}if (.not. IEEE_IS_FINITE(value({{vars}}))) then - {{indent}} write(stderr, '(5G0,{{dim}}(G0,X),5G0)') "PSyData: Variable ", & - {{indent}} name," has the invalid value ", & - {{indent}} value({{vars}}), " at index/indices ", {{vars}}, & - {{indent}} "in module '", trim(this%module_name), & - {{indent}} "' region '", trim(this%region_name),"'." - {{indent}}endif - {% for j in range(dim, 0, -1) %} - {{" "*3*(j-1)}}enddo - {% endfor %} - endif - {% else %} - ! Variables of type {{type}} do not have NANs - ! So nothing to do here. - {% endif %} - - call this%PSyDataBaseType%ProvideArray{{dim}}d{{name}}(name, value) - - end subroutine ProvideArray{{dim}}d{{name}} - - {% endfor -%} {# for dim #} -{%- endfor -%} {# for #} - - ! ------------------------------------------------------------------------- - -end module nan_test_base_mod diff --git a/lib/read_only/read_only_base.jinja b/lib/read_only/read_only_base.jinja index e5f44e2efe..13433f6f10 100644 --- a/lib/read_only/read_only_base.jinja +++ b/lib/read_only/read_only_base.jinja @@ -247,7 +247,7 @@ contains class(ReadOnlyBaseType), intent(inout), target :: this call this%PSyDataBaseType%PostStart() - ! The pointer mst be reset to 1 to make sure we compare + ! The pointer must be reset to 1 to make sure we compare ! with the previously computed checksums this%next_var_index = 1 this%verify_checksums = .true. diff --git a/lib/value_range_check/.gitignore b/lib/value_range_check/.gitignore new file mode 100644 index 0000000000..46d2dc024b --- /dev/null +++ b/lib/value_range_check/.gitignore @@ -0,0 +1 @@ +value_range_check_base.f90 diff --git a/lib/nan_test/Makefile b/lib/value_range_check/Makefile similarity index 89% rename from lib/nan_test/Makefile rename to lib/value_range_check/Makefile index 5b284ef916..878d9d75a5 100644 --- a/lib/nan_test/Makefile +++ b/lib/value_range_check/Makefile @@ -50,16 +50,20 @@ PSYDATA_LIB_DIR ?= ./.. # The nan-test library is implemented for int, real and # double scalars and 2-dimension arrays -PROCESS_ARGS = -prefix=nan_test_ -types=int,real,double \ +PROCESS_ARGS = -prefix=value_range_check_ -types=int,real,double \ -dims=2 PROCESS = $$($(PSYDATA_LIB_DIR)/get_python.sh) $(PSYDATA_LIB_DIR)/process.py -default: nan_test_base.o psy_data_base.o +default: value_range_check_base.o psy_data_base.o -process: nan_test_base.f90 +process: value_range_check_base.f90 -.PHONY: default process clean +.PHONY: default process clean all + +all: + $(MAKE) -C dl_esm_inf + $(MAKE) -C lfric %.f90: %.jinja Makefile $(PROCESS) $(PROCESS_ARGS) -generic-provide $< > $*.f90 @@ -67,10 +71,10 @@ process: nan_test_base.f90 psy_data_base.f90: $(PSYDATA_LIB_DIR)/psy_data_base.jinja Makefile $(PROCESS) $(PROCESS_ARGS) -generic-declare $< > psy_data_base.f90 -nan_test_base.o: psy_data_base.o +value_range_check_base.o: psy_data_base.o %.o: %.f90 $(F90) $(F90FLAGS) -c $< clean: - rm -f nan_test_base.f90 psy_data_base.f90 *.o *.mod + rm -f value_range_check_base.f90 psy_data_base.f90 *.o *.mod diff --git a/lib/nan_test/README.md b/lib/value_range_check/README.md similarity index 82% rename from lib/nan_test/README.md rename to lib/value_range_check/README.md index bad4bde098..9455ba66d0 100644 --- a/lib/nan_test/README.md +++ b/lib/value_range_check/README.md @@ -1,17 +1,18 @@ -# ``NaN``-Test Verification Libraries +# ``ValueRangeCheck``- Libraries This directory contains files related to testing all input and output -parameters of a kernel to make sure they are not [``NaN`` or infinite]( -https://psyclone.readthedocs.io/en/latest/psy_data.html#nan-test), -i.e. checks at runtime that a read-only parameter of a subroutine is indeed -not changed in a kernel. There is a [PSyData base class]( +parameters of a kernel to make sure they are within a user-specified range, +and not [``NaN`` or infinite]( +https://psyclone.readthedocs.io/en/latest/psy_data.html#psydata-value-range-check). + +There is a [PSyData base class]( https://psyclone-dev.readthedocs.io/en/latest/psy_data.html#psydata-base-class) as a Jinja template that can be used to simplify the creation of API-specific wrapper libraries. -## NANTest base class +## ValueRangeCheck base class -The file ``nan_test_base.jinja`` contains a Jinja template that is used +The file ``value_range_check_base.jinja`` contains a Jinja template that is used by the [GOcean ``dl_esm_inf``-](./dl_esm_inf/README.md) and [LFRic-specific]( ./lfric/README.md) wrapper libraries. It implements the required [PSyData API]( https://psyclone.readthedocs.io/en/stable/psy_data.html) calls for @@ -29,13 +30,13 @@ this directory. ## [``dl_esm_inf``](./dl_esm_inf) directory -Contains the ``NaN``-test, PSyData-API-based, wrapper library for the +Contains the ``ValueRangeCheck``, PSyData-API-based, wrapper library for the ``dl_esm_inf`` [GOcean API]( https://psyclone.readthedocs.io/en/latest/gocean1p0.html). ## [``lfric``](./lfric) directory -Contains the ``NaN``-test, PSyData-API-based, wrapper library for the +Contains the ``ValueRangeCheck``, PSyData-API-based, wrapper library for the [LFRic (Dynamo 0.3) API]( https://psyclone.readthedocs.io/en/stable/dynamo0p3.html). @@ -46,7 +47,7 @@ https://psyclone.readthedocs.io/en/stable/dynamo0p3.html). BSD 3-Clause License -Copyright (c) 2020-2024, Science and Technology Facilities Council. +Copyright (c) 2024, Science and Technology Facilities Council. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -78,5 +79,4 @@ POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------- Authors: J. Henrichs, Bureau of Meteorology, - I. Kavcic, Met Office --> diff --git a/lib/nan_test/dl_esm_inf/Makefile b/lib/value_range_check/dl_esm_inf/Makefile similarity index 80% rename from lib/nan_test/dl_esm_inf/Makefile rename to lib/value_range_check/dl_esm_inf/Makefile index 7f0eba660a..387a17103a 100644 --- a/lib/nan_test/dl_esm_inf/Makefile +++ b/lib/value_range_check/dl_esm_inf/Makefile @@ -52,7 +52,7 @@ F90FLAGS ?= # distributed with PSyclone. Overwrite for a different infrastructure version. GOCEAN_INF_DIR ?= ./../../../external/dl_esm_inf/finite_difference # Paths to the PSyclone wrapper libraries and required templates. They default -# to the relative paths to the top-level 'lib' and 'lib/nan_test' +# to the relative paths to the top-level 'lib' and 'lib/value_range_check' # directories. Overwrite for a custom location. PSYDATA_LIB_DIR ?= ./../.. LIB_TMPLT_DIR ?= ./.. @@ -63,14 +63,14 @@ INF_LIB_NAME = _fd INF_LIB = $(GOCEAN_INF_DIR)/src/lib$(INF_LIB_NAME).a F90FLAGS += -I$(INF_INC) -PSYDATA_LIB_NAME = _nan_test +PSYDATA_LIB_NAME = _value_range_check PSYDATA_LIB = lib$(PSYDATA_LIB_NAME).a -OBJS = nan_test.o psy_data_base.o nan_test_base.o +OBJS = value_range_check.o psy_data_base.o value_range_check_base.o # The arguments for the jinja templates. GOcean only needs # 2-dimensional arrays, and int, real- and double basic Fortran types -PROCESS_ARGS = -prefix=nan_test_ -types=int,real,double \ +PROCESS_ARGS = -prefix=value_range_check_ -types=int,real,double \ -dims=2 PROCESS = $$($(PSYDATA_LIB_DIR)/get_python.sh) $(PSYDATA_LIB_DIR)/process.py @@ -83,32 +83,32 @@ $(PSYDATA_LIB): $(INF_LIB) $(OBJS) # Dependencies: # ------------- -# Compilation of nan_test needs the .mod file from dl_esm_inf, -# the base class nan_test_base (which in turn needs psy_data_base). -nan_test.o: $(INF_LIB) nan_test.f90 nan_test_base.o +# Compilation of value_range_check needs the .mod file from dl_esm_inf, +# the base class value_range_check_base (which in turn needs psy_data_base). +value_range_check.o: $(INF_LIB) value_range_check.f90 value_range_check_base.o -nan_test_base.o: psy_data_base.o +value_range_check_base.o: psy_data_base.o # Rules for file creation # ----------------------- %.o: %.f90 $(F90) $(F90FLAGS) -c $< -# The dl_esm_inf NaN-test library is built on the PSyData base library -# and the NaN-test base library. So add the rules to process +# The dl_esm_inf ValueRangeCheck library is built on the PSyData base library +# and the ValueRangeCheck base library. So add the rules to process # these files to get the plain Fortran files: psy_data_base.f90: $(PSYDATA_LIB_DIR)/psy_data_base.jinja Makefile $(PROCESS) $(PROCESS_ARGS) -generic-declare $< > psy_data_base.f90 -nan_test_base.f90: $(LIB_TMPLT_DIR)/nan_test_base.jinja Makefile - $(PROCESS) $(PROCESS_ARGS) -generic-provide $< > nan_test_base.f90 +value_range_check_base.f90: $(LIB_TMPLT_DIR)/value_range_check_base.jinja Makefile + $(PROCESS) $(PROCESS_ARGS) -generic-provide $< > value_range_check_base.f90 # The dl_esm_inf library $(INF_LIB): $(MAKE) -C $(GOCEAN_INF_DIR) clean: - rm -f *.o *.mod $(PSYDATA_LIB) psy_data_base.* nan_test_base.* + rm -f *.o *.mod $(PSYDATA_LIB) psy_data_base.* value_range_check_base.* allclean: clean $(MAKE) -C $(GOCEAN_INF_DIR) clean diff --git a/lib/nan_test/dl_esm_inf/README.md b/lib/value_range_check/dl_esm_inf/README.md similarity index 80% rename from lib/nan_test/dl_esm_inf/README.md rename to lib/value_range_check/dl_esm_inf/README.md index 3b7ae5a492..079bc4a8dc 100644 --- a/lib/nan_test/dl_esm_inf/README.md +++ b/lib/value_range_check/dl_esm_inf/README.md @@ -1,9 +1,9 @@ -# ``NaN``-Test Verification Library for GOcean +# ``ValueRangeCheck`` Library for GOcean This library implements the [PSyData API]( -https://psyclone.readthedocs.io/en/latest/psy_data.html#nan-test) -to verify that input and output parameters of a GOcean kernel are not ``NaN`` -or infinite, using the [``dl_esm_inf`` library]( +https://psyclone.readthedocs.io/en/latest/psy_data.html#psydata-value-range-check) +to verify that input and output parameters of a GOcean kernel are within +a user-specified range, and not ``NaN`` or infinite, using the [``dl_esm_inf`` library]( https://github.com/stfc/dl_esm_inf). ## Dependencies @@ -21,13 +21,13 @@ with the application. The following dependencies must be available: submodules). However, it is not included in the PSyclone [installation]( ./../../README.md#installation) and has to be cloned separately. -- The NANTest (``nan_test_base.jinja``) and PSyData +- The ValueRangeCheck (``value_range_check_base.jinja``) and PSyData (``psy_data_base.jinja``) base classes, which are included in PSyclone installation. These Jinja templates are processed to create - the ``NaN``-test verification code for ``integer``, 32- and 64-bit ``real`` + the ``ValueRangeCheck`` verification code for ``integer``, 32- and 64-bit ``real`` scalars, and 2-dimensional ``real`` and ``integer`` arrays. The generated - Fortran modules, ``nan_test_base.f90`` and ``psy_data_base.f90``, are then - used by the supplied ``nan_test.f90`` module to create the wrapper library. + Fortran modules, ``value_range_check_base.f90`` and ``psy_data_base.f90``, are then + used by the supplied ``value_range_check.f90`` module to create the wrapper library. ## Compilation @@ -47,12 +47,12 @@ so the exact path **must be specified** during the compilation process, e.g. GOCEAN_INF_DIR= make ``` -The locations of the NANTest and PSyData base classes are specified +The locations of the ValueRangeCheck and PSyData base classes are specified using the environment variables ``$LIB_TMPLT_DIR`` and ``$PSYDATA_LIB_DIR``, respectively. They default to the relative paths to the -[``lib/nan_test``](./../) and top-level [``lib``](./../../) directories. +[``lib/value_range_check``](./../) and top-level [``lib``](./../../) directories. -The compilation process will create the wrapper library ``lib_nan_test.a``. +The compilation process will create the wrapper library ``lib_value_range_check.a``. The ``Makefile`` will compile the ``dl_esm_inf`` infrastructure library, ``lib_fd.a``, if required, with the previously selected compiler flags. @@ -65,12 +65,12 @@ or compiler flags). ### Linking the wrapper library -The application needs to provide the parameters to link in this ``NaN``-test -library, ``_nan_test`` and the ``dl_esm_inf`` infrastructure library, ``_fd``. +The application needs to provide the parameters to link in this ``ValueRangeCheck`` +library, ``_value_range_check`` and the ``dl_esm_inf`` infrastructure library, ``_fd``. For instance: ```shell -$(F90) ... -L$(PSYDATA_LIB_DIR)/nan_test/dl_esm_inf -l_nan_test \ +$(F90) ... -L$(PSYDATA_LIB_DIR)/value_range_check/dl_esm_inf -l_value_range_check \ -L$(GOCEAN_INF_DIR) -l_fd ``` @@ -81,7 +81,7 @@ $(F90) ... -L$(PSYDATA_LIB_DIR)/nan_test/dl_esm_inf -l_nan_test \ BSD 3-Clause License -Copyright (c) 2020-2024, Science and Technology Facilities Council. +Copyright (c) 2024, Science and Technology Facilities Council. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -113,5 +113,4 @@ POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------- Authors: J. Henrichs, Bureau of Meteorology, - I. Kavcic, Met Office --> diff --git a/lib/nan_test/dl_esm_inf/nan_test.f90 b/lib/value_range_check/dl_esm_inf/value_range_check.f90 similarity index 75% rename from lib/nan_test/dl_esm_inf/nan_test.f90 rename to lib/value_range_check/dl_esm_inf/value_range_check.f90 index 449105ac13..16e07b0842 100644 --- a/lib/nan_test/dl_esm_inf/nan_test.f90 +++ b/lib/value_range_check/dl_esm_inf/value_range_check.f90 @@ -34,26 +34,27 @@ ! Author J. Henrichs, Bureau of Meteorology ! Modified I. Kavcic, Met Office -!> This module implements a PSyData-based verification that floating point -!! input and output parameters are not NAN and not infinite for the -!! dl_esm_inf library. It is based on the NANTestBaseType (from which it -!! inherits the handling of the basic Fortran data types and 2d-arrays, -!! as specified in the Makefile). It adds the support for the -!! dl_esm_inf-specific field type. +!> This module implements a PSyData-based verification that checks if +!! variable values are within a certain range. It is based on the +!! ValueRangeCheckBaseType (from which it inherits the handling of the +!! basic Fortran data types and 2d-arrays, as specified in the Makefile). +!! It adds the support for the dl_esm_inf-specific field type. -module nan_test_psy_data_mod + +module value_range_check_psy_data_mod use, intrinsic :: iso_fortran_env, only : int64, int32, & real32, real64, & stderr => Error_Unit - use nan_test_base_mod, only : NANTestBaseType, nan_test_PSyDataInit, & - nan_test_PSyDataShutdown, is_enabled, & - nan_test_PSyDataStart, nan_test_PSyDataStop + use value_range_check_base_mod, only : ValueRangeCheckBaseType, & + value_range_check_PSyDataInit, & + value_range_check_PSyDataShutdown, is_enabled, & + value_range_check_PSyDataStart, value_range_check_PSyDataStop implicit none - type, extends(NANTestBaseType), public:: nan_test_PSyDataType + type, extends(ValueRangeCheckBaseType), public:: value_range_check_psydatatype contains @@ -73,14 +74,14 @@ module nan_test_psy_data_mod !! are not NAN and not infinite. generic, public :: ProvideVariable => ProvideField - end type nan_test_PSyDataType + end type value_range_check_psydatatype contains ! ------------------------------------------------------------------------- !> This subroutine declares a field as defined in !! dl_esm_inf (r2d_field). It does nothing in the NAN checking library. - !! @param[in,out] this The instance of the nan_test_PSyDataType. + !! @param[in,out] this The instance of the value_range_check_psydatatype. !! @param[in] name The name of the variable (string). !! @param[in] value The value of the variable. subroutine DeclareField(this, name, value) @@ -89,31 +90,32 @@ subroutine DeclareField(this, name, value) implicit none - class(nan_test_PSyDataType), intent(inout), target :: this + class(value_range_check_psydatatype), intent(inout), target :: this character(*), intent(in) :: name type(r2d_field), intent(in) :: value + call this%PreDeclareVariable(name, value%data) end subroutine DeclareField ! ------------------------------------------------------------------------- !> This subroutine checks that a dl_esm_inf field does not contain !! a NAN or infinite floating point value. - !! @param[in,out] this The instance of the nan_test_PSyDataType. + !! @param[in,out] this The instance of the value_range_check_psydatatype. !! @param[in] name The name of the variable (string). !! @param[in] value The value of the variable. subroutine ProvideField(this, name, value) use field_mod, only : r2d_field - use nan_test_base_mod, only : NANTestBaseType + use value_range_check_base_mod, only : ValueRangeCheckBaseType implicit none - class(nan_test_PSyDataType), intent(inout), target :: this + class(value_range_check_psydatatype), intent(inout), target :: this character(*), intent(in) :: name type(r2d_field), intent(in) :: value - call this%ProvideArray2dDouble(name, value%data) + call this%ProvideVariable(name, value%data) end subroutine ProvideField -end module nan_test_psy_data_mod +end module value_range_check_psy_data_mod diff --git a/lib/value_range_check/lfric/.gitignore b/lib/value_range_check/lfric/.gitignore new file mode 100644 index 0000000000..eb33396044 --- /dev/null +++ b/lib/value_range_check/lfric/.gitignore @@ -0,0 +1 @@ +value_range_check.f90 diff --git a/lib/nan_test/lfric/Makefile b/lib/value_range_check/lfric/Makefile similarity index 85% rename from lib/nan_test/lfric/Makefile rename to lib/value_range_check/lfric/Makefile index a39a12a33c..1b20f55cb9 100644 --- a/lib/nan_test/lfric/Makefile +++ b/lib/value_range_check/lfric/Makefile @@ -51,7 +51,7 @@ F90FLAGS ?= # Overwrite for a different infrastructure version. LFRIC_INF_DIR ?= ./../../../src/psyclone/tests/test_files/dynamo0p3/infrastructure # Paths to the PSyclone wrapper libraries and required templates. They default -# to the relative paths to the top-level 'lib' and 'lib/nan_test' +# to the relative paths to the top-level 'lib' and 'lib/value_range_check' # directories. Overwrite for a custom location. PSYDATA_LIB_DIR ?= ./../.. LIB_TMPLT_DIR ?= ./.. @@ -62,7 +62,7 @@ INF_LIB_NAME = lfric INF_LIB = $(LFRIC_INF_DIR)/lib$(INF_LIB_NAME).a F90FLAGS += -I$(INF_INC)/field -I$(INF_INC)/function_space -I$(INF_INC)/mesh -I$(INF_INC)/utilities -I$(INF_INC)/scalar -I$(INF_INC)/configuration -PSYDATA_LIB_NAME = _nan_test +PSYDATA_LIB_NAME = _value_range_check PSYDATA_LIB = lib$(PSYDATA_LIB_NAME).a # The LFRic NaN checking library needs the PreDeclareVariable() and @@ -70,11 +70,11 @@ PSYDATA_LIB = lib$(PSYDATA_LIB_NAME).a # and double, and for 1-, 2-, 3-, and 4-dimensional arrays. These arguments # are used for both base classes: PSyDataBase and NANTestBase to ensure # consistent code creation. -PROCESS_ARGS = -prefix=nan_test_ -types=real,int,logical,double \ +PROCESS_ARGS = -prefix=value_range_check_ -types=real,int,logical,double \ -dims=1,2,3,4 PROCESS = $$($(PSYDATA_LIB_DIR)/get_python.sh) $(PSYDATA_LIB_DIR)/process.py -OBJS = nan_test.o psy_data_base.o nan_test_base.o +OBJS = value_range_check.o psy_data_base.o value_range_check_base.o default: $(PSYDATA_LIB) @@ -90,17 +90,17 @@ $(INF_LIB): # Add all dependencies. The LFRic NaN checking library extends the # NANTestBase type, which in turn extends the PSyDataBase type. # --------------------------------------------------------------- -nan_test.o: $(INF_LIB) nan_test.f90 nan_test_base.o +value_range_check.o: $(INF_LIB) value_range_check.f90 value_range_check_base.o -nan_test_base.o: psy_data_base.o +value_range_check_base.o: psy_data_base.o # Rules for file creation: # ------------------------ %.o: %.f90 $(F90) $(F90FLAGS) -c $< -nan_test.f90: nan_test.jinja Makefile - $(PROCESS) $(PROCESS_ARGS) $< > nan_test.f90 +value_range_check.f90: value_range_check.jinja Makefile + $(PROCESS) $(PROCESS_ARGS) $< > value_range_check.f90 # The LFRic NaN checking library is built on the PSyData base library # and the NAN-checking base library. So add the rules to process @@ -108,11 +108,11 @@ nan_test.f90: nan_test.jinja Makefile psy_data_base.f90: $(PSYDATA_LIB_DIR)/psy_data_base.jinja Makefile $(PROCESS) $(PROCESS_ARGS) -generic-declare $< > psy_data_base.f90 -nan_test_base.f90: $(LIB_TMPLT_DIR)/nan_test_base.jinja Makefile - $(PROCESS) $(PROCESS_ARGS) -generic-provide $< > nan_test_base.f90 +value_range_check_base.f90: $(LIB_TMPLT_DIR)/value_range_check_base.jinja Makefile + $(PROCESS) $(PROCESS_ARGS) -generic-provide $< > value_range_check_base.f90 clean: - rm -f *.o *.mod $(PSYDATA_LIB) psy_data_base.* nan_test_base.* + rm -f *.o *.mod $(PSYDATA_LIB) psy_data_base.* value_range_check_base.* allclean: clean $(MAKE) -C $(LFRIC_INF_DIR) allclean diff --git a/lib/nan_test/lfric/README.md b/lib/value_range_check/lfric/README.md similarity index 78% rename from lib/nan_test/lfric/README.md rename to lib/value_range_check/lfric/README.md index ae528355f1..520c97f03a 100644 --- a/lib/nan_test/lfric/README.md +++ b/lib/value_range_check/lfric/README.md @@ -1,9 +1,10 @@ -# ``NaN``-Test Verification Library for LFRic +# ``ValueRangeCheck`` Library for LFRic This library implements the [PSyData API]( -https://psyclone.readthedocs.io/en/latest/psy_data.html#nan-test) -to verify that input and output parameters of an LFRic kernel are not ``NaN`` -or infinite, using the LFRic infrastructure library. +https://psyclone.readthedocs.io/en/latest/psy_data.html#psydata-value-range-check) +to verify that input and output parameters of an LFRic kernel are within +a user-specified range, and not ``NaN`` or infinite, using the LFRic +infrastructure library. ## Dependencies @@ -20,14 +21,14 @@ the application. The following dependencies must be available: https://psyclone.readthedocs.io/en/stable/dynamo0p3.html) documentation for information on how to obtain access to the LFRic code. -- The NANTest (``nan_test_base.jinja``) and PSyData +- The ValueRangeCheck (``value_range_check_base.jinja``) and PSyData (``psy_data_base.jinja``) base classes, which are included in PSyclone - installation. These Jinja templates are processed to create the - ``NaN``-test verification code for ``integer``, 32- and 64-bit ``real`` - scalars, and 1, 2, 3, and 4-dimensional ``real`` and ``integer`` arrays. - The generated Fortran modules, ``nan_test_base.f90`` and - ``psy_data_base.f90``, are then used by the supplied ``nan_test.f90`` - module to create the wrapper library. + installation. These Jinja templates are processed to create + the ``ValueRangeCheck`` verification code for ``integer``, 32- and 64-bit ``real`` + scalars, and 1, 2, 3, and 4-dimensional ``real`` and ``integer`` arrays. The + generated Fortran modules, ``value_range_check_base.f90`` and ``psy_data_base.f90``, + are then used by the supplied ``value_range_check.f90`` module to create the wrapper + library. ## Compilation @@ -50,15 +51,15 @@ F90=ifort F90FLAGS="-g -check bounds" LFRIC_INF_DIR= make ``` It is the responsibility of the user to make sure that the module files -used when compiling the LFRic ``NaN``-test library are identical to the +used when compiling the LFRic ``ValueRangeCheck`` library are identical to the ones used when running an LFRic application. -The locations of the NANTest and PSyData base classes are specified +The locations of the ValueRangeCheck and PSyData base classes are specified using the environment variables ``$LIB_TMPLT_DIR`` and ``$PSYDATA_LIB_DIR``, respectively. They default to the relative paths to the -[``lib/nan_test``](./../) and top-level [``lib``](./../../) directories. +[``lib/value_range_check``](./../) and top-level [``lib``](./../../) directories. -The compilation process will create the wrapper library ``lib_nan_test.a``. +The compilation process will create the wrapper library ``lib_value_range_check.a``. The ``Makefile`` will compile the LFRic infrastructure library, ``liblfric.a``, if required, with the previously selected compiler flags. @@ -72,11 +73,11 @@ or compiler flags). ### Linking the wrapper library The application needs to provide the parameters to link in this -``NaN``-test library, ``_nan_test``, and the LFRic infrastructure library, +``ValueRangeCheck`` library, ``_value_range_check``, and the LFRic infrastructure library, ``lfric``. For instance: ```shell -$(F90) ... -L$(PSYDATA_LIB_DIR)/nan_test/lfric -l_nan_test \ +$(F90) ... -L$(PSYDATA_LIB_DIR)/nan_test/lfric -l_value_range_check \ -L$(LFRIC_INF_DIR) -llfric $(LFRIC_SPECIFIC_LINKING_PARAMETERS) ``` @@ -87,7 +88,7 @@ $(F90) ... -L$(PSYDATA_LIB_DIR)/nan_test/lfric -l_nan_test \ BSD 3-Clause License -Copyright (c) 2020-2024, Science and Technology Facilities Council. +Copyright (c) 2024, Science and Technology Facilities Council. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -119,5 +120,4 @@ POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------- Authors: J. Henrichs, Bureau of Meteorology, - I. Kavcic, Met Office --> diff --git a/lib/nan_test/lfric/nan_test.jinja b/lib/value_range_check/lfric/value_range_check.jinja similarity index 70% rename from lib/nan_test/lfric/nan_test.jinja rename to lib/value_range_check/lfric/value_range_check.jinja index 22dfb4c4fa..034810a36a 100644 --- a/lib/nan_test/lfric/nan_test.jinja +++ b/lib/value_range_check/lfric/value_range_check.jinja @@ -8,7 +8,7 @@ ! ----------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2020-2022, Science and Technology Facilities Council. +! Copyright (c) 2020-2024, Science and Technology Facilities Council. ! All rights reserved. ! ! Redistribution and use in source and binary forms, with or without @@ -41,12 +41,12 @@ ! Author J. Henrichs, Bureau of Meteorology ! Modified I. Kavcic, Met Office -!> This module implements a NAN verification for the LFRic API +!> This module implements the value range check for the LFRic API !! {% set ALL_PREC = ["32", "64"] -%} -module nan_test_psy_data_mod +module value_range_check_psy_data_mod use, intrinsic :: iso_fortran_env, only : int64, int32, & real32, real64, & @@ -55,12 +55,12 @@ module nan_test_psy_data_mod use field_r{{prec}}_mod, only : field_r{{prec}}_type, & field_r{{prec}}_proxy_type {% endfor %} - use integer_field_mod, only : integer_field_type - use nan_test_base_mod, only : NANTestBaseType, is_enabled + use integer_field_mod, only : integer_field_type, integer_field_proxy_type + use value_range_check_base_mod, only : ValueRangeCheckBaseType, is_enabled implicit none - type, extends(NANTestBaseType), public :: nan_test_PSyDataType + type, extends(ValueRangeCheckBaseType), public :: value_range_check_PSyDataType contains @@ -102,39 +102,46 @@ module nan_test_psy_data_mod ProvideIntField, & ProvideIntFieldVector - end type nan_test_PSyDataType + end type value_range_check_PSyDataType contains {% for prec in ALL_PREC %} ! ------------------------------------------------------------------------- - !> @brief This subroutine declares LFRic real-valued fields. No functionality is - !! needed for NAN checking, so it is just an empty function. - !! @param[in,out] this The instance of the nan_test_PSyDataType. + !> @brief This subroutine declares LFRic real-valued fields. No + !! functionality is needed for ValueRangeCheck, so it just calls the + !! function in the base class. + !! @param[in,out] this The instance of the value_range_check_PSyDataType. !! @param[in] name The name of the variable (string). !! @param[in] value The value of the variable. subroutine DeclareField_r{{prec}}(this, name, value) implicit none - class(nan_test_PSyDataType), intent(inout), target :: this + class(value_range_check_PSyDataType), intent(inout), target :: this character(*), intent(in) :: name type(field_r{{prec}}_type), intent(in) :: value + type(field_r{{prec}}_proxy_type) :: value_proxy + + value_proxy = value%get_proxy() + call this%PreDeclareVariable(name, value_proxy%data) + end subroutine DeclareField_r{{prec}} ! ------------------------------------------------------------------------- - !> @brief This subroutine checks whether a real-valued LFRic field has NAN or - !! infinite floating-point values. - !! @param[in,out] this The instance of the nan_test_PSyDataType. + !> @brief This subroutine calls the corresponding function in the base class + !! to do the value range checking. It accesses the actual data in the field + !! using the proxy object. + !! @param[in,out] this The instance of the value_range_check_PSyDataType. !! @param[in] name The name of the variable (string). !! @param[in] value The value of the variable. subroutine ProvideField_r{{prec}}(this, name, value) implicit none - class(nan_test_PSyDataType), intent(inout), target :: this + class(value_range_check_PSyDataType), intent(inout), target :: this character(*), intent(in) :: name type(field_r{{prec}}_type), intent(in) :: value @@ -151,32 +158,43 @@ contains end subroutine ProvideField_r{{prec}} ! ------------------------------------------------------------------------- - !> @brief This subroutine declares LFRic real-valued field vectors. No - !! functionality is needed here, so it is just an empty function. - !! @param[in,out] this The instance of the nan_test_PSyDataType. + !> @brief This subroutine declares LFRic real-valued field vectors. It + !! calls the corresponding function in the base class for each member + !! of the field vector. + !! @param[in,out] this The instance of the value_range_check_PSyDataType. !! @param[in] name The name of the variable (string). !! @param[in] value The value of the variable. subroutine DeclareFieldVector_r{{prec}}(this, name, value) implicit none - class(nan_test_PSyDataType), intent(inout), target :: this + class(value_range_check_PSyDataType), intent(inout), target :: this character(*), intent(in) :: name type(field_r{{prec}}_type), dimension(:), intent(in) :: value + integer :: i + character(8) :: index_string ! Enough for a 6 digit number plus '()' + + ! Provide each member of the vector as a normal field. This way + ! the value range checking will be done for each member individually. + do i = 1, size(value, 1) + write(index_string, '("(",i0,")")') i + call this%PreDeclareVariable(name//trim(index_string), value(i)) + enddo + end subroutine DeclareFieldVector_r{{prec}} ! ------------------------------------------------------------------------- - !> @brief This subroutine checks whether a real-valued LFRic vector field has NAN - !! or infinite floating-point values. - !! @param[in,out] this The instance of the nan_test_PSyDataType. + !> @brief This subroutine calls the value range check function for each + !! member in the real-valued LFRic vector field. + !! @param[in,out] this The instance of the value_range_check_PSyDataType. !! @param[in] name The name of the variable (string). !! @param[in] value The vector of fields. subroutine ProvideFieldVector_r{{prec}}(this, name, value) implicit none - class(nan_test_PSyDataType), intent(inout), target :: this + class(value_range_check_PSyDataType), intent(inout), target :: this character(*), intent(in) :: name type(field_r{{prec}}_type), dimension(:), intent(in) :: value @@ -186,7 +204,7 @@ contains if (.not. is_enabled) return ! Provide each member of the vector as a normal field. This way - ! the NAN/infinite testing will be done for each member individually. + ! the value range checking will be done for each member individually. do i = 1, size(value, 1) write(index_string, '("(",i0,")")') i call this%ProvideVariable(name//trim(index_string), value(i)) @@ -197,25 +215,32 @@ contains {% endfor %} ! ------------------------------------------------------------------------- - !> @brief This subroutine declares LFRic integer-valued fields. No functionality - !! is needed for NAN checking, so it is just an empty function. - !! @param[in,out] this The instance of the nan_test_PSyDataType. + !> @brief This subroutine declares LFRic integer-valued fields. No + !! functionality is needed for ValueRangeCheck, so it is just calls the + !! corresponding function in the base class. + !! @param[in,out] this The instance of the value_range_check_PSyDataType. !! @param[in] name The name of the variable (string). !! @param[in] value The value of the variable. subroutine DeclareIntField(this, name, value) implicit none - class(nan_test_PSyDataType), intent(inout), target :: this + class(value_range_check_PSyDataType), intent(inout), target :: this character(*), intent(in) :: name type(integer_field_type), intent(in) :: value + type(integer_field_proxy_type) :: value_proxy + + value_proxy = value%get_proxy() + call this%PreDeclareVariable(name, value_proxy%data) + end subroutine DeclareIntField ! ------------------------------------------------------------------------- - !> @brief This subroutine checks whether an integer-valued LFRic field has NAN or - !! infinite values. - !! @param[in,out] this The instance of the nan_test_PSyDataType. + !> @brief This subroutine calls the corresponding function in the base + !! class to do the integer field value range checking. It accesses the + !! actual data in the field using the proxy object. + !! @param[in,out] this The instance of the value_range_check_PSyDataType. !! @param[in] name The name of the variable (string). !! @param[in] value The value of the variable. subroutine ProvideIntField(this, name, value) @@ -225,7 +250,7 @@ contains implicit none - class(nan_test_PSyDataType), intent(inout), target :: this + class(value_range_check_PSyDataType), intent(inout), target :: this character(*), intent(in) :: name type(integer_field_type), intent(in) :: value @@ -243,8 +268,9 @@ contains ! ------------------------------------------------------------------------- !> @brief This subroutine declares LFRic integer-valued field vectors. No - !! functionality is needed here, so it is just an empty function. - !! @param[in,out] this The instance of the nan_test_PSyDataType. + !! functionality is needed here, so it is just calls the corresponding + !! function for each member of the vector of fields. + !! @param[in,out] this The instance of the value_range_check_PSyDataType. !! @param[in] name The name of the variable (string). !! @param[in] value The value of the variable. subroutine DeclareIntFieldVector(this, name, value) @@ -253,16 +279,26 @@ contains implicit none - class(nan_test_PSyDataType), intent(inout), target :: this + class(value_range_check_PSyDataType), intent(inout), target :: this character(*), intent(in) :: name type(integer_field_type), dimension(:), intent(in) :: value + integer :: i + character(8) :: index_string ! Enough for a 6 digit number plus '()' + + ! Provide each member of the vector as a normal field. This way + ! the value range checking will be done for each member individually. + do i = 1, size(value, 1) + write(index_string, '("(",i0,")")') i + call this%PreDeclareVariable(name//trim(index_string), value(i)) + enddo + end subroutine DeclareIntFieldVector ! ------------------------------------------------------------------------- - !> @brief This subroutine checks whether an integer-valued LFRic vector field has - !! NAN or infinite values. - !! @param[in,out] this The instance of the nan_test_PSyDataType. + !> @brief This subroutine calls the value range checking test for each + !! member of the vector of fields. + !! @param[in,out] this The instance of the value_range_check_PSyDataType. !! @param[in] name The name of the variable (string). !! @param[in] value The vector of fields. subroutine ProvideIntFieldVector(this, name, value) @@ -271,7 +307,7 @@ contains implicit none - class(nan_test_PSyDataType), intent(inout), target :: this + class(value_range_check_PSyDataType), intent(inout), target :: this character(*), intent(in) :: name type(integer_field_type), dimension(:), intent(in) :: value @@ -281,7 +317,7 @@ contains if (.not. is_enabled) return ! Provide each member of the vector as a normal field. This way - ! the NAN/infinite testing will be done for each member individually. + ! the value range checking will be done for each member individually. do i = 1, size(value, 1) write(index_string, '("(",i0,")")') i call this%ProvideVariable(name//trim(index_string), value(i)) @@ -291,4 +327,4 @@ contains ! ------------------------------------------------------------------------- -end module nan_test_psy_data_mod +end module value_range_check_psy_data_mod diff --git a/lib/value_range_check/value_range_check_base.jinja b/lib/value_range_check/value_range_check_base.jinja new file mode 100644 index 0000000000..e55a491b8c --- /dev/null +++ b/lib/value_range_check/value_range_check_base.jinja @@ -0,0 +1,529 @@ +{# Added this as Jinja code so that it is understood that the + comment does not apply to THIS file. #} +{{ "! ================================================== !" }} +{{ "! THIS FILE IS CREATED FROM THE JINJA TEMPLATE FILE. !" }} +{{ "! DO NOT MODIFY DIRECTLY! !" }} +{{ "! ================================================== !" }} + +{# This jinja template file creates a base class for a value-range-check + verification library. It produces the required ProvideVariable() + functions for the specified Fortran basic types. Any library + using this base class can provide the required Fortran basic + types (see ALL_TYPES below) and the list of array dimensions + (see ALL_DIMS) that need to be supported when processing this + template. + + This ValueRangeCheck base class depends on the PSyData base + class, which will provide the other Fortran-type-specific + functions for PreDeclarVariable(). Any function can obviously + be overwritten by a derived class. +#} + +{% if ALL_DIMS is not defined %} + {# Support 1 to 4 dimensional arrays if not specified #} + {% set ALL_DIMS = [1, 2, 3, 4] %} +{% endif %} + +{# The types that are supported. The first entry of each tuple + is the name used when naming subroutines and in user messages. + The second entry is the Fortran declaration. The third entry + is the number of bits. There is slightly different code + required for 32 and 64 bit values (due to the fact that the + Fortran transfer(value, mould) function leaves undefined bits + when mould is larger than value.) #} + +{% if ALL_TYPES is not defined %} + {% set ALL_TYPES = [ ("Double", "real(kind=real64)", 64), + ("Real", "real(kind=real32)", 32), + ("Int", "integer(kind=int32)", 32) ] %} +{% endif %} + +! ----------------------------------------------------------------------------- +! BSD 3-Clause License +! +! Copyright (c) 2020-2024, Science and Technology Facilities Council. +! All rights reserved. +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions are met: +! +! * Redistributions of source code must retain the above copyright notice, this +! list of conditions and the following disclaimer. +! +! * Redistributions in binary form must reproduce the above copyright notice, +! this list of conditions and the following disclaimer in the documentation +! and/or other materials provided with the distribution. +! +! * Neither the name of the copyright holder nor the names of its +! contributors may be used to endorse or promote products derived from +! this software without specific prior written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +! FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +! COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +! INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +! BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +! ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +! POSSIBILITY OF SUCH DAMAGE. +! ----------------------------------------------------------------------------- +! Author J. Henrichs, Bureau of Meteorology +! Modified I. Kavcic, Met Office + +!> This module implements a PSyData-based verification that checks if +!! variable values are within a certain range. This range is specified +!! by environment variables. Three different formats can be used: +!! 1. PSYVERIFY__module__region__variable +!! Checks the specified variable in the given module and region (i.e. +!! kernel). +!! 2. PSYVERIFY__module__variable +!! Checks the specified variable anywhere in the given module. +!! 3. PSYVERIFY__variable +!! Checks the specified variable in any instrumented region. +!! Additionally, the library will also check that any floating point +!! input and output parameters are not NAN and not infinite. + +module value_range_check_base_mod + + use, intrinsic :: iso_fortran_env, only : int64, int32, & + real32, real64, & + stderr => Error_Unit + use psy_data_base_mod, only : PSyDataBaseType, & + value_range_check_PSyDataShutdown, & + value_range_check_PSyDataInit, is_enabled, & + value_range_check_PSyDataStart, value_range_check_PSyDataStop + + implicit none + + integer, parameter :: has_none = 0 + integer, parameter :: has_min = 1 + integer, parameter :: has_max = 2 + integer, parameter :: has_min_max = 3 + + type, extends(PSyDataBaseType), public :: ValueRangeCheckBaseType + ! Indicates if a variable has test values, and if so, which kind of + ! test (has_min/max/min_max) + integer, dimension(:), allocatable :: has_checks + ! This array stores the minimum and maximum value for each variable + real(kind=real64), dimension(:, :), allocatable :: value_ranges + + ! Is set if the environment variables have been read + logical :: env_var_have_been_read = .false. + + contains + + procedure get_min_max + + ! The various procedures used + procedure :: PreEndDeclaration + procedure :: PostEnd + + {# Collect and declare the various procedures for the same generic interface -#} + {# ------------------------------------------------------------------------- -#} + {% set all_declares=[] -%} + {% set all_provides=[] -%} + {% for name, type, bits in ALL_TYPES %} + procedure :: ProvideScalar{{name}} + {{- all_provides.append("ProvideScalar"~name) or "" }} + {% for dim in ALL_DIMS %} + procedure :: ProvideArray{{dim}}d{{name}} + {{- all_provides.append("ProvideArray"~dim~"d"~name) or "" }} + {% endfor %} + {% endfor %} + + {% set indent=" " %} + {% if GENERIC_PROVIDE %} + !> The generic interface for providing the value of variables: + generic, public :: ProvideVariable => & + {{all_provides|join(", &\n"+indent) }} + {% endif %} + + end type ValueRangeCheckBaseType + +contains + + subroutine get_min_max(this, module, region, variable_name, & + has_values, value_range) + !> @brief This subroutine checks if any environment variables are set + !! that specify minimum and/or maximum values to be checked for the + !! specified variable. It returns result information in + !! has_values (one of has_none, has_min, has_max, has_min_max). + !! If a minimum is specified, its value will be stored in + !! value_range(1). If a maximum is specified, its value will be + !! stores in value_range(2). + !! @param[in] this The instance of the ValueRangeCheckBaseType. + !! @param[in] module The name of the module of the instrumented + !! region. + !! @param[in] region The name of the instrumented region. + !! @param[in] variable_name The name of the variable to test. + !! @param[out] has_values Indicates if the variable should be range tested. + !! One off has_none, has_min, has_max, has_min_max + !! @param[out] value_range Index 1 stores the minimum value if specified, + !! index 2 the maximum (if specified) + + implicit none + class(ValueRangeCheckBaseType), intent(in), target :: this + character(len=*), intent(in) :: module, region, variable_name + integer, intent(out) :: has_values + real(kind=real64), dimension(2), intent(out) :: value_range + + character(len=:), allocatable :: env_name, env_value, sub_str + ! We need a copy of the names to replace '-' with '_' + character(len=:), allocatable :: local_module, local_region + integer :: status, value_len, index_colon, i + + local_module = module + do i=1, len(local_module) + if (local_module(i:i) == "-") then + local_module(i:i) = "_" + endif + enddo + local_region = region + do i=1, len(local_region) + if (local_region(i:i) == "-") then + local_region(i:i) = "_" + endif + enddo + env_name = "PSYVERIFY__" // trim(local_module) // "__" // trim(local_region) & + // "__" // trim(variable_name) + + + call get_environment_variable(env_name, length=value_len, status=status) + ! Status >=1 indicates the variable does not exist, or some other OS + ! specific error - in any case we have to ignore it. + if (status >= 1) then + ! No specification for this variable with module and region. + ! Try without region: + env_name = "PSYVERIFY__" // trim(local_module) // "__" & + // trim(variable_name) + + call get_environment_variable(env_name, length=value_len, & + status=status) + if (status >= 1) then + ! No specification with only variable and module name. + ! Try without module: + env_name = "PSYVERIFY__" // trim(variable_name) + call get_environment_variable(env_name, length=value_len, & + status=status) + if (status >= 1) then + has_values = has_none + return + endif + endif ! No specification without region + endif ! No specification with module & region + allocate(character(len=value_len)::env_value) + call get_environment_variable(env_name, env_value, status=status) + ! Analyse the specified range + index_colon = index(env_value, ":") + if (index_colon == 0) then + ! No range at all, must be a constant value?? + write(stderr,*) "PSyData: env variable '", env_name,"' = ", & + trim(env_value), " does not contain a ':' ", & + "- ignored" + has_values = has_none + else if (index_colon == 1) then + ! ":5" i.e. 5 or smaller + has_values = has_max + read(env_value(2:), *) value_range(2) + if (this%verbosity>0) then + write(stderr,*) "PSyData: checking '", trim(module), & + "' region '", trim(region),"' :", & + variable_name, " <= ", value_range(2) + endif + else if (index_colon == len(env_value)) then + ! "5:" i.e. 5 or larger: + has_values = has_min + read(env_value(:index_colon-1), *) value_range(1) + if (this%verbosity>0) then + write(stderr,*) "PSyData: checking '", trim(module), & + "' region '", trim(region),"' :", & + value_range(1), " <= ", variable_name + endif + else + ! "1:3" i.e. between 1 and 3 + has_values = has_min_max + read(env_value(1:index_colon-1), *) value_range(1) + read(env_value(index_colon+1:), *) value_range(2) + if (this%verbosity>0) then + write(stderr,*) "PSyData: checking '", trim(module), & + "' region '", trim(region),"' :", & + value_range(1), " <= ", variable_name, & + " <= ", value_range(2) + endif + endif + end subroutine get_min_max + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine is called once all variables are declared. It makes + !! sure that the next variable index is starting at 1 again. + !! @param[in,out] this The instance of the ValueRangeCheckBaseType. + subroutine PreEndDeclaration(this) + implicit none + class(ValueRangeCheckBaseType), intent(inout), target :: this + integer :: err + + if (.not. is_enabled) return + + ! During the declaration the number of variables was counted + ! (in LFRic this number can be different from the number of input- + ! and output-variables) in next_var_index, so allocate the array + ! now (if it has not been allocated already in a previous call): + if (.not. allocated(this%has_checks)) then + allocate(this%has_checks(this%next_var_index-1), stat=err) + if(err/=0) then + write(stderr, *) "PSyData: Could not allocate ", & + this%next_var_index-1, & + " integers, aborting." + call this%Abort("Out of memory") + endif + allocate(this%value_ranges(this%next_var_index-1,2), stat=err) + if(err/=0) then + write(stderr, *) "PSyData: Could not allocate ", & + this%next_var_index-1, & + "*2 real64 array, aborting." + call this%Abort("Out of memory") + endif + endif + + call this%PSyDataBaseType%PreEndDeclaration() + + end subroutine PreEndDeclaration + + ! ------------------------------------------------------------------------- + !> @brief This subroutine is called after the instrumented region has been + !! executed and all values of variables after the instrumented + !! region have been provided. This sets the flag that all environment + !! variables have been read to true. + !! @param[in,out] this The instance of the PSyDataBaseType. + subroutine PostEnd(this) + + implicit none + + class(ValueRangeCheckBaseType), intent(inout), target :: this + + this%env_var_have_been_read = .true. + call this%PSyDataBaseType%PreEnd() + + end subroutine PostEnd + + ! ========================================================================= + ! Jinja created code. + ! ========================================================================= + +{% for name, type, bits in ALL_TYPES %} + + ! ========================================================================= + ! Implementation for all {{type}} types + ! ========================================================================= + ! ------------------------------------------------------------------------- + !> @brief This subroutine checks if a numerical variable is within the + !! specified range (based on environment variables set by the user). + !! Additionally, in the case of a floating point value, it will also check + !! that this value is not NAN or infinite using the IEEE_IS_FINITE function. + !! @param[in,out] this The instance of the ValueRangeCheckBaseType. + !! @param[in] name The name of the variable (string). + !! @param[in] value The value of the variable. + subroutine ProvideScalar{{name}}(this, name, value) + + use, intrinsic :: ieee_arithmetic + + implicit none + + class(ValueRangeCheckBaseType), intent(inout), target :: this + character(*), intent(in) :: name + {{type}}, intent(in) :: value + + if (.not. is_enabled) return + + if (.not. this%env_var_have_been_read) then + call this%get_min_max(this%module_name, this%region_name, & + name, this%has_checks(this%next_var_index), & + this%value_ranges(this%next_var_index,:)) + endif + + {% if name !="Logical" %} + + if (this%has_checks(this%next_var_index) == has_min) then + {# The spaces take care of proper indentation #} + {{indent}}if (value< this%value_ranges(this%next_var_index,1)) then + {{indent}} write(stderr, '(11G0)') "PSyData: Variable '", & + {{indent}} name,"' has the value ", & + {{indent}} value, " in module '", trim(this%module_name), & + {{indent}} "', region '", trim(this%region_name), & + {{indent}} "' which is less than '", & + {{indent}} this%value_ranges(this%next_var_index,1),"'." + {{indent}}endif + else if (this%has_checks(this%next_var_index) == has_max) then + {# The spaces take care of proper indentation #} + {{indent}}if (value > this%value_ranges(this%next_var_index,2)) then + {{indent}} write(stderr, '(11G0)') "PSyData: Variable '", & + {{indent}} name,"' has the value ", & + {{indent}} value, " in module '", trim(this%module_name), & + {{indent}} "', region '", trim(this%region_name), & + {{indent}} "', which is greater than '", & + {{indent}} this%value_ranges(this%next_var_index,2),"'." + {{indent}}endif + else if (this%has_checks(this%next_var_index) == has_min_max) then + {# The spaces take care of proper indentation #} + {{indent}}if (value < this%value_ranges(this%next_var_index,1) .or. & + {{indent}} value > this%value_ranges(this%next_var_index,2)) then + {{indent}} write(stderr, '(13G0)') "PSyData: Variable '", & + {{indent}} name,"' has the value ", & + {{indent}} value , " in module '", trim(this%module_name), & + {{indent}} "', region '", trim(this%region_name), & + {{indent}} "', which is not between '", & + {{indent}} this%value_ranges(this%next_var_index,1),"' and '", & + {{indent}} this%value_ranges(this%next_var_index,2),"'." + {{indent}}endif + endif + {% endif %} + + {% if name not in ["Int", "Logical"] %} + if (IEEE_SUPPORT_DATATYPE(value)) then + if (.not. IEEE_IS_FINITE(value)) then + write(stderr, '(8G0)') "PSyData: Variable '", name,"' has invalid value ", & + value, " in module '", trim(this%module_name), & + "', region '", trim(this%region_name),"'." + endif + endif + {% else %} + ! Variables of type {{type}} do not have NANs, and cannot usefully be + ! checked for range. So nothing to do here. + {% endif %} + + call this%PSyDataBaseType%ProvideScalar{{name}}(name, value) + + end subroutine ProvideScalar{{name}} + + {# Now provide the array implementations #} + {# ------------------------------------- #} + {% for dim in ALL_DIMS %} + {# Create the ':,:,:,:' string + We repeat the list [":"] DIM-times, which is then joined #} + {% set DIMENSION=([":"]*dim)|join(",") %} + + {# Create list of variables: "i1, i2, i3, i4" #} + {% set vars = "i"~range(1,dim+1)|join(", i") %} + {% set indent = " "*3*dim %} + + ! ------------------------------------------------------------------------- + !> @brief This subroutine checks if each element of a numerical array is + !! within the specified range (based on environment variables set by the + !! user). Additionally, in the case of a floating point value, it will also + !! check that this value is not NAN or infinite using the IEEE_IS_FINITE + !! function. + !! @param[in,out] this The instance of the ValueRangeCheckBaseType. + !! @param[in] name The name of the variable (string). + !! @param[in] value The value of the variable. + subroutine ProvideArray{{dim}}d{{name}}(this, name, value) + + use, intrinsic :: ieee_arithmetic + + implicit none + + class(ValueRangeCheckBaseType), intent(inout), target :: this + character(*), intent(in) :: name + {{type}}, dimension({{DIMENSION}}), intent(in) :: value + + {# IEEE_SUPPORT_DATATYPE does not even compile for int data types #} + {% if name not in ["Int", "Logical"] %} + integer :: {{vars}} + + if (.not. is_enabled) return + if (.not. this%env_var_have_been_read) then + call this%get_min_max(this%module_name, this%region_name, & + name, this%has_checks(this%next_var_index), & + this%value_ranges(this%next_var_index,:)) + endif + + if (this%has_checks(this%next_var_index) == has_min) then + {# The spaces take care of proper indentation #} + {% for j in range(dim, 0, -1) %} + {{ " "*3*(dim-j)}}do i{{j}}=1, size(value, {{j}}) + {% endfor %} + {{indent}}if (value({{vars}}) < this%value_ranges(this%next_var_index,1)) then + {{indent}} write(stderr, '(5G0,{{dim}}(G0,X),7G0)') "PSyData: Variable '", & + {{indent}} name,"' has the value ", & + {{indent}} value({{vars}}), " at index/indices ", {{vars}}, & + {{indent}} "in module '", trim(this%module_name), & + {{indent}} "', region '", trim(this%region_name), & + {{indent}} "' which is less than '", & + {{indent}} this%value_ranges(this%next_var_index,1),"'." + {{indent}}endif + {% for j in range(dim, 0, -1) %} + {{" "*3*(j-1)}}enddo + {% endfor %} + else if (this%has_checks(this%next_var_index) == has_max) then + {# The spaces take care of proper indentation #} + {% for j in range(dim, 0, -1) %} + {{ " "*3*(dim-j)}}do i{{j}}=1, size(value, {{j}}) + {% endfor %} + {{indent}}if (value({{vars}}) > this%value_ranges(this%next_var_index,2)) then + {{indent}} write(stderr, '(5G0,{{dim}}(G0,X),7G0)') "PSyData: Variable '", & + {{indent}} name,"' has the value ", & + {{indent}} value({{vars}}), " at index/indices ", {{vars}}, & + {{indent}} "in module '", trim(this%module_name), & + {{indent}} "', region '", trim(this%region_name), & + {{indent}} "', which is greater than '", & + {{indent}} this%value_ranges(this%next_var_index,2),"'." + {{indent}}endif + {% for j in range(dim, 0, -1) %} + {{" "*3*(j-1)}}enddo + {% endfor %} + else if (this%has_checks(this%next_var_index) == has_min_max) then + {# The spaces take care of proper indentation #} + {% for j in range(dim, 0, -1) %} + {{ " "*3*(dim-j)}}do i{{j}}=1, size(value, {{j}}) + {% endfor %} + {{indent}}if (value({{vars}}) < this%value_ranges(this%next_var_index,1) .or. & + {{indent}} value({{vars}}) > this%value_ranges(this%next_var_index,2)) then + {{indent}} write(stderr, '(5G0,{{dim}}(G0,X),9G0)') "PSyData: Variable '", & + {{indent}} name,"' has the value ", & + {{indent}} value({{vars}}), " at index/indices ", {{vars}}, & + {{indent}} "in module '", trim(this%module_name), & + {{indent}} "', region '", trim(this%region_name), & + {{indent}} "', which is not between '", & + {{indent}} this%value_ranges(this%next_var_index,1),"' and '", & + {{indent}} this%value_ranges(this%next_var_index,2),"'." + {{indent}}endif + {% for j in range(dim, 0, -1) %} + {{" "*3*(j-1)}}enddo + {% endfor %} + endif + + if (IEEE_SUPPORT_DATATYPE(value)) then + {# The spaces take care of proper indentation #} + {% for j in range(dim, 0, -1) %} + {{ " "*3*(dim-j)}}do i{{j}}=1, size(value, {{j}}) + {% endfor %} + {{indent}}if (.not. IEEE_IS_FINITE(value({{vars}}))) then + {{indent}} write(stderr, '(5G0,{{dim}}(G0,X),5G0)') "PSyData: Variable '", & + {{indent}} name,"' has the invalid value '", & + {{indent}} value({{vars}}), "' at index/indices ", {{vars}}, & + {{indent}} "in module '", trim(this%module_name), & + {{indent}} "', region '", trim(this%region_name),"'." + {{indent}}endif + {% for j in range(dim, 0, -1) %} + {{" "*3*(j-1)}}enddo + {% endfor %} + endif + {% else %} + ! Variables of type {{type}} do not have NANs + ! So nothing to do here. + {% endif %} + + call this%PSyDataBaseType%ProvideArray{{dim}}d{{name}}(name, value) + + end subroutine ProvideArray{{dim}}d{{name}} + + {% endfor -%} {# for dim #} +{%- endfor -%} {# for #} + + ! ------------------------------------------------------------------------- + +end module value_range_check_base_mod diff --git a/psyclone.pdf b/psyclone.pdf index b20d5a0685..89080a74e6 100644 Binary files a/psyclone.pdf and b/psyclone.pdf differ diff --git a/setup.py b/setup.py index f55875aead..f903406d19 100644 --- a/setup.py +++ b/setup.py @@ -162,12 +162,11 @@ def get_files(directory, install_path, valid_suffixes): packages=PACKAGES, package_dir={"": "src"}, install_requires=['pyparsing', 'fparser>=0.1.4', 'configparser', - 'jsonschema', 'sympy'], + 'jsonschema', 'sympy', "Jinja2"], extras_require={ 'dag': ["graphviz"], 'doc': ["sphinx", "sphinxcontrib.bibtex", "sphinx_rtd_theme", "autoapi"], - 'psydata': ["Jinja2"], 'test': ["flake8", "pylint", "pytest-cov", "pytest-xdist"], }, include_package_data=True, diff --git a/src/psyclone/core/component_indices.py b/src/psyclone/core/component_indices.py index 7f6a54374b..4772bebbc2 100644 --- a/src/psyclone/core/component_indices.py +++ b/src/psyclone/core/component_indices.py @@ -90,7 +90,7 @@ def __init__(self, indices=None): # ------------------------------------------------------------------------ def __str__(self): - '''Returns a string representating the indices.''' + '''Returns a string representing the indices.''' return str(self._component_indices) # ------------------------------------------------------------------------ diff --git a/src/psyclone/psyad/domain/lfric/lfric_adjoint_harness.py b/src/psyclone/psyad/domain/lfric/lfric_adjoint_harness.py index c4a37be882..05dac9fab6 100644 --- a/src/psyclone/psyad/domain/lfric/lfric_adjoint_harness.py +++ b/src/psyclone/psyad/domain/lfric/lfric_adjoint_harness.py @@ -34,6 +34,7 @@ # Authors R. W. Ford and A. R. Porter, STFC Daresbury Lab # Modified by J. Henrichs, Bureau of Meteorology # Modified by L. Turner, Met Office +# Modified by T. Vockerodt, Met Office ''' Provides LFRic-specific PSyclone adjoint test-harness functionality. ''' @@ -522,11 +523,13 @@ def _lfric_log_write(sym_table, kernel, var1, var2): def generate_lfric_adjoint_harness(tl_psyir, coord_arg_idx=None, - panel_id_arg_idx=None): + panel_id_arg_idx=None, + test_name="adjoint_test"): ''' Constructs and returns the PSyIR for a Container and Routine that implements a test harness for the adjoint of the supplied tangent-linear - kernel. + kernel. The base name to use for the Container and Routine is given by + the test_name argument. :param tl_psyir: the PSyIR of an LFRic module defining a \ tangent-linear kernel. @@ -535,6 +538,8 @@ def generate_lfric_adjoint_harness(tl_psyir, coord_arg_idx=None, field in the list of arguments in the kernel metadata (if present). :param Optional[int] panel_id_arg_idx: 1-indexed position of the panel-id \ field in the list of arguments in the kernel metadata (if present). + :param Optional[str] test_name: Name of the adjoint test algorithm \ + (if present). :returns: PSyIR of an Algorithm that tests the adjoint of the supplied \ LFRic TL kernel. @@ -555,7 +560,8 @@ def generate_lfric_adjoint_harness(tl_psyir, coord_arg_idx=None, f"does not have a Container node:\n{tl_psyir.view(colour=False)}") lfalg = LFRicAlg() - container = lfalg.create_alg_routine("adjoint_test") + # Variable test_name is validated inside create_alg_routine. + container = lfalg.create_alg_routine(test_name) routine = container.walk(Routine)[0] table = routine.symbol_table diff --git a/src/psyclone/psyad/main.py b/src/psyclone/psyad/main.py index 8dd293f99f..5507718ffb 100644 --- a/src/psyclone/psyad/main.py +++ b/src/psyclone/psyad/main.py @@ -33,6 +33,7 @@ # ----------------------------------------------------------------------------- # Authors: R. W. Ford and A. R. Porter, STFC Daresbury Lab # Modified by J. Henrichs, Bureau of Meteorology +# T. Vockerodt, Met Office '''Top-level driver functions for PSyAD : the PSyclone Adjoint support. Transforms an LFRic tangent linear kernel to its adjoint. @@ -40,6 +41,8 @@ ''' import argparse import logging +import os +import re import sys from psyclone.configuration import Config, LFRIC_API_NAMES @@ -97,7 +100,7 @@ def msg(): help='the position of the panel-ID field in the ' 'meta_args list of arguments in the kernel metadata ' '(LFRic only)') - parser.add_argument('-otest', + parser.add_argument('-otest', default=None, help='filename for the unit test (implies -t)', dest='test_filename') parser.add_argument('-oad', help='filename for the transformed code') @@ -128,6 +131,22 @@ def msg(): "the 'lfric' API.") sys.exit(1) + # Processing test filename + test_name = "adjoint_test" + if generate_test: + if args.api in LFRIC_API_NAMES: + filename_standard = "adjt_.+_alg_mod.[Xx]90|atlt_.+_alg_mod.[Xx]90" + regex_search = re.search(filename_standard, args.test_filename) + if regex_search is None: + logger.error("Filename '%s' with 'lfric' API " + "must be of the form " + "/adjt__alg_mod.[Xx]90 or " + "/atlt__alg_mod.[Xx]90.", + args.test_filename) + sys.exit(1) + # At this stage filename should be valid, so we take the base name + test_name = os.path.basename(args.test_filename).split("_mod.")[0] + # TL Fortran code filename = args.filename logger.info("Reading kernel file %s", filename) @@ -145,7 +164,8 @@ def msg(): tl_fortran_str, args.active, api=args.api, coord_arg_index=args.coord_arg, panel_id_arg_index=args.panel_id_arg, - create_test=generate_test) + create_test=generate_test, + test_name=test_name) except TangentLinearError as info: print(str(info.value)) sys.exit(1) diff --git a/src/psyclone/psyad/tl2ad.py b/src/psyclone/psyad/tl2ad.py index 025ca2c58a..040d308ef8 100644 --- a/src/psyclone/psyad/tl2ad.py +++ b/src/psyclone/psyad/tl2ad.py @@ -32,6 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab +# Modified by T. Vockerodt, Met Office '''The implementation of PSyAD : the PSyclone Adjoint support. Transforms an LFRic tangent linear kernel to its adjoint. @@ -63,7 +64,7 @@ def generate_adjoint_str(tl_fortran_str, active_variables, - api=None, create_test=False, + api=None, create_test=False, test_name="adjoint_test", coord_arg_index=None, panel_id_arg_index=None): '''Takes a tangent-linear kernel encoded as a string as input and returns its adjoint encoded as a string along with (if requested) @@ -75,6 +76,8 @@ def generate_adjoint_str(tl_fortran_str, active_variables, :param Optional[str] api: the PSyclone API in use, if any. :param Optional[bool] create_test: whether or not to create test code for the adjoint kernel. + :param Optional[str] test_name: base name to use for the file containing + the created adjoint test. :param Optional[int] coord_arg_index: the (1-based) index of the kernel argument holding the mesh coordinates (if any). Only applies to the LFRic API. @@ -130,7 +133,8 @@ def generate_adjoint_str(tl_fortran_str, active_variables, if create_test: test_psyir = generate_lfric_adjoint_harness(tl_psyir, coord_arg_index, - panel_id_arg_index) + panel_id_arg_index, + test_name) else: raise NotImplementedError( f"PSyAD only supports generic routines/programs or LFRic " diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index af421886d7..88fc6cc512 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -1164,6 +1164,7 @@ def _parse_dimensions(self, dimensions, symbol_table): shape = [] # Traverse shape specs in Depth-first-search order for dim in walk(dimensions, (Fortran2003.Assumed_Shape_Spec, + Fortran2003.Deferred_Shape_Spec, Fortran2003.Explicit_Shape_Spec, Fortran2003.Assumed_Size_Spec)): @@ -1194,6 +1195,11 @@ def _parse_dimensions(self, dimensions, symbol_table): else: shape.append(None) + elif isinstance(dim, Fortran2003.Deferred_Shape_Spec): + # Deferred_Shape_Spec has no children (R520). For our purposes + # it is equivalent to Assumed_Shape_Spec(None, None). + shape.append(None) + elif isinstance(dim, Fortran2003.Explicit_Shape_Spec): upper = self._process_array_bound(dim.items[1], symbol_table) @@ -1680,7 +1686,8 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, has_save_attr = False if attr_specs: for attr in attr_specs.items: - if isinstance(attr, Fortran2003.Attr_Spec): + if isinstance(attr, (Fortran2003.Attr_Spec, + Fortran2003.Component_Attr_Spec)): normalized_string = str(attr).lower().replace(' ', '') if normalized_string == "save": if interface is not None: diff --git a/src/psyclone/psyir/nodes/__init__.py b/src/psyclone/psyir/nodes/__init__.py index 6390758dcb..b43f98f751 100644 --- a/src/psyclone/psyir/nodes/__init__.py +++ b/src/psyclone/psyir/nodes/__init__.py @@ -64,7 +64,7 @@ from psyclone.psyir.nodes.extract_node import ExtractNode from psyclone.psyir.nodes.kernel_schedule import KernelSchedule from psyclone.psyir.nodes.member import Member -from psyclone.psyir.nodes.nan_test_node import NanTestNode +from psyclone.psyir.nodes.value_range_check_node import ValueRangeCheckNode from psyclone.psyir.nodes.profile_node import ProfileNode from psyclone.psyir.nodes.psy_data_node import PSyDataNode from psyclone.psyir.nodes.read_only_verify_node import ReadOnlyVerifyNode @@ -142,8 +142,8 @@ 'PSyDataNode', 'ExtractNode', 'ProfileNode', - 'NanTestNode', 'ReadOnlyVerifyNode', + 'ValueRangeCheckNode', # Directive Nodes 'Directive', 'RegionDirective', diff --git a/src/psyclone/psyir/nodes/array_mixin.py b/src/psyclone/psyir/nodes/array_mixin.py index 5c992f0638..7acb40380f 100644 --- a/src/psyclone/psyir/nodes/array_mixin.py +++ b/src/psyclone/psyir/nodes/array_mixin.py @@ -610,7 +610,8 @@ def _get_effective_shape(self): :rtype: list[:py:class:`psyclone.psyir.nodes.DataNode`] :raises NotImplementedError: if any of the array-indices involve a - function call or an expression. + function call or an expression or are + of unknown type. ''' shape = [] for idx, idx_expr in enumerate(self.indices): @@ -621,7 +622,7 @@ def _get_effective_shape(self): dtype = idx_expr.datatype if isinstance(dtype, ArrayType): # An array slice can be defined by a 1D slice of another - # array, e.g. `a(b(1:4))`. + # array, e.g. `a(b(1:4))` or `a(b)`. indirect_array_shape = dtype.shape if len(indirect_array_shape) > 1: raise NotImplementedError( @@ -630,8 +631,22 @@ def _get_effective_shape(self): f"used to index into '{self.name}' has " f"{len(indirect_array_shape)} dimensions.") # pylint: disable=protected-access - shape.append(idx_expr._extent(idx)) - + if isinstance(idx_expr, ArrayMixin): + shape.append(idx_expr._extent(idx)) + else: + # We have a Reference (to an array) with no explicit + # indexing. The extent of this is then the SIZE of + # that array. + sizeop = IntrinsicCall.create( + IntrinsicCall.Intrinsic.SIZE, [idx_expr.copy()]) + shape.append(sizeop) + elif isinstance(dtype, (UnsupportedType, UnresolvedType)): + raise NotImplementedError( + f"The array index expression " + f"'{idx_expr.debug_string()}' in access " + f"'{self.debug_string()}' is of '{dtype}' type and " + f"therefore whether it is an array slice (i.e. an " + f"indirect access) cannot be determined.") elif isinstance(idx_expr, (Call, Operation, CodeBlock)): # We can't yet straightforwardly query the type of a function # call or Operation - TODO #1799. diff --git a/src/psyclone/psyir/nodes/structure_reference.py b/src/psyclone/psyir/nodes/structure_reference.py index fd7c4e8842..f4be0a6f65 100644 --- a/src/psyclone/psyir/nodes/structure_reference.py +++ b/src/psyclone/psyir/nodes/structure_reference.py @@ -269,10 +269,34 @@ def datatype(self): :returns: the datatype of this reference. :rtype: :py:class:`psyclone.psyir.symbols.DataType` - :raises NotImplementedError: if the structure reference represents \ + :raises NotImplementedError: if the structure reference represents an array of arrays. ''' + def _get_cursor_shape(cursor, cursor_type): + ''' + Utility that returns the shape of the supplied node. + + :param cursor: the node to get the shape of. + :type cursor: :py:class:`psyclone.psyir.nodes.Node` + :param cursor_type: the type of the node. + :type cursor_type: :py:class:`psyclone.psyir.symbols.DataType` + + :returns: the shape of the node or None if it is not an array. + :rtype: Optional[list[:py:class:`psyclone.psyir.nodes.Node`]] + + ''' + if not (isinstance(cursor, ArrayMixin) or + isinstance(cursor_type, ArrayType)): + return None + if isinstance(cursor, ArrayMixin): + # It is an ArrayMixin and has indices so could be a single + # element or a slice. + # pylint: disable=protected-access + return cursor._get_effective_shape() + # No indices so it is an access to a whole array. + return cursor_type.shape + # pylint: disable=too-many-return-statements, too-many-branches if self._overwrite_datatype: return self._overwrite_datatype @@ -296,14 +320,10 @@ def datatype(self): # If the reference has explicit array syntax, we need to consider it # to calculate the resulting shape. - if isinstance(cursor, ArrayMixin): - try: - # pylint: disable=protected-access - shape = cursor._get_effective_shape() - except NotImplementedError: - return UnresolvedType() - else: - shape = [] + try: + shape = _get_cursor_shape(cursor, cursor_type) + except NotImplementedError: + return UnresolvedType() # Walk down the structure, collecting information on any array slices # as we go. @@ -317,46 +337,24 @@ def datatype(self): # Once we've hit an Unresolved/UnsupportedType the cursor_type # will remain set to that as we can't do any better. cursor_type = cursor_type.components[cursor.name].datatype - if isinstance(cursor, ArrayMixin): - # pylint: disable=protected-access - try: - shape.extend(cursor._get_effective_shape()) - except NotImplementedError: - return UnresolvedType() - - # We've reached the ultimate member of the structure access. - if shape: - if isinstance(cursor_type, ArrayType): - # It's of array type but does it represent a single element, - # a slice or a whole array? (We use `children` rather than - # `indices` so as to avoid having to check that `cursor` is - # an `ArrayMember`.) - if cursor.children: - # It has indices so could be a single element or a slice. - # pylint: disable=protected-access - cursor_shape = cursor._get_effective_shape() - else: - # No indices so it is an access to a whole array. - cursor_shape = cursor_type.shape - if cursor_shape and len(shape) != len(cursor_shape): - # This ultimate access is an array but we've already - # encountered one or more slices earlier in the access - # expression. + try: + cursor_shape = _get_cursor_shape(cursor, cursor_type) + except NotImplementedError: + return UnresolvedType() + if cursor_shape: + if shape: raise NotImplementedError( f"Array of arrays not supported: the ultimate member " f"'{cursor.name}' of the StructureAccess represents " f"an array but other array notation is present in the " f"full access expression: '{self.debug_string()}'") + shape = cursor_shape + if shape: return ArrayType(cursor_type, shape) - # We don't have an explicit array access (because `shape` is Falsey) - # but is the ultimate member itself an array? + # We must have a scalar. if isinstance(cursor_type, ArrayType): - if not cursor.children: - # It is and there are no index expressions so we return the - # ArrayType. - return cursor_type # We have an access to a single element of the array. # Currently arrays of scalars are handled in a # different way to all other types of array. Issue #1857 will diff --git a/src/psyclone/psyir/nodes/nan_test_node.py b/src/psyclone/psyir/nodes/value_range_check_node.py similarity index 68% rename from src/psyclone/psyir/nodes/nan_test_node.py rename to src/psyclone/psyir/nodes/value_range_check_node.py index 5f3a894fbd..7348579e7c 100644 --- a/src/psyclone/psyir/nodes/nan_test_node.py +++ b/src/psyclone/psyir/nodes/value_range_check_node.py @@ -44,50 +44,78 @@ from psyclone.psyir.nodes.psy_data_node import PSyDataNode -class NanTestNode(PSyDataNode): +class ValueRangeCheckNode(PSyDataNode): ''' This class can be inserted into a Schedule to mark Nodes for NAN-checking using the NanTestTrans transformation. The Nodes - marked for checking become children of (the Schedule of) a NanTestNode. + marked for checking become children of (the Schedule of) a + ValueRangeCheckNode. ''' # Textual description of the node. - _text_name = "NanTest" + _text_name = "ValueRangeCheck" _colour = "green" # The default prefix to add to the PSyData module name and PSyDataType - _default_prefix = "nan_test" + _default_prefix = "value_range_check" @property - def nan_test_body(self): + def value_range_check_body(self): ''' - :returns: the Schedule associated with this NanTestNode. + :returns: the Schedule associated with this ValueRangeCheckNode. :rtype: :py:class:`psyclone.psyir.nodes.Schedule` ''' return super().psy_data_body - def lower_to_language_level(self): - # pylint: disable=arguments-differ - ''' - Lowers this node (and all children) to language-level PSyIR. The - PSyIR tree is modified in-place. + def _get_var_lists(self): + '''This method uses the CallTreeUtils to get all input- + and output-variables. They are added to a dictionary, which + will be provided to the code creation method in the base class. - :returns: the lowered version of this node. - :rtype: :py:class:`psyclone.psyir.node.Node` + :returns: dictionary with key/values for pre_var_list and + post_var_list. + :rtype: Dict[str, List[Tuple[str,:py:class:`psyclone.core.Signature`]]] ''' # This cannot be moved to the top, it would cause a circular import # pylint: disable=import-outside-toplevel from psyclone.psyir.tools.call_tree_utils import CallTreeUtils - # Determine the variables to check: + ctu = CallTreeUtils() read_write_info = ctu.get_in_out_parameters(self) + return {'pre_var_list': read_write_info.read_list, + 'post_var_list': read_write_info.write_list} + + def gen_code(self, parent, options=None): + '''Old style code creation function. + + :param parent: f2pygen node to which to add AST nodes. + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` + :param options: a dictionary with options for transformations + and validation. + :type options: Optional[Dict[str, Any]] + + ''' + local_options = options.copy() if options else {} - options = {'pre_var_list': read_write_info.read_list, - 'post_var_list': read_write_info.write_list} + var_lists_options = self._get_var_lists() + local_options.update(var_lists_options) + super().gen_code(parent, local_options) + + def lower_to_language_level(self): + # pylint: disable=arguments-differ + ''' + Lowers this node (and all children) to language-level PSyIR. The + PSyIR tree is modified in-place. + + :returns: the lowered version of this node. + :rtype: :py:class:`psyclone.psyir.node.Node` + + ''' + options = self._get_var_lists() return super().lower_to_language_level(options) # For AutoAPI documentation generation -__all__ = ['NanTestNode'] +__all__ = ['ValueRangeCheckNode'] diff --git a/src/psyclone/psyir/transformations/__init__.py b/src/psyclone/psyir/transformations/__init__.py index 927e67145c..60b55c1449 100644 --- a/src/psyclone/psyir/transformations/__init__.py +++ b/src/psyclone/psyir/transformations/__init__.py @@ -86,7 +86,7 @@ from psyclone.psyir.transformations.loop_tiling_2d_trans \ import LoopTiling2DTrans from psyclone.psyir.transformations.loop_trans import LoopTrans -from psyclone.psyir.transformations.nan_test_trans import NanTestTrans +from psyclone.psyir.transformations.value_range_check_trans import ValueRangeCheckTrans from psyclone.psyir.transformations.omp_loop_trans import OMPLoopTrans from psyclone.psyir.transformations.omp_target_trans import OMPTargetTrans from psyclone.psyir.transformations.omp_taskwait_trans import OMPTaskwaitTrans @@ -132,7 +132,6 @@ 'LoopTrans', 'Maxval2LoopTrans', 'Minval2LoopTrans', - 'NanTestTrans', 'OMPLoopTrans', 'OMPTargetTrans', 'OMPTaskTrans', @@ -145,4 +144,5 @@ 'Reference2ArrayRangeTrans', 'RegionTrans', 'ReplaceInductionVariablesTrans', - 'TransformationError'] + 'TransformationError', + 'ValueRangeCheckTrans'] diff --git a/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py b/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py index 0fc0238a5d..59a65b99ff 100644 --- a/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py +++ b/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py @@ -49,10 +49,10 @@ CodeBlock, Routine, BinaryOperation) from psyclone.psyir.nodes.array_mixin import ArrayMixin from psyclone.psyir.symbols import ( - DataSymbol, INTEGER_TYPE, ScalarType, UnresolvedType, SymbolError, - ArrayType) -from psyclone.psyir.transformations.transformation_error \ - import TransformationError + ArrayType, DataSymbol, INTEGER_TYPE, ScalarType, SymbolError, + UnresolvedType) +from psyclone.psyir.transformations.transformation_error import ( + TransformationError) from psyclone.psyir.transformations.reference2arrayrange_trans import ( Reference2ArrayRangeTrans) @@ -241,17 +241,16 @@ def validate(self, node, options=None): f"Range, but none were found in '{node.debug_string()}'.")) # All the ArrayMixins must have the same number of Ranges to expand - found = None + num_of_ranges = None for accessor in node.walk(ArrayMixin): - num_of_ranges = len([x for x in accessor.indices - if isinstance(x, Range)]) - if num_of_ranges > 0: - if not found: + count = len([x for x in accessor.indices if isinstance(x, Range)]) + if count > 0: + if not num_of_ranges: # If it's the first value we find, we store it - found = num_of_ranges + num_of_ranges = count else: # Otherwise we compare it against the previous found value - if found != num_of_ranges: + if count != num_of_ranges: raise TransformationError(LazyString( lambda: f"{self.name} does not support statements" f" containing array accesses that have varying " @@ -322,8 +321,8 @@ def validate(self, node, options=None): lambda: f"{message} in:\n{node.debug_string()}.")) # For each top-level reference (because we don't support nesting), the - # apply will have to be able to decide if its an Array (and access it - # with the index) or an Scalar (and leave it as it is). We can not + # apply() will have to determine whether it's an Array (and access it + # with the index) or a Scalar (and leave it as it is). We cannot # transform references where this is unclear. for reference in node.walk(Reference, stop_type=Reference): if isinstance(reference.parent, Call): @@ -351,12 +350,12 @@ def validate(self, node, options=None): # scalar. if not isinstance(reference.datatype, (ScalarType, ArrayType)): if isinstance(reference.symbol, DataSymbol): - typestr = f"an {reference.symbol.datatype}" + typestr = f"an {reference.datatype}" else: typestr = "not a DataSymbol" message = ( f"{self.name} cannot expand expression because it " - f"contains the variable '{reference.symbol.name}' " + f"contains the access '{reference.debug_string()}' " f"which is {typestr} and therefore cannot be guaranteed" f" to be ScalarType.") if not isinstance(reference.symbol, DataSymbol) or \ diff --git a/src/psyclone/psyir/transformations/inline_trans.py b/src/psyclone/psyir/transformations/inline_trans.py index 47da70ff55..d44df08d74 100644 --- a/src/psyclone/psyir/transformations/inline_trans.py +++ b/src/psyclone/psyir/transformations/inline_trans.py @@ -530,7 +530,10 @@ def _replace_formal_struc_arg(self, actual_arg, ref, call_node, break cursor = cursor.member - if not actual_arg.walk(Range) and local_indices: + # TODO #1858 - once we support converting structure accesses into + # explicit array accesses, we can put back the testing in + # inline_trans_test.py that covers this code and remove the pragma: + if not actual_arg.walk(Range) and local_indices: # pragma: no cover # There are no Ranges in the actual argument but the local # reference is an array access. # Create updated index expressions for that access. diff --git a/src/psyclone/psyir/transformations/nan_test_trans.py b/src/psyclone/psyir/transformations/value_range_check_trans.py similarity index 89% rename from src/psyclone/psyir/transformations/nan_test_trans.py rename to src/psyclone/psyir/transformations/value_range_check_trans.py index 35892521c2..81bdc7973e 100644 --- a/src/psyclone/psyir/transformations/nan_test_trans.py +++ b/src/psyclone/psyir/transformations/value_range_check_trans.py @@ -42,30 +42,28 @@ actual code. ''' -from __future__ import absolute_import - from psyclone import psyGen -from psyclone.psyir.nodes import NanTestNode +from psyclone.psyir.nodes import ValueRangeCheckNode from psyclone.psyir import nodes from psyclone.psyir.transformations.read_only_verify_trans \ import ReadOnlyVerifyTrans -class NanTestTrans(ReadOnlyVerifyTrans): - '''This transformation inserts a NanTestNode into the PSyIR of a +class ValueRangeCheckTrans(ReadOnlyVerifyTrans): + '''This transformation inserts a ValueRangeCheckNode into the PSyIR of a schedule. At code creation time this node will use the PSyData API to create code that will verify all input parameters are not NANs and not infinite, and the same for all output parameters. After applying the transformation the Nodes marked for verification are - children of the NanTestNode. + children of the ValueRangeCheckNode. Nodes to verify can be individual constructs within an Invoke (e.g. Loops containing a Kernel or BuiltIn call) or entire Invokes. - :param node_class: The class of Node which will be inserted \ - into the tree (defaults to NanTestNode), but can be any \ + :param node_class: The class of Node which will be inserted + into the tree (defaults to ValueRangeCheckNode), but can be any derived class. - :type node_class: :py:class:`psyclone.psyir.nodes.NanTestNode` or \ + :type node_class: :py:class:`psyclone.psyir.nodes.ValueRangeCheckNode` or derived class ''' @@ -73,7 +71,7 @@ class NanTestTrans(ReadOnlyVerifyTrans): valid_node_types = (nodes.Loop, psyGen.Kern, psyGen.BuiltIn, nodes.Directive, nodes.Literal, nodes.Reference) - def __init__(self, node_class=NanTestNode): + def __init__(self, node_class=ValueRangeCheckNode): # This function is only here to change the default node type super().__init__(node_class=node_class) @@ -105,4 +103,4 @@ def validate(self, node_list, options=None): # ============================================================================ # For automatic documentation creation: -__all__ = ["NanTestTrans"] +__all__ = ["ValueRangeCheckTrans"] diff --git a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py index e0abe8fec6..17968d278a 100644 --- a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py +++ b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py @@ -572,11 +572,12 @@ def test_generate_lfric_adjoint_harness(fortran_reader, fortran_writer): '''Test that the generate_lfric_adjoint_harness() function generates the expected test-harness code.''' tl_psyir = fortran_reader.psyir_from_source(TL_CODE) - psyir = generate_lfric_adjoint_harness(tl_psyir) + adjt_name = "adjoint_test_alg" + psyir = generate_lfric_adjoint_harness(tl_psyir, test_name=adjt_name) gen = fortran_writer(psyir).lower() assert "use finite_element_config_mod, only : element_order" in gen - assert "module adjoint_test_mod" in gen - assert "subroutine adjoint_test(mesh, chi, panel_id)" in gen + assert "module adjoint_test_alg_mod" in gen + assert "subroutine adjoint_test_alg(mesh, chi, panel_id)" in gen # We should have a field, a copy of that field and an inner-product value # for that field. assert (" real(kind=r_def) :: ascalar\n" diff --git a/src/psyclone/tests/psyad/main_test.py b/src/psyclone/tests/psyad/main_test.py index 8b93b49082..03e4242e4a 100644 --- a/src/psyclone/tests/psyad/main_test.py +++ b/src/psyclone/tests/psyad/main_test.py @@ -33,6 +33,7 @@ # ----------------------------------------------------------------------------- # Authors: R. W. Ford, A. R. Porter and N. Nobre, STFC Daresbury Lab # Modified by J. Henrichs, Bureau of Meteorology +# Modified by T. Vockerodt, Met Office '''A module to perform pytest tests on the code in the main.py file within the psyad directory. @@ -62,6 +63,59 @@ "end module my_mod\n" ) +TEST_LFRIC_KERNEL = '''module tl_foo_kernel_mod + + use argument_mod, only : arg_type, func_type, & + GH_FIELD, GH_REAL, & + GH_INC, CELL_COLUMN + use constants_mod, only : r_def, i_def + use fs_continuity_mod, only : W2 + use kernel_mod, only : kernel_type + + implicit none + + private + + type, public, extends(kernel_type) :: tl_foo_kernel_type + private + type(arg_type) :: meta_args(1) = (/ & + arg_type(GH_FIELD, GH_REAL, GH_INC, W2) & + /) + integer :: operates_on = CELL_COLUMN + contains + procedure, nopass :: tl_foo_kernel_code + end type + + public :: tl_foo_kernel_code + +contains + +subroutine tl_foo_kernel_code(nlayers, & + field, & + ndf2, undf2, map2 & + ) + + implicit none + + ! Arguments + integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: ndf2, undf2 + integer(kind=i_def), dimension(ndf2), intent(in) :: map2 + real(kind=r_def), dimension(undf2), intent(inout) :: field + + ! Internal variables + integer(kind=i_def) :: df2, k + + do k = 0, nlayers-1 + do df2 = 1,ndf2 + field( map2(df2)+k ) = 0.0_r_def + end do + end do + +end subroutine tl_foo_kernel_code + +end module tl_foo_kernel_mod''' + EXPECTED_HARNESS_CODE = '''program adj_test use my_mod, only : kern use adj_my_mod, only : adj_kern @@ -512,3 +566,43 @@ def test_main_otest_verbose(tmpdir, caplog): "github actions.") assert "Writing test harness for adjoint kernel to file" in caplog.text assert "/harness.f90" in caplog.text + + +def test_main_otest_lfric(tmpdir, capsys): + ''' Test that the -otest option combined with LFRic API + generates the expected adjoint test. ''' + filename_in = str(tmpdir.join("tl_foo_kernel_mod.f90")) + filename_out = str(tmpdir.join("atl_foo_kernel_mod.f90")) + harness_out = str(tmpdir.join("atlt_foo_alg_mod.x90")) + with open(filename_in, "w", encoding='utf-8') as my_file: + my_file.write(TEST_LFRIC_KERNEL) + main([filename_in, "-a", "field", "-api", "lfric", "-oad", filename_out, + "-otest", harness_out]) + output, error = capsys.readouterr() + assert error == "" + assert output == "" + with open(harness_out, 'r', encoding='utf-8') as my_file: + data = my_file.read() + assert "module atlt_foo_alg_mod" in data.lower() + assert "subroutine atlt_foo_alg" in data.lower() + + +def test_main_otest_lfric_error_name(capsys, caplog): + ''' Test that a bad -otest option combined with LFRic API + generates the expected error. ''' + harness_out = "some/path/foo_alg_mod.x90" + logger = logging.getLogger("psyclone.psyad.main") + logger.propagate = True + with caplog.at_level(logging.ERROR, "psyclone.psyad.main"): + with pytest.raises(SystemExit) as err: + main(["input.f90", "-a", "field", "-api", "lfric", "-oad", + "output.f90", "-otest", harness_out]) + assert str(err.value) == "1" + x_fail_str = (rf"Filename '{harness_out}' with 'lfric' API " + "must be of the form " + "/adjt__alg_mod.[Xx]90 or " + "/atlt__alg_mod.[Xx]90.") + assert x_fail_str in caplog.text + output, error = capsys.readouterr() + assert error == "" + assert output == "" diff --git a/src/psyclone/tests/psyad/tl2ad_test.py b/src/psyclone/tests/psyad/tl2ad_test.py index c407d17b10..843dd0e93c 100644 --- a/src/psyclone/tests/psyad/tl2ad_test.py +++ b/src/psyclone/tests/psyad/tl2ad_test.py @@ -32,6 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab +# Modified by T. Vockerodt, Met Office '''A module to perform pytest tests on the code in the tl2ad.py file within the psyad directory. @@ -137,10 +138,14 @@ def test_generate_adjoint_str_lfric_api(): testkern = os.path.join(LFRIC_TEST_FILES_DIR, "tl_testkern_mod.F90") with open(testkern, mode="r", encoding="utf-8") as kfile: tl_code = kfile.read() - result, _ = generate_adjoint_str(tl_code, + adj, test = generate_adjoint_str(tl_code, ["xi", "u", "res_dot_product", "curl_u"], - api="lfric") - assert "subroutine adj_testkern_code" in result.lower() + api="lfric", + create_test=True, + test_name="atlt_testkern") + assert "subroutine adj_testkern_code" in adj.lower() + assert "module atlt_testkern_mod" in test.lower() + assert "subroutine atlt_testkern" in test.lower() def test_generate_adjoint_str_function(): diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index 3135b37a58..1e6a8c8553 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -850,6 +850,18 @@ def test_unsupported_decln(fortran_reader): assert ("Invalid variable declaration found in _process_decln for " "'problem'" in str(err.value)) + reader = FortranStringReader("integer, intent(in) :: l1") + fparser2spec = Specification_Part(reader).content[0] + # Break the attribute-spec list for this declaration. + attr_specs = fparser2spec.items[1] + attr_specs.items = (attr_specs.items[0], "rubbish") + fake_parent = KernelSchedule.create("dummy_schedule") + symtab = fake_parent.symbol_table + processor = Fparser2Reader() + with pytest.raises(NotImplementedError) as error: + processor._process_decln(fake_parent, symtab, fparser2spec) + assert "Unrecognised attribute type 'str'" in str(error.value) + def test_unsupported_decln_structure_type(fortran_reader): ''' diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index 3b51510dcb..b87f0666a2 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -692,16 +692,24 @@ def test_where_stmt_validity(): @pytest.mark.usefixtures("parser") -def test_where_stmt(): +def test_where_stmt(fortran_reader): ''' Basic check that we handle a WHERE statement correctly. ''' - fake_parent, _ = process_where( - "WHERE( at_i(:,:) > rn_amax_2d(:,:) ) " - "a_i(:,:,jl) = a_i(:,:,jl) * rn_amax_2d(:,:) / at_i(:,:)", - Fortran2003.Where_Stmt, ["at_i", "rn_amax_2d", "jl", "a_i"]) - assert len(fake_parent.children) == 1 - assert isinstance(fake_parent[0], Loop) + code = '''\ + program where_test + implicit none + integer :: jl + real, dimension(:,:), allocatable :: at_i, rn_amax_2d + real, dimension(:,:,:), allocatable :: a_i + WHERE( at_i(:,:) > rn_amax_2d(:,:) ) & + a_i(:,:,jl) = a_i(:,:,jl) * rn_amax_2d(:,:) / at_i(:,:) + end program where_test + ''' + psyir = fortran_reader.psyir_from_source(code) + routine = psyir.walk(Routine)[0] + assert len(routine.children) == 1 + assert isinstance(routine[0], Loop) def test_where_stmt_no_reduction(fortran_reader, fortran_writer): @@ -765,11 +773,11 @@ def test_where_ordering(parser): ("where (my_type%var(:) > epsi20)\n" "my_type2(jl)%array2(:,jl) = 3.0\n", "my_type%var"), ("where (my_type%block(jl)%var(:) > epsi20)\n" - "my_type%block%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), + "my_type%block(jl)%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), ("where (my_type%block(jl)%var(:) > epsi20)\n" "my_type%block(jl)%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), - ("where (my_type2(:)%var > epsi20)\n" - "my_type2(:)%var = 3.0\n", "my_type2")]) + ("where (my_type2(:)%var(jl) > epsi20)\n" + "my_type2(:)%var(jl) = 3.0\n", "my_type2")]) def test_where_derived_type(fortran_reader, code, size_arg): ''' Test that we handle the case where array members of a derived type are accessed within a WHERE. ''' @@ -805,10 +813,12 @@ def test_where_derived_type(fortran_reader, code, size_arg): assert isinstance(loops[1].loop_body[0], IfBlock) # All Range nodes should have been replaced assert not loops[0].walk(Range) - # All ArrayMember accesses should now use the `widx1` loop variable + # All ArrayMember accesses other than 'var' should now use the `widx1` + # loop variable array_members = loops[0].walk(ArrayMember) for member in array_members: - assert "+ widx1 - 1" in member.indices[0].debug_string() + if member.name != "var": + assert "+ widx1 - 1" in member.indices[0].debug_string() @pytest.mark.parametrize( diff --git a/src/psyclone/tests/psyir/nodes/array_mixin_test.py b/src/psyclone/tests/psyir/nodes/array_mixin_test.py index f9290e82b3..7c4630fed1 100644 --- a/src/psyclone/tests/psyir/nodes/array_mixin_test.py +++ b/src/psyclone/tests/psyir/nodes/array_mixin_test.py @@ -608,6 +608,7 @@ def test_get_effective_shape(fortran_reader): code = ( "subroutine test()\n" " use some_mod\n" + " integer :: idx = 2\n" " integer :: indices(8,3)\n" " real a(10), b(10,10)\n" " a(1:10) = 0.0\n" @@ -621,6 +622,8 @@ def test_get_effective_shape(fortran_reader): " b(indices(2:3,1:2), 2:5) = 2.0\n" " a(f()) = 2.0\n" " a(2+3) = 1.0\n" + " b(idx, a) = -1.0\n" + " b(scalarval, arrayval) = 1\n" "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) routine = psyir.walk(Routine)[0] @@ -691,6 +694,49 @@ def test_get_effective_shape(fortran_reader): with pytest.raises(NotImplementedError) as err: _ = routine.children[child_idx].lhs._get_effective_shape() assert "include a function call or expression" in str(err.value) + # Array access with indices given by another array that is not explicitly + # indexed. + child_idx += 1 + shape = routine.children[child_idx].lhs._get_effective_shape() + assert len(shape) == 1 + assert "SIZE(a)" in shape[0].debug_string() + # Array-index expressions are symbols of unknown type so we don't know + # whether we have an array slice or just a scalar. + child_idx += 1 + with pytest.raises(NotImplementedError) as err: + _ = routine.children[child_idx].lhs._get_effective_shape() + assert ("index expression 'scalarval' in access 'b(scalarval,arrayval)' is" + " of 'UnresolvedType' type and therefore whether it is an array " + "slice (i.e. an indirect access) cannot be determined." + in str(err.value)) + + +def test_struct_get_effective_shape(fortran_reader): + '''Tests for the _get_effective_shape() method for ArrayMember and + ArrayOfStructuresMixin (since they inherit it from ArrayMixin).''' + code = ( + "subroutine test()\n" + " type :: my_type\n" + " real, dimension(21,12) :: data\n" + " end type my_type\n" + " type(my_type) :: grid\n" + " type(my_type), dimension(5) :: grid_list\n" + " grid%data(:,:) = 0.0\n" + " grid_list(:)%data(1) = 0.0\n" + "end subroutine\n") + psyir = fortran_reader.psyir_from_source(code) + routine = psyir.walk(Routine)[0] + # Slice of ArrayMember + child_idx = 0 + shape = routine.children[child_idx].lhs.member._get_effective_shape() + assert len(shape) == 2 + assert "SIZE(grid%data, dim=1)" in shape[0].debug_string() + assert "SIZE(grid%data, dim=2)" in shape[1].debug_string() + # Slice of ArrayOfStructuresMixin + child_idx += 1 + shape = routine.children[child_idx].lhs._get_effective_shape() + assert len(shape) == 1 + assert isinstance(shape[0], IntrinsicCall) # get_outer_range_index diff --git a/src/psyclone/tests/psyir/nodes/array_reference_test.py b/src/psyclone/tests/psyir/nodes/array_reference_test.py index 8073840657..027e812ac8 100644 --- a/src/psyclone/tests/psyir/nodes/array_reference_test.py +++ b/src/psyclone/tests/psyir/nodes/array_reference_test.py @@ -580,6 +580,13 @@ def test_array_datatype(): aref6._symbol = test_struc_sym assert isinstance(aref6.datatype, UnresolvedType) + # TODO #2448 - we don't handle an array access to something that we + # don't know is an array. + aref7 = ArrayReference(generic_sym) + aref7.addchild(one.copy()) + aref7._symbol = DataSymbol("int_test", INTEGER_TYPE) + assert isinstance(aref7.datatype, UnresolvedType) + def test_array_create_colon(fortran_writer): '''Test that the create method accepts ":" as shortcut to automatically diff --git a/src/psyclone/tests/psyir/nodes/structure_reference_test.py b/src/psyclone/tests/psyir/nodes/structure_reference_test.py index a8cf02a4ab..55881ccdd7 100644 --- a/src/psyclone/tests/psyir/nodes/structure_reference_test.py +++ b/src/psyclone/tests/psyir/nodes/structure_reference_test.py @@ -241,7 +241,9 @@ def test_struc_ref_datatype(): grid_type = symbols.StructureType.create([ ("nx", symbols.INTEGER_TYPE, symbols.Symbol.Visibility.PUBLIC, None), ("data", atype, symbols.Symbol.Visibility.PRIVATE, None), + # A single member of structure type. ("roger", rtype, symbols.Symbol.Visibility.PUBLIC, None), + # An array of structure type. ("titty", artype, symbols.Symbol.Visibility.PUBLIC, None)]) # Symbol with type defined by StructureType ssym0 = symbols.DataSymbol("grid", grid_type) @@ -273,6 +275,20 @@ def test_struc_ref_datatype(): ssym, [("titty", [one.copy(), two.copy()])]) assert singleref.datatype == rtype_sym + # Reference to grid%titty(1,2)%gibber + sref = nodes.StructureReference.create( + ssym, [("titty", [one.copy(), two.copy()]), "gibber"]) + assert sref.datatype == symbols.BOOLEAN_TYPE + + # Reference to grid%titty(my_func(1), 2) where my_func is unresolved. + func_sym = symbols.DataSymbol("my_func", + datatype=symbols.UnresolvedType(), + interface=symbols.UnresolvedInterface()) + fref = nodes.ArrayReference.create(func_sym, [one.copy()]) + sref2 = nodes.StructureReference.create( + ssym, [("titty", [fref, two.copy()])]) + assert isinstance(sref2.datatype, symbols.UnresolvedType) + # Reference to sub-array of structure members of structure myrange = nodes.Range.create(two.copy(), nodes.Literal("4", symbols.INTEGER_TYPE)) diff --git a/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py b/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py index e9ffea258f..95cb0be342 100644 --- a/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py @@ -303,6 +303,40 @@ def test_apply_to_arrays_with_different_bounds(fortran_reader, fortran_writer): in output_test4) +def test_apply_indirect_indexing(fortran_reader, fortran_writer): + ''' + Check the application of the transformation when the array is indexed + indirectly. + + ''' + psyir = fortran_reader.psyir_from_source(''' + program test + use dom_oce + INTEGER, DIMENSION(4) :: iwewe + INTEGER, DIMENSION(8,kfld) :: ishtSi + integer :: jf + do jf = 1, kfld + ishtSi(5:8,jf) = ishtSi(iwewe, jf) + ishtSi(1:4,jf) = grid(3)%var(iwewe, jf) + end do + end program test + ''') + assignments = psyir.walk(Assignment) + trans = ArrayAssignment2LoopsTrans() + trans.apply(assignments[0]) + trans.apply(assignments[1]) + result = fortran_writer(psyir) + assert (''' + do idx = 5, 8, 1 + ishtsi(idx,jf) = ishtsi(iwewe(idx + (1 - 5)),jf) + enddo''' in result) + assert (''' + do idx_1 = 1, 4, 1 + ishtsi(idx_1,jf) = grid(3)%var(iwewe(idx_1),jf) + enddo + enddo''' in result) + + def test_apply_outside_routine(fortran_reader, fortran_writer): ''' Check that the transformation still succeeds and generates valid code when found in a scope detached from a Routine. ''' @@ -568,15 +602,18 @@ def test_validate_rhs_plain_references(fortran_reader, fortran_writer): ''' psyir = fortran_reader.psyir_from_source(''' subroutine test(unsupported) - use my_variables, only: unresolved + use my_variables, only: unresolved, map integer :: scalar = 1 integer, dimension(:) :: array = 1, x integer, dimension(:), optional :: unsupported - + integer, dimension(4) :: ishtsi x(:) = scalar x(:) = array x(:) = unresolved x(:) = unsupported + ! An indirectly-addressed RHS but we can't tell whether or not map is + ! an array reference + x(:) = ishtsi(map,scalar) end subroutine test ''') @@ -594,7 +631,7 @@ def test_validate_rhs_plain_references(fortran_reader, fortran_writer): with pytest.raises(TransformationError) as info: trans.apply(assign_with_unresolved, opts) assert ("ArrayAssignment2LoopsTrans cannot expand expression because it " - "contains the variable 'unresolved' which is not a DataSymbol " + "contains the access 'unresolved' which is not a DataSymbol " "and therefore cannot be guaranteed to be ScalarType. Resolving " "the import that brings this variable into scope may help." in str(info.value)) @@ -604,18 +641,25 @@ def test_validate_rhs_plain_references(fortran_reader, fortran_writer): with pytest.raises(TransformationError) as info: trans.apply(assign_with_unresolved) assert ("ArrayAssignment2LoopsTrans cannot expand expression because it " - "contains the variable 'unresolved' which is an UnresolvedType " + "contains the access 'unresolved' which is an UnresolvedType " "and therefore cannot be guaranteed to be ScalarType. Resolving " "the import that brings this variable into scope may help." in str(info.value)) with pytest.raises(TransformationError) as info: trans.apply(psyir.walk(Assignment)[3], opts) assert ("ArrayAssignment2LoopsTrans cannot expand expression because it " - "contains the variable 'unsupported' which is an Unsupported" + "contains the access 'unsupported' which is an Unsupported" "FortranType('INTEGER, DIMENSION(:), OPTIONAL :: unsupported') " "and therefore cannot be guaranteed to be ScalarType." in str(info.value)) + with pytest.raises(TransformationError) as info: + trans.apply(psyir.walk(Assignment)[4], opts) + assert ("ArrayAssignment2LoopsTrans cannot expand expression because it " + "contains the access 'ishtsi(map,scalar)' which is an " + "UnresolvedType and therefore cannot be guaranteed to be " + "ScalarType" in str(info.value)) + # The end result should look like: assert ( " do idx = LBOUND(x, dim=1), UBOUND(x, dim=1), 1\n" @@ -625,15 +669,19 @@ def test_validate_rhs_plain_references(fortran_reader, fortran_writer): " x(idx_1) = array(idx_1)\n" " enddo\n" " ! ArrayAssignment2LoopsTrans cannot expand expression because it " - "contains the variable 'unresolved' which is not a DataSymbol and " + "contains the access 'unresolved' which is not a DataSymbol and " "therefore cannot be guaranteed to be ScalarType. Resolving the import" " that brings this variable into scope may help.\n" " x(:) = unresolved\n" " ! ArrayAssignment2LoopsTrans cannot expand expression because it " - "contains the variable 'unsupported' which is an UnsupportedFortran" + "contains the access 'unsupported' which is an UnsupportedFortran" "Type('INTEGER, DIMENSION(:), OPTIONAL :: unsupported') and therefore " "cannot be guaranteed to be ScalarType.\n" " x(:) = unsupported\n" + " ! ArrayAssignment2LoopsTrans cannot expand expression because it " + "contains the access 'ishtsi(map,scalar)' which is an UnresolvedType " + "and therefore cannot be guaranteed to be ScalarType.\n" + " x(:) = ishtsi(map,scalar)\n" ) in fortran_writer(psyir) @@ -685,7 +733,7 @@ def test_validate_non_elemental_functions(fortran_reader): with pytest.raises(TransformationError) as err: trans.validate(assignment3, options={"verbose": True}) errormsg = ("ArrayAssignment2LoopsTrans cannot expand expression because " - "it contains the variable 'myfunc' which is not a DataSymbol " + "it contains the access 'myfunc(y)' which is not a DataSymbol " "and therefore cannot be guaranteed to be ScalarType. " "Resolving the import that brings this variable into scope " "may help.") @@ -695,9 +743,95 @@ def test_validate_non_elemental_functions(fortran_reader): with pytest.raises(TransformationError) as err: trans.validate(assignment4, options={"verbose": True}) errormsg = ("ArrayAssignment2LoopsTrans cannot expand expression because " - "it contains the variable 'var' which is an UnresolvedType " - "and therefore cannot be guaranteed to be ScalarType. " - "Resolving the import that brings this variable into scope " - "may help.") + "it contains the access 'var%myfunc(y)' which is an " + "UnresolvedType and therefore cannot be guaranteed to be " + "ScalarType. Resolving the import that brings this variable " + "into scope may help.") assert errormsg in assignment4.preceding_comment assert errormsg in str(err.value) + + +def test_validate_indirect_indexing(fortran_reader): + ''' + Check the validation of the transformation when the array is indexed + indirectly in unsupported ways. + + ''' + psyir = fortran_reader.psyir_from_source(''' + program test + use dom_oce + INTEGER, DIMENSION(4) :: iwewe + INTEGER, DIMENSION(8,kfld) :: ishtSi + ! Assignment with CodeBlock on RHS. + iwewe(:) = (/ jpwe,jpea,jpwe,jpea /) + ! Assignment with CodeBlock in array-index expression. + iwewe(:) = ishtSi((/ jpwe,jpea,jpwe,jpea /), 1) + ! Index expression does evaluate to an array but we don't currently + ! handle getting a type in this case (TODO #1799). + ishtSi(5:8,jf) = ishtSi(iwewe+1, jf) + ! Index expression contains a call to an unknown function. + ishtSi(5:8,jf) = ishtSi(my_func(1), jf) + end program test + ''') + assignments = psyir.walk(Assignment) + assert isinstance(assignments[0].rhs, CodeBlock) + trans = ArrayAssignment2LoopsTrans() + with pytest.raises(TransformationError) as err: + trans.validate(assignments[0]) + assert ("ArrayAssignment2LoopsTrans does not support array assignments " + "that contain a CodeBlock anywhere in the expression" + in str(err.value)) + with pytest.raises(TransformationError) as err: + trans.validate(assignments[1]) + assert ("ArrayAssignment2LoopsTrans does not support array assignments " + "that contain a CodeBlock anywhere in the expression" + in str(err.value)) + # TODO #1799 - this requires that we extended ArrayMixin.datatype to + # support array accesses with index expressions that are Operations. + with pytest.raises(TransformationError) as err: + trans.validate(assignments[2]) + assert ("cannot expand expression because it contains the access " + "'ishtsi(iwewe + 1,jf)' which is an UnresolvedType and therefore " + "cannot be guaranteed" in str(err.value)) + with pytest.raises(TransformationError) as err: + trans.validate(assignments[3]) + assert ("cannot expand expression because it contains the access " + "'ishtsi(my_func(1),jf)' which is an UnresolvedType and therefore " + "cannot be guaranteed to be ScalarType." in str(err.value)) + + +def test_validate_structure(fortran_reader): + ''' + Check the validation of the transformation when it is a structure access + on the RHS. + + ''' + psyir = fortran_reader.psyir_from_source(''' + program test + integer, parameter :: ngrids = 4, kfld=5 + integer, dimension(8,kfld) :: ishtSi + type :: sub_grid_type + integer, dimension(kfld,kfld) :: map + end type sub_grid_type + type :: grid_type + real, dimension(:,:), allocatable :: data + type(sub_grid_type), dimension(ngrids) :: subgrid + end type grid_type + type(grid_type) :: grid + integer :: jf + ! Cannot tell whether or not the access on the RHS is an array. + ishtSi(5:8,jf) = grid%data(my_func(1), jf) + ! The array access to subgrid is not yet supported. + ishtSi(5:8,jf) = grid%subgrid%map(1,1) + end program test + ''') + assignments = psyir.walk(Assignment) + trans = ArrayAssignment2LoopsTrans() + with pytest.raises(TransformationError) as err: + trans.validate(assignments[0]) + assert ("contains the access 'grid%data(my_func(1),jf)' which is an " + "UnresolvedType" in str(err.value)) + # TODO #1858 - once we've extended Reference2ArrayRangeTrans to support + # StructureMembers we can use it as part of this transformation and this + # example will be supported. + trans.validate(assignments[1]) diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index 35748414aa..e7efd1c172 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -833,10 +833,10 @@ def test_apply_struct_array_slice_arg(fortran_reader, fortran_writer, tmpdir): f" grid%data2d(:,:) = 1.0\n" f" do i=1,10\n" f" a(i) = 1.0\n" - f" call sub(grid%local%data)\n" f" call sub(micah%grids(3)%region%data(:))\n" f" call sub(grid%data2d(:,i))\n" f" call sub(grid%data2d(1:5,i))\n" + f" call sub(grid%local%data)\n" f" end do\n" f" end subroutine run_it\n" f" subroutine sub(x)\n" @@ -854,35 +854,42 @@ def test_apply_struct_array_slice_arg(fortran_reader, fortran_writer, tmpdir): inline_trans = InlineTrans() for call in psyir.walk(Call): if not isinstance(call, IntrinsicCall): + if call.arguments[0].debug_string() == "grid%local%data": + # TODO #1858: this if construct can be removed once we + # support getting the type of `grid%local%data`. + continue inline_trans.apply(call) output = fortran_writer(psyir) assert (" do i = 1, 10, 1\n" " a(i) = 1.0\n" " do ji = 1, 5, 1\n" - " grid%local%data(ji) = 2.0 * grid%local%data(ji)\n" - " enddo\n" - " grid%local%data(1:2) = 0.0\n" - " grid%local%data(:) = 3.0\n" - " grid%local%data = 5.0\n" - " do ji_1 = 1, 5, 1\n" - " micah%grids(3)%region%data(ji_1) = 2.0 * " - "micah%grids(3)%region%data(ji_1)\n" + " micah%grids(3)%region%data(ji) = 2.0 * " + "micah%grids(3)%region%data(ji)\n" " enddo\n" " micah%grids(3)%region%data(1:2) = 0.0\n" " micah%grids(3)%region%data(:) = 3.0\n" " micah%grids(3)%region%data(:) = 5.0\n" - " do ji_2 = 1, 5, 1\n" - " grid%data2d(ji_2,i) = 2.0 * grid%data2d(ji_2,i)\n" + " do ji_1 = 1, 5, 1\n" + " grid%data2d(ji_1,i) = 2.0 * grid%data2d(ji_1,i)\n" " enddo\n" " grid%data2d(1:2,i) = 0.0\n" " grid%data2d(:,i) = 3.0\n" " grid%data2d(:,i) = 5.0\n" - " do ji_3 = 1, 5, 1\n" - " grid%data2d(ji_3,i) = 2.0 * grid%data2d(ji_3,i)\n" + " do ji_2 = 1, 5, 1\n" + " grid%data2d(ji_2,i) = 2.0 * grid%data2d(ji_2,i)\n" " enddo\n" " grid%data2d(1:2,i) = 0.0\n" " grid%data2d(1:5,i) = 3.0\n" " grid%data2d(1:5,i) = 5.0\n" + # TODO #1858: replace the following line with the commented-out + # lines below. + " call sub(grid%local%data)\n" + # " do ji_3 = 1, 5, 1\n" + # " grid%local%data(ji_3) = 2.0 * grid%local%data(ji_3)\n" + # " enddo\n" + # " grid%local%data(1:2) = 0.0\n" + # " grid%local%data(:) = 3.0\n" + # " grid%local%data = 5.0\n" " enddo\n" in output) assert Compile(tmpdir).string_compiles(output) diff --git a/src/psyclone/tests/psyir/transformations/nan_test.py b/src/psyclone/tests/psyir/transformations/nan_test.py deleted file mode 100644 index b550fd68cd..0000000000 --- a/src/psyclone/tests/psyir/transformations/nan_test.py +++ /dev/null @@ -1,167 +0,0 @@ -# ----------------------------------------------------------------------------- -# BSD 3-Clause License -# -# Copyright (c) 2020-2024, Science and Technology Facilities Council. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# ----------------------------------------------------------------------------- -# Author: J. Henrichs, Bureau of Meteorology -# Modified by: R. W. Ford, S. Siso and N. Nobre, STFC Daresbury Lab -# ----------------------------------------------------------------------------- - -''' Module containing tests for NanTestTrans and NanTestNode -''' - -from __future__ import absolute_import - -import pytest - -from psyclone.errors import InternalError -from psyclone.psyir.nodes import colored, Node, NanTestNode, Schedule -from psyclone.psyir.transformations import (NanTestTrans, - TransformationError) -from psyclone.tests.utilities import get_invoke -from psyclone.transformations import OMPParallelLoopTrans - - -# --------------------------------------------------------------------------- # -def test_extract_trans(): - '''Tests basic functions in NanTestTrans.''' - nan_test = NanTestTrans() - assert str(nan_test) == "Create a sub-tree of the PSyIR that has " \ - "a node of type NanTestNode at its root." - assert nan_test.name == "NanTestTrans" - - -# ----------------------------------------------------------------------------- -def test_malformed_extract_node(monkeypatch): - ''' Check that we raise the expected error if a NanTestNode does - not have a single Schedule node as its child. ''' - read_node = NanTestNode() - monkeypatch.setattr(read_node, "_children", []) - with pytest.raises(InternalError) as err: - _ = read_node.nan_test_body - assert "malformed or incomplete. It should have a " in str(err.value) - monkeypatch.setattr(read_node, "_children", [Node(), Node()]) - with pytest.raises(InternalError) as err: - _ = read_node.nan_test_body - assert "malformed or incomplete. It should have a " in str(err.value) - - -# ----------------------------------------------------------------------------- -def test_nan_test_basic(): - '''Check basic functionality: node names, schedule view. - ''' - _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", - "gocean", idx=0, dist_mem=False) - nan_test = NanTestTrans() - nan_test.apply(invoke.schedule[0].loop_body[0]) - result = invoke.schedule.view() - - # Create the coloured text (if required) - read_node = colored("NanTest", NanTestNode._colour) - sched_node = colored("Schedule", Schedule._colour) - assert f"""{sched_node}[] - 0: {read_node}[] - {sched_node}[]""" in result - - -# ----------------------------------------------------------------------------- -def test_nan_test_options(): - '''Check that options are passed to the NanTestNode and trigger - the use of the newly defined names. - ''' - _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", - "gocean", idx=0, dist_mem=False) - nan_test = NanTestTrans() - nan_test.apply(invoke.schedule[0].loop_body[0], - options={"region_name": ("a", "b")}) - code = str(invoke.gen()) - assert 'CALL nan_test_psy_data % PreStart("a", "b", 4, 2)' in code - - -# ----------------------------------------------------------------------------- -def test_invalid_apply(): - '''Test the exceptions that should be raised by NanTestTrans. - - ''' - _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", - "gocean", idx=0) - nan_test = NanTestTrans() - omp = OMPParallelLoopTrans() - omp.apply(invoke.schedule[0]) - with pytest.raises(TransformationError) as err: - nan_test.apply(invoke.schedule[0].dir_body[0], - options={"region_name": ("a", "b")}) - - assert "Error in NanTestTrans: Application to a Loop without its "\ - "parent Directive is not allowed." in str(err.value) - - with pytest.raises(TransformationError) as err: - nan_test.apply(invoke.schedule[0].dir_body[0].loop_body[0], - options={"region_name": ("a", "b")}) - - assert "Error in NanTestTrans: Application to Nodes enclosed within a "\ - "thread-parallel region is not allowed." in str(err.value) - - -# ----------------------------------------------------------------------------- -def test_nan_test_psyir_visitor(fortran_writer): - '''Check that options are passed to the NanTestNode and trigger - the use of the newly defined names. This test uses the FortranWriter - for creating output, which triggers a different code path - (it is based on lower_to_language_level). - - ''' - _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", - "gocean", idx=0, dist_mem=False) - - nan_test = NanTestTrans() - nan_test.apply(invoke.schedule, options={"region_name": ("a", "b")}) - - code = fortran_writer(invoke.schedule) - # Test only some of the lines to keep this test short: - expected = ['CALL nan_test_psy_data % PreStart("a", "b", 12, 4)', - 'CALL nan_test_psy_data % PreDeclareVariable("cv_fld%' - 'internal%xstart", cv_fld % internal % xstart)', - 'CALL nan_test_psy_data % PreDeclareVariable("ncycle", ' - 'ncycle)', - 'CALL nan_test_psy_data % PreEndDeclaration', - 'CALL nan_test_psy_data % ProvideVariable("ncycle", ncycle)', - 'CALL nan_test_psy_data % PreEnd', - 'CALL nan_test_psy_data % PostStart', - 'CALL nan_test_psy_data % ProvideVariable("cv_fld", cv_fld)', - 'CALL nan_test_psy_data % ProvideVariable("i", i)', - 'CALL nan_test_psy_data % ProvideVariable("j", j)', - 'CALL nan_test_psy_data % ProvideVariable("p_fld", p_fld)', - 'CALL nan_test_psy_data % PostEnd', - ] - - for line in expected: - assert line in code diff --git a/src/psyclone/tests/psyir/transformations/value_range_check_trans_test.py b/src/psyclone/tests/psyir/transformations/value_range_check_trans_test.py new file mode 100644 index 0000000000..fda6cca1b5 --- /dev/null +++ b/src/psyclone/tests/psyir/transformations/value_range_check_trans_test.py @@ -0,0 +1,201 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2020-2024, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Author: J. Henrichs, Bureau of Meteorology +# Modified by: R. W. Ford, S. Siso and N. Nobre, STFC Daresbury Lab +# ----------------------------------------------------------------------------- + +''' Module containing tests for ValueRangeCheckTrans and ValueRangeCheckNode +''' + +import pytest + + +from psyclone.errors import InternalError +from psyclone.psyir.nodes import colored, Node, ValueRangeCheckNode, Schedule +from psyclone.psyir.transformations import (ValueRangeCheckTrans, + TransformationError) +from psyclone.tests.utilities import get_invoke +from psyclone.transformations import OMPParallelLoopTrans + + +# --------------------------------------------------------------------------- +def test_value_range_check_trans(): + '''Tests basic functions in ValueRangeCheckTrans.''' + value_range = ValueRangeCheckTrans() + assert str(value_range) == ("Create a sub-tree of the PSyIR that has a " + "node of type ValueRangeCheckNode at its " + "root.") + assert value_range.name == "ValueRangeCheckTrans" + + +# ----------------------------------------------------------------------------- +def test_malformed_value_range_check_node(monkeypatch): + ''' Check that we raise the expected error if a ValueRangeCheckNode does + not have a single Schedule node as its child. ''' + value_range_check_node = ValueRangeCheckNode() + monkeypatch.setattr(value_range_check_node, "_children", []) + with pytest.raises(InternalError) as err: + _ = value_range_check_node.value_range_check_body + assert "malformed or incomplete. It should have a " in str(err.value) + monkeypatch.setattr(value_range_check_node, "_children", [Node(), Node()]) + with pytest.raises(InternalError) as err: + _ = value_range_check_node.value_range_check_body + assert "malformed or incomplete. It should have a " in str(err.value) + + +# ----------------------------------------------------------------------------- +def test_value_range_check_basic(): + '''Check basic functionality: node names, schedule view. + ''' + _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", + "gocean", idx=0, dist_mem=False) + value_range_check = ValueRangeCheckTrans() + value_range_check.apply(invoke.schedule[0].loop_body[0]) + result = invoke.schedule.view() + + # Create the coloured text (if required) + value_range_check_node = colored("ValueRangeCheck", + ValueRangeCheckNode._colour) + sched_node = colored("Schedule", Schedule._colour) + assert f"""{sched_node}[] + 0: {value_range_check_node}[] + {sched_node}[]""" in result + + +# ----------------------------------------------------------------------------- +def test_value_range_check_options(): + '''Check that options are passed to the ValueRangeCheckNode and trigger + the use of the newly defined names. + ''' + _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", + "gocean", idx=0, dist_mem=False) + value_range_check = ValueRangeCheckTrans() + value_range_check.apply(invoke.schedule[0].loop_body[0], + options={"region_name": ("a", "b")}) + code = str(invoke.gen()) + assert 'CALL value_range_check_psy_data % PreStart("a", "b", 4, 2)' in code + + +# ----------------------------------------------------------------------------- +def test_invalid_apply(): + '''Test the exceptions that should be raised by ValueRangeCheckTrans. + + ''' + _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", + "gocean", idx=0) + value_range_check = ValueRangeCheckTrans() + omp = OMPParallelLoopTrans() + omp.apply(invoke.schedule[0]) + with pytest.raises(TransformationError) as err: + value_range_check.apply(invoke.schedule[0].dir_body[0], + options={"region_name": ("a", "b")}) + + assert ("Error in ValueRangeCheckTrans: Application to a Loop without " + "its parent Directive is not allowed." in str(err.value)) + + with pytest.raises(TransformationError) as err: + value_range_check.apply(invoke.schedule[0].dir_body[0].loop_body[0], + options={"region_name": ("a", "b")}) + + assert ("Error in ValueRangeCheckTrans: Application to Nodes enclosed " + "within a thread-parallel region is not allowed." + in str(err.value)) + + +# ----------------------------------------------------------------------------- +def test_value_range_check_psyir_visitor(fortran_writer): + '''Check that options are passed to the ValueRangeCheckNode and trigger + the use of the newly defined names. This test uses the FortranWriter + for creating output, which triggers a different code path + (it is based on lower_to_language_level). + + ''' + _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", + "gocean", idx=0, dist_mem=False) + + value_range_check = ValueRangeCheckTrans() + value_range_check.apply(invoke.schedule, + options={"region_name": ("a", "b")}) + + code = fortran_writer(invoke.schedule) + # Test only some of the lines to keep this test short: + expected = ['CALL value_range_check_psy_data % PreStart("a", "b", 12, 4)', + 'CALL value_range_check_psy_data % PreDeclareVariable("cv_fld%' + 'internal%xstart", cv_fld % internal % xstart)', + 'CALL value_range_check_psy_data % PreDeclareVariable(' + '"ncycle", ncycle)', + 'CALL value_range_check_psy_data % PreEndDeclaration', + 'CALL value_range_check_psy_data % ProvideVariable("ncycle", ' + 'ncycle)', + 'CALL value_range_check_psy_data % PreEnd', + 'CALL value_range_check_psy_data % PostStart', + 'CALL value_range_check_psy_data % ProvideVariable("cv_fld", ' + 'cv_fld)', + 'CALL value_range_check_psy_data % ProvideVariable("i", i)', + 'CALL value_range_check_psy_data % ProvideVariable("j", j)', + 'CALL value_range_check_psy_data % ProvideVariable("p_fld", ' + 'p_fld)', + 'CALL value_range_check_psy_data % PostEnd', + ] + + for line in expected: + assert line in code + + +# ----------------------------------------------------------------------------- +def test_value_range_check_lfric(): + '''Check that the value range check transformation works in LFRic. + Use the old-style gen_code based implementation. + + ''' + psy, invoke = get_invoke("1.2_multi_invoke.f90", api="lfric", + idx=0, dist_mem=False) + + value_range_check = ValueRangeCheckTrans() + value_range_check.apply(invoke.schedule) + + code = str(psy.gen) + + # Test some lines - make sure that the number of variables is correct + # (first line), and some declaration and provide variable before and + # after the kernel: + expected = [ + 'CALL value_range_check_psy_data%PreStart("multi_invoke_psy", ' + '"invoke_0-r0", 20, 2)', + 'CALL value_range_check_psy_data%PreDeclareVariable("a", a)', + 'CALL value_range_check_psy_data%ProvideVariable("m1_data", m1_data)', + 'CALL value_range_check_psy_data%ProvideVariable("f1_data", f1_data)'] + + for line in expected: + assert line in code diff --git a/tutorial/practicals/LFRic/building_code/4_psydata/Makefile b/tutorial/practicals/LFRic/building_code/4_psydata/Makefile index 1a0876b0b2..084b71e028 100755 --- a/tutorial/practicals/LFRic/building_code/4_psydata/Makefile +++ b/tutorial/practicals/LFRic/building_code/4_psydata/Makefile @@ -41,12 +41,12 @@ default: @echo "Please use one of the transformation-specific Makefiles, e.g." @echo "make -f Makefile.extract_one" -PSYDATA_PATH?=$(PSYCLONE_RELPATH)/lib/nan_test/lfric -PSYDATA_LIB_NAME?=_nan_test +PSYDATA_PATH?=$(PSYCLONE_RELPATH)/lib/value_range_check/lfric +PSYDATA_LIB_NAME?=_value_range_check # Target for testing the tutorials with compilation (use only one PSyData wrapper) compile: - make -f solutions/Makefile.nan_all + make -f solutions/Makefile.value_range_check run: compile ./time_evolution @@ -58,7 +58,7 @@ transform: make clean make -f solutions/Makefile.extract_all time_evolution_alg_mod_psy.f90 make clean - make -f solutions/Makefile.nan_all time_evolution_alg_mod_psy.f90 + make -f solutions/Makefile.value_range_check time_evolution_alg_mod_psy.f90 make clean make -f solutions/Makefile.readonly_all time_evolution_alg_mod_psy.f90 diff --git a/tutorial/practicals/LFRic/building_code/4_psydata/Makefile.nan_all b/tutorial/practicals/LFRic/building_code/4_psydata/Makefile.value_range_check similarity index 93% rename from tutorial/practicals/LFRic/building_code/4_psydata/Makefile.nan_all rename to tutorial/practicals/LFRic/building_code/4_psydata/Makefile.value_range_check index 10c1dadb74..e30d79c99f 100755 --- a/tutorial/practicals/LFRic/building_code/4_psydata/Makefile.nan_all +++ b/tutorial/practicals/LFRic/building_code/4_psydata/Makefile.value_range_check @@ -33,9 +33,9 @@ # ------------------------------------------------------------------------------ # Author: J. Henrichs, Bureau of Meteorology -# Setting for the PSyData NAN check library -PSYDATA_PATH?=$(PSYCLONE_RELPATH)/lib/nan_test/lfric -PSYDATA_LIB_NAME?=_nan_test +# Setting for the PSyData value range check library +PSYDATA_PATH?=$(PSYCLONE_RELPATH)/lib/value_range_check/lfric +PSYDATA_LIB_NAME?=_value_range_check include Makefile.inc diff --git a/tutorial/practicals/LFRic/building_code/4_psydata/README.md b/tutorial/practicals/LFRic/building_code/4_psydata/README.md index 56329c95bf..6f33688fe7 100755 --- a/tutorial/practicals/LFRic/building_code/4_psydata/README.md +++ b/tutorial/practicals/LFRic/building_code/4_psydata/README.md @@ -68,7 +68,6 @@ Once this is done, you can then create your application using: make -f Makefile.extract_one - You need the NetCDF development package installed, the makefiles will be using ``nf-config`` to get the appropriate compiler and linker flags. Additionally, compiler and compiler flags can be provided @@ -257,24 +256,45 @@ Note that this error is only printed in the first time step. After the first mod the modified value is not changed again in the application, so no change to the read-only field is detected. -### NAN Verification -This library verifies that all input- and output-parameters of a kernel are -neither NAN nor an infinite number. If such a value is detected, a message -like this will be printed: +### ValueRangeCheck +The ``ValueRangeCheck`` library allows the user to specify that certain variables should be +within a specified range. This will be tested if the variables are an input parameter +to a kernel before the kernel is called, and after the kernel if the variable is an +output parameter of the kernel. Additionally, this library also verifies that all input- and +output-parameters of a kernel are neither NAN nor an infinite number. In case of an +out-of-range error or an invalid value, an error message like the following will be printed: + + PSyData: Variable 'perturbation_data' has the value 0.53116938666871878E-80 at index/indices 74932 in module 'time_evolution', region 'invoke_initialise_perturbation', which is not between '1.0000000000000000' and '2.0000000000000000'. + PSyData: Variable perturbation has the invalid value NaN at index/indices 11 -The transformation ``NanTestTrans`` is imported from ``psyclone.psyir.transformations``. -You can use the template ``nan_all_transform.py`` for your script, and ``Makefile.nan_all`` -for the makefile to use. +The transformation ``ValueRangeCheckTrans`` is imported from ``psyclone.psyir.transformations``. +You can use the template ``value_range_check_transform.py`` for your script, +and ``Makefile.value_range_check`` for the makefile to use. This example by itself will not print any message (since there is no invalid floating -point number), so you have to use ``PSYDATA_VERBOSE`` and set it to 1 or 2 to see that -tests are actually happening. Alternatively, the file -``prop_perturbation_kernel_mod.f90`` contains code that you can uncomment that will -introduce a NAN into the result field. Search for the comment +point number). In order to define a range for a variable (see +[the documentation](https://psyclone.readthedocs.io/en/stable/psy_data.html#value-range-check) +for details), set the following environment variable: + + PSYVERIFY__time_evolution__perturbation_data=0:4000 ./time_evolution + +This will check that each element of the perturbation variable has a value between 0 +and 4000 in the module ``time_evolution``. If the variable name is unique across +all program units, you can also just drop the module name and use: + + PSYVERIFY__perturbation_data=0:4000 ./time_evolution + +If you compile and run the job with this variable defines, a few elements +will trigger a message, e.g.: + + PSyData: Variable 'perturbation_data' has the value 4161.8196594729216 at index/indices 49361 in module 'time_evolution', region 'invoke_propagate_perturbation', which is not between '0.0000000000000000' and '4000.0000000000000'. + +Alternatively, the file ``prop_perturbation_kernel_mod.f90`` contains code that +you can uncomment that will introduce a NAN into the result field. Search for the comment ! FOR NAN VERIFICATION: diff --git a/tutorial/practicals/LFRic/building_code/4_psydata/solutions/Makefile.nan_all b/tutorial/practicals/LFRic/building_code/4_psydata/solutions/Makefile.value_range_check similarity index 81% rename from tutorial/practicals/LFRic/building_code/4_psydata/solutions/Makefile.nan_all rename to tutorial/practicals/LFRic/building_code/4_psydata/solutions/Makefile.value_range_check index ccac90fec7..1bfc2408f1 100755 --- a/tutorial/practicals/LFRic/building_code/4_psydata/solutions/Makefile.nan_all +++ b/tutorial/practicals/LFRic/building_code/4_psydata/solutions/Makefile.value_range_check @@ -33,15 +33,16 @@ # ------------------------------------------------------------------------------ # Author: J. Henrichs, Bureau of Meteorology -# Setting for the PSyData NAN check library -PSYDATA_PATH?=$(PSYCLONE_RELPATH)/lib/nan_test/lfric -PSYDATA_LIB_NAME?=_nan_test +# Setting for the PSyData value range check library +PSYDATA_PATH?=$(PSYCLONE_RELPATH)/lib/value_range_check/lfric +PSYDATA_LIB_NAME?=_value_range_check include Makefile.inc -time_evolution_alg_mod_psy.f90: time_evolution_alg_mod.x90 solutions/nan_all_transform.py - psyclone $(PSYCLONE_CMD) \ - -d . -d ../gungho_lib \ - -s ./solutions/nan_all_transform.py -opsy time_evolution_alg_mod_psy.f90 \ +time_evolution_alg_mod_psy.f90: time_evolution_alg_mod.x90 solutions/value_range_check_all_transform.py + psyclone $(PSYCLONE_CMD) \ + -d . -d ../gungho_lib \ + -s ./solutions/value_range_check_all_transform.py \ + -opsy time_evolution_alg_mod_psy.f90 \ -oalg time_evolution_alg_mod.f90 time_evolution_alg_mod.x90 diff --git a/tutorial/practicals/LFRic/building_code/4_psydata/solutions/nan_all_transform.py b/tutorial/practicals/LFRic/building_code/4_psydata/solutions/value_range_check_all_transform.py similarity index 84% rename from tutorial/practicals/LFRic/building_code/4_psydata/solutions/nan_all_transform.py rename to tutorial/practicals/LFRic/building_code/4_psydata/solutions/value_range_check_all_transform.py index ede506cc10..df4dbfddfd 100644 --- a/tutorial/practicals/LFRic/building_code/4_psydata/solutions/nan_all_transform.py +++ b/tutorial/practicals/LFRic/building_code/4_psydata/solutions/value_range_check_all_transform.py @@ -34,19 +34,16 @@ # Author: J. Henrichs, Bureau of Meteorology # Modified: R. W. Ford, STFC Daresbury Lab -'''Python script intended to be passed to PSyclone's generate() -function via the -s option. It adds NAN verification code to -the invokes. +'''Python script passed to the psyclone command via the -s option. It +adds ValueRangeCheck code to the invokes. ''' -from __future__ import print_function - -from psyclone.psyir.transformations import NanTestTrans +from psyclone.psyir.transformations import ValueRangeCheckTrans def trans(psy): ''' - Take the supplied psy object, and add NAN verification code. + Take the supplied psy object, and add value_range_check code. :param psy: the PSy layer to transform. :type psy: :py:class:`psyclone.psyGen.PSy` @@ -55,7 +52,7 @@ def trans(psy): :rtype: :py:class:`psyclone.psyGen.PSy` ''' - nan_check = NanTestTrans() + value_range_check = ValueRangeCheckTrans() for invoke_name in psy.invokes.names: @@ -65,8 +62,8 @@ def trans(psy): schedule = invoke.schedule # Apply the transformation - nan_check.apply(schedule, {"region_name": ("time_evolution", - str(invoke_name))}) + value_range_check.apply(schedule, {"region_name": ("time_evolution", + str(invoke_name))}) # Just as feedback: show the modified schedule, which should have # a new node at the top: diff --git a/tutorial/practicals/LFRic/building_code/4_psydata/nan_all_transform.py b/tutorial/practicals/LFRic/building_code/4_psydata/value_range_check_transform.py similarity index 91% rename from tutorial/practicals/LFRic/building_code/4_psydata/nan_all_transform.py rename to tutorial/practicals/LFRic/building_code/4_psydata/value_range_check_transform.py index e362e234cc..645a4dd504 100644 --- a/tutorial/practicals/LFRic/building_code/4_psydata/nan_all_transform.py +++ b/tutorial/practicals/LFRic/building_code/4_psydata/value_range_check_transform.py @@ -34,17 +34,14 @@ # Author: J. Henrichs, Bureau of Meteorology # Modified: R. W. Ford, STFC Daresbury Lab -'''Python script intended to be passed to PSyclone's generate() -function via the -s option. It adds NAN verification code to -the invokes. +'''Python script passed to the psyclone command via the -s option. It +adds ValueRangeCheck code to the invokes. ''' -from __future__ import print_function - def trans(psy): ''' - Take the supplied psy object, and add NAN verification code. + Take the supplied psy object, and add value_range_check code. :param psy: the PSy layer to transform. :type psy: :py:class:`psyclone.psyGen.PSy` @@ -76,5 +73,3 @@ def trans(psy): # Just as feedback: show the modified schedule, which should have # a new node at the top: print(schedule.view()) - - return psy