compile-time Fortran polymorphism with CMake

2 minute read

Related: Simple Fortran polymorphic procedures

Fortran polymorphism and generic programming (sets, lists, generators) are consistently the highest ranked survey feature requests for Fortran 202X. Fortran programmers introduce polymorphic procedures and variables into Fortran ≤ 2018 by:

  • C++ preprocessor overloading #ifdef etc.
  • using Fortran user-defined types, with duplicated procedures for each and every desired type/kind.
  • using include statements with generators

I primarily use the include method, as it is Fortran standard (since Fortran 90) and easy to accomplish from the command line. That is, it’s compile-time polymorphism. It’s not a perfect solution, and not true polymorphism since each procedure still requires exactly one type/kind per argument. However, virtually all of my programs use the same kind (number of bits) for each type of variable (real vs. integer). That is, either I’m using all real64 or real32 throughout. Rarely do I mix real kinds for procedure arguments. On the other hand, there are those with perfectly valid reasons to mix kinds in a program in certain cases, so this method wouldn’t help them. However, I think for a majority of Fortran programs, compile-time Fortran polymorphism is so easy to do, it’s worthwhile.

Compile-time Fortran polymorphism enables compiling different source files, which is often necessary where a procedure might accept real or complex inputs.

Compile-time Fortran polymorphic REAL

  1. in all your Fortran code, for each REAL variable and function, make kind=wp. For example polyreal.f90:
    program polyreal
     use, intrinsic:: iso_fortran_env
     implicit none
     include 'kind.txt'
     real(wp) :: pi,b
     integer :: i
     pi = 4._wp * atan(1._wp)
     b = timestwo(pi)
     print *,'pi',pi,'2pi',b
     elemental real(wp) function timestwo(a) result(b)
       real(wp), intent(in) :: a
       b = 2*a
     end function timestwo
    end program 
  2. make a command-line options -Drealkind=real64 or -Drealkind=real32 etc. by creating CMakeLists.txt:
    cmake_minimum_required (VERSION 3.1)
    project(realpoly Fortran)
    # Polymorphic-like real kind program-wide
        string(TOLOWER ${realkind} realkind)
    if(realkind STREQUAL real32)   # -Drealkind=real32
      file(WRITE kind.txt "integer,parameter :: wp=real32")
      message(STATUS "real kind: real32")
    elseif(realkind STREQUAL real64)
      file(WRITE kind.txt "integer,parameter :: wp=real64")
      message(STATUS "real kind: real64")
    elseif(realkind STREQUAL real128)
      file(WRITE kind.txt "integer,parameter :: wp=real128")
      message(STATUS "real kind: real128")
    else()  # default to real64
      file(WRITE kind.txt "integer,parameter :: wp=real64")
      message(STATUS "real kind: real64")
    # your modules and programs
    add_executable(polyrealdemo polyreal.f90)

    This creates file kind.txt which is included in a module common to all other modules and your program. In my programs, I typically have a comm.f90 that contains various constants including wp.

  3. Generate then build as usual:
    cmake -Drealkind=real64 ..

    pi 3.1415926535897931 2pi 6.2831853071795862

    That uses double-precision real64 variables and functions. The concept is trivially extensible to large programs consisting of many files and modules.

  4. To then select a different kind and rerun, perhaps to evaluate accuracy vs. runtime tradeoffs (real32 is generally faster than real64, but less accurate):
    cmake -Drealkind=real32 ..

    pi 3.14159274 2pi 6.28318548

    or for quad-precision Fortran real128:

    cmake -Drealkind=real128 ..

    pi 3.14159265358979323846264338327950280 2pi 6.28318530717958647692528676655900559

The contents of kind.txt are simply like:

integer,parameter :: wp=real64

Real and Complex compile-time polymorphism

This is shown by example in signal_subspace. Let me know if you’d like more detailed explanation. It includes different procedures for real vs. complex.


Leave a comment