Table of Contents Previous Chapter
We have seen that the real power of computers lies in their ability to do many of the same types of calculations very rapidly. This is typically accomplished using arrays and do loops. However, Fortran 90 has been designed to simplify the coding of do loops, by using array operations, which operate on entire arrays simultaneously, without the need for coding do loops. For example, consider the following code:
program array_operations ! ! define variables ! real(kind=4), dimension (100) :: a, b, c ! ! fill a and b ! do i=1,100 a = real(i) b = 1./a end do ! ! now compute c as an array operation ! c = a + b ! stop ! end program array_operations
Note that in the above computation, all 100 elements of array b are added to all 100 elements of array a, to result in all 100 elements of array c - all done with one simple statement, using the entire arrays. In Fortran 90, this is termed an "array operation."
The ability to operate (compute) entire arrays is an extremely powerful tool, as it means that Fortran 90 provides parallelism automatically, as Fortran 90 will spread array operations across multiple CPU's on parallel supercomputers, or cause vector instructions to be issued on vector supercomputers. This all happens automatically, and is transparent to the user. This transparency makes it easy for the programmer, but sometimes results in inefficiency if the compiler is not sufficiently "smart" so as to organize the parallel computation efficiently. However, this transparency, at the expense of possibly some inefficiency, is an excellent way to introduce the concepts of parallelism, and to learn parallel programming. Fortran 90 is a parallel computer language. The great advantage of this is that Fortran 90 programs are portable among all computers which have Fortran 90 compilers, so that one no longer needs to learn the special syntax of parallel operations for each individual supercomputer.
Another way to view arrays, then, is as "objects," or single data types containing possibly many numbers. This is exactly consistent with the concepts of vectors, matrices and tensors, used widely in physics and engineering. Indeed, Fortran 90 is designed to perform scientific and engineering computations on array objects just for this purpose. Where one can write a matrix or vector operation as a scientific equation, one can now usually program it directly in Fortran 90 as an array operation.
A few basic rules apply when performing array operations. A step by step procedure is:
Note: The arrays must be the same shape (i.e. number of dimensions) and size (length), so that there is a one to one correspondence among array elements. Also, note that scalars (such as pi above) work in array operations element by element.
See Figure 13.13 on page 464 for some intrinsic functions used for arrays.
Sometimes, we wish to perform array operations subject to a conditional. That is, we want to perform array operations on only a subset of the array, subject to some conditional. Here, we can use a "truth mask," which is a "truth" array of the same shape and size as the array for which we are performing the array operation. The computation is done only for those elements of the array, where the "truth mask" is true. This is just like including an if-then-else inside a do loop. This is done in Fortran 90 using the "where" statement:
where (truth_mask) array_assignment_statement end where
For example, suppose we want to invert, element by element, an array:
program invert ! ! set storage ! real (kind=2) :: a(100,100), b(100,100) ! ! fill a ! do 1 = 1, 100 do j = 1, 100 a(i,j) = real(i-1)*real(j-1) end do end do ! ! array masked operation ! where (a /= 0.) b = 1./a end where ! stop ! end program invert
Typically, all 10,000 computations are performed, and are stored only where the truth_mask is "true." Some implementations of Fortran 90 create a temporary truth_array (all 10,000 elements) in memory when doing this computation. Thus, lots of memory can be used when doing array operations in Fortran 90 (but memory sizes are getting larger and larger all the time).
We have seen how to control array operations indirectly based on conditional truth statements. It is also possible to control array operations directly using array subsections. Entire "parent" arrays must be defined in type declarations, and allocated if allocatable. Then, operations can be done on only portions of the arrays, called "array sections," using a triplet to define array indices, as follows for a one-dimensional array:
array(index_start, index_stop, index_stride)
This is exactly like the three indices in a do loop. The first defines the starting index, the second defines the sopping index, and the third defines the stride. For example, suppose we wish to double all array elements with an even index, and square all array elements with an odd index:
program sub_sections ! ! set storage ! real, dimension (100000) :: a, b ! ! read all of array a ! read *, a ! ! array sub-section operations ! ! - even indices: double ! b(2:100000:2) = 2.*a(2:100000:2) ! ! - odd indices: square ! b(1:100000:2) = a(1:100000:2)**2 ! ! output: print all of b ! print *, b ! stop ! end program sub_sections
Table of Contents Next Chapter