ACM Computing Seminar Fortran Guide

Table of Contents

×

1 Introduction

This guide is intended to quickly get you up-and-running in scientific computing with Fortran.

1.1 About the language

Fortran was created in the 1950s for mathematical FOR-mula TRAN-slation, and has since gone through a number of revisions (FORTRAN 66, 77, and Fortran 90, 95, 2003, 2008, 2015). The language standards are put forth by the Fortran standards committee J3 in a document (ISO 1539-1:2010) available for purchase. The language syntax and intrinsic procedures make it especially suited for scientific computing. Fortran is a statically-typed and compiled language, like C++. You must declare the type, i.e. integer, real number, etc. of variables in programs you write. Your programs will be translated from human-readable source code into an executable file by software called a compiler. Fortran is not case-sensitive, so matrix and MaTrIx are translated to the same token by the compiler.

2 Getting started

The software that you need to get started comes prepackaged and ready to download on most Linux distributions. There are a few options for emulating a Linux environment in Windows or Mac OS, such as a virtual machine (VirtualBox) or package manager (MinGW or Cygwin on Windows and Brew on Mac OS).

2.1 Text editor

You will write the source code of your programs using a text editor. There are many options that have features designed for programming such as syntax highlighting and auto-completion. If you are an impossible-to-please perfectionist, you might want to check out Emacs. If you are easier to please, you might want to check out Sublime Text.

2.2 Compiler

To translate your source code into an executable, you will need a Fortran compiler. A free option is gfortran, part of the GNU compiler collection (gcc). The features of the Fortran language that are supported by the gfortran compiler are specified in the compiler manual. This is your most complete reference for the procedures intrinsic to Fortran that your programs can use. At the time of this writing, gfortran completely supports Fortran 95 and partially supports more recent standards.

2.3 Writing and compiling a program

A program is delimited by the begin program / end program keywords. A useful construct for keeping code that a program can use is called a module. A module is delimited by the begin module / end module keywords.

2.3.1 Hello world

Let's write a tiny program that prints "hello world" to the terminal screen in hello.f90.

1: program main
2:   print*, 'hello world'
3: end program main

To compile the program, execute the following command on the command line in the same directory as hello.f90

gfortran hello.f90

This produces an executable file named a.out by default (On Windows, this is probably named a.exe by default). To run, execute the file.

./a.out

We could have specified a different name for the executable file during compilation with the -o option of gfortran.

gfortran hello.f90 -o my_executable_file

On Windows, you should append the .exe extension to my_executable_file.

2.3.2 Template

Now let's write an empty source code template for future projects. Our source code template will consist of two files in the same directory (./source/). In the following files, the contents of a line after a ! symbol is a comment that is ignored by the compiler. One file header.f90 contains a module that defines things to be used in the main program.

1: module header
2:   implicit none
3:   ! variable declarations and assignments
4: contains
5:   ! function and subroutine definitions
6: end module header

This file should be compiled with the -c option of gfortran.

gfortran -c header.f90

This outputs the object file named header.o by default. An object file contains machine code that can be linked to an executable. A separate file main.f90 contains the main program.

1: program main
2:   use header
3:   implicit none
4:   ! variable declarations and assignments
5:   ! function and subroutine calls
6: contains
7:   ! function and subroutine definitions
8: end program main

On line 2 of main.f90, we instruct the main program to use the contents of header.f90, so we must link header.o when compiling main.f90.

gfortran main.f90 header.o -o main

To run the program, execute the output file main.

./main

As you get more experience, you may find it cumbersome to repeatedly execute gfortran commands with every modification to your code. A way around this is to use the make command-line utility. Using make, all the of the compilation commands for your project can be coded in a file named makefile in the same directory as your .f90 source files. For example, the template above could use the following makefile.

 1: COMPILER = gfortran
 2: SOURCE = main.f90
 3: EXECUTABLE = main
 4: OBJECTS = header.o
 5: 
 6: all: $(EXECUTABLE)
 7: $(EXECUTABLE): $(OBJECTS)
 8: 	$(COMPILER) $(SOURCE) $(OBJECTS) -o $(EXECUTABLE)
 9: %.o: %.f90
10: 	$(COMPILER) -c $<

Then, to recompile both header.f90 and main.f90 after modifying either file, execute

make

in the same directory as makefile. The first four lines of the makefile above define the compiler command, file name of the main program, file name of the executable to be created, and file name(s) of linked object file(s), respectively. If you wrote a second module in a separate file my_second_header.f90 that you wanted to use in main.f90, you would modify line 4 of makefile to OBJ = header.o my_second_header.o. The remaining lines of makefile define instructions for compilation.

2.4 Exercises

  1. Compile and run hello.f90.
  2. Execute man gfortran in any directory to bring up the manual for gfortran. Read the description and skim through the options. Do the same for make.

3 Data types

In both programs and modules, variables are declared first before other procedures. A variable is declared by listing its data type followed by :: and the variable name, i.e. integer :: i or real :: x.

We will use the implicit none keyword at the beginning of each program and module as in line 2 of header.f90 and line 3 of main.f90 in Section 2.3.2. The role of this keyword is to suppress implicit rules for interpreting undeclared variables. By including it, we force ourselves to declare each variable we use, which should facilitate debugging when our program fails to compile. Without it, an undeclared variable with a name such as i is assumed to be of the integer data type whereas an undeclared variable with a name such as x is assumed to be of the real data type.

In addition to the most common data types presented below, Fortran has a complex data type and support for data types defined by the programmer (see Section 7.1).

3.1 The logical type

A logical data type can have values .true. or .false.. Logical expressions can be expressed by combining unary or binary operations.

 1: logical :: a,b,c
 2: a = .true.
 3: b = .false.
 4: 
 5: ! '.not.' is the logical negation operator
 6: c = .not.a ! c is false
 7: 
 8: ! '.and,' is the logical and operator
 9: c = a.and.b ! c is false
10: 
11: ! '.or.' is the logical or operator
12: c = a.or.b ! c is true
13: 
14: ! '==' is the test for equality
15: c = 1 == 2 ! c is false
16: 
17: ! '/=' is test for inequality
18: c = 1 /= 2 ! c is true
19: print*, c

Other logical operators include

  • < or .lt.: less than
  • <= or .le.: less than or equal
  • > or .gt.: greater than
  • >= or .ge.: greater than or equal

Logical expressions are often used in control structures.

3.2 The integer type

An integer data type can have integer values. If a real value is assigned to an integer type, the decimal portion is chopped off.

 1: integer :: a = 6, b = 7 ! initialize a and b to 6 and 7, resp
 2: integer :: c
 3: 
 4: c = a + b ! c is 13
 5: c = a - b ! c is -1
 6: c = a / b ! c is 0
 7: c = b / a ! c is 1
 8: c = a*b ! c is 42
 9: c = a**b ! c is 6^7
10: c = mod(b,a) ! c is (b mod a) = 1
11: c = a > b ! c is 0 (logical gets cast to integer)
12: c = a < b ! c is 1 (logical gets cast to integer)

3.3 Floating point types

The two floating point data types real and double precision correspond to IEEE 32- and 64-bit floating point data types. A constant called machine epsilon is the least positive number in a floating point system that when added to 1 results in a floating point number larger than 1. It is common in numerical analysis error estimates.

 1: real :: a ! declare a single precision float
 2: double precision :: b ! declare a double precision float
 3: 
 4: ! Print the min/max value and machine epsilon
 5: ! for the single precision floating point system
 6: print*, tiny(a), huge(a), epsilon(a)
 7: 
 8: ! Print the min/max value and machine epsilon
 9: ! for the double precision floating point system
10: print*, tiny(b), huge(b), epsilon(b)
1.17549435E-38   3.40282347E+38   1.19209290E-07
2.2250738585072014E-308   1.7976931348623157E+308   2.2204460492503131E-016

3.4 The character type

A character data type can have character values, i.e. letters or symbols. A character string is declared with a positive integer specifying its maximum possible length.

1: ! declare a character variable s at most 32 characters
2: character(32) :: s
3: 
4: ! assign value to s
5: s = 'file_name'
6: 
7: ! trim trailing spaces from s and
8: ! append a character literal '.txt'
9: print*, trim(s) // '.txt'
file_name.txt

3.5 Casting

An integer can be cast to a real and vice versa.

 1: integer :: a = 1, b
 2: real :: c, PI = 3.14159
 3: 
 4: ! explicit cast real to integer
 5: b = int(PI) ! b is 3
 6: 
 7: ! explicit cast integer to real then divide
 8: c = a/real(b) ! c is .3333...
 9: 
10: ! divide then implicit cast real to integer
11: c = a/b ! c is 0

3.6 The parameter keyword

The parameter keyword is used to declare constants. A constant must be assigned a value at declaration and cannot be reassigned a value. The following code is not valid because of an attempt to reassign a constant.

1: ! declare constant variable
2: real, parameter :: PI = 2.*asin(1.) ! 'asin' is arcsine
3: 
4: PI = 3 ! not valid

The compiler produces an error like Error: Named constant ‘pi’ in variable definition context (assignment).

3.7 Setting the precision

The kind function returns an integer for each data type. The precision of a floating point number can be specified at declaration by a literal or constant integer of the desired kind.

 1: ! declare a single precision
 2: real :: r 
 3: ! declare a double precision
 4: double precision :: d
 5: ! store single precision and double precision kinds
 6: integer, parameter :: sp = kind(r), dp = kind(d)
 7: ! set current kind
 8: integer, parameter :: rp = sp
 9: 
10: ! declare real b in double precision
11: real(dp) :: b
12: 
13: ! declare real a with precision kind rp
14: real(rp) :: a
15: 
16: ! cast 1 to real with precision kind rp and assign to a
17: a = 1.0_rp
18: 
19: ! cast b to real with precision kind rp and assign to a
20: a = real(b,rp)

To switch the precision of each variable above with kind rp, we would only need to modify the declaration of rp on line 8.

3.8 Pointers

Pointers have the same meaning in Fortran as in C++. A pointer is a variable that holds the memory address of a variable. The implementation of pointers is qualitatively different in Fortran than in C++. In Fortran, the user cannot view the memory address that a pointer stores. A pointer variable is declared with the pointer modifier, and a variable that it points to is declared with the target modifier. The types of a pointer and its target must match.

 1: ! declare pointer
 2: integer, pointer :: p
 3: ! declare targets
 4: integer, target :: a = 1, b = 2
 5: 
 6: p => a ! p has same memory address as a
 7: p = 2 ! modify value at address
 8: print*, a==2 ! a is 2
 9: 
10: p => b ! p has same memory address as b
11: p = 1 ! modify value at address
12: print*, b==1 ! b is 1
13: 
14: ! is p associated with a target?
15: print*, associated(p)
16: 
17: ! is p associated with the target a?
18: print*, associated(p, a)
19: 
20: ! point to nowhere
21: nullify(p)
T
T
T
F

3.9 Arrays

The length of an array can be fixed or dynamic. The index of an array starts at 1 by default, but any index range can be specified.

3.9.1 Fixed-length arrays

An array can be declared with a single integer specifying its length in which cast the first index of the array is 1. An array can also be declared with an integer range specifying its first and last index.

Here's a one-dimensional array example.

 1: ! declare arrray of length 5
 2: ! index range is 1 to 5 (inclusive)
 3: real :: a(5)
 4: 
 5: ! you can work with each component individually
 6: ! set the first component to 1
 7: a(1) = 1.0
 8: 
 9: ! or you can work with the whole array
10: ! set the whole array to 2
11: a = 2.0
12: 
13: ! or you can with slices of the array
14: ! set elements 2 to 4 (inclusive) to 3
15: a(2:4) = 3.0

And, here's a two-dimensional array example.

 1: ! declare 5x5 array
 2: ! index range is 1 to 5 (inclusive) in both axes
 3: real :: a(5,5)
 4: 
 5: ! you can work with each component individually
 6: ! set upper left component to 1
 7: a(1,1) = 1.0
 8: 
 9: ! or you can work with the whole array
10: ! set the whole array to 2
11: a = 2.0
12: 
13: ! or you can with slices of the array
14: ! set a submatrix to 3
15: a(2:4, 1:2) = 3.0

Fortran includes intrinsic functions to operate on an array a such as

  • size(a): number of elements of a
  • minval(a): minimum value of a
  • maxval(a): maximum value of a
  • sum(a): sum of elements in a
  • product(a): product of elements in a

See the gfortran documentation for more.

3.9.2 Dynamic length arrays

Dynamic arrays are declared with the allocatable modifier. Before storing values in such an array, you must allocate memory for the array. After you are finished the array, you ought to deallocate the memory that it occupies.

Here's a one-dimensional array example.

 1: ! declare a one-dim. dynamic length array
 2: real, allocatable :: a(:)
 3: 
 4: ! allocate memory for a
 5: allocate(a(5))
 6: 
 7: ! now you can treat a like a normal array
 8: a(1) = 1.0
 9: ! etc...
10: 
11: ! deallocate memory occupied by a
12: deallocate(a)
13: 
14: ! we can change the size and index range of a
15: allocate(a(0:10))
16: 
17: a(0) = 1.0
18: ! etc...
19: 
20: deallocate(a)

Without the last dellaocate statement on line 20 the code above is valid, but the memory that is allocated for a will not be freed. That memory then cannot be allocated to other resources.

Here's a two-dimensional array example.

 1: ! declare a two-dim. dynamic length array
 2: real, allocatable :: a(:,:)
 3: 
 4: ! allocate memory for a
 5: allocate(a(5,5))
 6: 
 7: ! now you can treat a like a normal array
 8: a(1,1) = 1.0
 9: ! etc...
10: 
11: ! deallocate memory occupied by a
12: deallocate(a)
13: 
14: ! we can change the size and index range of a
15: allocate(a(0:10,0:10))
16: 
17: a(0,0) = 1.0
18: ! etc...
19: 
20: deallocate(a)

4 Control structures

Control structures are used to direct the flow of code execution.

4.1 Conditionals

4.1.1 The if construct

The if construct controls execution of a single block of code. If the block of code is more than one line, it should be delimited by an if / end if pair. If the block of code is one line, it can be written on one line. A common typo is to forget the then keyword following the logical in an if / end if pair.

1: real :: num = 0.75
2: 
3: if (num < .5) then
4:    print*, 'num: ', num
5:    print*, 'num is less than 0.5'
6: end if
7: 
8: if (num > .5) print*, 'num is greater than 0.5'
num is greater than 0.5

4.1.2 Example: if / else and random number generation

The if / else construct controls with mutually exclusive logic the execution of two blocks of code.

The following code generates a random number between 0 and 1, then prints the number and whether or not the number is greater than 0.5

 1: real :: num
 2: 
 3: ! seed random number generator
 4: call srand(789)
 5: 
 6: ! rand() returns a random number between 0 and 1
 7: num = rand()
 8: 
 9: print*, 'num: ', num
10: 
11: if (num < 0.5) then
12:    print*, 'num is less than 0.5'
13: else
14:    print*, 'num is greater then 0.5'
15: end if
16: 
17: ! do it again
18: num = rand()
19: 
20: print*, 'num: ', num
21: 
22: if (num < 0.5) then
23:    print*, 'num is less than 0.5'
24: else
25:    print*, 'num is greater then 0.5'
26: end if
num:    6.17480278E-03
num is less than 0.5
num:   0.783314705    
num is greater then 0.5

Since the random number generator was seeded with a literal integer, the above code will produce the same output each time it is run.

4.1.3 Example: if / else if / else

The if / else if / else construct controls with mutually exclusive logic the execution of three or more blocks of code. The following code generates a random number between 0 and 1, then prints the number and which quarter of the interval \([0,1]\) that the number is in.

 1: real :: num
 2: 
 3: ! seed random number generator with current time
 4: call srand(time())
 5: 
 6: ! rand() returns a random number between 0 and 1
 7: num = rand()
 8: 
 9: print*, 'num:', num
10: 
11: if (num > 0.75) then
12:    print*, 'num is between 0.75 and 1'
13: else if (num > 0.5) then
14:    print*, 'num is between 0.5 and 0.75'
15: else if (num > 0.25) then
16:    print*, 'num is between 0.25 and 0.5'
17: else
18:    print*, 'num is between 0 and 0.25'
19: end if
num:  0.570252180    
num is between 0.5 and 0.75

Since the random number generator was seeded with the current time, the above code will produce a different output each time it is run.

4.2 Loops

4.2.1 The do loop

A do loop iterates a block of code over a range of integers. It takes two integer arguments specifying the minimum and maximum (inclusive) of the range and takes an optional third integer argument specifying the iteration stride in the form do i=min,max,stride. If omitted, the stride is 1.

The following code assigns a value to each component of an array then prints it.

 1: integer :: max = 10, i
 2: real, allocatable :: x(:)
 3: 
 4: allocate(x(0:max))
 5: 
 6: do i = 0,max
 7:    ! assign to each array component
 8:    x(i) = i / real(max)
 9: 
10:    ! print current component
11:    print "('x(', i0, ') = ', f3.1)", i, x(i)
12: end do
13: 
14: deallocate(x)
x(0) = 0.0
x(1) = 0.1
x(2) = 0.2
x(3) = 0.3
x(4) = 0.4
x(5) = 0.5
x(6) = 0.6
x(7) = 0.7
x(8) = 0.8
x(9) = 0.9
x(10) = 1.0

An implicit do loop can be used for formulaic array assignments. The following code creates the same array as the last example.

1: integer :: max = 10
2: real, allocatable :: x(:)
3: 
4: allocate(x(0:max))
5: 
6: ! implicit do loop for formulaic array assignment
7: x = [(i / real(max), i=0, max)]
8: 
9: deallocate(x)
4.2.1.1 Example: row-major matrix

The following code stores matrix data in a one-dimensional array named matrix in row-major order. This means the first n_cols elements of the array will contain the first row of the matrix, the next n_cols of the array will contain the second row of the matrix, etc.

 1: integer :: n_rows = 4, n_cols = 3
 2: real, allocatable :: matrix(:)
 3: ! temporary indices
 4: integer :: i,j,k
 5: 
 6: ! index range is 1 to 12 (inclusive)
 7: allocate(matrix(1:n_rows*n_cols))
 8: 
 9: ! assign 0 to all elements of matrix
10: matrix = 0.0
11: 
12: do i = 1,n_rows
13:    do j = 1,n_cols
14:       ! convert (i,j) matrix index to "flat" row-major index
15:       k = (i-1)*n_cols + j
16: 
17:       ! assign 1 to diagonal, 2 to sub/super-diagonal
18:       if (i==j) then
19: 	 matrix(k) = 1.0
20:       else if ((i==j-1).or.(i==j+1)) then
21: 	 matrix(k) = 2.0
22:       end if
23:    end do
24: end do
25: 
26: ! print matrix row by row
27: do i = 1,n_rows
28:    print "(3(f5.1))", matrix(1+(i-1)*n_cols:i*n_cols)
29: end do
30: 
31: deallocate(matrix)
1.0 2.0 0.0
2.0 1.0 2.0
0.0 2.0 1.0
0.0 0.0 2.0

4.2.2 The do while loop

A do while loop iterates while a logical condition evaluates to .true..

4.2.2.1 Example: truncated sum

The following code approximates the geometric series

\begin{equation*} \sum_{n=1}^{\infty}\left(\frac12\right)^n=1. \end{equation*}

The do while loop begins with \(n=1\) and exits when the current summand does not increase the current sum. It prints the iteration number, current sum, and absolute error

\begin{equation*} E=1-\sum_{n=1}^{\infty}\left(\frac12\right)^n. \end{equation*}
 1: real :: sum = 0.0, base = 0.5, tol = 1e-4
 2: real :: pow = 0.5
 3: integer :: iter = 1
 4: 
 5: do while (sum+pow > sum)
 6:    ! add pow to sum
 7:    sum = sum+pow
 8:    ! update pow by one power of base
 9:    pow = pow*base
10: 
11:    print "('Iter: ', i3, ', Sum: ', f0.10, ', Abs Err: ', f0.10)", iter, sum, 1-sum
12: 
13:    ! update iter by 1
14:    iter = iter+1
15: end do
Iter:   1, Sum: .5000000000, Abs Err: .5000000000
Iter:   2, Sum: .7500000000, Abs Err: .2500000000
Iter:   3, Sum: .8750000000, Abs Err: .1250000000
Iter:   4, Sum: .9375000000, Abs Err: .0625000000
Iter:   5, Sum: .9687500000, Abs Err: .0312500000
Iter:   6, Sum: .9843750000, Abs Err: .0156250000
Iter:   7, Sum: .9921875000, Abs Err: .0078125000
Iter:   8, Sum: .9960937500, Abs Err: .0039062500
Iter:   9, Sum: .9980468750, Abs Err: .0019531250
Iter:  10, Sum: .9990234375, Abs Err: .0009765625
Iter:  11, Sum: .9995117188, Abs Err: .0004882812
Iter:  12, Sum: .9997558594, Abs Err: .0002441406
Iter:  13, Sum: .9998779297, Abs Err: .0001220703
Iter:  14, Sum: .9999389648, Abs Err: .0000610352
Iter:  15, Sum: .9999694824, Abs Err: .0000305176
Iter:  16, Sum: .9999847412, Abs Err: .0000152588
Iter:  17, Sum: .9999923706, Abs Err: .0000076294
Iter:  18, Sum: .9999961853, Abs Err: .0000038147
Iter:  19, Sum: .9999980927, Abs Err: .0000019073
Iter:  20, Sum: .9999990463, Abs Err: .0000009537
Iter:  21, Sum: .9999995232, Abs Err: .0000004768
Iter:  22, Sum: .9999997616, Abs Err: .0000002384
Iter:  23, Sum: .9999998808, Abs Err: .0000001192
Iter:  24, Sum: .9999999404, Abs Err: .0000000596
Iter:  25, Sum: 1.0000000000, Abs Err: .0000000000
4.2.2.2 Example: estimating machine epsilon

The following code finds machine epsilon by shifting the rightmost bit of a binary number rightward until it falls off. Think about how it does this. Could you write an algorithm that finds machine epsilon using the function rshift that shifts the bits of float rightward?

 1: double precision :: eps
 2: integer, parameter :: dp = kind(eps)
 3: integer :: count = 1
 4: 
 5: eps = 1.0_dp
 6: do while (1.0_dp + eps*0.5 > 1.0_dp)
 7:    eps = eps*0.5
 8:    count = count+1
 9: end do
10: 
11: print*, eps, epsilon(eps)
12: print*, count, digits(eps)
2.2204460492503131E-016   2.2204460492503131E-016
       53          53

4.2.3 Example: the exit keyword

The exit keyword stops execution of code within the current scope.

The following code finds the hailstone sequence of \(a_1=6\) defined recursively by

\begin{equation*} a_{n+1} = \begin{cases} a_n/2 & \text{if } a_n \text{ is even}\\ 3a_n+1 & \text{ if } a_n \text{ is odd} \end{cases} \end{equation*}

for \(n\geq1\). It is an open conjecture that the hailstone sequence of any initial value \(a_1\) converges to the periodic sequence \(4, 2, 1, 4, 2, 1\ldots\). Luckily, it does for \(a_1=6\) and the following infinite do loop exits.

 1: integer :: a = 6, count = 1
 2: 
 3: ! infinite loop
 4: do
 5:    ! if a is even, divide by 2
 6:    ! otherwise multiply by 3 and add 1
 7:    if (mod(a,2)==0) then
 8:       a = a/2
 9:    else
10:       a = 3*a+1
11:    end if
12: 
13:    ! if a is 4, exit infinite loop
14:    if (a==4) then
15:       exit
16:    end if
17: 
18:    ! print count and a
19:    print "('count: ', i2, ', a: ', i2)", count, a
20: 
21:    ! increment count
22:    count = count + 1
23: end do
count:  1, a:  3
count:  2, a: 10
count:  3, a:  5
count:  4, a: 16
count:  5, a:  8

5 Input/Output

5.1 File input/output

5.1.1 Reading data from file

The contents of a data file can be read into an array using read. Suppose you have a file ./data/array.txt that contains two columns of data

1 1.23
2 2.34
3 3.45
4 4.56
5 5.67

This file can be opened with the open command. The required first argument of open is an integer that specifies a file unit for array.txt. Choose any number that is not in use. The unit numbers 0, 5, and 6 are reserved for system files and should not be used accidentally. Data are read in row-major format, i.e. across the first row, then across the second row, etc.

The following code reads the contents of ./data/array.txt into an array called array.

 1: ! declare array
 2: real :: array(5,2)
 3: integer :: row
 4: 
 5: ! open file and assign file unit 10
 6: open (10, file='./data/array.txt', action='read')
 7: 
 8: ! read data from file unit 10 into array
 9: do row = 1,5
10:    read(10,*) array(row,:)
11: end do
12: 
13: ! close file
14: close(10)

5.1.2 Writing data to file

Data can be written to a file with the write command.

 1: real :: x
 2: integer :: i, max = 5
 3: 
 4: ! open file, specify unit 10, overwrite if exists
 5: open(10, file='./data/sine.txt', action='write', status='replace')
 6: 
 7: do i = 0,max
 8:    x = i / real(max)
 9: 
10:    ! write to file unit 10
11:    write(10,*) x, sin(x)
12: end do

This produces a file sine.txt in the directory data containing

 0.00000000       0.00000000    
0.200000003      0.198669329    
0.400000006      0.389418334    
0.600000024      0.564642489    
0.800000012      0.717356086    
 1.00000000      0.841470957    

5.2 Formatted input/output

The format of a print, write, or read statement can be specified with a character string. A format character string replaces the * symbol in print* and the second * symbol in read(*,*) or write(*,*). A format string is a list of literal character strings or character descriptors from

  • a: character string
  • iW: integer
  • fW.D: float point
  • esW.DeE: scientific notation
  • Wx: space

where W, D, and E should be replaced by numbers specifying width, number of digits, or number of exponent digits, resp. The width of a formatted integer or float defaults to the width of the number when W is 0.

 1: character(32) :: fmt, a = 'word' 
 2: integer :: b = 1
 3: real :: c = 2.0, d = 3.0
 4: 
 5: ! character string and 4 space-delimited values
 6: print "('four values: ', a, 1x i0, 1x f0.1, 1x, es6.1e1)", trim(a), b, c, d
 7: 
 8: ! character string and 2 space-delimited values
 9: fmt = '(a, 2(f0.1, 1x))'
10: print fmt, 'two values: ', c, d
four values: word 1 2.0 3.0E+0
two values: 2.0 3.0

5.3 Command line arguments

Arguments can be passed to a program from the command line using get_command_argument. The first argument received by get_command_argument is the program executable file name and the remaining arguments are passed by the user. The following program accepts any number of arguments, each at most 32 characters, and prints them.

 1: program main
 2:   implicit none
 3: 
 4:   character(32) :: arg
 5:   integer :: n_arg = 0
 6: 
 7:   do
 8:      ! get next command line argument
 9:      call get_command_argument(n_arg, arg)
10: 
11:      ! if it is empty, exit
12:      if (len_trim(arg) == 0) exit
13: 
14:      ! print argument to screen
15:      print"('argument ', i0, ': ', a)", n_arg, trim(arg)
16: 
17:      ! increment count
18:      n_arg = n_arg+1
19:   end do
20: 
21:   ! print total number of arguments
22:   print "('number of arguments: ', i0)", n_arg
23: 
24: end program main

After compiling to a.out, you can pass arguments in the executing command.

./a.out 1 2 34
argument 0: ./a.out
argument 1: 1
argument 2: 2
argument 3: 34
number of arguments: 4

6 Functions/Subroutines

Functions and subroutines are callable blocks of code. A function returns a value from a set of arguments. A subroutine executes a block of code from a set of arguments but does not explicitly return a value. Changes to arguments made within a function are not returned whereas changes to arguments made within a subroutine can be returned to the calling program. Both functions and subroutines are defined after the contains keyword in a module or program.

6.1 Writing a function

The definition of a function starts with the name of the function followed by a list of arguments and return variable. The data types of the arguments and return variable are defined within the function body.

6.1.1 Example: linspace: generating a set of equally-space points

The following program defines a function linspace that returns a set of equidistant points on an interval. The main function makes a call to the function.

 1: program main
 2:   implicit none
 3: 
 4:   real :: xs(10)
 5: 
 6:   ! call function linspace to set values in xs
 7:   xs = linspace(0.0, 1.0, 10)
 8: 
 9:   ! print returned value of xs
10:   print "(10(f0.1, 1x))" , xs
11: 
12: contains
13: 
14:   ! linspace: return a set of equidistant points on an interval
15:   ! min: minimum value of interval
16:   ! max: maximum value of interval
17:   ! n_points: number of points in returned set
18:   ! xs: set of points
19:   function linspace(min, max, n_points) result(xs)
20:     real :: min, max, dx
21:     integer :: n_points
22:     integer :: i
23:     real :: xs(n_points)
24: 
25:     ! calculate width of subintervals
26:     dx = (max-min) / real(n_points-1)
27: 
28:     ! fill xs with points
29:     do i = 1,n_points
30:        xs(i) = min + (i-1)*dx
31:     end do
32: 
33:   end function linspace
34: 
35: end program main
.0 .1 .2 .3 .4 .6 .7 .8 .9 1.0

6.2 Writing a subroutine

The definition of a subroutine begins with the name of the subroutine and list of arguments. Arguments are defined within the subroutine body with one of the following intents

  • intent(in): changes to the argument are not returned
  • intent(inout): changes to the argument are returned
  • intent(out): the initial value of the argument is ignored and changes to the argument are returned.

Subroutines are called using the call keyword followed by the subroutine name.

6.2.1 Example: polar coordinates

The following code defines a subroutine polar_coord that returns the polar coordinates \((r,\theta)\) defined by \(r=\sqrt{x^2+y^2}\) and \(\theta=\arctan(y/x)\) from the rectangular coordinate pair \((x,y)\).

 1: program main
 2: 
 3:   real :: x = 1.0, y = 1.0, rad, theta
 4: 
 5:   ! call subroutine that returns polar coords
 6:   call polar_coord(x, y, rad, theta)
 7:   print*, rad, theta
 8: 
 9: contains
10: 
11:   ! polar_coord: return the polar coordinates of a rect coord pair
12:   ! x,y: rectangular coord
13:   ! rad,theta: polar coord
14:   subroutine polar_coord(x, y, rad, theta)
15:     real, intent(in) :: x, y
16:     real, intent(out) :: rad, theta
17: 
18:     ! compute polar coord
19:     ! hypot = sqrt(x**2+y**2) is an intrinsic function
20:     ! atan2 = arctan with correct sign is an intrinsic function
21:     rad = hypot(x, y)
22:     theta = atan2(y, x)
23: 
24:     end subroutine polar_coord
25: 
26: end program main
1.41421354      0.785398185

6.3 Passing procedures as arguments

An inteface can be used to pass a function or subroutine to another function or a subroutine. For this purpose, an interface is defined in the receiving procedure essentially the same way as the passed procedure itself but with only declarations and not the implementation.

6.3.1 Example: Newton's method for rootfinding

Newton's method for finding the root of a function \(f:\mathbb{R}\rightarrow\mathbb{R}\) refines an initial guess \(x_0\) according to the iteration rule

\begin{equation*} x_{n+1}=x_n-\frac{f(x_n)}{f'(x_n)} \end{equation*}

for \(n\geq1\) until \(f(x)\) is less than a chosen tolerance or a maximum number of iterations.

The following code defines a subroutine newton_root that returns a root of an input function as well as the number of iterations of Newton's method used to find the root. It is called by the main program to approximate the positive root of \(f(x)=x^2-2\) from an initial guess \(x_0=1\).

 1: program main
 2:   implicit none
 3: 
 4:   character(64) :: fmt
 5:   real :: x = 1.0
 6:   integer :: iter = 1000
 7: 
 8:   ! call newton rootfinding function
 9:   call newton_root(f, df, x, iter, 1e-6, .true.)
10: 
11:   ! print found root and number of iterations used
12:   fmt = "('number of iterations: ', i0, ', x: ', f0.7, ', f(x): ', f0.7)"
13:   print fmt, iter, x, f(x)
14: 
15: contains
16: 
17:   ! function f(x) = x^2 - 2
18:   function f(x) result(y)
19:     real :: x, y
20:     y = x*x - 2
21:   end function f
22: 
23:   ! function df(x) = 2x
24:   function df(x) result(dy)
25:     real :: x, dy
26:     dy = 2*x
27:   end function df
28: 
29:   ! newton_root: newtons method for rootfinding
30:   ! f: function with root
31:   ! df: derivative of f
32:   ! x: sequence iterate
33:   ! iter: max number of iterations at call, number of iterations at return
34:   ! tol: absolute tolerance
35:   ! print_iters: boolean to toggle verbosity
36:   subroutine newton_root(f, df, x, iter, tol, print_iters)
37: 
38:     ! interface to function f
39:     interface
40:        function f(x) result(y)
41: 	 real :: x, y
42:        end function f
43:     end interface
44: 
45:     ! interface to function df
46:     interface
47:        function df(x) result(dy)
48: 	 real :: x, dy
49:        end function df
50:     end interface
51: 
52:     real, intent(inout) :: x
53:     real, intent(in) :: tol
54:     integer, intent(inout) :: iter
55:     logical, intent(in) :: print_iters
56:     integer :: max_iters
57: 
58:     max_iters = iter
59:     iter = 0
60: 
61:     ! while f(x) greater than absolute tolerance
62:     ! and max number of iterations not exceeded
63:     do while (abs(f(x))>tol.and.iter<max_iters)
64:        ! print current x and f(x)
65:        if (print_iters) print "('f(', f0.7, ') = ', f0.7)", x, f(x)
66: 
67:        ! Newton's update rule
68:        x = x - f(x)/df(x)
69: 
70:        ! increment number of iterations
71:        iter = iter + 1
72:     end do
73: 
74:   end subroutine newton_root
75: 
76: end program main
f(1.0000000) = -1.0000000
f(1.5000000) = .2500000
f(1.4166666) = .0069444
f(1.4142157) = .0000060
number of iterations: 4, x: 1.4142135, f(x): -.0000001

6.3.2 Example: The midpoint rule for definite integrals

The midpoint rule approximates the definite integral \(\int_a^bf(x)~dx\) with integrand \(f:\mathbb{R}\rightarrow\mathbb{R}\) by

\begin{equation} \Delta x\sum_{i=1}^nf(\bar{x}_i) \end{equation}

where \(\Delta x=(b-a)/n\), \(x_i=a+i\Delta x\) and \(\bar{x}_i=(x_{i-1}+x_i)/2\).

The following code defines a function midpoint that computes the approximation eq. 1 given \(a\), \(b\), and \(n\). The main program calls midpoint to approximate the definite integral of \(f(x)=1/x\) on \([1,e]\) for a range of \(n\).

 1: program main
 2:   implicit none
 3: 
 4:   real, parameter :: E = exp(1.)
 5:   integer :: n
 6:   real :: integral
 7: 
 8:   ! Approximate the integral of 1/x from 1 to e
 9:   ! with the midpoint rule for a range of number of subintervals
10:   do n = 2,20,2
11:      print "('n: ', i0, ', M_n: ', f0.6)", n, midpoint(f, 1.0, E, n)
12:   end do
13: 
14: contains
15: 
16:   ! function f(x) = 1/x
17:   function f(x) result(y)
18:     real :: x, y
19:     y = 1.0/x
20:   end function f
21: 
22:   ! midpoint: midpoint rule for definite integral
23:   ! f: integrand
24:   ! a: left endpoint of interval of integration
25:   ! b: right endpoint of interval of integration
26:   ! n: number of subintervals
27:   ! sum: approximate definite integral
28:   function midpoint(f, a, b, n) result(sum)
29: 
30:     ! interface to f
31:     interface
32:        function f(x)
33: 	 real :: x, y
34:        end function f
35:     end interface
36: 
37:     real :: a, b, min, xi, dx, sum
38:     integer :: n, i
39: 
40:     ! subinterval increment
41:     dx = (b-a)/real(n)
42:     ! minimum to increment from
43:     min = a - dx/2.0
44: 
45:     ! midpoint rule
46:     do i = 1,n
47:        xi = min + i*dx
48:        sum = sum + f(xi)
49:     end do
50:     sum = sum*dx
51:   end function midpoint
52: 
53: end program main
n: 2, M_n: .976360
n: 4, M_n: .993575
n: 6, M_n: .997091
n: 8, M_n: .998353
n: 10, M_n: .998942
n: 12, M_n: .999264
n: 14, M_n: .999459
n: 16, M_n: .999585
n: 18, M_n: .999672
n: 20, M_n: .999735

6.4 Polymorphism

An interface can be used as an entry into two different implementations of a subroutine or function with the same name so long as the different implementations have different argument signatures. This may be particularly useful for defining both a single precision and double precision version of a function or subroutine.

6.4.1 Example: machine epsilon

The following code implements two versions of a function that computes machine epsilon in either single or double precision. The different implementations are distinguished by their arguments. The single precision version mach_eps_sp accepts one single precision float and the double precision version mach_eps_dp accepts one double precision float. Both functions are listed in the interface and can be called by its name mach_eps.

 1: program main
 2:   implicit none
 3: 
 4:   integer, parameter :: sp = kind(0.0)
 5:   integer, parameter :: dp = kind(0.d0)
 6: 
 7:   interface mach_eps
 8:      procedure mach_eps_sp, mach_eps_dp
 9:   end interface mach_eps
10: 
11:   print*, mach_eps(0.0_sp), epsilon(0.0_sp)
12:   print*, mach_eps(0.0_dp), epsilon(0.0_dp)
13: 
14: contains
15: 
16:   function mach_eps_sp(x) result(eps)
17:     real(sp) :: x, eps
18:     integer :: count = 0
19: 
20:     eps = 1.0_sp
21:     do while (1.0_sp + eps*0.5 > 1.0_sp)
22:        eps = eps*0.5
23:        count = count+1
24:     end do
25:   end function mach_eps_sp
26: 
27:   function mach_eps_dp(x) result(eps)
28:     real(dp) :: x, eps
29:     integer :: count = 0
30: 
31:     eps = 1.0_dp
32:     do while (1.0_dp + eps*0.5 > 1.0_dp)
33:        eps = eps*0.5
34:        count = count+1
35:     end do
36:   end function mach_eps_dp
37: 
38: end program main
1.19209290E-07   1.19209290E-07
2.2204460492503131E-016   2.2204460492503131E-016

6.5 Recursion

A function or subroutine that calls itself must be defined with the recursive keyword preceding the construct name.

6.5.1 Example: factorial

The following code defines a recursive function factorial that computes \(n!\). If \(n>1\), the function call itself to return \(n(n-1)!\), otherwise the function returns \(1\). The main program calls factorial to compute \(5!\).

 1: program main
 2:   implicit none
 3: 
 4:   ! print 5 factorial
 5:   print*, factorial(5)
 6: 
 7: contains
 8: 
 9:   ! factorial(n): product of natural numbers up to n
10:   ! n: integer argument
11:   recursive function factorial(n) result(m)
12:     integer :: n, m
13: 
14:     ! if n>1, call factorial recursively
15:     ! otherwise 1 factorial is 1
16:     if (n>1) then
17:        m = n*factorial(n-1)
18:     else
19:        m = 1
20:     end if
21: 
22:   end function factorial
23: 
24: end program main
120

7 Object-oriented programming

7.1 Derived types

Data types can be defined by the programmer. Variables and procedures that belong to a defined data type are declared between a type / end type pair. Type-bound procedures, i.e. functions and subroutines, are defined by the procedure keyword followed by :: and the name of the procedure within the type / end type pair after the contains keyword. A variable with defined type is declared with the type keyword and the name of the type. The variables and procedures of a defined type variable can be accessed by appending a % symbol to the name of the variable.

 1: ! define a 'matrix' type
 2: ! type-bound variables: shape, data
 3: ! type-bound procedures: construct, destruct
 4: type matrix
 5:    integer :: shape(2)
 6:    real, allocatable :: data(:,:)
 7:  contains
 8:    procedure :: construct
 9:    procedure :: destruct
10: end type matrix
11: 
12: ! declare a matrix variable
13: type(matrix) :: mat
14: 
15: ! assign value to type-bound variable
16: mat%shape = [3,3]

7.2 Modules

A type-bound procedure can be defined after the contains keyword in the same program construct, i.e. a module, as the type definition. The first argument in the definition of a type-bound procedure is of the defined type and is declared within the procedure body with the class keyword and the name of the type.

 1: module matrix_module
 2:   implicit none
 3: 
 4:   type matrix
 5:      integer :: shape(2)
 6:      real, allocatable :: data(:,:)
 7:    contains
 8:      procedure :: construct
 9:      procedure :: destruct
10:   end type matrix
11: 
12: contains
13: 
14:   ! construct: populate shape and allocate memory for matrix
15:   ! m,n: number of rows,cols of matrix
16:   subroutine construct(this, m, n)
17:     class(matrix) :: this
18:     integer :: m, n
19:     this%shape = [m,n]
20:     allocate(this%data(m,n))
21:   end subroutine construct
22: 
23:   ! destruct: deallocate memory that matrix occupies
24:   subroutine destruct(this)
25:     class(matrix) :: this
26:     deallocate(this%data)
27:   end subroutine destruct
28: 
29: end module matrix_module

To define variables of the matrix type in the main program, tell it to use the module defined above with use matrix_module immediately after the program main line. The procedures bound to a defined type can be access through variables of that type by appending the % symbol to the name of the variable.

 1: program main
 2:   use matrix_module
 3:   implicit none
 4: 
 5:   type(matrix) :: mat
 6:   mat%shape = [3,3]
 7: 
 8:   ! create matrix
 9:   call mat%construct(3,3)
10: 
11:   ! treat matrix variable 'data' like an array
12:   mat%data(1,1) = 1.0
13:   ! etc...
14: 
15:   ! destruct matrix
16:   call matrix%destruct()
17: end program main

7.3 Example: determinant of random matrix

The following module defines a matrix type with two variables: an integer array shape that stores the number of rows and columns of the matrix and a real array data that stores the elements of the matrix. The type has four procedures: a subroutine construct that sets the shape and allocates memory for the data, a subroutine destruct that deallocates memory, a subroutine print that prints a matrix, and a function det that computes the determinant of a matrix. Note det is based on the definition of determinant using cofactors, and is very inefficient. A function random_matrix defined within the module generates a matrix with uniform random entries in \([-1,1]\).

  1: module matrix_module
  2:   implicit none
  3: 
  4:   type matrix
  5:      integer :: shape(2)
  6:      real, allocatable :: data(:,:)
  7:    contains
  8:      procedure :: construct
  9:      procedure :: destruct
 10:      procedure :: print
 11:      procedure :: det
 12:   end type matrix
 13: 
 14: contains
 15: 
 16:   subroutine construct(this, m, n)
 17:     class(matrix) :: this
 18:     integer :: m,n
 19:     this%shape = [m,n]
 20:     allocate(this%data(m,n))
 21:   end subroutine construct
 22: 
 23:   subroutine destruct(this)
 24:     class(matrix) :: this
 25:     deallocate(this%data)
 26:   end subroutine destruct
 27: 
 28:   ! print: formatted print of matrix
 29:   subroutine print(this)
 30:     class(matrix) :: this
 31:     ! row_fmt: format character string for row printing
 32:     ! fmt: temporary format string
 33:     character(32) :: row_fmt, fmt = '(a,i0,a,i0,a,i0,a)'
 34:     ! w: width of each entry printed
 35:     ! d: number of decimal digits printed
 36:     integer :: w, d = 2, row
 37:     ! find largest width of element in matrix
 38:     w = ceiling(log10(maxval(abs(this%data)))) + d + 2
 39:     ! write row formatting to 'row_fmt' variable
 40:     write(row_fmt,fmt) '(',this%shape(2),'(f',w,'.',d,',1x))'
 41:     ! print matrix row by row
 42:     do row = 1,this%shape(1)
 43:        print row_fmt, this%data(row,:)
 44:     end do
 45:   end subroutine print
 46: 
 47:   ! det: compute determinant of matrix
 48:   ! using recursive definition based on cofactors
 49:   recursive function det(this) result(d)
 50:     class(matrix) :: this
 51:     type(matrix) :: submatrix
 52:     real :: d, sgn, element, minor
 53:     integer :: m, n, row, col, i, j
 54: 
 55:     m = this%shape(1)
 56:     n = this%shape(2)
 57:     d = 0.0
 58: 
 59:     ! compute cofactor
 60:     ! if 1x1 matrix, return value
 61:     if (m==1.and.n==1) then
 62:        d = this%data(1,1)
 63:     ! if square and not 1x1
 64:     else if (m==n) then
 65:        ! cofactor sum down the first column
 66:        do row = 1,m
 67: 	  ! sign of term
 68: 	  sgn = (-1.0)**(row+1)
 69: 	  ! matrix element
 70: 	  element = this%data(row,1)
 71: 	  ! construct the cofactor submatrix and compute its determinant
 72: 	  call submatrix%construct(m-1,n-1)
 73: 	  if (row==1) then
 74: 	     submatrix%data = this%data(2:,2:)
 75: 	  else if (row==m) then
 76: 	     submatrix%data = this%data(:m-1,2:)
 77: 	  else
 78: 	     submatrix%data(:row-1,:) = this%data(:row-1,2:)
 79: 	     submatrix%data(row:,:) = this%data(row+1:,2:)
 80: 	  end if
 81: 	  minor = submatrix%det()
 82: 	  call submatrix%destruct()
 83: 
 84: 	  ! determinant accumulator
 85: 	  d = d + sgn*element*minor
 86:        end do
 87:     end if
 88:   end function det
 89: 
 90:   ! random_matrix: generate matrix with random entries in [-1,1]
 91:   ! m,n: number of rows,cols
 92:   function random_matrix(m,n) result(mat)
 93:     integer :: m,n,i,j
 94:     type(matrix) :: mat
 95:     ! allocate memory for matrix
 96:     call mat%construct(m,n)
 97:     ! seed random number generator
 98:     call srand(time())
 99:     ! populate matrix
100:     do i = 1,m
101:        do j = 1,n
102: 	  mat%data(i,j) = 2.0*rand() - 1.0
103:        end do
104:     end do
105:   end function random_matrix
106: 
107: end module matrix_module

The main program uses the matrix_module defined above to find the determinants of a number of random matrices of increasing size.

 1: program main
 2:   use matrix_module
 3:   implicit none
 4: 
 5:   type(matrix) :: mat
 6:   integer :: n
 7: 
 8:   ! compute determinants of random matrices
 9:   do n = 1,5
10:      ! generate random  matrix
11:      mat = random_matrix(n,n)
12: 
13:      ! print determinant of matrix
14:      print "('n: ', i0, ', det: ', f0.5)", n, det(mat)
15: 
16:      ! destruct matrix
17:      call mat%destruct()
18:   end do
19: 
20: end program main
./main
n: 1, det: -.68676
n: 2, det: .45054
n: 3, det: .37319
n: 4, det: -.27328
n: 5, det: .26695

7.4 Example: matrix module

  1: module matrix_module
  2:   implicit none
  3: 
  4:   public :: zeros
  5:   public :: identity
  6:   public :: random
  7: 
  8:   type matrix
  9:      integer :: shape(2)
 10:      real, allocatable :: data(:,:)
 11:    contains
 12:      procedure :: construct => matrix_construct
 13:      procedure :: destruct => matrix_destruct
 14:      procedure :: norm => matrix_norm
 15:   end type matrix
 16: 
 17:   type vector
 18:      integer :: length
 19:      real, allocatable :: data(:)
 20:    contains
 21:      procedure :: construct => vector_construct
 22:      procedure :: destruct => vector_destruct
 23:      procedure :: norm => vector_norm
 24:   end type vector
 25: 
 26:   ! assignments
 27:   interface assignment(=)
 28:      procedure vec_num_assign, vec_vec_assign, mat_num_assign, mat_mat_assign
 29:   end interface assignment(=)
 30: 
 31:   ! operations
 32:   interface operator(+)
 33:      procedure vec_vec_sum, mat_mat_sum
 34:   end interface operator(+)
 35: 
 36:   interface operator(-)
 37:      procedure vec_vec_diff, mat_mat_diff
 38:   end interface operator(-)
 39: 
 40:   interface operator(*)
 41:      procedure num_vec_prod, num_mat_prod, mat_vec_prod, mat_mat_prod
 42:   end interface operator(*)
 43: 
 44:   interface operator(/)
 45:      procedure vec_num_quot, mat_num_quot
 46:   end interface operator(/)
 47: 
 48:   interface operator(**)
 49:      procedure mat_pow
 50:   end interface operator(**)
 51: 
 52:   ! functions
 53:   interface norm
 54:      procedure vector_norm, matrix_norm
 55:   end interface norm
 56: 
 57:   ! structured vectors/matrices
 58:   interface zeros
 59:      procedure zeros_vector, zeros_matrix
 60:   end interface zeros
 61: 
 62:   interface random
 63:      procedure random_vector, random_matrix
 64:   end interface random
 65: 
 66: contains
 67: 
 68:   subroutine matrix_construct(this, m, n)
 69:     class(matrix) :: this
 70:     integer :: m,n
 71:     this%shape = [m,n]
 72:     allocate(this%data(m,n))
 73:   end subroutine matrix_construct
 74: 
 75:   subroutine vector_construct(this, n)
 76:     class(vector) :: this
 77:     integer :: n
 78:     this%length = n
 79:     allocate(this%data(n))
 80:   end subroutine vector_construct
 81: 
 82:   subroutine matrix_destruct(this)
 83:     class(matrix) :: this
 84:     deallocate(this%data)
 85:   end subroutine matrix_destruct
 86: 
 87:   subroutine vector_destruct(this)
 88:     class(vector) :: this
 89:     deallocate(this%data)
 90:   end subroutine vector_destruct
 91: 
 92:   ! assignment
 93:   subroutine vec_num_assign(vec,num)
 94:     type(vector), intent(inout) :: vec
 95:     real, intent(in) :: num
 96:     vec%data = num
 97:   end subroutine vec_num_assign
 98: 
 99:   subroutine vec_vec_assign(vec1,vec2)
100:     type(vector), intent(inout) :: vec1
101:     type(vector), intent(in) :: vec2
102:     vec1%data = vec2%data
103:   end subroutine vec_vec_assign
104: 
105:   subroutine mat_num_assign(mat,num)
106:     type(matrix), intent(inout) :: mat
107:     real, intent(in) :: num
108:     mat%data = num
109:   end subroutine mat_num_assign
110: 
111:   subroutine mat_mat_assign(mat1,mat2)
112:     type(matrix), intent(inout) :: mat1
113:     type(matrix), intent(in) :: mat2
114:     mat1%data = mat2%data
115:   end subroutine mat_mat_assign
116: 
117:   ! operations
118:   function vec_vec_sum(vec1,vec2) result(s)
119:     type(vector), intent(in) :: vec1, vec2
120:     type(vector) :: s
121:     call s%construct(vec1%length)
122:     s%data = vec1%data + vec2%data
123:   end function vec_vec_sum
124: 
125:   function mat_mat_sum(mat1,mat2) result(s)
126:     type(matrix), intent(in) :: mat1, mat2
127:     type(matrix) :: s
128:     call s%construct(mat1%shape(1),mat1%shape(2))
129:     s%data = mat1%data+mat2%data
130:   end function mat_mat_sum
131: 
132:   function vec_vec_diff(vec1,vec2) result(diff)
133:     type(vector), intent(in) :: vec1, vec2
134:     type(vector) :: diff
135:     call diff%construct(vec1%length)
136:     diff%data = vec1%data-vec2%data
137:   end function vec_vec_diff
138: 
139:   function mat_mat_diff(mat1,mat2) result(diff)
140:     type(matrix), intent(in) :: mat1, mat2
141:     type(matrix) :: diff
142:     call diff%construct(mat1%shape(1),mat1%shape(2))
143:     diff%data = mat1%data-mat2%data
144:   end function mat_mat_diff
145: 
146:   function num_vec_prod(num,vec) result(prod)
147:     real, intent(in) :: num
148:     type(vector), intent(in) :: vec
149:     type(vector) :: prod
150:     call prod%construct(vec%length)
151:     prod%data = num*vec%data
152:   end function num_vec_prod
153: 
154:   function num_mat_prod(num,mat) result(prod)
155:     real, intent(in) :: num
156:     type(matrix), intent(in) :: mat
157:     type(matrix) :: prod
158:     call prod%construct(mat%shape(1),mat%shape(2))
159:     prod%data = num*mat%data
160:   end function num_mat_prod
161: 
162:   function mat_vec_prod(mat,vec) result(prod)
163:     type(matrix), intent(in) :: mat
164:     type(vector), intent(in) :: vec
165:     type(vector) :: prod
166:     call prod%construct(mat%shape(1))
167:     prod%data = matmul(mat%data,vec%data)
168:   end function mat_vec_prod
169: 
170:   function mat_mat_prod(mat1,mat2) result(prod)
171:     type(matrix), intent(in) :: mat1, mat2
172:     type(matrix) :: prod
173:     call prod%construct(mat1%shape(1),mat2%shape(2))
174:     prod%data = matmul(mat1%data,mat2%data)
175:   end function mat_mat_prod
176: 
177:   function vec_num_quot(vec,num) result(quot)
178:     type(vector), intent(in) :: vec
179:     real, intent(in) :: num
180:     type(vector) :: quot
181:     call quot%construct(vec%length)
182:     quot%data = vec%data/num
183:   end function vec_num_quot
184: 
185:   function mat_num_quot(mat,num) result(quot)
186:     type(matrix), intent(in) :: mat
187:     real, intent(in) :: num
188:     type(matrix) :: quot
189:     call quot%construct(mat%shape(1),mat%shape(2))
190:     quot%data = mat%data/num
191:   end function mat_num_quot
192: 
193:   function mat_pow(mat1,pow) result(mat2)
194:     type(matrix), intent(in) :: mat1
195:     integer, intent(in) :: pow
196:     type(matrix) :: mat2
197:     integer :: i
198:     mat2 = mat1
199:     do i = 2,pow
200:        mat2 = mat1*mat2
201:     end do
202:   end function mat_pow
203: 
204:   ! functions
205:   function vector_norm(this,p) result(mag)
206:     class(vector), intent(in) :: this
207:     integer, intent(in) :: p
208:     real :: mag
209:     integer :: i
210:     ! inf-norm
211:     if (p==0) then
212:        mag = 0.0
213:        do i = 1,this%length
214: 	  mag = max(mag,abs(this%data(i)))
215:        end do
216:     ! p-norm
217:     else if (p>0) then
218:        mag = (sum(abs(this%data)**p))**(1./p)
219:     end if
220:   end function vector_norm
221: 
222:   function matrix_norm(this, p) result(mag)
223:     class(matrix), intent(in) :: this
224:     integer, intent(in) :: p
225:     real ::  mag, tol = 1e-6
226:     integer :: m, n, row, col, iter, max_iters = 1000
227:     type(vector) :: vec, last_vec
228:     m = size(this%data(:,1)); n = size(this%data(1,:))
229: 
230:     ! entry-wise norms
231:     if (p<0) then
232:        mag = (sum(abs(this%data)**(-p)))**(-1./p)
233:     ! inf-norm
234:     else if (p==0) then
235:        mag = 0.0
236:        do row = 1,m
237: 	  mag = max(mag,sum(abs(this%data(row,:))))
238:        end do
239:     ! 1-norm
240:     else if (p==1) then
241:        mag = 0.0
242:        do col = 1,n
243: 	  mag = max(mag,sum(abs(this%data(:,col))))
244:        end do
245:     ! p-norm
246:     else if (p>0) then
247:        vec = random(n)
248:        vec = vec/vec%norm(p)
249:        last_vec = zeros(n)
250:        mag = 0.0
251:        do iter = 1,max_iters
252: 	  last_vec = vec
253: 	  vec = this*last_vec
254: 	  vec = vec/vec%norm(p)
255: 	  if (vector_norm(vec-last_vec,p)<tol) exit
256:        end do
257:        mag = vector_norm(this*vec,p)
258:     end if
259:   end function matrix_norm
260: 
261:   ! structured vectors/matrices
262:   function random_matrix(m,n) result(mat)
263:     integer :: m,n
264:     type(matrix) :: mat
265:     call mat%construct(m,n)
266:     call random_seed()
267:     call random_number(mat%data)
268:   end function random_matrix
269: 
270:   function random_vector(n) result(vec)
271:     integer :: n
272:     type(vector) :: vec
273:     call vec%construct(n)
274:     call random_seed()
275:     call random_number(vec%data)
276:   end function random_vector
277: 
278:   function zeros_vector(n) result(vec)
279:     integer :: n
280:     type(vector) :: vec
281:     call vec%construct(n)
282:     vec = 0.0
283:   end function zeros_vector
284: 
285:   function zeros_matrix(m,n) result(mat)
286:     integer :: m,n
287:     type(matrix) :: mat
288:     call mat%construct(m,n)
289:     mat = 0.0
290:   end function zeros_matrix
291: 
292:   function identity(m,n) result(mat)
293:     integer :: m,n,i
294:     type(matrix) :: mat
295:     call mat%construct(m,n)
296:     do i = 1,min(m,n)
297:        mat%data(i,i) = 1.0
298:     end do
299:   end function identity
300: 
301: end module matrix_module
 1: program main
 2:   use matrix_module
 3:   implicit none
 4: 
 5:   type(vector) :: vec1, vec2
 6:   type(matrix) :: mat1, mat2
 7:   real :: x
 8:   integer :: i
 9: 
10:   ! 0s, id, random
11:   mat1 = zeros(3,3)
12:   call mat1%destruct()
13:   mat1 = identity(3,3)
14:   mat2 = random(3,3)
15:   mat1 = mat1*mat1
16:   vec1 = zeros(3)
17:   call vec1%destruct()
18:   vec1 = random(3)
19:   vec2 = random(3)
20:   ! +,-,*,/,**
21:   mat1 = mat1+mat2
22:   vec1 = vec1+vec2
23:   mat1 = mat1-mat2
24:   vec1 = vec1-vec2
25:   vec1 = mat1*vec2
26:   mat1 = mat2*mat1
27:   mat1 = 2.0*mat1
28:   vec1 = 2.0*vec1
29:   mat1 = mat1/2.0
30:   vec1 = vec1/2.0
31:   mat2 = mat1**3
32:   ! norm
33:   x = norm(vec1,0)
34:   x = norm(vec1,1)
35:   x = norm(mat1,-1)
36:   x = norm(mat1,0)
37:   x = norm(mat1,1)
38:   x = norm(mat1,2)
39:   call vec1%destruct
40:   call vec2%destruct
41:   call mat1%destruct
42:   call mat2%destruct
43: end program main
./main
2          14   1.03737926       3.62866984E-07
3          10  0.976910591       1.07453801E-07
4           9   1.84589541       2.06476543E-07
5          10   2.39860868       2.45756240E-07

:snippets:

:end: