Fortran: Change function based on parameter - function

Is it possible to change the function called for a program based on a parameter value? I'm thinking of something similar to a function overload, the example below shows what I'm thinking. I'm just am wondering if there is a cleaner / better way to do it.
function squareArea(s) result(A)
real :: s, A
A = s*s
end function squareArea
function circleArea(r) result(A)
real :: r, A
A = 3.14159 * r * r
end function circleArea
function Area(shape, dim) result(A)
character(len = *) shape
real dim, A
if (shape == 'circle') then
A = circleArea(dim)
elseif (shape == 'square') then
A = squareArea(dim)
end if
end function Area
program main
character(len = 6) :: sh = 'circle'
real :: r = 1.4
real :: A
A = Area(sh, r)
write(*,*) sh, r, A
end program main

Yes - and you show one possible way.
Fortran 2003 permits overriding of procedures based on the dynamic type of the object used to reference the procedure. Whether this is better/cleaner depends on your circumstances.
MODULE Shapes
IMPLICIT NONE
TYPE, ABSTRACT :: Shape
CONTAINS
PROCEDURE(shape_Area), DEFERRED :: Area
END TYPE Shape
INTERFACE
FUNCTION shape_Area(sh) RESULT(area)
IMPORT :: Shape
IMPLICIT NONE
CLASS(Shape), INTENT(IN) :: sh
REAL :: area
END FUNCTION shape_Area
END INTERFACE
TYPE, EXTENDS(Shape) :: Circle
REAL :: radius
CONTAINS
PROCEDURE :: Area => circle_Area
END TYPE Circle
TYPE, EXTENDS(Shape) :: Square
REAL :: side
CONTAINS
PROCEDURE :: Area => square_Area
END TYPE Square
CONTAINS
FUNCTION circle_Area(sh) RESULT(area)
CLASS(Circle), INTENT(IN) :: sh
REAL :: area
area = 3.14159 * sh%radius**2
END FUNCTION circle_Area
FUNCTION square_Area(sh) RESULT(area)
CLASS(Square), INTENT(IN) :: sh
REAL :: area
area = sh%side**2
END FUNCTION square_Area
END MODULE Shapes
PROGRAM Areas
USE Shapes
IMPLICIT NONE
TYPE(Circle) :: c = Circle(1.4)
TYPE(Square) :: s = Square(1.4)
CHARACTER(*), PARAMETER :: fmt = "(A,G0,' has area ',G0)"
PRINT fmt, 'Circle with radius ', c%radius, c%Area()
PRINT fmt, 'Square with side ', s%side, s%Area()
END PROGRAM Areas

Related

Reading function pointer from file in Fortran

I am trying to externally specify which function should be called. In the following code, getEnvelope is called with the chosen envelope gaussian. I would however want to read from a file which specific envelope is used rather than coding env => gaussian.
Is there a way to do this? Or is there another preferred way?
module laser
implicit none
abstract interface
double precision function envelope(t, tau) result(f)
double precision, intent(in) :: t, tau
end function envelope
end interface
contains
double precision function getEnvelope(env, t, tau) result(f)
procedure(envelope) :: env
double precision, intent(in) :: t, tau
f = env(t, tau)
end function getEnvelope
double precision function gaussian(t, tau) result(f)
double precision, intent(in) :: t, tau
f = exp(-2.d0*log(2.d0)*(t/tau)**2.d0)
end function gaussian
end module laser
program main
use laser
implicit none
procedure(envelope), pointer :: env
double precision :: f
double precision :: t, tau
env => gaussian
t = 1.d0
tau = 200.d0
f = getEnvelope(env, t, tau)
end program main

Adding a print statement in a Fortran 90 function this do not work [duplicate]

I'm trying to learn Fortran (unfortunately a necessity for my research group) - one of the tasks I set myself was to package one of the necessary functions (Associated Legendre polynomials) from the Numerical Recipes book into a fortran 03 compliant module. The original program (f77) has some error handling in the form of the following:
if(m.lt.0.or.m.gt.1.or.abs(x).gt.1)pause 'bad arguments in plgndr'
Pause seems to have been deprecated since f77 as using this line gives me a compiling error, so I tried the following:
module sha_helper
implicit none
public :: plgndr, factorial!, ylm
contains
! numerical recipes Associated Legendre Polynomials rewritten for f03
function plgndr(l,m,x) result(res_plgndr)
integer, intent(in) :: l, m
real, intent(in) :: x
real :: res_plgndr, fact, pll, pmm, pmmp1, somx2
integer :: i,ll
if (m.lt.0.or.m.gt.l.or.abs(x).gt.1) then
write (*, *) "bad arguments to plgndr, aborting", m, x
res_plgndr=-10e6 !return a ridiculous value
else
pmm = 1.
if (m.gt.0) then
somx2 = sqrt((1.-x)*(1.+x))
fact = 1.
do i = 1, m
pmm = -pmm*fact*somx2
fact = fact+2
end do
end if
if (l.eq.m) then
res_plgndr = pmm
else
pmmp1 = x*(2*m+1)*pmm
if(l.eq.m+1) then
res_plgndr = pmmp1
else
do ll = m+2, l
pll = (x*(2*ll-1)*pmmp1-(ll+m-1)*pmm)/(ll-m)
pmm = pmmp1
pmmp1 = pll
end do
res_plgndr = pll
end if
end if
end if
end function plgndr
recursive function factorial(n) result(factorial_result)
integer, intent(in) :: n
integer, parameter :: RegInt_K = selected_int_kind(20) !should be enough for the factorials I am using
integer (kind = RegInt_K) :: factorial_result
if (n <= 0) then
factorial_result = 1
else
factorial_result = n * factorial(n-1)
end if
end function factorial
! function ylm(l,m,theta,phi) result(res_ylm)
! integer, intent(in) :: l, m
! real, intent(in) :: theta, phi
! real :: res_ylm, front_block
! real, parameter :: pi = 3.1415926536
! front_block = sqrt((2*l+1)*factorial(l-abs(m))/(4*pi*))
! end function ylm
end module sha_helper
The main code after the else works, but if I execute my main program and call the function with bad values, the program freezes before executing the print statement. I know that the print statement is the problem, as commenting it out allows the function to execute normally, returning -10e6 as the value. Ideally, I would like the program to crash after giving a user readable error message, as giving bad values to the plgndr function is a fatal error for the program. The function plgndr is being used by the program sha_lmc. Currently all this does is read some arrays and then print a value of plgndr for testing (early days). The function ylm in the module sha_helper is also not finished, hence it is commented out. The code compiles using gfortran sha_helper.f03 sha_lmc.f03 -o sha_lmc, and
gfortran --version
GNU Fortran (GCC) 4.8.2
!Spherical Harmonic Bayesian Analysis testbed for Lagrangian Dynamical Monte Carlo
program sha_analysis
use sha_helper
implicit none
!Analysis Parameters
integer, parameter :: harm_order = 6
integer, parameter :: harm_array_length = (harm_order+1)**2
real, parameter :: coeff_lo = -0.1, coeff_hi = 0.1, data_err = 0.01 !for now, data_err fixed rather than heirarchical
!Monte Carlo Parameters
integer, parameter :: run = 100000, burn = 50000, thin = 100
real, parameter :: L = 1.0, e = 1.0
!Variables needed by the program
integer :: points, r, h, p, counter = 1
real, dimension(:), allocatable :: x, y, z
real, dimension(harm_array_length) :: l_index_list, m_index_list
real, dimension(:,:), allocatable :: g_matrix
!Open the file, allocate the x,y,z arrays and read the file
open(1, file = 'Average_H_M_C_PcP_boschi_1200.xyz', status = 'old')
read(1,*) points
allocate(x(points))
allocate(y(points))
allocate(z(points))
print *, "Number of Points: ", points
readloop: do r = 1, points
read(1,*) x(r), y(r), z(r)
end do readloop
!Set up the forwards model
allocate(g_matrix(harm_array_length,points))
!Generate the l and m values of spherical harmonics
hloop: do h = 0, harm_order
ploop: do p = -h,h
l_index_list(counter) = h
m_index_list(counter) = p
counter = counter + 1
end do ploop
end do hloop
print *, plgndr(1,2,0.1)
!print *, ylm(1,1,0.1,0.1)
end program sha_analysis
Your program does what is known as recursive IO - the initial call to plgndr is in the output item list of an IO statement (a print statement) [directing output to the console] - inside that function you then also attempt to execute another IO statement [that outputs to the console]. This is not permitted - see 9.11p2 and p3 of F2003 or 9.12p2 of F2008.
A solution is to separate the function invocation from the io statement in the main program, i.e.
REAL :: a_temporary
...
a_temporary = plgndr(1,2,0.1)
PRINT *, a_temporary
Other alternatives in F2008 (but not F2003 - hence the [ ] parts in the first paragraph) include directing the output from the function to a different logical unit (note that WRITE (*, ... and PRINT ... reference the same unit).
In F2008 you could also replace the WRITE statement with a STOP statement with a message (the message must be a constant - which wouldn't let you report the problematic values).
The potential for inadvertently invoking recursive IO is part of the reason that some programming styles discourage conducting IO in functions.
Try:
if (m.lt.0.or.m.gt.l.or.abs(x).gt.1) then
write (*, *) "bad arguments to plgndr, aborting", m, x
stop
else
...
end if

General purpose runge-kutta function for second order differential equations in Modern Fortran

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
%---------------------------------------------------------------------------
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.

Returning Polymorphic Class

I'm trying to understand why one of the below is allowed by the standard while the other is not. They don't seem different except for boilerplate code to me. I feel like I'm misunderstanding something, or that there is a better way of doing it. Any help would be appreciated.
Not allowed:
real :: x
class(*) :: temp
x = 4
temp = genericAssignment(x)
select type(temp)
type is(real)
write(*,*) temp
end select
contains
function genericAssignment(a) result(b)
class(*) :: a
class(*) :: b
allocate(b, source=a)
end function genericAssignment
Allowed:
Type GenericContainer
class(*), pointer :: gen
End Type
real :: x
class(*) :: ptr
type(GenericContainer) :: temp
x = 4
temp = genericAssignment(x)
select type(ptr => temp%gen)
type is(real)
write(*,*) ptr
end select
contains
function genericAssignment(a) result(b)
class(*) :: a
type(GenericContainer) :: b
allocate(b%gen, source=a)
end function genericAssignment
The current standard allows both.
The "allowed" code block has a function with a non-polymorphic result, with the result of evaluating the function being assigned to a non-polymorphic variable. This is valid Fortran 2003.
The "not allowed" block has a function with a polymorphic result, with the result of evaluating the function being assigned to a polymorphic variable. This is valid Fortran 2008.
Note that the number of complete Fortran 2008 compiler implementations out there is small.
~~~
The function in the "not allowed" block is somewhat pointless - the code block is equivalent to:
real :: x
class(*) :: temp
x = 4
temp = x
select type(temp)
type is(real)
write(*,*) temp
end select

Pointer to a function inside a derived type on a module in fortran

I guess I could easily use some help here, since I'm messing around with some fortran 2003 but can't seem to understand how to do things really.
The fact is that I need to write a fortran code that declares, inside a module, a new data type
that has as one of its members a pointer to a real function. Something like
module new_mod
type my_type
real*8 :: a, b
(here something that declares a real*8 function), pointer :: ptr
end type my_type
end module_new
module funcs
real*8 function function1(x)
real*8 :: x
function1 = x*x
end function function1
real*8 function function2(x)
real*8 :: x
function2 = x*x
end function function2
end module funcs
then in the main program I would like to have something like
program my_prog
use module_new
use module_funcs
implicit none
real*8 :: y, z
type(my_type) :: atom
...
atom%ptr => function1
y = atom%ptr(x)
...
atom%ptr => function2
z = atom%ptr(x)
end program my_prog
while
so the main idea is that module_new contains a type that has a pointer to a real
function. This pointer in th eobjects of the new type I must be able to point to different functions in the main program.
I have seen one can do similar things with abstract interfaces and such, but honestly, I'm in a mess here. If someone could help, I'll appreciate that.
Cheers...
Well, that is not really the type of question you would send to stackoverflow, but actually your code needs only a "slight improvement" (by appropriate definition of slight) to work:
module accuracy
implicit none
integer, parameter :: dp = kind(1.0d0)
end module accuracy
module typedef
use accuracy
implicit none
type :: mytype
real(dp) :: aa, bb
procedure(myinterface), pointer, nopass :: myfunc
end type mytype
abstract interface
function myinterface(xx)
import :: dp
real(dp), intent(in) :: xx
real(dp) :: myinterface
end function myinterface
end interface
end module typedef
module funcs
use accuracy
implicit none
contains
function func1(xx)
real(dp), intent(in) :: xx
real(dp) :: func1
func1 = xx
end function func1
function func2(xx)
real(dp), intent(in) :: xx
real(dp) :: func2
func2 = 2.0_dp * xx
end function func2
end module funcs
program test
use accuracy
use typedef
use funcs
implicit none
real(dp) :: xx
type(mytype) :: atom
xx = 12.0_dp
atom%myfunc => func1
print *, atom%myfunc(xx)
atom%myfunc => func2
print *, atom%myfunc(xx)
end program test
There are several things to be worth to mentioned:
You should use one global parameter for your accuracy (see module accuracy) and forget about real*8.
Your procedure pointer in your derived type needs an interface, which is provided within the following abstract interface block (see 'abstract interfaces' in a good F2003 book).
You need the nopass option for the procedure pointer in the derived type as otherwise Fortran will assume that the first parameter passed to the function/subroutine is the derived type itself (see 'type bound procedures' in a good F2003 book).
Finally, although rather obvious: You should definitely read a book about the Fortran 2003 features if you are serious about using them in a production code.