Fortran によるプログラミング超入門 2009/03/07 はじめに 物理や工学における数値計算では Fortran 言語でプログラムを書くことが多い。最初の数値計 算用高級言語である Fortran には,プログラムライブラリーや書籍の形で多くの公共財産が蓄積 されたことが大きい。Fortran も,ゆっくりと改良がおこなわれ,現在の標準は Fortran 95 と呼 ばれているものであり,1997 年に ISO 国際規格になった。日本の JIS 規格はこれの翻訳である。 Fortran 95 では,大規模なプログラム開発の効率化や,数値計算のための機能強化がおこなわれ, 数値計算プログラムが楽に作れるようになった。 このテキストはプログラミングの入門であるので,Fortran 95 の機能から,容易に理解ができ る基本的な部分だけを使用する。しかし,モジュール機能など,小さいプログラムには不必要なも のは出てこない。 プログラムの編集や実行のしかたは環境によって多少異なる。ここでは,UNIX 系システムで のコマンド操作によってプログラムの実行などをおこなうことを想定している。プログラムの編集 には emacs を使い,グラフを表示するために GNUPlot を使い,画像表示のために ImageMagick または GraphicsMagick を使うことにする。これは tutorial であるので,コマンドやプログラムを 実行するとどういう結果が得られるかはほとんど書いていない。したがって,実際にやってみなけ ればならない。そのために必要な準備のしかたは,最初に出てくる。 なお、この文書の PDF ファイルには,文中のソースプログラムなどが添付ファイルとして含 まれている (ファイル名が多少異なるものもある)。Adobe Reader(acrobat) には添付ファイル を保存する機能がある。また,pdftk というプログラムがある場合は,端末で PDF ファイル ( Intro_fortran.pdf) があるディレクトリに移り,次のコマンドで全ファイルを取り出すことがで きる。 pdftk Intro_fortran.pdf unpack_files Windows のメモ帳ではこれらのファイルは正常に表示されないので,プログラミングに適したテ キストエディタをインストールする必要があるかもしれない。Linux などでは問題ない。 目次 1 2 準備 1.1 1.2 コンパイラー . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Emacs エディター . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 4 4 1.3 その他のツール . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 はじめの一歩 5 2.1 2.2 作業ディレクトリー . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . プログラムの作成 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 6 2.3 2.4 2.5 プログラムのコンパイル . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . make によるコンパイル . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . プログラムのテスト . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 7 7 2.6 プログラムの完成 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 8 3 サブルーチン 8 4 変数 9 4.1 4.2 5 変数の使用手順 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 代入 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 10 4.3 型について . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 11 繰り返し構文 11 11 12 5.1 5.2 カウントダウンによる繰り返し . . . . . . . . . . . . . . . . . . . . . . . . . . . . カウントアップによる繰り返し . . . . . . . . . . . . . . . . . . . . . . . . . . . . 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 実数型の精度 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 13 5.4 収束条件 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 14 繰り返し構文の例 5.3 6 7 8 6.1 ユークリッドの互除法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 14 6.2 最大公約数のプログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 関数 15 7.1 関数計算 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2 グラフ表示 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 16 16 7.3 関数の作成 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 18 条件分岐 18 19 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 9 入力 9.1 キーボード入力 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2 9.3 最大公約数計算機 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 20 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 21 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 ループ脱出 10 配列 22 10.1 配列変数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2 配列演算 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3 配列を返すサブルーチン . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 23 24 11 配列の応用 11.1 遷移規則 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 25 11.2 セル・オートマトンのプログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 28 11.3 C 言語との連携 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 12 物理の例 31 12.1 1次元格子振動の定式化 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2 プログラム作成開始 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 32 12.3 1次近似のプログラムと精度 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.4 2次近似による精密化 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 35 36 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 13 複素数計算 37 13.1 複素数型 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2 収束点による平面の色分け . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 38 練習問題. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 14 参考資料 40 3 準備 1 マシンは高性能なワークステーションではなく,普通のパソコンで十分である。必要なソフト ウェアが動くように準備されているのなら,OS は何でもよい。Fortran コンパイラーなどが無い 場合は,それが入っているコンピューターを探すか,自分のコンピューターにそれをインストー ルする必要がある。ソフトウェアのインストールなどは Linux でおこなうのが最も簡単であるが, Windows や Macintosh でも可能である。それについては,最後にあげた参考資料が役立つかもし れない。 1.1 コンパイラー Fortran 95 のコンパイラーが動作することを確かめるため,端末で f95 と打ってみる (終わりに必ず Enter キーを押す)。このとき, f95: no input files のようなメッセージが表示されれば,f95 コマンドが使えることがわかる。f95 が無い場合は, gfortran か g95 か ifc か ifort を試してみよう。コンパイラーを発見したら, f95 -v のようにコマンドラインスイッチを付けて実行してみると,それがどういうコンパイラーである かがわかる。コンパイラーによってコマンドラインスイッチは違っているが,-v, -V, --version, -version のどれかの場合が多いから,これらを全部試してみる。また,-h や--help が役立つか もしれない。以後は,Fortran コンパイラーのコマンドを f95 としているが,他のコマンドの場合 は,適宜読み替えること。 もし Fortran 95 コンパイラーが使えないようであれば,それが使えるコンピューターを探すか, Fortran 95 コンパイラーをインストールしなければならない。たとえば,フリーソフトウェアであ る gfortran をインストールして使うことができる。いろいろな Linux では,gfortran はパッケー ジの形で提供されていて,簡単に探してインストールすることができる。Linux 以外でも gfortran は動くが,インストールの仕方はいろいろである。gfortran wiki などを参考にしてがんばると, 動作させることができるかもしれない。また,高価な商品であっても,個人が学習用に使うために ダウンロードすることができるコンパイラーもある。 1.2 Emacs エディター テキストエディターとして emacs を使う場合は,emacs の編集支援機能が効くことを確かめる (emacs 以外のテキストエディターを使う場合は以下の説明を飛ばす)。まず,端末で emacs xxxxx.f90 & と打って emacs を立ち上げる。emacs の編集画面に program xxxxx と打ち込んで改行する。ここで program と xxxxx に異なる色が着いたらOK。さらに 4 end と打ち,改行しないで <Ctrl>j (Ctrl キーを押しながら j) を打つ。end の右側に自動的に program xxxxx と出たらOK。 以上のテストで,<Ctrl>j が効かない場合は,編集支援機能を使うのをあきらめるか,emacs を新しいものに入れ替えるか,xemacs を使う。<Ctrl>j は効くのに文字に色が付かない場合は, ホームディレクトリーにある .emacs というファイルを emacs か他のテキストエディターで開いて, その最後に次の1行を追加して保存し終了する。記号 ( ) ’ に注意。スペースは2カ所である。 (add-hook ’f90-mode-hook ’f90-font-lock-on) ’ はシングルクォートあるいはアポストロフィという記号で,‘ (バッククォート) ではない。 emacs を起動してみてエラーにならないことを確かめる。 もし emacs でエラーメッセージが出たら,.emacs の追加した部分をよく見て修正する。エラー メッセージがどうしてもひっこまないようなら,.emacs に追加した行を消して元に戻してあきら める。emacs が立ち上がらなくなったら,端末で emacs -q .emacs のように打って立ち上げれば よい。-q は設定ファイルを無視させるスイッチである。 emacs 以外のテキストエディターを選ぶ場合は,色分けの機能と整形機能の,少なくともどちら かがあるものが望ましい。 1.3 その他のツール 計算結果をグラフ表示するために必要な GNUPlot が動くことを確かめておく。端末で, gnuplot と打つ。GNUPlot が起動してメッセージが表示されれば OK. quit と打って終了させる。 画像を表示するプログラムも必要である。ImageMagick か GraphicsMagick というパッケージ は Linux ではほとんどの場合,初めから入っている。端末で display もしくは gm display と打ってみると確認ができる。 はじめの一歩 2 2.1 作業ディレクトリー 普通やるように,作業ディレクトリーを作ってその中で作業しよう。このディレクトリー名をメ モに取っておく。端末で次のように打つと,Fortran という名前のディレクトリーを作って,そこ に移動し,移動ができたことを確かめることができる (Windows などではコマンドが異なるかも しれないから適宜読み替えること)。 mkdir Fortran cd Fortran pwd カレントディレクトリーは端末ごとに異なることに注意しなければならない。つまり,一つの端末 で cd Fortran と打って Fortran ディレクトリーに移動しても,他の端末には影響しない。また, 5 新しく立ち上げた端末のカレントディレクトリーは,他の端末のカレントディレクトリーには関係 なく,通常,ホームディレクトリーに設定される。 2.2 プログラムの作成 一つめのプログラムは firstprog という名前にしよう。コンパイラー言語であるから,ソースファ イルを作る。そのファイル名は firstprog.f90 とする。拡張子 .f90 を付ける必要がある。いろいろな 事情があって,Fortran 95 のプログラムでも,.f95 ではなく .f90 である。(もし,.f90 では問題が 起こるようなら,拡張子の付け方について,使っているコンパイラーのマニュアルを調べること。) テキストエディターでこのファイルの作成を始める。emacs を使うなら, emacs firstprog.f90 & と打つ。アンパーサンド & は emacs プロセスをバックグラウンド実行にするので,emacs を終了 しないで端末によるコンパイル作業とプログラムの実行を行うことができる。次のように入力して 保存する (終了はしない)。 print *, ’Commo esta?’ print *, ’How are you?’ end 書き方であるが,各行は左端から書いてもよいし,左に一定の空白を置いて書いてもよい。print と end は命令であるが,Fortran の命令には大文字と小文字の区別は無いから,print を Print と書いてもよいし,end を END と書いてもよい。print の右側には,アステリスク * とコンマ , とクォーテーションマーク (シングルクォート) ’ に挟まれた文字列データがある。 2.3 プログラムのコンパイル コンパイルをおこなうために,端末で f95 -o firstprog firstprog.f90 と打つ (gfortran や ifort を使う場合は,f95 を適宜読み替えること)。コンパイルが成功しなかっ た場合は,何かエラーメッセージが表示される。Error という表示が現れたら,プログラムをよく 見て修正する必要がある。コンマがピリオドになっていたり,クォーテーションマークが閉じてい なかったりしているかもしれない。テキストエディターでプログラムを修正したら,保存してから コンパイルをやり直し,エラーにならないことを確かめる。 ls -l でファイルのリストを見ると,ソースファイル firstprog.f90 の他に実行ファイル firstprog があるのが確認できる。プログラムを修正した場合は,firstprog.f90~ もあるだろう。これは古 いソースプログラムのバックアップである。ソースファイルはテキストファイルであるから,端末 で中身を表示することができる。たとえば,次のように,cat コマンドを使う。 cat firstprog.f90 実行ファイルはテキストファイルではないから, 6 cat firstprog とやると,でたらめな表示が出る。プロンプトがおかしくなることもある。プロンプトがおかしく なるのは,実行ファイルの中に偶然含まれている制御文字によって,端末のモードが切り替えられ るためである。 reset と打つことによって,プロンプトを正常に戻すことができる (文字化けしても構わず reset と打ち Enter キーを押す)。端末によっては,この操作をメニューからおこなえる。 2.4 make によるコンパイル make コマンドを使うとコンパイルが楽になる。make コマンドを使うには,設定をおこなう必 要があるかもしれない。テストしよう。まず,実行ファイルを消すため,端末で rm firstprog と打つ。それから make firstprog と打ってみる。正しく打ったのに ターゲット ‘firstprog’ を make するルールがありません. というようなメッセージ (英語かもしれない) が出たら,設定が必要である。次のようにして設定 をおこなう。端末で emacs Makefile & のように打ってテキストエディターを立ち上げ,次のように入力する (ここでも f95 は適当に変更)。 % : %.f90 f95 -o $@ $< この Makefile の中身の書き方には特別な注意が必要である。まず,1行目は左端から書く。次の 行は左端に Tab キーで空白を開けてから書き,最後には改行を入れる。保存終了し,端末で make firstprog と打ってみる。これでコンパイルがうまくいくはずである。だめなら,ls -l と打って,Makefile と firstprog.f90 があることを確かめたり,cat Makefile でファイルの中を見たり,pwd でカ レントディレクトリーを確かめたりすれば,原因がわかるだろう。 2.5 プログラムのテスト コンパイルがうまくいったら,プログラムを走らせてみよう。端末で, ./firstprog 7 と打つだけである。 実行ができたら,プログラムをいじってみよう。文字列 (クォーテーションマークで囲まれた部 分) をいろいろに変えると,実行結果が変わることを確かめる。テキストエディターでプログラム を変更したら,保存してから,端末で先程のようにしてコンパイルをおこない,プログラムを実行 する。 プログラムの1行目と2行目を入れ替えるとどうなるかもやってみる。次に,end を消すとコン パイル時にエラーになることを確かめる。このとき表示されるエラーメッセージの意味を考えてみ よう。また,end を1行目の下に入れるとどうなるかも試してみよう。プログラムを元に戻してか ら,1行目の左端に ! を入れてみよう。! はコメント記号であり,! の右側を無効にする。 2.6 プログラムの完成 プログラムは end で終わるが,これと対になる始めの言葉として,次の行を一番上に入れる。 program FirstProgram これは言うまでもなくプログラムのタイトルである。これは無くても済むが,習慣としていつも書 くこと。さらに,最後の行は end ではなく end program FirstProgram にする。また,program... と end... の間の2行は少し右にずらしてインデント (段付け) して格好 良くする。 emacs では,end 行への追加も,中間の行のインデントも,各行で <Tab> を打てば自 動でできる。また,emacs の F95 メニューの Indent Subprogram を使うと,これらの調整が 一度におこなわれる。 結局このプログラムは次のようになった。 program FirstProgram print *, ’Commo esta?’ print *, ’How are you?’ end program FirstProgram 実行される2つの文と前後の飾りを含めて一つのまとまりになっていると見てほしい。 このよう なまとまりをプログラム単位という。次に出てくるサブルーチンもプログラム単位である。 練習問題 上と同じプログラムを first2.f90 という名前のソースファイルに入れ,make コマンドで実行 ファイル first2 を作り,この実行ファイルを実行し,結果を前と比較しなさい。 first2.f90 の一行目のプログラム名 FirstProgram を First2Program に書き換え,最後の行 で <Tab> キーを打つと,どうなるか (emacs の場合)? 3 サブルーチン サブルーチンは C言語の値を返さない関数や,Pascal の procedure と同様なものであり,ひとか たまりの処理に名前を付けて抽象的に扱えるようにする。下のプログラムを SubroutineTest.f90 8 という名前のソースファイルに入れて保存しよう。emacs では編集補助機能で楽をするとよい。す なわち,改行を<Ctrl>j でおこなう。左端がずれたら,各行で <Tab> を打つか,F95 メニューの Indent Subprogram を使う。 program SubroutineTest call English call Spanish end program SubroutineTest subroutine English print *, ’How are you?’ end subroutine English subroutine Spanish print *, ’?Commo esta usted?’ end subroutine Spanish subroutine French print *, ’Comment allez-vous?’ end subroutine French 次のように,make コマンドでコンパイルし,実行する。 make SubroutineTest ./SubroutineTest 英語とスペイン語は表示されるがフランス語は表示されなかっただろう。次に,call English の 下に call French と書いて,保存し,コンパイルし,実行してみる。今度は,英語,フランス語, スペイン語の順で表示されただろう。 実行の順番や実行するかどうかは,program SubroutineTest と end program SubroutineTest の間の記述で決まることがわかった。この部分をメインプログラム (メインルーチン) という。 subroutine と end subroutine の間は各サブルーチンの作業内容の定義であって,それだけでは 実行されない。メインプログラムの中の call 文でサブルーチン名を指定することで,サブルーチ ンの実行がおこなわれる。 end program の前に call english を入れるとどうなるかをやってみよ。同じサブルーチンを 何度でも呼び出すことができることがわかるだろう。さらに,print *, ’How are you?’ のすぐ 上に call French を入れてみよ。サブルーチンの中で別のサブルーチンを呼び出すことができる。 変数 4 4.1 変数の使用手順 変数とは,データに名前を付けて記憶させておき,後でその名前を使って値を呼び出すしくみであ + X1 のように書くだろう。Y は計算結果を意味し,X は元の数を意味する。この X と Y が変数であ る。 「ある数の半分とその数の逆数とを加える」という計算を考える。中学生以上なら,Y = る。次のプログラムを走らせてみてほしい (ソースファイル名は VariableTest.f90)。 9 X 2 program VariableTest implicit none ! バグ除けお守り real(8) x, y, z ! 倍精度実数 x, y, z を使う宣言 x = 1 ! x の初期化 print *, x ! x の値を表示 y = x/2 + 1/x ! x の値で計算した結果を y に入れる print *, y ! y の値を表示 z = y/2 + 1/y ! その y の値で計算した結果を z に入れる print *, z ! z の値を表示 end program VariableTest implicit none は,プログラムのバグを減らすためのおまじないであり,常に書くことにすれば 御利益がある。program xxxx の次の行に書くことに注意。 3行目は使う変数が x, y, z の3つであることと,それらの変数の種類が倍精度実数であること を宣言している。real(8) の real は実数という意味,8 は情報量をあらわす数値である。倍精度 実数の有効数字は 15 桁から 16 桁である。 real(8) は real*8 と書いてもよい。 4行目から順に見ればわかるように,x には始めの値として 1 が入り,それの半分と逆数の和を 計算して y に結果を置き,さらに y に対して同じ計算をおこなって z に結果を置いている。始め の値とそれぞれの結果は print 文で表示される。 各行の ! から右側はコメントであって,プログラムには全く影響しないので,メモを書いておく のに利用する。ここに書いた日本語が場合によっては文字化けを起こすことがある。たとえば,プ ログラムを書いたのと異なるプログラムで表示したり,違うコンピューターに転送して表示する場 合に,それが起こる可能性がある。しかし,これはプログラムそのものには全く影響しないので, 放っておけばよい。 4.2 代入 同じ計算をさらに続けてみようと思ってほしい。使用する変数を増やさなければならないのだろ うか? それとも,同じ変数を使い回すことができるのだろうか? 当然,後者が正しい。しかも, 下のプログラムのように,計算の元の値が入っていた変数に計算結果を入れることすらできる。 program VariableTest implicit none ! バグ除けお守り real(8) x ! 倍精度実数 x を使う宣言 x = 1 ! x の初期化 print *, x ! x の値を表示 x = x/2 + 1/x ! x の値で計算し,結果を x に入れる print *, x ! x の値を表示 x = x/2 + 1/x ! x の値で計算し,結果を x に入れる print *, x ! x の値を表示 x = x/2 + 1/x ! x の値で計算し,結果を x に入れる print *, x ! x の値を表示 end program VariableTest x = x/2 + 1/x は,まず x の現在の値を使って式の右辺の計算をおこなってから,結果を x に収 める。これで,6行目以降は全く同じ文の繰り返しになってしまった。 10 4.3 型について 変数を使う前に宣言をおこなうのは煩わしいと感じるかもしれない。現に,変数の宣言も型への 拘束も無いプログラミング言語もある。Fortran で宣言や型について几帳面に扱うようにしてある のは,高性能な数値計算プログラムを作るという目的のためであるから,慣れるしかない。 Fortran には,変数一つ一つに対して型を指定するのではなく,変数を名前で分類して一括して 型を決める方法がある。たとえば,名前の1文字目が R になっているものはすべて real(8) であ ると決めることができる。これを暗黙の型宣言という。宣言文が多少減らせる以外にはデメリット の方が多いので,このやり方はおこなわない。implicit none は暗黙の型宣言が none であると いう意味である。implicit none でないプログラムは暗黙の型宣言を使っている可能性がある。 練習問題 変数の名前は変数の中身とは関係がない仮の姿であるから,違う名前を使っても同じ結果が得ら れるはずである。上のプログラムの変数名 x をすべて Harry かもしくは Kiki に書き換えて,保 存,コンパイル,実行をおこなってみよ。 繰り返し構文 5 5.1 カウントダウンによる繰り返し 繰り返し構文 (ループ) を理解すれば,上のようなプログラムを短く書くことができる。しかも, 繰り返しの回数を変えてもプログラムはほとんど変わらない。次のプログラムは上のプログラムと 同じことをするものである。ファイル名を repeating.f90 とし,コンパイルして実行してみよう。 program Repeating implicit none real(8) x integer count x = 1 print *, x count = 3 do while (count > 0) x = x/2 + 1/x print *, x count = count - 1 end do end program Repeating おまじない 倍精度実数変数 x 整数変数 count (制御変数に使用) x を 1 に初期化 x の値を表示 count を 3 に初期化 count が正である限り繰り返す ! 次の x を計算 ! 表示 ! 残り回数を計算 ! 繰り返し範囲の終端 ! ! ! ! ! ! ! 前のプログラムと,どこが同じでどこが違うだろうか。 do while (count > 0) と end do の間に繰り返される文が入っている。わかりやすくするた め,この部分がインデントされていることに注意してほしい。count という名前の変数が繰り 返しを数えるために使われていて,始めの値は count = 3 で 3 にセットされ,繰り返しの中の count = count - 1 で一つずづ減らされる。do while (count > 0) の括弧の中は,count の値 がゼロ以上であることが繰り返しを続ける条件であることを示している。変数 count は初期値 3 11 からカウントダウンされていって,3回繰り返した時点でゼロになるから,そこで繰り返しは終了 する。このような繰り返し回数を数えるための変数は,制御変数と呼ばれる。 制御変数 count は整数値しか取らないので,x とは異なる種類の変数として宣言している。integer (インテジャー) とは整数という意味である。整数の計算は確実で速い。 5.2 カウントアップによる繰り返し 上のプログラムは,制御変数を一つずつ減らしながら繰り返し処理をおこなっているので,カウ ントダウン方式である。これをカウントアップ方式に変えることもできる。下のプログラムの動作 は自明であろう ( <= はより小さいか等しいという意味)。 program Repeating implicit none ! お守り real(8) x ! 倍精度実数変数 x integer count ! 整数変数 count (制御変数に使用) x = 1 ! x を 1 に初期化 print *, x count = 1 ! count を 1 にする do while (count <= 3) ! count が 3 になるまで繰り返す x = x/2 + 1/x ! 次の x を計算 print *, x ! 表示 count = count + 1 ! count up end do ! 繰り返し範囲の終端 end program Repeating このプログラムでは,何回目の繰り返しをおこなっているかが count に入っている。そこで,x の値と共に繰り返し回数を表示するため, ループ中の print 文を print *, count, x に変えてみよう。do while ... の上の方の print 文は次のように変更。 print *, 0, x これで計算の過程が少し見やすくなった。 このカウントアッププログラムをもう少しだけ短く書く構文がある。次のプログラムを試してみ てほしい。 program Repeating2 implicit none real(8) x integer count x = 1 print *, 0, x do count = 1,3 x = x/2 + 1/x print *, count, x end do end program Repeating2 12 count への初期設定と繰り返し条件の判定が1行にまとまっていて,カウントアップも自動的に おこなわれる。便利なのでこの構文は常用される。ただし,制御変数には整数しか使うことができ ない。 では,繰り返し回数をいっきょに 10 ぐらいにして走らせてみよう (do count = 1,3 を do count = 1,10 に変更)。2 の平方根が小数点以下 14 桁まで求められただろう。 2 の平方根の小数点以下50桁ま で正確な値は次のとおりである。プログラムでの計算結果をこれと比較してほしい。 1.41421356237309504880168872420969807856967187537695 Linux などでは,端末で echo "scale=50; sqrt(2)" | bc と打つとこれが得られるだろう。bc は任意桁計算機プログラムである。 練習問題 上の上のプログラムを参考にして,上のプログラムの各行にコメントを書き込め。コンパイルし て,エラーにならないことを確かめよ。 5.3 実数型の精度 上の Repeating2 プログラムでは,計算の精度は変数 x の精度によって決まる。real(8) の 8 という数字を 4 に変更してプログラムを走らせてみてほしい。単に real と書くこともできる。こ れは real(4) と同じである。 real(4) または real は 単精度実数であり精度が低い。通常は real(8) を使うが,メモリー容 量が十分でない場合や,特別な並列演算ハードウェアを使って高速計算をおこなう場合に,単精度 実数を使うことがある。一方,倍精度よりも高い精度を使うこともできる。real(16) は4倍精度 という。(gfortran ではまだ real(16) は使えないかもしれない。real(10) が使えるかも。) 5.4 収束条件 Repeating2 プログラムを改良して,繰り返し回数を自動的に決めるようにしよう。計算結果が ほとんど変化しなくなったら,収束したとみなしておしまいにする。 program Repeating3 implicit none real(8) :: x = 1, x0 = 0, a = 10 integer :: c = 0 do while (abs(x - x0) > spacing(x0)*2) c = c + 1 x0 = x x = (x + a/x)/2 print *, c, x end do end program Repeating3 13 3行目と4行目は変数の宣言であるが,型の右に2重コロン :: を入れると,宣言文にいろいろな 機能を持たせることができる。ここでは,宣言と同時に,各変数に初期値を与えている。このプロ グラムでは 10 の平方根を求める計算をしているのだが,3行目の a = 10 のところを変えると, 任意の正数の平方根が計算できる。なぜそうなるかは,x = (x + a/x)/2 を x について解いてみれ ばだいたいわかる。 x の値が変化したかどうかで繰り返し条件を決めるため,以前の値を x0 に保持するようにして いる。abs は絶対値を得る関数である。したがって,abs(x-x0) は x の変化の大きさということ になる。spacing(x0) は x0 の値とその隣の数との間隔,つまり分解能である。 よって,このプ ログラムでは,x の変化の大きさが分解能の2倍以下になったら計算が終了する。 デジタル計算機では,実数といえども連続ではなく,飛び飛びになっている。分解能 (飛びの間 隔) は実数の値によって異なっていて,spacing で獲得することができる。数値の計算では,この 分解能程度の誤差が生じる可能性がある。また,数値の差が分解能を下回ると,それらは等しく なってしまう。 練習問題 1. abs(x - x0) > spacing(x0)*2 を abs(x - x0) > 0 にしたり,x /= x0 にしてみよ。/= は 6= のことである。たぶん,全然同じ結果になる。なーんだ! この計算はもともと収束が速いし,単純なために高精度なのである。複雑な計算では,spacing の値の8倍や128倍を使って打ち切ったり,繰り返し回数や計算時間の上限を設定したり する。 2. 表示に spacing(x0) の値も出るようにせよ。また,real(8) を real(4) にして計算してみ よ。また,a=10 を他のいろいろな値に変えてみよ。もし、プログラムが走ったまま終了しな くなったら,<Ctrl>c で止める。 繰り返し構文の例 6 ここのプログラムは後で別のところで再利用するから,必ず,動作確認をおこなうこと。 6.1 ユークリッドの互除法 2つの負でない整数の最大公約数とは,両方の数を割り切る整数の内の最大のものである。b を a で割った余りを b mod a のように書き,a と b の最大公約数を gcd(a, b) のように書くと, gcd ((b mod a), a) (a > 0) gcd(a, b) = b (a = 0) 14 である。b mod a は必ず a より小さいから,a と b をそれぞれ (b mod a) と a に置き換えるとい う操作を繰り返すと,いずれ a がゼロになる。そのときの b が最大公約数である。たとえば, gcd(2214, 77760) =⇒ gcd(77760 mod 2214, 2214) =⇒ gcd(270, 2214) =⇒ gcd(2214 mod 270, 270) =⇒ gcd(54, 270) =⇒ gcd(270 mod 54, 54) =⇒ gcd(0, 54) =⇒ 54 のようにして,2214 と 77760 の最大公約数 54 が求められる。これは古代ギリシャから伝わるユー クリッドの互除法である。 6.2 最大公約数のプログラム 上のような計算を任意の2つの数に対しておこなうプログラムを作ろう。なお,Fortran では割 り算の余りを mod という関数で計算する。 program gcdtest implicit none integer a, b, bx a = 2214 b = 77760 do while (a > 0) bx = a a = mod(b, a) b = bx end do print *, b end program gcdtest ファイル名は gcdtest.f90 にするとよい。片方の数がゼロになったら繰り返しをやめて,もう片 方の数を答えとして表示している。do ループの中では,bx=a で a の値を一旦 bx に入れ,後でそ れを b にコピーしている。このようなことが必要なのは,a=mod(b,a) で a の値が更新されてし まうからである。a のみから a を更新するような場合は何も問題はないが,複数の変数を同時に更 新するような場合には注意が必要である。同様な例であるが,a と b の値を入れ替えたい場合に, a = b b = a と書くと,b の値が変わらない。 関数 7 7.1 関数計算 数学関数を使ってみよう。 15 program SineFunction implicit none real(8) x, y x = 0 do while (x < 2*3.14) y = sin(x) print *, x, y x = x + 0.1 end do end program SineFunction ! 倍精度字数 x, y を使用 ! x を 0 に初期化 ! x が 6.28 を超えると終了 ! x の正弦関数の値を計算 ! x と sin x の値を表示 ! 0.1 増やして次の x にする ! 繰り返しはここまで これをソースファイル SineFunc.f90 に収めてコンパイルする。実行すると,x の値とその正弦 (sin x) の値が次々に表示されるだろう。プログラムでは,x = 0 から始めて,x = x+0.1 で 0.1 ずつ増やし,6.28 を超えたところで終了しているから,x の値は 0, 0.1, 0.2, ... , 6.2 である。x の 単位はラジアンであって,sin(x) の値がゼロから始まって1周期の振動をおこなっていることが 見て取れるだろうか? 数値の表現に関してであるが,たとえば,-8.308931074976279E-002 と表示されたら, −8.308931074976279 × 10−2 ,つまり,−0.08308931074976279 という意味である。 このプログラムでは,y = sin(x) で正弦関数の値を計算している。括弧を付けないで y = sin x のように書くとコンパイル時にエラーになる。 7.2 グラフ表示 計算結果をグラフにするため,gnuplot コマンドを使ってみよう。端末で ./SineFunc と打って 計算結果が表示されるのを確かめてから, gnuplot と打って gnuplot を起動する。さらに, plot ’< ./SineFunc’ と打つ。クォーテーションマークなどの記号を間違えないようにしなければならない。gnuplot を 終了するには,exit と打つ。 1 0.8 0.6 0.4 0.2 0 -0.2 -0.4 -0.6 -0.8 -1 ’<./Function’ 0 1 2 3 4 5 6 7 練習問題 1. 上のプログラムの y = sin(x) を何か別の式に変えてみる。たとえば,y=sin(2*x) とか, y=sin(x-0.5) とか,y=cos(x)*x などとしてみる。どういうグラフになるかを予想してか 16 ら,gnuplot でプログラムを走らせてグラフを表示してみよ。 2. 上のプログラムの数値出力を見て気づいただろうが,x の値は正確に 0.1 ずつ大きくなって はいない。その主因は,0.1 と書かれた数値の誤差である。10進法では 0.1 と書いた数値 に誤差はあり得ないが,コンピューターの内部では数値が2進法で扱われているために,誤 差が生じる。しかも,実数の数値をそのまま書いた場合は,単精度実数として扱われるので 誤差が大きい。 倍精度にするには,0.1d0 のように書く。d0 は倍精度であることと 10 のゼロ乗倍を意味す る。すなわち,0.1 × 100 である。1 × 10−1 でもあるから,1d-1 と書いてもよい。上のプロ グラムの 0.1 を 倍精度に直し,x の値がより正確になることを確かめよ。 7.3 関数の作成 複雑な式はまとめて名前を付けて一つの関数にすると,プログラムが見やすくなる。式を後でい ろいろに変更するような場合,どこをいじればよいかがわかりやすい。また,式を取り出して別の プログラムで再利用することが楽になる。次のプログラムを NewFunc.f90 に収めよう。 function f (x) implicit none real(8) f, x f = (x-1)*x*(x+1) end function f program NewFunc implicit none real(8) x, y, f x = -1.5 do while (x < 1.5) y = f(x) print *, x, y x = x + 0.1d0 end do end program NewFunc このプログラムは2つの部分からなっていることは明らかだろう。program NewFunc から始まる 後半は,正弦関数を計算したプログラムとほぼ同じである。x の範囲が -1.5 から 1.5 になった ことと,関数名が sin ではなく f になっているのが違う。また,x,y とともに f の型も指定さ れている。0.1d0 という書き方については,前節の練習問題の2を参照すること。 前半は関数 f の定義である。関数定義の内部では,関数自体の型が real(8) と指定されてい る。f(x) の引数 x の型も指定されている。計算式は f = (x-1)*x*(x+1) であるが,これは3次 関数である。どんなグラフになるかを予想してから,コンパイルして gnuplot でグラフにしてみ ること。 関数定義で使われている引数名 x を別の名前,たとえば h に変えてみよう。関数定義の中にあ る x を全部 h に変える。そうしてから,プログラムがちゃんと動くことを確かめてほしい。変数 には有効範囲というものがあって,関数定義の中で宣言された変数はその関数定義の内部が有効範 囲になり,メインプログラムや他の関数定義からは隔離されていて無関係である。引数も変数と同 17 様である。このような変数をローカル変数という。関数定義中の引数名は仮引数という。関数を呼 び出すときに引数として与えるデータは実引数という。仮引数は関数呼び出しによって一時的に実 引数に結びつけられ,データの伝達が可能になる。 練習問題 1. グラフを見ると,関数 f(x) の極小点が x=0.5 と x=1 の間にあることがわかる。この範囲に 限定したグラフが描かれるようにプログラムを変更してグラフを表示せよ (x の増分は 0.01 ぐらいにする)。同様にして,範囲を狭めていき,極小点の x 座標を小数点以下 5 位まで求 めよ。(答え: 0.57735) 2. 関数の定義の方で指定している型と関数を呼び出すときに使用する型とが異なると,悲劇が 起こる可能性がある。上のプログラムの関数定義の中で real(8) f, x となっているとこ ろを, real(8) f real(4) x に変えて,プログラムを走らせてみよ。コンパイル時にエラーになる/走らせたときにエラー になる/間違った結果を表示しながら走る。この内どれになるだろうか? メインルーチンでは関数呼び出し f(x) の実引数 x の型は real(8) であるのに,関数定義 の仮引数の型は real(4) になっていることが問題である。Fortran にはこのような間違いを 防ぐための機能もあるが,とりあえずは,型の一致に注意を払い,やたらにいろいろな型を 使わないことが重要である。 8 条件分岐 いつも同じ式を使うのではなく,場合によって異なる計算をおこなうことがある。上で作ったプ ログラムに次の関数定義を追加しよう。どこへ入れればよいかであるが,関数定義やメインルーチ ンの外側であればどこでもよい。たとえば,function f (x) の上側に入れるとよい。(後でメイ ンルーチンも直す。) function g (x) implicit none real(8) g, x if (abs(x) < 1) then g = sqrt(1 - x**2) else g = sqrt(x**2 - 1) end if end function g abs(x) は x の絶対値 (absolute value) を求める関数である。そこで,引数 x の絶対値が 1 より 小さいかどうかで,異なる計算をおこなうことになる。then と else の間の文は条件が成り立つ 場合にのみ実行され,else と end if の間の文は条件が成り立たない場合にのみ実行される。条 件式 abs(x) < 1 を括弧の中に入れることにも注意。sqrt は平方根 (square root) を求める関数, 18 x**2 は x の2乗である。そこで,この関数のグラフは,-1<x<1 の範囲では半円,x<-1 と x>1 で は双曲線の半分になる。 次に,g(x) を呼び出すため,メインルーチンを次のように直す。 program NewFunc implicit none real(8) x, f, g x = -1.5d0 do while (x < 1.5d0) print *, x, f(x), g(x) x = x + 0.1d0 end do end program NewFunc メインルーチンでは,まず,関数 g を使うために,型の指定に g を含めている。print 文では,x と f(x) に加えて,g(x) の値も表示するようにした。変数が多くなると見にくいので,print 文 の中に関数呼び出しを直接書いている。端末でプログラムを実行すれば,各行に x の値,f(x) の 値,g(x) の値の順で表示されるのがわかる。 f(x) のグラフは gnuplot で plot ’< ./NewFunc’ と打てば得られるし,g(x) のグラフは gnuplot で plot ’< ./NewFunc’ using 1:3 と打てば得られる。 using 1:3 は,データの各行の1列目と3列目を取り出すことを意味してい る。1列目は x,3列目は g(x) である。 plot ’< ./NewFunc’ using 1:3 with linespoints と打つと,点が線でつながって,グラフが見やすくなるかもしれない。gnuplot の命令は,次のよ うに省略して書くこともできる。 pl ’< ./NewFunc’ u 1:3 w lp f(x) と g(x) の両方を表示するには次のようにする。コンマ, に注意。 pl ’< ./NewFunc’ w l, ’< ./NewFunc’ u 1:3 w lp 練習問題 g(x) のグラフが折れ曲がるところは,点が少なすぎるために曲線がきれいではない。これを改 善するため,現在 0.1d0 にしてある x の間隔を小さくして,0.05d0 や 0.01d0 にしてみよ。 19 1.5 1 0.5 0 -0.5 -1 -1.5 -2 -1.5 -1 -0.5 0 0.5 1 1.5 入力 9 実行中に外部からデータを取り込むプログラムを作ってみよう。 入力というと,ウィンドウシステムでのダイアログボックスによるデータ入力や,スプレッド シートでのデータ入力はわかりやすい例であるかもしれない。しかし,このようなプログラムは複 雑で,Fortran だけで作るのは不可能である。センサーから送られてくるデータを取り込んだり, ネットワークでつながった装置と対話したりすることもあるが,こういったことも Fortran では難 しい。 これらに比べると,あらかじめファイルに記録されているデータを読み込んで使うのは基本的で あるし,人がキーボードを叩いてその場でデータを入力することは,さらに基本的である。UNIX の標準入力という概念は,このキーボード入力に対応しているが,それだけではなく,リダイレク ションとパイプという機能を使って,ファイルからの入力や他のプログラムの出力の利用にも対応 している。よって,Fortran での標準入力の使い方をマスターしよう。 9.1 キーボード入力 次のプログラムは,2つの数値データを入力してそれらを表示するだけである。input.f90 に 入れて,コンパイルし,実行する。 program input implicit none integer n, m read *, n, m print *, n, m end program input ./input と打って実行開始した後,キーボードから2つの整数値をスペースかコンマで区切って 入力して Enter キーを押すと,入力した数値が表示される。1つの数値を入れ,Enter を押して から2番目の数値を入れ,最後に Enter を押してもよい。数値はマイナスの数値でもよい。しか し,値が大きすぎる場合や整数でないものを入れるとエラーになるかもしれない。また,3つ以上 のデータを入れても,エラーにはならず,余分のものは無視される。 9.2 最大公約数計算機 入力した2つの数から,最大公約数を計算して結果を表示する機能を装備しよう。プログラム 名は gcd にする。最大公約数の計算は getgcd という名前の関数にする。メインルーチンは上の 20 input プログラムをコピーして手直しすればよいし,関数 getgcd は, 「繰り返し構文の例」で出て きた gcdtest プログラムをコピーして修正すればよい。 program gcd implicit none integer n, m, getgcd read *, n, m print *, n, m, getgcd(n,m) end program gcd function getgcd(a, b) implicit none integer getgcd, a, b, bx do while (a > 0) bx = a a = mod(b, a) b = bx end do getgcd = b end function getgcd メインルーチンは getgcd を呼び出すので,型指定に getgcd を加える必要がある。また,表示に 計算結果も含めるようにする。関数定義では,関数自身の型を指定する必要がある。関数の内部で は,計算結果を表示するのではなく,getgcd = b で最終の結果を関数値として返すようにする。 このプログラムの走らせ方は input プログラムと同様である。 9.3 ループ脱出 1度計算して終わるのではなく,入力と計算を何度でも繰り返しておこなえるようにしてみよ う。ただし,入力データが無くなったら終わりにする。ファイルに入っているデータの終了はファ イルの終わりであるが,キーボードからの入力の終了は,<Ctrl>d で知らせることになっている。 gcd プログラムのメインルーチンを次のように改造して,コンパイル,実行をしてみよう。計算を 終了するには,Ctrl キーを押しながら d を押す。 program gcd implicit none integer n, m, getgcd, state do read (*,*,iostat=state) n, m ! 高級 read 文 if (state /= 0) then ! データが無ければ exit ! ループから脱出 end if print *, n, m, getgcd(n,m) end do end program gcd read 文は余分の仕事をさせるために少しややこしくなった。2つのアステリスク * と iostat=state というオプション指定が括弧の中に入り,変数はその後に続いている。コンマの付け方に注意。 iostat=state は,read 文に対する指示であって,この指示によって,read 文の実行結果を表す 値が state という変数に入る。state は integer 型として宣言しておかなければならない。入力 21 が正常におこなわれた場合は state にゼロが入るが,データが無くなったら state はマイナスの 値になり,何かエラーが起こったらプラスの値になる。 ここでは,入力の後で state がゼロでなければ (すなわち state /= 0 のとき),exit でループ を強制終了させるようにしている。exit は end do の直後に飛ぶことを意味している。繰り返し 処理の途中で繰り返し条件が決まる場合は,このように,途中からのループ脱出がよく使われる。 練習問題 ファイルからデータを読み取って最大公約数を計算することができることを確かめよ。ファイル には, 12 40 280 368 810 180 4674 13338 ........ のようにデータを入れて保存しておく。ファイル名を data とすると, ./gcd <data で計算がおこなわれ,結果が表示されるだろう。<data は入力に対するリダイレクションである。 10 配列 配列変数は同じ型の複数のデータをまとめて記憶するしくみである。Fortran には,配列に入っ たデータの束を一度に操作できる演算子や関数やサブルーチンが組み込まれている。たとえば,四 則計算やべき乗やほとんどの数学関数を配列に対して使用することができる。 10.1 配列変数 配列変数は一つの名前で指定されるが,配列中の各データ要素は配列名とインデックス (番号) で 指定される。たとえば,配列 a が 1 番から 10 番までの要素を持つなら,その始めの要素は a(1), 2 番目の要素は a(2),最後の要素は a(10) である。10 個の要素を持つ配列 b の最初の要素が 0 番 であるとき,その始めの要素は b(0),2 番目の要素は b(1),最後の要素は b(9) である。複数のイ ンデックスで要素を指定する多次元配列を使うこともできるが,ここでは1次元配列だけを扱う。 配列を使うには,あらかじめ,配列の名前と要素の型,および,インデックスの範囲を指定す る。たとえば,1番から10番までの10個の倍精度実数からなる a という名前の配列は,次の ように書いて宣言する。 real(8) a(1:10) real(8) は倍精度実数,a は配列名である。括弧の中の 1 は最初の要素のインデックス,10 は最後 の要素のインデックスである。最初と最後のインデックスは,コロン : で区切って書く。 22 10.2 配列演算 配列にデータを入れたり配列からデータを取り出す方法としては,一度に全体を扱う方法,各要 素ごとに扱う方法,部分列を扱う方法がある。次のプログラムをソースファイル arraytest.f90 に入れて,コンパイルし,走らせてみよ。 program ArrayTest implicit none integer(2) a(0:9) print *,a a = (/1,2,3,4,5,6,7,8,32767,0/) print *,a a(4) = 14 print *,a a(5:6) = (/15,16/) print *,a a(7:9) = a(7:9) + 1 print *,a a = -a print *,a print *,mod(a,2) print *,a print *,mod(a,2)==0 print *,merge(’ Even ’, ’ Odd end program ArrayTest !短い整数 a(0) ... a(9) !構成子による全要素代入 !要素への代入 !構成子による部分列への代入 !部分列の各要素を1増やす !全要素の符号反転 !2の剰余を表示 !a は変わらない !偶数かどうかを表示 ’, mod(a,2)==0) このプログラムでは,print *,a で配列の全要素を表示しているので,配列に対するいろいろ な操作の結果がわかる。 integer(2) a(0:9) は a という名前の配列を宣言している。この配列は 0 番から 9 番までの 10 個 の要素からなる。各要素の型は,短い整数型 integer(2) である。integer(2) は −32768 から 32767 までの範囲の整数を表すことができる型である。宣言によって配列の要素の値がどう なるかが,次の print 文でわかる。プログラムを走らせるとわかるように,宣言の直後では,配列 の中身はでたらめである。 a = (/1,2,3,4,5,6,7,8,32767,0/) は配列 a に初期値を与えている。この右辺は配列の全要 素の値を直接指定するもので,配列構成子と言う。括弧とスラッシュとコンマをこのとおりに使っ て書く。要素数を一致させる必要がある。次の print 文による表示で,a の中身が配列構成子で与 えられたとおりになっていることがわかる。 次に現れている a(5:6) は配列 a の 5 番要素から 6 番要素までの部分列を意味している。5 番 要素と 6 番要素の値はそれぞれ 6 と 7 であったが,a(5:6) = (/15,16/) によって 15 と 16 に 変更される。 a(7:9) = a(7:9) + 1 は,部分列 a(7:9) に 1 を加えている。配列にスカラー値を加えるよう な式は,各要素に同じスカラー値を加えることと解釈される。a(7) は 8 であったから,9 になり, a(9) は 0 であったから,1 になる。a(8) は 32767 であったから,32768 になると思うかもしれ ないが,実際には −32768 になってしまっただろう。32767 は短い整数 integer(2) の最大値な ので,1 を加えることが正常にはできないのである。 a = -a は全要素を符号反転する。ここでも a(8) の計算はうまくいかない。−32768 を符号反 転すると 32768 になるはずだが,これは integer(2) の範囲を超えている。 23 mod(a,2) は,a の各要素を 2 で割った余りである。mod(a,2) を計算しただけでは a は変わら ない。 mod(a,2)==0 の == は「等しい」という意味の比較演算子である。比較の結果は論理値であ り,これは真か偽かという2値のデータで,print 文では T と F で表示される。a は配列なので, mod(a,2)==0 は,a の偶数値の要素に対して真 (T),奇数値の要素に対して偽 (F) であるような 配列 (論理値配列) になる。 merge(’ Even ’, ’ Odd ’, mod(a,2)==0) は,論理値配列 mod(a,2)==0 の真と偽の要素に 対して,2つの文字列データ’ Even ’ と ’ Odd ’ を割り当てて新たな配列を作る。 10.3 配列を返すサブルーチン 配列を使ってデータを伝達するサブルーチンを使ってみよう。date_and_time という組み込み サブルーチンは,現在の日付と時刻などのデータを教えてくれるものである。組み込みサブルーチ ンだから,すぐに使える。配列変数を引数として与えてこのサブルーチンを呼び出すと,日付など のデータがその配列変数にセットされる。次のプログラムをソースファイル datetime.f90 に入 れて,コンパイルと実行をおこなってみよ。 program DateTime implicit none integer d(1:8) call date_and_time(values=d) print *, d end program DateTime カレンダーと (正確な) 時計を見ながら続けて何度も実行してみると,表示される数字の意味がだ いたいわかるだろう。ただし,4番目と8番目はわからないかもしれない。4番目は世界標準時か らの時差を分で表した値であり,8番目は秒の小数点以下をミリ秒単位で表した値である。 ./datetime; ./datetime; ./datetime; ./datetime; ./datetime のように入力して一度に実行すると,コマンドが一つあたりどれぐらいの時間で実行されているか がわかる。並列実行をおこなうためにアンパサンド & を使って ./datetime& ./datetime& ./datetime& ./datetime& ./datetime とやると,興味深いことに,実行時間が短縮されているように見えるが,完全に同時に実行されて いるわけでもないことがわかる。[3] 7959 のような表示は emacs をアンパサンド付きで立ち上げ たときに見られるのと同じプロセス情報であり,プログラム自体からの出力ではないから,無視す ること。 では,プログラムを見よう。組み込みサブルーチン date_and_time に与える配列変数の要素の 型は integer,要素数は 8 と決められているので,配列変数 d を integer d(1:8) と宣言して いる。インデックスの範囲は違っていてもよい。たとえば,integer d(0:7) でもかまわない。ま た,要素数が多すぎるのもかまわない。余分の要素は放っておかれる。確かめるために,次のよう に変更してみよう。 24 program DateTime implicit none integer d(0:9) d = -1 call date_and_time(values=d) print *, d end program DateTime d のインデックスの始まりをずらし,要素数を2つ増やした。また,全要素を −1 に初期化してか ら,date_and_time を呼び出した。結果を見れば,何が起こったかは一目瞭然である。要素数が 少なすぎる場合は恐らく実行時にエラーになる。それも試してみよ。 date_and_time への引数の与え方には少し注意が必要である。引数の指定は (values=d) となってる。これは,date_and_time から必要な情報だけを取り出すためにおこなう書き方であ る。date_and_time は機能が過剰で,もっと多くの情報を取り出すことができる。values= で, 日付などを数値で表したデータを要求している。この values のようなものは引数キーワードとい う。date_and_time の引数には,他に,date, time, および,zone があるのだが,これらは数値 配列ではなく文字列の形で情報が与えられるもので,ここでは不要である。 11 配列の応用 単純なセル・オートマトンを試してみよう。横に1列に並んだセル (細胞) よりなるひもがあっ て,各セルは常に0か1という2つの状態だけをとりながら時間変化する。1つのセルが次にとる 状態は,そのセルと両側にあるセルのあわせて3つのセルの状態で決まる。この遷移規則はどのセ ルに関しても同じである。ひもの左端のセルをひもの右端のセルの右側のセルとみなし,ひもの右 端のセルをひもの左端のセルの左側のセルとみなす。 以下の説明を読むのが大変であるなら,とりあえず下のプログラムを走らせ,練習問題をやって みるだけでもよい。 11.1 遷移規則 あるセルに対して,その両側と自分自身の3つのセルを近傍セルと呼ぶ。各セルの近傍セルが取 りうる状態は,000,001,010 など,23 = 8 とおりある。これらを3桁の2進数とみなすと,次 のように,0 から 7 の整数になる ((· · · )b は2進数をあらわす)。 (000)b = 0,(001)b = 1,(010)b = 2,(011)b = 3, (100)b = 4,(101)b = 5,(110)b = 6,(111)b = 7 この近傍状態に応じて,各セルの次の値 (遷移値) が 0 か 1 に決まる。遷移値も 0 か 1 だから,8 とおりの近傍状態に対応する遷移値を並べて2進数にすることができる。そこで,近傍状態 0 か ら 近傍状態 7 までに対応する遷移値を右から左に並べて8桁の2進数にし,その値で遷移規則を 指定する。10進数であらわした値を使って, 「ルール 44」 などと呼ぶ。 25 2進数の各桁は右端から 0, 1, · · · と番号を付けて指定する。こうすると,桁 0,桁 1, 桁 2, · · · の重みは,20 = 1, 21 = 2, 22 = 4, · · · であるから,一般に,桁 n の重みは 2n である。 以上より,全セルの遷移値を計算するには次のようにすればよい。 1. まず遷移規則の値を決める。これを rule と呼ぶ。 2. 各セルに対して,近傍セルの値 x, y, z から近傍状態の値 s = (x y z)b = 4x + 2y + z を計算 する。 3. rule の2進数としての桁 s を調べ,それが 0 なら遷移値を 0 とし,それが 1 なら遷移値を 1 とする。 たとえば,ルール 44 だと,44 = 32 + 8 + 4 = 25 + 23 + 22 = (00101100)b だから,近傍セルの 状態と遷移値の対応は以下のようになる。 近傍セルの値 (xyz) 近傍状態の値 (s) 遷移値 111 7 0 110 6 0 101 5 1 100 4 0 011 3 1 010 2 1 001 1 0 000 0 0 あるセルの近傍セルが 011 であれば,s = (011)b = 3 より,44 = (00101100)b の桁3,つまり右 端から4桁目を調べて,遷移値は 1 を得る。 11.2 セル・オートマトンのプログラム プログラムでは,はじめにセルに初期値をセットする。遷移は,Fortran の配列演算機能を生か し,すべてのセルに対して一度に処理する。これと表示とを必要回数だけ繰り返す。 26 program CellAtm implicit none integer,parameter:: rule = 90 !遷移規則 (90, 30, 110, 184) real(8),parameter:: density = 0.04 !初期状態密度 integer,parameter:: W=78, H=20 !横幅,縦長さ integer(1) a(1:W), s(1:W) !セル状態 a, 近傍状態 s integer i call random_array(a, W, density) !初期状態を a にセット print *, merge(’X’, ’ ’, a/=0) !表示 1 -> X do i = 1, H ! H 回遷移を繰り返し s(1) = a(W)*4 + a(1)*2 + a(2) !左端の近傍状態 s(W) = a(W-1)*4 + a(W)*2 + a(1) !右端の近傍状態 s(2:W-1) = a(1:W-2)*4 + a(2:W-1)*2 + a(3:W) !他の近傍状態 a = merge(1, 0, btest(rule, s)) !遷移実行 print *, merge(’X’, ’ ’, a/=0) !表示 1 -> X end do end program CellAtm subroutine random_array(a, n, densty ) integer n integer(1) a(1:n) real(8) densty integer ck, sz, i real(8) rnd(1:n) call system_clock(ck) !クロック値 (毎回違う) を取得 call random_seed(size=sz) !乱数シードの数を取得 call random_seed(put=(/(ck+i, i=1,sz)/)) !乱数初期値変更 call random_number(rnd) !rnd に n 個の乱数をセット a = merge(1, 0, rnd < densty) !a に密度 densty の 1 をセット end subroutine random_array サブルーチン random_array は配列に1と0をランダムにセットするものである。1番目の引数 は配列名,2番目の引数は配列のサイズ,3番目の引数は配列の要素の内の1をセットする要素の 割合 (0 · · · 1) である。このサブルーチンは,1番目の引数として与えられた配列の内容を使って何 かをおこなうのではなく,結果をこの配列にセットする。このように、Fortran のサブルーチンの 引数は,呼び出し側からサブルーチンにデータを伝えるだけではなく,サブルーチンから呼び出し 側にデータを伝えることもできる。両方できてややこしいので,注意が必要である (一方通行にす る技もある)。サブルーチン random_array は,毎回ランダムに異なる値がセットされるようにす るために複雑なことをおこなっているので,このサブルーチンの中身は解説しない。詳しくは参考 資料を見ること。 メインルーチンでは,はじめに「遷移規則」の値の設定がある。複雑なパターンが現れる遷移規 則はごく限られている。それを括弧の中に書いておいた。次の「初期状態密度」は,はじめにど れだけの割合のセルに1を入れるかを決める数である。0 だと全部ゼロ,0.5 なら半分ほどが 1,1 だと全部 1 という具合である。次の行の「横幅」とはセルの数のことであり, 「縦長さ」とは遷移 を何回繰り返すかである。これらの変数宣言にある parameter は,変数に対する代入が禁止され ることを示す。宣言で設定された値に鍵がかけられ,安心が得られる。このようなおまけの指定を 「属性」という。 配列 a はセルの状態 (0 または 1) を保持する。配列 s には近傍状態の値 (0, 1, 2, 3, 4, 5, 6, 7) 27 を置く。いずれも integer(1) というタイプにしてある。integer の仲間ではこのタイプが最も値 の範囲が狭く,−128 − −127 である。このタイプにしたのはたまたまである。これらの配列のイ ンデックスの上限は数値ではなく W で指定されている。こうすると,横幅を変更する場合は W の 値を設定しているところだけを変えればよいので便利である。ただし,parameter 属性を付けて W を宣言しないと, 配列の宣言でエラーが起こる。つまり,配列インデックスの上下限は定数でなけ ればならないのである。 merge 関数は第1引数の値と第2引数の値を並べて配列を作る。第3引数は並べ方を表す論理 値配列であり,真の要素の場所に第1引数の値が置かれ,偽の要素の場所に第2引数の値が置かれ る。merge(’X’, ’ ’, a/=0) は 文字 X と空白文字を並べて配列 a と同じ大きさの配列を作る。 そのとき,配列 a のゼロでない要素の場所に X それ以外の場所に空白が入る。これを print 文で 出力すると,全てのセルの状態が1行に表示される。 各セルの近傍状態は 0 から 7 までの整数として配列 s に入れる。この計算は部分配列を使って 簡単におこなっているが,両端は別に計算する必要がある。 遷移の実行では,まず,btest(rule, s) で,遷移規則 rule の2進数としての桁 s が 1 であ るかどうかをテストして,論理値配列を作っている。btest の結果が数値であれば遷移値としてそ のまま使えたのだが,論理値であるので,merge で 1 と 0 に変換してから a を更新している。 このプログラムは乱数を操作して,走らせるたびに異なる出力が得られるようにしてある。それ を確かめよ。 練習問題 1. 配列を循環移動する関数 cshift を使うと,上のプログラムの s を求めている3行の計算が次 の1行で済んでしまう。 s = cshift(a,-1)*4 + a*2 + cshift(a,1) cshift(a,n) で 配列 a を左に n 要素分ずらしたものが得られる。このとき,a の右端と左 端はつながって輪になっているとみなされる。これを使ってプログラムを短くし,動作を確 かめよ。 2. 上のプログラムで,横幅 W と縦の長さ H をもっと増やしてみよ。端末ウィンドウを大きくし 文字を小さくすると,横に数百文字表示することができるはずである。また,遷移規則 rule の値や初期状態密度 density の値も変えていろいろ試してみよ。 X X XX X X X XXXXX X X X X XX X X X X X XX X XXXX X X X XX X XX X X X X X XXXXX X XXXXX X X X X X X X XX X X X X X X X X X XX X X X X X XXXX X X X XX X XX X X X X X X X XXXXX X XXXXX X X X X X X X X X X XX X X XX X X X X X X X X X X X X X XX X XXXX X XX X XXXX X X X X X X X X X X X X X XX X XX X XX X XX X X X X X X X X X X X X X XXXXX X XXXXX X XXXXX X XXXXX X X X X X X X X X X X X X X X X X X X X X X X X X XX X X XXX XXX XXX XXX XXX X X X X X X X X X X X X X X X X X X XX X X X X X X X X X X X X X XXXX X X X X X X X X X X X XX X XX X X X X X X XX X X X X XXXXX X XXXXX X X X X X X X XX X X X X X XX X X XX X X X X X X X X X X XXXXXX X X X X X X XX X XXXX X XX X XXXX X X X X X X X X X X X X X X X XX X XX X XX X XX X X X X X X X X X X XX X X XX X X X X X X XXXXX X XXXXX X XXXXX X XXXXX X X X X X X X X X X X X X X XX XX XX X X X X X X X X X XX X X X X XX X X X X X X X X X X X X X X XXXXXXXXXXXXXX X X X X X X X X X X XX X X X X X XXXX X XX X X X X X XXXX X X X X X X X X X X X X XX X X X X X X X X X X X X XX X XX X XX X XX X X X X X X X X X X XX X X XX X X X X X X XXXXX X XXXXX X XXXXX X XXXXX X X X X X X XX XX X X X X X XX X X XX X X X X X X X XX X X XX X X XX X X XX X X X X X X X X X X X X X XXXXXX X X XXXXXX X X X X X X X X X X XX X XXXX X XX X XXXX X XX X XXXX X XX X XXXX X X X X X X X X XX X X X X X X X X X X X X X X X X X X X X X X X XX X XX X XX X XX X XX X XX X XX X XX X X X XX X XXXX X XXX X X X X X X X XX X X XX X X XX X X XX X X X X X X X X X X XXXXX X XXXXX X XXXXX X XXXXX X XXXXX X XXXXX X XXXXX X XXXXX X X X X X X X X XXXXX X XXXXX X XX X X X X X X X X X XX XX XX XX XX XX XX X X X X X X X X X X X X X X X X X X X X X X X X X XX X X X X X X X X X X X X X X X X X X X X X X X X XX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X XXXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X XXX XXXX X X X X X X X X X X X X X X X XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X X X X XX X XX X XXXX X X X X X X X XXXXX X XXXXX X X XX XX X X X X X X X X XX X X XX X X XXXXXXXX X X X X X X X X X X X X X XX X XXXX X XX X XXXX X X X X XX XX X X X X X X X XX X XX X XX X X X XXXX XXXX X X X X X X X X X X X XXXXX X XXXXX X XXXXX X XX X X X XX XX XX XX X X X X X X X X X X X X XX X X XX X X X XX X X X XXXXXXXXXXXXXXXX X X X X X X X X X X X X X X X X X X X X X XXX X X X X XXXX X XX X X X XXXXX X X X X X XX XX X X X X X X XX X X XX X XX X X XX X XXXX XXXX X X X X X X X X X X X XXXX X X XXXXX X XXXXX X X X XXXXX X X XX XX XX XX X X X X X X X X X X X X XXX X X X XX X X XX X X XX X X XXXXXXXX XXXXXXXX X X X X X X X X X X X X X X X X X X X X X XX XXX X X X XX X XXXX X XX X XXXX X X X X XXXX X X X X XX XX XX XX X X X X X X X X X X X XX X X XX X X XX X XX X XX X X X X X X X XXXX XXXX XXXX XXXX X X X X X X X X X X X X X X X X X X X XXXX XXXX XXXXX X XXXXX X XXXXX X XX X X XX X X XX X X X XX XX XX XX XX XX XX XX X X X X X X X XX X X X X X X X X X X X XX XX XX XX XX X X XX X X X XX XX XX XX XX X X X XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X X X X X X X X X X X X X X XXXX X X X X X X X X X X X X X X X X X X X XXXXXXXXXXXXXXXXXXX X X X X XXXX X XX X X X XXXXXXXXXXXXXXXXXXXXX X X X X X XX XX X X X X X X XX X X XX X XX X X XX X XXXX XXXXX X X XX X X XX X X XXXXX X X X XXXXX X XXXXX X X X XXXXX X X XX XX XX X X X XX X X XX X X X XX X X X X XX X X XX X X X X XX X X XXXXXXX XXXX X XX X X XXXXXX X X XXXXXX X X XX X XXXX X X X X X XX X XXXX X XX X XXXX X X X X X XX X XXXX X X X X X XX XX X XX X X X X X X X X X X XX X XX X X X XX X XX X XX X X X X XX X X X XX X XXXX XXXXX X XXXXX X X XX X X XX X X XX X X XX XXXXX X XXXXX X X X XXXXX X XXXXX X XXXXX X XX X X X X XXXXX X XX X X X XXX XX XX XX XX X X X X X XX XX XX X X XX XX XXX XX X X XX X X X X XX X X XX X X X XX X X X X X X XX X X X XX XXXX X X X X X XX X X XXXXXXXXXXXXXX X X XXXXXXXXXXX X XXX X X X X XXXX X X X X X XXX X X X X XXXX X XX X X X XXXXX X X X X X X XX X X X XXXXX X X X X XXXXXXXXXXXX XXXXX XX XX X XX X X X X X X X X X X X XX X X XX X X XX X XX X X XX X X XX X X XX X XX XXXX XXXX XXXXX X XXXXX X X XX X X XX X X XX X X X X X X XXXX X X XXXX X X XXXXX X XXXXX X X X XXXXX X X X XXXXX X X X XXXXX X X XX XX XX X X XX X X X XX X X XX X X XX X X X X X X X XXX X X X XXX X X X XX X X XX X X XX X X XX X X XX X X XX XX XXXXXXXX XXXX X XX X XXXX X XX X X XXXXXX X X XXXXXX X X XXXXXX X X X X X X XX XXX X X X XX XXX X X X XX X XXXX X XX X XXXX X X X X XXXX X X X X XXXX X X X X XXXX X X X X XX XXXXXXXX XX XX X XX X XX X XX X X X X X X X X X X X X X X X X X X X X XX X X XX X X XX X X XX X X XX X XX X XX X X X X X X X X X X X X X X XXXXX XX XX X XXXX XXX XXXXX X XXXXX X XXXXX X XXXXX X X XX X X XX X X XX X X XX X X XX X X XX X X X X XXXX XXXX XXXX XXXX XXXXX X XXXXX X XXXXX X XX X X XX X X XX X X XX X X XX X X XX X X XX X X XX XXXX XXXXXX X X X X X X X XX XX XX XX XX XX XX XX XX XX XX XXX X X X X XX XX XX XX XX XX XX XX XX X X X X X XX XX XX XX XX XX XX XX XX XX XX XX XX X X XXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXX X X X X X X X X XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X X X X X X X X X X XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X X X X X XXXXXXXXXXXXXX X X X X X X X X X X X X X XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X X XX X X X X XX X X X X X X XXXXX X X X X X X X XX XXX X X X X X X X X XX X X X X X X X X XX X X X X X X X X X X X XX X XXXX X X X X X X X X X X X X X XX X XXXX X X X X X X X X X XX X XX X X X X X X X XX X XXX X X X X X X X X XXXXX X XXXXX X X X X X X X X X X X X X XXXXX X XXX XX X X XX XX X X X X X X X X X X XX X X X X X X X X X X X X X X X X X XXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 28 11.3 C 言語との連携 上のプログラムで,セルの状態を白と黒の画素に対応させて画像を作ってみたい。Fortran プロ グラムでも画像データを作ることが可能であるが,Fortran の入出力機能はひじょうに高級で抽象 化されているため,いろいろな制約を乗り越える必要がある。Fortran プログラムが出力した数値 データを画像データに変換するプログラムを別に用意して,それでデータ変換をおこなってもよい が,中間データのためのスペースが必要になったりして,あまり効率的ではない。 そこで,ここでは,Fortran プログラムから C 言語で書いた出力サブルーチンを呼び出すこと によって,画像データを直接出力するプログラムを作る。こういうテクニックは,使用するコンパ イラーによってやり方が異なる可能性がある。ここで示す例は,gfortran と ifort で有効である。 ソースファイルは,Fortran の部と C の部の2つに分けて書く。まず Fortran の部を以下に示 す。ファイル名は cellatmt.f90 としよう。 program CellAtmt implicit none integer,parameter:: rule = 110 !遷移規則 (90, 30, 110, 184) real(8),parameter:: density = 0.01 !初期状態の 1 の密度 integer,parameter:: W=800, H=750 !横幅,縦長さ integer(1) a(1:W), s(1:W) !integer(1) は 8 ビット整数 integer i call random_array(a, W, density) !初期状態を a にセット call put_head(W, H+1, 1) !出力初期化ルーチン (C) call put_raw(a, W) !出力ルーチン (C) do i = 1, H ! H 回遷移を繰り返し s = cshift(a,-1)*4 + a*2 + cshift(a,1) !近傍状態計算 a = merge(1, 0, btest(rule, s)) !遷移実行 call put_raw(a, W) !出力ルーチン (C) end do end program CellAtmt subroutine random_array(a, n, densty ) *** 前と同じ *** end subroutine random_array 前のプログラムとほとんど同じだが,print 文ではなく, C のサブルーチン put_head と put_raw を使ってデータを出力している。put_head は,画像データのパラメータを設定するために,始め に呼び出す。その第1引数と第2引数は,画像の横方向と縦方向のピクセル数,第3引数は画素値 の最大値である。画素値は 0 から 最大値までの整数値をとり,黒から白までの明るさに対応する。 配列 a に入っているセルの値は 0 か 1 であり,それをそのまま画素値にするため,put_head の第 3引数は 1 にしている。put_raw は配列とその長さを渡すと,配列の中身を画素として出力する。 次は C の部である。ファイル名は pgmout.c としよう。 29 #include <stdio.h> int Width, Height, Max; void put_head_(int *width, int *height, int *max){ printf("P5\n%d %d\n%d\n", *width, *height, *max); } void put_raw_(char *a, int *width){ fwrite(a, 1, *width, stdout); } このプログラムは PGM という形式の画像データを出力するようにしてある。PGM にしたのは, プログラムがシンプルになるからである。C のサブルーチンの詳細は説明しないが,Fortran から 呼び出すことを可能にするための条件は以下のとおりである。 まず,関数名は Fortran と C で同じではない。Fortran プログラム中の関数名の末尾にアンダー スコアを付けて C での関数名にする。この名前の対応規則は,コンパイラーによって異なる可能 性がある。次に,C の関数の引数はポインターにする (* を付ける) 必要がある。また,型の対応 は,integer⇒int, integer*1⇒char である。 PGM のデータ形式について知りたい場合は,端末で man pgm と打てばわかるかもしれない。 一つだけ知っておくとよいのは,PGM がモノクロ画像用の形式であって,画素値を 8ビット整 数であらわすということである。8ビットだから,最大 28 = 256 階調の明るさを表現する。この プログラムでは2階調しか使っていないから無駄があるが,最大 255 階調であれば他のプログラ ムで再利用することができる。 コンパイルのしかたはコンパイラーによって異なる。gfortran の場合は次のようにする。 gfortran -o cellatmt cellatmt.f90 pgmout.c make でコンパイルができるようにするには,Makefile に次のように書き加える。 cellatmt : cellatmt.f90 pgmout.c gfortran -o $@ $^ ここでも,2行目の左側の空白には Tab を使う。ifort (Intel の Fortran) の場合は,icc (Intel の Cコンパイラー) が必要である。icc と打ってみると,それが入っているかどうかがわかる。コン パイルは次のようにする。 icc -c pgmout.c ifort -o cellatmt cellatmt.f90 pgmout.o make でコンパイルができるようにするには,Makefile に次のように書き加える。 cellatmt : cellatmt.f90 pgmout.c icc -c pgmout.c ifort -o cellatmt cellatmt.f90 pgmout.o この場合は,2行目と3行目の左側の空白に Tab を使う。make でコンパイルをおこなうには, make cellatmt と打つ。実行は次のようにする。 ./cellatmt >x.pgm 30 x.pgm は画像データを入れるファイルのファイル名である。拡張子は.pgm とする。画像表示には, 任意の画像表示プログラムが使える。たとえば,ImageMagick が入っていれば, display x.pgm とやるとよい。display では,マウス中ボタンで部分拡大ができる。また,スペースキーを押すと 終了する。次のように,パイプ記号 | を使うと,ファイルを介さず直接表示ができる。 ./cellatmt | display ファイルへの保存と表示を同時におこなうには,次のようにやるのが便利である。 ./cellatmt | tee x.pgm | display 画面の大きさにあわせてなるべく大きな画像が出てくるように, Fortran プログラムの W と H の値を変えてみるとよい。 12 物理の例 Fortran は他の言語に比べると配列に対する操作や計算が容易に行えるので,複数の次元や多数 の粒子のようなものを一度に扱うのに適している。複数の粒子より成る1次元格子の上を波が伝播 する様子を計算してみよう。 12.1 1次元格子振動の定式化 n-1 n n +1 たくさんの同じ質点が並んでいて,隣同士は同じバネで繋がっているとする。単純な方がよいの で,両端がつながって環状になっていて,質点は環に沿って動くことだけが可能と考える。これは 1次元格子である。質点の運動はバネを通して隣へ伝えられるので,格子上を音波が伝播する。 おもりの質量は全部 1,ばね定数も全部 1 とする。n 番目のおもりの速度を vn とし,n 番目の おもりと n − 1 番目のおもりの間の張力を fn とし,時間微分を ˙ であらわすと (何故かはそのう ち考えるとして),運動方程式, v̇n = fn+1 − fn f˙n = vn − vn−1 31 が成り立つ。これによって,時刻 t = 0 での vn と fn を与えると,その後の時間変化が決まる。 ここでは,これを数値的に計算し,精度をテストしてみる。 時間 ∆t の間の変化は,近似的に, ∆vn = v̇n ∆t = (fn+1 − fn )∆t ∆fn = f˙n ∆t = (vn − vn−1 )∆t によって計算できる。これは荒い近似であるが,∆t を十分小さくすれば最低限の精度を確保する ことができる。∆t2 までの近似では,上の式に次の項を追加すればよい (運動方程式を微分して2 階微分を計算)。 1 1 v̈n ∆t2 = (∆fn+1 − ∆fn )∆t 2 2 1¨ 2 1 2 ∆ fn = fn ∆t = (∆vn − ∆vn−1 )∆t 2 2 ∆2 vn = 12.2 プログラム作成開始 プログラムには,上の近似計算をおこなう機能以外に,初期値を設定する機能,計算結果を出力 する機能などが必要である。ここでは,内部サブルーチンによって機能分割をおこなうことにする。 内部サブルーチンの定義は,メインルーチンの内部に置く。内部サブルーチンはメインルーチンが 使っている変数を直接利用することができるので,やりとりするデータが多い場合には助かる。 まず,配列に初期値を入れるところまでを作る。初速度の分布を滑らかなパルス状の関数にしよ う。GNUPlot でグラフを見るようにする。 32 program elastic implicit none integer,parameter:: POINTS=400 ! 格子点の数 real(8),parameter:: PULSEWDT=33.0 ! パルス半値全幅 real(8),dimension(1:POINTS) :: f, v ! 張力 f, 速度 v call setup(v, PULSEWDT) ! 初期値設定 (v) call setup(f, PULSEWDT/2) ! 初期値設定 (f) call plot(v) ! 出力 ! call plot(f) ! 出力 contains !! 以後は内部サブルーチン !! データ出力サブルーチン subroutine plot(array) real(8) array(1:POINTS) print ’(f0.4)’, array print * ; print * end subroutine plot !呼出方: call plot(配列名) !! パルス波形設定サブルーチン subroutine setup(array, width) !呼出方: call setup(配列名, パルス幅) real(8) array(1:POINTS), width integer x(1:POINTS), i x = (/( i, i=1-POINTS, POINTS-1, 2)/) !x=1-p, 3-p, ..., p-3, p-1 array = 1d0/cosh(x/width*1.317) end subroutine setup end program elastic これは全体がメインルーチンになっているが,実際には,メインルーチンの実体は contains の上 側だけであり, contains の下側は内部サブルーチン定義を置く場所という決まりになっている。 ここでは,2つの内部サブルーチンが定義されている。サブルーチンでは,格子点の数 POINTS を, 引数を介さず直接使っている。内部サブルーチンではこういういい加減なことができる。 5行目では,real(8) に dimension(1:POINTS) という属性が付いている。これによって,f と v の両方のインデックス範囲を指定したことになる。 plot 関数の定義中の print ’(f0.4)’, array の ’(f0.4)’ は書式指定文字列 であり,実数 の値を小数点以下 4 桁で表示するようにしている。print 文で * のかわりに書式指定文字列を使う と,配列の値が縦方向に出力され,GNUPlot でグラフにするのに都合がよい。また,print * は 改行を出力するだけであり,これを2つ続けることによって2行分の空白ができる。GNUPlot は これをデータの区切として認識するので,複数のデータをグラフにすることが可能になる。 setup の定義中の x =· · · の右辺は 配列構成子の一種で,1-POINTS から POINTS-1 まで 2 ず つ増える等差数列になる。実際には,−399, −397, · · · , −1, 1, · · · , 397, 399 のようになる。このタ イプの配列構成子に使用する整数型変数 (i) はあらかじめ宣言しておく必要がある。 1 1 = ax , (ただし a = 1.37/(width)) である。こ 1d0/cosh(x/width*1.317) は cosh(ax) e + e−ax れはなめらかな山形の関数になるが,その幅 (半値全幅) は width の値で決まる。 このプログラムは elastic.f90 という名前にしよう。コンパイルして実行すると,数値の出方 がわかる。グラフにするには,gnuplot で plot ’<./elastic’ with lines と打つ。1/ cosh 型 のパルス波形は,裾が適度に広がっていて優美である。 33 1 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 0 12.3 50 100 150 200 250 300 350 400 1次近似のプログラムと精度 次は一応の完成品。時刻 100 まで次々に計算するが,表示は 20 単位時間ごと,計算時間の間隔 は 1/64 単位時間,つまり,∆t = 1/64 とした。∆t はプログラム中では dt である。 program elastic implicit none integer,parameter:: POINTS=400 ! 格子点の数 real(8),parameter:: PULSEWDT=33.0 ! パルス半値全幅 integer,parameter:: PERIOD=100 ! 最終時刻 integer,parameter:: MULTIPLE=20 ! 表示時間間隔 integer,parameter:: DIVISION=64 ! 単位時間あたりステップ数 real(8),parameter:: dt = 1d0/DIVISION ! 1 ステップあたり時間 real(8),dimension(1:POINTS) :: f, v ! 張力 f, 速度 v integer i, j, k ! ループカウンタ call setup(v, PULSEWDT) ! 初期値設定 f = 0 ! f はゼロで埋める call plot(v) ! 出力 do i = MULTIPLE, PERIOD, MULTIPLE !最終時刻までのくりかえし do j = 1, MULTIPLE !次の表示までのくりかえし do k = 1, DIVISION !単位時間分のくりかえし call step !一歩進む end do !くりかえし終端 end do !くりかえし終端 call plot(v) !出力 end do !くりかえし終端 contains !! 以後は内部サブルーチン !! 予測計算 subroutine step real(8),dimension(1:POINTS) :: df, dv !f, d の1次の変化 dv = (cshift(f,1) - f) * dt df = (v - cshift(v, -1)) * dt v = v + dv f = f + df end subroutine step ***** plot および setup は前と同じにつき省略 ***** end program elastic 34 メインルーチンの do ループは3重構造になっている。インデントを重視すれば,この構造を簡 単に認識することができるはずである。最も内側のループは,DIVISION 回の計算をおこなって いて,単位時間の処理になっている。中間のループでは,単位時間の処理を表示間隔の分繰り返 す。外側のループは表示間隔内の計算と表示とを最終時間まで繰り返す。このループの do 文は do i = a,b,c という形であるが,これは i を a から b まで c ずつ変化させるという意味である。 1 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 ’<./elastic’ 0 50 100 150 200 250 300 350 400 gnuplot で実行しよう。最初は単一のパルスであったものが,左右にわかれて伝播する様子がわ かるだろう。v には call setup(v,PULSEWDT) でパルス波形を与え,f は f=0 でゼロにしたが, これを f=-v に変えると,一方向に進む波になり,f=v だと逆方向に進む波になるだろう。 単位時間あたりステップ数 DIVISION は 64 にしてあるが,これを減らして 10 か 9 にすると, グラフに異変があらわれはじめ,8 にすると大きな不安定が生じる。∆t が大きいと,計算の最後 の方で誤差の急成長が生じるのである。 1.5 ’<./elastic’ 1 0.5 0 -0.5 -1 -1.5 0 50 100 150 200 250 300 350 400 練習問題 データを途中で打ち切って表示するには,gnuplot で plot ’<./elastic1’ index 0:2 w lines のようにすればよい。index 0:2 は,初めから3つのデータだけを表示するという指示である。こ れを利用して,いろいろなところで打ち切って表示してみよ (下の注記参照)。表示間隔 MULTIPLE の値も考えると,誤差が急成長する時刻はだいたいどれぐらいか? 注記: gnuplot で index 指定を使って表示データを選択した場合,グラフは表示されるが,Fortran プロ グラムからのデータの流れが途中で止まってしまってエラーメッセージが出ることがある。エラーメッセージ を見たくない場合は,データをいったんファイルにしまい,そのファイルをグラフにするとよい。それには, たとえば,端末で,./elastic > data とやって,Fortran プログラムの出力をファイル data に保存する。 gnuplot の中で, ! ./elastic > data とやってもよい。それから,gnuplot で 35 plot index 0:2 ’data’ with lines のように打つとグラフが得られる。ファイルに全データが入っているので,違う部分のグラフを見る場合で も計算をやり直す必要は無い。 12.4 2次近似による精密化 step サブルーチンを精密化して ∆t の2次の項まで計算に入れる。そのため,次のように変更 する。∆2 f と ∆2 v を,プログラムでは,ddf,ddv と書いている。 subroutine step real(8),dimension(1:POINTS) :: df, dv !f, v の1次の変化 real(8),dimension(1:POINTS) :: ddf, ddv !f, v の2次の変化 dv = (cshift(f,1) - f) * dt df = (v - cshift(v, -1)) * dt ddv = (cshift(df,1) - df) * (dt/2) ddf = (dv - cshift(dv, -1)) * (dt/2) v = v + dv + ddv f = f + df + ddf end subroutine step このように直した上で,上と同じく,DIVISION=8, PERIOD=100 の条件で計算してみると,不安定 が完全に消える。 練習問題 1. PERIOD=100 で固定した場合, 安定性を保ったまま,DIVISION をいくらまで減らせるか? DIVISION=8 で固定した場合, 安定性を保ったまま,PERIOD はどれぐらいまで増やせるか? 注記: もし計算時間がかかって困るようになった場合は,Makefile 中の f95(もしくは ifort ) のコ マンド行にオプションを追加すると,スピードアップする可能性がある。まず,gfortran の場合は,コ ンパイラーの最適化オプション -O が有効である。Makefile を emacs で開き, % : %.f90 gfortran -O -o $@ $< のように変更する。このオプションは ifort には無効。 次に,Intel や AMD のプロセッサーの場合,内蔵されている SSE (並列演算ユニット) を使うと計算 速度が上がることがある (ただし 100% ではない)。Linux では,端末で cat /proc/cpuinfo と打つ とプロセッサーの情報が表示されるが,その最後あたりの,flags : という項目の中に,sse2 という語 が見つかれば,並列演算ユニットが存在している。sse2 とともに pni があるとなお良い。SSE があっ たら,Makefile を emacs で開き,gfortran の場合は % : %.f90 gfortran -O -msse2 -mfpmath=sse -o $@ $< のように変更する。pni もあった場合は,-msse2 を -msse3 にする。ifort の場合は, % : %.f90 ifort -msse2 -o $@ $< のようにする。 36 2. f の初期値を f=-v にすると,右へ進む波になる。しかし,実はこれはパルス幅がある程度 大きい場合であり,その場合,1次元格子は連続体に近い。パルス幅を小さくすると,逆方 向の波が生じることを確認せよ。 POINTS を 2000,PERIOD を 1000,MULTIPLE を 200 ぐらいにし,パルス幅を 10 ぐら いにしたとき,進行中のパルスにも波形の変化が起こることを確かめる。このとき,計算が 正しく行われていることを確認するため,DIVISION の値を2倍にして実行する。結果がほ とんど変わらなければ,計算は正しい。結果に目立った差がある場合は,DIVISION の値を 増やす必要がある。 計算が正確であることを確認したら,パルス幅による違いをみるために,PULSEWDT を 15 にしたり 5 にしたり 2 にしたりしてみよ。進行波パルスの波形変化は波長分散の効果で ある。一つのパルスのすそを引き延ばして観察すると,高い周波数の成分ほど遅れて進んで いるのがわかるだろう。 注記: データをファイル (たとえば data ) に保存しておいて,plot コマンドで plot [1500:2000] ’data’ with lines のようにすると,指定した範囲 (1500 ∼ 2000) を拡大表示して細かく観察することができる。 1 0.8 0.6 0.4 0.2 0 -0.2 -0.4 0 400 800 1200 1600 2000 3. fn を −fn に置き換えると,時間の向きを反転するのと同じことになる (なぜそうなるかは 後で考えればよい)。そこで,上のプログラムにおいて,波形がかなり変化したところまで計 算をおこなった時点で f の符号を変えてみたい。進行方向が逆転し,波形も元に戻るであろ うか? プログラムに手を加えて,これを実際に確かめなさい。f=-f で fn の符号は変わる。 たとえば,パルス幅を 2 程度,表示時間間隔を 400,最終時刻を 1600 にし,if 文で判断し て,時刻 800 の表示のときに符号反転をおこなうようにする。単位時間あたりステップ数を 16 ぐらいにすると,最終波形に初期波形とのずれが少し見られるだろう。計算結果ををファ イルに保存し,gnuplot の範囲指定や index 指定を使って,詳しく観察すること。 13 13.1 複素数計算 複素数型 今では複素数は C などいろいろなプログラミング言語で使えるが,Fortran では古くから複素 数が利用できた。複素数は実部と虚部に分ければ2つの実数に過ぎないのだが,それを一つの固ま 37 りで扱うことで,プログラムをすばやく書くことができる。複素数 a + ib は Fortran では (a,b) と書く。虚数単位は (0,1) である。四則計算は,複素数どうしでも複素数と実数や整数の間でも おこなうことができる。複素数から実部や虚部や絶対値や共役を得る関数がある (real, aimag, abs, conj)。sin, cos, exp, log など,主な数学関数は複素数の計算もできる。2つの数から複素数を作 る関数 cmplx もあるが,これは使いにくいし必要性も無い。実数と同じく複素数型にも単精度と 倍精度があり,complex(4), complex(8) とあらわす。complex(4) は complex でもよい。 13.2 収束点による平面の色分け 2 の平方根に収束する計算が前に出てきたが,今度は 1 の4乗根に収束する次の計算を扱う。 znext = 3 1 z+ 3 4 4z z の初期値が実数なら,この計算を繰り返すと 1 か −1 に収束する。範囲を複素数に広げると,i と −i も収束点に加わる。z の初期値によってどの点に収束するかは異なるし,収束しない場合も ある。そこで,適当な矩形の領域内に初期値を置くことにし,その領域内で, 収束しない点,1 に 収束する点,i に収束する点,−1 に収束する点,−i に収束する点の5種類に分類して色分けして みる。すると,なかなか不思議な形ができあがる。 結果は画面上に画像として表示する。そのために,前に使った 256 階調モノクロの C 言語サブ ルーチンを使うことにする。そのファイル (pgmout.c) がまだあることを確認してほしい。Fortran プログラムを次に示す。ファイル名を fractal.f90 としよう。gfortran の場合,コンパイルは次の ようにして行う。 f95 -o fractal fractal.f90 pgmout.c 実行は, ./fractal | display で OK. make コマンドを使ってコンパイルをおこなう方法や,Intel のコンパイラーでのコンパイ ル方法は前に示した (30 ページ)。また,-O オプションや SSE による高速化オプション (36 ペー ジ) を付けると,スピードアップするかもしれない。 38 program fracta1 implicit none real(8),parameter :: B_LEFT=0.0, B_RIGHT=1.0, B_BOTTOM=0.0, B_TOP=1.0 integer,parameter :: WIDTH=700, HEIGHT=700 real(8) x, y integer i, j, r integer*1 :: raw(1:WIDTH) call put_head(WIDTH, HEIGHT, 255) ! 画像出力の初期化 (C) do j = 1, HEIGHT ! 縦ピクセル数分くりかえし y = (B_BOTTOM - B_TOP)/HEIGHT * j + B_TOP ! 虚部を計算 do i = 1, WIDTH ! 横ピクセル数分くりかえし x = (B_RIGHT - B_LEFT)/WIDTH * i + B_LEFT ! 実部を計算 r = converge(x, y) ! 点 (x,y) を分類 raw(i) = r ! 結果を蓄積 end do ! call put_raw(raw, WIDTH) ! 1 行分出力 (C) end do ! contains function converge(x, y) result(r) ! 点の分類を計算する関数 real(8) x, y complex(8) z, old integer r ! result(r) で r を結果変数にした z = x + (0, 1)*y ! (x,y) を複素数に変換 r = -1 ! r<0 を未決定状態とみなす do while(r < 0) ! 決定するまで繰り返し old = z ! 現在の値を記憶 z = z*(3d0/4) + 1/(4*(z**3)) ! 1ステップの計算 if (abs(z-old) < 0.01) then ! 差が 0.01 以下なら収束 if (real(z) > 0.5 ) r = 64 ! 1 に収束、結果は 暗灰 if (aimag(z) > 0.5 ) r = 127 ! i に収束、結果は 中灰 if (real(z) < -0.5) r = 191 ! -1 に収束、結果は 明灰 if (aimag(z) < -0.5) r = 255 ! -i に収束、結果は 白 else ! if (abs(z) > 10000) r = 0 ! あまり遠くへ行ったら発散、黒 end if ! end do ! end function converge end program fracta1 矩形領域の左端の x 座標,右端の x 座標,下端の y 座標,上端の y 座標を B_LEFT, B_RIGHT, B_BOTTOM, B_TOP にセットしている。B_ は Border— というつもりである。仲間に同じプレフィッ クスを付けると,間違いが起こりにくくなる。 WIDTH と HEIGHT は画面上に表示する場合の横と縦のピクセル数である。 put_head は C 言語 サブルーチンで,画像のパラメータ (横幅,縦幅,最大ピクセル値) を設定する。do ループは2重 になっている。内側のループでは,y 座標 (虚部) を決め,x 座標 (実部) を左から右に動かして横 1列分のピクセル値 (色) を配列 raw に蓄積し,結果を C 言語サブルーチン put_raw で 出力して いる。converge(x,y) という関数は1点 (x,y) の色を決めるものである。外側のループは y 座標 39 を B_TOP から下へ B_BOTTOM まで動かしている。画像データは上から下へ向けて配置される。 converge 関数では,座標をそのまま複素数の実部と虚部に割り当てて,z の初期値にしている。 (0,1) が虚数単位であることを思い出してほしい。繰り返し計算の中では,計算前の値と計算後 の値の差の絶対値がかなり小さくなったら収束と判定する。収束する点は4点しか無いので,ど こに収束したかの判定は適当におこなっている。また,z があまり大きくなったら発散とみなす。 then の無い if 文は一つの単純文しか続けることができないが,短いので便利である。結果の値は 変数 r にセットすることによって,関数の結果として呼び出し元へ伝えられる。これは,関数定 義の頭部で result(r) という指定がしてあることによる。この変数を結果変数という。これを使 うと,関数の名前を変えるのが容易になる。結果の値は,0 から 255 までの色値に適当に割り当 てた。ループの継続は r の値によっている。ループに入る前に r を -1 にしているので,収束か発 散かの判定が下らない限り,計算が続けられることになる。 練習問題 矩形領域を −2 < x < 2, −2 < y < 2 に広げたり, 0.3 < x < 0.7, 0.3 < y < 0.7 に狭めたりし て計算してみよ。また,WIDTH と HEIGHT をディスプレーに表示できる大きさいっぱいまで広 げてみよ。 14 参考資料 Fortran について詳しく調べるには本が必要である。洋書ならいくらでもあるが,日本語でなけ れば困る人には選択肢は少ない。(FORTRAN 77 の本ならやたらにある。) ここでは2点だけ紹介する。JIS 規格の方は学習にはあまり役立たないだろうが,組み込み関数 などはすべて説明されているので役立つ。JISC ホームページの「JIS 検索」で見ることもできる (印刷はできない)。 もう一つの方は実践的で役立つが,プログラミングのイロハまでは面倒を見 てくれない。 • JIS X3001-1:1998 プログラム言語 Fortran −第 1 部:基底言語, 日本規格協会 • 数値計算のための Fortran90/95 プログラミング入門,牛島省 著,2007 年,森北出版 40
© Copyright 2024 Paperzz