This question already has an answer here:
Using a vector to index a multidimensional Fortran array
(1 answer)
Closed 4 years ago.
Is there a way I can access the nth element of an array a, where n is a 1D array and size(n) is the rank of a.
Edit 2015-08-22 15:21
I am thinking of something similar to
program Example1D
integer :: a(6), b(1)
a = reshape( (/ (i , i = 1, size(a) ) /) , shape(a) )
b = (/ 5 /)
write(*,*) a(b)
end program Example1D
So I can call like this
program Want2D
integer :: a(6,5), b(2)
a = reshape( (/ (i , i = 1, size(a) ) /) , shape(a) )
b = (/ 5 , 3 /)
write(*,*) a(b)
end program Want2D
You are attempting to use a vector-subscript (e.g. Fortran 2008, cl. 6.5.3.3.2). This allows you to use a vector (1D array) to select random elements from an array dimension. This, however, cannot be used exactly as you intend it to select an element from multiple dimensions.
From 6.5.3.3 (emphasis mine):
In an array-section having a section-subscript-list, each subscript-triplet and vector-subscript in the section sub-
script list indicates a sequence of subscripts, which may be empty. Each subscript in such a sequence shall be
within the bounds for its dimension unless the sequence is empty. The array section is the set of elements from
the array determined by all possible subscript lists obtainable from the single subscripts or sequences of subscripts
specified by each section subscript.
If you goal in your example code is to select the element a(5,3) with your vector b = [5, 3], then you could change your write from
write (*,*) a(b) ! doesn't work
to:
write (*,*) a(b(1),b(2)) ! does work
You can do more complicated array-sections with b of higher ranks as long as you use a 1D section for each dimension of a. For example if a is a 5x5 array, you could get the corners with of a as a 2x2 array with:
integer :: b(2,2) ! 2 dimensional
b(1,:) = [1,5]
b(2,:) = [1,5]
write (*,*) a(b(1,:),b(2,:)) ! prints a(1,1), a(5,1), a(1,5), a(5,5)
In the comments below you requested that this be abstracted to an n-dimensional array a. Below is a function I consider ugly due to its need to use c interop in a way I consider a hack. You'll also need a newer compiler to even use the code, as it depends on assumed-rank arrays. Here is a module containing a subroutine that takes a 2-dimensional b containing array indices to print and an n-dimensional a to get the values from.
module future
implicit none
contains
subroutine print_array_vector(a, b)
use, intrinsic :: iso_c_binding, only: c_loc, c_f_pointer
implicit none
integer, dimension(..), target :: a
integer, dimension(:,:) :: b
integer :: a_rank, b_len1
integer, dimension(:,:,:), pointer :: a3
integer, dimension(:,:), pointer :: a2
integer, dimension(:), pointer :: a1
a_rank = rank(a)
if (a_rank /= size(b,1)) then
print *, "Rank mismatch between array and vector"
return
end if
if (a_rank == 3) then
call c_f_pointer(c_loc(a), a3, shape=[size(a,1), size(a,2), size(a,3)])
print *, a3(b(1,:),b(2,:),b(3,:))
else if (a_rank == 2) then
call c_f_pointer(c_loc(a), a2, shape=[size(a,1), size(a,2)])
print *, a2(b(1,:),b(2,:))
else if (a_rank == 1) then
call c_f_pointer(c_loc(a), a1, shape=[size(a,1)])
print *, a1(b(1,:))
else
print *, "Unsupported rank"
return
end if
end subroutine print_array_vector
end module future
This takes in assumed-rank a, which is not directly usable in Fortran except to pass as an actual argument to a C interface. However, we can use other parts of c-interop to get the C pointer to a, then turn it into a Fortran pointer of the appropriate shape. Now we have a in a usable form, but we have to do this within if/else blocks properly reference the different cases. I've only implemented up to 3-dimensional a, the rest is left as an exercise to the reader.
To use this function, here is an example:
program test
use future
implicit none
integer :: a3(5,5,5), a2(5,5), a1(5)
integer :: b3(3,2), b2(2,2), b1(1,2)
integer :: i
a3 = reshape([(i,i=1,125)],shape(a3))
a2 = reshape([(i,i=1,25)],shape(a2))
a1 = [(i,i=1,5)]
b3 = reshape([1,1,1,5,5,5],shape(b3))
b2 = reshape([1,1,5,5],shape(b2))
b1 = reshape([1,5],shape(b1))
call print_array_vector(a1,b1)
call print_array_vector(a2,b2)
call print_array_vector(a3,b3)
end program test
This constructs a 3-dim a, a 2-dim a, and a 1-dim a, and a few 2-dim b's with the locations of the corners of the arrays and then we call the function to print the locations of the vector from the array.
% ./arraysection
1 5
1 5 21 25
1 5 21 25 101 105 121 125
I compiled and tested this with gfortran 5.2 and I have no idea the current state of support for assumed-rank arrays in other version of gfortran or in other Fortran compilers.
Related
I want to do the tensor product of two arrays A and B, of size L x M, and reshape the result AB so that its size is L^2 x M^2.
To me, the easiest way to do it is with this code
abj=0
do aj=1,M
do bj=1,M
abj=abj+1
abi=0
do ai=1,L
do bi=1,L
abi=abi+1
AB(abi,abj)=A(ai,aj)*B(bi,bj)
end do
end do
end do
end do
Is there a more efficient way to accomplish the same task?
EDIT
Two clarifications
Using BLAS/LAPACK is totally fine
Typically, L and M are of the order of hundreds
I'd be inclined to first create an L*L*M*M array, and then to reshape that, something like
integer, parameter :: l = 10
integer, parameter :: m = 20
integer :: a(l,m)
integer :: b(l,m)
integer :: ab(l*l,m*m)
integer :: intermediate(l,l,m,m)
integer :: i,j
do i=1,m
do j=1,l
intermediate(:,j,:,i) = a*b(j,i)
enddo
enddo
ab = reshape(intermediate, [l*l, m*m])
This should run a little faster than the naive quadruple loop, as the matrix*scalar multiplication can be optimised (or indeed performed using BLAS/LAPACK if desired).
The representation in memory of ab and intermediate is identical, so you might want to use associate to avoid the reshape and copy, e.g.
integer, parameter :: l = 10
integer, parameter :: m = 20
integer :: a(l,m)
integer :: b(l,m)
integer :: ab(l*l,m*m)
integer :: i,j,x,y
do i=1,m
x = m*(i-1)
do j=1,l
y = l*(j-1)
associate(temp => ab(y+1:y+l, x+1:x+m))
temp = a*b(j,i)
end associate
enddo
enddo
This question already has answers here:
Allocate dynamic array with interdependent dimensions
(2 answers)
Multidimensional array with different lengths
(2 answers)
Closed 1 year ago.
Suppose I have an array A[a,b]. The dimension of a is 2. When a=1, the dimension of b is 3; when a=2, the dimension of b is 6.
For example, one array A looks like
[[1,2,3]
[4,5,6,7,8,9]]
it is combined from 1*3 and 1*6. In total 9 entries, not from input.
Is there any method to define such an array in Fortran? or I need other type of data structure?
You need to provide a better description of what you want to do. With allocatable entities you can trivially achieve what you have described.
program foo
integer a, b
real, allocatable :: x(:,:)
read(*,*) a
if (a == 1 .or. a == 2) then
b = 3 * a
allocate(x(a,b))
else
stop 'Is this right?'
end if
x = 1
print *, shape(x)
end program foo
I'm trying to move below Intel Fortran line to gfortran, but I get following error:
DOUBLE PRECISION, DIMENSION(0:0,0:0) :: value = (/ -999D99 /)
Incompatible ranks 2 and 1 in assignment at (1)
If I understand it correctly, we are creating a 2-dim array with 1 element. I came with following fix. Is this standard conforming?
DOUBLE PRECISION, DIMENSION(0:0,0:0) :: value = reshape ((/-999D99/), shape(value))
It is not allowed to make an assignment (even in initialization) between arrays of different ranks. Therefore the line
...DIMENSION(0:0,0:0) :: value = (/ -999D99 /)
is illegal.
Reshaping the right hand side to an array of rank 2
...DIMENSION(0:0,0:0) :: value = reshape ((/-999D99/), shape(value))
is a standard conforming solution, but it is easier to assign a scalar:
...DIMENSION(0:0,0:0) :: value = -999D99
Of course, this will work only if you have just 1 value. It will be assigned to all elements of the array on the left hand side.
i have allocated a lot of 2D arrays in my code, and I want each one array to read from a file named as array's name. The problem is that each array has different size, so I am looking for the most efficient way. The code is like this:
Module Test
USE ...
implicit NONE
private
public:: initializeTest, readFile
real(kind=8),dimension(:,:),allocatable,target:: ar1,ar2,ar3,ar4,ar5,...,ar10
real(kind=8),dimension(:,:),pointer:: pAr
CONTAINS
!
subroutine initializeTest
integer:: k1,k2,k3,k4,k5
integer:: ind1,ind2
allocate(ar1(k1,k1),ar2(k1,k2),ar3(k2,k4),ar4(k5,k5),...) !variable sizes
! here needs automatization - since its repeated
pAr => ar1
ind1 = size(pAr,1)
ind2 = size(pAr,2)
call readFile(par,ind1,ind2)
pAr => ar2
ind1 = size(pAr,1)
ind2 = size(pAr,2)
call readFile(par,ind1,ind2)
!....ar3, ... , ar9
pAr => ar10
ind1 = size(pAr,1)
ind2 = size(pAr,2)
call readFile(par,ind1,ind2)
end subroutine initializeTest
!
!
subroutine readFile(ar,row,col)
real(kind=8),dimension(row,col)
integer:: i,j,row,col
! it should open the file with same name as 'ar'
open(unit=111,file='ar.dat')
do i = 1, row
read(222,*) (ar(i,j),j=1,col)
enddo
end subroutine importFile
!
!
end module Test
If your arrays ar1, ar2, etc. had the same dimensions you could put them all in a 3-dimensional array. Since they have different dimensions, you can define a derived type, call it a "matrix", with an allocatable array component and then create an array of that derived type. Then you can read the i'th matrix from a file such as "input_1.txt" for i=1.
The program below, which works with g95 and gfortran, shows how the derived type can be declared and used.
module foo
implicit none
type, public :: matrix
real, allocatable :: xx(:,:)
end type matrix
end module foo
program xfoo
use foo, only: matrix
implicit none
integer, parameter :: nmat = 9
integer :: i
character (len=20) :: fname
type(matrix) :: y(nmat)
do i=1,nmat
allocate(y(i)%xx(i,i))
write (fname,"('input_',i0)") i
! in actual code, read data into y(i)%xx from file fname
y(i)%xx = 0.0
print*,"read from file ",trim(fname)
end do
end program xfoo
As far as I know, extracting the name of the variable from the variable at runtime isn't going to work.
If you need lots of automation for the arrays, consider using an array of a derived type, as one other answer suggests, in order to loop over them both for allocation and reading. Then you can enumerate the files, or store a label with the derived type.
Sticking to specific array names, an alternative is to just read/write the files with the required name as an argument to the routine:
Module Test
...
! here needs automatization - since its repeated
call readFile(ar1,'ar1')
call readFile(ar2,'ar2')
!....ar3, ... , ar9
call readFile(ar10,'ar10')
end subroutine initializeTest
subroutine readFile(ar,label)
real(kind=8) :: ar(:,:)
character(len=*) :: label
integer:: i,j,nrow,ncol,fd
nrow=size(ar,1)
ncol=size(ar,2)
open(newunit=fd,file=label)
do i = 1, row
read(fd,*) (ar(i,j),j=1,col)
enddo
end subroutine readFile
end module Test
Some unsollicited comments: I don't really get why (in this example) readFile is public, why the pointers are needed? Also, kind=8 shouldn't be used (Fortran 90 kind parameter).
I have the following minimal example:
program main
double precision, dimension(3) :: rankone
double precision, dimension(3,1) :: ranktwo
double precision, dimension(3) :: output
rankone = 1
ranktwo = 2
output = rankone + ranktwo
print *, 'output: ', output
end program main
Is there a function like squeeze in Matlab that removes the singleton dimension from the variable ranktwo. I'm looking for something like
output = rankone + squeeze(ranktwo)
If not, is there any workaround for this setting?
Are there any differences between the various versions of Fortran regarding this problem?
Try this
output = rankone + reshape(ranktwo,[3])
then look at the documentation for reshape to see what is going on. You could also write
output = rankone + ranktwo(:,1)
which slices a 1D array out of ranktwo. In my experience reshape usually causes an array copy so there may be a memory-use-efficiency argument for preferring the second version.
No, there are no differences between Fortran versions regarding this problem, a 3x1 array is not the same shape as a 1D array of length 3.
Alternatively, in F2003 you may point to your rank one array with a rank two pointer, like this:
program main
double precision, dimension(3), target :: rankone
double precision, dimension(3,1) :: ranktwo
double precision, dimension(3,1) :: output
double precision, pointer :: pp(:,:)
rankone(:) = 1
ranktwo(:,:) = 2
pp(1:size(rankone),1:1) => rankone
output = pp + ranktwo
print *, 'output: ', output
end program main
The other direction (pointing to a rank two array by a rank one pointer) only works in Fortran 2008, if I remember correctly.