Creating a subroutine that accepts different kinds of reals - fortran

I want to implement a subroutine that can work with reals in single precision, double precision and extended precision. The only solution I can come up with is shown in the code below. This solution works but I have to duplicate the code 3 times. Can this code duplication be avoided?
module mymodule
....
! some code here
interface my_func
module procedure my_func_sp
module procedure my_func dp
module procedure my_func_ep
end interface
contains
subroutine my_func_sp(x,y)
real(kind=sp), dimension(:) :: x,y
... LONG IMPLEMENTATION HERE ...
end subroutine
subroutine my_func_dp(x,y)
real(kind=dp), dimension(:) :: x,y
... LONG IMPLEMENTATION HERE THAT IS EXACTLY THE SAME AS ABOVE ...
end subroutine
subroutine my_func_ep(x,y)
real(kind=ep), dimension(:) :: x,y
... LONG IMPLEMENTATION HERE THAT IS EXACTLY THE SAME AS THE TWO ABOVE ...
end subroutine
end module

Can this code duplication be avoided? Not really, this is the way Fortran works. You could:
Write the code once, for the highest-precision kind you care about, and have the other subroutines call that variant, casting the kinds of variables on the way in and out.
Another approach I have seen regularly is to write the computational statements in a file and to include that file in each of the subroutines. Just take care that the included statements are valid for all kinds of the type. Take care too that the same statements work across kinds. If, for example, your included lines include comparisons with a tolerance, as many numeric codes do, you may have to take special care that the tolerance is adjusted wrt the kind.

If your entire code will use single, double, or quadruple precision reals, you can define a parameter real_kind in a module and use that parameter to specify kinds throughout your code, including the declarations of real variables in your subroutine. This solution does not work if your code calls more than one of my_func_sp, my_func_dp, and my_func_ep in a single run.

Related

ISO_FORTRAN_ENV or -fdefault-real-8 to promote reals to double precision

I've always been using the -fdefault-real-8 option of gfortran to automatically promote every single REAL declared anywhere in the program to double precision, along with any constant, e.g. 1.23. If I ever wanted to switch back to single precision, I only had to remove that option and recompile, without changing a single character in the source code.
At a point I started using ISO_FORTRAN_ENV module, since it allows me to use constants like INPUT|OUTPUT|ERROR_UNIT, as well as IOSTAT_END and IOSTAT_EOR and others (which seemed to be a good and easy move in the direction of portability, am I wrong?). From then on, I've been seeing and ignoring the following warning
Warning: Use of the NUMERIC_STORAGE_SIZE named constant from intrinsic module ISO_FORTRAN_ENV at (1) is incompatible with option -fdefault-real-8
since such incompatibility seems to have no effect so far.
Now I'd like to get rid of this warning if it is possible and worth it.
If I correctly understood, to avoid this warning I should give up on -fdefault-real-8 option and change every REAL to REAL(real64) and/or to REAL(dp) (provided that, in the latter case, the statement USE, INTRINSIC :: ISO_FORTRAN_ENV, dp => real64 is put in that unit), which is not a difficult task for sed or vim.
Nevertheless, it seems to me that this change wouldn't be the same as using -fdefault-real-8 option, since all constants would stay single precision as long as I don't add d0 to them.
Assumed the -fdefault-real-8 option is removed and ISO_FORTRAN_ENV is used anywhere, is there any way to make any constant across the program behave as each had d0 suffix?
Whether or not this is possible, have I correctly extrapolated that I can put the following lines in a single module which is used by all others program units, each of which can then use dp as kind type parameter?
USE, INTRINSIC :: ISO_FORTRAN_ENV
INTEGER, PARAMETER :: dp = real64
I would prefer this way since I could switch to real32 or real128 or whatever by changing only that line.
If you just want to silence the warning and you do not care about the implications -fdefault-real-8 has on storage association and some Fortran standard requirements, just do not import NUMERIC_STORAGE_SIZE from the module. For example,
USE, INTRINSIC :: ISO_FORTRAN_ENV, only: INPUT_UNIT,OUTPUT_UNIT,ERROR_UNIT
Assumed the -fdefault-real-8 option is removed and ISO_FORTRAN_ENV is used anywhere, is there any way to make any constant across the program behave as each had d0 suffix?
No.
By the way, d0 is exactly the same as double precision, so that doesn't fixate much either, since the meaning of double precision is allowed to vary as much as real.
Whether or not this is possible, have I correctly extrapolated that I can put the following lines in a single module which is used by all others program units, each of which can then use dp as kind type parameter?
Yes. That is a common practice.

Free-dimensional array as input in subroutine

I am trying to write a subroutine that can take as input a one-dimensional array OR a two-dimensional array. How can I declare that the input of the subroutine can be either be a vector or a matrix?
If I do this:
SUBROUTINE TEST1(x)
REAL, INTENT(IN) :: x(:)
<do something>
END SUBROUTINE TEST1
I clearly cannot pass a matrix as an input in the subroutine. A non-elegant solution could be to pass the matrix in vectorized form and then re-arrange it in matrix form inside the subroutine (I would need a couple of extra inputs, of course). Is there a better way of doing this?
creating a generic interface seems a pretty clean way to do it. (per comment, but I though worth writing up )
module gen
interface test1
module procedure t1,t2
end interface
contains
subroutine t1(y)
real y(:)
write(*,*)'shape is',shape(y)
y=2*y
end subroutine
subroutine t2(y)
real y(:,:)
write(*,*)'shape is',shape(y)
y=2*y
end subroutine
end module
use gen
real m(4),n(3,3)
m=4
n=3
call test1(m)
call test1(n)
end
With Intel you can also use MAP/UNION, so you can pass the 1d as a one 1d, and also pass the 2D as it's MAP/UNION 1D version.
You may want to use RESHAPE, but usually it is not needed, depending on what you are doing.
Your comment of "clearly cannot pass in a matrix" seems counter to what I know. If it is 2D, and always 2D, then there is no issue whatsoever. You can pass any rank in.

Have time in a pure manner in Fortran?

I am looking for a pure way to have access to time information. I thought about intrinsic functions and subroutines of standards compiler (date_and_time,cpu_time, system_clock, ltime, ctime, ...), the format do not really matter to me. I also thought about MPI function, but it is the same as intrinsic functions, there are all impure functions.
Here is a minimal example:
elemental subroutine add(message, text)
! function add
IMPLICIT NONE
character(len=:),allocatable,intent(inout) :: message
character(len=*), intent(in) :: text
character(len=1), parameter :: nl=char(10)
character(10) :: time
! character(8) :: date
! time= 'hhmmss.sss'
call DATE_AND_TIME(time)
Message= Message//time////text//nl
end subroutine add
and I get a logical error :
Error: Subroutine call to intrinsic ‘date_and_time’ at (1) is not PURE
Thus, I am wandering if a pure way to have a time information exists, or if it is impossible to have it purely (maybe because it has to use cpu information which, for a reason unknown to me, could be thread-unsafe).
Maybe, a subquestion, could be is there a solution to force the compiler to consider date_and_time pure (or any other function of that kind)?
The answer about a pure manner to get time is no. A function that returns the current time or date, is impure because at different times it will yield different results—it refers to some global state.
There are certainly tricks to persuade the compiler that a subroutine is pure. One is to flat out lie in an interface block.
But there are consequences from lying to the compiler. It can do optimizations which are unsafe to do and the results will be undefined (most often correct anyway, but...).
module m
contains
elemental subroutine add(text)
IMPLICIT NONE
character(len=*), intent(in) :: text
character(len=1), parameter :: nl=char(10)
character(10) :: time
intrinsic date_and_time
interface
pure subroutine my_date_and_time(time)
character(10), intent(out) :: time
end subroutine
end interface
call MY_DATE_AND_TIME(time)
end subroutine add
end module
program test
use m
call add("test")
end program
subroutine my_date_and_time(time)
character(10), intent(out) :: time
call date_and_time(time)
end subroutine
Notice I had to delete your message because that was absolutely incompatible with elemental.

Array of unknown rank as subroutine argument

I am designing a module which works with the hdf5 Fortran library. This module contains subroutines to read and write arrays of different types and shapes to/from a file.
e.g. I wish to be able to call writeToHDF5(filepath, array) regardless of what the shape and type of array is. I realise that interfaces have to be used to achieve this with different types. I am however wondering if it is possible to have an assumed shape of the array.
e.g.
if an array was defined such as
integer(kind=4), dimension(*),intent(in) :: array
and a two dimensional array was passed this would work. Is there any way to do this without creating separate subroutines for each shape of the array?
As Vladimir F says, Fortran 2015 adds "assumed-rank" - this is useful Fortran-Fortran (it was requested by MPI for the Fortran bindings), but when you receive such an array, you can't do much with it directly without additional complications. Several compilers support this already, but few (if any?) support the newly added SELECT RANK construct that make this a bit more useful.
You can, however, use C_LOC and C_F_POINTER to "cast" the assumed-rank dummy to a pointer to an array of whatever rank you like, so that's a possibility.
The standard (even back to Fortran 90) does give you an out here. If you write:call writeToHDF5(filepath, array(1,1)) (assuming array is rank 2 here), the explicit interface of the called procedure can specify any rank for the dummy argument through the magic of "sequence association". There are some restrictions, though - in particular the array is not allowed to be assumed-shape or POINTER.
I know that this comes out late, but for any future readers - the answer is actually yes.
This is a simple example of a procedure to read a single data-set of integers with any given shape. The inputs needed were read from the HDF5 using a single routine which does not require any special specification and are the same for integer, real, string and so on.
I have tested it on a "0-D" array (so size of (/1/) ), 1-D, 2-D, 3-D and 4-D arrays.
In all cases, the data was retrieved properly.
(Note: I removed some checks regarding the Errorflag, as they are not critical for the example)
subroutine ReadSingleDataset_int(FileName,DataName,DataType,Data_dims,Errorflag,InputArray)
implicit none
character(len=*), intent(in) :: FileName,DataName
integer(HID_T), intent(in) :: DataType
integer(hsize_t), dimension(:), intent(in) :: Data_dims
logical, intent(out) :: ErrorFlag
integer, dimension(*), intent(inout) :: InputArray
integer :: hdferr
integer(HID_T) :: file_id,dset_id
ErrorFlag=.FALSE.
IF (.NOT.HDF5_initialized) THEN
CALL h5open_f(hdferr)
HDF5_initialized=.TRUE.
ENDIF
call h5fopen_f(trim(FileName),H5F_ACC_RDONLY_F, file_id, hdferr)
call h5dopen_f(file_id,trim(DataName),dset_id,hdferr)
call h5dread_f(dset_id,DataType,InputArray,Data_dims,hdferr)
call h5dclose_f(dset_id,hdferr)
call h5fclose_f(file_id,hdferr)
end subroutine ReadSingleDataset_int
My main interest in this now is - would it be possible to replace the type/class specific (integer/real/etc') with a generic one on the lines of:
class(*), dimension(*), intent(inout) :: InputArray
As I have several routines like that (for int, real, string) which only vary in that specification of the input type/class. If that limitation could be alleviated as well, that will be even more elegant.
(Just to point out the issue - the h5dread_f does not accept the buffer argument, InputArray in my case, to be unlimited polymorphic)

Fortran: Variables changing on their own

I'm having a problem of variables getting over-written for I don't know what reason. I've posted a chunk of the code below so you can see how things are declared. The variables strain,Qi,Qf,Qd,tel and Gc are passed into the subroutine and used to calculate ssgrad,strn0,strss0.
My problem is that tel and Gc are passed into the subroutine OK but are for some reason change value during this chunk of code.
Using print statements I've found that the problem first occurs during the 2nd do loop. When I set strss0 to 0, Gc and tel change value from both being equal to 1, to seemingly random numbers: tel=11.52822 Gc=-8.789086 (Just shown for the sake of example)
Each time I run the code they are set to the same values though.
Just to let you know, this subroutine interfaces with a commercial finite element package.
Many thanks in advance for any help with this
subroutine initcalcs(strain,Qi,Qf,Qd,tel,Gc,ssgrad,strn0,strss0)
implicit none
integer :: i,j
real*8:: nstrn0,nstrs0,strn0,strnf,varsq,normvar,lmbda0,lmbdaf,
# ssgrad,t0,tt,tel,nstrnf,nstrsf,Gc
real*8, dimension(3) :: strain,stran0,stranf,strss0,strssf,var
real*8, dimension(3,3) :: Qd,Qi,Qf
lmbda0=1.0d0
nstrn0=0.0d0
do i=1,3
stran0(i)=0.0d0
stran0(i)=strain(i)*lmbda0
nstrn0=nstrn0+stran0(i)**2
end do
nstrn0=dsqrt(nstrn0)
do i=1,3
strss0(i)=0.0d0
end do
In Fortran, there are two common causes of the corruption of memory values. One is a subscript error, where you assign to an array element using an incorrect subscript value. This writes to a memory location outside of the array. The other is a disagreement between the arguments in the call to a procedure (subroutine or function) and the dummy arguments of the procedure. Either can cause problems to appear at source code locations different from the actual cause. Suggestions: inspect your code for these problems. Turn on stringent warning and error checking options of your compiler. The use of Fortran >=90 and modules gives Fortran much better ability to automatically find argument consistency problems. You could monitor the memory locations with a debugger and see what it modifying it.
I concur with M. S. B.: turn on stringent warnings and error checking and verify the subroutine calls are passing arguments that have the same type and shape (array dimensions) as the subroutine expects.
The colons in the variable declaration syntax imply this is Fortran90 or later. If that's the case, I strongly suggest using the INTENT modifier to specify whether arguments are intended to be read-only.
For example, let's assume that of the arguments passed to this routine, strain, Qi, Qf, Qd, tel, and Gc are read-only input and the arguments are ssgrad, strn0, and strss0 are returned as output; that is, whatever value they have is overwritten by this routine.
The variable declarations for the arguments would change to:
real*8, dimension(3), intent(in) :: strain
real*8, dimension(3,3), intent(in) :: Qi, Qf, Qd
real*8, intent(in) :: tel, Gc
real*8, intent(out) :: strn0, ssgrad
real*8, dimension(3), intent(out) :: strss0
The INTENT keyword is an addition to Fortran 90 which allows the user to specify which arguments are read-only (INTENT(IN)), initialized but which may be modified within the routine (INTENT(INOUT)) and which are treated as uninitialized and will be set within the routine (INTENT(OUT)).
If INTENT is not specified, it is defaults to INOUT which is consistent with FORTRAN 77 (Note that there are minor differences between INTENT(INOUT) and INTENT not being specified but they aren't relevant in this example).
A good compiler will throw an error if a routine tries to assign a value to a variable declared INTENT(IN) and will at least throw a warning if a variable declared INTENT(OUT) doesn't get assigned a value.
If possible, set INTENT(IN) on all the variables which are supposed to be read-only. This may not be possible, depending on how those variables are passed to other routines. If INTENT isn't specified on arguments to routines called within this routine, it will default to INOUT. If you pass an INTENT(IN) variable as an INTENT(INOUT) argument, the compiler will throw an error. If this is happening in code you control, you have have to specify INTENT in a number of routines. This may or may not be desirable depending on whether you want to generally improve your code or just fix this one problem really quickly.
I'm assuming some of these variables are passed to external routines in the finite element package which I 'm guessing is linked to your code rather than compiled; I'm not sure how compile-time intent checking is handled in that case.