導入

PythonからFortranを呼ぶ #

1. 実行環境 #

種類 情報、バージョン コマンド
OS Windows 11 Home 64-bit, 24H2
CPU Intel Core Ultra 7 265K
CPU アーキテクチャ x86_64 uname -m (WSL)
Powershell 5.1.26100.3624 $PSVersionTable (PS)
Python Python 3.13.2 python --version (PS)
pip pip 24.3.1 pip --version (PS)
NumPy 2.1.3 python -c "import numpy; print(numpy.__version__)"(PS)
gfortran GNU Fortran (tdm64-1) 10.3.0 gfortran --version (PS)

※WSLはWindows Subsystem for Linux、PSはPowershellを意味します。

種類 情報、バージョン コマンド
Meson 1.7.2 meson --version (PS)
Ninja 1.11.1.git.kitware.jobserver-1 ninja --version (PS)

2. 全体の流れ #

2.1. 必要なツールのインストール #

  • meson, ninjaのインストール

f2pyはNumPy同梱されているため、基本的にNumPy をインストールするだけで利用可能となります。 本当にインストールされているかは、f2py -vまたはpython -m numpy.f2pyを実行してコマンドが通るかどうかを確認してください。

2.2. 全てのツールがうまくいった場合の流れ #

  1. Fortranコードのサブルーチンを書きます。
  2. f2pyコマンドを用いて、Fortranコマンドをコンパイルし、XXXX.pydファイルを生成します。
  3. PythonでXXXX.pydをimportし、呼び出します。
  4. Pythonを実行します。

3. 具体的な導入手順 #

3.1. Meson, Ninjaのインストール #

MesonとNinjaは、Pythonのf2pyがFortranモジュールをビルドする際に用いるビルド自動化ツールです。

  • Meson
    • Pythonで書かれた高生産性ビルドシステムで、複数言語(C/C++/Fortranなど)をサポートし、ビルドファイルを生成します​。
  • Ninja
    • Mesonの出力を受け取り、最小限の依存グラフ駆動で高速にコンパイル・リンクを実行する小規模ビルドシステムです​。

NumPyのf2pyはPython 3.12以上でMesonバックエンドを標準化しており、そのためMesonとNinjaが必須となります​。

MesonとNinjaはPython環境で以下のコマンドによりインストール可能です:

pip install --user meson ninja

インストール後、Meson と Ninja のパスは自動で追加されない(はず?)のでをユーザー環境変数のPathに手動で追加します。

%USERPROFILE%\AppData\Roaming\Python\Python3xx\Scripts

私の環境では、下記のパスを指定しています。

C:\Users\username\AppData\Roaming\Python\Python313\Scripts

3.2. 動作確認 #

3.2.1. ファイル構成確認 #

下記のように、適当なフォルダ以下に空のファイルadd.f90call_add.pyを用意します。

.
├── ./add.f90 (空ファイル)
└── ./call_add.py (空ファイル)

3.2.2. Fortranのコンパイル #

まずadd.f90を編集し、下記の内容を記載します。

! add.f90
subroutine add(a, b, c)
    implicit none
    real(8), intent(in) :: a, b
    real(8), intent(out) :: c
    c = a + b
end subroutine add

f2pyを利用して、コンパイルします。

f2py -c add.f90 -m addmodule
PS> f2py -c add.f90 -m addmodule
Cannot use distutils backend with Python>=3.12, using meson backend instead.
Using meson backend
Will pass --lower to f2py
See https://numpy.org/doc/stable/f2py/buildtools/meson.html
Reading fortran codes...

...(省略)

C:/Users/siki/AppData/Local/Programs/Python/Python313/Lib/site-packages/numpy/f2py/src/fortranobject.c:230:36: warning: too many arguments for format [-Wformat-extra-args]
  230 |         n = PyOS_snprintf(p, size, ",%" NPY_INTP_FMT, def.dims.d[i]);
      |                                    ^~~~
[6/6] Linking target addmodule.cp313-win_amd64.pyd
PS>

途中警告が出ますが、エラーではないので無視します。
完了すると、下記のようにaddmodule.xxxxxxxxx.pydファイルが生成されます。 環境によりますが、私の場合はファイルaddmodule.cp313-win_amd64.pydが生成されます。

この.pydファイルは謂わばdllファイルのようなものと考えて問題ありません。ここまででファイルは下記のようになります。

.
├── ./add.f90
├── ./addmodule.cp313-win_amd64.pyd
└── ./call_add.py(空ファイル)

3.2.3. Pythonからimport #

これまでの実行で次のようなファイル構成となっています。

.
├── ./add.f90
├── ./addmodule.cp313-win_amd64.pyd
└── ./call_add.py(空ファイル)

続いてPythonを書きます。call_add.pyに次のコードを書き保存します。

# call_add.py
import addmodule

# Fortranサブルーチン add を呼び出す
a = 1.5
b = 2.5
c = addmodule.add(a, b)
print(f"{a} + {b} = {c}")

3.2.4. Pythonの実行 #

Powershell上でPythonを実行します。すると、次のような結果を得ます。

> python .\call_add.py
1.5 + 2.5 = 4.0
>

Pythonにaddmoduleは実装していないので、PythonからFortranが実行できていることが分かります。

4. f2pyで書く時のFortranのルール #

  • Subroutineの場合
    • Fortran の subroutine において intent(out)(および intent(inout))と指定された引数は、Python 関数呼び出し時の 戻り値 として返される。
    • 返り値の順番はFortranソースの intent(out) 引数順序そのまま
  • Functionの場合
    • Fortran の function をラップ(f2pyでコンパイル) すると、第一要素が関数本体の返り値 となり、その後に intent(out) 引数が続けてタプル返却される。

ChatGPTで上記の回答を生成していますが、実際に実行してみると異なっている点があります。例えば配列の格納順などが説明と異なる場合があったりします。

そのため、どのように関数の順番などが指定されているかを確認するために、直接確認します。 具体的には、f2pyのコンパイルが通ればpython側で下記のように__doc__を参照すると使用方法がわかるため、これで確認するべきです。

import idxmod  # f2py -c idxop.f90 -m idxmod

print(idxmod.idxop.__doc__)
  • Fortranプログラム
! idxop.f90
subroutine idxop(n, a, b)
    implicit none
    integer, intent(in) :: n
    real(8), intent(in) :: a(n)
    real(8), intent(out) :: b(n)
    integer :: i
    do i = 1, n
        b(i) = a(i)*i
    end do
end subroutine idxop
  • Pythonプログラム 上記のプログラムをf2pyでコンパイル後、pythonを実行すると下記の結果を得ます。
> python .\call_idxop.py
b = idxop(a,[n])

Wrapper for ``idxop``.

Parameters
----------
a : input rank-1 array('d') with bounds (n)

Other Parameters
----------------
n : input int, optional
    Default: shape(a, 0)

Returns
-------
b : rank-1 array('d') with bounds (n)

関数の呼び出しには、

# 関数呼び出し時に n と a を渡す
b = idxmod.idxop(a=a, n=a.size)

のように全ての引数名を指定するとエラーを減らせると思います。