Implementation of a parametrized objective function without using module variables or internal subroutines

At the time of OOP’s introduction to the language, Oberon-2-like signatures were not taken into account, so calling problem%objective(x, y) is syntactic sugar for objective(problem, x, y).

In a programming language like Go (which inherits Oberon-2-like signatures), the passed-object (called “receiver” in Go), is not part of the function signature, so in that case what you’re trying to do would work, like in this example.

1 Like

The idea is that you can pass anything you want, as long as it’s scalar and it’s understood by the objective procedure, without further affecting solver (and no base type needs to be provided by the solver module).

The main disadvantage is the select-type-construct, which is a runtime thing —but that’s similar to passing a void* pointer in C, which needs to be type-casted in order to be useful.

Thank you @jwmwalrus for the explanation. So my understanding is not completely wrong but does not match the design of the language, unfortunately.

What is the advantage of Fortran’s design here?

I recall (from a class taken 20 years ago) that a member function of a C++ class has access to the data in the class via the reserved this pointer. It seems that C++ behaves similarly to Go in this respect (Oberon-2-like signatures), right? What about Python? I don’t know these languages well enough.

IIRC, in C++ (and some languages inspired by it) the this is a hidden argument/parameter, so the issue would be similar. Evidence of that is the fact that when overloading binary operators, some of the overloading has to be implemented outside the class (otherwise, there’ll be three arguments instead of two).

Since, unlike C++, Fortran has no reserved keywords, the passed-object can have any name but must be either explicitly passed or not at all (although, if passed, the argument doesn’t have to be the first one).

The second case you provided elsethread, should become valid if you do:

...
    type PROB_T
        real hyper_parameter
    contains
        procedure, nopass :: objective
    end type PROB_T

contains
    subroutine objective(x, y)
        real, intent(in):: x
        real, intent(out):: y
        ...
    end subroutine objective
...

But that defeats the main idea of this thread (since objective would have no access to the instance of PROB_T).

In Python, the class object is passed as the first argument/parameter, unless the method has a staticmethod decorator, and the convention is to name that argument self.

So, Oberon-2 and Go may actually be the odd kids in the block.

1 Like

I had to use a select-type-construct in my posted code too, so that looks like a wash. Just to see what it looks like, here is my code modified to use class(*) rather than class(base).

module lib
   implicit none
   abstract interface
      subroutine work( a, b )
         class(*), intent(inout) :: a
         integer, intent(inout) :: b
      end subroutine work
   end interface
contains
   subroutine lib_sub( callback, a, b )
      implicit none
      procedure(work) :: callback
      class(*), intent(inout) :: a
      integer, intent(inout) :: b
      print*, 'lib_sub entry: b=', b
      call callback( a, b )
      print*, 'lib_sub return: b=', b
      return
   end subroutine lib_sub
end module lib

program classx
   use lib
   implicit none
   type :: mytype
      integer :: c
   end type mytype
   type(mytype) :: a = mytype( c=3 )
   integer :: b = 42
   print*, 'before lib_sub:  a%c=', a%c, ' b=', b
   call lib_sub( mywork, a, b )
   print*, 'after lib_sub: a%c=', a%c, ' b=', b
contains
   subroutine mywork( a, b )
      class(*), intent(inout) :: a
      integer, intent(inout) :: b
      print*, 'entering mywork:'
      select type (a)
      type is (mytype)
         a%c = a%c + 1
         b   = b + 1
      end select
      return
   end subroutine mywork
end program classx

$ nagfor classx.f90 && a.out
NAG Fortran Compiler Release 7.2(Shin-Urayasu) Build 7203
[NAG Fortran Compiler normal termination]
 before lib_sub:  a%c= 3  b= 42
 lib_sub entry: b= 42
 entering mywork:
 lib_sub return: b= 43
 after lib_sub: a%c= 4  b= 43

I used nagfor in the above, but the code also works with gfortran and flang (all on MacOS with an arm64 cpu).

This seems a little simpler, but I still wonder if there are any practical differences between the two approaches. In particular, I wonder if class(*) requires more run time effort to associate the unlimited polymorphic argument to the actual argument compared to the class(base) case. This could be important in actual application code since the callback() routine could be called an arbitrarily large number of times.

Since the base type in your example is empty, I agree that the use of the select-type-construct is not a disadvantage.

But if the base type is non-trivial and good enough, you might choose not to extend it, and therefore the select-type-construct is not needed, e.g.:

module solver_mod
    implicit none
    private

    type, public :: point
        real :: x, y
    end type

    abstract interface
        subroutine work(a, b)
            import
            class(point), intent(in) :: a
            real, intent(out) :: b
        end subroutine work
    end interface

    public :: solver

contains
    subroutine solver(callback, a, b)
        procedure(work) :: callback
        class(point), intent(in) :: a
        real, intent(out) :: b
        call callback(a, b)
    end subroutine
end module solver_mod

module problem_mod
    use solver_mod

    implicit none
    private

    type, extends(point), public :: point3d
        real :: z
    end type

    public :: stick_to_default
    public :: try_3d

contains
    subroutine stick_to_default(a, b)
        class(point), intent(in) :: a
        real, intent(out) :: b
        print*,'sticking to a plane'
        b = sqrt(a%x ** 2 + a%y ** 2)
    end subroutine

    subroutine try_3d(a, b)
        class(point), intent(in) :: a
        real, intent(out) :: b
        select type (a)
        type is (point3d)
            print*,'trying 3d'
            b = sqrt(a%x ** 2 + a%y ** 2 + a%z ** 2)
        class default
            call stick_to_default(a, b)
        end select
    end subroutine
end module problem_mod

program usepoint
   use solver_mod
   use problem_mod

   implicit none

   real :: res
   type(point) :: a = point(1., 2.)
   type(point3d) :: b = point3d(1., 2., 3.)

   call solver(stick_to_default, a, res)
   print*, 'default result=',res
   call solver(try_3d, b, res)
   print*, '3d result=',res
end program usepoint

In the class(*) case, the select-type-construct is always required (unless you completely ignore the argument, that is).

As a curiosity I looked at how Matlab would solve this problem, here: https://uk.mathworks.com/help/matlab/math/parameterizing-functions.html

The matlab help mentions two methods: (1) use nested functions, (2) use anonymous functions. My understanding is that Fortran can do (1) (since F2008) but not (2).

Method 1

function y = findzero(b,c,x0)

y = fzero(@poly,x0);

   function y = poly(x)
   y = x^3 + b*x + c;
   end
end

** Method 2**

b = 2;
c = 3.5;
x = fzero(@(x) cubicpoly(x,b,c),0);

function y = cubicpoly(x,b,c)
y = x^3 + b*x + c;
end
1 Like

Fortran has allowed subprograms to be passed as arguments since f66. The new thing added in f2008 was the ability to pass a contained subprogram. Interpreted languages, such as matlab and mathematica, sometimes allow anonymous functions to be defined, but fortran doesn’t. Fortran doesn’t have something like an eval() function to evaluate expressions or to compile text expressions into machine instructions. That capability might be useful.

2 Likes

Thanks for the clarification.

As far as I understand, Matlab does not recommend using eval and I am not sure how Matlab deals with anonymous functions (like f below) under the hood

f = @(x) cubicpoly(x,b,c);

There is actually another aporoach in Matlab to deal with the problem of the OP, which is to pass varargin to the solver. Something like this:

function y = cubicpoly(x,a,b,c)

y = a*x^2+b*x+c;

end

Then the solver looks like this:

function xsol = myfzero(fun, x0, varargin)

del = 0.5;
F1 = fun(x0, varargin{:});
F2 = fun(x0+del, varargin{:});
% etc.

end

And it is invoked as follows:

a=2;
b=0.5;
c=-6;
xsol = myfzero(@cubicpoly, x0, a, b, c);

I think this varargin approach doesn’t work with the built-in functions like fzero, fmincon, ode45, etc. because they use varargin for other purposes, like the optional parameters that configure the solver, or to provide optional problem components such as constraints. In those cases you generally have to use anonymous functions to pass your extra parameters to the underlying function.

In your example it looks like you’ve overridden the built-in fzero. Please correct me if I have misunderstood.

2 Likes

I checked and this approach works also with built-in functions like fzero. (To avoid confusion, I renamed fzero as myfzero in the code above).

Toy example to show this:

clear,clc

% a,b,c are extra parameters to be passed to myfun
a = 1; b = 1; c = 0;
% Initial condition for solver
x0 = 0.1;
% Call built-in root-finder fzero
x_zero = fzero(@myfun,x0,[],a,b,c);

% Can we do something like this (using varargin{:}) in Fortran?
% Probably not :(
% In Fortran, (1) use module variables, (2) use internal subprograms (since
% F2008)

% Function to be zeroed
function y = myfun(x,a,b,c)

y = a*x^2+b*x+c;

end %end myfun
2 Likes

Wow, and it works for ode45 too. All these years I’ve used Matlab I never knew about this method.

[x,y] = ode45(@(x,y,a,b) 1-a*x^2+b*x, [0 1], 0, [], 1, 3);
2 Likes