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 | コメントをどうぞ

FortranとC++のstringクラスでやり取りする

このエントリーをはてなブックマークに追加

FortranではC言語の文字列(char[])位のやり取りをするついでに、C++のstringクラスとのやり取りも実験的に。
今回、画面側がMFCなので、CStringを使うかstringを使うか悩むところなのですが、まぁSTLをごりごり使うという前提でstd::stringを使ってみます。

module strmodule
    implicit none
contains
    ! 文字列を受け取る
    subroutine fstr1( str )
        character(*) :: str
        print *, "in fstr1:", str
    end subroutine fstr1
    ! 文字列を返す
    subroutine fstr2( str )
        character*80 :: str
        str = "masuda tomoaki"
    end subroutine fstr2
end module strmodule

Fortran側の module は、「character(*)」で文字列を受け取ります。逆に文字列を渡す場合には「character*80」のようにサイズを指定します。Fortranだけでで文字列を表示するときはいいのですが、C言語とやり取りする時にはうしろの空白が面倒なのです。Fortranの場合は、空きの分は空白で埋めてくれます。

extern "C" {
	void STRMODULE_mp_FSTR1( const char *str, int length );
	void STRMODULE_mp_FSTR2( char *str, int length );
}
// 文字列を設定
void FSTR1( string &str )
{
	STRMODULE_mp_FSTR1(str.c_str(), str.length());
}
// 文字列を取得
string FSTR2()
{
	static char buf[1000] = {0}; // 大きめのバッファ
	STRMODULE_mp_FSTR2( buf, sizeof(buf));
	string str = buf;
	int e = str.find_last_not_of(" ");
	if ( e != string::npos ) {
		str = string( str,0, e+1 );
	}
	return str;
}

なので、std::stringから、Fortranの関数に渡す時には長さを指定して、逆に受け取る場合には string::find_last_not_of で後ろの空白を削除する(trimする)という定番処理が必要になります。

	// string クラスとの相互運用
	string str = "masuda tomoaki";
	// そのまま fortran に渡す
	cout << "call FSTR1" << endl;
	FSTR1( str );
	// fortran から受け取る
	str = FSTR2();
	cout << "call FSTR2:[" << str << "]" << endl;

文字列の場合は、この変換が必要なので適当なマクロを作るか、関数を作るかするかなぁと。char[] のまま C++ で扱うのは厄介なので(今回、STLを使ってよいという話なので)、このあたりは適当に隠蔽したいところ。

本来ならば、unicode を気遣う必要があるのですが、今回は英語版なので...まあ良しとしましょう(ってのがi18nの問題でもあるのだけど、既にあるコードが string を使っているからね。Unicode に気を遣う場合は、MFC の時は CString を使うのが良いです)。

カテゴリー: C++, Fortran | コメントをどうぞ

Fortranでファイルの読み書きRead/Writeする

このエントリーをはてなブックマークに追加

Fortran入門: データ入出力
http://www.nag-j.co.jp/fortran/FI_14.html
format文 (Fortranプログラミング入門マニュアル | Fortran プログラミング 入門 講座)
http://harukin.la.coocan.jp/fortran/004/format.html

Fortranのファイル読み書き&フォーマット文に関しては上記を参照。
ただし、Fortran77 の時もそうだったのだが、Fortran で書き出して、Fortran で読み込む分にはさほど書式に気を遣わなくていい...というか、適当に write(*,*) で書き出して、read(*,*) で読み込めるってのが Fortran の便利なところ。

program Console1
    implicit none
    integer count
    integer readdata
    character*80 fname

    ! ファイル出力
    open(10,file='sample.txt',status='replace')
    write (10,*) 'Hello', x, y
    close(10)
    print *, 'write sample.txt'

	! 一応初期化
    x = 0
    y = 0

    ! ファイル入力
    open(11,file='sample.txt',status='old')
    read(11,*), str, x, y
    close(11)

    print *, 'read sample.txt'
    print *, 'read:', str, x, y

    fname = 'sample2.txt'
    count = readdata(fname)
    print *,'read count:', count
end program Console1

フォーマットを決めずに write した後に read する例。単純に、変数を並べて write すれば良しなにしてくれるので非常に便利です。固定のデータ数や配列の場合には、ちまちま書くか、do ループを使って固定数分読み書きすればよい。

    ! データを終端 "END" まで読み込む
    integer function readdata(fname)
        implicit none
        ! character*80 :: fname
        character(*), intent(in) :: fname
        integer :: count
        character*20 :: mark
        integer :: x, y
        count = 0

        print *,'fname:', fname
        open(10,file=fname,status='old')
        do
            read(10,*,end=100), mark, x, y
            count = count + 1
            if ( mark == 'end' ) then
                exit
            end if
            print *, 'read:',x,y
        end do
100     close(11)
        readdata = count
    end function readdata

この do ループの部分は定番です。read の時に end=100 のように行番号で飛ばすのは、Fortran90 のスタイルではどうなのだろう?と思うのですが、ひとまずこれで。ファイル番号を付けるのは、VB6 と同じです。適当にダブらない番号を付ければ OK です。ただし、サブルーチンからサブルーチンを呼び出すときに、このファイル番号がダブった場合どうなるのかは不明。なので、open/close 自体はちょっと注意しないと。

このサンプルでは、文字列の先頭が「end」で終わったときに終端と判断していますが、数値が「-1」の時というのも定番な動作です。C++の場合は、1行読み込んだ後に、先頭が「end」なのか、有効な数字なのかという判断をしますが、Fortranの場合は、文字列を数値を混在させるのは面倒なので、最初のカラムが数値ならば「-1」で、文字列にするならば「end」でという使い分けをしたほうが良いでしょう...と fortran90 初心者は思う訳です(fortran77は昔やったことがあるけど)。「end」を終端にする場合は、先頭のカラムの文字列は捨てカラムにするのが良いですね。

data	10	21
data	11	22
data	12	23
data	13	24
end

のように、先頭のカラムを捨ててしまうわけです。実は、これが便利なのは先頭が「data」の時はデータ行をあらわして「end」の場合は終端、という形で1行を読み込んだときに判断が付くところです。perl で処理する時に楽だったりします。大学の頃は、フォーマットの前処理を perl でやって、最終的に fortran で読み込ませる方法を取っていました。

カテゴリー: Fortran, 開発 | コメントをどうぞ

FortranにC++から文字列を渡す

このエントリーをはてなブックマークに追加

文字列の受け渡しはなるべくしたくないのだが、設定やら名称取得やらで必要になってくる。
Fortran の場合は、NULL terminate ではないのでC++で扱うときにはちょっと厄介である...そういえば、stringクラスを使うことになるので、そのあたりも考えないと。
基本は、固定領域をとっておいて、あとからC++側でnullを付け加えるとok。

module structmodule
    implicit none
contains
    ! 文字列を受け取る
    subroutine fstr1( s )
        character*20 s
        print *, 'in fstr1:[', s,']'
    end subroutine fstr1
    ! 文字列を渡す
    subroutine fstr2( s )
        character*20 s
        integer i
        ! * で埋める
        do i=1,10
            s(i:i) = '*'
        end do
    end subroutine fstr2
    ! 文字列を渡す
    subroutine fstr3( s )
        character*20 :: s
        s = 't.masuda'
    end subroutine fstr3
end module structmodule

文字列を引数にすると、C++の関数では長さを定義する変数が入る。これは数値の変数とは違って、値型で渡す。

extern "C" {
	// 文字列を渡す
	void STRUCTMODULE_mp_FSTR1( char *str, int count );
	// 文字列を受け取る
	void STRUCTMODULE_mp_FSTR2( char *str, int count );
	void STRUCTMODULE_mp_FSTR3( char *str, int count );
}
	char str1[20+1] = {0};
	strcpy( str1, "t.masuda" );
	STRUCTMODULE_mp_FSTR1( str1, sizeof(str1)-1);

	char str2[20+1] = {0};
	STRUCTMODULE_mp_FSTR2( str2, sizeof(str2)-1);
	cout << "str2:[" << str2 << "]" << endl;

	char str3[20+1] = {0};
	STRUCTMODULE_mp_FSTR3( str3, sizeof(str3)-1);
	cout << "str3:[" << str3 << "]" << endl;

戻り値で扱うことができるかは不明。引数で渡すほうが無難だろう。
string型との変換を考えると、適当なマクロをC++側に仕込むのがよかろう。

カテゴリー: C++, Fortran, 開発 | 1件のコメント

FortranにC言語から固定配列を渡す

このエントリーをはてなブックマークに追加

科学計算の場合、行列式や1次元配列、2次元配列が頻繁に出てくる...と思われる。
Fortran の場合、あまり可変配列を使わずに、あらかじめメモリ領域を確保する固定配列を使う...ってそうしていたのだが、最近はどうなのだろう。可変にするとメモリの確保に時間がかかってスピードに問題がでるので、固定長さの配列のほうが有利。

module structmodule
    implicit none
    ! struct define
    type F3D
        double precision :: x,y,z
    end type F3D
contains
    ! one array
    integer function sum1( v, count )
        integer :: v(10)
        integer :: i, total
        integer :: count

        total = 0
        do i=1,count
            print *, 'in sum1: ', i, v(i)
            total = total + v(i)
        end do
        sum1 = total
    end function sum1
    ! 二次元配列を渡す
    integer function sum2( v, cnti, cntj )
        integer :: v(3,4)
        integer :: cnti, cntj
        integer :: i,j
        integer :: total

        total = 0
        do i=1,cnti
            do j=1,cntj
                print *,i,j,v(i,j)
                total = total + v(i,j)
            end do
        end do
        sum2 = total
    end function sum2
end module structmodule

引き数で配列の数を渡しているが、内部で固定にしかならないので、この場合はあまり意味がない。
最大長をチェックしてオーバーフローにならないように気を付けるとか。

extern "C" {
	// 1次元配列
	int STRUCTMODULE_mp_SUM1( int ary[], int *count );
	// 2次元配列
	int STRUCTMODULE_mp_SUM2( int ary[][3], int *cnti, int *cntj );
}
	int v1[] = {1,2,3,4,5,6,7,8,9,10};
	int count = 10;
	int sum1 = STRUCTMODULE_mp_SUM1( v1, &count );
	cout << "ans:" << sum1 << endl;

	int v2[4][3] = {
		{1,2,3},
		{2,3,4},
		{3,4,5},
		{4,5,6},
	};
	int cnti = 3, cntj = 4;
	int sum2 = STRUCTMODULE_mp_SUM2( v2, &cnti, &cntj );
	cout << "ans:" << sum2 << endl;

注意しないといけないのは、Fortranは「1」始まりで、C++は「0」始まりということ。なので、Fortranで「3」番目にアクセスをすると、C++では添え字の「2」にアクセスする(0始まりなので、3番目の要素になる)。配列そのものを扱う場合には気にならないが、index を渡す場合には注意が必要。
Fortranのほうを0始まりにできるオプションもあるらしいのだが、元のソースに合わせるとすると標準的な1始まりの配列を扱うほうがよかろう。

あと、2次元配列の場合は、次元が逆のように見える。これは、Fortranのほうにあわせて作る。

カテゴリー: Fortran, 開発 | コメントをどうぞ