Reputation: 1316
I'm running the following code, that is the implementation of a Runge-Kutta method to solve a system of differential equations.
The main code just calls the rk
subroutine, which is the implementation itself, and myfun
is just an example to test the code.
program main
use ivp_odes
implicit none
double precision, allocatable :: t(:), y(:,:)
double precision :: t0, tf, y0(2), h
integer :: i
t0 = 0d0
tf = 0.5d0
y0 = [0d0, 0d0]
h = 0.1d0
call rk4(t, y, myfun, t0, tf, y0, h)
do i=0,size(t)
print *, t(i), y(:,i)
end do
contains
pure function myfun(t,y) result(dy)
! input variables
double precision, intent(in) :: t, y(:)
! output variables
double precision :: dy(size(y))
dy(1) = -4*y(1) + 3*y(2) + 6
dy(2) = -2.4*y(1) + 1.6*y(2) + 3.6
end function myfun
end program main
and the subroutine is inside a module:
module ivp_odes
implicit none
contains
subroutine rk4(t, y, f, t0, tf, y0, h)
! input variables
double precision, intent(in) :: t0, tf, y0(1:)
double precision, intent(in) :: h
interface
pure function f(t,y0) result(dy)
double precision, intent(in) :: t, y0(:)
double precision :: dy(size(y))
end function
end interface
! output variables
double precision, allocatable :: t(:), y(:,:)
! Variáveis auxiliares
integer :: i, m, NN
double precision, allocatable :: k1(:), k2(:), k3(:), k4(:)
m = size(y0)
allocate(k1(m),k2(m),k3(m),k4(m))
NN = ceiling((tf-t0)/h)
if (.not. allocated(y)) then
allocate(y(m,0:NN))
else
deallocate(y)
allocate(y(m,0:NN))
end if
if (.not. allocated(t)) then
allocate(t(0:NN))
else
deallocate(t)
allocate(t(0:NN))
end if
t(0) = t0
y(:,0) = y0
do i=1,NN
k1(:) = h * f(t(i-1) , y(:,i-1) )
k2(:) = h * f(t(i-1)+h/2 , y(:,i-1)+k1(:)/2)
k3(:) = h * f(t(i-1)+h/2 , y(:,i-1)+k2(:)/2)
k4(:) = h * f(t(i-1)+h , y(:,i-1)+k3(:) )
y(:,i) = y(:,i-1) + (k1(:) + 2*k2(:) + 2*k3(:) + k4(:))/6
t(i) = t(i-1) + h
end do
deallocate(k1,k2,k3,k4)
return
end subroutine rk4
end module ivp_odes
The problem here is that the assignment in the rk
subroutine
y(:,i) = y(:,i-1) + (k1(:) + 2*k2(:) + 2*k3(:) + k4(:))/6
is erasing the previous values calculated. In the i-th iteration of the do-loop, it erases the previous values of the array y
and assigns just the i-th column of the array y
, so when the subroutine ends, y
has only the last value saved.
Since Fortran has implemented element-wise operations and assignments to arrays, I think the code this is easier to read and probably runs faster than doing assignments to each element in a loop. So, why is it not working? What am I missing in the assignment here? Shouldn't it just change the values in the i-th row, instead of also erasing the rest of the array?
Upvotes: 1
Views: 84
Reputation: 18098
This is a typical case of accessing an array out of its bounds. You can find these errors easily using the appropriate compiler flags. With gfortran
, this would be -fbounds-check
.
With such checks you will find the error to be an erroneous size of the function result in the interface block - dy
should have the same length as y0
(the one-dimensional dummy argument of f
), and not y
:
interface
pure function f(t,y0) result(dy)
double precision, intent(in) :: t, y0(:)
double precision :: dy(size(y0))
end function
end interface
Additionally, although not related to your particular error, you started indexing of t
and the second dimension of y
with zero. So you need to adjust the loop in the main program run to size(t)-1
only, or use ubound(t)
. Otherwise you will, again, exceed the boundaries of the arrays.
Upvotes: 4