FortranからPython、使いたいですよね?
Fortranで何かファイル出力とかディレクトリ操作とか大変ですので、そのようなFortranが苦手とすることはPythonに渡してしまいましょう。
例えば、二つのFortranコードを合体させたようなFortranコードの場合、Fortranのコードが何かのインプットファイルをテキストファイルとして吐き出して、それをインプットとするFortranサブルーチンを動かす、などがあります。インプットファイルをテキストファイルにするところがFortranは苦手だったりします。
あるいは、Fortranで数値計算をして、その結果を機械学習したいときにPythonを呼び出してTensorFlowとか使ったりしたいこともあるかと思います。
というわけで、forpyというものについて解説します。
これはFortranからPythonを呼び出せるようになるライブラリです。
環境
- macOS Catalina 10.15.7
- gcc version 10.2.0 (Homebrew GCC 10.2.0_4)
Pythonは3.7を使いました。すでにPythonは入っているとします。gfortranを使いましたが、ifortでも動くらしいです。
インストールと動作確認
git clone https://github.com/ylikx/forpy.git
でforpyを持ってきます。
自分の作業用ディレクトリを適当に作成して、その中にforpy_mod.F90をコピーしましょう。
以後はその作業用ディレクトリで作業します。
まず、ちゃんと動くかどうか確認するために、サンプルコードを動かします。
program intro_to_forpy
use forpy_mod
implicit none
integer :: ierror
type(list) :: my_list
ierror = forpy_initialize()
ierror = list_create(my_list)
ierror = my_list%append(19)
ierror = my_list%append("Hello world!")
ierror = my_list%append(3.14d0)
ierror = print_py(my_list)
call my_list%destroy
call forpy_finalize
end program
をintro_to_forpy.F90として保存します。
Python 3.7以前の場合は
gfortran -c forpy_mod.F90
gfortran intro_to_forpy.F90 forpy_mod.o `python3-config --ldflags`
Python 3.8以降であれば
# Python 3.8 and higher
gfortran -c forpy_mod.F90
gfortran intro_to_forpy.F90 forpy_mod.o `python3-config --ldflags --embed`
だそうです。
ただ、手元のMacではこれだと
gfortran test.f90 forpy_mod.o `python3-config --ldflags`
ld: library not found for -lintl
collect2: error: ld returned 1 exit status
となりました。これはgettextというものが入っていないか適切にリンクされていないと起きるようです。解決策はbrewで
brew install gettext
をしてから、
gfortran test.f90 forpy_mod.o `python3-config --ldflags` -L/usr/local/Cellar/gettext/0.21/lib
としてください。0.21の部分はご自分のgettextのバージョンに合わせてください。
コンパイルができたら、
./a.out
で実行すれば、
[19, 'Hello world!', 3.14]
とアウトプットが出ます。これで無事Pythonを呼べました。
自分で作ったPythonモジュールの使用
Hello worldまでできましたが、次は、自分で作ったPythonモジュールを呼び出す方法についてです。ここでは一番単純なものを紹介します。他の方法はリンク先にありますので見てください。
まず、Pythonのファイルをhellomodule.py
として
def helloworld():
print("hello world from Python!")
保存しておきます。
そして、
subroutine test2()
use forpy_mod
implicit none
type(module_py) :: hellomodule
type(list) :: paths
integer::ierror
ierror = forpy_initialize()
ierror = get_sys_path(paths)
ierror = paths%append(".")
ierror = import_py(hellomodule, "hellomodule")
ierror = call_py_noret(hellomodule,"helloworld")
call forpy_finalize
end
program intro_to_forpy
integer :: ierror
call test2()
end program
とすれば、Pythonのモジュールを呼べます。他にもPythonのリストとかタプルとかを扱う方法がリンク先には書かれてあります。
しかし、FortranからPythonを呼びたいときは、大抵Fortranだと書きにくいものを使いたいときですので、このように自分で作ったPythonのコードを呼ぶことができさえすれば、あとはPythonのコードの中身を適当に書けば良い、ということになります。
もし返り値が必要な場合は、
def print_args(*args, **kwargs):
print("Arguments: ", args)
print("Keyword arguments: ", kwargs)
return "Returned from mymodule.print_args"
program mymodule_example
use forpy_mod
implicit none
integer :: ierror
type(tuple) :: args
type(dict) :: kwargs
type(module_py) :: mymodule
type(object) :: return_value
type(list) :: paths
character(len=:), allocatable :: return_string
ierror = forpy_initialize()
! Instead of setting the environment variable PYTHONPATH,
! we can add the current directory "." to sys.path
ierror = get_sys_path(paths)
ierror = paths%append(".")
ierror = import_py(mymodule, "mymodule")
! Python:
! return_value = mymodule.print_args(12, "Hi", True, message="Hello world!")
ierror = tuple_create(args, 3)
ierror = args%setitem(0, 12)
ierror = args%setitem(1, "Hi")
ierror = args%setitem(2, .true.)
ierror = dict_create(kwargs)
ierror = kwargs%setitem("message", "Hello world!")
ierror = call_py(return_value, mymodule, "print_args", args, kwargs)
ierror = cast(return_string, return_value)
write(*,*) return_string
! For call_py, args and kwargs are optional
! use call_py_noret to ignore the return value
! E. g.:
! ierror = call_py_noret(mymodule, "print_args")
call args%destroy
call kwargs%destroy
call mymodule%destroy
call return_value%destroy
call paths%destroy
call forpy_finalize
end program
みたいにcall_py
を使えば良いようです。