Como trabalhar com ponteiros de função no Fortran em programas científicos

11

Aqui está um uso típico de ponteiros de função em C. Eu gostaria de fazer algo semelhante no Fortran. Tenho algumas idéias, mas gostaria de saber se existe alguma maneira canônica de fazer isso.

Os ponteiros de função e os contextos transmitidos pelo usuário são armazenados e depois chamados posteriormente.

typedef PetscErrorCode (*TSIFunction)(TS,PetscReal,Vec,Vec,Vec,void*);
PetscErrorCode TSSetIFunction(TS ts,Vec res,TSIFunction f,void *ctx);

A função do usuário é chamada de volta usando seu contexto em vários momentos posteriores.

No PETSc, eles também usam muito as tabelas de ponteiros de função string>. Tudo é um plugin, para que o usuário possa registrar suas próprias implementações e elas são de primeira classe.

#define PCGAMG "gamg"
  PCRegisterDynamic(PCGAMG         ,path,"PCCreate_GAMG",PCCreate_GAMG);

Isso registra a rotina de criação em um "FList" e, em seguida, PCSetFromOptions () oferece a capacidade de escolher esse método em comparação com qualquer uma das outras opções. Se o sistema suportar carregamento dinâmico, você poderá pular a dependência do símbolo PCCreate_GAMG em tempo de compilação e passar NULL, em seguida, o símbolo será procurado na biblioteca compartilhada em tempo de execução.

Observe que este passo além de uma "fábrica", é uma inversão do dispositivo de controle semelhante ao que Martin Fowler chama de "localizador de serviço".

Nota: isso surgiu em minha correspondência particular com Jed Brown, onde ele me fez essa pergunta. Decidi terceirizá-lo e ver quais respostas as pessoas podem apresentar.

Ondřej Čertík
fonte

Respostas:

5

Não é necessário usar a transferência para emular void *em um código Fortran moderno. Em vez disso, basta usar o módulo intrínseco ISO_C_BINDING , suportado por todos os principais compiladores Fortran. Este módulo facilita muito a interface entre o Fortran e o C, com algumas ressalvas muito pequenas. Pode-se usar as funções C_LOCe C_FUNLOCpara obter ponteiros C para dados e procedimentos do Fortran, respectivamente.

Com relação ao exemplo PETSC acima, presumo que o contexto seja tipicamente um ponteiro para uma estrutura definida pelo usuário, que é equivalente a um tipo de dados derivado no Fortran. Isso não deve ser um problema para lidar com o uso C_LOC. O identificador TSIFunction opaco também é muito simples de lidar: basta usar o tipo de dados ISO_C_BINDING c_ptr, que é equivalente a void *C. Uma biblioteca escrita em Fortran pode ser usada c_ptrse for necessário contornar a verificação estrita de tipos do Fortran moderno.

Brian
fonte
Brian, sim, enquanto isso, Jed e eu descobrimos algumas soluções para o retorno de chamada, veja aqui: fortran90.org/src/best-practices.html#type-casting-in-callbacks , o tipo (c_ptr) é o número da seção V.
Ondřej Čertík
9

Há muito do que eu acho que é uma linguagem específica do PETSc na sua pergunta (com a qual eu não estou familiarizado), então pode haver uma ruga aqui que eu não entendo direito, mas talvez isso ainda seja útil para você começado.

Basicamente, você precisa definir a interface para o procedimento e, em seguida, pode passar um ponteiro para uma função que segue essa interface. O código a seguir mostra um exemplo. Primeiro, existe um módulo que define a interface e mostra um exemplo rápido de um pedaço de código que executaria a rotina fornecida pelo usuário que segue essa interface. A seguir, é apresentado um programa que mostra como o usuário usaria esse módulo e definiria a função a ser executada.

MODULE xmod

  ABSTRACT INTERFACE
  FUNCTION function_template(n,x) RESULT(y)
      INTEGER, INTENT(in) :: n
      REAL, INTENT(in) :: x(n)
      REAL :: y
  END FUNCTION function_template
  END INTERFACE

CONTAINS

  SUBROUTINE execute_function(n,x,func,y)
    INTEGER, INTENT(in) :: n
    REAL, INTENT(in) :: x(n)
    PROCEDURE(function_template), POINTER :: func
    REAL, INTENT(out) :: y
    y = func(n,x)
  END SUBROUTINE execute_function

END MODULE xmod


PROGRAM xprog

  USE xmod

  REAL :: x(4), y
  PROCEDURE(function_template), POINTER :: func

  x = [1.0, 2.0, 3.0, 4.0]
  func => summation

  CALL execute_function(4,x,func,y)

  PRINT*, y  ! should give 10.0

CONTAINS

  FUNCTION summation(n,x) RESULT(y)
    INTEGER, INTENT(in) :: n
    REAL, INTENT(in) :: x(n)
    REAL :: y
    y = SUM(x)
  END FUNCTION summation

END PROGRAM xprog
Barron
fonte
Obrigado pela resposta. O exemplo do PETSc acima também armazena o ponteiro de função em alguma estrutura de dados interna, mas acho que é bastante trivial salvar PROCEDURE(function_template), POINTER :: funcinternamente.
Ondřej Čertík
Observe que o ponteiro é um objeto opaco e não o endereço desse código; portanto, o AFAIK não pode ter interoperabilidade com C. No PETSc, precisamos manter tabelas de ponteiros de função para wrappers C para essas coisas.
precisa saber é o seguinte
O exemplo PETSc armazena o ponteiro da função e o contexto (dados do usuário privado que são retornados quando a função é chamada). O contexto é realmente crucial, caso contrário, o usuário acaba fazendo coisas horríveis, como referenciar globais. Como não há equivalente void*, o usuário acaba tendo que escrever interfaces para as próprias funções da biblioteca. Se você implementar a biblioteca em C, isso é suficiente, mas se você implementar no Fortran, precisará garantir que o compilador nunca veja o INTERFACE "fictício" da biblioteca ao mesmo tempo que o INTERFACE do usuário.
quer
2
O equivalente void*em Fortran é um transfermétodo. Veja aqui um exemplo de uso. As outras 3 abordagens além do transfermétodo são "matrizes de trabalho", "tipo derivado específico em vez de void *" e usam variáveis ​​de módulo locais para o módulo.
Ondřej Čertík
É uma pena que o usuário tenha que mexer com transfer um tipo sem sentido ( character (len=1), allocatable) apenas para chamar a função.
Jed Brown