1

[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 gfortran 13/14, ifx 2024/25, and nagfor 7.
  • 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, or nagfor -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
14
  • 1
    There is a lively discussion at fortran-lang.discourse.group/t/… Commented Jul 17 at 17:00
  • Thanks @steve for the kind reminder. Does this constitute a valid reason for downvoting? StackOverflow has its independent community and audience. Commented Jul 17 at 17:02
  • 3
    Why not disclose that you've already asked the question in fortran-lang and there are 45 responses. This prevents wasting the time of those here repeating the answers you already have. To answer your question, yes, there is likely an implementation that uses neither modules nor internal subprograms. <hint>COMMON</hint>. Oh yeah, watch out for thread-safety issue. Commented Jul 17 at 17:12
  • 1
    @Nuno The suggestion by RonShepard on the Discord is a good one. I would have suggested it in an answer myself but I won't repeat it. If the threading library you uses numbered threads, you can create an array of hyperparameters (possibly with padding to avoid false sharing) in a module and leat each thread to use the hyperparameter value in a different array element. This is not limited to OpenMP. Commented Jul 17 at 19:21
  • 1
    You later wrote that you don't like that solution because it is limited to OpenMP. It is not. You just need your threads to be aware which thread they are. That should be always possible. Commented Jul 17 at 19:27

2 Answers 2

1

With the new constraints imposed in the edited question, the answer is "there is no implementation possible."

Sign up to request clarification or add additional context in comments.

Comments

0

That discourse, sadly, is often swamped with authoritative-sounding misinformation.

I would suggest that you avoid using internal procedures with uplevel references as actual procedures or procedure pointer targets, and use a more simple approach of defining an explicit closure derived type to maintain contextual state and a pointer to a particular subroutine. E.g.,

module optimizerObjective
  implicit none
  type objectiveFuncObj
    real parameter
    procedure(objective), pass, pointer :: objective
  end type
  interface
    subroutine objective(parms, x, y)
      import objectiveFuncObj
      class(objectiveFuncObj), intent(in) :: parms
      real, intent(in) :: x, y
    end
  end interface
end

module optimizer
  use optimizerObjective
  implicit none
 contains
  subroutine optimize(objective)
    type(objectiveFuncObj), intent(in) :: objective
    call objective%objective(1., 2.)
  end
end

module demo
  use optimizerObjective
  implicit none
  private
  public demoObjective
  type(objectiveFuncObj) :: demoObjective = objectiveFuncObj(42., myObjective)
 contains
  subroutine myObjective(parms, x, y)
    class(objectiveFuncObj), intent(in) :: parms
    real, intent(in) :: x, y
    print *, parms%parameter +x + y
  end
end

program main
  use optimizer
  use demo
  implicit none
  call optimize(demoObjective)
end

4 Comments

Thank you, Peter, for the answer. Derived types were my hope, but I have been unable to arrive at a solution due to limits of my knowledge. Would you mind extending you answer to a complete piece of compilable code? Thank you so much.
This answer doesn't address the constraint that "[subroutine solver is provided by a] third [party] library, so we CANNOT change its signature to accept a hyper-parameter for objective".
This sort of solution was already been suggested near the start of the Discord thread but rejected by the OP.
If module variables, COMMON, uplevel references, and modification of the interface are all unacceptable, then the only remaining method of getting extra state into a procedure that I can think of would be: 1) external I/O from a file whose name is derived from the process and thread IDs, or 2) environment variables, and neither of those methods can be used without using extensions. OP is probably out of luck.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.