11
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

FortranからPythonを使いたい!:forpy

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として

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のコードの中身を適当に書けば良い、ということになります。

もし返り値が必要な場合は、

mymodule.py
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を使えば良いようです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
11
Help us understand the problem. What are the problem?