Passing subarray of 2-dimensional array in FORTRAN-77 - arrays

I have 2-dimensional array
real triangle(0:2, 0:1)
where "triangle" is an array of vectors (1-dim arrays)
also i have subroutine
subroutine vecSub(lhs, rhs, result)
real lhs(0:1), rhs(0:1), result(0:1)
result(0) = lhs(0) - rhs(0)
result(1) = lhs(1) - rhs(1)
return
end
is there any way to pass one of the vectors from "triangle" variable to this subroutine? Fortran-90 can do this: triangle(0, :) which gives first array of triangle, but i'm allowed to use only FORTRAN-77, so this won't do, any suggestions?

#Javier Martin wrote "not with the current layout of your array", but missed the opportunity to suggest an alternative.
If instead you declared the variable as follows:
real triangle(0:1, 0:2)
reversing the order of the bounds, you could then pass triangle(0,0), triangle(0,1) or triangle(0,2) to the subroutine and get exactly the behavior you want, due to a Fortran feature called "sequence association". When you pass a single array element to a dummy argument that is an array, you are implicitly passing that and following elements, in array element order. This is about the only allowed violation of the normal Fortran shape-matching rules, and was part of FORTRAN 77.

No, not with the current layout of your array, because of two reasons:
Fortran uses an array element order in which the leftmost dimension is contiguous. That is, in an array of size (n,m,l) the distance between elements (the stride) is (1,n,m), measured in units of array elements (that is, not bytes).
F77 does not include assumed-shape arrays a(:) which are generally implemented by passing a small descriptor structure that communicates details like the stride or the number of elements. Instead, you can only use assumed-length arrays a(*) which are normally a pointer to the first element, kind of like C arrays. You have to pass the length as a separate argument, and array elements have to be contiguous
This is the reason why you can "pass a subarray" to an F77 subroutine, as long as that subarray is e.g. a matrix column: elements therein are contiguous.
A possible solution (one that many current Fortran compilers implement) is that when you try to pass a non-contiguous subarray to a function that is not known to accept them, they make a copy of the array, and even write it back in memory if required. This would be equivalent to:
! Actual array
integer m(3,5)
integer dummy(5)
dummy = m(2,:)
call myF77sub(dummy, 5)
m(2,:) = dummy
However, as others are saying, you should try not to call F77 functions directly, but either adapt them to or at least wrap them in more recent Fortran interfaces. Then you can have code like the above in the wrapper, and call that wrapper "normally" from modern Fortran routines. Then you may eventually get around to rewriting the actual implementation in modern Fortran without affecting client code.

Related

How to concatenate two arrays in Fortran 90

I have an original array called pres_lev3d, whose size is defined by pres_lev3d(im*jm, levsi), where im*jm is 72960 and levsi is 64. This corresponds to global atmospheric data, thus the size. The array is allocatable: real (kind=kind_io8), allocatable :: pres_lev3d(:, :). I have a second array, press_1d, whose size is also defined in a similar fashion pres_1d(im*jm, levsi), but in this array levsi is 1.
I need to concatenate both arrays (technically a 2d and 1d array) to an array of the shape (/72960, 65/). In MATLAB this seems like a very simple process, however, I can't seem to find an easy way to go around it in Fortran 90.
I have tried to create a third array
pres_lev=(/pres_lev3d, pres_1d/)
and also tried to use merge, but none of these approaches seem to work out.
I am fairly new to Fortran.
If I've followed your explanation correctly this would probably work
real(kind_io8), dimension(72960,65) :: out_array
...
out_array(:,1:64) = pres_lev3d
out_array(:,65) = pres_1d
If that's not easy enough, or if I've misunderstood your question, explain further. To allocate out_array to conform to your input arrays, try something like
real(kind_io8), dimension(:,:), allocatable :: out_array
...
allocate(out_array(size(pres_lev3d,1),size(pres_lev3d,2)+1))
...
out_array(:,1:64) = pres_lev3d
out_array(:,65) = pres_1d

Resizing of Multi-Dimensional Arrays when Passed

All references to arrays in this post are multi-dimensional.
I came to know that when an array is passed a subroutine, it can be declared with different dimensions/sizes as the caller.
As a specific example, BMAIN is declared with DIMENSION(6,5) in the main program. BMAIN is passed to a subroutine as BSUB, which is declared as:
INTEGER, INTENT(IN) :: BSUB(3,2,0:4)
Questions:
Are the entries in BSUB simply filled one by one from SBMAIN until it is filled (in the order explained here Linear Indexing of Multi-Dimension Arrays in Fortran)?
Are there any dimension matching performed by the compiler? For instance, had BSUB been declared as BSUB(0:4,3,2), would it still hold the correct entries in the correct places?
From the Fortran 2008 standard (12.5.2.11.4):
An actual argument that represents an element sequence and
corresponds to a dummy argument that is an array is sequence
associated with the dummy argument if the dummy argument is an
explicit-shape or assumed-size array. The rank and shape of the actual
argument need not agree with the rank and shape of the dummy argument,
but the number of elements in the dummy argument shall not exceed the
number of elements in the element sequence of the actual argument. If
the dummy argument is assumed-size, the number of elements in the
dummy argument is exactly the number of elements in the element
sequence.
It is completely legal to use a different shape and rank of the array in the subroutine, just do not reference more elements in the subroutine than the array actually has. Array temporary may be necessary, but often it is not.
Multi-dimensional arrays in Fortran are stored in column-major order. In memory, they the elements are linear order and the memory offset is calculated from the multi-dimensional indices. The equation is given at http://en.wikipedia.org/wiki/Row-major_order. The Fortran compiler will use that equation and calculate an position in memory based on the dimensions that you provide. To figure out the correspondence between elements when you declare a multi-dimensional array with different dimensions, apply the equation twice using the different dimensions. The compiler doesn't move values in memory. It calculates position in memory from the index values, based on the dimensions.
There are cases where the code generated by the Fortran compiler will copy values, creating a temporary array. e.g., if the actual array argument in the call involves a stride, the compiler might need to create a contiguous temporary array to be compatible with the argument of the subroutine.

How can a scalar be passed to a vector (1D array) to a Fortran subroutine?

There is this program:
INTEGER i,k
REAL*8 mp(15,48)
REAL*8 sp(15)
k=0
do i=1,12
k=k+1
call Equaltensors(sp,mp(1,k),15)
enddo
end
c=====================
subroutine Equaltensors(tensA,tensB,n)
REAL*8 tensA(n),tensB(n)
INTEGER i
do i=1,n
tensB(i)=tensA(i)
enddo
return
end
So basically the value of mp(1,1) and so on is passed to the subroutine as a vector tensB(15) with n=15. What I don't understand is how a real number can be stored in a one-dimension array in a subroutine.
The title of your question is a bit misleading. Fortran doesn't allow you to pass a scalar to an array. But what it DOES allow is passing a single element of an array to a routine's array dummy argument - this is called "sequence association" in Fortran. As IanH and others have said, the following elements are automatically associated with the elements of the dummy array, up to the last element in the called routine's actual array.
There are some restrictions on this feature, though. If the element is of a POINTER array,you can't do this.
Going back to your title, I have seen many programs pass, say, the constant 3 to a routine where the dummy is an array. The routine only uses the first element, but this is not legal and newer compilers may detect the error and complain. One workaround for this is to turn the argument into an array by using an array constructor - for example, CALL FOO ([3]), but this works only if the value is to be read, not written.
I've written some blog posts on this general issue - see Doctor Fortran in “I’ve Come Here For An Argument” and Doctor Fortran in “I’ve Come Here For An Argument, Side 2”
EDIT: corrected per the comment by IanH, who points out that the behavior is guaranteed without making assumptions about the argument passing convention.
This approach started in early FORTRAN, by assuming that the argument is being passed as an address, typically called "call by reference". The address of scaler mp(1,k) is the address of the first element of this column k. Since Fortran stores arrays in column major format (http://en.wikipedia.org/wiki/Row-major_order#Column-major_order), the 15 values of the kth column will be sequential in memory. So if the called subroutine interprets this address as that of a 1-D array tensB of length 15, it will access the elements of the kth column.
In modern Fortran one could write the argument in a clearer manner by selecting a column with an array slice: mp (:,k).

Allocating arrays of the same size

I'd like to allocate an array B to be of the same shape and have the same lower and upper bounds as another array A. For example, I could use
allocate(B(lbound(A,1):ubound(A,1), lbound(A,2):ubound(A,2), lbound(A,3):ubound(A,3)))
But not only is this inelegant, it also gets very annoying for arrays of (even) higher dimensions.
I was hoping for something more like
allocate(B(shape(A)))
which doesn't work, and even if this did work, each dimension would start at 1, which is not what I want.
Does anyone know how I can easily allocate an array to have the same size and bounds as another array easily for arbitrary array dimensions?
As of Fortran 2008, there is now the MOLD optional argument:
ALLOCATE(B, MOLD=A)
The MOLD= specifier works almost in the same way as SOURCE=. If you specify MOLD= and source_expr is a variable, its value need not be defined. In addition, MOLD= does not copy the value of source_expr to the variable to be allocated.
Source: IBM Fortran Ref
You can either define it in a preprocessor directive, but that will be with a fixed dimensionality:
#define DIMS3D(my_array) lbound(my_array,1):ubound(my_array,1),lbound(my_array,2):ubound(my_array,2),lbound(my_array,3):ubound(my_array,3)
allocate(B(DIMS3D(A)))
don't forget to compile with e.g. the -cpp option (gfortran)
If using Fortran 2003 or above, you can use the source argument:
allocate(B, source=A)
but this will also copy the elements of A to B.
If you are doing this a lot and think it too ugly, you could write your own subroutine to take care of it, copy_dims (template, new_array), encapsulating the source code line you show. You could even set up a generic interface so that it could handle arrays of several ranks -- see how to write wrapper for 'allocate' for an example of that concept.

Passing c arrays into fortran as a variable sized matrix

So, i've been commissioned to translate some fortran subroutines into C. These subroutines are being called as part of the control flow of a large porgram based primarily in C.
I am translating the functions one at a time, starting with the functions that are found at the top of call stacks.
The problem I am facing is the hand-off of array data from C to fortran.
Suppose we have declared an array in c as
int* someCArray = (int*)malloc( 50 * 4 * sizeof(int) );
Now, this array needs to be passed down into a fortran subroutine to be filled with data
someFortranFunc( someCArray, someOtherParams );
when the array arrives in fortran land, it is declared as a variable sized matrix as such:
subroutine somefortranfunc(somecarray,someotherparams)
integer somefarray(50,*)
The problem is that fortran doesn't seem to size the array correctly, becuase the program seg-faults. When I debug the program, I find that indexing to
somefarray(1,2)
reports that this is an invalid index. Any references to any items in the first column work fine, but there is only one available column in the array when it arrives in fortran.
I can't really change the fact that this is a variable sized array in fortran. Can anyone explain what is happening here, and is there a way that I can mitigate the problem from the C side of things?
[edit]
By the way, the fortran subroutine is being called from the replaced fortran code as
integer somedatastorage(plentybignumber)
integer someindex
...
call somefarray(somedatastorage(someindex))
where the data storage is a large 1d array. There isn't a problem with overrunning the size of the data storage. Somehow, though, the difference between passing the C array and the fortran (sub)array is causing a difference in the fortran subroutine.
Thanks!
Have you considered the Fortran ISO C Binding? I've had very good results with it to interface Fortran and C in both directions. My preference is to avoid rewriting existing, tested code. There are a few types that can't be transferred with the current version of the ISO C Binding, so a translation might be necessary.
What it shouldn't be that others suggested:
1. Size of int vs. size of Integer. Since the first column has the right values.
2. Row vs. column ordering. Would just get values in wrong order not segmentation faulted.
3. Reference vs value passing. Since the first column has the right values. Unless the compiler is doing something evil behind your back.
Are you sure you don't do this in some secret way?:
someCArray++
print out the value of the someCArray pointer right after you make it and right before you pass it. You also should print it out using the debugger in the fortran code just to verify that the compiler is not generating some temporary copies to help you.

Resources