Fortran Notes

Started: 04 Mar 2025
Updated: 05 Mar 2025

Collection of Fortran notes.

During my master’s studies, I worked with a large Fortran 77 codebase that many avoided due to its age and complexity. Despite its intimidating size, especially for someone at my experience level, I was intrigued by the challenge of porting it to Python using modern libraries. To do so, I first needed to understand how the original Fortran code functioned, which meant learning to read Fortran before attempting to write it.

Over time, I became familiar with its structure, syntax, and quirks. This foundational experience proved valuable during my doctoral studies, where I encountered a substantial Fortran 90 codebase. Compared to Fortran 77, Fortran 90 offers a more refined and readable syntax, particularly for those with a background in Python.

Below are some key Fortran concepts I found especially useful:

Compiling and linking in Fortran

Assume we have a module b.f90 that contains a functionality that is used in the main program. To linked this to the main, do the following steps.

  1. compile a module file *.mod
  2. linked it to the main program
gfortran -c b.f90   ! b.o and module_name.mod is created 
gfortran main.f90 b.o ! a.out is created 

Arrays

  integer, parameter :: V = 200, L = 1000
  integer :: array1(V)
  real :: vector(0:L)
  real :: matrix(V,L)
  ! or 
  integer, dimension(V) :: array1 
  real, dimension(0:L) :: vector 
  real, dimension(M, N) :: matrix 
  

Dynamic memory allocation

  integer :: allocatable :: M(:,:) 
  or 
  integer :: err
  allocate(M(4,5), err=err)
  if (err /=0 ) STOP
  ...
  deallocate(M)
  

Intrinsic array functions

Functions, Subroutine, Modules,

Dividing a complicated computational tasks into modular form of procedures is a common task in any programming language. In Fortran, this can be achieve by using the following programm unit: functions, subroutine, and modules. Each of these has a purpose and should be used properly for efficiency and speed.

FUNCTIONS

supplying the functions:

  1. functions
    • internal - the use of CONTAINS
    • external
      • same file :
        • the use of INTERFACE
      • different file
        • the use of USE : regarded as a re-declaratioin of all the modules entities inside the local scoping unit, with exactly the same names and properties [4].
    • optional arguments
      • either in the positional lists or in the keyword list
    • procedures as arguments

2 ways to define a FUNCTON:

function distance(p,q)
real :: distance 
...
end function distance 
! or 
real function distance(p,q)
...
end function distance 
! or 
function distance(p,q) result(r)
real :: r
...
end function
subroutine mincon(n, f, x, upper, lower, equalities, inequalities, convex, xstart)
real, optional, dimension(x) :: upper, lower
...
end subroutine mincon 

! and we may call it using 
call mincon(n, f, x, upper)

! another way to set an optional argument
call mincon(n, f, x, equalities=q, xstart=x0)

SUBROUTINE

CONTAINS, USE

module example 
    type type1 
    contains 
        procedure, nopass :: static_method1 
    end type type1 
    type type2
    contains 
        procedure, nopass :: static_method1 
    end type type2 
end module example 

use example  ! all public objects in namespace example available 
use example, only: type1 ! class prototype type1 available, type2 not available 
use example, mytype => type1 ! within this scoping unit, type1 is referred to as "mytype" 

INTENT

program swap
implicit none 
real :: a=1, b=2

print *, "Before calling swap fxn:", a, b
CALL swap_num(a,b)
print *, "After calling swap fxn: ", a, b

contains 
    subroutine swap_num(a,b)
        real, intent(inout) :: a, b 
         real :: temp 
         temp = a
         a = b 
         b = temp
    end subroutine swap_num4
end program swap   

this will print:

 Before calling swap fxn:   1.00000000       2.00000000
 After calling swap fxn:    2.00000000       1.00000000

INTERFACE

program square_a_number 
implicit none  
INTERFACE 
    real function squared_num(a) 
     real, intent(in) :: a 
   end function squared_num 
END INTERFACE
real :: a=2 
print *, squared_num(a) 
end program square_a_number  
    
real function squared_num(a) 
real, intent(in) :: a  
    squared_num = a*a 
end function squared_num 

MODULE

module Circle 
    real, parameter :: pi = 3.1415925 
    real :: r
end module Circle


program Area 
use Circle, only : r
implicit none 

interface 
    function area_circle(r) 
        real, intent(in) :: r 
    end function area_circle 
end interface 

! prompt user for radius of circle 
write(*, '(A)', ADVANCE = "NO") "Enter the radius of the circle: "
read(*,*) r

! write out the area of circle using function call 
write(*, 100) "Area of circle with radius", r, " is", area_circle(r)
100 format (A, 2x, F6.2, A, 2x, F12.2)
end program Area

function area_circle(r)
use Circle, only: pi 
implicit none 
real :: area_circle 
real, intent(in) :: r 

area_circle = pi*r*r 
end function area_circle 

Putting it all together

!main program 
program test
    use sorts 
    type(length) :: s = length(10.0), l
    type(length_squared) :: s2 = length_squared(10.0)
    type(velocity) :: v 
    type(time) :: t = time(3.0)
    v = s/t 
! Note: v = s + t or v = s*t would be illegal 
    t = t + time(1.0)
    l = sqrt(s2)
    print *, v, t, l 
end program test

sorts.f95 code :

module sorts
    type time
        real :: seconds 
    end type time 

    type velocity
        real :: metres_per_second 
    end type velocity

    type length 
        real :: metres 
    end type length 

    type length_squared
        real :: metres_squared 
    end type length_squared 

    interface operator(/)
        module procedure length_by_time 
    end interface 

    interface operator(+)
        module procedure time_plus_time
    end interface 

    interface sqrt 
        module procedure sqrt_metres_squared 
    end interface 

contains 
    function length_by_time(s, t)
        type(length), intent(in) :: s 
        type(time), intent(in) :: t 
        type(velocity) :: length_by_time 
        length_by_time%metres_per_second = s%metres / t%seconds 
    end function length_by_time 

    function time_plus_time(t1, t2) 
        type(time), intent(in) :: t1, t2 
        type(time) :: time_plus_time
        time_plus_time%seconds = t1%seconds + t2%seconds 
    end function time_plus_time

    function sqrt_metres_squared(l2)
        type(length_squared), intent(in) :: l2 
        type(length) :: sqrt_metres_squared
        sqrt_metres_squared%metres = sqrt(l2%metres_squared)
    end function sqrt_metres_squared

end module sorts

Object-oriented programming

Fortran doesnt have “notion on separate instances of a module. In order to obtain class-like behaviour, one can combine a module, which contains the methods that operate on the class, with derived type containing the data.” [2].

The “instances” comes from the derived data type being allocated multiple times which can used as arguments to the methods within that module.

{“class” module is made up of (derived data type) + methods on this derived data type} ==> {one can create instances of this derived data type and call the methods on this derived data type}

References

  1. annefou.github.io/Fortran/classes/classes.html
  2. fortranwiki.org/fortran/show/Object+oriented+programming
  3. SELECT TYPE -IBM
  4. http://kea.princeton.edu/che422/arrays.htm
  5. https://fortran-lang.org/en/learn/building_programs/linking_pieces/
  6. https://pages.mtu.edu/~shene/COURSES/cs201/
  7. https://web.stanford.edu/class/me200c/tutorial_90/
  8. http://www.lahey.com/docs/lfenthelp/NLMARUSEStmt.htm
  9. Fortran 95/2003 explained
  10. web.stanford.edu/class/me200c/tutorial_90/09_modules.html