In my Fortran code I need to update many variables by adding something to them. For example, if x is my variable, I may need to do something like this:
x = x + 1
The problem is that my variables are array elements and have big names etc so repeating x in the above equation is cumbersome task. In Python for example we have the += operator to achieve this. Do we have something similar in Fortran?
No, Fortran does not have this operator. However, you could implement a subroutine to do so:
elemental subroutine my_incr( var, incr )
implicit none
integer,intent(inout) :: var
integer,intent(in) :: incr
var = var + incr
end subroutine
Then you could call that in your code:
! ...
call my_incr( x, 1 )
! ...
Due to the elemental nature of the subroutine, you can also perform this operation on arrays:
! ...
call my_incr( array(:), 1 )
! ...
"The problem is that my variables are array elements and have big names etc so repeating x in the above equation is cumbersome task."
Well, if this is your only problem then the simplest and cleanest solution is to use the associate construct (I use it all the time for exactly this purpose):
ASSOCIATE( x => variable_with_really_long_name, &
y => another_one)
! some calculations here
x = x + 1
! some calculations here again
end associate
Related
I decided to use lapack subroutine dtrmm instead of matmul to multiply lower triangular (d,d) matrix and general (d,n) matrix. However, it doesn't seem to work correctly. The following code compares results of matlum (top) and dtrmm
Program test
implicit none
integer, parameter :: n = 3, d = 3 ! arbitrary numbers, no luck with other values
integer :: i
real(8) :: a(d,d), b(d,n), c(d,n)
call random_number(a)
call random_number(b)
do i=2,d
a(1:i-1,i) = 0
end do
c = matmul(a,b)
call dtrmm('L','L','N','N',d,n,1,a,d,b,d) ! documentation linked in the question
print*, 'correct result : '
do i=1,d
print*, c(i,:)
end do
print*, 'dtrmm yields : '
do i=1,d
print*, b(i,:)
end do
End Program test
returns this
correct result :
0.75678922130735249 0.51830058846965921 0.51177304237548271
1.1974740765385026 0.46115246753697681 0.98237114765741340
0.98027798241945430 0.53718796235743815 1.0328498570683342
dtrmm yields :
6.7262070844500211E+252 4.6065628207770121E+252 4.5485471599475983E+252
1.0642935166471391E+253 4.0986405551607272E+252 8.7311388520015068E+252
8.7125351741793473E+252 4.7744304178222945E+252 9.1797845822711462E+252
Other lapack suboutines I used work fine. What could be causing this to misbehave? Is this a bug, or have I just badly misunderstood something?
It is a simple data type error. The factor alpha must be of type double precision whereas you supplied an integer of default kind.
Thus
...
! call dtrmm('L','L','N','N',d,n,1,a,d,b,d) WRONG
call dtrmm('L','L','N','N',d,n,1d0,a,d,b,d) ! note 1d0 instead of 1
...
gives the correct result.
Is there a simple and quick way to multiply a column of a matrix with element of a vector. We can do this explicitly,
program test
integer :: x(3,3), y(3), z(3,3)
x = reshape([(i,i=1,9)],[3,3])
y = [1,2,3]
do i=1,3
z(:,i) = x(:,i) * y(i)
print *, z(:,i)
enddo
end program test
Is there a way to perform the do loop in one line. For example in Numpy python we can do this to do the job in one shot
z = np.einsum('ij,i->ij',x,y)
#or
z = x*y[:,None]
Try
z = x * spread(y,1,3)
and if that doesn't work (no Fortran on this computer so I haven't checked) fiddle around with spread until it does. In practice you'll probably want to replace the 3 by size(x,1) or suchlike.
I expect that this will cause the compiler to create temporary arrays. And I expect it will be easy to find situations where it underperforms the explicit looping scheme in the question. 'neat' one-liners often have a cost in both time and space. And often tried-and-trusted Fortran approach of explicit looping is the one to go with.
Why replace clear easy to read code with garbage?
program test
implicit none
integer i,j
integer :: x(3,3), y(3), z(3,3)
x = reshape([(i,i=1,9)],[3,3])
y = [1,2,3]
z = reshape ([((x(j,i)*y(i) ,j=1,3),i=1,3)], [3,3])
print *, z(1,:)
print *, z(2,:)
print *, z(3,:)
end program test
Here's my factorial function in Fortran.
module facmod
implicit none
contains
function factorial (n) result (fac)
use FMZM
integer, intent(in) :: n
integer :: i
type(IM) :: fac
fac = 1
if(n==0) then
fac = 1
elseif(n==1) then
fac = 1
elseif(n==2) then
fac = 2
elseif(n < 0) then
write(*,*) 'Error in factorial N=', n
stop 1
else
do i = 1, n
fac = fac * i
enddo
endif
end function factorial
end module facmod
program main
use FMZM
use facmod, only: factorial
implicit none
type(IM) :: res
integer :: n, lenr
character (len=:), allocatable :: str
character(len=1024) :: fmat
print*,'enter the value of n'
read*, n
res = factorial(n)
lenr = log10(TO_FM(res))+2
allocate(character(len=lenr) :: str)
write (fmat, "(A5,I0)") "i", lenr
call im_form(fmat, res, str)
print*, trim( adjustl(str))
end program main
I compile using FMZM:
gfortran -std=f2008 fac.F90 fmlib.a -o fac
echo -e "1000" | .fac computes easy. However, if I give this echo -e "3600" | .fac, I already get an error on my machine:
Error in FM. More than 200000 type (FM), (ZM), (IM) numbers
have been defined. Variable SIZE_OF_START in file
FMSAVE.f95 defines this value.
Possible causes of this error and remedies:
(1) Make sure all subroutines (also functions that do not
return type FM, ZM, or IM function values) have
CALL FM_ENTER_USER_ROUTINE
at the start and
CALL FM_EXIT_USER_ROUTINE
at the end and before any other return, and all
functions returning an FM, ZM, or IM function value have
CALL FM_ENTER_USER_FUNCTION(F)
at the start and
CALL FM_EXIT_USER_FUNCTION(F)
at the end and before any other return, where the actual
function name replaces F above.
Otherwise that routine could be leaking memory, and
worse, could get wrong results because of deleting some
FM, ZM, or IM temporary variables too soon.
(2) Make sure all subroutines and functions declare any
local type FM, ZM, or IM variables as saved. Otherwise
some compilers create new instances of those variables
with each call, leaking memory.
For example:
SUBROUTINE SUB(A,B,C,X,Y,RESULT)
TYPE (FM) :: A,B,C,X,Y,RESULT,ERR,TOL,H
Here A,B,C,X,Y,RESULT are the input variables and
ERR,TOL,H are local variables. The fix is:
SUBROUTINE SUB(A,B,C,X,Y,RESULT)
TYPE (FM) :: A,B,C,X,Y,RESULT
TYPE (FM), SAVE :: ERR,TOL,H
(3) Since = assignments for multiple precision variables are
the trigger for cleaning up temporary multiple precision
variables, a loop with subroutine calls that has no =
assignments can run out of space to store temporaries.
For example:
DO J = 1, N
CALL SUB(A,B,C,TO_FM(0),TO_FM(1),RESULT)
ENDDO
Most compilers will create two temporary variables with
each call, to hold the TO_FM values.
One fix is to put an assignment into the loop:
DO J = 1, N
ZERO = TO_FM(0)
CALL SUB(A,B,C,ZERO,TO_FM(1),RESULT)
ENDDO
(4) If a routine uses allocatable type FM, ZM, or IM arrays
and allocates and deallocates with each call, then after
many calls this limit on number of variables could be
exceeded, since new FM variable index numbers are
generated for each call to the routine.
A fix for this is to call FM_DEALLOCATE before actually
deallocating each array, so those index numbers can be
re-used. For example:
DEALLOCATE(T)
becomes:
CALL FM_DEALLOCATE(T)
DEALLOCATE(T)
(5) If none of this helps, try running this program again
after increasing the value of SIZE_OF_START and
re-compiling.
What optimizations or Fortran idioms am I missing that is hurting my performance so much?
For example, in python, I can factorial numbers much larger than 3500:
>>> import math
>>> math.factorial(100000)
Or in Haskell:
Prelude> product [1..100000]
Both these compute, not exactly quickly, but without error.
How can I improve my algorithm or better use existing libraries to improve performance of large integer factorials in Fortran? Is there a more appropriate big integer library than FMZM?
Try this. Apart from minor cosmetic changes, I just followed the recommendations of the error message in your question:
added calls to FM_ENTER_USER_FUNCTION and FM_EXIT_USER_FUNCTION,
added an assignment inside the loop (without this ii = to_im(i), it still fails, but I'm not sure why, as there is already an assignment with fac = fac * i, and accordind to the doc the assignment triggers cleaning up temporaries),
renamed factorial in main program as there is already a function with this name in FMZM.
Tested with ifort and n=100000.
module fac_mod
implicit none
contains
function factorial(n) result(fac)
use FMZM
integer, intent(in) :: n
integer :: i
type(IM) :: fac
type(IM), save :: ii
call FM_ENTER_USER_FUNCTION(fac)
fac = to_im(1)
if (n < 0) then
write (*, *) "Error in factorial N=", n
stop 1
else if (n > 1) then
do i = 1, n
ii = to_im(i)
fac = fac * ii
end do
end if
call FM_EXIT_USER_FUNCTION(fac)
end function factorial
end module fac_mod
program main
use FMZM
use fac_mod, only: f=>factorial
implicit none
type(IM) :: res
integer :: n, lenr
character(:), allocatable :: str
character(1024) :: fmat
print *, "enter the value of n"
read *, n
res = f(n)
lenr = 2 + log10(TO_FM(res))
allocate (character(lenr) :: str)
write (fmat, "(A5,I0)") "i", lenr
call im_form(fmat, res, str)
print *, trim(adjustl(str))
end program main
The Python code below generates all the different vectors of size ndim, built from the values 0 and 1:
import itertools
ndim = 8
A = list(itertools.product([0,1], repeat=ndim))
print (A)
Is it possible to do the same in Fortran? What is then the equivalent of intertools.product in Fortran?
Yes, it's possible to do the same in Fortran, though some might think it a little more bothersome. What is the equivalent of itertools.product ? I haven't a scooby what itertools.product is in the first place. But this little function seems to do what you want ...
FUNCTION bit_vectors(ndim) RESULT(bv)
INTEGER, INTENT(in) :: ndim
INTEGER, DIMENSION(0:2**ndim-1,ndim) :: bv
INTEGER :: jx
INTEGER, DIMENSION(0:ndim-1) :: bits
bits = [(jx,jx=0,ndim-1)]
DO jx = 0, 2**ndim-1
bv(jx,:) = MERGE(1,0,BTEST(jx,bits))
END DO
END FUNCTION bit_vectors
I imagine you could generalise this approach for a wider range of purposes.
So I am using the taylor series to calculate sin(0.75) in fortran 90 up until a certain point, so I need to run it in a do while loop (until my condition is met). This means I will need to use a factorial, here's my code:
program taylor
implicit none
real :: x = 0.75
real :: y
integer :: i = 3
do while (abs(y - sin(0.75)) > 10.00**(-7))
i = i + 2
y = x - ((x**i)/fact(i))
print *, y
end do
end program taylor
Where i've written fact(i) is where i'll need the factorial. Unfortunately, Fortran doesn't have an intrinsic ! function. How would I implement the function in this program?
Thanks.
The following simple function answers your question. Note how it returns a real, not an integer. If performance is not an issue, then this is fine for the Taylor series.
real function fact(n)
integer, intent(in) :: n
integer :: i
if (n < 0) error stop 'factorial is singular for negative integers'
fact = 1.0
do i = 2, n
fact = fact * i
enddo
end function fact
But the real answer is that Fortran 2008 does have an intrinsic function for the factorial: the Gamma function. For a positive integer n, it is defined such that Gamma(n+1) == fact(n).
(I can imagine the Gamma function is unfamiliar. It's a generalization of the factorial function: Gamma(x) is defined for all complex x, except non-positive integers. The offset in the definition is for historical reasons and unnecessarily confusing it you ask me.)
In some cases you may want to convert the output of the Gamma function to an integer. If so, make sure you use "long integers" via INT(Gamma(n+1), kind=INT64) with the USE, INTRINSIC :: ISO_Fortran_env declaration. This is a precaution against factorials becoming quite large. And, as always, watch out for mixed-mode arithmetic!
Here's another method to compute n! in one line using only inline functions:
product((/(i,i=1,n)/))
Of course i must be declared as an integer beforehand. It creates an array that goes from 1 to n and takes the product of all components. Bonus: It even works gives the correct thing for n = 0.
You do NOT want to use a factorial function for your Taylor series. That would meant computing the same terms over and over. You should just multiply the factorial variable in each loop iteration. Don't forget to use real because the integer will overflow quickly.
See the answer under the question of your schoolmate Program For Calculating Sin Using Taylor Expansion Not Working?
Can you write the equation which gives factorial?
It may look something like this
PURE FUNCTION Bang(N)
IMPLICIT NONE
INTEGER, INTENT(IN) :: N
INTEGER :: I
INTEGER :: Bang
Bang = N
IF(N == 2) THEN
Bang = 2
ELSEIF(N == 1) THEN
Bang = 1
ELSEIF(N < 1) THEN
WRITE(*,*)'Error in Bang function N=',N
STOP
ELSE
DO I = (N-1), 2, -1
Bang = Bang * I
ENDDO
ENDIF
RETURN
END FUNCTION Bang