Fortranで複雑な構造体にファイルをロードする

もう少し本格的に Fortran と C++ の相互運用を試してみる。

Fortran に FEMDATA構造体とFEMFILE構造体を作る。FEMDATA構造体は、有限要素データを意識してidと頂点(x,y,z)と応力(qx,qy,qz)のデータを持つ。この頂点データを、FEMFILE構造体にまとめて持つことにする。FEMDATA構造体の最大数は固定長にしておいて、別途 count を持って実際の頂点数を決める。

! 構造体定義
module FEMMODULE
    implicit none
    type FEMDATA
        integer :: id                ! idenitry code
        double precision :: x,y,z    ! position
        double precision :: qx,qy,qz ! potision stress
    end type FEMDATA

    type FEMFILE
        character*10    filename    ! file name
        character*10    author      ! author name
        character*10    makedate    ! make datetime 'yyyy/mm/dd'
        integer :: count            ! data count
        type(FEMDATA) dat(1000)     ! data
    end type FEMFILE

    type(FEMFILE) fdata             ! inner data

contains

    ! read data file
    subroutine FEMREAD( fname ) 
        character(*) :: fname
        character*80 :: temp
        integer :: i, count
        open(10,file=fname,status='old')
        read(10,*) temp, fdata%author
        read(10,*) temp, fdata%makedate
        read(10,*) temp, fdata%count
        count = fdata%count
        print *,"count:", fdata%author, fdata%makedate, count
        do i=1,count
            read(10,*) temp, &
             & fdata%dat(i)%id, &
             & fdata%dat(i)%x, & 
             & fdata%dat(i)%y, & 
             & fdata%dat(i)%z, & 
             & fdata%dat(i)%qx,& 
             & fdata%dat(i)%qy,& 
             & fdata%dat(i)%qz  
        end do
        close(10)
    end subroutine FEMREAD

    ! write data file
    subroutine FEMWRITE( fname ) 
        character(*) :: fname
        integer :: i, count

        fdata%filename = fname
        open(10,file=fname,status='replace')
        write(10,*) "author ", fdata%author
        write(10,*) "makedate ", fdata%makedate
        count = fdata%count
        write(10,*) "count ", fdata%count
        do i=1,count
            write(10,"(A,I5,6E15.7)") "data", &
             & fdata%dat(i)%id, &
             & fdata%dat(i)%x, & 
             & fdata%dat(i)%y, & 
             & fdata%dat(i)%z, & 
             & fdata%dat(i)%qx,& 
             & fdata%dat(i)%qy,& 
             & fdata%dat(i)%qz  
        end do
        close(10)
    end subroutine FEMWRITE

    ! set dummy data
    subroutine FEMDUMMY()
        integer :: i
        
        fdata%filename = "sample.txt"
        fdata%author   = "t.masuda"
        fdata%makedate = "2012-05-02"
        fdata%count = 100
        do i=1,100
            fdata%dat(i)%id = i
            fdata%dat(i)%x = 1.0
            fdata%dat(i)%y = 1.0
            fdata%dat(i)%z = 1.0
            fdata%dat(i)%qx = 0.1
            fdata%dat(i)%qy = 0.1
            fdata%dat(i)%qz = 0.1
        end do
    end subroutine FEMDUMMY

end module FEMMODULE

データをファイルに書き出すのがFEMWRITE関数で、同じファイルから読み込むのがFEMREAD関数。いわゆる永続化処理。Fortranで書き出しファイルはFortran自身で読み出すのが良いのと、配列の実体はFortran上にあるのでこれをアクセスするのは、Fortran自身で行うのが良い、という主旨です。

で、データをGUIを使って突っ込むところはC++(MFC)でやったほうが良かろうということです。最近のIntel Fortran では windowsアプリケーションを作ることもできるようなのですが、まぁ、C++で作ったほうが便利。もっと云えば、C++/CLIを通して、C#を使うのもありかと。ただし、C#で作る場合は、相互運用部分のクラスを相当考え抜かないと解析スピードに難が出そうな気がします。

出力されるデータは、以下のような形です。C言語でも読み込めないことはないけど、コードを見て分かる通り Fortran の場合は、read(*,*) でさっくりと読み込めます。

 author t.masuda  
 makedate 2012-05-02
 count          100
data    1  0.1000000E+01  0.1000000E+01  0.1000000E+01  0.1000000E+00  0.1000000E+00  0.1000000E+00
data    2  0.1000000E+01  0.1000000E+01  0.1000000E+01  0.1000000E+00  0.1000000E+00  0.1000000E+00
data    3  0.1000000E+01  0.1000000E+01  0.1000000E+01  0.1000000E+00  0.1000000E+00  0.1000000E+00
data    4  0.1000000E+01  0.1000000E+01  0.1000000E+01  0.1000000E+00  0.1000000E+00  0.1000000E+00
... 以下 100 まで続く

構造体自体が入れ子になっているので、以下のように「%」が二回続く(C言語で言う「.」です)のがいやらしいところですね。Fortranでポインタを使っても良いけど…C++の「参照(&)」に当たるものってあるんでしょうか?

        do i=1,count
            read(10,*) temp, &
             & fdata%dat(i)%id, &
             & fdata%dat(i)%x, & 
             & fdata%dat(i)%y, & 
             & fdata%dat(i)%z, & 
             & fdata%dat(i)%qx,& 
             & fdata%dat(i)%qy,& 
             & fdata%dat(i)%qz  
        end do

これを C++ から呼び出すときは以下のコードで。

extern "C" {
	void FEMMODULE_mp_FEMDUMMY( void );
	void FEMMODULE_mp_FEMWRITE( const char *);
}

// 構造体の再定義
struct FEMDATA {
	int id ;
	double x,y,z;
	double qx,qy,qz;
};
struct FEMFILE {
	char filename[10];
	char author[10];
	char makedate[10] ;
	int count ;
	FEMDATA dat[1000];
};
extern "C" FEMFILE FEMMODULE_mp_FDATA;

int _tmain(int argc, _TCHAR* argv[])
{
	cout << "call fortran struct" << endl;

	FEMMODULE_mp_FEMDUMMY();
	FEMMODULE_mp_FEMWRITE("sample03.txt");

	FEMFILE &dat = FEMMODULE_mp_FDATA ;
	cout << "filename: " << FEMMODULE_mp_FDATA.filename << endl;
	cout << "count: " << dat.count << endl ;
	return 0;
}

実は、このコードは意図的にバグがあって、filenameとかauthorとかは null 終端ではないのですよ。なので、単純に、FEMMODULE_mp_FDATA.filename を出力すると、10文字以下の文字もずらずらと表示されてしまいます。なので、この部分を string に変えたいですね。Fortran側で、NULL 分の char を入れておくという方法もあるのですが、以下な構造体で扱いたい。

struct FEMFILEX {
	string filename ;
	string author ;
	string makedate ;
	vector<FEMDATA*> dat;
};

文字列のところは string に変換して、配列のところは vector に直します。カウンターはいらないので vector::size を参照ということにしたい。欲を言えば、これを C++ のクラスにしたいですね。

Fortran の構造体と C++ のクラスを相互運用させる時、自前でちまちま書けばできないことはないのでしょうが、それだと可搬性が悪くなるし、たくさんのFortran構造体を扱ったときに、それだけで作業量が膨大になってしまう。なので、実務的にFortarn/C の相互運用を考える場合、

  1. Fortran構造体をC構造体に逐一直す作業
  2. C構造体をstring/vectorを使ったC++構造体に直す作業

を考えないといけない。これを template か適当なスクリプトでやろうかなと。1のほうはperlスクリプトかマクロを使って手作業、2のほうはtemplateを使えると良いかなと、思案中。

カテゴリー: C++, Fortran パーマリンク