Array

In Fortran, the scalar are defined to be zero-rank.

In Fortran, the lower bound of index can be any integer, and default lower bound of index is NOT 0 but 1. The elements of array in Fortran are in column-major order, like \(a_{00} \to a_{10} \to a_{01} \to a_{11}\), which is DIFFERENT the row-major order in Python and C.

rank(a), lbound(a), ubound(a), shape(a) and size(a) are the rank, the lower bounds of indexes, the upper bounds of indexes, the shape and the size of array a.

Array construction

List elements between (//[ and /)/] to construct an array.

program main
    implicit none
    print *, [1, 2, 3]
    print *, (/1, 2, 3/)
end program main

Implied-DO-loop can be used in an array constructor, which makes the array constructor similar to a list comprehension in Python. You should notice that the loop variable for the implied-DO-loop needs to be declared.

program main
    implicit none
    integer :: i
    print *, [(i**2, i = 1, 9)]
    print *, (/(i**2, i = 1, 9, 1)/)
end program main

However, an array constructor in Fortran can only construct a rank-one array, even though using a nested implied-DO-loop. To maintain a higher rank array, you can use the reshape intrinsic function to reshape the rank-one array. See subsection 16.9.175 of Fortran 2023 interpretation document for documentation of the reshape intrinsic function.

program main
    implicit none
    integer :: i, j
    integer :: a(9)
    a = [(((i+j)**2, i = 1, 3), j = 1, 3)]
    print *, a
    print *, shape(a)
    print *, reshape(a, [3, 3])
    print *, shape(reshape(a, [3, 3]))
end program main

The reshape intrinsic function can also be used to transpose an array.

program main
    implicit none
    integer :: i, a(9), b(3, 3)
    a = [(i, i = 1, 9)]
    b = reshape(a, [3, 3])
    print *, reshape(b, shape(b), order=[2, 1])
    print *, reshape(a, shape(b), order=[2, 1])
end program main

Using a subscript triplet in an array constructor, like [1:9:1] or [(1:9:1)], in an array constructor is INVALID.

Array element and array section

The method to maintain array element or array section is very similar to the method in Numpy, but you should use (/) instead of [/]. However, there is a difference that the “stop” of a subscript triplet can be INCLUDED.

program main
    implicit none
    integer :: i, a(3, 3)
    a = reshape([(i, i = 1, 9)], [3, 3])
    print *, a(2, 2)
    print *, a(1:3:2, ::2)
end program main

We CAN’T maintain an array section of an array section, since the array section is not a “whole array” and the lower and upper bounds of indexes of the array section is not determined. The following program is NOT valid.

program main
    implicit none
    integer :: i, a(3, 3)
    a = reshape([(i, i = 1, 9)], [3, 3])
    print *, a(:, :)(:, :) ! Invalid!
end program main

If an array a is not a “whole array”, lbound(a) and ubound(a) will return the bounds as if lower bounds of indexes of a are all 1, though it is not true.

program main
    implicit none
    integer :: i, a(0:2, 0:2)
    a = reshape([(i, i = 1, 9)], [3, 3])
    print *, lbound(a), ubound(a)
    print *, lbound(a(:, :)), ubound(a(:, :))
end program main

Array declaration

There are two ways to declare an array. One is to add a `attr-spec` which is dimension(`bound-list`). The other is to add a (`array-spec`) which is (`bound-list`) behind the `object-name`. `bound` is {`lower-bound`:}`upper-bound`, and if there is no `lower-bound`:, the lower bound of the index will be 1.

program main
    implicit none
    real :: array1(1, 10)
    real, dimension(1, 10) :: array2
    real :: array3(1:1, 1:10)
    real, dimension(1:1, 1:10) :: array4
end program main

We can declare a deferred-shape array, and the method is similar to that to declare a deferred-shape character. We can then apply the ALLOCATE statement and the DEALLOCATE statement to the deferred-shape array.

program main
    implicit none
    real, allocatable :: array1(:)
    real, dimension(:, :), allocatable :: array2
    allocate(array1(10))
    allocate(array2(0:9, 0:9))
    deallocate(array1)
    deallocate(array2)
end program main

Array assignment and array operations

Array assignment and array operations in Fortran are also very similar to those in Numpy. You can mix zero-rank normal constants and variables with arrays.

program main
    implicit none
    integer :: a(3, 3)
    a = 1
    print *, a
    a = a + 1
    print *, a
end program main

When assign to a deferred-shape array, the deferred-shape array MAY be allocated or reallocated. If we assign an array `b` to a deferred-shape array `a`, and `a` is unallocated or `a` is allocated but in different shape with `b`, `a` will be allocated or reallocated, and lbound(`a`)/ubound(`a`) will be the same as lbound(`b`)/ubound(`b`).

program main
    implicit none
    integer :: i, b(0:2, 0:2)
    integer, allocatable :: a(:, :)
    b = reshape([(i, i = 1, 9)], [3, 3])
    a = b
    print *, lbound(a), ubound(a)
end program main

However, if `a` is allocated and in the same shape with `b`, `a` will NOT be allocated or reallocated.

program main
    implicit none
    integer :: i, b1(0:2, 0:2), b2(-1:1, -1:1) 
    integer, allocatable :: a(:, :)
    b1 = reshape([(i, i = 1, 9)], [3, 3])
    a = b1
    print *, lbound(a), ubound(a)
    b2 = reshape([(i, i = 1, 9)], [3, 3])
    a = b2
    print *, lbound(a), ubound(a)
end program main

If `b` is not an array but a scalar, and `a` is unallocated, it is impossible to automatically allocate `a`, thus the following program is INVALID.

program main
    implicit none
    integer, allocatable :: a(:, :)
    a = 0 ! Invalid!
    print *, lbound(a), ubound(a)
end program main

While, if `a` is allocated, `a` does be reallocated, but the bounds of indexes of `a` will be as before.

program main
    implicit none
    integer, allocatable :: a(:, :)
    allocate(a(0:2, 0:2))
    a = 0
    print *, lbound(a), ubound(a)
end program main

You can use WHERE construct and DO CONCURRENT construct, which is defined as a kind of DO construct in Fortran 2023 interpretation document, to do complex and parallel array assignment, but you should not use FORALL construct, since it is obsolescent. You should notice that the syntax of WHERE construct is SLIGHTLY DIFFERENT from that of IF construct, and the syntax of DO CONCURRENT construct is SLIGHTLY DIFFERENT from those of normal DO construct, IF construct and subscript triplet of array.

WHERE construct

The following two programs are equivalent.

program main
    implicit none
    integer :: i, j, a(3, 3)
    a = reshape([(i, i = 1, 9)], [3, 3])
    do i = 1, 3
        do j = 1, 3
            if (a(i, j) <= 3) then
                a(i, j) = a(i, j) + 1
            else if (a(i, j) <= 6) then
                a(i, j) = a(i, j) + 2
            else
                a(i, j) = a(i, j) + 3
            end if
        end do
    end do
    do i = 1, 3
        print *, a(i, :)
    end do
end program main
program main
    implicit none
    integer :: i, a(3, 3)
    a = reshape([(i, i = 1, 9)], [3, 3])
    where (a <= 3)
        a = a + 1 ! Parallel!
    elsewhere (a <= 6)
        a = a + 2 ! Parallel!
    elsewhere
        a = a + 3 ! Parallel!
    end where
    do i = 1, 3
        print *, a(i, :)
    end do
end program main

The following two programs use WHERE construct too.

program main
    implicit none
    integer :: i, a(3, 3)
    a = reshape([(i, i = 1, 9)], [3, 3])
    where (a(1:2, 2:3) <= 3)
        a(1:2, 2:3) = a(1:2, 2:3) + 1 ! Parallel!
    elsewhere (a(1:2, 2:3) <= 6)
        a(1:2, 2:3) = a(1:2, 2:3) + 2 ! Parallel!
    elsewhere
        a(1:2, 2:3) = a(1:2, 2:3) + 3 ! Parallel!
    end where
    do i = 1, 3
        print *, a(i, :)
    end do
end program main
program main
    implicit none
    integer :: i, a(3, 3), b(3, 3)
    a = reshape([(i, i = 1, 9)], [3, 3])
    b = reshape([(i, i = 1, 9)], [3, 3])
    where (a <= 3)
        b = b + 1 ! Parallel!
    elsewhere (a <= 6)
        b = b + 2 ! Parallel!
    elsewhere
        b = b + 3 ! Parallel!
    end where
    do i = 1, 3
        print *, b(i, :)
    end do
end program main

However, there is a gotcha of WHERE construct. In the following program, log in a = log(a) is an elemental function, thus a in log(a) represents the “> 0.0 part” of array a, and a = log(a) in this program is equivalent to a(6:9) = log(a(6:9)). While, sum in a = a / sum(log(a)) is NOT an elemental function, and a in sum(log(a)) is in “()” behind nonelemental function sum, thus a in sum(log(a)) DOESN’T represent the “> 0.0 part” of array a but represents THE WHOLE OF a, even though log is an elemental function, and a = a / sum(log(a)) in this program is equivalent to a(6:9) = a(6:9) / sum(log(a(:))).

program main
    implicit none
    integer :: i
    real :: a(9)
    a = [(real(i), i = -4, +4)]
    where(a > 0.0)
        ! log is invoked only for positive elements,
        ! because log is elemental.
        a = log(a) 
    end where
    print *, a
    print *, a / sum(a)
    a = [(real(i), i = -4, +4)]
    where(a > 0.0)
        ! log is invoked for ALL elements,
        ! because sum is NOT elemental.
        a = a / sum(log(a)) 
    end where
    print *, a
end program main

DO CONCURRENT construct

The following two programs are equivalent.

program main
    implicit none
    integer :: i, j, a(9), b(9)
    a = [(i, i = 1, 9)]
    b = [(j, j = 1, 9)]
    do i = 1, 3, 1
        do j = 7, 9, 1
            if ((a(i) > 1) .and. (b(j) < 9)) then
                b(i) = -j
                a(j) = -i
            end if
        end do
    end do
    print *, a
    print *, b
end program main
program main
    implicit none
    integer :: i, j, a(9), b(9)
    a = [(i, i = 1, 9)]
    b = [(j, j = 1, 9)]
    do concurrent (i = 1:3:1, j = 7:9:1, &
                   (a(i) > 1) .and. (b(j) < 9))
        b(i) = -j
        a(j) = -i
    end do
    print *, a
    print *, b
end program main