David's Blog

Living a quiet life in Coquitlam, B.C.

Name:
Location: Coquitlam, British Columbia, Canada

Monday, July 20, 2015

FORTRAN: Accessing a 1-D Array as a Multiple-Dimensional Array


FORTRAN allows a one-dimensional array to be passed into a sub-routine and re-dimensioned in such a way that it can be accessed like a multi-dimensional array. This blog post (i) examines what is going on in the cases of re-dimensioning to 2D and 3D arrays, (ii) confirms the accessing method with test code, and (iii) presents corresponding C++ code that produces the identical behaviour.

In the following examples, assume that a one-dimensional array, A, of size 60 has been declared in the main program of the original FORTRAN code, as well as two sub-routines:

REAL A(60)
. . .
SUBROUTINE SUBR1 (ID1MAX, ID2MAX, ARRIN)
. . .
SUBROUTINE SUBR2 (ID1MAX, ID2MAX, ID3MAX, ARRIN)
. . .

SUBR1 accepts a 1-D array, ARRIN, as a parameter and treats it like a 2-D array of dimension (ID1MAX, ID2MAX).
SUBR2 accepts a 1-D array, ARRIN, as a parameter and treats it like a 3-D array of dimension (ID1MAX, ID2MAX, ID3MAX).

These sub-routines are called from the main program like so:

. . .
SUBR1(ID1, ID2, A)
. . .
SUBR2(ID1, ID2, ID3, A)
. . .

An important requirement must be stated: the multi-dimensional arrays defined in the sub-routines must be declared such that they contain the same number of elements as the one-dimensional array declared in the main program. In other words, since A is size 60, the values of ID1 and ID2 passed in to SUBR1 must be such that ID1*ID2 = 60. Similarly for SUBR2: the values of ID1, ID2, and ID3 passed in to SUBR2 must be such that ID1*ID2*ID3 = 60. If this condition is not met, there would be a risk of accessing an invalid element of the array, and having the program return incorrect results--if it completes execution at all.

Furthermore, three programs will be referenced in this post:
(i) the original FORTRAN program;
(ii) a C program created by using f2c to translate the FORTRAN code to C, and which uses FORTRAN 's 1-based array indexing default;
(iii) a completely new C++ program that uses a 0-based array indexing method, but produces identical behaviour as the FORTRAN program.

Let's first examine the case where the A array is passed into a sub-routine and treated as a 2-D array.

Treating a 1-D Array as a 2-D Array

Inside sub-routine SUBR1, the first few statements might be something like the following:

DIMENSION ARRIN(ID1MAX, ID2MAX)
REAL RVAR
. . .
RVAR = ARRIN(2, 2)*0.5*ARRIN(4, 1)
. . .

What is going on here? It appears that local real variable, RVAR, is being assigned a value that is a function of a two-dimensional array--even though the array that was passed into the sub-routine is actually a one-dimensional array! In fact, it is actually the address of the A array that is passed in to SUBR1 as the parameter ARRIN. This behaviour is one aspect of FORTRAN that makes it different from other computer languages. Many other computer languages pass variables into a sub-routine by value; that is, they make a copy of the variable being passed into the sub-routine and changes made to that variable within the sub-routine are not transmitted into the main program when program flow returns to the main program. Those other computer languages (e.g., C++) must specifically pass the address of the variable into the sub-routine. In addition, a C++ sub-routine must be declared to properly receive a variable address when called--not just a value copy. As seen in the sample FORTRAN sub-routine call above, SUBR1 accepts the address of the A array, and re-dimensions it as a 2-D array of dimension (ID1MAX, ID2MAX)--with the same number of elements as the original 1-D array. It can now be accessed via 2-D notation (e.g. - ARRIN(2,2).)

Now to translate this behaviour into a C++ code block. Let's say the corresponding C++ code has declared its 1-D array, arrIn, in the main program to be the same size and contain identical entries as the FORTRAN program, and in the sub-routine the local real variable is rvar. Also keep in mind that in FORTRAN array indexing begins at 1 whereas C++ array indexing begins at 0.

A FORTRAN statement may access an array as follows:

RVAR = ARRIN(d1, d2)

To access the same element in corresponding C++ code, using a 0-index based array, the statement would translate as follows:

rvar = arrIn[(d1 - 1) + ID1MAX*(d2 - 1)];

This correspondence can be confirmed by using f2c (a FORTRAN-to-C translation tool) to translate the origin FORTRAN program and running some tests:
(i) f2c a FORTRAN program that contains a sub-routine that treats a 1-D array as a 2-D array,
(ii) add a loop in the C main program to fill a test array with elements that are the array's own index numbers, and
(iii) add a code block in the sub-routine that cycles through the array elements:

// In the main program
const int N = 60;
int Main_Array[N];

for (int i = 0; i < N; ++i) Main_Array[i] = (i + 1);

If the original FORTRAN program calls sub-routine SUBR1 with the parameters SUBR1(6, 10, A), A would be re-dimensioned as DIMENSION ARRIN(6, 10).

Now to see how ARRIN is accessed inside sub-routine SUBR1--in the f2c'ed program--we add a small code block that outputs the contents of the input array:

// In the sub-routine
int d1, d2, dumInt;

d1 = 1; //Hold first index constant while cycling through second index
for (int kkk = 1; kkk <= ID2MAX; ++kkk){
    d2 = kkk;
    dumInt = arrIn[d1 + (d2 * ID1MAX)];
    cout << "arrIn(1, kkk) = " << dumInt << "\n";
} // End for kkk

The results that were output indicate the following:

arrIn (1, 1) = 1
arrIn(1, 2) = 7
arrIn (1, 3) = 13
arrIn (1, 4) = 19
arrIn (1, 5) = 25
arrIn (1, 6) = 31
arrIn (1, 7) = 37
arrIn (1, 8) = 43
arrIn (1, 9) = 49
arrIn (1, 10) = 55

Note that incrementing the second parameter causes the underlying array index to increment by ID1MAX elements each time through the loop.

Alternately, cycling through the first parameter:

d2 = 1; //Hold second index constant while cycling through first index
for (int kkk = 1; kkk <= ID1MAX; ++kkk){
    d1 = kkk;
    dumInt = arrIn[d1 + (d2* ID1MAX)];
    cout << "arrIn(kkk, 1) = " << dumInt << "\n";
} // End for kkk

The results that were output indicate the following:

arrIn (1, 1) = 1
arrIn (2, 1) = 2
arrIn (3, 1) = 3
arrIn (4, 1) = 4
arrIn (5, 1) = 5
arrIn (6, 1) = 6

Incrementing the first parameter causes the underlying array index to increment by a single element only. Remember, these results are from an f2c'ed program that imitates FORTRAN's default 1-based array indexing method. To convert the code to C++, with a 0-based array indexing system, it must be altered slightly.

By working with an f2c'ed version of the original FORTRAN program, we have confirmed its expected behaviour. In turn, we have confirmed the C++ code stated above.

Let's now continue on to examine the case where a 1-D array is treated like a 3-D array.

Treating a 1-D Array as a 3-D Array

Inside sub-routine SUBR2, the first few statements might be something like the following:

. . .
DIMENSION ARRIN(ID1MAX, ID2MAX, ID3MAX)
. . .
REAL RVAR
. . .
RVAR = ARRIN(3, 2, 2)*0.5*ARRIN(4, 1, 1)
. . .

This time, the 1-D array is re-dimensioned for treatment as an array of dimension (ID1MAX, ID2MAX, ID3MAX). So now it can be accessed via 3-D notation (e.g. - ARRIN(3,2,2).)

Going through a process similar to that used for the 1-D to 2-D re-dimensioning, we find that the accessing technique is similar.

If the FORTRAN code accesses a 1-D array acting like a 3-D array,

RVAR = ARRIN(d1, d2, d3),

to access the same element in corresponding 0-index based C++ code, the statement would be as follows:

rvar = arrIn[(d1 - 1) + ID1MAX*(d2 - 1) + ID1MAX*ID2MAX*(d3 - 1)];

Again, this correspondence can be confirmed by using an f2c'ed translation of the original FORTRAN program and creating some test cases.

If the original FORTRAN program calls sub-routine SUBR2 with the parameters SUBR2(4, 5, 3, A), A is re-dimensioned as DIMENSION ARRIN(4, 5, 3).

Now to see how ARRIN is accessed inside sub-routine SUBR2--in the f2c'ed program--we can add small code blocks that output the contents of the input array:

// In the sub-routine
int d1, d2, d3, dumInt;

d2 = 1; //Hold second and third indices constant while cycling through first index
d3 = 1;

for (int kkk = 1; kkk <= ID1MAX; ++kkk){
    d1 = kkk;
    dumInt = arrIn[d1 + (d2 + (ID2MAX * d3)) * ID1MAX];
    cout << "arrIn(kkk, 1, 1) = " << dumInt << "\n";
} // End for kkk

The results that were output indicate the following:

arrIn (1, 1, 1) = 1
arrIn (2, 1, 1) = 2
arrIn(3, 1, 1) = 3
arrIn(4, 1, 1) = 4

Incrementing the first parameter in FORTRAN causes the underlying one-dimensional array index to increment by a single element only.

Now let's cycle through the second index values.

d1 = 1; //Hold first and third indices constant while cycling through second index
d3 = 1;

for (int kkk = 1; kkk <= ID2MAX; ++kkk){
    d2 = kkk;
    dumInt = arrIn[d1 + (d2 + (ID2MAX * d3)) * ID1MAX];
    cout << "arrIn(1, kkk, 1) = " << dumInt << "\n";
} // End for kkk

The results that were output indicate the following:

arrIn (1, 1, 1) = 1
arrIn (1, 2, 1) = 5
arrIn (1, 3, 1) = 9
arrIn (1, 4, 1) = 13
arrIn (1, 5, 1) = 17

Incrementing the second index value causes the underlying array index to increment by ID1MAX elements each time through the loop.

Finally, let's cycle through the third index values.

d1 = 1; // Hold first and second indices constant while cycling through third index
d2 = 1;

for (int kkk = 1; kkk <= ID3MAX; ++kkk){
    d3 = kkk;
    dumInt = arrIn[d1 + (d2 + (ID2MAX * d3)) * ID1MAX];
    cout << "arrIn(1, 1, kkk) = " << dumInt << "\n";
} // End for kkk

The results that were output indicate the following:

arrIn (1, 1, 1) = 1
arrIn (1, 1, 2) = 21
arrIn (1, 1, 3) = 41

Incrementing the third parameter in FORTRAN causes the underlying one-dimensional array index to increment by ID1MAX*ID2MAX.

Again, by working with an f2c'ed version of the original FORTRAN program, we have confirmed the way FORTRAN accesses the re-dimensioned array.

Summary

If a FORTRAN program declares a 1-D array, ARRIN, in the main program, re-dimensions it as a 2-D array in a sub-routine (ID1MAX, ID2MAX), and accesses it via a statement like the following:

RVAR = ARRIN(d1, d2),

the corresponding C++ code is

rvar = arrIn[(d1 - 1) + ID1MAX*(d2 - 1)];

Alternately, if the 1-D array is re-dimensioned as a 3-D array in a sub-routine (ID1MAX, ID2MAX, ID3MAX), and accessed via a statement like the following:

RVAR = ARRIN(d1, d2, d3),

the corresponding C++ code is

rvar = arrIn[(d1 - 1) + ID1MAX*(d2 - 1) + ID1MAX*ID2MAX*(d3 - 1)];

Labels: , , , , ,

0 Comments:

Post a Comment

<< Home