1

How to make a function func2(func1,t,y0) which receives another function func1 as an argument, but where func1 is a function that returns a 1D real(kind=8), dimension(:) array?

I have the following code written in Matlab, and I would like to write an equivalent one in Modern Fortran for speed and portability. I have written one for first order differential equations, but I'm struggling with the task of writing the code for a code for second and higher order differential equations because the external variable corresponding to differential equations must return an array with dimension(:). I want a code to be general purpose, i.e. I want a function or subroutine to which I can pass any differential equation.

The MatLab code is:

%---------------------------------------------------------------------------

clear all
close all
clc

t = [0:0.01:20]';
y0 = [2, 0]';

y = func_runge_kutta(@func_my_ode,t,y0);

function dy=func_my_ode(t,y)
    % Second order differential equation y'' - (1-y^2)*y'+y = 0
    dy = zeros(size(y));
    dy(1) = y(2);
    dy(2) = (1-y(1)^2)*y(2)-y(1);
end

function y = func_runge_kutta(func_my_ode,t,y0)
    y = zeros(length(t),length(y0));
    y(1,:) = y0';
    for i=1:(length(t)-1)
        h = t(i+1)-t(i);
        F_1 = func_my_ode(t(i),y(i,:)');
        F_2 = func_my_ode(t(i)+h/2,y(i,:)'+h/2*F_1);
        F_3 = func_my_ode(t(i)+h/2,y(i,:)'+h/2*F_2);
        F_4 = func_my_ode(t(i)+h,y(i,:)'+h*F_3);
        y(i+1,:) = y(i,:)+h/6*(F_1+2*F_2+2*F_3+F_4)';
    end
end

%---------------------------------------------------------------------------

Lutz Lehmann
  • 25,219
  • 2
  • 22
  • 51
  • 1
    So, your question is _"How to write a Fortran function that returns an automatic sized 1D array?"_ – Rodrigo Rodrigues Jun 30 '18 at 14:21
  • Not really, my question is how to make a function func2(func1,t,y0) which receives another function func1 as an argument, but where func1 is a function that returns a 1D real(kind=8), dimension(:) array. – Lucas Silveira Jun 30 '18 at 15:30
  • 1
    What have you tried so far? – Rodrigo Rodrigues Jun 30 '18 at 16:19
  • For 1st order differential equations, i.e. when func1 returns a single real value, I've the following code. However, when I try the following code with a func1 which returns an array, the compiler complains about the mismatch. I does not accept an external variable with dimension either. – Lucas Silveira Jun 30 '18 at 19:37
  • function func_runge_kutta(func,t,y0)result(y) ! i/o variables real(8), external :: func real(8), allocatable, intent(in) :: t(:) real(8), intent(in) :: y0 real(8), allocatable :: y(:) ! internal variables ... end function func_runge_kutta – Lucas Silveira Jun 30 '18 at 19:49
  • Edit the question to add the additional info, pls – Rodrigo Rodrigues Jun 30 '18 at 23:56
  • You can find a nice fortran90 code in https://people.sc.fsu.edu/~jburkardt/f_src/rk4/rk4.html with embedded documentation and example problem. – Lutz Lehmann Jul 02 '18 at 08:20

1 Answers1

2

If a function returns an array its interface must be explicit in the caller. The easiest way to achieve this for a dummy argument function is to use the PROCEDURE statement to clone the interface from a function that may be used as an actual argument. Starting with your code, translating to Fortran and adding declarations, we get:

module everything
   use ISO_FORTRAN_ENV, only : wp => REAL64
   implicit none
   contains
function func_my_ode_1(t,y) result(dy)
    ! Second order differential equation y'' - (1-y**2)*y'+y = 0
real(wp) t
real(wp) y(:)
real(wp) dy(size(y))
    dy(1) = y(2);
    dy(2) = (1-y(1)**2)*y(2)-y(1);
end

function func_runge_kutta(func_my_ode,t,y0) result(y)
   procedure(func_my_ode_1) func_my_ode
   real(wp) t(:)
   real(wp) y0(:)
   real(wp) y(size(t),size(y0))
   integer i
   real(wp) h
   real(wp) F_1(size(y0)),F_2(size(y0)),F_3(size(y0)),F_4(size(y0))
    y(1,:) = y0;
    do i=1,(size(t)-1)
        h = t(i+1)-t(i);
        F_1 = func_my_ode(t(i),y(i,:));
        F_2 = func_my_ode(t(i)+h/2,y(i,:)+h/2*F_1);
        F_3 = func_my_ode(t(i)+h/2,y(i,:)+h/2*F_2);
        F_4 = func_my_ode(t(i)+h,y(i,:)+h*F_3);
        y(i+1,:) = y(i,:)+h/6*(F_1+2*F_2+2*F_3+F_4);
    end do
end
end module everything

program main
!clear all
!close all
!clc
use everything
implicit none
real(wp), allocatable :: t(:)
real(wp), allocatable :: y0(:)
real(wp), allocatable :: y(:,:)
integer i
integer iunit

t = [(0+0.01_wp*i,i=0,nint(20/0.01_wp))];
y0 = [2, 0];

y = func_runge_kutta(func_my_ode_1,t,y0);
open(newunit=iunit,file='rk4.txt',status='replace')
do i = 1,size(t)
   write(iunit,*) t(i),y(i,1)
end do
end program main

I had Matlab read the data file and it plotted the same picture as the original Matlab program would have, had it plotted its results.

user5713492
  • 954
  • 5
  • 11
  • 1
    +1. It would had been more clear if you used an interface block for `func_my_ode_1` in the module and passed a matching procedure declared in the program as an argument to `func_runge_kutta`, so the OP would know the function is generic and how to use it in the general case. – Rodrigo Rodrigues Jul 01 '18 at 09:39