OpenMP depend clause - rule on structure elements - fortran

I'm struggling to understand the rules in the 5.2 specifications regarding what is allowed in an OpenMP depend clause with respect to types in Fortran.
The basic code I'm testing is this clause (as a proxy for a larger example):
!$omp task private(jj, i) firstprivate(j, jj_end) shared(e12c) depend(out: e12c%e12f%e12t)
e12c%e12f%e12t is a type containing a type containing an array.
Page 62 of the spec says:
Unless otherwise specified, a variable that is part of another variable (as an array element or a
structure element) cannot be a variable list item, an extended list item or locator list item.
I know that array elements are allowed as members of a depend clause (and the code works with both gfortran12 and ifort if its only e12c%e12t without the extra type in between), and I think I'd assumed structure members were as well (and at least for non-nested structure members this works).
If I try this code with gfortran 12 then it fails to compile, however with intel 2023.0.0 it compiles ok. Is it valid OpenMP code or do I need to find another way (is there another way?) to express such dependencies?
Notably the C equivalent (depend(out: e12c.e12f.e12t[j:j+32])) compiles ok with gcc12, so is this just gfortran lagging behind again?
Adressing the comment, the gfortran compiler just says a syntax error:
gfortran -fopenmp test.f90
test.f90:26:82:
26 | !$omp task private(jj, i) firstprivate(j, jj_end) shared(e12c) depend(out: e12c%e12f%e12t)
| 1
Error: Syntax error in OpenMP variable list at (1)
For Ian's comment, to be explicit as to what everything is, here is the full Fortran file that fails with gcc:
module ax
implicit none
type array_container
real, dimension(500, 500) ::e12t
end type
type array_container2
type(array_container) :: e12f
end type
end module ax
Program test
use ax
type(array_container2) :: e12c
integer, dimension(500,500):: dum
integer :: i, ii, j, jj, jj_end
e12c%e12f%e12t(:,:) = 1.0
!$omp parallel default(shared) private(j, jj_end)
!$omp single
DO j = 1, 500, 32
jj_end = MIN(j_out_var + (32 - 1), 500)
!$omp task private(jj, i) firstprivate(j, jj_end) shared(e12c) depend(out: e12c%e12f%e12t)
Do jj = j, jj_end, 1
Do i = 1, 500, 1
e12c%e12f%e12t(i,j) = 1.0
End Do
End Do
!$omp end task
End Do
!$omp end single
!$omp end parallel
End Program

Related

OpenMP reduction on user defined Fortran type containing allocatable array

I want to do an OpenMP reduction on a user defined Fortran type. I know OpenMP
does not support Fortran types in reduction clauses but it is possible to define
own reductions. This is done in the following example. This also works and does what it is
expected to
module types
!!! your type this can contain scalars and arrays
type my_type
Real*8,allocatable,dimension( : ) ::x
end type
!!! makes it possible to use the plus symbol for the reduction staement
!!! replaces my_add by plus symbol
interface operator(+)
module procedure :: my_add
end interface
!$omp declare reduction (+ : my_type : omp_out = omp_out + omp_in) initializer (omp_priv = my_type ([0,0,0,0,0,0,0,0,0,0]))
contains
function my_add( a1 , a2 )
type( my_type ),intent( in ) :: a1, a2
type( my_type ) :: my_add
my_add%x = a1%x + a2%x
return
end function my_add
end module types
program main
use types
use omp_lib
type(my_type) :: my_var
! Initialize the reduction variable before entering the OpenMP region
Allocate( my_var%x( 1:10 ) )
my_var%x = 0d0
!$omp parallel reduction (+ : my_var) num_threads(4)
my_var%x = omp_get_thread_num() + 6
print*,omp_get_thread_num()
!$omp end parallel
print *, "sum of x is ", my_var%x
end program
My problem is now the allocatable array.
Because I hard coded the array initializer for the OpenMP reduction statement as initializer (omp_priv = my_type ([0,0,0,0,0,0,0,0,0,0]))
I have to put 10 zeros there since the array is allocated with a length of 10.
Is it possible to this with a variable name N (length of array)??
Inside the reduction initializer clause we have limited access to variables, making an array constructor of variable length difficult. However, we have available to us the Fortran version of the C++ approach.
We can use the variable omp_orig to refer to the "storage of the original variable to be reduced":
!$omp declare reduction (+ : my_type : omp_out = omp_out + omp_in) &
!$omp& initializer (omp_priv=omp_orig)
The assignment statement here successfully allocates the array component of each private copy.

Dimension check of automatic objects in explicit interfaces

I always assumed that giving values in automatic sized dummy arguments within subroutine definitions has some benefit in error detection, when the routine is defined inside a module that gives an explicit interface. But following example shows that there is not really a checking of array bounds:
module test_mod
Implicit none
contains
!> Testing routine that has an automatic array arr of length n. The integer n is also given.
!> It is included in a module and has an explicit interface.
subroutine print_array(n, arr)
Integer, intent(in) :: n !< Size of arr
Integer, Dimension(n), intent(in) :: arr !< A test array that should be of size n
Integer :: i
do i = 1, n
print *, 'number:', i, arr(i)
enddo
End Subroutine print_array
end module test_mod
program playground
use test_mod, only : print_array
! Call print_array with an array smaller than 3 elements
Call print_array(3, [8, 9])
end program playground
arr is created with the dimension of n in subroutine print_array, but it is not checked if the dimension of the given array is correct. In my example the output is:
number: 1 8
number: 2 9
number: 3 -858993460
So the 3rd value is taken outside of arr bounds and in my case -858993460 is printed. I understand that such behaviour can not generally be catched at compile time. But even using ifort with /check:bounds option doesn't give an error message at run time.
Therefore, I would like to ask if it is good practice to specify arr with Dimension(n) or is it even better to define it with Dimension(:). What is the advantage of giving the dimension in such way?

Is it possible to do OpenMP reduction over an element of a derived Fortran type?

I am trying to adapt a Fortran code (Gfortran) to make use of OpenMP. It is a particle based code where the index of arrays can correspond to particles or pairs. The code uses a derived type to store a number of matrices for each particle. It is very common to come across loops which require the use of a matrix stored in this derived type. This matrix may be accessed by multiple threads. The loop also requires a reduction over an element in this derived type. I currently have to write a temporary array in order to do this reduction and then I set the element of the derived type equal to this temporary reduction array. If not using OpenMP no temporary array is needed.
Question: Is it possible to do a reduction over an element of a derived type? I don't think I can do a reduction over the entire derived type as I need to access some of the elements in the derived type to do work, which means it needs to be SHARED. (From reading the specification I understand that when using REDUCTION a private copy of each list item is created.)
Complete minimal working example below. It could be more minimal but I feared that removing more components might over simplify the problem.
PROGRAM TEST_OPEN_MP
USE, INTRINSIC :: iso_fortran_env
USE omp_lib
IMPLICIT NONE
INTEGER, PARAMETER :: dp = REAL64
INTEGER, PARAMETER :: ndim=3
INTEGER, PARAMETER :: no_partic=100000
INTEGER, PARAMETER :: len_array=1000000
INTEGER :: k, i, ii, j, jj
INTEGER, DIMENSION(1:len_array) :: pair_i, pair_j
REAL(KIND=dp), DIMENSION(1:len_array) :: pair_i_r, pair_j_r
REAL(KIND=dp), DIMENSION(1:no_partic) :: V_0
REAL(KIND=dp), DIMENSION(1:ndim,1:no_partic) :: disp, foovec
REAL(KIND=dp), DIMENSION(1:ndim,1:len_array) :: dvx
REAL(KIND=dp), DIMENSION(1:2*ndim,1:len_array):: vec
REAL(KIND=dp), DIMENSION(1:ndim) :: disp_ij,temp_vec1,temp_vec2
REAL(KIND=dp), DIMENSION(1:ndim,1:ndim) :: temp_ten1,temp_ten2
REAL(KIND=dp), DIMENSION(1:no_partic,1:ndim,1:ndim):: reduc_ten1
REAL(KIND=dp) :: sum_check1,sum_check2,cstart,cend
TYPE :: matrix_holder !<-- The derived type
REAL(KIND=dp), DIMENSION(1:ndim,1:ndim) :: mat1 !<-- The first element
REAL(KIND=dp), DIMENSION(1:ndim,1:ndim) :: mat2 !<-- The second element, etc.
END TYPE matrix_holder
TYPE(matrix_holder), DIMENSION(1:no_partic) :: matrix
! Setting "random" values to the arrays
DO k = 1, no_partic
CALL random_number(matrix(k)%mat1(1:ndim,1:ndim))
CALL random_number(matrix(k)%mat2(1:ndim,1:ndim))
END DO
CALL random_number(pair_i_r)
CALL random_number(pair_j_r)
CALL random_number(disp)
CALL random_number(vec)
CALL random_number(dvx)
CALL random_number(V_0)
disp = disp*10.d0
vec = vec*100.d0
dvx = dvx*200.d0
V_0 = V_0*10d0
pair_i = FLOOR(no_partic*pair_i_r)+1
pair_j = FLOOR(no_partic*pair_j_r)+1
! Doing the work
cstart = omp_get_wtime()
!$OMP PARALLEL DO DEFAULT(SHARED) &
!$OMP& PRIVATE(i,j,k,disp_ij,temp_ten1,temp_ten2,temp_vec1,temp_vec2,ii,jj), &
!$OMP& REDUCTION(+:foovec,reduc_ten1), SCHEDULE(static)
DO k= 1, len_array
i = pair_i(k)
j = pair_j(k)
disp_ij(1:ndim) = disp(1:ndim,i)-disp(1:ndim,j)
temp_vec1 = MATMUL(matrix(i)%mat2(1:ndim,1:ndim),&
vec(1:ndim,k))
temp_vec2 = MATMUL(matrix(j)%mat2(1:ndim,1:ndim),&
vec(1:ndim,k))
DO jj=1,ndim
DO ii = 1,ndim
temp_ten1(ii,jj) = -disp_ij(ii) * vec(jj,k)
temp_ten2(ii,jj) = disp_ij(ii) * vec(ndim+jj,k)
END DO
END DO
reduc_ten1(i,1:ndim,1:ndim)=reduc_ten1(i,1:ndim,1:ndim)+temp_ten1*V_0(j) !<--The temporary reduction array
reduc_ten1(j,1:ndim,1:ndim)=reduc_ten1(j,1:ndim,1:ndim)+temp_ten2*V_0(i)
foovec(1:ndim,i) = foovec(1:ndim,i) - temp_vec1(1:ndim)*V_0(j) !<--A generic reduction vector
foovec(1:ndim,j) = foovec(1:ndim,j) + temp_vec1(1:ndim)*V_0(i)
END DO
!$OMP END PARALLEL DO
cend = omp_get_wtime()
! Checking the results
sum_check1 = 0.d0
sum_check2 = 0.d0
DO i = 1,no_partic
matrix(i)%mat2(1:ndim,1:ndim)=reduc_ten1(i,1:ndim,1:ndim) !<--Writing the reduction back to the derived type element
sum_check1 = sum_check1+SUM(foovec(1:ndim,i))
sum_check2 = sum_check2+SUM(matrix(i)%mat2(1:ndim,1:ndim))
END DO
WRITE(*,*) sum_check1, sum_check2, cend-cstart
END PROGRAM TEST_OPEN_MP
The only other alternative I can think of would be to remove all the derived types and replace these with large arrays similar to reduc_ten1 in the example.
Unfortunately, what you want is not possible. At least if I understood your (very complicated for me!) code correctly.
The problem is that you have an array of derived types each have an array. You cannot reference that.
Consider this toy example:
type t
real :: mat(3)
end type
integer, parameter :: n = 100, nk = 1000
type(t) :: parts(n)
integer :: i
real :: array(3,n,nk)
do k = 1, nk
array(:,:,nk) = k
end do
do i = 1, n
parts(i)%mat = 0
end do
!$omp parallel do reduction(+:parts%mat)
do k = 1, nk
do i = 1, n
parts(i)%mat = parts(i)%mat + array(:,i,nk)
end do
end do
!$omp end parallel do
end
Intel Fortran gives a more concrete error:
reduction6.f90(23): error #6159: A component cannot be an array if the encompassing structure is an array. [MAT]
!$omp parallel do reduction(+:parts%mat)
--------------------------------------^
reduction6.f90(23): error #7656: Subobjects are not allowed in this OpenMP* clause; a named variable must be specified. [PARTS]
!$omp parallel do reduction(+:parts%mat)
--------------------------------^
Remember that it is not even allowed to do this, completely without OpenMP:
parts%mat = 0
Intel:
reduction6.f90(21): error #6159: A component cannot be an array if the encompassing structure is an array. [MAT]
gfortran:
Error: Two or more part references with nonzero rank must not be specified at (1)
You must do this:
do i = 1, n
parts(i)%mat = 0
end do
The reason for the error reported by Intel above is very similar.
Actually no derived type components are allowed in the reduction clause, only variable names can be used. That is the reason for the syntax error reported by gfortran. It does not expect any % there. Intel again gives a clearer error message.
But one could make a workaround around that, like passing it to a subroutine and do the reduction there.

same print statement yielding different results

A sample code is below to indicate the problem I am having. This is as minimal/complete I can make it in order to reproduce the problem I'm having. My problem is that the same print statement, three lines apart, is yielding two different results. The first print statement yields the correct result while the second one yields the wrong result. Am I re-writing the variable definitions within these 3 lines? How come when I move the print statment down 3 lines I get a different, incorrect result? The print statements are shown below. Since the variables are somehow being changed, this causes a lot of problems in the expected results later on in my code. However I just want to understand whats going on with these print statements. Thanks.
Module Sample
integer :: n,m
contains
subroutine Sub1(localf0)
implicit none
complex, dimension(-10:10,-10:10), intent(inout) :: localf0
complex, dimension(-10:10,-10:10) :: localf1
complex, dimension(-10:10,-10:10) :: Deriv0, Deriv1
Deriv0 = Deriv(localf0)
do while (norm2(abs(Deriv0)) > 0.000001)
print*, abs(localf1(1,1))-abs(localf0(1,1))
localf1 = localf0 +1.0*Deriv(localf0)
Deriv0 = Deriv(localf0)
Deriv1 = Deriv(localf1)
print*, abs(localf1(1,1))-abs(localf0(1,1))
end do
end subroutine Sub1
function Deriv(func)
implicit none
real :: x, y
complex, dimension(-10:10,-10:10), intent(in) :: func
complex, dimension(-10:10,-10:10) :: localf,Deriv
localf = func
do m=-9,9
do n=-9,9
x = real(n)*0.1
y = real(m)*0.1
localf(n,m)= cmplx(z1(x,y),z2(x,y))
end do
end do
do m=-9,9
do n=-9,9
Deriv(n,m) = (localf(n+1,m)+localf(n-1,m)+localf(n,m+1)+localf(n,m-1)-4*localf(n,m))/0.01
end do
end do
end function Deriv
End Module Sample
program MyProgram
use Sample
implicit none
complex, dimension(-10:10,-10:10) :: localf0,localf1
do m=-10,10
do n=-10,10
localf0(n,m) = cmplx(z1(real(n)*0.1,real(m)*0.1) , z2(real(n)*0.1,real(m)*0.1))
localf1 = localf0 + 1.0*Deriv(localf0)
end do
end do
call Sub1(localf0)
end program
Now that I've run it there is a insidious thing here that I frankly think the compiler should throw an error on. (Why I made this an answer rather than comment)
You have made n,m module variables, effectively making them global variables and you use the same n,m in the main program loop and in the loops inside the function.
Your function deriv changes the values of n and m. (They return as the end value of its last loop) so that the main program loop does not complete.
You can see this if you put in the main loop:
print*,m,n
localf1 = localf0 + 1.0*deriv(localf0)
print*,m,n
you get :
-10 -10
10 10
and the loop exits.. ( the 10,10 is one more than the the loop limit of 9,9 inside the function.. )
What you should do is remove the integer n,m from the module. Declare n,m integers where needed (That is declare individually in deriv and in MyProgram )
You may note also there is no reason for localf1 to be assigned inside the main program loop in the first place (or set at all for that matter since you never use it ). Removing that assignment would fix the issue as well, but I'd still recommend changing the n,m declarations to local.
Note with that fixed you still have the problem that nothing changes with each iteration of the while loop so it loops indefinitely printing the same values. I guess you want to update localf0=localf1 at the end of the loop.

Error: Expected PARAMETER symbol in complex constant at (1)

I am writing a small piece of Fortran 90 code to compute some quantities using complex variables.
I have a subroutine with the following instructions:
complex, dimension(3) :: v
integer :: i
real:: tmp
do i = 1,3
tmp = vg(i)
v(i) = (tmp, 0.0)
enddo
v is a complex array of length 3. vg is an array of length 3 too whose elements are real.
When I compile the above code with gfortran 4.7.3 I get the following error:
v(i) = (tmp,0.0)
Error: Expected PARAMETER symbol in complex constant at (1)
I do not understand what's the problem.
You have to use
v(i) = cmplx(tmp, 0.0)
Your syntax (re, im) works only for constant expressions, i.e. when re and im are real or integer constants.
This means you cannot make a complex constant from a real variable and a real constant. You have to use the intrinsic function cmplx which converts real variables to complex ones, or builds complex variables from pairs of real variables (or integer).