Skip to content

Latest commit

 

History

History
1053 lines (758 loc) · 33.9 KB

fortran90.org

File metadata and controls

1053 lines (758 loc) · 33.9 KB

F90 For Beginners

Introduction

This started from a short syntax refresher on Fortran 90 that I found on the web from the University of Munich: F90 For Beginners. Thanks to the original authors! I’ve reformatted the text into org mode for my own reference, annotation. As I learn more Fortran, notes and ideas on usage will end up in here until I’ve internalized enough that I don’t need to refer to this any more.

As it’s now 2024 and the Fortran 2023 standard is released, I expect to have to update this to use F95, maybe 2003? My last serious Fortran was on an IBM 360 in 1978, but I know it wasn’t FORTRAN 77. I’ve got a lot of catching up to do.

After a few weeks of using Fortran daily with Emacs on the Mac, it’s grown on me. I need to update the text below somewhat, but the spirit of the changes described below from the Fortran I learned in college holds true. More features are added every few years, but for my use Fortran 95/2003 provide most everything I want.

If you can do it in Pascal or C, you can do it in Fortran. It’s just a matter of preference.

I’m making a pass to remove anachronisms from my original attempt and absorbing the F90 For Beginners material and hope this will serve as a quick set of pointers and reminders. I don’t expect anyone else would have much use for this level of reference, but if you find it helpful all the better.

Resources

There are several decent overviews of the basic language syntax suitable for undergraduate programming classes. The one listed in the introduction was one of the better ones I reviewed.

Books

A recent version of Modern Fortran Explained by Metcalf, Reid, & Cohen appears to be the best language reference. I have the 2018 version (red cover). If you don’t live on the bleeding edge of HPC, I get the impression that the Fortran 2008 language is more than adequate for most hobbyist programming.

I have found Modern Fortran: Style and Usage 1st Edition by Clerman & Spector an enjoyable read. Rules of thumb explained clearly. Strunk & White for the programmer.

I have, but have yet to read in depth, Modern Fortran: Building efficient parallel applications 1st Edition, by Curcic. It was a COVID lock down deal from the publisher and I saw enough to make me want to explore Fortran. This book is why I started using Fortran for my Advent of Code work.

I plan to purchase Modern Fortran in Practice 1st Edition by Markus once I’ve completed the 2022 and 2023 Advent of Code exercises and made some progress on Curcic’s book.

Online

Compilers and Tools

Infrastructure

To do anything programming related on the Mac you’ll need to install the XCode Command Line Tools. Once that’s done, pick a package manager and use it. Several are available. Many in the Fortran community are in the Big Data business and a lot of their tooling comes from Conda. Brew and MacPorts are the other options. I dislike the Conda environment switching, so I only use Conda for lfortran related work. I get everything else from Brew.

Here’s the bits you’ll want to lay down on your system:

  • XCode command line tools from Apple xcode-select --install
  • Homebrew via their macOS .pkg installer.
  • CMake, fprettify, fortls, and emacs-mac with brew install cmake fprettify fortls emacs-mac. There are a lot of dependencies that come down with these, but they don’t get in the way or take much space.

Reference sites:

Compiler gfortran

The LLVM Flang doesn’t seem to get much use. The two compilers referenced most are gfortran and ifort (Intel). An alpha stage lfortran is in active development.

I’m doing this on an Apple silicon Mac Book Air. Apple’s infrastructure is built on LLVM so there are a few hoops to jump through if you want to use gfortran and gdb. Any of the options for just the compiler work fine, but the only gdb setup I’ve seen working is from MacPorts

Do the following to get a working debug capable gfortran compiler, most of which require ~sudo~.

- install macports from https://www.macports.org/install.php MacPorts - install gcc and friends ~port install gcc13 gdb cgdb~ - select the version to use ~port select –set gcc=mp-gcc13~ - code sign the gdb executable (ggdb). You can find various sources for this but I used https://sourceware.org/gdb/wiki/PermissionsDarwin these instructions, but the interactive approach instead of a shell script.

After the above, gfortran and ggdb (note the extra “g”) are available and work well.

Sadly, the above doesn’t work consistently. I see a lot of frustration when searching the web about this. After experimentation, I decided to switch rather than fight. A dev container for Docker is a workable solution. I created an Ubuntu image for AARCH64 with tooling for gfortran and gdb. Starting that container with Fortran source directories shared lets me build and use gdb when I need to. Most of the time I work in MacOS and it’s mostly no issue to switch. I want to make this a bit more transparent, but it works.

Editor Doom Emacs

I tried using straight Emacs, but my hands know vim key binds too well. I’d rather switch than fight. Follow the installation steps from https://github.com/doomemacs/doomemacs to get the basic setup. For Fortran support, enable it in the languages section of init.el along with a few other things. Here are the pertinent sections from my init.el with minimal context:

:editor
       (format +onsave)  ; automated prettiness

:tools
       lookup              ; navigate your code and its documentation
       lsp               ; M-x vscode
       make              ; run make tasks from Emacs
       tree-sitter       ; syntax and parsing, sitting in a tree...

:lang
       (fortran +lsp)           ; in FORTRAN, GOD is REAL (unless declared INTEGER)
  

If you have installed fortls and fprettify, things will just work. They are pretty much zero configuration. I have the following in my config.el to tweak some of the formatting rules:

For editing, Doom Emacs has decent Fortran support for the level of work I’m doing. Specifically, make, lsp for basic code navigation, and fprettify for formatting code. While I don’t like all the rules, formatting code on save is the right way to go.

Build and packaging

I haven’t had to get too deep into this aspect yet. The Fortran Package Manager (fpm) is available and I built it from GitHub, but most will probably want to install it from brew. CMake seems to be common but so far I’m still at the simple makefile stage for my work.

Testing

This is unexplored territory. There are several tools available but I haven’t settled on one. I prefer to find a Fortran-only solution which will limit my choices.

Best Practices (I hope)

Generalities

  • Always use implicit none. It should be specified in each scope.
  • Use underscore instead of camel or Pascal casing for names.
  • Factor out code into modules when possible.
  • Use FPM.
  • Use CMake.
  • Use a testing framework. Two I have found that look good for my needs and style are:
    • See ftnunit by Arjen Markus mentioned on discourse and found at SF flibs
    • See test-drive from Fortran-Lang (by awvwgk/Sebastian Ehlert) at github

Modules

use directives must be the first thing in a scope, even before implicit none.

There are two standard modules that should be available on all compilers that I expect to use.

  • iso_fortran_env provides several named integer constants that can be used refine variable types by kind, common unit numbers for standard input/output, and some error status codes. Fortran 2003 and later.
  • iso_c_binding provides intrinsic functions for low level access and named constants to help define interfaces to C based APIs.

It seems to be the common practice to specify only what you expect to use from a module. The syntax for this is use module, only: name. It is also possible to provide an alias via use module, only: alias => name.

Some examples:

use iso_fortran_env, only: sp => real32, dp => real64, qp => real128
! If we decide later to use iso_c_binding instead of iso_fortran_env:
! use iso_c_binding, only: sp => c_float, dp => c_double, qp => c_float128

! resolve name collision
use module1, only: mod1_init => initialize
use module2, only: mod2_init => initialize

call mod1_init
call mod2_init

Basic Syntax

Free form

Modern Fortran is a line oriented language but the old card column restrictions no longer apply. Comments begin with an exclamation point and extend to the end of the line. Statements are separated by either the end of the line or a semicolon. A physical line is limited to 132 characters. Lines can be continued with an ampersand.

! code all on one line
if (a > 5) then; b = 7; else; b = 8; end if

! is the same as this more readable code
if (a > 5) then
   b = 7
else
   b = 8
end if

! and lines can be split as
a = 3 * b + &
    7 * c

Character set, tokens, and names

I’m sure it’s more diverse than this, but everything I see is Western Latin alphabetic and numeric characters. The special characters used for non-identifier tokens and grouping are a small set. You could still easily type in source on a card punch machine.

  • a-z (or A-Z), case insensitive
  • 0-9
  • _ an underscore
  • + - / * ! . , ( ) < > ; : ’ ” = % [ ]

Identifier names can be up to 31 characters long, must start with an alphabetic character, followed by any of alphabetic, numeric, or underscore characters.

Declare variables explicitly

Always begin a program with implicit none. Otherwise the old Fortran 77 convention of names beginning with I-N imply the variable is an integer, while all others are real.

Programs should be named, as in program test, and conclude with end program test’.

Data Types and Variables

Data Types

The elementary or basic data types are generic. They are:

  • integer
  • real
  • complex (real and imaginary parts)
  • character (fixed length)
  • logical (true, false)

Use the kind attribute for type specificity

kind can be used to specify expected precision or other implementation dependent properties of numbers. See also the iso_fortran_env and iso_c_binding modules. kind=n where n is an integer.

In earlier Fortran implementations, star notation was used to indicate size which carried with it an implied precision. Then kind=n was introduced with the following meanings in GNU compilers.

  • 1 means the default size for the generic type in the implementation. GNU says these are typically REAL*4, INTEGER*4, LOGICAL*4, and COMPLEX*8. Full words from my assembly days.
  • 2 means twice the default. Double words.
  • 3 means the type occupies the storage required for a default character in the implementation. Typically one byte, as in INTEGER*1 and LOGICAL*1. Further increments by 3 double the storage of the prior 3-kind. This is not available in all Fortran implementations.
  • 5 means half the default. INTEGER*2, LOGICAL*2. Half words.
  • 7 integer only, and refers to the smallest sized pointer in the implementation that can address a byte or array element or sub-string.

Fortran 2003 and 2008 cleaned this up. In addition to providing standard names for kinds (see later) there are intrinsic functions to determine if the required precision or range are available.

Fortran 2003 introduced the iso_fortran_env module, and Fortran 2008 introduced the following standard names that mean what most of us would expect.

  • INT8
  • INT16
  • INT32
  • INT64
  • REAL32
  • REAL64
  • REAL128

Literal constants also have type and kind. A common idiom is to append _<kind> to a constant. The <kind> name could be a one of the above, or a named constant created with the selected_real_kind(mantissa_digits, exponent_range) or selected_int_kind(digits) functions.

! example, use one of sp and dp below and then the declarations follow
! mnemonically sp for single precision, dp for double precision, etc
integer, parameter :: sp = selected_real_kind(6, 37)
integer, parameter :: sp = kind(1.)
integer, parameter :: dp = selected_real_kind(15,307)
integer, parameter :: dp = kind(1.d0)
integer, parameter :: qp = selected_real_kind(33,4931)
integer, parameter :: i4 = selected_int_kind(9)
integer, parameter :: i8 = selected_int_kind(16)
real (kind=sp) :: x,y ! or: real (sp) :: x,y
real (kind=dp) :: a,b ! ("double precision")
! other code
a = 3.14159_dp
dword = 8_i4

There are some heated debates on how to properly use the selected_xxxxx_kind() intrinsic functions, but following creates sets r8 to the kind that will support reals with at least 15 decimal digits of precision and an exponent range of at least 9. This might map to double precision floating point (REAL64) but that is not guaranteed. All that is guaranteed is if selected_real_kind returns a positive value, that kind will support the requested precision.

integer, parameter :: r8 = selected_real_kind(15,9)
real(kind=r8) :: a

Derived types or structures

Derived or programmer defined types are created by the type…end type construct. For example:

type person
   character (len=20) :: name
   integer            :: age
end type person
type(person)      :: myself
myself % name = 60

The percent sign % is used to access the named member of the type, much like . is used in other languages.

Attributes of variables beyond type

Variables can be further refined by attributes describing usage, source, scope, and so forth.

  • dimension
  • allocatable (dynamically acquired and sized)
  • parameter (within this scoping unit, this is a constant)
  • intent (in, out, “inout” for arguments)
  • len
  • save (static)
  • pointer
  • public
  • private
  • optional

For example:

integer, parameter :: np = 3
real, dimension(np) :: b ! vector of length 3
integer :: i
do i = 1, np
   b(i) = sqrt(i)
end do

A potential gotcha

You might be tempted to add an initialization to the variable definition. DO NOT DO THIS While integer, parameter :: np = 3 creates a constant named np, integer :: counter = 1 implies save and creates a static variable. counter will keep its value across function/subroutine invocations.

Expression Operators

Numeric operators and intrinsic functions

Fortran has all the traditional operators and intrinsic functions:

  • + - * /
  • ** (power)
  • sin cos tan atan, hyperbolic varieties of these
  • rand, int, real
  • min, max
  • exp log log10
  • sqrt
  • and so many more

Operations use the precision of the most precise operand. It’s not clear to me yet if or when lower precision operands are promoted, but I don’t expect many surprises in my code.

  • 1/2 ==> 0
  • 1./2 ==> 0.5000000
  • 1/2. ==> 0.5000000
  • 1/2._dp ==> 0.50000000000000
  • 1+(1.,3) ==> (2.000000,3.000000)

Logical and comparison operators use the original .xx. style and have some symbolic representations as well.

  • .and.
  • .or.
  • .not.
  • .eq. or ==
  • .ne. or /=
  • .gt. or >
  • .ge. or >=
  • .lt. or <
  • .le. or <=
  • .eqv. and .neqv. to compare logical variables
  • .llt., .lle., .lgt., .lge. for lexical comparison of characters
  • // (string concatenation)

Looping

For counting or fixed length loops, use do/end do. do var=begin, end, increment where increment is optional and assumed to be one if omitted. enddo is legal but end do is preferred. If begin > end and increment is not provided, the loop does not execute.

! executes with i of 1, 3, 5, 7, and 9
do i = 1, 10, 2
   print *, i, i**2
end do

! does not execute
do i = 10, 1
   print *, i, i**2
end do

! executes for i = 10, 8, 6, 4, 2
do i = 10, 1, -2
   print *, i, i**2
end do

! executes for i = 10
do i = 10, 10
   print *, i, i**2
end do

Use do while for non counting conditional loops. For example:

do while(x .lt. .95)
   x = 3.8 * x * (1. - x)
end do

An infinite loop can be coded with just do:

do
   print *, 'enter a number, negative to exit'
   read *, x
   if (x .lt. 0.) exit
   print *, 'the square root of ', x, ' is ', sqrt(x)
end do

In some situations, an implied do can be used. For example:

print *, (i, i**2, i=1, 100)

exit may be used to end a loop. This is the same as break in other languages. Control passes to the statement after the end do. The name of the loop to cycle back to can be specified and works correctly when dealing with nested loops.

real, dimension(327) :: a
integer :: i
! … do something here to populate a with increasing numbers
do i = 1, 327
   if (a(i) .gt. 1.2345) exit
end do
! loop control variable is reliable at exit
if (i .eq. 327 + 1) then
   print *, 'index not found'
   stop
else
   print *, 'index', i, ': value =', a(i)
end if

cycle starts a new cycle of a loop. (continue in other languages). The name of the loop to cycle back to can be specified and works correctly when dealing with nested loops. For example:

real, dimension(5,5) :: a
integer :: i, j
call random_number(a)
do i = 1, 5
   print *, (a(i, j), j = 1, 5)
enddo
outer: do i = 1, 5          ! all matrix rows
   inner: do j = 1, 5      ! matrix columns, search loop:
      ! searches for first number > 0.8 in row i
      if (a(i, j) .gt. 0.8) then
         print *, 'row', i, ': column', j, ':', a(i, j)
         cycle outer
      end if
   end do inner            ! named do requires named end do
   print *, 'row ', i, ': nothing found'
end do outer

Statements versus Constructs

Block structuring seems to have come along with the Fortran 77 standard. Some things can be done in one statement or more completely in a block. The logical and arithmetic if statements from Fortran IV are still available but I believe only the logical form should be used.

  • logical if : IF (logical expression) <any statement other than DO or IF> would be useful for exit or cycle in loops.
  • arithmetic if : IF (numeric expression) <label if negative>,<label if zero>,<label if positive> should not be used.

Conditional Statements

if then else end if and variations are available.

! a single statement
if (x > 0.) x = sqrt(x)

! a block style
if (x > 0.) then
   x = sqrt(x)
   y = y - x
end if

! if-then-else
if (x < 0.) then
   print *, 'x is negative'
else
   if (x > 0.) then
      print *, 'x is positive'
   else
      print *, 'x must be zero'
   end if
end if

! or even better for the above, if-then-else if-…
if (x < 0.) then
   print *, 'x is negative'
else if (x > 0.) then
   print *, 'x is positive'
else
   print *, 'x must be zero'
end if

The select case can be used for picking among ordinal values (integer, boolean, and character).

read *, i
select case(i)
case(1)
   print *, 'excellent'
case(2, 3)
   print *, 'meh'
case(4:6)
   print *, 'for shame!'
case default
   print *, 'unpossible'
end select

Input/Output

Terminal oriented with minimal formatting. Older code may use write(*,*)` or ~read(*,*) but for the terminal or standard input and output, print *, and read *, are preferred.

real :: a
print *, 'enter a real number'
read *, a
print *, 'input was ', a

The (*,*) is a shorthand for (unit=*, fmt=*). Formatting will come along soon.

To open a file for writing:

open (1, file='output')
write (1,*) 'hello world'
close (1)

Error or event handling on files are specified as keyword operands in the (unit,…) portion of the statement. Two options are end= for end of file, and err= for an error. This example uses line numbers but I hope that isn’t the only option.

program read
  implicit none
  integer, parameter :: m = 10
  integer :: i
  real, dimension (m) :: a
  real :: t
  open (77, file='numbers')
  i = 0
  do
     read (77, *, end=200, err=100) t
     i = i + 1
     if (i > m) then
        print *, 'array too small! increase m and recompile!'
        close (77)
        stop
     end if
     a(i) = t
  end do

100 continue
  print *, 'read error in line ', i + 1
  close (77)
  stop

200 continue
  print *, i, ' numbers read'
  close (77)
  print *, a(1:i)
end program read ! program

After some research, the iostat= parameter should be used instead. The status can be checked in a visible and readable way in code. Negative values are end of file, 0 is normal completion, while positive values are an error.

Reading and writing to character variables can use a concept of an “internal file”. Character index addressing, slicing, and concatenation are also available. I need to write up a complete section on character variables.

character (len=20) :: a
write(a, *) "Hello, world!"

Classic formatted input/output is still available, but seems discouraged in favor of list-directed input/output. This is fmt=*.

  write (*, 700) 1, 1.23, (7., 8.), 'Hello', .true.
  write (*, 701)
  write (*, 702)
700 format (i5, e12.4e3, 2f8.2, 1x, a3, l7)
701 format ('12345678901234567890123456789012345678901234567890')
702 format ('         1         2         3         4         5')
  write(*,'(i5, e12.4e3, 2f8.2, 1x, a3, l7)') 1, 1.23, (7.,8.), 'Hello', .true.

Produces:

    1 0.1230E+001    7.00    8.00 Hel      T
12345678901234567890123456789012345678901234567890
         1         2         3         4         5
    1 0.1230E+001    7.00    8.00 Hel      T

Format definitions can be a separate labeled statement, a character constant, or a character variable. Parenthesis are part of the format specification in this form. These are all equivalent:

  real :: x
  character (len=8) :: a

  write (*, 123) x
123 format (es10.2)

  write(*, '(es10.2)') x

  a = '(es10.2)'
  write (*, a) x

Format descriptors can be used to format output allowing for leading blanks (or right alignment if you prefer), different number base, precision, and to select between exponential, scientific, and engineering floating point conventions.

  • integers
    • i decimal
    • b binary (BOZ literals are a thing)
    • o octal
    • z hexadecimal
  • real
    • d
    • e exponential (0.nnnnnnexx)
    • f
    • g
    • es exponential using scientific convention (n.nnnnnexx)
    • en exponential using engineering convention (powers of 10 by orders of magnitude, 12.378e03)
  • logical
    • l (ell) produces T or F for .true. or .false.
  • character
    • a
  • other
    • n (number) repeat following n times, as in 3f8.2
    • x space
    • / new line
    • ‘…’ literal text
    • (…) for grouping
    • p scale

Arrays

Arrays have dimensions. An array can be a vector (one dimension) or matrix (multiple dimensions). Fortran allows up to seven fifteen dimensions. In Fortran the default starting subscript is 1. Bravo. Start and end bounds can be specified to override the default.

real, dimension(2, 2) :: a ! 2x2, (1,1) -> (2,2)
real, dimension(3:4, -2:-1) :: q ! also a 2x2, (3,-2) -> (4,-1)
integer, parameter :: m=27, n=123
real, dimension(n, m) :: b, c
real, dimension(m) :: x, y

Intrinsic functions can describe the array (reflection). Referring to the prior definitions:

shape(b)      !-> 123, 27 (= n,m)
size(b)       !-> 3321 (= 123*27)
size(b, 1)    !-> 123
size(b, 2)    !-> 27
lbound(q, 2)  !-> -2
ubound(q, 1)  !-> 4

Array constructors provide a constant or initialization of an array:

x = (/ 1., 2., 3., 4., 5. /)
y = (/ (0.1*i, i=1, m) /)     ! -> 0.1 0.2 0.3 0.4 0.5 …

This technique only works for single dimensional arrays. It is possible to reshape an array, but be aware that in Fortran the first index cycles first. A Fortran two dimensional array is not laid out as it would be in C, where each row (or first index) can be viewed as holding another array.

This is column major order. Fortran and Julia store arrays in column major order, while C and Pascal store them in row major order.

a = reshape( (/ 1., 2., 3., 4. /), (/ 2, 2 /) )

Before the reshape the elements are a(1) = 1., a(2) = 2., a(3) = 3., a(4) = 4., while afterwards they are a(1,1) = 1., a(2,1) = 2., a(1,2) = 3., a(2,2) = 4.!

Fortran provides operations for complete arrays, removing the need to write code to iterate over elements in many situations.

real, dimension(n,m) :: b, c
b = sin(c)
! is much better than
real, dimension(n, m) :: b, c
integer :: i, j
do i = 1, n
   do j = 1, m
      b(i, j) = sin(c(i, j))
   end do
end do

Similarly, you can operate on slices or sections of arrays if they are the same shape.

real, dimension(10) :: u, v
real, dimension(5, 4) :: w
u(2:10, 2) = sin(w(:,1))
v(1:3) = 5 ! or v(:3) = 5

So u(i:j:k) means those elements of u starting from index i until index j, but only every k-th element. k is optional and defaults to 1. Omitting i or j implies the lower or upper bound.

Where blocks allow selection or filtering by cell contents (e.g., avoid division by 0):

where (x == 0)
   y = 1.
else where
   y = sin(x) / x
end where

Array level operations and do loop variations have different semantics. Array level operations evaluate the entire right side of the expression. The following are not equivalent:

do i = 2, m
   x(i) = x(i) + x(i - 1)
end do

! versus
x(2:m) = x(2:m) + x(1:m-1)

Character Variables

Character variables are fixed length, which I should have no problem adjusting to given my career as an assembly language programmer. In my work so far I see them all padded on the right. Slicing by byte index works but remember those blanks!

character(len=255) :: str

str = ""
str = str//"asdf"        ! wrong, str will be "" after this statement
str = trim(str)//"asdf"  ! right, str will be "asdf" after this statement

Subroutines and Functions

The specific syntax for passing arrays and allowing for non-compile-time constant dimensions isn’t completely clear to me yet. The snippets and recommendations from the original document are helpful, but this needs to be clarified.

Here is a simple example.

program main
  implicit none
  integer i
  real :: x, y, sinc
  do i=0, 80, 2
     x = i / 10.
     y = sinc(x)          ! ??? implicit function ???
     print *, x, y
  end do
  call output(0, 80, 2)   ! ??? explicit subroutine ???
end program main

function sinc(x)
  implicit none
  real :: x, sinc
  if (x .eq. 0.) then
     ! be careful with comparison to real numbers because of rounding errors
     ! better: if (abs(x).lt.1.e-16) then
     sinc = 1.
  else
     sinc = sin(x) / x
  endif
end function sinc

subroutine output(a, e, s)
  integer, intent(in) :: a, e, s
  real :: x, y, sinc
  integer :: i
  open(1, file='sinc.data')
  do i = a, e, s
     x = i / 10.
     y = sinc(x)
     write (1,10) x, y
  end do
  close(1)
10 format(2e14.6)
end subroutine output

Function sinc above cannot be called with array arguments as it is defined above. Who reserves the storage for arrays? Must the size be fixed at compile time or can it change at run time?

program main
  implicit none
  !
  integer, parameter :: n=100
  real, dimension(n) :: a, b, c, d
  call sub(a, b, c, d, n)
end program main

subroutine sub(u, v, w, x, m)
  real, dimension(100) :: u            ! constant size
  real, dimension(m) :: v              ! adjustable size
  real, dimension(*) :: w              ! assumed size
  real, dimension(:) :: x              ! assumed shape (needs interface block in caller)
  real, dimension(100) :: y            ! constant size (local)
  real, dimension(m) :: z              ! automatic (local)
  real, dimension(:), allocatable :: t ! deferred-shape (local)
  !
  allocate(t(m))
  !
  print *, u, v, x, y, z, t            ! assumed size needs explicit indexing
  print *, w(1:m)                      ! because upper bound is unknown
  !
  deallocate(t)
end subroutine sub

The original of this recommends using either adjustable size (passed as a parameter) or assumed shape (requires an interface block in the caller, see later). There may be limits on the maximum size of automatic arrays.

Array slices or sections are a special case of ‘assumed shape’ and also require an interface block. Upcoming.

program main
  implicit none
  interface
     subroutine sub(x)
       real, dimension(:) :: x
     end subroutine sub
  end interface
  integer, parameter :: n=100
  real, dimension(n) :: a
  call sub(a(1:50:3))
end program main

subroutine sub(x)
  real, dimension(:) :: x
  print *, shape(x)
end subroutine sub

Interface blocks should be collected in a specific module. Modules are described next.

Modules

While not exactly the same, a module is similar to a Pascal unit. They are included by the use directive and are best kept in separate source files.

  • Declare subroutines, functions, and interface blocks.
  • Global variables can be defined in a module and explicitly exposed on the use directive.
  • Supporting variables and implementation details can be hidden (private) to the module.

Modules can also be used to control precision by the definition of kind-numbers.

module my_type
  ! Useful trick: precision of following routines can be easily changed
  ! from single to double precision by alternatively
  ! commenting/uncommenting the statements defining sp
  integer, parameter :: ib = selected_int_kind(9) !integer*4
  integer, parameter :: sp = selected_real_kind(6,37) !real*4 or sp = kind(1.)
  ! integer, parameter :: sp = selected_real_kind(15,307) !real*8 or dp = kind(1.d0)
end module my_type

program random
  use my_type ! use statement(s) must be given before further declarations
  implicit none
  integer(ib) :: i
  real(sp) :: x
  do i = 1,5
     call random_number(x)
     print *,x
  end do
end program random

An example of global variables.

module common
  implicit none
  real :: x, y=5.
end module common

program test
  implicit none
  call sub1
  call sub2
  call sub3
end program test

subroutine sub1
  use common, only: x         ! note that common.y is not visible
  implicit none
  real :: y
  x = 3.
  y = 1.
  print *, x, y
end subroutine sub1

subroutine sub2
  use common, only: x
  implicit none
  print *, x
  x = 7.
end subroutine sub2

subroutine sub3
  use common               ! both x and y are visible
  implicit none
  print *, x, y
end subroutine sub3

The following is rather raw and I haven’t grokked it yet, but this shows how to better handle assumed shape parameters, among other things. I have done minimal reformatting but a real pass through this to fully understand it and link back to the subroutine section is still needed.

Declaration of subroutine(s) or corresponding interfaces in a module:

No explicit interface block if the subroutine is ‘contained’ in the module.

module mymod
  ! no explicit interface block if routine is "contained"
contains
  subroutine mysub(x)
    implicit none
    real, dimension(:) :: x
    write(*,*) shape(x)
  end subroutine mysub
end module mymod

program main
  use mymod
  implicit none
  integer, parameter :: n=100
  real, dimension(n) :: a
  call mysub(a(1:50:3))
end program main

An interface block is needed if the routine is defined elsewhere.

module mymod
  interface
     subroutine mysub(x)
       implicit none
       real, dimension(:) :: x
     end subroutine mysub
  end interface
end module mymod

program main
  use mymod
  implicit none
  integer, parameter :: n=100
  real, dimension(n) :: a
  call mysub(a(1:50:3))
end program main

subroutine mysub(x)
  implicit none
  real, dimension(:) :: x
  print *, shape(x)
end subroutine mysub

And finally an example of using an interface block to overload a function definition to allow for passing scalars or arrays.

module sincm
  interface sinc
     module procedure sinca, sincs
  end interface sinc

contains

  function sinca(x) result(z) ! array
    implicit none
    real, dimension(:) :: x
    real, dimension(size(x)) :: z
    where(x == 0.)
       z = 1.
    elsewhere
       z = sin(x) / x
    endwhere
  end function sinca

  function sincs(x) result(z) ! scalar
    implicit none
    real :: x,z
    if(x == 0.) then
       z = 1.
    else
       z = sin(x) / x
    endif
  end function sincs
end module sincm

program main
  use sincm
  implicit none
  integer, parameter :: m=100
  real, dimension(m) :: x,y
  integer :: i
  x=(/ (0.2*i,i=1,m) /)
  y=sinc(x) ! array sinc
  write(*,777) (i,x(i),y(i),i=1,m)
777 format(i5,2e12.4)
  print *, sinc(1.23) ! scalar sinc
end program main