Multidimensional Arrays

The arrays we have seen so far this semester are one-dimensional arrays.  We often visualize such arrays as being laid out in a single row.

A two-dimensional array can be visualized as a table made up of several rows and columns.  If the columns are a single dimension array then a table (two-dimensional array) can be thought of as a series of single dimensional arrays (columns).

 
 
 
 

Above, single dimension array, below, table, two-dimensional array.

       
       
       
       

Why think of a single-dimension array as a column?  Because in physical memory, arrays are stored by column, such that all of column 1's contents reside consecutively then column 2's, column 3's, and so on.  Such arrangement in memory is known as column major order.  Not all programming languages will be column major order, some will be row major order.

Declaring Two Dimensional Arrays

PROGRAM twoD
IMPLICIT NONE

        INTEGER, PARAMETER :: SIZE = 5
        INTEGER :: array(SIZE, SIZE)
        INTEGER :: i
        INTEGER :: j

        DO i = 1, 5
                DO j = 1, 5
                        array(i, j) = j
                END DO
        END DO

        DO i = 1, 5
                DO j = 1, 5
                        WRITE(*,*) "Array (", i, ",", j, ") = ", array(i,j)
                END DO
        END DO

END PROGRAM twoD

The program above will iterate through the entire array first initializing the array and then printing the array contents.

Output:

 Array ( 1 , 1 ) =  1
 Array ( 1 , 2 ) =  2
 Array ( 1 , 3 ) =  3
 Array ( 1 , 4 ) =  4
 Array ( 1 , 5 ) =  5
 Array ( 2 , 1 ) =  1
 Array ( 2 , 2 ) =  2
 Array ( 2 , 3 ) =  3
 Array ( 2 , 4 ) =  4
 Array ( 2 , 5 ) =  5
 Array ( 3 , 1 ) =  1
 Array ( 3 , 2 ) =  2
 Array ( 3 , 3 ) =  3
 Array ( 3 , 4 ) =  4
 Array ( 3 , 5 ) =  5
 Array ( 4 , 1 ) =  1
 Array ( 4 , 2 ) =  2
 Array ( 4 , 3 ) =  3
 Array ( 4 , 4 ) =  4
 Array ( 4 , 5 ) =  5
 Array ( 5 , 1 ) =  1
 Array ( 5 , 2 ) =  2
 Array ( 5 , 3 ) =  3
 Array ( 5 , 4 ) =  4
 Array ( 5 , 5 ) =  5
Question, does i represent the rows or the columns of this table?

Memory.  How in memory are arrays stored?

Let's change the problem above.

        INTEGER, PARAMETER :: COLSIZE = 3
        INTEGER, PARAMETER :: ROWSIZE = 2
        INTEGER :: array(ROWSIZE, COLSIZE)
        INTEGER :: i
        INTEGER :: j

        DO i = 1, ROWSIZE
                DO j = 1, COLSIZE
                        array(i, j) = j
                END DO
        END DO

        DO i = 1, ROWSIZE
                DO j = 1, COLSIZE
                        WRITE(*,*) "Array (", i, ",", j, ") = ", array(i,j)
                END DO
        END DO
Output:
 Array ( 1 , 1 ) =  1
 Array ( 1 , 2 ) =  2
 Array ( 1 , 3 ) =  3
 Array ( 2 , 1 ) =  1
 Array ( 2 , 2 ) =  2
 Array ( 2 , 3 ) =  3

But in memory, the elements are stored as follows: (by columns)

(1, 1) (2, 1) (1, 2) (2, 2) (1, 3) (2, 3)

Example, sum each row and each column

Data File

3
4
5
10
1
2

Which equals the following array

3 4
5 10
1 2
Output:
 Column  1  Sum  9
 Column  2  Sum  16
 Row  1  Sum  7
 Row  2  Sum  15
 Row  3  Sum  3
Code
PROGRAM sum
IMPLICIT NONE

        INTEGER, PARAMETER :: ROWSIZE = 3
        INTEGER, PARAMETER :: COLSIZE = 2

        INTEGER :: array(ROWSIZE, COLSIZE)
        INTEGER :: rowsum(ROWSIZE)
        INTEGER :: colsum(COLSIZE)

        INTEGER :: row
        INTEGER :: col
        INTEGER :: ierror
        INTEGER :: rowc = 1
        INTEGER :: colc = 1

        rowsum = 0
        colsum = 0

        OPEN(UNIT = 8, FILE = "data", STATUS = "OLD", ACTION = "READ", IOSTAT = ierror)

        IF (ierror == 0) THEN
          DO row = 1, ROWSIZE
                DO col = 1, COLSIZE
                        READ(8, *) array(row, col)
                END DO
          END DO
          
          DO row = 1, ROWSIZE
            DO col = 1, COLSIZE
                  colsum(col) = colsum(col) + array(row, col)
                  rowsum(row) = rowsum(row) + array(row, col)
            END DO
          END DO

          DO col = 1, COLSIZE
                WRITE(*,*) "Column ", col, " Sum ", colsum(col)
          END DO

          DO row = 1, ROWSIZE
                WRITE(*,*) "Row ", row, " Sum ", rowsum(row)
          END DO
        ELSE
                WRITE(*,*) "Couldn't open file!"
        END IF

        CLOSE(UNIT = 8)

END PROGRAM sum

Arrays Beyond Two Dimensions

According to the text an array can have up to seven different subscripts (dimensions). 

Above is a possible visualization of a three dimensional array.

Example, Declare a 3D array and initialize its values to be the plane index times the row index times the column index.  Print the results by plane.

 Plane # 1
 1 2 3
 2 4 6
 Plane # 2
 2 4 6
 4 8 12
 Plane # 3
 3 6 9
 6 12 18
Code
PROGRAM threeD
IMPLICIT NONE

        INTEGER, PARAMETER :: NUMPLANE = 3
        INTEGER, PARAMETER :: NUMROW = 2
        INTEGER, PARAMETER :: NUMCOL = 3

        INTEGER :: array (NUMPLANE, NUMROW, NUMCOL)

        INTEGER :: plane
        INTEGER :: row
        INTEGER :: col

        DO plane = 1, NUMPLANE
          DO row = 1, NUMROW
            DO col = 1, NUMCOL
                array(plane, row, col) = plane * row * col
            END DO
          END DO
        END DO

        DO plane = 1, NUMPLANE
          WRITE(*,*) "Plane #", plane
          DO row = 1, NUMROW
                WRITE(*,*) (array(plane, row, col), col = 1, NUMCOL)
          END DO
        END DO

END PROGRAM threeD

Elemental Intrinsic Functions

Such functions are specified for scalar arguments but may also be applied to array arguments.

	REAL :: x(4) = (/ 0.0, 1.1, 2.2, 3.3 /)
	REAL :: y(4)
	
	y = SIN(x)
The result is the same as if the function were applied to each element of the array. The shape of the resulting array must be the same as the input argument.

Inquiry Intrinsic Functions

This type of function will return a value that is dependent on the properties of the object being evaluated.

LBOUND(array) - Returns ALL of the lower bounds of array.

	INTEGER :: array (NUMPLANE, NUMROW, NUMCOL)
        WRITE(*,*) "LBOUND is ", LBOUND(array)
        LBOUND is  1 1 1
LBOUND(array, dim) - Returns the lower bound of dim of array.

There is a similiar function called UBOUND.

SHAPE(array) - Returns the shape of array.

WRITE(*,*) "SHAPE is ", SHAPE(array)
SHAPE is  3 2 3

SIZE(array) - Returns the number of elements in the array.

WRITE(*,*) "SIZE is ", SIZE(array)
SIZE is  18

Transformational Intrinsic Functions (Appendix B has much more information)

These function have one or more array-valued arguments or an array-valued result.  The difference between elemental and transformational is that transformational functions operate on arrays as a whole.

        INTEGER :: x(5) = (/ 1, 2, 3, 4, 5 /)
        INTEGER :: y

        y = SUM(x)

        WRITE(*,*) y
The value of y is 15.

The FORTRAN 90 WHERE Construct

Let's say we have an array val and we want the LOG of each element in val to be stored in a second array called logval.  However, some of the values in val are not positive and we want those values to be assigned a value of -1 in logval.

One way to accomplish this:

        DO i = 1, 3
                DO j = 1, 3
                  IF(val(i,j) > 0) THEN
                        logval(i,j) = LOG(val(i,j))
                  ELSE
                        logval(i,j) = -1
                  END IF
                END DO
        END DO
The WHERE construct will help accomplish the same task on an element by element basis.
        WHERE (val > 0)
                logval = LOG(val)
        ELSEWHERE
                logval = - 1
        END WHERE
Which is better? That is, which would be faster and save the CPU some processing? Difficult to say without a way to time. Learning to time the two approaches might be an interesting test if we noticed the performance of either method declining for a large number of array elements.