Fortran


整数を文字列に変換するサブルーチン
intelフリーコンパイラのインストール手順
g77でコンパイルしたモジュールをgccから呼び出す
配列要素を一行に出力する
長さ不定の配列を渡す
データを変えながら同じ計算を何度も実行するPerlスクリプト
WRITE文の改行を抑制する
do文のカウンタからファイル名を自動生成する
Fortran90の文法


整数を文字列に変換するサブルーチン

DO文のカウンタからユニークなファイル名を生成し,ループの度に配列要素などを そのファイルに書き出したいことがある. DelphiのIntToStrのような関数がFortranで用意されていれば何の問題も無いのだが, Fortranでは可変長の文字列がサポートされていないので 整数を文字列に変換するのは単純にはいかない.

以下のサブルーチンINT2CHARは5ケタまでの整数を文字列に変換する. 変換後の文字列は右詰めとなり,左の余白は "0" (ゼロ)で埋められる. 例えば,整数の100を変換した場合は "00100" となる.

      SUBROUTINE INT2CHAR( NUM, CWRK )
      INTEGER NUM, J
      CHARACTER*5 CWRK
C
      CWRK = '     '
      WRITE( CWRK, '( I5 )' ) NUM
      DO J = 1, 5
          IF ( CWRK( J:J ).EQ.' ' ) CWRK( J:J ) = '0'
      END DO
      RETURN
      END


intelフリーコンパイラのインストール手順

intelのサイトから RedHat Linux 6.2または7.1に対応したフリーのFortranコンパイラがダウンロードできる (フリーのC/C++コンパイラもある). コンパイル時のオプションを設定すれば,AthlonでもMMX命令などが使えるらしい (CyrixIIIではインストールもできなかったのはここに書いた通り). Athlon 1.4GHzのマシンにインストールしてみたので,その方法をまとめておく.

  1. 適当なディレクトリでダウンロードしたファイル(RedHat7.2向けならf010821rh71.tar)を展開する.
    % tar xvf f010821rh71.tar
    
  2. 展開したディレクトリでスクリプトinstallを実行する.
    % ./install
    RPM shows no Intel packages as installed.
    Which of the following would you like to install?
    1. Intel(R) Fortran Compiler for the Itanium(TM)-based applications, Version 5.0.1, Build 20010705
    2. Intel(R) Fortran Compiler for 32-bit applications, Version 5.0.1 Build 010730D0
    3. Linux Application Debugger, Version 5.0.1, Build 20010720.
    x. Exit.
    
  3. 上のメッセージが表示されるので2を選ぶ. インストールするディレクトリなどを聞いてくるが,すべてデフォルトのままでOK(そのままenterを押す).
  4. /etc/ld.so.confに以下の一行を追加する.
    /opt/intel/compiler50/ia32/lib
    
    追加したら,ダイナミックリンクライブラリのキャッシュを再構築するために, /sbin/ldconfigを実行する. このときいくつかwarningが表示されるが無視してよい.
  5. ダウンロードの際に入力したe-mailアドレスにライセンス設定ファイルl_for.licが添付された メールが送られてくる. このファイルを/opt/intel/licensesにコピーする.
  6. 以下のようにリンクを張る(この設定は重要! 最初これを忘れてコンパイルができなかった).
    % cd /usr
    % ln -s /opt/intel/compiler50 intel
    
  7. /opt/intel/compiler50/ia32/binにパスを通し, 環境変数LM_LICENSE_FILEを/opt/intel/licenses/l_for.licに設定する. シェルがbashなら,ホームディレクトリの.bash_profileに 以下のような記述を追加すればよい.
    PATH=$PATH:/opt/intel/compiler50/ia32/bin
    LM_LICENSE_FILE=/opt/intel/licenses/l_cpp.lic:/opt/intel/licenses/l_for.lic
    export PATH LM_LICENSE_FILE
    
以上でコンパイラが使えるようになる. 使い方は
% ifc xxxx.f
である.


g77でコンパイルしたモジュールをgccから呼び出す

g77でモジュール(オブジェクトファイル)を作成するには,

% g77 -c jh2o.f
のようにすれば良い.同じディレクトリにjh2o.oが作成される. この中の関数をgccから利用する場合,以下の点に留意してプロトタイプ宣言を書かなければならない.
  1. 引数はすべて参照渡しとなる.
  2. 関数名はすべて小文字になり,終わりに "_" (アンダースコア)が付加される
例えばjh2o.fのKPAMESとPSTを呼び出すならば,
void kpames_( int* kpa, int* mes );
float pst_( float* t );
のように書く.以下は実際のコード例である.
#include < stdio.h>

void kpames_( int* kpa, int* mes );
float pst_( float* t );

int main(){

   int kpa, mes;
   float t, ps;

   kpa = 1; mes = 1;
   kpames_( &kpa, &mes );

   printf( "input t\n" );
   scanf( "%g", &t );
   ps = pst_( &t );
   printf( "ps = %10.5f\n", ps );

   return 0;

}
リンクは以下の手順で行う.
% g77 -c jh2o.f
% gcc -c h2omain.c
% g77 h2omain.o jh2o.o
二つのオブジェクトファイルをリンクする際にはg77を用いることに注意


配列要素を一行に出力する

例えば要素数10の整数型配列ARRAYがあるとする. この配列の全要素を縦一列に出力するときは,

DO I = 1, 10
   WRITE(*, '( I3 )') ARRAY( I )
END DO
のように書けばよい.横一列に出力したいときは,
WRITE(*, '( 10I3 )') ARRAY( 1 ), ARRAY( 2 ), ...
のように配列要素を羅列してもいいが,要素数が多いときは面倒である.

このようなときは,次のように書けばよい.

WRITE(*, '( 10I3 )') ( ARRAY( I ), I = 1, 10 )
1番目から10番目までの要素にはそれぞれ1から10までの整数が格納されているとすれば, 出力は以下のようになる
  1  2  3  4  5  6  7  8  9 10

長さ不定の配列を渡す

引数が配列であっても,実際に関数やサブルーチンに渡されるのはその配列へのポインタ(先頭要素へのポインタ)である. また,関数やサブルーチン側で要素数の範囲チェックは行われないようだ(コンパイラオプションで範囲チェックを行う ように設定できるのかも知れない).

したがって,関数やサブルーチン側で仮引数の配列の要素数を適当に(例えば1として)宣言しておき, 実際の配列の長さを整数型の引数として別に渡せ,どのような長さの配列でも渡すことができる. 例えば,受け取った配列の総和を返す関数は以下のように定義できる.

      INTEGER FUNCTION SUM_OF_ARRAY( THEARRAY, NELEMENTS )
      INTEGER THEARRAY( 1 ), NELEMENTS, SUM, I
C
      SUM = 0
      DO I = 1, NELEMENTS
         SUM = SUM + THEARRAY( I )
      END DO
      SUM_OF_ARRAY = SUM
      RETURN
      END
この関数では,配列の実際の長さは引数NELEMENTSで渡される. また,配列要素数はCOMMONブロック経由で渡してもよい.

このコードはWindows上のVisual Fortran 5.0とLinux上のg77およびIntel Fortran Compilerで問題なくコンパイルできた. ただし,g77では仮引数の配列要素数を0とするとコンパイルエラーになる(他の二つはそれでもOK).


データを変えながら同じ計算を何度も実行するPerlスクリプト

ある研究ではニューラルネットワーク理論に基づいた解析を行っている. その研究のために書いたコードは,以下のようなマトリックス形式のデータを入力として読み込む.

   1256  8956  5468  1282  ....  6715
   6385  2397  8584  2383  ....  9784
   6525  4887  8789  3258  ....  4568
   ....  ....  ....  ....  ....  ....

   9684  2568  7458  3695  ....  1986
各行が一つのレコードである. 読み込まれた全レコード数がnであった場合,1番目から(n-1)番目までがネットワークの学習に 使われ,n番目のレコードは学習後のネットワークの評価に用いられる.

研究が進展するにつれ,学習用と評価用のレコードをいろいろ入れ換えて 計算を行う必要が出てきた. 全レコード数がnならばn通りの入れ換え方があるが.入力ファイルをいちいちエディタで 書き直すのは非能率的である. 元々のコードにレコードの順序入れ換えルーチンを追加しても良いのだが, 妥当性が確認されているコードには手を入れたくないのが人情だ.

こういう処理はPerlが得意とするところだろう. 幸い,計算を走らせるワークステーションはPerlが動く環境にある. Perlは,以前cgiを書くときにちょこっと使ったことがあるだけだが,ドキュメントと格闘しながら なんとか動くものを作ってみた.

#!/usr/bin/perl -w

# データファイル sample.dat の各行を配列 data に読み込む.
# count は行数をカウントするための変数.
open( FILEHANDLE, "< sample.dat" );
$count = 0;
until( eof FILEHANDLE ) {

   $data[ $count ] = readline( FILEHANDLE );
   $count++;

}
close( FILEHANDLE );

# 行数だけ繰り返す.trial は繰り返し回数を表す変数.
for ( $trial = 0; $trial < $count; $trial++ ) {

   # trial を文字に変換して number に代入
   $number = sprintf "%d", $trial + 1;
   print "trial = " . $number . "\n";

   # 配列 data を入力ファイル input.dat に書き出す
   open( FILEHANDLE, "%gt input.dat" );
   print FILEHANDLE @data;
   close( FILEHANDLE );

   # 計算コードを実行 
   system "./ann.out";

   # 出力ファイルをリネーム
   rename "output.dat", "output_" . $number . ".dat";

   # 配列をシフトし,先頭レコードを末尾に追加
   $thedata = shift( @data );
   $data[ $count ] = $thedata;

}
特筆すべきはshift関数であろう. この関数の引数に配列を渡すと,最初の値をシフトして取り出し,その値を返す. その結果,配列は1要素分だけ短くなり,他のすべての要素が先頭に向かって一つずつずれる. 今やりたいことがこれ一つで済んでしまう. あとは,取り出した要素を配列の末尾に追加するだけである. これと同じことをCやFortranで書くのは結構面倒だ.やはりPerlはこのあたりが上手である.

また,コードを実行するときは必ずsystem関数を使わなければならない.exec関数も systemと同じく子プロセスを起動する関数であるが,こちらは子プロセスが終了するまで待たずに 制御を戻す. 一方,systemは子プロセスが起動してから終了するまで待機する.

コードが終了したら,出力ファイルを適当にリネームする. これをしないと出力ファイルが上書きされてしまう.


WRITE文の改行を抑制する

単純にWRITE文を実行すると無条件に改行されてしまう. 以下のプログラムは掛け算九九を出力するプログラムであるが, WRITE文の改行の抑制と改行のみの出力を用いている.

      PROGRAM MAIN
      PARAMETER( NROW = 9, NCOL = 9 )
      INTEGER L( NROW, NCOL )
C
      CALL SUB1( L, NROW, NCOL )
      CALL SUB2( L, NROW, NCOL )
C
      STOP
      END
C
C
      SUBROUTINE SUB1( L, NROW, NCOL )
      INTEGER L( NROW, NCOL )
      DO I = 1, NROW
          DO J = 1, NCOL
             L( I, J ) = I * J
          END DO
      END DO
      RETURN
      END     
C
C
      SUBROUTINE SUB2( L, NROW, NCOL )
      INTEGER L( NROW, NCOL )
      DO I = 1, NROW
          DO J = 1, NCOL
             WRITE( *, '( I4, $ )' ) L( I, J )
          END DO
          WRITE( *, '( 1H )' )
      END DO
      RETURN
      END
つまり,改行を抑制するには出力フォーマットの末尾に$を追加し, 改行のみ出力する場合は単に1Hのみを指定する. 出力結果は以下のようになる.
   1   2   3   4   5   6   7   8   9 
   2   3   4   5   6   7   8   9  18 
   3   4   5   6   7   8   9  18  27 
   4   5   6   7   8   9  18  27  36 
   5   6   7   8   9  18  27  36  45 
   6   7   8   9  18  27  36  45  54 
   7   8   9  18  27  36  45  54  63 
   8   9  18  27  36  45  54  63  72 
   9  18  27  36  45  54  63  72  81 

do文のカウンタからファイル名を自動生成する

do文の1回のループ毎に結果を異なるファイルに出力したい場合, 以下のコードのようにファイル名を自動生成すればよい.

program trimtest

integer i
character*5 c

 c = "     "
 do i = 1, 20
   write( c, '( I2 )' ) i
   c = adjustl( c )
   open( 1, file = "dat_" // trim( c ) // ".txt", status = "new" )
   write( 1, * ) i
   close( 1 )
 end do
 stop
 end
このコードを実行すると,dat_1.dat,dat_2.dat,...,dat_20.dat というファイルが自動的に作られる. ここ に書いた要領で整数を文字列に変換し, ADJUSTLで文字列を左寄せにした後,TRIMで後の空白を取り除く. TRIMは後の空白しか取ってくれない.

ちなみに,文字列を数値に変換するには以下のようにすればよい.

real x
integer i
character*50 text

text = "12345.67"
read( text, * ) x
text = "10"
read( text, * ) i
write( *, * ) x, i
これを実行すると画面に "12345.67 10" のように表示される.
Fortran90の文法

近ごろFortran90でコードを書く必要に迫られいろいろと試行錯誤しているので,ここに備忘録としてまとめておく. あくまで私的な備忘録なので認識間違いがあるかも知れない.記述は順次追加する. 参考になるサイトとして,

等がある.

書式

Fortran90では6カラムや72カラムの呪縛が無い自由形式でのコーディングができる. Visual Fortran(以下VF)では拡張子が ".f90" だと自由形式コードと見なされる. 自由形式の場合,コメント行は "!" (エクスクラメーション)で始める. また,72カラムの制限が無いとは言っても1行は132文字までらしい. 記述が次の行まで及ぶ場合は,末尾に "&" (アンパサンド)を付ける.

 temp = temp + weight( neuron_input + neuron_hidden + k, neuron_input + j ) & 
            * signal_out( index, neuron_input + j )
Fortran90はケースセンシティブではないが, サンプルコードを見るとC調に小文字で書くのが流行りのようだ.

ポインタ

Fortran90では明示的にポインタが使えるようになっている. 宣言した変数がスカラではなくポインタであることはpointer属性を指定することで行う. 例えば整数へのポインタを宣言する場合,以下のようにする.

 integer, pointer :: p
また,すでに型宣言をした変数に後からpointer属性を与えてもよい.
 integer p
 pointer :: p
面倒なのは,ポインタの参照先となれるのが,同じ型でかつtarget属性が指定された変数に限ることである. ポインタとスカラ変数のリンクは "=>" によって行う. 例えば,以下のコードはコンソールに "10" を出力する.
 program ptest
 integer, pointer :: p
 integer, target :: i
 i = 10
 p => i
 write( *, * ) p
 stop
 end
つまり,Fortran90のポインタには逆参照の概念が無く,一旦リンクが確定すればリンク先のスカラ変数と等価となる. ちなみに,上のコードで変数iの宣言を単に
 integer i
とするとコンパイル時に "ポインタとリンクする変数にはTARGET属性が必要です" という旨のエラーが出る.

さらに問題を複雑にするのがVFの拡張構文であるpointer文で,これは "=>" を使わずにポインタと変数をリンクさせるものだ. 例えば以下のように書いた場合,qは整数型変数jを差すポインタとなる.

 integer j
 pointer ( q, j ) 
ところが,いきなりjに値を代入するとaccess violationになる. オンラインドキュメント によると, pointer文でポインタとリンクされた変数に独自の記憶域は割り当てられない. qにはjと同じ型であればtarget属性が無い変数でもそのアドレスを代入することができる. また,qに有意なアドレスが入っている場合はjに対する代入が可能で,それはqの参照先への代入となる. 例えば,
 integer j, k, l
 pointer ( q, j ) 

 k = 5
 l = 6
 q = loc( k )
 write( *, * ) j
 q = loc( l )
 write( *, * ) j
 j = 2
 write( *, * ) l
とすると,順に5,6,2と表示される. 同じことをCでやろうとすれば,
 int k, l;
 int* q;
 
 k = 5;
 l= 6;
 q = &k;
 printf( "%d\n", *q );
 q = &l;
 printf( "%d\n", *q );
 *q = 2;
 printf( "%d\n", l );
となるところだ. まとめると,VF拡張のpointer文はポインタと逆参照用変数を同時に宣言するものであり, 宣言したポインタには逆参照用変数と同じ型のアドレスなら何でも代入可能だ. 逆参照用変数への代入はポインタが有意であるときのみ可能である.

Compaqがこのような拡張構文を追加したのは,おそらくWindows APIの呼び出しを可能にするためだろう. APIを駆使したシステムプログラミングでは,OSの管理下にあるオブジェクトへのアドレスを取得する機会が度々ある. そのようなオブジェクトは型のみが既知であるから, ユーザ側で同型の読み出し専用変数を定義し, 取得したアドレスを代入するポインタとその変数をpointer文でリンクさせておけばオブジェクトの読み出しが可能になる仕組みだ. 実際,pointer文ではポインタのリンク先に関数型を指定することもできるので, LoadLibraryとGetProcAddressを使ってDLL内関数を呼び出すことができる(というかこれしか方法が無い).

モジュール

モジュールとは独立した一つのコード単位であり,型,変数,関数(サブルーチン)の定義を含むことができる. モジュールを使う目的として,ブロック間の独立性を高める,グローバル変数の参照を簡略化する,等がある. Delphiのユニットに似た概念であるが,ユニットがファイル毎に管理されるのに対し, モジュールは一つのファイルに二つ以上定義することが可能だ.

一つのモジュールは大まかに言って次のような格好をしている.

 module foo
 
 use bar
 
 (型の定義や変数の宣言)
  
 contains
 
 (関数やサブルーチンの定義)
 
 end module foo
他のモジュールで定義されている識別子を参照するときは,そのモジュール名をuse以下に書く. この点はDelphiのuses節と同じだ. ただし,use以下には一つのモジュール名しか書けないので,複数のモジュールをuseする場合はその数だけ "use xxxx" と書く必要がある.

あるモジュールに定義されている特定の関数のみ参照したい場合はonly属性が役に立つ. 例えば以下のように使う.

use dfwin, only: LoadLibrary, FreeLibrary, GetProcAddress
dfwinはWindows APIが定義されたモジュールであるが, ここではその中から LoadLibrary,FreeLibraryおよびGetProcAddressのみを参照している.

一つ注意しなければならないのは,useするモジュールはバイナリコードに限るということである. つまり,モジュールAがモジュールBをuseしているとき, Aをコンパイルするにはその時点でBがコンパイル済みでなければならない. "モジュール" とはコンパイル済みのバイナリコードを差す名称で, 上の例のように書いたコードはあくまで "モジュールのソースコード" に過ぎない. 筆者はこれでハマった. Delphiの場合,ソースでもバイナリでも "ユニット" である(拡張子は異なる.ソースは ".pas",バイナリは ".dcu" ). コンパイル時には列挙された全ユニットのソースとバイナリのタイムスタンプが調べられ, ソースがバイナリよりも新しくなっていればリコンパイルしてくれる.

モジュールの中だけで有効な変数と外部からも参照可能な変数を区別したい場合はprivate属性およびpublic属性を使う. どちらも指定されていない場合,その変数はpublicと見なされる. ある変数をprivateにしたい場合は,モジュールの変数宣言部で

integer, private :: p
のように書く. (以下作成中)
お問い合わせはメールにて: akasaka@klc.ac.jp

戻る
SEO [PR] 爆速!無料ブログ 無料ホームページ開設 無料ライブ放送