Array Intrinsics and Reductions
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3 final draft J3/23-007r1) and was checked on 2026-10-18
with GNU Fortran 16.2 and LLVM Flang 23. The array intrinsic functions are what lets Fortran code say "the sum of the
temperatures", "where is the hottest cell" or "shift the field one cell to the left" without writing a loop, and a compiler
is free to turn each such statement into the best loop, vectorised or not, for the machine. The page covers the whole
family in the order in which a numerical program meets it: the rules shared by all of them (arguments, dim, mask,
kind, array element order), the reductions, the location functions, the inquiry functions, the construction and
manipulation functions, and matmul and dot_product. It ends with the diagnostics each compiler gives for misuse, the
cost of the temporaries that some of these functions create, and a table of where each function came from.
The arrays themselves (declaration, sections, constructors, whole-array operations and where) are on
Arrays Fundamentals; allocatable arrays on
Allocatable Arrays and Dynamic Memory;
how array arguments reach a procedure on
Array Arguments: Assumed Shape, Size and Rank;
and the catalogue of all other intrinsic procedures on
Intrinsic Procedures Overview.
Every example belongs to the heatflow scenario (a two-dimensional heat-diffusion solver) and uses its names (dp for
real64, a temperature field u), but each one is a complete, self-contained file that can be extracted and run on its
own. Unless a paragraph says otherwise, every example compiles without warnings under
gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all and was also built and run with LLVM Flang 23,
and the output shown is what it printed. Examples that are deliberately wrong are labelled as non-conforming in the text
and in a comment on their first line, and the compiler messages or run-time failures they produce are shown.
The rules shared by every array intrinsic
Most of this page concerns transformational functions: functions that take whole arrays and return a value (or an
array) computed from all of them, rather than working element by element. Three other classes appear alongside them.
Inquiry functions (size, shape, lbound, ubound, rank, is_contiguous) return a property of an argument and
do not even need its value to be defined; elemental functions (merge, and the bit and mathematical functions that
are applied to arrays) work on each element independently; and a few array intrinsics are subroutines. The classes are
listed for every procedure in J3/23-007r1, Table 16.1, and the specification of each function is in 16.9.
The rules that apply to all of them:
-
Argument keywords. Every intrinsic can be called with positional or keyword arguments (16.2.1), using the names in the standard (
array,dim,mask,kind,back,vector,shift,boundary,ncopies, …). Because many functions have several optional arguments, keywords are the readable way to call them:maxloc(u, back=.true.),eoshift(v, 2, boundary=-1). -
The
dimargument selects one dimension to collapse. For an array of rank n,dimis an integer scalar from 1 to n. A reduction called withdimreturns an array of rank n - 1 whose shape is that of the argument with dimensiondimremoved; withoutdimthe result is a scalar (or, for the location functions, a rank-one array of subscripts). A rank-one argument withdimgives a scalar (16.2.4). Adimthat is not a valid dimension is non-conforming; a compiler rejects a constant one and, with run-time checking, a variable one (see the diagnostics section). -
The
maskargument selects the elements that take part. It is a logical array conformable with the argument (same shape, or scalar). Elements that the mask does not select need not even be defined (16.2.3), but the mask does not change how the arguments, which may be array expressions, are evaluated before the call.sum(u, mask=m)is defined to equalsum(pack(u, m)); the function form just avoids the temporary. -
The
kindargument sets the kind of an integer result.count,maxloc,minloc,findloc,size,shape,lboundanduboundreturn default integers unlesskind=names another integer kind; it must be a constant expression. Usekind=int64for arrays whose extents or subscripts can exceedhuge(0). -
Array element order. When a function reads or writes an array "in order" (
reshape,pack,unpack, andreduceunlessordered=says otherwise) it uses array element order: the first subscript varies fastest, so the elements ofu(4, 3)areu(1,1),u(2,1),u(3,1),u(4,1),u(1,2), and so on (9.5.3.3). This is the memory order of Fortran arrays, and the opposite of C. -
Shapes must be defined. The arguments of transformational and elemental functions must have a defined shape (16.2.2), so an unallocated allocatable array or a disassociated pointer cannot be passed.
The table lists every function of the family with its class, the argument that gets reduced or shifted, and the shape of the result. Source names the clause of J3/23-007r1 where the function is specified.
| Function | Class | Result | Source |
|---|---|---|---|
|
transformational |
scalar, or rank n - 1 with |
16.9.201, 16.9.165 |
|
transformational |
scalar, or rank n - 1 with |
16.9.138, 16.9.144 |
|
transformational |
scalar (integer or logical), or rank n - 1 with |
16.9.66, 16.9.15, 16.9.12, 16.9.159 |
|
transformational |
scalar integer, or rank n - 1 with |
16.9.99, 16.9.101, 16.9.112 |
|
transformational |
scalar real, or rank n - 1 with |
16.9.153 |
|
transformational |
scalar of the array’s type, or rank n - 1 |
16.9.173 |
|
transformational |
rank-one array of subscripts, or rank n - 1 with |
16.9.137, 16.9.143, 16.9.88 |
|
inquiry |
scalar, or rank-one array |
16.9.194, 16.9.185, 16.9.119, 16.9.215, 16.9.171, 16.9.115 |
|
transformational |
a new array whose shape is computed |
16.9.175, 16.9.197, 16.9.158, 16.9.217 |
|
transformational |
an array of the same shape (reversed for |
16.9.68, 16.9.77, 16.9.213 |
|
elemental |
same shape as the conformable arguments |
16.9.139 |
|
transformational |
matrix, vector or scalar |
16.9.134, 16.9.73 |
Reductions
A reduction collapses an array along one dimension or all of them. The simplest are sum, product, maxval and
minval (for numeric and, for the last two, character data), count (how many true elements of a logical array), any
and all. They are all called as f(array [, dim] [, mask]), with dim and mask as keywords whenever one is skipped,
and count also takes kind. The first example applies each of them to a 4 by 3 temperature field and prints the
result, so the effect of dim=1 (collapse the first dimension, one result per column), dim=2 (one result per row) and
mask can be read from the numbers. It ends with the results for zero-sized arrays and with count called with a kind.
program reductions
use, intrinsic :: iso_fortran_env, only: real64, int8, int64
implicit none
integer, parameter :: dp = real64
real(dp) :: u(4, 3)
logical :: hot(4, 3)
integer :: i, j
! temperatures on a 4 x 3 grid; u(i, j) = 10*i + j makes element order visible
do j = 1, 3
do i = 1, 4
u(i, j) = real(10*i + j, dp)
end do
end do
hot = u > 25.0_dp
print '(a, f7.1)', 'sum(u) = ', sum(u)
print '(a, 3f7.1)', 'sum(u, dim=1) = ', sum(u, dim=1)
print '(a, 4f7.1)', 'sum(u, dim=2) = ', sum(u, dim=2)
print '(a, f7.1)', 'sum(u, mask=hot) = ', sum(u, mask=hot)
print '(a, 3f7.1)', 'product(u(1:2,:),1) = ', product(u(1:2, :), dim=1)
print '(a, f7.1)', 'maxval(u) = ', maxval(u)
print '(a, 3f7.1)', 'minval(u, dim=1) = ', minval(u, dim=1)
print '(a, f7.1)', 'maxval(u, u < 25) = ', maxval(u, mask=u < 25.0_dp)
print '(a, i0)', 'count(hot) = ', count(hot)
print '(a, 3i2)', 'count(hot, dim=1) = ', count(hot, dim=1)
print '(a, l1, a, l1)', 'any(hot) = ', any(hot), ' all(hot) = ', all(hot)
print '(a, 3l2)', 'all(hot, dim=1) = ', all(hot, dim=1)
print '(a, 4l2)', 'any(hot, dim=2) = ', any(hot, dim=2)
! empty arrays: the identity element of each reduction
print '(a, f4.1, a, f4.1, a, i0, a, l1, a, l1)', 'empty: sum=', sum(u(1:0, 1)), &
' product=', product(u(1:0, 1)), ' count=', count(hot(1:0, 1)), ' any=', any(hot(1:0, 1)), &
' all=', all(hot(1:0, 1))
print '(a, es14.4e3, a, es14.4e3)', 'empty: maxval=', maxval(u(1:0, 1)), ' minval=', minval(u(1:0, 1))
! kind= on count and the integer overflow trap of small kinds
print '(a, i0, a, i0)', 'kind(count(hot))=', kind(count(hot)), ' kind(count(hot, kind=int64))=', &
kind(count(hot, kind=int64))
block
integer(int8) :: small(3)
small = [100_int8, 100_int8, 100_int8]
print '(a, i0)', 'sum of three int8 values of 100 (int64 widening): ', sum(int(small, int64))
end block
end program reductions
sum(u) = 324.0
sum(u, dim=1) = 104.0 108.0 112.0
sum(u, dim=2) = 36.0 66.0 96.0 126.0
sum(u, mask=hot) = 222.0
product(u(1:2,:),1) = 231.0 264.0 299.0
maxval(u) = 43.0
minval(u, dim=1) = 11.0 12.0 13.0
maxval(u, u < 25) = 23.0
count(hot) = 6
count(hot, dim=1) = 2 2 2
any(hot) = T all(hot) = F
all(hot, dim=1) = F F F
any(hot, dim=2) = F F T T
empty: sum= 0.0 product= 1.0 count=0 any=F all=T
empty: maxval= -1.7977E+308 minval= 1.7977E+308
kind(count(hot))=4 kind(count(hot, kind=int64))=8
sum of three int8 values of 100 (int64 widening): 300
What the output shows and the rules behind it:
-
sum(u, dim=1)has three elements, one per column ofu(4, 3);sum(u, dim=2)has four, one per row. The dimension named indimis the one that disappears. -
The
maskis applied before reducing:sum(u, mask=hot)adds only the six elements greater than 25, andmaxval(u, mask=u < 25)is the maximum of the elements that satisfy the mask (23), not of the whole array. -
count(hot, dim=1)andall(hot, dim=1)follow the same shape rule assum: one value per column. -
Empty arrays return the identity of the operation.
sumof nothing is 0,productis 1,countis 0,anyis false andallis true.maxvalof a zero-sized integer or real array is the negative number of largest magnitude the type supports (-huge), andminvalis+huge; for a zero-sized character array they return a string ofchar(0)(16.9.138). A loop that starts from the first element would instead read outside the array, which is why these functions are safer than handwritten reductions on slices that can be empty (an inactive boundary strip, for example). -
The result has the type and kind of the argument.
sumof a default-integer array is a default integer and can overflow; the standard does not say what happens then, and compilers do not trap it even with-fcheck=all. Convert the array to a wider kind first (sum(int(small, int64))) when sums may be large. For real data the same applies to accuracy: summing a millionreal32values in areal32accumulator loses digits that areal64one would keep. -
The order of evaluation is not specified. The standard lets a processor evaluate any mathematically equivalent expression (10.1.5.2.4), so the last bits of a floating-point
sumcan depend on the optimisation level, on vectorisation and on OpenMP. A test that compares two sums for exact equality is wrong; compare with a tolerance. -
countreturns a default integer unlesskind=is given, socount(mask, kind=int64)is the way to count more thanhuge(0)elements.
maxval and minval have no back argument and return values, not positions; the positions come from maxloc and
minloc (below). The result of maxval for an array that contains a NaN is not defined by the standard and can differ
between processors, so clean the data first or reduce with reduce and an operation that tests ieee_is_nan (see
Floating-Point and IEEE Arithmetic).
Norms, bit reductions and parity
norm2(x [, dim]) is the Euclidean (L2) norm of a real array. It is not simply sqrt(sum(x**2)): the standard recommends
that the processor compute it without undue overflow or underflow (16.9.153), and both compilers do. dot_product(x, y)
is the inner product of two vectors of the same size. iall, iany and iparity reduce an integer array with the bitwise
iand, ior and ieor; parity reduces a logical array with .neqv., so it is true when an odd number of elements are
true. All of them accept dim and, except dot_product, mask.
The second example uses them on the heatflow data, adds reduce, and is described in the next subsection. The bit
reductions are applied to four small flag words (one bit per kind of boundary condition seen by a cell).
module reduce_ops
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: dp, absmax, hotter
integer, parameter :: dp = real64
type, public :: sample_t
real(dp) :: t = 0.0_dp
integer :: cell = 0
end type sample_t
contains
pure function absmax(a, b) result(r) ! associative and commutative
real(dp), intent(in) :: a, b
real(dp) :: r
r = max(abs(a), abs(b))
end function absmax
pure function hotter(a, b) result(r) ! keeps the sample with the larger t
type(sample_t), intent(in) :: a, b
type(sample_t) :: r
if (b%t > a%t) then
r = b
else
r = a
end if
end function hotter
end module reduce_ops
program norms_bits_reduce
use, intrinsic :: iso_fortran_env, only: int32
use reduce_ops
implicit none
real(dp) :: r(5), g(2, 3), big(2)
integer(int32) :: flags(4)
logical :: odd(4)
type(sample_t) :: s(4), best
integer :: k
r = [3.0_dp, -4.0_dp, 0.0_dp, 12.0_dp, -84.0_dp]
g = reshape([1.0_dp, 3.0_dp, 2.0_dp, 4.0_dp, 5.0_dp, 6.0_dp], [2, 3])
print '(a, f10.4)', 'norm2(r) = ', norm2(r)
print '(a, f10.4)', 'sqrt(sum(r**2)) = ', sqrt(sum(r**2))
print '(a, 3f9.4)', 'norm2(g, dim=1) = ', norm2(g, dim=1)
print '(a, 2f9.4)', 'norm2(g, dim=2) = ', norm2(g, dim=2)
big = 1.0e200_dp
print '(a, es10.3e3)', 'norm2 of [1e200, 1e200]: ', norm2(big)
print '(a, es10.3e3)', 'sqrt(sum(x**2)) : ', sqrt(sum(big**2))
print '(a, f10.4)', 'dot_product(r, r) = ', dot_product(r, r)
! one bit per boundary condition seen in a cell
flags = [3_int32, 6_int32, 5_int32, 7_int32] ! 0011 0110 0101 0111
print '(a, b4.4, a, b4.4, a, b4.4)', 'iall=', iall(flags), ' iany=', iany(flags), ' iparity=', iparity(flags)
odd = [(btest(flags(k), 0), k = 1, 4)]
print '(a, l1, a, l1)', 'parity(odd)=', parity(odd), ' count(odd) odd? ', mod(count(odd), 2) == 1
! reduce: user-defined operation
print '(a, f6.1)', 'reduce(r, absmax) = ', reduce(r, absmax)
print '(a, f6.1)', 'reduce(r, absmax, r > 0) = ', reduce(r, absmax, mask=r > 0.0_dp, identity=0.0_dp)
print '(a, f6.1)', 'nothing selected, identity 0.0 = ', reduce(r, absmax, mask=r > 100.0_dp, identity=0.0_dp)
print '(a, 3f6.1)', 'reduce(g, absmax, dim=1) = ', reduce(g, absmax, dim=1)
s = [sample_t(1.5_dp, 1), sample_t(9.0_dp, 2), sample_t(4.0_dp, 3), sample_t(9.0_dp, 4)]
best = reduce(s, hotter, ordered=.true.)
print '(a, f4.1, a, i0)', 'hottest sample: t=', best%t, ' cell=', best%cell
end program norms_bits_reduce
norm2(r) = 85.0000
sqrt(sum(r**2)) = 85.0000
norm2(g, dim=1) = 3.1623 4.4721 7.8102
norm2(g, dim=2) = 5.4772 7.8102
norm2 of [1e200, 1e200]: 1.414E+200
sqrt(sum(x**2)) : Infinity
dot_product(r, r) = 7225.0000
iall=0000 iany=0111 iparity=0111
parity(odd)=T count(odd) odd? T
reduce(r, absmax) = 84.0
reduce(r, absmax, r > 0) = 12.0
nothing selected, identity 0.0 = 0.0
reduce(g, absmax, dim=1) = 3.0 4.0 6.0
hottest sample: t= 9.0 cell=2
From the output: norm2(g, dim=1) is the norm of each column of the 2 by 3 array g, and norm2(g, dim=2) the norm of each
row; norm2 of two values of 1e200 is 1.414e200 where the naive sqrt(sum(big**2)) overflows to infinity;
iall of 3, 6, 5 and 7 is 0 (the three-way iand leaves no common bit), iany is 7, iparity is the xor 7, and the
parity of the four low bits is true because three of the four flags are odd. Bit manipulation in general, including the
bit model of 16.3, is on
Numeric Model, Bit Manipulation and Conversion.
reduce: a reduction with your own operation
reduce (Fortran 2018) is the general reduction. reduce(array, operation [, mask, identity, ordered]) or
reduce(array, operation, dim [, mask, identity, ordered]) applies a pure binary function repeatedly to adjacent
elements until one value is left. It works for arrays of any type, including derived types, which none of the other
reductions do. The rules, from J3/23-007r1, 16.9.173:
-
operationis a pure function of exactly two arguments, both scalar, non-allocatable, non-pointer, non-polymorphic, and both of the same declared type and type parameters asarray; the result has that type as well. A function that does not fit this description does not conform. -
operationshould be associative, because the processor may group the elements in any way, and is not required to be commutative. If it is not associative, or if it depends on the order, giveordered=.true., which makes the reduction combine the first two elements of the remaining sequence at each step, in array element order. The price is that the compiler can no longer evaluate in parallel. -
maskselects the elements, as for the other reductions, anddimreduces one dimension. -
If the selected sequence is empty, the result is
identitywhen it is present; otherwise the program is error terminated. That is why theidentityargument matters whenever amaskcan select nothing. -
Fortran 2023 adds the
reducelocality specifier todo concurrentfor loops written as reductions; that is a separate feature (see do concurrent and GPU Offload), and the coarray collective for the same job isco_reduce(see Coarray Teams, Events, Collectives and Atomics).
In the example, absmax finds the element of largest magnitude, reduce(r, absmax, mask=r > 0.0_dp, identity=0.0_dp)
restricts it to positive elements (12), the masked call that selects nothing returns the identity, reduce(g, absmax,
dim=1) reduces columns, and hotter reduces an array of a derived type sample_t (temperature and cell number) to the
sample with the highest temperature, which sum or maxval could not do.
The compilers differ here, and the difference is a bug rather than an extension, so it is worth showing in a program of
its own. With GNU Fortran 16.2, reduce of a zero-sized array without a mask returns an undefined value instead of the
identity, whereas the masked form (a mask that selects nothing) works and LLVM Flang 23 returns the identity in all
three cases. The program is conforming; the first two lines should print 7.
program reduce_empty
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
real(dp) :: r(5), none(0)
r = [3.0_dp, -4.0_dp, 0.0_dp, 12.0_dp, -84.0_dp]
print '(a, g0)', 'zero-sized array, identity=7: ', reduce(none, absmax, identity=7.0_dp)
print '(a, g0)', 'zero-sized section, identity=7: ', reduce(r(1:0), absmax, identity=7.0_dp)
print '(a, g0)', 'nothing selected by mask, identity=7: ', reduce(r, absmax, mask=r > 100.0_dp, identity=7.0_dp)
contains
pure function absmax(a, b) result(c)
real(dp), intent(in) :: a, b
real(dp) :: c
c = max(abs(a), abs(b))
end function absmax
end program reduce_empty
$ gfortran -std=f2023 -Wall -Wextra -pedantic reduce_empty.f90 && ./a.out (GNU Fortran 16.2)
zero-sized array, identity=7: 0.13906697895580856E-308
zero-sized section, identity=7: 3.0000000000000000
nothing selected by mask, identity=7: 7.0000000000000000
$ flang reduce_empty.f90 && ./a.out (LLVM Flang 23)
zero-sized array, identity=7: 7.
zero-sized section, identity=7: 7.
nothing selected by mask, identity=7: 7.
The first two values on the GNU Fortran side change from build to build (it printed 0.13262473693532952E-314 at -O2)
and, in the second case, are the first element of the array that should have been ignored. Until the compiler is fixed,
guard the call (if (size(x) > 0)) or pass a mask. GNU Fortran’s linker step also printed "requires executable stack"
for programs that call reduce; that is a linker warning about the generated code, not a compiler warning, and it does
not appear with Flang. GNU Fortran’s manual lists the other intrinsics of this page individually but has no page for
reduce at the time of writing.
Location functions
maxloc and minloc return the position of the largest or smallest element; findloc returns the position of an element
equal to a given value (Fortran 2008). All three return subscripts, not values and not offsets, and their result form is
easy to get wrong:
-
Without
dim, the result is a rank-one integer array with one element per dimension of the argument, even for a rank-one argument (an array of size 1).maxloc(u)is[i, j]for a rank-2u; assigning it to a scalar is an error. Declare an integer array of sizerank(u)or usemaxloc(u, dim=1)for rank-one arrays. -
With
dim, the result is the position along that dimension for every vector of the other dimensions, with rank n - 1.maxloc(u, dim=1)has one element per column. For a rank-one argument the result withdimis a scalar. -
Ties return the first match in array element order, and with
back=.true.the last (Fortran 2008). Thebackargument makesmaxloc(v)andmaxloc(v, back=.true.)different when the maximum occurs more than once. -
No match returns zero: if the array has size zero, if the mask selects nothing, or if
findlocfinds no element equal tovalue, every element of the result is 0 (16.9.137, 16.9.88). Zero is never a valid subscript for a lower bound of 1, but it can be a valid subscript for an array declared with0:, so test withany(loc == 0)only for arrays whose lower bounds are 1. -
The subscripts are relative to the array as if its lower bounds were 1, not to its declared bounds. For an array declared
w(0:3, -1:1),maxloc(w)still counts from one; to get the subscript to use withw, add the lower bound and subtract one, as inloc + lbound(w) - 1. This is the usual source of off-by-one errors with these functions. -
maxlocandminloctake integer, real and character arrays;findloctakes arrays of any intrinsic type (numeric, logical or character) and a scalarvalueof a type that can be compared. Because it tests equality,findlocon real data is only as exact as==is; use it with integers, logicals and characters, or with reals that are known to be bit-equal. To locate the first element satisfying a condition, usefindloc(condition, .true., dim=1). -
kind=sets the integer kind of the result, as forcount.
The example uses a 4 by 3 field in which the maximum 43 occurs twice (at u(2,3) and u(4,3)), and a rank-one array
with a repeated maximum.
program locations
use, intrinsic :: iso_fortran_env, only: real64, int64
implicit none
integer, parameter :: dp = real64
real(dp) :: u(4, 3)
real(dp), allocatable :: w(:, :)
real(dp) :: v(6)
integer :: i, j, loc(2)
integer(int64) :: loc64(2)
do j = 1, 3
do i = 1, 4
u(i, j) = real(10*i + j, dp)
end do
end do
u(2, 3) = u(4, 3) ! the maximum 43.0 now appears twice: (2,3) and (4,3)
v = [2.0_dp, 6.0_dp, 4.0_dp, 6.0_dp, 1.0_dp, 6.0_dp]
loc = maxloc(u) ! rank-one array of size 2, not a scalar
print '(a, 2i3)', 'maxloc(u) = ', loc
print '(a, 2i3)', 'maxloc(u, back=.true.) = ', maxloc(u, back=.true.)
print '(a, 2i3)', 'minloc(u) = ', minloc(u)
print '(a, 3i3)', 'maxloc(u, dim=1) = ', maxloc(u, dim=1)
print '(a, 4i3)', 'minloc(u, dim=2) = ', minloc(u, dim=2)
print '(a, 2i3)', 'maxloc(u, mask=u<40) = ', maxloc(u, mask=u < 40.0_dp)
print '(a, i0)', 'maxloc(v, dim=1) = ', maxloc(v, dim=1)
print '(a, 3i2)', 'maxloc(v), maxloc(v, back=.true.), size(maxloc(v)) = ', maxloc(v), maxloc(v, back=.true.), size(maxloc(v))
loc64 = maxloc(u, kind=int64)
print '(a, i0)', 'kind of maxloc(u, kind=int64): ', kind(loc64)
print '(a, 2i3)', 'maxloc of nothing selected = ', maxloc(u, mask=u < 0.0_dp)
! findloc: where is a given value (equality, so use it for integers, logicals, characters)
print '(a, 2i3)', 'findloc(u, 43.0) = ', findloc(u, 43.0_dp)
print '(a, 2i3)', 'findloc(u, 43.0, back) = ', findloc(u, 43.0_dp, back=.true.)
print '(a, 3i3)', 'findloc(u, 43.0, dim=1)= ', findloc(u, 43.0_dp, dim=1)
print '(a, 2i3)', 'findloc(u, 99.0) = ', findloc(u, 99.0_dp)
print '(a, i0, a, i0)', 'first .true. in v>5: ', findloc(v > 5.0_dp, .true., dim=1), &
' last: ', findloc(v > 5.0_dp, .true., dim=1, back=.true.)
! subscripts come back relative to the array as an expression, not the declared bounds
allocate (w(0:3, -1:1))
w = u
loc = maxloc(w)
print '(a, 2i3, a, 2i3)', 'maxloc(w) with w(0:3,-1:1) = ', loc, ' declared subscripts of that element: ', &
loc + lbound(w) - 1
print '(a, f5.1, a, f5.1)', 'w(1, 1) = ', w(1, 1), ' maxval(w) = ', maxval(w)
end program locations
maxloc(u) = 2 3
maxloc(u, back=.true.) = 4 3
minloc(u) = 1 1
maxloc(u, dim=1) = 4 4 2
minloc(u, dim=2) = 1 1 1 1
maxloc(u, mask=u<40) = 3 3
maxloc(v, dim=1) = 2
maxloc(v), maxloc(v, back=.true.), size(maxloc(v)) = 2 6 1
kind of maxloc(u, kind=int64): 8
maxloc of nothing selected = 0 0
findloc(u, 43.0) = 2 3
findloc(u, 43.0, back) = 4 3
findloc(u, 43.0, dim=1)= 0 0 2
findloc(u, 99.0) = 0 0
first .true. in v>5: 2 last: 6
maxloc(w) with w(0:3,-1:1) = 2 3 declared subscripts of that element: 1 1
w(1, 1) = 43.0 maxval(w) = 43.0
The output confirms the rules: maxloc(u) is 2 3 (the first 43 in element order) and maxloc(u, back=.true.) is 4 3;
maxloc(u, dim=1) gives 4 4 2 (the first row that holds each column’s maximum); maxloc(v) has size 1;
findloc(u, 43.0, dim=1) gives 0 0 2 because columns 1 and 2 contain no 43; and for w(0:3, -1:1) the element
maxloc reports as 2 3 is w(1, 1).
Inquiry functions
The inquiry functions describe an array without computing anything from its values, which is why the standard lets them be applied to arrays whose elements are undefined (16.2.2).
| Function | Result |
|---|---|
|
The number of elements, or the extent along |
|
A rank-one array of the extents; for a scalar it is an array of size zero. |
|
The lower and upper bounds. Without |
|
The rank of any data object as a scalar integer (Fortran 2018); 0 for a scalar. Works on assumed-rank dummies, where it is the way to find the rank at run time. |
|
True if the array is contiguous in memory (Fortran 2008). For a pointer it asks whether its target is contiguous. |
Details that matter in practice (16.9.119, 16.9.215, 16.9.185, 16.9.194):
-
lboundanduboundof a whole array return the declared bounds, so foru(0:4, -1:2)they are[0, -1]and[4, 2]. Anything that is not a whole array has lower bounds of 1: an array section, an array expression and a function result. An assumed-shape dummy argument also has lower bounds of 1 unless the dummy declares another lower bound, whatever the bounds of the actual argument were. -
For a dimension of extent zero,
lboundis 1 anduboundis 0, soubound(a, dim) - lbound(a, dim) + 1is always the extent (zero), anduboundis smaller thanlbound. -
size(a)is the product ofshape(a)and is zero when any extent is zero. -
shapeandsizetake an optionalkind, butrankdoes not. -
size(shape(x))is the rank ofx, and is 0 for a scalar.
module inquiry_demo
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
contains
subroutine describe(a)
real(dp), intent(in) :: a(:, :) ! assumed shape: lower bounds are 1
print '(a, 2i3, a, 2i3, a, 2i3)', ' in describe: shape', shape(a), ' lbound', lbound(a), ' ubound', ubound(a)
end subroutine describe
end module inquiry_demo
program inquiry
use, intrinsic :: iso_fortran_env, only: int8, int64
use inquiry_demo
implicit none
real(dp), allocatable :: u(:, :)
real(dp) :: tile(2, 2)
real(dp), pointer :: p(:, :)
real(dp), allocatable, target :: store(:, :)
integer :: n = 0
allocate (u(0:4, -1:2), source=0.0_dp) ! 5 x 4, declared bounds 0:4 and -1:2
print '(a, 2i3)', 'shape(u) =', shape(u)
print '(a, 2i3)', 'lbound(u) =', lbound(u)
print '(a, 2i3)', 'ubound(u) =', ubound(u)
print '(a, 3i3)', 'size(u), size(u,1), size(u,2) =', size(u), size(u, 1), size(u, 2)
print '(a, i0)', 'rank(u) = ', rank(u)
print '(a, i0)', 'rank(3.0) = ', rank(3.0)
print '(a, 2i3)', 'lbound(u, dim=2), ubound(u, dim=2) =', lbound(u, dim=2), ubound(u, dim=2)
print '(a, i0, a, i0)', 'kind(shape(u)) = ', kind(shape(u)), ' kind(shape(u, kind=int8)) = ', kind(shape(u, kind=int8))
print '(a, i0, a, i0)', 'kind(size(u, kind=int64)) = ', kind(size(u, kind=int64)), ' kind(size(u)) = ', kind(size(u))
print '(a)', 'the same array passed to an assumed-shape dummy:'
call describe(u)
print '(a)', 'an array section and an array expression:'
call describe(u(1:3, 0:1))
call describe(u + 1.0_dp)
print '(a, 2i3)', ' lbound of the section u(1:3,0:1) in place =', lbound(u(1:3, 0:1))
print '(a, 2i3)', ' lbound of the expression (u + 1) =', lbound(u + 1.0_dp)
! zero-sized dimensions
deallocate (u)
allocate (u(3, 0))
print '(a, 2i3, a, 2i3, a, i0)', 'zero size: shape', shape(u), ' ubound', ubound(u), ' size ', size(u)
! scalars, and inquiry without a defined value
print '(a, i0, a, i0)', 'size(shape(tile)) = ', size(shape(tile)), ' size(shape(n)) = ', size(shape(n))
! pointer association and contiguity
allocate (store(6, 6), source=0.0_dp)
p => store(::2, ::2)
print '(a, l1, a, l1, a, l1)', 'is_contiguous(store) ', is_contiguous(store), ' (store(::2,::2)) ', &
is_contiguous(p), ' (store(:,2:3)) ', is_contiguous(store(:, 2:3))
print '(a, 2i3)', 'shape(p) = ', shape(p)
end program inquiry
shape(u) = 5 4
lbound(u) = 0 -1
ubound(u) = 4 2
size(u), size(u,1), size(u,2) = 20 5 4
rank(u) = 2
rank(3.0) = 0
lbound(u, dim=2), ubound(u, dim=2) = -1 2
kind(shape(u)) = 4 kind(shape(u, kind=int8)) = 1
kind(size(u, kind=int64)) = 8 kind(size(u)) = 4
the same array passed to an assumed-shape dummy:
in describe: shape 5 4 lbound 1 1 ubound 5 4
an array section and an array expression:
in describe: shape 3 2 lbound 1 1 ubound 3 2
in describe: shape 5 4 lbound 1 1 ubound 5 4
lbound of the section u(1:3,0:1) in place = 1 1
lbound of the expression (u + 1) = 1 1
zero size: shape 3 0 ubound 3 0 size 0
size(shape(tile)) = 2 size(shape(n)) = 0
is_contiguous(store) T (store(::2,::2)) F (store(:,2:3)) T
shape(p) = 3 3
The output shows the declared bounds 0 -1 and 4 2 of u(0:4, -1:2), the dummy argument in describe seeing
lower bounds 1 1 for the whole array, a section and an expression alike, and the zero-sized u(3, 0) with
ubound equal to 3 0. The pointer p => store(::2, ::2) is not contiguous, while store(:, 2:3), a block of whole
columns, is. The consequences for argument passing, copy-in and copy-out, and the contiguous attribute are on
Array Arguments: Assumed Shape, Size and Rank.
Construction and manipulation
These functions build a new array from existing ones. None of them changes its arguments.
| Call | What it returns |
|---|---|
|
The elements of |
|
|
|
The elements of |
|
An array of the shape of |
|
Elemental: element-wise, |
|
A circular shift: element |
|
An end-off shift: the elements that fall off one end are lost and |
|
The rank-two array with rows and columns exchanged. It has no |
The example runs each of them on small integer arrays so the result can be checked by hand. The two lines at the end build
a one-dimensional second difference (the 1D heat Laplacian) twice: with cshift, which treats the array as periodic, and with
eoshift, which keeps the edges fixed by supplying the boundary value.
program construction
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
integer :: a(2, 3), b(3, 2), m(3, 3)
integer :: v(6) = [1, 2, 3, 4, 5, 6]
logical :: keep(2, 3)
integer, allocatable :: packed(:)
real(dp) :: u(5), lap(5)
! reshape: array element order is column-major; order= permutes it; pad= fills the rest
a = reshape(v, [2, 3])
print '(a, 6i3)', 'reshape(v,[2,3]) ', a
b = reshape(v, [3, 2], order=[2, 1])
print '(a, 6i3)', 'reshape(v,[3,2],order=[2,1])', b
m = reshape([1, 2, 3, 4], [3, 3], pad=[0], order=[2, 1])
print '(a, 9i3)', 'with pad=[0] ', m
print '(a, 6i3)', 'transpose(a) = ', transpose(a)
! spread: replicate along a new dimension
print '(a, 6i3)', 'spread([1,2,3],dim=1,ncopies=2) ', spread([1, 2, 3], dim=1, ncopies=2)
print '(a, 6i3)', 'spread([1,2,3],dim=2,ncopies=2) ', spread([1, 2, 3], dim=2, ncopies=2)
! pack and unpack
keep = a > 2
packed = pack(a, keep)
print '(a, 4i3)', 'pack(a, a>2) ', packed
print '(a, 6i3)', 'pack(a, a>2, vector=v) ', pack(a, keep, vector=v)
print '(a, 6i3)', 'unpack(packed, keep, 0) ', unpack(packed, keep, 0)
print '(a, 6i3)', 'unpack(packed, keep, field=-a)', unpack(packed, keep, field=-a)
! merge: elementwise choice
print '(a, 6i3)', 'merge(a, -a, keep) ', merge(a, -a, keep)
print '(a, 6i3)', 'merge(a, 0, mod(a,2)==0) ', merge(a, 0, mod(a, 2) == 0)
! cshift and eoshift: periodic and fixed boundaries of a 1D stencil
print '(a, 6i3)', 'cshift(v, 1) ', cshift(v, 1)
print '(a, 6i3)', 'cshift(v, -2) ', cshift(v, -2)
print '(a, 6i3)', 'eoshift(v, 2) ', eoshift(v, 2)
print '(a, 6i3)', 'eoshift(v, -2, boundary=-1) ', eoshift(v, -2, boundary=-1)
print '(a, 6i3)', 'cshift(a, shift=[1,-1], dim=2)', cshift(a, shift=[1, -1], dim=2)
print '(a, 6i3)', 'eoshift(a, 1, dim=1) ', eoshift(a, 1, dim=1)
u = [0.0_dp, 1.0_dp, 4.0_dp, 9.0_dp, 16.0_dp]
lap = cshift(u, 1) - 2.0_dp*u + cshift(u, -1) ! periodic second difference
print '(a, 5f6.1)', 'periodic laplacian ', lap
lap = eoshift(u, 1, boundary=u(5)) - 2.0_dp*u + eoshift(u, -1, boundary=u(1)) ! fixed edges
print '(a, 5f6.1)', 'fixed laplacian ', lap
end program construction
reshape(v,[2,3]) 1 2 3 4 5 6
reshape(v,[3,2],order=[2,1]) 1 3 5 2 4 6
with pad=[0] 1 4 0 2 0 0 3 0 0
transpose(a) = 1 3 5 2 4 6
spread([1,2,3],dim=1,ncopies=2) 1 1 2 2 3 3
spread([1,2,3],dim=2,ncopies=2) 1 2 3 1 2 3
pack(a, a>2) 3 4 5 6
pack(a, a>2, vector=v) 3 4 5 6 5 6
unpack(packed, keep, 0) 0 0 3 4 5 6
unpack(packed, keep, field=-a) -1 -2 3 4 5 6
merge(a, -a, keep) -1 -2 3 4 5 6
merge(a, 0, mod(a,2)==0) 0 2 0 4 0 6
cshift(v, 1) 2 3 4 5 6 1
cshift(v, -2) 5 6 1 2 3 4
eoshift(v, 2) 3 4 5 6 0 0
eoshift(v, -2, boundary=-1) -1 -1 1 2 3 4
cshift(a, shift=[1,-1], dim=2) 3 6 5 2 1 4
eoshift(a, 1, dim=1) 2 0 4 0 6 0
periodic laplacian 17.0 2.0 2.0 2.0 -23.0
fixed laplacian 1.0 2.0 2.0 2.0 -7.0
The cases to read carefully: reshape(v, [3, 2], order=[2, 1]) returns 1 3 5 2 4 6 in memory order, which is the
matrix [[1, 2], [3, 4], [5, 6]] filled row by row; spread([1,2,3], dim=1, ncopies=2) gives a 2 by 3 array whose rows
are the vector (printed column by column); pack(a, a>2, vector=v) keeps the selected 3 4 5 6 and then the unused tail
5 6 of v; unpack with a scalar 0 as field zeroes the elements that were not selected;
cshift(v, 1) moves 1 from the front to the back and cshift(v, -2) the last two to the front; eoshift(v, 2) drops
the first two and appends two zeros; and cshift(a, shift=[1, -1], dim=2) shifts the first row one way and the second
the other. In the Laplacian lines, the periodic version wraps the end values, giving 17 and -23 at the edges where the
eoshift version, with the end values repeated as the boundary, gives 1 and -7.
Shifts, sections and array temporaries
cshift and eoshift are convenient and they are also the functions where the convenience has a cost. Each call
creates a complete shifted copy of the array, so a stencil written with four shifts allocates and fills four temporaries
per sweep. Writing the same update with array sections needs no temporaries: the compiler reads the original array
through the shifted views. The next program computes the five-point Laplacian of a 6 by 5 field both ways; the interior
values are identical and the difference is what the compiler has to do.
program stencil
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
integer, parameter :: nx = 6, ny = 5
real(dp) :: u(nx, ny), lap_shift(nx, ny), lap_slice(nx, ny)
integer :: i, j
do j = 1, ny
do i = 1, nx
u(i, j) = real((i - 3)**2 + (j - 2)**2, dp)
end do
end do
! (a) shifts: simple to write, but each cshift/eoshift is a whole-array temporary
lap_shift = cshift(u, 1, dim=1) + cshift(u, -1, dim=1) + cshift(u, 1, dim=2) + cshift(u, -1, dim=2) &
- 4.0_dp*u
! (b) sections: no temporaries, only the interior is updated
lap_slice = 0.0_dp
lap_slice(2:nx - 1, 2:ny - 1) = u(3:nx, 2:ny - 1) + u(1:nx - 2, 2:ny - 1) &
+ u(2:nx - 1, 3:ny) + u(2:nx - 1, 1:ny - 2) &
- 4.0_dp*u(2:nx - 1, 2:ny - 1)
print '(a, f6.1)', 'largest interior difference: ', &
maxval(abs(lap_shift(2:nx - 1, 2:ny - 1) - lap_slice(2:nx - 1, 2:ny - 1)))
print '(a, f6.1)', 'interior laplacian (all equal for this field): ', lap_slice(3, 3)
print '(a, 2f6.1)', 'wrapped edge value from cshift vs untouched slice: ', lap_shift(1, 1), lap_slice(1, 1)
end program stencil
largest interior difference: 0.0
interior laplacian (all equal for this field): 4.0
wrapped edge value from cshift vs untouched slice: 9.0 0.0
gfortran -Warray-temporaries makes the cost visible. For this program it reports, for the line that calls
cshift four times:
Warning: Creating array temporary at (1) [-Warray-temporaries]
Warning: Creating array temporary at (1) [-Warray-temporaries]
Warning: Creating array temporary at (1) [-Warray-temporaries]
Warning: Creating array temporary at (1) [-Warray-temporaries]
and nothing for the statement written with sections. Use shifts for clarity or when the boundary condition is the point
(periodic wrap-around is a one-liner with cshift); use sections in a kernel that runs every time step. The same trade-off
is discussed for the full solver on
Performance and Memory Layout and the sections
themselves on Arrays Fundamentals. Other operations that
produce temporaries are pack, spread, reshape, transpose and any array expression passed to an argument that
needs contiguous storage.
matmul and dot_product
matmul(a, b) multiplies matrices or vectors. The shapes allowed are the ones that make mathematical sense (16.9.134):
[n, m] times [m, k] gives [n, k]; [m] times [m, k] gives [k] (a row vector times a matrix); [n, m] times
[m] gives [n] (a matrix times a column vector). The two arguments cannot both be rank one; use dot_product or
sum(x * y) for that. The type of the result follows the rules of the * operator (so real times complex is complex),
and logical arguments give a logical result in which element (i, j) is true when any(a(i, :) .and. b(:, j)).
dot_product(x, y) is the sum of the products of corresponding elements of two vectors of the same size. For complex
vectors the first argument is conjugated, dot_product(z, w) is sum(conjg(z) * w), unlike sum(z * w). For logical
vectors it is any(x .and. y). The third example below shows all of it.
program matrix
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
real(dp) :: a(2, 3), x(3), y(2), b(3, 2), c(2, 2)
complex(dp) :: z(2), w(2)
logical :: p(2, 2), q(2)
a = reshape([1.0_dp, 4.0_dp, 2.0_dp, 5.0_dp, 3.0_dp, 6.0_dp], [2, 3]) ! rows [1 2 3] and [4 5 6]
x = [1.0_dp, 0.0_dp, -1.0_dp]
y = [1.0_dp, 2.0_dp]
b = transpose(a)
print '(a, 2f7.1)', 'matmul(a, x) = ', matmul(a, x)
print '(a, 3f7.1)', 'matmul(y, a) = ', matmul(y, a)
c = matmul(a, b)
print '(a, 4f7.1)', 'matmul(a, b) = ', c
print '(a, f7.1)', 'dot_product(x, x) = ', dot_product(x, x)
print '(a, f7.1)', 'sum(a * a) = ', sum(a*a)
z = [(1.0_dp, 2.0_dp), (0.0_dp, 1.0_dp)]
w = [(3.0_dp, 0.0_dp), (0.0_dp, 1.0_dp)]
print '(a, 2f6.1)', 'dot_product(z, w) = ', dot_product(z, w)
print '(a, 2f6.1)', 'sum(z * w) = ', sum(z*w)
print '(a, 2f6.1)', 'sum(conjg(z) * w) = ', sum(conjg(z)*w)
p = reshape([.true., .false., .true., .true.], [2, 2])
q = [.true., .true.]
print '(a, 2l2)', 'matmul(p, q) (logical: or of ands) = ', matmul(p, q)
print '(a, l2)', 'dot_product(q, q) (logical) = ', dot_product(q, q)
end program matrix
matmul(a, x) = -2.0 -2.0
matmul(y, a) = 9.0 12.0 15.0
matmul(a, b) = 14.0 32.0 32.0 77.0
dot_product(x, x) = 2.0
sum(a * a) = 91.0
dot_product(z, w) = 4.0 -6.0
sum(z * w) = 2.0 6.0
sum(conjg(z) * w) = 4.0 -6.0
matmul(p, q) (logical: or of ands) = T T
dot_product(q, q) (logical) = T
In the output, matmul(a, x) with x = [1, 0, -1] gives -2 -2 (the first element of each row minus its last),
matmul(y, a) gives 9 12 15, matmul(a, b) with b = transpose(a) is the symmetric 2 by 2 matrix printed in memory order,
14 32 32 77, and for complex input dot_product(z, w) equals sum(conjg(z) * w) (4, -6) but not the unconjugated
sum(z * w) (2, 6).
How the compilers implement matmul is a performance decision, not a language one. GNU Fortran inlines small products
(-finline-matmul-limit=n, default 30, with front-end optimisation on) and can call an external BLAS gemm for larger
ones when asked with -fexternal-blas and a link against a BLAS library (-fblas-matmul-limit=n sets the size above
which BLAS is used, and -fexternal-blas64 selects a library with 64-bit integers). Without those options the library
routine in libgfortran is used, which is correct but not tuned for large matrices. For big problems, call BLAS and
LAPACK directly; see
BLAS, LAPACK and Numerical Libraries. Compiler
options are covered on Compilers and Compiler Options.
Choosing the right function
Diagnostics
Misuse of these functions is caught at compile time when the shapes or arguments are constants and at run time, with
-fcheck=bounds (part of -fcheck=all) or by the library, otherwise. The following non-conforming programs, each of
which is in its own file, show the messages. All of them are shown with GNU Fortran 16.2 and then LLVM Flang 23.
| Non-conforming statement | GNU Fortran 16.2 | LLVM Flang 23 |
|---|---|---|
|
|
compiles; at run time |
|
|
at run time |
|
|
|
|
|
|
|
|
compiles; at run time |
|
|
|
|
|
|
|
|
|
Not everything is diagnosed. Conformance of shapes is checked, but a maxloc result assigned to a scalar is rejected
only because the types do not match, a reduce over an empty array without identity is an error termination whose message
is compiler specific, a wrong lower bound applied to a maxloc result is not diagnosed by anything, and an integer
sum that overflows is silent. Run development builds with -fcheck=all (GNU Fortran) and keep -Wall -Wextra enabled.
Where each function comes from
The array intrinsics of Fortran 90 (sum, product, maxval, minval, count, any, all, maxloc, minloc,
size, shape, lbound, ubound, reshape, spread, pack, unpack, merge, cshift, eoshift, transpose,
matmul, dot_product) are the same in every later standard, with the following changes:
| Standard | Addition | Compiler support |
|---|---|---|
Fortran 2003 |
|
Both compilers, all current versions. |
Fortran 2008 |
|
Both compilers. Used here with GNU Fortran 16.2 and LLVM Flang 23. |
Fortran 2018 |
|
Both compilers; GNU Fortran 16.2 has the empty-array defect described above. |
Fortran 2023 |
No new array intrinsic function. |
See that page. |
Nothing described above is obsolescent or deleted in Fortran 2023. This is a point where older books are out of date:
texts written before Fortran 2008 describe only maxloc and minloc for locating elements (and compensate with
maxval followed by a masked minval or a loop), implement norms as sqrt(sum(x**2)), write reductions with
user-defined functions and loops for lack of reduce, and know nothing of findloc, parity, iall/iany/iparity
or is_contiguous. Books written for Fortran 2018 are current on this subject; the additions of Fortran 2023 concern
do concurrent, rank-independent declarations and the intrinsics of other topics, not these functions.
Pitfalls
| Mistake | Effect | Fix |
|---|---|---|
Assigning |
Compile error ( |
Use an integer array of size |
Using the |
The wrong element, silently. |
Add |
Treating a |
Out-of-bounds access or an element near the origin. |
Test for 0 before using it; see "no match returns zero". |
|
It finds nothing, because |
Compare with a tolerance in a loop or |
|
Silent overflow. |
Convert to a wider kind first. |
Comparing two |
A result that changes with the optimisation level. |
Compare with a tolerance. |
|
Error termination (and, in GNU Fortran 16.2, wrong values for a zero-sized array without a mask). |
Pass |
A non-associative |
The result depends on grouping and differs between compilers and optimisation levels. |
Use an associative operation or |
|
An array temporary per call, with the memory traffic that goes with it. |
Use array sections. |
|
|
Use |
Expecting |
It is 1. |
Compute with the section’s own |
|
Correct but slow without an optimised BLAS. |
Link a BLAS with |
|
The first argument is conjugated, which |
Use the one that matches the mathematics. |