Fortran polymorphism with CMake

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 modern Fortran by:

  • C++ preprocessor #ifdef etc. (simplest, not officially part of language (or truly polymorphism), but very widely supported and used)
  • Fortran 2003 static procedure polymorphism is simple to use and is standard Fortran language feature.
  • using Fortran user-defined types, with duplicated procedures for each and every desired type/kind. This is more verbose to use, but it is the most powerful / flexible true Fortran polymorphism.

The preprocessor method might be thought of as 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, this technique combined with static polymorphism is simple to develop and handles many real-life use cases quickly and easily.

Compile-time Fortran polymorphic REAL

  1. in all your Fortran code, for each REAL variable and function, make kind=wp. For example polyreal.F90 (notice the capital F90):
    program polyreal
       
    use, intrinsic:: iso_fortran_env
    implicit none
         
    #if REALBITS==32
    integer,parameter :: wp=real32
    #elif REALBITS==64
    integer,parameter :: wp=real64
    #elif REALBITS==128
    integer,parameter :: wp=real128
    #endif
       
    real(wp) :: pi,b
    integer :: i
         
    pi = 4._wp * atan(1._wp)
         
    b = timestwo(pi)
         
    print *,'pi',pi,'2pi',b
       
    contains
       
    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 -Drealbits=64 or -Drealbits=32 etc. in CMakeLists.txt:
    cmake_minimum_required(VERSION 3.1)
    project(realpoly Fortran)
    
    if(NOT realbits)
      set(realbits 64)
    endif()
    
    # your modules and programs
    add_executable(poly polyreal.f90)
    target_compile_definitions(poly PRIVATE REALBITS=${realbits})
    

    We typically have a comm.f90 that contains various constants including wp used throughout a program.

  3. Generate then build as usual:
    cmake -Drealbits=64 ..
       
    make
       
    ./poly
    

    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 -Drealbits=32 ..
        
    make
        
    ./poly
    

    pi 3.14159274 2pi 6.28318548

    or for quad-precision Fortran real128:

    cmake -Drealbits=128 ..
        
    make
        
    ./poly
    

    pi 3.14159265358979323846264338327950280 2pi 6.28318530717958647692528676655900559

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.

Notes

Tags: ,

Categories: ,

Written by Michael Hirsch, Ph.D. //

Comments