Tools and utilities for integrating icon4py code into the ICON model.
To install icon4pytools
in a virtual environment, one can use pip with either the requirements-dev.txt
or requirements.txt
file. While the requirements.txt
file will install the package along with its runtime dependencies, the requirements-dev.txt
file additionally includes development dependencies required for running tests, generating documentation, and building the package from source. Furthermore by using the requirements-dev.txt
file, the package will be installed in editable mode, allowing the user to make changes to the package's source code and immediately see the effects without having to reinstall the package every time. This is particularly useful during development and testing phases.
# create a virtual environment
python3 -m venv .venv
# activate the virtual environment
source .venv/bin/activate
# install all dependencies
pip install -r requirements-dev.txt
A variety of command-line tools are available in the shell after installation of icon4pytools
.
A bindings generator that generates C++ and Fortran bindings from Gt4Py programs. This tools generates the following code:
- GridTools C++
gridtools::fn
header file (.hpp
). - A Fortran interface file containing all wrapper functions which can be called from within ICON (
.f90
). - A corresponding C (
.cpp, .h
) interface which in turn calls the Gridtools C++ code, as well as the C++ verification utils.
Usage: icon4pygen [OPTIONS] FENCIL [BLOCK_SIZE] [LEVELS_PER_THREAD] [OUTPATH]
Generate Gridtools C++ code for an icon4py fencil as well as all the associated C++ and Fortran bindings.
Arguments:
FENCIL: may be specified as <module>:<member>, where <module>
is the dotted name of the containing module and <member> is the name of the fencil.
BLOCK_SIZE: refers to the number of threads per block to use in a cuda kernel.
LEVELS_PER_THREAD: how many k-levels to process per thread.
OUTPATH: represents a path to the folder in which to write all generated code.
Options:
--is_global Whether this is a global run.
--imperative Whether to use the imperative code generation backend.
--help Show this message and exit.
In order to turn on autocomplete for the available fencils in your shell for icon4pygen
you need to execute the following in your shell:
eval "$(_ICON4PYGEN_COMPLETE=bash_source icon4pygen)"
To permanently enable autocomplete on your system add the above statement to your ~/.bashrc
file.
A preprocessor that facilitates integration of GT4Py code into the ICON model. icon_liskov
is a CLI tool which takes a fortran file as input and processes it with the ICON-Liskov DSL Preprocessor, generating code and inserting that into the target output file.
icon_liskov
can either operate in integration or serialisation mode. In integration mode liskov generates calls to Fortran wrapper functions which enable calling icon4py DSL code inside of ICON from Fortran. In serialisation mode ppser serialbox statements are generated allowing the serialisation of all variables in all stencils decorated with liskov directives.
icon_liskov integrate [--profile] [--metadatagen] <input_filepath> <output_filepath>
Options:
profile
: adds nvtx profile statements to the stencils (optional).metadatagen
: generates a metadata header at the top of the file which includes information on icon_liskov such as the version used.
icon_liskov serialise [--multinode] <input_filepath> <output_filepath>
Options:
multinode
: ppser init contains the rank of the MPI process to facilitate writing files in a multinode context.
Note: By default the data will be saved at the default folder location of the currently run experiment and will have a prefix of liskov-serialisation
.
The ICON-Liskov DSL Preprocessor supports the following directives:
This directive generates the necessary USE
statements to import the Fortran to C interfaces.
This directive generates an OpenACC DATA CREATE
statement. The directive requires an optional keyword argument to specify extra fields to include in the DATA CREATE
statement called extra_fields
. Here you can specify a comma-separated list of strings which should be added to the DATA CREATE
statement as follows extra_fields=foo,bar
.
This directive generates an OpenACC END DATA
statement which is neccessary to close the OpenACC data region.
This directive is used to declare all DSL input/output fields. The required arguments are the field name and its associated dimensions. For example:
!$DSL DECLARE(vn=(nproma, p_patch%nlev, p_patch%nblks_e))
will generate the following code:
! DSL INPUT / OUTPUT FIELDS
REAL(wp), DIMENSION((nproma, p_patch%nlev, p_patch%nblks_e)) :: vn_before
Furthermore, this directive also takes two optional keyword arguments. type
takes a string which will be used to fill in the type of the declared field, for example type=LOGICAL
. suffix
takes a string which will be used as the suffix of the field e.g. suffix=dsl
, by default the suffix is before
.
This directive denotes the start of a stencil. Required arguments are name
, vertical_lower
, vertical_upper
, horizontal_lower
, horizontal_upper
. The value for name
must correspond to a stencil found in one of the stencil modules inside icon4py
, and all fields defined in the directive must correspond to the fields defined in the respective icon4py stencil. Optionally, absolute and relative tolerances for the output fields can also be set using the _tol
or _abs
suffixes respectively. For each stencil, an ACC DATA region will be created. This ACC DATA region contains the before fileds of the according stencil. An example call looks like this:
!$DSL START STENCIL(name=mo_nh_diffusion_stencil_06; &
!$DSL z_nabla2_e=z_nabla2_e(:,:,1); area_edge=p_patch%edges%area_edge(:,1); &
!$DSL fac_bdydiff_v=fac_bdydiff_v; vn=p_nh_prog%vn(:,:,1); vn_abs_tol=1e-21_wp; &
!$DSL vertical_lower=1; vertical_upper=nlev; &
!$DSL horizontal_lower=i_startidx; horizontal_upper=i_endidx)
In addition, other optional keyword arguments are the following:
-
accpresent
: Takes a boolean string input, and controls the default data-sharing behavior for variables used in the OpenACC parallel region. Setting the flag to true will cause all variables to be assumed present on the device by default (DEFAULT(PRESENT)
), and no explicit data-sharing attributes need to be specified. Setting it to false will require explicit data-sharing attributes for every variable used in the parallel region (DEFAULT(NONE)
). By default it is set to false. -
mergecopy
: Takes a boolean string input. When set to True consecutive before field copy regions of stencils that have the mergecopy flag set to True are combined into a single before field copy region with a new name created by concatenating the names of the merged stencil regions. This is useful when there are consecutive stencils. By default it is set to false. -
copies
: Takes a boolean string input, and controls whether before field copies should be made or not. If set to False only the#ifdef __DSL_VERIFY
directive is generated. Defaults to true. -
optional_module
: Takes a boolean string input, and controls whether stencils is part of an optional module. Defaults to "None".
This directive denotes the end of a stencil. The required argument is name
, which must match the name of the preceding START STENCIL
directive.
Together, the START STENCIL
and END STENCIL
directives result in the following generated code at the start and end of a stencil respectively.
!$ACC DATA CREATE( &
!$ACC vn_before)
#ifdef __DSL_VERIFY
!$ACC KERNELS DEFAULT(NONE) ASYNC(1)
vn_before(:, :, :) = vn(:, :, :)
!$ACC END KERNELS
call nvtxEndRange()
#endif
call wrap_run_mo_nh_diffusion_stencil_06( &
z_nabla2_e=z_nabla2_e(:, :, 1), &
area_edge=p_patch%edges%area_edge(:, 1), &
fac_bdydiff_v=fac_bdydiff_v, &
vn=p_nh_prog%vn(:, :, 1), &
vn_before=vn_before(:, :, 1), &
vn_abs_tol=1e-21_wp, &
vertical_lower=1, &
vertical_upper=nlev, &
horizontal_lower=i_startidx, &
horizontal_upper=i_endidx
)
!$ACC END DATA
Additionally, there are the following keyword arguments:
-
noendif
: Takes a boolean string input and controls whether an#endif
is generated or not. Defaults to false. -
noprofile
: Takes a boolean string input and controls whether a nvtx end profile directive is generated or not. Defaults to false. -
noaccenddata
: Takes a boolean string input and controls whether a!$ACC END DATA
directive is generated or not. Defaults to false.
This directive denotes the start of a fused stencil. Required arguments are name
, vertical_lower
, vertical_upper
, horizontal_lower
, horizontal_upper
. The value for name
must correspond to a stencil found in one of the stencil modules inside icon4py
, and all fields defined in the directive must correspond to the fields defined in the respective icon4py stencil. Optionally, absolute and relative tolerances for the output fields can also be set using the _tol
or _abs
suffixes respectively. For each stencil, an ACC ENTER/EXIT DATA statements will be created. This ACC ENTER/EXIT DATA region contains the before fileds of the according stencil. An example call looks like this:
!$DSL START FUSED STENCIL(name=calculate_diagnostic_quantities_for_turbulence; &
!$DSL kh_smag_ec=kh_smag_ec(:,:,1); vn=p_nh_prog%vn(:,:,1); e_bln_c_s=p_int%e_bln_c_s(:,:,1); &
!$DSL geofac_div=p_int%geofac_div(:,:,1); diff_multfac_smag=diff_multfac_smag(:); &
!$DSL wgtfac_c=p_nh_metrics%wgtfac_c(:,:,1); div_ic=p_nh_diag%div_ic(:,:,1); &
!$DSL hdef_ic=p_nh_diag%hdef_ic(:,:,1); &
!$DSL div_ic_abs_tol=1e-18_wp; vertical_lower=2; &
!$DSL vertical_upper=nlev; horizontal_lower=i_startidx; horizontal_upper=i_endidx)
This directive denotes the end of a fused stencil. The required argument is name
, which must match the name of the preceding START STENCIL
directive.
Note that each START STENCIL
and END STENCIL
will be transformed into a DELETE
section, when using the --fused
mode.
Together, the START FUSED STENCIL
and END FUSED STENCIL
directives result in the following generated code at the start and end of a stencil respectively.
!$ACC DATA CREATE( &
!$ACC kh_smag_e_before, &
!$ACC kh_smag_ec_before, &
!$ACC z_nabla2_e_before )
#ifdef __DSL_VERIFY
!$ACC KERNELS DEFAULT(PRESENT) ASYNC(1)
kh_smag_e_before(:, :, :) = kh_smag_e(:, :, :)
kh_smag_ec_before(:, :, :) = kh_smag_ec(:, :, :)
z_nabla2_e_before(:, :, :) = z_nabla2_e(:, :, :)
!$ACC END KERNELS
call wrap_run_calculate_diagnostic_quantities_for_turbulence( &
kh_smag_ec=kh_smag_ec(:, :, 1), &
vn=p_nh_prog%vn(:, :, 1), &
e_bln_c_s=p_int%e_bln_c_s(:, :, 1), &
geofac_div=p_int%geofac_div(:, :, 1), &
diff_multfac_smag=diff_multfac_smag(:), &
wgtfac_c=p_nh_metrics%wgtfac_c(:, :, 1), &
div_ic=p_nh_diag%div_ic(:, :, 1), &
div_ic_before=div_ic_before(:, :, 1), &
hdef_ic=p_nh_diag%hdef_ic(:, :, 1), &
hdef_ic_before=hdef_ic_before(:, :, 1), &
div_ic_abs_tol=1e-18_wp, &
vertical_lower=2, &
vertical_upper=nlev, &
horizontal_lower=i_startidx, &
horizontal_upper=i_endidx)
!$ACC EXIT DATA DELETE( &
!$ACC div_ic_before, &
!$ACC hdef_ic_before )
This directive allows the user to generate any text that is placed between the parentheses.
This is useful for situations where custom code generation is necessary.
Note that, the INSERT`` statement is verbatim, such that there is no filtering or fortran formatting. Also, that line continuation with
& is not provided for
INSERT` statements.
This directive allows generating an nvtx start profile data statement, and takes the stencil name
as an argument.
This directive allows generating an nvtx end profile statement.
This directive allows to disable code. The code is only disabled if both the fused mode and the substition mode are enabled.
The START DELETE
indicates the starting line from which on code is deleted.
This directive allows to disable code. The code is only disabled if both the fused mode and the substition mode are enabled.
The END DELETE
indicates the ending line from which on code is deleted.
This directive generates an #endif
statement.
Check out tools/docs/ICON_Liskov_integration_style_guide.md
for having a unique look and feel in the fortran integration.
This tool is designed to parse a well-defined Fortran granule interface and generate ppser statements for each variable in the interface. It uses the f2py
library to perform the parsing and liskov
for the generation tasks.
f2ser [OPTIONS] GRANULE_PATH OUTPUT_FILEPATH
GRANULE_PATH A path to the Fortran source file to be parsed.
OUTPUT_FILEPATH A path to the output Fortran source file to be generated.
--dependencies PATH Optional list of dependency paths.
--directory TEXT The directory to serialise the variables to.
--prefix TEXT The prefix to use for each serialised variable.
Note: The output of f2ser still has to be preprocessed using pp_ser.py
, which then yields a compilable unit. The serialised files will have f2ser
as their prefix in the default folder location of the experiment.
py2fgen
is a command-line interface (CLI) tool designed to generate C and Fortran 90 (F90) wrappers, as well as a C library, for embedding a Python module into C and Fortran applications. This tool facilitates the embedding of Python code into Fortran programs by utilizing the CFFI
library. CFFI
instantiates a Python interpreter to execute Python code which is "frozen" into the dynamic library generated by CFFI
.
Note: py2fgen
has been used to embed the diffusion and dycore granule into ICON. It is important to remember that there are performance implications related to converting Fortran pointers to array-like objects that can be used in Python. It is also important to note that functions embedded into Fortran can only accept arguments with intrinsic types, as well as arrays. It is currently not possible to pass derived types to embedded Python functions.
py2fgen
simplifies the process of embedding Python functions into C and Fortran codebases. Here's how to use it:
py2fgen [OPTIONS] MODULE_IMPORT_PATH FUNCTION_NAME
Arguments:
MODULE_IMPORT_PATH The Python module import path to the module where the functions to be embedded are defined.
FUNCTIONS A comma-separated list of functions to be embedded in the case of multiple, otherwise just the function name.
PLUGIN_NAME The name of the plugin used for creating the shared library and bindings.
Options:
-o, --output-path PATH Specify the directory for generated code and
compiled libraries.
-b, --backend [CPU|GPU|ROUNDTRIP]
Set the backend to use, thereby unpacking
Fortran pointers into NumPy or CuPy arrays
respectively.
-d, --debug-mode Enable debug mode to log additional Python
runtime information.
-p, --profile Profile granule runtime and unpacking
Fortran pointers into NumPy or CuPy arrays.
--limited-area Enable limited area mode.
When embedding granules it may be necessary to have access to the representation of the ICON grid inside the granule. In order to initialise the grid for each granule there exists a grid_init_<granule_name>
function which must also be embedded and called from Fortran. Each granule has access to a module state which is defined in a dictionary at the top of the module, for example diffusion_wrapper_state
in the diffusion wrapper.
In order to run the embedded code from Fortran it is necessary to set environment variables based on whether you are running in a CPU or GPU context. For more information on these, as well as other information on how to build and integrate embedded code into ICON using py2fgen see this document.
To create a Fortran interface along with the dynamic library for a Python function named square
within the module example.functions
, execute:
py2fgen example.functions square
It is also possible to generate bindings for more than one function at a time by using a comma-separated list of function names:
py2fgen example.functions square,square2
py2fgen
can accept two types of functions:
- Simple Function: Any Python function can be exposed.
- GT4Py Program: Specifically, a Python function decorated with a
@program
decorator.
Important: All arguments in the exposed functions must use GT4Py style type hints. These are used by the parser to map GT4Py types to C and Fortran types in the generated bindings.
Running py2fgen
generates five key files:
- .c File: Contains the generated CFFI code and the frozen Python code.
- .so File: The compiled dynamic C library containing the CFFI code.
- .h File: Declares the function signature of your exposed function.
- .f90 File: Contains a Fortran interface to the C function in the dynamic library.
- .o File: Represents the object code of the CFFI plugin.
- (Optional) .py File: Contains the Python code frozen into the dynamic library (available with
--debug-mode
).
To use the generated CFFI plugin in a Fortran program, call the subroutine defined in the .f90
interface file. Ensure that any arrays passed to the subroutine are in column-major order.
Examples can be found under tools/tests/py2fgen/fortran_samples
.
Compiling your Fortran driver code requires a Fortran compiler, such as gfortran
or nvfortran
. Follow these steps:
- Compile and link the Fortran driver code along with the Fortran interface and dynamic library:
gfortran -I. -Wl,-rpath=. -L. <function_name>_plugin.f90 <fortran_driver>.f90 -l<function_name>_plugin -o <executable_name>
Replace <function_name>
, <fortran_driver>
, and <executable_name>
with the appropriate names for your project.
Note: When executing the compiled binary make sure that you have sourced a Python virtual environment where all required dependencies to run the embedded Python code are present.
All generated Python wrapper code is wrapped in a try: ... except: ...
block, with the wrapper function returning an error code 1
if an Exception ocurred
and 0
otherwise. In case of an exception the error message is written to the py2f_cffi.log
file, which is located in the same directory as the generated bindings.
This means that on the Fortran side we can handle errors gracefully as follows:
integer(c_int) :: rc
real(c_double), dimension(:, :), allocatable :: input, result
call square(input, result, rc)
! handle the Python error here
if (rc /= 0) then
print *, "Python failed with exit code = ", rc
call exit(1)
end if
- Embedded Python functions must have type hints for all function parameters, as these are used to derive the corresponding C and Fortran types.
- Embedded Python functions are assumed to modify function parameters in-place. Explicitly returning anything is currently not supported.