Skip to content

Transferring fortran90.org "Fortran Best Practices" into a mini-book #246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Sep 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6f3648d
Transfer fortran90.org Best Practices content (#79)
vmagnin Apr 21, 2021
b713a9b
Removed vestigial best_practices.md (2020-04-15)
vmagnin Apr 21, 2021
16b65a5
Replaced HTML code by Markdown italics *...*
vmagnin Apr 24, 2021
1a256d3
Typo: replaced 3 'enddo' by 'end do'
vmagnin Apr 24, 2021
6c15dea
Consistent indentation (2 spaces) with quickstart minibook
awvwgk May 8, 2021
c84e3f0
Minor adjustments, wording, stop -> error stop
awvwgk May 8, 2021
3383039
Extend the alloctable arrays section
awvwgk Jun 1, 2021
976fd62
Expand chapter on arrays
awvwgk Jun 1, 2021
c358ace
Expand chapter on integer division
awvwgk Jun 1, 2021
4114262
Add definition of callback
awvwgk Jun 1, 2021
4b3de58
Add intent in callback snippet
awvwgk Jun 1, 2021
f146342
Rework multidimensional array section
awvwgk Jun 2, 2021
b885654
Rework floating point chapter
awvwgk Jun 2, 2021
1fb3b44
Remove parallel_programming from best practices
awvwgk Jun 2, 2021
7a9fcbc
Remove Python interfacing chapter from best practices
awvwgk Jun 2, 2021
f5978fb
Remove Fortran-C interfacing from best practice
awvwgk Jun 2, 2021
81aa4d5
Apply suggestions from code review
awvwgk Jun 2, 2021
e17cba7
Fix some typos in allocatable arrays section
awvwgk Jun 2, 2021
e73677a
Add correct syntax highlighting
awvwgk Jun 2, 2021
158a970
Rework file IO section
awvwgk Jun 2, 2021
a8da64c
Rework module and program chapter
awvwgk Jun 3, 2021
5cf80cc
Update authors and introduction for best practice minibook
awvwgk Jun 3, 2021
5a86e10
Fix typo
awvwgk Jun 3, 2021
c1e492a
Rework callback section in best practise minibook
awvwgk Jun 3, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions _data/learning.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ books:
- link: /learn/os_setup/ides
- link: /learn/os_setup/tips

- title: Fortran Best Practices
description: This tutorial collects a modern canonical way of doing things in Fortran.
category: Getting started
link: /learn/best_practices
pages:
- link: /learn/best_practices/style_guide
- link: /learn/best_practices/floating_point
- link: /learn/best_practices/integer_division
- link: /learn/best_practices/modules_programs
- link: /learn/best_practices/arrays
- link: /learn/best_practices/multidim_arrays
- link: /learn/best_practices/element_operations
- link: /learn/best_practices/allocatable_arrays
- link: /learn/best_practices/file_io
- link: /learn/best_practices/callbacks
- link: /learn/best_practices/type_casting

# Web links listed at the bottom of the 'Learn' landing page
#
reference-links:
Expand Down
5 changes: 0 additions & 5 deletions learn/best_practices.md

This file was deleted.

131 changes: 131 additions & 0 deletions learn/best_practices/allocatable_arrays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
layout: book
title: Allocatable Arrays
permalink: /learn/best_practices/allocatable_arrays
---

The ``allocatable`` attribute provides safe way for memory handling.
In comparison to variables with ``pointer`` attribute the memory is managed
automatically and will be deallocated automatically once the variable goes
out-of-scope. Using ``allocatable`` variables removes the possibility to
create memory leaks in an application.

They can be used in subroutines to create scratch or work arrays, where
automatic arrays would become too large to fit on the stack.

```fortran
real(dp), allocatable :: temp(:)
allocate(temp(10))
```

The allocation status can be checked using the ``allocated`` intrinsic
to avoid uninitialized access

```fortran
subroutine show_arr(arr)
integer, allocatable, intent(in) :: arr(:)

if (allocated(arr)) then
print *, arr
end if
end subroutine show_arr
```

To allocate variables inside a procedure the dummy argument has to carry
the ``allocatable`` attribute. Using it in combination with ``intent(out)``
will deallocate previous allocations before entering the procedure:

```fortran
subroutine foo(lam)
real(dp), allocatable, intent(out) :: lam(:)
allocate(lam(5))
end subroutine foo
```

The allocated array can be used afterwards like a normal array

```fortran
real(dp), allocatable :: lam(:)
call foo(lam)
```

An already allocated array cannot be allocated again without prior deallocation.
Similarly, deallocation can only be invoked for allocated arrays. To reallocate
an array use

```fortran
if (allocated(lam)) deallocate(lam)
allocate(lam(10))
```

Passing allocated arrays to procedures does not require the ``allocatable`` attribute
for the dummy arguments anymore.

```fortran
subroutine show_arr(arr)
integer, intent(in) :: arr(:)

print *, arr
end subroutine show_arr

subroutine proc
integer :: i
integer, allocatable :: arr

allocate(arr(5))

do i = 1, size(arr)
arr(i) = 2*i + 1
end do
call show_arr(arr)
end subroutine proc
```

Passing an unallocated array in this context will lead to an invalid memory access.
Allocatable arrays can be passed to ``optional`` dummy arguments, if they are unallocated
the argument will not be present. The ``allocatable`` attribute is not limited to
arrays and can also be associated with scalars, which can be useful in combination
with ``optional`` dummy arguments.

Allocations can be moved between different arrays with ``allocatable`` attribute
using the ``move_alloc`` intrinsic subroutine.

```fortran
subroutine resize(var, n)
real(wp), allocatable, intent(inout) :: var(:)
integer, intent(in), optional :: n
integer :: this_size, new_size
integer, parameter :: inital_size = 16

if (allocated(var)) then
this_size = size(var, 1)
call move_alloc(var, tmp)
else
this_size = initial_size
end if

if (present(n)) then
new_size = n
else
new_size = this_size + this_size/2 + 1
end if

allocate(var(new_size))

if (allocated(tmp)) then
this_size = min(size(tmp, 1), size(var, 1))
var(:this_size) = tmp(:this_size)
end if
end subroutine resize
```

Finally, allocations do not initialize the array, the content of the uninitialized
array is most likely just the bytes of whatever was previously at the respective address.
The allocation supports initialization using the source attribute:

```fortran
real(dp), allocatable :: arr(:)
allocate(arr(10), source=0.0_dp)
```

The ``source`` keyword supports scalar and array valued variables and constants.
200 changes: 200 additions & 0 deletions learn/best_practices/arrays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
---
layout: book
title: Arrays
permalink: /learn/best_practices/arrays
---

Arrays are a central object in Fortran. The creation of dynamic sized arrays
is discussed in the [allocatable arrays arrays](./allocatable_arrays.html).

To pass arrays to procedures four ways are available

1. *assumed-shape* arrays
4. *assumed-rank* arrays
2. *explicit-shape* arrays
3. *assumed-size* arrays

The preferred way to pass arrays to procedures is as *assumed-shape* arrays

```fortran
subroutine f(r)
real(dp), intent(out) :: r(:)
integer :: n, i
n = size(r)
do i = 1, n
r(i) = 1.0_dp / i**2
end do
end subroutine f
```

Higher dimensional arrays can be passed in a similar way.

```fortran
subroutine g(A)
real(dp), intent(in) :: A(:, :)
...
end subroutine g
```

The array is simply passed by

```fortran
real(dp) :: r(5)
call f(r)
```

In this case no array copy is done, which has the advantage that the shape and size
information is automatically passed along and checked at compile and optionally at
runtime.
Similarly, array strides can be passed without requiring a copy of the array but as
*assumed-shape* discriptor:

```fortran
real(dp) :: r(10)
call f(r(1:10:2))
call f(r(2:10:2))
```

This should always be your default way of passing arrays in and out of subroutines.
Avoid passing arrays as whole slices, as it obfuscates the actual intent of the code:

```fortran
real(dp) :: r(10)
call f(r(:))
```

In case more general arrays should be passed to a procedure the *assumed-rank*
functionality introduced in the Fortran 2018 standard can be used

```fortran
subroutine h(r)
real(dp), intent(in) :: r(..)
select rank(r)
rank(1)
! ...
rank(2)
! ...
end select
end subroutine h
```

The actual rank can be queried at runtime using the ``select rank`` construct.
This easily allows to create more generic functions that have to deal with
differnet array ranks.

*Explicit-shape* arrays can be useful for returning data from functions.
Most of their functionality can be provided by *assumed-shape* and *assumed-rank*
arrays but they find frequent use for interfacing with C or in legacy Fortran
procedures, therefore they will be discussed briefly here.

To use *explicit-shape* arrays, the dimension has to be passed explicitly as dummy
argument like in the example below

``` fortran
subroutine f(n, r)
integer, intent(in) :: n
real(dp), intent(out) :: r(n)
integer :: i
do i = 1, n
r(i) = 1.0_dp / i**2
end do
end subroutine
```

For high-dimensional arrays additional indices have to be passed.

``` fortran
subroutine g(m, n, A)
integer, intent(in) :: m, n
real(dp), intent(in) :: A(m, n)
...
end subroutine
```

The routines can be invoked by

``` fortran
real(dp) :: r(5), s(3, 4)
call f(size(r), r)
call g(size(s, 1), size(s, 2), s)
```

Note that the shape is not checked, therefore the following would be valid code
with will potentially yield incorrect results:

```fortran
real(dp) :: s(3, 4)
call g(size(s), 1, s) ! s(12, 1) in g
call g(size(s, 2), size(s, 1), s) ! s(4, 3) in g
```

In this case the memory layout is preserved but the shape is changed.
Also, *explicit-shape* arrays require contiguous memory and will create temporary
arrays in case non-contiguous array strides are passed.

To return an array from a function with *explicit-shape* use

``` fortran
function f(n) result(r)
integer, intent(in) :: n
real(dp) :: r(n)
integer :: i
do i = 1, n
r(i) = 1.0_dp / i**2
end do
end function
```

Finally, there are *assumed-size* arrays, which provide the least compile and runtime
checking and can be found be found frequently in legacy code, they should be avoided
in favour of *assumed-shape* or *assumed-rank* arrays.
An *assumed-size* array dummy argument is identified by an asterisk as the last dimension,
this disables the usage of this array with many intrinsic functions, like ``size`` or
``shape``.

To check for the correct size and shape of an *assumed-shape* array the ``size`` and
``shape`` intrinsic functions can be used to query for those properties

```fortran
if (size(r) /= 4) error stop "Incorrect size of 'r'"
if (any(shape(r) /= [2, 2])) error stop "Incorrect shape of 'r'"
```

Note that ``size`` returns the total size of all dimensions, to obtain the shape of
a specific dimension add it as second argument to the function.

Arrays can be initialized by using an array constructor

```fortran
integer :: r(5)
r = [1, 2, 3, 4, 5]
```

The array constructor can be annoted with the type of the constructed array

```fortran
real(dp) :: r(5)
r = [real(dp) :: 1, 2, 3, 4, 5]
```

Implicit do loops can be used inside an array constructor as well

```fortran
integer :: i
real(dp) :: r(5)
r = [(real(i**2, dp), i = 1, size(r))]
```

In order for the array to start with different index than 1, do:

```fortran
subroutine print_eigenvalues(kappa_min, lam)
integer, intent(in) :: kappa_min
real(dp), intent(in) :: lam(kappa_min:)

integer :: kappa
do kappa = kappa_min, ubound(lam, 1)
print *, kappa, lam(kappa)
end do
end subroutine print_eigenvalues
```
Loading