Fortran Guidelines#
This section provides general guidelines for Fortran code developed in the Bristol Composites Institute. These guidelines have been developed to encourage best-practices that avoid common pitfalls and to ensure consistency across all projects.
Visual Layout#
The visual layout of code heavily influences how easy it is to read by those unfamiliar with it. Code with a good layout allows you to quickly recognise familiar constructs such as functions, loops, if/else blocks, etc., and where they start and end. Code layout is largely governed by whitespace, specifically:
Indentation
Blank lines
Spacing around variables and operators
Line length
Format#
Fortran has two standard formats for code layout:
Fixed-form (legacy)
Free-form
Recommendation
Where possible, use modern free-form Fortran instead of fixed-form.
Rationale
Fixed-form Fortran is a legacy format that should be avoided for several reasons: it reduces readability of the code; it needlessly increases developer workload and; it permits ambiguities that lead to obscure errors.See also
Abaqus: see this guide for how to use free-form with Abaqus user subroutines.
Indentation#
Recommendation
Always indent the body of functions, subroutines, loops, if/else blocks and similar such constructs.
Be consistent with the number of spaces used per indentation level.
Rationale
Indentation is a standard technique for visually communicating the structure of codeRecommendation
Do not use a different indentation for comments; always align comments with their associated code.
Rationale
Using a different indentation for comments breaks up the indentation of code constructs making it difficult to visualise where they start and end. Comment visibility is enhanced by using a code editor with proper syntax highlighting.Example: indent the function body and that of the if
construct
function dot(a,b)
real, intent(in) :: a(3)
real, intent(in) :: b(3)
real :: dot
dot = sum(a*b)
if (dot < 1d-8) then
dot = 0
end if
end function
Caution
Use SPACEs, not TABs, for indentation. TABs are not valid input characters for Fortran.
Choose either 2, 3 or 4 spaces to representation ‘one indentation level’ and be consistent throughout your code. Most code editors let you configure how many spaces to indent when the TAB key is pressed.
Blank lines#
Recommendation
Separate related blocks of code with 1 blank line
Separate subroutines and functions with 2 blank lines
Rationale
Spacing out code with blank lines improves readabilityLine Length#
Recommendation
Continue long statements onto newlines. Lines should not exceed 100 characters.
Rationale
Long lines may necessitate horizontal scrolling which obscures code. Side-by-side editor configurations, which typically limit editor width to half a screen, are increasingly common and useful for code development.In fixed-form Fortran, statements are continued onto new lines by placing any character in the 6th column of the next line
In modern free-form Fortran, statements are continued by terminating the line with an ampersand
&
Example: continuing a long statement in fixed-form Fortran
y = a0 + a1 * x**2 + a2 * x**3
& + a3 * x**4 + a4 * x**5
Example: continuing a long statement in free-form Fortran
y = a0 + a1 * x**2 + a2 * x**3 &
+ a3 * x**4 + a4 * x**5
Recommendation
Do not use semicolons to place multiple statements on the same line.
Rationale
Placing multiple statements on one line reduces the readability of the code by obscuring assignments and procedure calls since they are not immediately visible upon reading through.Style#
Naming & Capitalisation#
The naming of variables and procedures is important for conveying the purpose and functionality of your code to those not familiar with it.
It is therefore important that your names are meaningful and accurately describe the variable or procedure. Moreover, you should follow the following recommendations for consistency and readability.
Recommendation
Use snake case for variables and functions:
All lowercase
Separate words with underscores
Examples:
linear_tolerance
calculate_damage()
Rationale
Fortran is case-insensitive, so the same variable or function can be referenced differently using different capitalisations. Using all lowercase is therefore recommended to encourage consistency and avoid confusion for readers of the code.Recommendation
Use all lowercase for fortran keywords such as if
, end
, function
etc..
All caps should not be used for fortran keywords - it is well known that all caps has measurably reduced readability[1]
Rationale
There is no need to highlight fortran keywords using case, since Fortran is case-insensitive. Keyword visibility is enhanced by using a code editor with proper syntax highlighting.Variable Declarations#
Recommendation
Use the full double-colon syntax for all parameter and variable declarations.
Examples:
integer, parameter :: n = 100
integer :: m
real :: tolerance
real :: parameters(n)
Rationale
Using the double-colon syntax everywhere improves readability and ensures all declarations are consistent with those that include attributes.Caution
Do NOT assign to variables in the declaration statement:
integer :: iter = 10 ! NOT RECOMMENDED
This is not normal initialisation, it implies the save
attribute which
means that the variable retains its value between procedure calls.
Good practice is to initialise your variables separately to their declaration:
integer :: iter
iter = 10
This does not affect assignment to parameters at declaration which is okay:
integer, parameter :: N_MAX = 1E6 ! This is okay
Numerical#
Declaring Double Precision Reals#
Recommendation
Define a parameter dp
at the top of your module and use it
to declare double precision reals.
Rationale
Other forms of double precision declaration are not portable across different platforms and compilers.Option 1 (recommended): Use the intrinsic 64 bit (double precision) floating point kind
use iso_fortran_env, only: dp=>real64
Option 2: Request a specific (minimum) precision p
and exponent range r
integer, parameter, private :: dp = selected_real_kind(p=15, r=307)
Example usage:
module demo
use iso_fortran_env, only: sp=>real32, dp=>real64
implicit none
real(sp), parameter :: pi_sp = 3.141593_sp
real(dp), parameter :: pi_dp = 3.14159265358979_dp
end module demo
Caution
You must use a precision suffix when writing double precision constants anywhere within your code, otherwise they will be truncated to single precision.
Example: real(dp), parameter :: a1 = 0.588375419731621_dp
Further reading: Learn more about Fortran variable kinds in “It Takes All KINDS”[2]
Integer Division#
Caution
Be careful when performing division with integer variables:
If all variables in the expression are integers, then an integer division will occur which will return the result rounded-down to the nearest integer.
Floating-point division will occur if at least one of the variables in the
expression is a real
:
You can use the
real()
function to convert an integer to a real, e.g.real(N,dp)
Program Units#
Modules#
See also
See the Using Fortran Modules for more information on how to modularise your user subroutine code.
Fortran modules contain definitions that are made accessible to programs, procedures,
and other modules through the use
statement.
They can contain parameters, variables, type definitions, procedures, and interfaces.
Placing variables and procedures into modules has several important benefits:
Modules provide a scoped namespace for related variables and procedures which have to be explicitly imported
Modules automatically generate explicit interfaces which are required for modern procedures and which allow for better compile-time error detection
Modules can be used to logical organise code by collecting related procedures and variables together in the same module
Module code can be easily reused and combined by other subroutines without duplication
Recommendation
It is strongly recommended that all auxilliary procedures and shared variables are stored in modules.
Rationale
Modules enable the use of modern Fortran features and provide automatic compile-time checking of procedure interfaces.Module variables are shared but do not suffer from the same problems as common block variables.
Further Recommendations
Always use explicit typing in modules by adding the
implicit none
statement at the top. (See Explicit Typing with Abaqus)Only store one module per file, and keep the module name and filename the same.
Subroutines and Functions#
Recommendation
It is strongly recommended that complex procedures are broken down into multiple functions or subroutines to reduce complexity.
Rationale:
Breaking up long procedures into smaller ones is a natural way to abstract complex functionality and clarify code structure and intent.
Small procedures with a clearly defined functionality are:
easier to conceptualise
easier to explain to others
easier to test and debug
Tip
Possible indications that a procedure is too long include:
There are a large number of variable declarations are the start
The procedure contains duplicated code
The procedure spans more than twice the standard screen height
The procedure has a large number of input arguments
Note
There is no need to place return
at the end of a function or subroutine.
It serves no purpose, except to increase the number of lines of code.
As a general rule, it is recommended to choose between subroutines and functions based on the following:
Use a function:
if your procedure calculates and returns a single scalar, or a small fixed-size array and;
if your procedure does not modify its input arguments
Use a subroutine:
if your procedure calculates and returns multiple values and/or;
if your procedure returns a large or variable-sized array and/or;
if your procedure modifies its input arguments
Procedure Arguments#
Recommendation
Always specify the intent
of procedure arguments:
intent(in)
: read-only within the procedureintent(out)
: modifiable, but undefined on entry to procedureintent(inout)
: modifiable
Rationale: Specifying argument intent has two important benefits:
It is self-documenting, providing important information to readers of the code
It is enforced by compile-time checks to avoid common errors
Example:
subroutine intent_example(a,b)
! We can read from a but not modify it
integer, intent(in) :: a
! We can modify b, but it is not defined initially
integer, intent(out) :: b
if (a > 0) then
b = a
else
b = 0
end if
end subroutine intent_example
End Statements#
Recommendation
Always use the full end statement for a block instead of just end
on its own
end if
end do
end block
Rationale
Using the full end statement makes it easier to understand deeply nested code.Recommendation
Always include the procedure or module name in the end statement
end module <module-name>
end subroutine <subroutine-name>
end function <function-name>
Rationale
Using the full end statement makes it easier to navigate files with many procedures.Checking Code Correctness#
Caution
Even if your code compiles and runs, this is no guarantee that your code is correct. Memory errors and undefined behaviour can easily hide in your code without symptom if you’re not using extra checks and testing.
Always use testing and compiler checks to ensure there are no errors
Most Fortran compilers can enable additional compile-time and runtime checks to ensure the correctness of the code you have written.
Recommendation
If you are developing Abaqus user subroutines, you can easily enable these extra code checks
using the Abaci tool with the
--debug
option.
The following compiler options are recommended for enabling extra checks:
Compiler |
Compiler Options for Extra Checks |
---|---|
Intel Fortran (Linux) |
|
Intel Fortran (Windows) |
|
gfortran |
|
These options will:
Check for out-of-bounds array accesses
Check if a variable is used before it is defined or allocated
Check that argument types match between interface definitions and routine calls (Intel compiler only)
Hint
Always use compiler checks when you are writing and testing new code, but don’t forget to disable them when you’re finished since the added checks slow down code execution.
Banned Features#
Background
Backwards-compatibility is very important for the Fortran standard due to the age of the language and consequently large amount of legacy code still in use. This means that many legacy features remain in the language standard despite their use being strongly discouraged or them being made obsolete.
The following features of Fortran should not be used:
goto
statement#
goto
statements are universally acknowledged as bad programming practice
since they obscure the control flow of the program and are highly error-prone.
Moreover, they have been entirely superseded by
structured programming constructs[3]
which includes familiar concepts such as conditional blocks (if/else
), loops
and functions.
Hint
The following statements can be used instead of goto
for common scenarios:
return
- return from a function or subroutine earlycycle
- jump straight to the next loop iteration, skipping the rest of the loop bodyexit
- exit out of a loop, skipping all remaining iterations
common
block#
A common block defines an area of memory which is globally accessible by all functions and subroutines and which persists between invocations thereof.
Commmon blocks should not be used since their definition is allowed to vary between different functions, which can permit subtle errors to occur without an obvious symptom.
Recommendation
Use modules for persistent variables, that need to be accessible from multiple functions.
equivalence
statement#
Equivalence statements should not be used, they are complex and error-prone. Pointers or derived types should be used instead.
Labelled do
loop#
Use an end do
statement instead.
Comments#
Code should ideally be as self-documenting as possible, not requiring excessive comments to explain it. This means:
Using informative names for variables and functions
Break code into self-contained functions/subroutines that each have a single clearly defined task
Recommendation
Write clear code that does not need excessive comments to explain
See also
See the Best Practices Guide on Comments.
There are some scenarios where the use of comments is recommended:
Use comments to concisely explain the purpose and methodology of each function or subroutine in your code (see Docstrings)
Use comments to explain obscure bug-fixes or workarounds that have no obvious interpretation alone
Use comments to label the end of loops for nested loops