Eu gostaria de usar f2py
com o Fortran moderno. Em particular, estou tentando fazer o exemplo básico a seguir funcionar. Este é o menor exemplo útil que eu poderia gerar.
! alloc_test.f90
subroutine f(x, z)
implicit none
! Argument Declarations !
real*8, intent(in) :: x(:)
real*8, intent(out) :: z(:)
! Variable Declarations !
real*8, allocatable :: y(:)
integer :: n
! Variable Initializations !
n = size(x)
allocate(y(n))
! Statements !
y(:) = 1.0
z = x + y
deallocate(y)
return
end subroutine f
Observe que n
é deduzido da forma do parâmetro de entrada x
. Observe que y
é alocado e desalocado dentro do corpo da sub-rotina.
Quando eu compilar isso com f2py
f2py -c alloc_test.f90 -m alloc
E então execute em Python
from alloc import f
from numpy import ones
x = ones(5)
print f(x)
Estou tendo o erro a seguir
ValueError: failed to create intent(cache|hide)|optional array-- must have defined dimensions but got (-1,)
Então, eu vou criar e editar o pyf
arquivo manualmente
f2py -h alloc_test.pyf -m alloc alloc_test.f90
Original
python module alloc ! in
interface ! in :alloc
subroutine f(x,z) ! in :alloc:alloc_test.f90
real*8 dimension(:),intent(in) :: x
real*8 dimension(:),intent(out) :: z
end subroutine f
end interface
end python module alloc
Modificado
python module alloc ! in
interface ! in :alloc
subroutine f(x,z,n) ! in :alloc:alloc_test.f90
integer, intent(in) :: n
real*8 dimension(n),intent(in) :: x
real*8 dimension(n),intent(out) :: z
end subroutine f
end interface
end python module alloc
Agora ele roda, mas os valores da saída z
são sempre 0
. Algumas impressões de depuração revelam que n
tem o valor 0
dentro da sub-rotina f
. Suponho que estou perdendo alguma f2py
mágica de cabeçalho para gerenciar essa situação corretamente.
De maneira geral, qual é a melhor maneira de vincular a sub-rotina acima ao Python? Eu preferiria fortemente não ter que modificar a própria sub-rotina.
Respostas:
Não estou familiarizado com os elementos internos do f2py, mas estou familiarizado com o agrupamento do Fortran. O F2py apenas automatiza algumas ou todas as coisas abaixo.
Você primeiro precisa exportar para C usando o módulo iso_c_binding, conforme descrito, por exemplo, aqui:
http://fortran90.org/src/best-practices.html#interfacing-with-c
Isenção de responsabilidade: Eu sou o principal autor das páginas de fortran90.org. Essa é a única maneira independente de plataforma e compilador de chamar o Fortran do C. Este é o F2003, portanto, atualmente, não há razão para usar qualquer outro meio.
Você só pode exportar / chamar matrizes com o comprimento total especificado (forma explícita), ou seja:
mas não assume forma:
Isso ocorre porque a linguagem C não suporta essas matrizes em si. Fala-se em incluir esse suporte no F2008 ou posterior (não tenho certeza), e a maneira como funcionaria é através de algumas estruturas de dados C de suporte, pois é necessário transportar informações de forma sobre a matriz.
No Fortran, você deve usar principalmente a forma assumida, apenas em casos especiais você deve usar a forma explícita, conforme descrito aqui:
http://fortran90.org/src/best-practices.html#arrays
Isso significa que você precisa escrever um invólucro simples em torno da subrotina de forma assumida, que envolverá as coisas em matrizes de forma explícitas, conforme meu primeiro link acima.
Depois de ter uma assinatura C, basta chamá-la de Python da maneira que quiser, eu uso o Cython, mas você pode usar ctype ou C / API manualmente.
A
deallocate(y)
declaração não é necessária, o Fortran desaloca automaticamente.http://fortran90.org/src/best-practices.html#allocatable-arrays
real*8
não deve ser usado, masreal(dp)
:http://fortran90.org/src/best-practices.html#floating-point-numbers
A instrução
y(:) = 1.0
está atribuindo 1.0 com precisão única, portanto o restante dos dígitos será aleatório! Esta é uma armadilha comum:http://fortran90.org/src/gotchas.html#floating-point-numbers
Você precisa usar
y(:) = 1.0_dp
.Em vez de escrever
y(:) = 1.0_dp
, você pode simplesmente escrevery = 1
, é isso. Você pode atribuir um número inteiro a um número de ponto flutuante sem perder a precisão e não precisa colocar o redundante(:)
nele. Muito mais simples.Ao invés de
Apenas use
e não se incomode com a
y
matriz.Você não precisa da declaração "return" no final da sub-rotina.
Finalmente, você provavelmente deve estar usando módulos, basta colocar
implicit none
no nível do módulo e não precisará repeti-lo em cada sub-rotina.Caso contrário, parece bom para mim. Aqui está o código que segue as sugestões de 1 a 10 acima:
Ele mostra a sub-rotina simplificada, bem como um wrapper C.
No que diz respeito ao f2py, ele provavelmente tenta gravar esse wrapper para você e falha. Também não tenho certeza se ele está usando o
iso_c_binding
módulo. Por todas essas razões, prefiro embrulhar as coisas à mão. Então fica exatamente claro o que está acontecendo.fonte
y
mas queria que algo fosse alocado (meu código real tem alocações não triviais). Eu não sabia sobre muitos dos outros pontos. Parece que eu deveria procurar algum tipo de guia de práticas recomendadas do Fortran90 ... Obrigado pela resposta completa!Tudo o que você precisa fazer é o seguinte:
Embora o tamanho da matriz x e z agora seja passado como um argumento explícito, f2py torna o argumento n opcional. A seguir, é apresentada a docstring da função como ela aparece no python:
Importando e chamando-o de python:
fornece a seguinte saída:
fonte
n
e quero obter uma matriz de tamanho2 ** n
. Até agora eu tenho que passar também 2 ** n como um argumento separado.