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. 全てのツールがうまくいった場合の流れ #
- Fortranコードのサブルーチンを書きます。
- f2pyコマンドを用いて、Fortranコマンドをコンパイルし、
XXXX.pyd
ファイルを生成します。 - Pythonで
XXXX.pyd
をimportし、呼び出します。 - 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.f90
とcall_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)
のように全ての引数名を指定するとエラーを減らせると思います。