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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
! 構造体定義
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(*,*) でさっくりと読み込めます。

1
2
3
4
5
6
7
8
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++の「参照(&)」に当たるものってあるんでしょうか?

1
2
3
4
5
6
7
8
9
10
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++ から呼び出すときは以下のコードで。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 を入れておくという方法もあるのですが、以下な構造体で扱いたい。

1
2
3
4
5
6
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 パーマリンク