LoginSignup
1
0

Python C拡張でFortranのClassを操る

Posted at

FortranでもSOLID原則を意識してみる

オブジェクト指向をするならSOLID原則が大事なんですね。今頃になって知ることができたので、今後はtype,abstract を効果的に使うことができそうです。なお、この記事では、計算速度のことは考えないことにします。

以下のコードは https://github.com/sakamoti/fortran_oop_from_python から抜粋したので、動かしたい人はcloneしてみてください。

抽象既定クラスの定義

インスタンスが作れない、型の情報だけをまず定義します。インスタンスを生成する際には、これを継承してメソッドを上書きする必要があります。

    type,abstract,public :: IShape
        !! Interface representing a generic shape.
        character(len=:),private,allocatable :: mytype
    contains
        !! abstractmethods
        procedure(IcalculateArea),pass,deferred,public :: calculatearea
        procedure(Ishowinfo),pass,deferred,public :: showinfo
    end type

    abstract Interface
        pure elemental function IcalculateArea(self) result(area)
            import IShape, c_double
            class(IShape),intent(in) :: self
            real(kind=c_double) :: area
            !! Caluculate the area of the shape.
            !!
            !! Returns
            !! -------
            !! c_double 
            !!     Area of the shape.
        end function
        subroutine Ishowinfo(self)
            import IShape
            class(IShape) :: self
            !! Show information.
        end subroutine
    end Interface

クラス実体の定義

Circle_tRectangle_tの2種類を定義しました。
また、クラスを生成するファクトリをインターフェースで作成することで、インスタンス生成時の引数の数でクラス実体が決まるようにしています。

    type,extends(IShape),public :: Circle_t
        real(c_double),private :: radius
    contains
        procedure :: calculatearea => calculatearea_circle
        procedure :: showinfo => showinfo_circle
    end type

    type,extends(IShape),public :: Rectangle_t
        real(c_double),private :: width, height
    contains
        procedure :: calculatearea => calculatearea_rectangle
        procedure :: showinfo => showinfo_rectangle
    end type

    !! Factory definithion ------
    public ShapeFactory
    interface ShapeFactory
        procedure :: circle__init__, rectangle__init__
    end interface

Fortranのメイン関数

class(IShape),allocatableとしているので、select typeを書かなくてよくなります。また、pure elementalを活用しているので、クラスの配列も簡単に生成できます。

program test_fort_main
    use, intrinsic :: iso_fortran_env , only : dp => real64
    use m_shape, only : IShape, Circle_t, Rectangle_t, ShapeFactory, &
                        test_circle_area, test_rectangle_area
    class(IShape),allocatable :: circle, rectangle
    class(IShape),allocatable :: circle_array(:), rectangle_array(:)
    integer :: i

    !! test
    call test_circle_area()
    call test_rectangle_area()

    !! scalar
    circle = ShapeFactory(radius=3d0)
    rectangle = ShapeFactory(width=2d0, height=3d0)
    !! show information
    print '("#--- scalar version ---")'
    call circle%showinfo()
    call rectangle%showinfo()

    !! generate class array like python's zip method
    allocate(circle_array, source = ShapeFactory(radius=[1d0,2d0,3d0]))
    allocate(rectangle_array, source = ShapeFactory(width=[1d0,2d0,3d0], height=[4d0,5d0,6d0]))
    !! show informations
    print '("#--- array version ---")'
    do i = 1, size(circle_array)
        print '("  -- array(",I0,") ---")',  i
        call circle_array(i)%showinfo()
        call rectangle_array(i)%showinfo()
    end do
end program

こういうFortranライブラリをPythonから使いたい

近年 iso_c_bindingの機能が拡充されてきていますが、そもそもC言語でクラスの概念が無いので、Fortranで作成したクラスをCで簡単に利用できるわけではありません。一方、Python C拡張ではクラスを実現することができます。その特徴として下記のような項目があげられます。

  • クラスは構造体として作成されるが、それらはすべてPyObjectになる
  • 整数、浮動小数点、リスト、辞書、関数、など、あらゆるデータ型がPyObjectとよばれる
  • 構造体成分にアロケータ、デアロケータなどの関数を指定する
  • メソッドを表現する特別な構造体を、クラスの構造体の成分として持つ

どのように説明すれば良いかわかりませんが、「クラスが無い言語で頑張ってクラスを実現している」という印象です。であれば、Fortranのクラスを上記のような要素に分解してPyObjectを作ることができれば、めでたくPythonからFortranクラスを利用することができるわけです。

実現方針

  • クラスのポインタをCのvoid*に変換
  • クラスのメソッドを呼び出す場合、void*からFortranのポインタへ変換した後にそのメソッドをFortranで呼び出す(fortranのiso_c_bindingを活用)

ただ、type,abstructを使って新しくPython側で子クラスを定義することは諦めます。(そもそもできるかどうかわかりません。できない気がする。)

ソースコード

Python C拡張はかなり面倒なので、GitHubのコードとPythonのドキュメントを眺めてください。(説明を書く余力がございませんでした。)

実行結果の例(Pythonから呼び出し)

<module 'custom' from '/home/test/fortran_oop_from_python/custom.so'>
<custom.FCircle object at 0x7890c61fe6d0>
 【 Hello from Fortran showinfo method 】
    type   : Circle_t
    radius :    1.0000000000000000     
    area   :    3.1415926535897931     
 【 Hello from Fortran showinfo method 】
    type   : Circle_t
    radius :    2.0000000000000000     
    area   :    12.566370614359172     
 【 Hello from Fortran showinfo method 】
    type   : Circle_t
    radius :    3.0000000000000000     
    area   :    28.274333882308138     

# ---- get area as PyObject ---
<class 'float'> 3.141592653589793
<class 'float'> 12.566370614359172
<class 'float'> 28.274333882308138

あとがき(感想)

PythonのC拡張を書くと、かなり細かいところまで制御できるようです。
念願の、Fortranクラスの相互運用もできることが分かったので満足。
ただ、かなり面倒。。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0