Description
Before we dive into the implementation of root-solvers, optimization routines, and quadrature routines (see #87 and #112 ) I think it is worth discussing the "standard" way of defining callback routines (or in other words user-defined functions) which are potentially parametrized by some variables.
Some of the possibilities are described at https://www.fortran90.org/src/best-practices.html#callbacks
The NAG library uses a very low level approach, where the user function carries along both integer and real user workspaces:
Function f(x, iuser, ruser)
Real (Kind=nag_wp) :: f
Integer, Intent (Inout) :: iuser(*)
Real (Kind=nag_wp), Intent (In) :: x
Real (Kind=nag_wp), Intent (Inout) :: ruser(*)
Do we want to support a high level API were the users extend a base class (see related issue #27 ) that carries an evaluate
method, as suggested already in the comment by @rweed #87 (comment)?
In the ideal case, the n-D root finding routines, the optimization routines, and quadrature routines could share the same callback interface.
What should we do for routines that require both the function value and its derivative? Should we write multiple functions, as in
real function quad(x)
real, intent(in) :: x
quad = 2.0*x**2 + 3.0
end function
real function dquad(x)
real, intent(in) :: x
dquad = 4.0*x
end function
or would a subroutine interface `like
subroutine quadd(f,df,x)
real, intent(in) :: x
real, intent(out) :: f
real, intent(out), optional :: df
f = 2.0*x**2 + 3.0
if (present(df)) df = 4.0*x
end subroutine
be preferable?
The downside of such a subroutine interface is that it cannot be nested in other expressions.I know that personally, from performance reasons, I sometimes use the second approach, as typically the function and the derivative share some common sub-expressions that can be reused, potentially saving a few CPU operations.
It is of course possible to support several interfaces using "adaptor" routines, but this introduces extra calls, de-referencing issues, and just makes things more complicated.