Fortran is case insensitive: FUNCTION
means the same as function
. We write all Fortran constructs in lower case. This includes, but is not limited to, the following: program
, module
, type
, class
, intent
, if
, do
, case
, select
, subroutine
, function
, pure
, elemental
, recursive
, integer
, real
, and logical
. Do not, under any circumstance, write in all-caps. All-caps things are hard to read and hard to type.
Write intrinsic math functions in lower case: sqrt
, exp
, log
, nint
, max
, mod
, modulo
, pack
, count
, etc.
For certain situations, the mathematical notation distinguishes between the upper and lower case. The Gamma
instead of gamma
.
Use the same rules for user-defined symbols.
Use snake_case
, not camelCase
. The latter is notoriously difficult to read, especially for long, descriptive names.
Use common sense when writing descriptive names. If it is clear from the context of the code, using i
, j
, and k
to denote Cartesian indices is perfectly fine. You definitely should not write Cartesian_index_in_direction_x_hat
! That’s just silly. Similarly, linspace
and derivx
are fine. On the other hand, it is better to write field_coupling_term
instead of fieldcouplingterm
. Most definitely, do not write monstrosities like fcouptrm
.
Your code should be properly indented. The following is acceptable:
do j = 1, size(B)
C(:, j) = A(:)*B(j)
end do
while the following is not:
do j = 1, size(B)
C(:, j) = A(:)*B(j)
end do
A good text editor allows selecting a region and pressing TAB
or something similar to automatically apply proper indentation. If your editor does not do that, get a better editor. Set your TAB
to 2 spaces for the indentation level under `program`, `module`, `subroutine`, and `function`, and to 3 spaces for `if` and `do`.
Write code the way you would write an essay. Use spaces and linebreaks to improve readability and separate conceptually distinct sections of the calculation. Below are some examples.
The following is more readable
pure elemental real(r64) function Fermi(e, chempot, T)
!! e Energy in eV
!! chempot Chemical potential in eV
!! T temperature in K
real(r64), intent(in) :: e, chempot, T
Fermi = 1.0_r64/(exp((e - chempot)/kB/T) + 1.0_r64)
end function Fermi
compared to
pure elemental real(r64) function Fermi(e, chempot, T)
!! e Energy in eV
!! chempot Chemical potential in eV
!! T temperature in K
real(r64), intent(in) :: e, chempot, T
Fermi = 1.0_r64/(exp((e - chempot)/kB/T) + 1.0_r64)
end function Fermi
The former reads better because conceptually different sections of the code are separated by a line break.
The following is an acceptable use of spaces around mathematical operations:
fx = sin(x)/(1 + (2 - x**2))
while the following is not:
fx=sin( x ) / (1+(2-x**2))
Several issues with the second code snippet. When we do math on paper, we write +
and -
, but not around *
, /
, and **
.
Use comma like you would in writing. So, write thing1, thing2
, not thing1,thing2
.
Following are some examples where one or more rules have been broken in the second column. Try to identify all the issues.
do | don’t |
---|---|
if(condition) | if( condition ) |
do i = 1, 3 | do i=1,3 |
call do_magic(hat, bunny) | call do_magic (hat,bunny) |
real(r64), intent(in) :: data(:, :) | real(r64), intent(in) :: data(:,:) |
interface operator(.umklapp.) | interface operator( .umklapp. ) |
use params, only: kB, twopi | use params,only : kB,twopi |
Use modern notation everywhere.
do | don’t |
---|---|
> and >= | .gt. and .ge. |
< and <= | .lt. and le |
== | .eq. |
/= | .ne |
[1, 2, 3] | (/1, 2, 3/) |
Keep your code brief by using a functional style over an imperative style. It is preferable to write
!Assume A, B, and C are NxN matrices and m is a scalar.
A = A*B !elementwise multiplication
C = A + B !elementwise addition
C = m*C !elementwise multiplication with a scalar
instead of
!Assume A, B, and C are NxN matrices and m is a scalar.
N = size(A, 1)
do j = 1, N
do i = 1, N
A(i, j) = A(i, j)*B(i, j)
end do
end do
do j = 1, N
do i = 1, N
C(i, j) = A(i, j) + B(i, j)
end do
end do
do j = 1, N
do i = 1, N
C(i, j) = m*C(i, j)
end do
end do
It is rather obvious why the first is preferable.
Fortran arrays are saved in a column major manner. C, on the other hand, is row major. Mind the difference when you access array slices.
Instead of
do j = 1, N
do i = 1, N
if(use_special_algo) then
y = function_special(i, j)
else
y = function_boring(i, j)
end if
end do
end do
consider pulling out the conditional outside by doing
function_pointer => function_boring
if(use_special_algo) function_pointer => function_special
do j = 1, N
do i = 1, N
y = function_pointer(i, j)
end do
end do
nullify(function_pointer)
In order to do the latter, you will have to define an abstract interface
for function_special
and function_boring
. Remember to nullify any associated pointers before exiting a procedure.
For condition based switching, use select case
over if
and else if
. So do
select case(particle)
case('el')
print*, "I'm an electron."
case('ph')
print*, "I'm a phonon."
case('pl')
print*, "I'm a plasmon."
case default
print*, "I don't know who I am."
end select
instead of
if(particle == 'el') then
print*, "I'm an electron."
else if(particle == 'ph') then
print*, "I'm a phonon."
else if(particle == 'pl') then
print*, "I'm a plasmon."
else
print*, "I don't know who I am."
end if
Every program
and module
should have implicit none
declared up top.
Please.
Always specify exactly what you need from a module. That is, always do use module_something, only: thing1, thing2
instead of just use module_something
.
No other variable should be. Subroutines should not access global variables. Write pure
subroutines and functions whenever possible. Strictly control data access and modification rights with the intent
keyword.
Use objects but don’t write Java. Use the functional style but don’t try to mimic Haskell. The style we use can be called object-based procedural. Try to strike a good balance between performance, readability, and extensibility. Think La Sagrada Familia, not the Sistine Chapel.