Skip to main content

Contributing

Guidelines for contributing to FPL.

Requirements

  • Fortran 90 only — no F2003+ features (class, type-bound procedures, abstract interfaces, etc.)
  • GFortran (any recent version)
  • NetCDF-Fortran libraries

Code Conventions

Intent Declarations

All subroutine/function arguments must have intent specified:

subroutine example(input, output, both)
integer, intent(in) :: input
integer, intent(out) :: output
integer, intent(inout) :: both

Allocation Safety

Always use stat= with allocate / deallocate and check with check_alloc:

integer :: istat
allocate(array(n), stat=istat)
call check_alloc(istat, "array")

Loop Order

Fortran uses column-major storage. Always iterate with the first index in the innermost loop:

! Correct
do j = 1, nlats
do i = 1, nlons
data(i, j) = ...
end do
end do

! Wrong — causes cache misses
do i = 1, nlons
do j = 1, nlats
data(i, j) = ...
end do
end do

No system() Calls

Use pure Fortran I/O instead of call system(...). See print_filename in FPL_checkerror.f90 for an example.

Type Naming

Follow the existing convention:

nc{D}d_{type}_{coord}[_t{ttype}][_l{ltype}]

Workflow

  1. Fork the repository
  2. Create a feature branch from dev
  3. Write your changes following the conventions above
  4. Test by compiling the library and running the examples
  5. Submit a pull request to dev

Building & Testing

make          # Build libFPL.so
make examples # Compile example programs

Code Generation

Source files are generated using CPP preprocessor templates. The system has two layers:

  1. Template files (src/templates/*.inc) — Fortran source with CPP macro placeholders, maintained manually
  2. Generator script (src/generate_cpp.py) — Produces .f90 files with #define/#include/#undef blocks

If you modify a template or add a new data type variant, regenerate and rebuild:

python3 src/generate_cpp.py
make clean && make

See the Code Generation section below for details on the template system.

Code Generation Architecture

Pipeline Overview

C4 — Code Generation Pipeline

How it works

Each .inc template file contains a single Fortran subroutine (or type definition) with CPP macro placeholders instead of concrete type names:

! src/templates/dealloc_2d.inc
subroutine FPL_SUBR(idata)
type(FPL_TYPE), intent(inout) :: idata
integer :: alloc_stat
deallocate(idata%longitudes, idata%latitudes, idata%ncdata, stat=alloc_stat)
deallocate(idata%dimid, idata%dimsize, idata%dimname, idata%dimunits, idata%varids, stat=alloc_stat)
end subroutine FPL_SUBR

The generator script (generate_cpp.py) produces .f90 files that instantiate each template for every type combination:

! Generated in FPL_dealloc.f90
#define FPL_SUBR dealloc2d_byte_llf
#define FPL_TYPE nc2d_byte_llf
#include "templates/dealloc_2d.inc"
#undef FPL_SUBR
#undef FPL_TYPE

#define FPL_SUBR dealloc2d_byte_lld
#define FPL_TYPE nc2d_byte_lld
#include "templates/dealloc_2d.inc"
#undef FPL_SUBR
#undef FPL_TYPE
...

CPP macros used

MacroUsed inDescription
FPL_SUBRAll except datatypesSubroutine name
FPL_TYPEAllDerived type name (e.g., nc2d_byte_llf)
FPL_FILL_DECLdatatypesFill value declaration (e.g., integer(kind=byte))
FPL_COORD_KINDdatatypesCoordinate kind (e.g., float, double)
FPL_TIME_DECLdatatypes (3D/4D)Time declaration (e.g., integer(kind=intgr))
FPL_LEVEL_DECLdatatypes (4D)Level declaration (e.g., real(kind=float))
FPL_LABELreadgridType label string for error messages
FPL_NF90_VARTYPEgengridNetCDF variable type constant
FPL_NF90_COORDwritegridNetCDF coordinate type
FPL_NF90_TIMEwritegrid (3D/4D)NetCDF time type
FPL_NF90_LEVELwritegrid (4D)NetCDF level type
FPL_MASK_TYPEsetfillvalueMask type (always 2D)
FPL_MAP_TYPEsetfillvalueMap type (matches subroutine dimensionality)
FPL_ZONE_TYPEzonalstatsZone/classification grid type (always 2D byte)
FPL_DATA_TYPEzonalstatsData grid type (matches subroutine dimensionality)

Template files

TemplateMacros required
datatypes_{2d,3d,4d}.incFPL_TYPE, FPL_FILL_DECL, FPL_COORD_KIND [+FPL_TIME_DECL] [+FPL_LEVEL_DECL]
dealloc_{2d,3d,4d}.incFPL_SUBR, FPL_TYPE
griddims_{2d,3d,4d}.incFPL_SUBR, FPL_TYPE
readgrid_{2d,3d,4d}.incFPL_SUBR, FPL_TYPE, FPL_LABEL
writegrid_{2d,3d,4d}.incFPL_SUBR, FPL_TYPE, FPL_NF90_COORD [+FPL_NF90_TIME] [+FPL_NF90_LEVEL]
gengrid_{2d,3d,4d}.incFPL_SUBR, FPL_TYPE, FPL_NF90_VARTYPE
setfillvalue_{2d,3d,4d}.incFPL_SUBR, FPL_MASK_TYPE, FPL_MAP_TYPE
zonalstats_{2d,3d,4d}.incFPL_SUBR, FPL_ZONE_TYPE, FPL_DATA_TYPE

Adding a new template

To add a new module (e.g., interpolate):

  1. Create the template files in src/templates/:

    src/templates/interpolate_2d.inc
    src/templates/interpolate_3d.inc
    src/templates/interpolate_4d.inc

    Use CPP macros (FPL_SUBR, FPL_TYPE, etc.) as placeholders.

  2. Add a generator function in src/generate_cpp.py. For simple modules that only need FPL_SUBR and FPL_TYPE:

    gen_simple("interpolate", "interpolate")

    For modules that need extra macros, create a dedicated function (see gen_writegrid(), gen_setfillvalue(), or gen_zonalstats() as examples).

  3. Add the #include in gen_fpl() inside generate_cpp.py:

    #include "FPL_interpolate.f90"
  4. Add the interface in gen_interfaces() inside generate_cpp.py.

  5. Regenerate and rebuild:

    python3 src/generate_cpp.py
    make clean && make