[Note: There is an discussion about this issue at Fortran Discourse.]
Below is a toy program that calls an optimization solver to optimize a simple parametrized objective function. A very similar example apperas in Note 12.18 on page 290 of WD 1539-1 J3/10-007r1 (F2008 Working Document).
The particular thing about the code is that the implementation of the objective function objective contains a hyperparameter hyper_parameter, but the interface of objective is fixed as objective(x,y) and that of the solver solver is fixed as solver(objective). Therefore, we cannot pass hyper_parameter explicitly to objective during the optimization. Hence, we implement objective as an internal subroutine, which has access to hyper_parameter of the host program. An alternative approach is to make hyper_parameter available to objective via a module variable.
Question: Is it possible to implement the same code without using module variables or internal subroutines? If yes, how?
N.B.
- If you are interested in answering the question positively, please provide a single file that can be compiled immediately by popular compilers such as
gfortran13/14,ifx2024/25, andnagfor7. - The code must be completely standard-conforming without depending on external libraries or directives.
- It must not depend on the compiler or the operating system.
- It must not use obsolescent / deprecated constructs (ref. B.3. Obsolescent features on page 563 of WD 1539-1, J3/24-007, Fortran 2023 Interpretation Document).
- No warning should be emitted when the code is compiled using
gfortran -Wall -Wextra,ifx -warn all, ornagfor -C. - The implementation must be thread-safe under common processors (sure, thread-safety is not a topic in Fortran standards).
!----------------------------------------------------------------------!
! optim.f90: a simple program that calls an optimization solver to
! optimize a simple parameterized objective function.
!----------------------------------------------------------------------!
!----------------------------------------------------------------------!
! solver_mod: a module defining an optimization solver
module solver_mod
implicit none
private
public:: solver
! OBJ: an abstract interface for an objective function. It is defined
! by a thrid library and CANNOT be changed.
abstract interface
subroutine OBJ(x, y)
real, intent(in):: x
real, intent(out):: y
end subroutine OBJ
end interface
contains
! solver: a doing-nothing solver for demonstration. It is provided by a
! third library, so we CANNOT change its signature to accept a hyper-
! parameter for `objective`.
subroutine solver(objective)
procedure(OBJ):: objective
real y
call objective(0.0, y)
end subroutine solver
end module solver_mod
!----------------------------------------------------------------------!
!----------------------------------------------------------------------!
program optimize
use solver_mod, only: solver
implicit none
! `hyper_parameter`: a hyperparameter for the objective function. In
! real applications, this parameter is not a constant, but a variable
! that cannot be predicted.
real hyper_parameter
hyper_parameter = 42.0
! Whatever your solution is, `solver` should receive a subroutine
! with interface `OBJ`. Otherwise, it is not a desired solution.
call solver(objective)
contains
!--------------------------------------------------------------!
! objective: a simple objective function for demonstration.
! F2008 allows to pass internal procedures as actual arguments.
! See Note 12.18 on page 290 of WD 1539-1 J3/10-007r1 (F2008
! Working Document). We implement `objective` internally so that
! `hyper_parameter` is visible to it. We choose not to pass the
! parameter by a module variable, which is recursion-unsafe.
subroutine objective(x, y)
real, intent(in):: x
real, intent(out):: y
y = (x+hyper_parameter)**2
end subroutine objective
!--------------------------------------------------------------!
end program optimize
!----------------------------------------------------------------------!
Motivation: As of today, the default implementation of internal procedures often needs executable stacks (it may be turned off using some flags, e.g., in gfortran 14). This is a security issue and may not be allowed by modern systems. Thus, we hope to avoid it. Module variables are not preferred because they are not recursion-safe.
I attempted to use derived types, but my knowledge is not sufficient for me to find a solution. I asked LLMs, but none of them could provide a solution either.
In case of any doubt about my toy code, you may check the example in Note 12.18 on page 290 of WD 1539-1 J3/10-007r1 (F2008 Working Document). As an example from the Fortran standard, it represents very typical use cases.
My attempt:
I hoped to have an implementation based on derived types as follows. Unfortunately, it is NOT valid Fortran.
! N.B.: The following code is NOT valid. For concept demonstration only.
!----------------------------------------------------------------------!
! solver_mod: a module defining an optimization solver
module solver_mod
implicit none
private
public:: solver
! OBJ: an abstract interface for an objective function
! We CANNOT change it.
abstract interface
subroutine OBJ(x, y)
real, intent(in):: x
real, intent(out):: y
end subroutine OBJ
end interface
contains
! solver: a doing-nothing solver for demonstration.
! We CANNOT change its interface.
subroutine solver(objective)
procedure(OBJ):: objective
real y
call objective(0.0, y)
end subroutine solver
end module solver_mod
!----------------------------------------------------------------------!
!----------------------------------------------------------------------!
! N.B.: This part of the code is for concept demonstration only.
! It is NOT valid Fortran.
module problem_mod
implicit none
private
public:: PROB_T
type PROB_T
real hyper_parameter
contains
subroutine objective(x, y)
! A simple objective function for demonstration,
! hoping that it has access to hyper_parameter.
real, intent(in):: x
real, intent(out):: y
y = (x+hyper_parameter)**2
end subroutine objective
end type PROB_T
end module problem_mod
!----------------------------------------------------------------------!
!----------------------------------------------------------------------!
program optimize
use solver_mod, only: solver
use problem_mod, only: PROB_t
implicit none
type(PROB_t):: problem
problem%hyper_parameter = 42.0
call solver(problem%objective)
end program optimize
!----------------------------------------------------------------------!
I also tried an alternative implementation of PROB_T similar to the following, which is still INVALID.
module problem_mod
implicit none
private
public:: PROB_T
type PROB_T
real hyper_parameter
contains
procedure:: objective
end type PROB_T
contains
subroutine objective(self, x, y)
class(PROB_T), intent(in):: self
real, intent(in):: x
real, intent(out):: y
! A simple objective function for demonstration.
y = (x+self%hyper_parameter)**2
end subroutine objective
end module problem_mod
COMMON</hint>. Oh yeah, watch out for thread-safety issue.