Fortran Arrays
:, grow with allocatable, and reduce with a single intrinsic. This lesson covers the mechanics that make every later lesson — from vectorized physics to MPI domain decomposition — work: declaration, memory order, slicing, and the where/mask family.
Declaration and construction
Declare with a dimension: real :: v(100) is a fixed 100-element array. Array constructors build values inline with square brackets: v = [1.0, 2.0, 3.0]. A constructor can use an implied-do loop, the closest Fortran has to a list comprehension: [(i*i, i = 1, 10)] builds squares. The rank (number of dimensions) is fixed at compile time even when sizes are dynamic.
program array_basics
implicit none
integer :: squares(5) ! fixed-size, index 1..5
integer :: i
squares = [(i*i, i = 1, size(squares))] ! implied-do constructor
print *, squares ! 1 4 9 16 25
print *, squares(2) ! element access
print *, size(squares) ! 5 — number of elements
print *, shape(squares) ! 5 — shape vector
end program array_basics
Column-major memory layout
Array elements occupy one contiguous block, and the first subscript varies fastest in memory. This is the opposite of C, and everything — cache efficiency, BLAS calls, MPI buffers — depends on knowing it. When you loop, iterate with the first index innermost:
Fig. 1 — Consecutive memory addresses run down the first index; loop that way to stay in cache.
program layout
implicit none
real :: a(1000, 1000)
integer :: i, j
! FAST: inner loop runs down the first index = contiguous addresses.
do j = 1, 1000
do i = 1, 1000
a(i, j) = 0.0
end do
end do
! SLOW (10-100x on real data): a(i,j) with i outer jumps a column each step.
end program layout
Slicing and striding
The triple-dot-and-colon syntax v(lo:hi:step) extracts a section — without copying in the common case, so slices double as compile-time-safe views into a parent array. Negative steps reverse. Sections of any rank can appear anywhere a full array can, including as actual arguments.
program slices
implicit none
integer :: v(10) = [1,2,3,4,5,6,7,8,9,10]
print *, v(3:6) ! elements 3..6: 3 4 5 6
print *, v(2:8:2) ! stride 2: 2 4 6 8
print *, v(10:1:-2) ! reversed: 10 8 6 4 2
print *, v(:5) ! from the start: 1..5
v(1:5) = 0 ! assign to a whole section at once
print *, v ! 0 0 0 0 0 6 7 8 9 10
end program slices
A two-dimensional section selects rows and columns independently: a(2:4, 3) is the sub-vector of rows 2–4 in column 3. This one rule — "a colon is a free dimension selector" — is the foundation of the stencil operations that dominate numerical code.
Whole-array operations and WHERE
Arithmetic on whole arrays is element-wise and shape-checked at compile time. where (mask) extends the idea to conditional assignment — the array version of an if, with elsewhere for the complement:
program masked
implicit none
real :: v(5) = [1.0, -2.0, 3.0, -4.0, 5.0]
where (v < 0.0)
v = 0.0 ! clamp negatives to zero
elsewhere
v = v * 2.0 ! double the positives
end where
print *, v ! 2.0 0.0 6.0 0.0 10.0
end program masked
The sibling forall exists but is effectively legacy — compilers treat it as a do loop, while do concurrent (control lesson) gives the modern, optimization-friendly spelling.
Allocatable arrays: grow at runtime
allocatable arrays defer sizing to runtime: declare real, allocatable :: v(:), then allocate(v(n)). Fortran 2003 added automatic reallocation on assignment — v = [1.0, 2.0] sizes it for you — and move_alloc transfers allocation without copying, the building block of growable containers. Deallocation is automatic when the variable goes out of scope, eliminating the leak class that C programmers accept with a shrug.
program dynamic_arrays
implicit none
real, allocatable :: v(:), w(:)
v = [1.0, 2.0, 3.0] ! auto-allocation on assignment
allocate (w(size(v) * 2)) ! explicit sizing for a known shape
w = v ! shape mismatch handled per element
deallocate (w) ! explicit release; also auto on exit
call move_alloc(v, w) ! steal the allocation, nothing copied
print *, size(w), allocated(v)
end program dynamic_arrays
Linear algebra building blocks
Intrinsics cover the small linear algebra: dot_product, matmul, transpose, maxloc/minloc. For dense systems beyond a toy size the ecosystem standard is BLAS/LAPACK, called through wrappers — the BLAS level-1 idioms (axpy, norms, dot) appear in your own code constantly:
program linalg
implicit none
real :: m(2, 2) = reshape([1.0, 2.0, 3.0, 4.0], [2, 2]) ! column-major!
real :: x(2) = [1.0, 1.0]
print *, matmul(m, x) ! (4.0, 6.0): m*x
print *, dot_product(x, x) ! 2.0
print *, maxloc(m) ! row/col of the largest element
end program linalg