地球流体電脳倶楽部 / 電脳Ruby / チュートリアル / 基礎編

Dennou-Rubyチュートリアルページ(基礎編)

ここでは、まずRubyのスクリプト言語としての基本的な使い方を紹介します。 そのあと数値計算・配列演算に必要なパッケージの紹介、 地球科学のデータを読み書きしたり可視化したりする方法の紹介へと 続きます。最後のほうでは流体数値計算もやっています。

1. Rubyの基本的な使い方
2. NArrayを使って配列演算
3. Ruby-DCLで絵を描く
4. テキストデータの読み書き
5. Ruby-NetCDFでデータの読み書き
6. 流体数値計算も

1. Rubyの基本的な使い方

RubyはPerlやシェルスクリプトと同じようなスクリプト言語です。 スクリプトを記述したファイルをつくりRubyを呼ぶと、中身が実行されます。 まずエディタで中身が

 print("Hello, World\n")

と書かれたファイルをつくり、test1.rb という名前で保存します。 そして

 % ruby test1.rb

とコマンドを実行すると(%はプロンプト、青太字が実際に入力する文字です)、

 Hello, World

と表示されます。 スクリプトは「""でくくられた部分(=文字列)を表示せよ」というものですね。 「\n」は改行を表します。

日本語も使えます。また 先頭に #! /usr/bin/env ruby と書いておき、chmod +x で実行可能にしておけば、 % ./test1.rb ですみます。

 % chmod +x test2.rb
 % cat test2.rb
 #! /usr/bin/env ruby
 print("こんにちわ、世界\n")

 % ./test2.rb
 こんにちわ、世界

エディタでファイルを編集しなくても、インラインで実行することも可能です。 -e オプションを使います。

 % ruby -e 'print("Hello, World\n")'
 Hello, World

また、irbという対話型のrubyも用意されています。 irb(main):001:0> などというプロンプトが現れるので、 そこにスクリプトを記述すると即座に結果が表示されます。

 % irb
 irb(main):001:0> print("Hello, World\n")
 Hello, World                              ←標準出力
 nil                                       ←irbでは戻り値も表示
 irb(main):002:0> exit
 % 

emacs 上の実行環境として irbsh というものもあります (別途インストールが必要、http://www.rubyist.net/~rubikitch/computer/irbsh/ あたりを参照のこと)。

irbsh[16:19](main):001:0> print("Hello, World\n") 行頭から始めると irb
Hello, World                              ←標準出力
nil                                       ←irbsh でも戻り値を表示
[pwd:~]
irbsh[16:19](main):002:0>
irbsh[16:20](*SHELL*):003:0>  ls       ←行頭に空白を入れると SHELL
~ $ ls
Choices  Desktop  Mail	      bin     doc      lib	    rpm  tmp
DATA	 GNUstep  Xrootenv.0  dennou  install  public_html  src  work

以下では irb を使ってrubyの基本的な使い方を見ていくことにします。

Rubyには型宣言は不要です。

 % irb
 irb(main):001:0> a = 2       # 整数
 => 2
 irb(main):002:0> b = 3.5     # 実数
 => 3.5
 irb(main):003:0> c = "apple" # 文字列
 => "apple"

"="の両側の空白は入れても入れなくてもかまいません。 これらは、a, b, c という変数に 2 という整数、3.5という実数、 "apple" という文字列を代入したと考えることもできますが、 実際には 2 という整数オブジェクト、 3.5という実数オブジェクト、 "apple" という文字列オブジェクトに a, b, c という名札をつけたということに相当しています。

オブジェクトはその値だけでなく型や大きさなど諸々の情報も自分で持っています。

 irb(main):004:0> a          # オブジェクトの値
 => 2
 irb(main):005:0> b.class    # オブジェクトの型
 => Float
 irb(main):006:0> c.size     # オブジェクトの大きさ
 => 5
 irb(main):007:0> 3.5.class  # b.class と同じ
 => Float

上の class や size はメソッドと呼ばれます。 オブジェクトに対して「型を述べよ」「大きさを答えよ」などとメッセージを送っていることになります。オブジェクトはそれに対して「実数だ」「5だ」などと答えてくれます。

演算も当然できます。いろいろやってみてください。

 irb(main):008:0> d = a * ( b**2 + 3.0e2 )
 => 624.5
 irb(main):009:0> e = c + "orange"
 => "appleorange"             # 文字列の足し算は単に順に並べるだけ。

ちなみに複数の変数をいっぺんに操作することもできます。

 irb(main):010:0> e,f = a*b, e+"lemon"
 => [7.0, "appleorangelemon"]

大文字ではじめると定数になります。 定数を書きかえようとすると警告が出ます。

 irb(main):011:0> A = 3.1415927
 => 3.1415927
 irb(main):012:0> A = 1.7320508
 (irb):22: warning: already initialized constant A
 => 1.7320508

'〜'でくくった部分も文字列になりますが、こちらは制御文字もそのまま 文字として扱います。

 irb(main):013:0> print 'apple\n'
 apple\n=> nil                      # 改行されずに\nが表示される

型変換は to_s, to_i, to_f といったメソッドを使います。

irb(main):101:0> g = b.to_s
     => "3.5"                       # to_s 文字列に変換
irb(main):102:0> h = b.to_i
     => 3                           # to_i 整数に変換
irb(main):103:0> a.to_f/3 - a/3
     => 0.666666666666667           # to_f 実数に変換

オブジェクトを順番に並べて [ ] でくくったものが配列です。 配列の中は同じ型である必要はありません。

irb(main):113:0> ary1 = [ 3, b, d-3.0, "orange"]
     => [3, 3.5, 621.5, "orange"]
irb(main):114:0> ary1[0]
     => 3                           # Cと同じで最初の要素が0番
irb(main):115:0> ary1[1..3]
     => [3.5, 621.5, "orange"]      # 1番から3番まで
irb(main):116:0> ary1[-1]
     => "orange"                    # 負の数はうしろから数える
irb(main):117:0> ary2 = [a, ary1]
     => [2, [3, 3.5, 621.5, "orange"]]   # 配列の中に配列があっても大丈夫。
irb(main):118:0> ary2[1][3]
     => "orange"                    # ary2[1]の3番の要素

プログラムの流れの制御には、for, if, while, caseなどが使えます。 もちろんirbの中でも使えます。

irb(main):121:0> for i in 1..5
irb(main):122:1>  print(i,"\n")
irb(main):123:1> end

プロンプトの">"の左の数字はネストの深さを表していて、 ネストがなくなる3行目まではなにもおこなわれません。

irb(main):131:0> if( a==3 ) then    # thenはなくてもよい
irb(main):132:1*  print "Hello\n"   # 曖昧でない場合は括弧を省略可能
irb(main):133:1> else
irb(main):134:1*  print a,"\n" if a != 4   # if文は後付けも可能
irb(main):135:1> end

配列の各要素に対して順に処理をおこなうイテレータというものもあります。

["dog","cat","bird"].each {|i|
 printf("a %s\n", i)
}

each は {...} (または do ... end) を引数にとるメソッドです。 配列の各要素を | | の中の変数(ここでは i )に格納し、 その i に対する操作を記述します。 イテレータにはいろいろなものがあり、 より直接的にループを記述することができます。 for を使うより便利なことが多いです。

(1..10).each {|i|
  printf( "%02d\n", i )
}

10.times do                      # 10回繰り返し
 print "hoge"
end

loop do                          # 無限ループ
 break if STDIN.gets=="\n"       # 空行を入力すればループを抜ける
end

loop do
 break if /^$/ =~ gets           # 上と同じ(/.../は正規表現、=~は正規表現とマッチしているかどうか、getsはSTDIN.gets
end

注:イテレータ(ブロック)は、変数のスコープ(有効範囲)を作ります。次のよ うなスクリプトはエラーになります。

(1..10).each {|i|
  b = i
}

b   # {…}の外では b が存在しないのでエラーになる

エラーを避けるには

b = 0
(1..10).each {|i|
  b = i
}
b   # 10

のようにします。

数学関数は、Mathモジュールを使います。

a = Math::cos(0) # Mathというモジュールのcosという関数を呼ぶ
p = Math::PI

include文を使うと、以降は"Math::"の部分が不要になります。

include Math
a = cos(PI)

メソッドを定義するには def を使います。

def wa(a,b,c)
 d = a+b+c
 return d        # 戻り値の指定。なければ最後の文の戻り値が返される。
end
e = wa(3,4,6)    # 定義前に実行することはできない。

すべてのオブジェクトはなんらかのクラスに属しています。
クラスとはオブジェクトの種類を表すもので、 クラス毎にオブジェクトに対する手続き(メソッド)が定義されています。 例えば「3.5」はFloatというクラスのオブジェクトであり、 Floatクラスでは +, -, *, / (四則演算)や abs, floor, to_i, to_s といったメソッドが定義されています。 また、モジュールとはメソッドや定数などの機能をまとめた集まりで、 必要に応じてインクルードして使います。

ここから先では、NArrayクラス、DCLモジュール、NetCDFクラスといったものを 用いて機能を拡張していきます。 自分でクラスを作る方法はそのあとに紹介します。

2. NArrayを使って配列演算

Rubyに標準装備されている配列(Array)は、 オブジェクトを順番に並べたものであり、これもまた1つのオブジェクトです。 上の例にあったように、配列の要素は同じ型である必要はなく 任意のオブジェクトを格納できるという柔軟なものなので、 その分計算速度は犠牲になっています。 そこでわれわれが数値計算をするときには NArray というクラスパッケージを用いることにします。

NArrayのメソッドについては NArray メソッド一覧 に書いてありますので適宜参照してください。

つくりかた

NArrayクラスを用いるには、まず

require "narray"

とします。これでNArrayクラスの定義が読み込まれます。 新たに配列を生成する(つまりNArrayクラスのオブジェクトを新たに生成する)には、 NArray.new("型",大きさ,大きさ,...) とします。 が通常はそれより簡単な NArray.型(大きさ,大きさ,...) を用います。たとえば3x4の単精度実数配列 ary1 を作るには

ary1 = NArray.sfloat(3,4)

です。これだけでは中身の値はすべて0です。 次にこの配列オブジェクトに中身をセットするメソッドを作用させます。

ary2 = NArray.sfloat(6).fill!(3.0)  # 長さ6の配列で、中身はすべて3.0
ary3 = NArray.sfloat(10).indgen!    # 1ずつ増やした値をセット。

また、通常の配列(Array)からNArrayに変換するには

ary4 = [ 2, 3, 4.5, 8 ]             # 通常の配列
ary5 = NArray.to_na(ary4)           # ary4 を NArray に変換

とします。NArrayでは、例えば中に1つでも実数が含まれていれば実数の配列となり、 要素はすべて実数になります。

インデックス

インデックスはArrayやC言語と同じで、 最初の要素番号は0、最後の要素番号は要素数-1です。 多次元のインデックスの順序はFortranと同じで、前の要素から数字を増やします。

ary6 = NArray.sfloat(4,3).indgen!(1.0,2.0) # 1から2ずつ増やした値をセット。
ary6[0,0]        # 最初の要素
ary6[1,0]        # 次の要素
ary6[1..2,0..-1] # 範囲取り出し
ary6[true,1]     # trueは0..-1と同じ
ary6[0..1,0] = 999 # 値の代入
ary6

演算

演算は要素ごとにやってくれます。

ary7 = NArray.sfloat(4,3).fill!(3.0)
ary6 + ary7
ary6 * ary7
ary6 % ary7
ary7 - 1.5
ary6 ** 2

演習問題

笑介くんのクラス20人のテストの点数は、 理科が
65 80 67 35 58 60 72 75 68 92 36 50 2 58.5 46 42 78 62 84 70
英語が
44 87 100 63 52 60 58 73 55 86 29 56 89 23 65 84 64 27 86 84
でした。 平均点、標準偏差、偏差値を教科別、および合計について求め、 点数の高い順に並べかえなさい。

3. Ruby-DCLで絵を描く

この節はDCL(Fortran版)を使ったことがあることを前提にしています。 使ったことがない場合は、 可視化に関してはGPhysを使った可視化(GGraph、またはGAVE)から習得したほうが 理解しやすいかもしれません。 GPhysはNetCDF,GrADS,grib等のデータ形式で扱われる格子点データを 取り扱うクラスで、ここまでの知識があれば GPhys チュートリアル に進んでいけば、そのような形式のデータの解析や可視化をおこなうことができます。 ただしGGraphもGAVEも内部でRuby-DCLを使っていて、 細かな設定をしようと思うと依然Ruby-DCLの知識が必要になります。 Ruby-DCLについて一から習得するには、 ごくらくDCLを読んで下さい。

まずはRuby-DCLを使って図を描いてみましょう。

require "numru/dcl"

を加えると、以降DCLモジュールを使うことができます。 DCLのサブルーチンを

NumRu::DCL::gropn(1)

などのように書いてやれば Fortran版のDCLと同じように動作します。 ただしこれでは長いので通常はNumRuはincludeしておきます。

include NumRu
DCL::gropn(1)
DCL::grfrm
DCL::sgtxr(0.5,0.5,"abc")
DCL::grcls

以降はスクリプトを書いてrubyコマンドを使うことにします。 配列には単精度実数(sfloat)のNArrayを与えます。

折れ線図

例: hop.rb

"#"より後ろはコメントで、無視されます。 スクリプトを使う場合には、 p というコマンド(オブジェクトの内容を人間に読みやすい形で出力)を利用します。

上のファイルをダウンロードして、

 % ruby hop.rb

で以下のようになります。

Fortran77版のDCLと比べると、 配列の長さを渡す引数は与える必要がなくなっています。 (例: CALL USGRPH(N,X,Y) -> DCL::usgrph(x,y) )

include DCL

と書いておけば、以降 DCL:: の部分を省略することもできます。

等高線・ぬりわけ図

例: contour.rb

行頭の"=begin"から"=end"までの部分もコメントになります。 21行目や34行目ではコマンドラインオプションを調べています。 ARGVはコマンドラインオプションの配列です。

 % ruby contour.rb -color

とすれば、下のような色つきの図ができます。 また、40行目では数値をto_sで文字列に変換して画面に出力しています。 42行目のように"date"クラスを使うと今日の日付も表示します。 #{...}も結果を文字列に変換します。

演習問題

笑介くんのクラスのテスト結果について、点数の頻度分布をグラフにしなさい。

手持ちのDCLお絵かきFortranプログラムをRubyに書き直しなさい (なければサンプルを書き直す)。

4. テキストデータの読み書き

RubyはPerlを意識したスクリプト言語であることもあって、 文字列操作の機能は強力です。 それを利用して、テキストファイルに書かれたデータの読み書きをしてみましょう。

テキストデータを読んで図を描く

例として、NOAAにあるラジオゾンデアーカイブ http://www.ncdc.noaa.gov/oa/cab/igra/ から地点を1つ選びラジオゾンデ観測データを取得します。 "ASCII Files" のリンクをたどっていくとテキストファイル(をzipで固めたもの) が取得できます。 館野(=つくば)の最近のデータを一時的に ここにおいておきます。

まずテキストファイルの中身を見てみると、

 % more 47646.y2d
#4764620040101002330  67
21101100B   31    58B   59  250   10
10100000   123B   66B   90    0    0
20 98600 -9999    64B  100-9999-9999
30 95200 -9999 -9999 -9999  120   30
10 92500   759B   24B   80  210   20
30 91300 -9999 -9999 -9999  250   20
30 90000 -9999 -9999 -9999  260   51
30 87500 -9999 -9999 -9999  285  108
10 85000  1435B  -25B   80  280  118

  :

20   770 -9999  -437A-9999-9999-9999
#4764620040101069999  25
31101100B   31 -9999 -9999  140   15
30 93200 -9999 -9999 -9999  135   20

  :

となっています。このファイルをRubyで読み込むには、

file1 = File.open("47646.y2d","r")

とします。file1がこのファイルに対応するFileクラスのオブジェクトになります ("r" は read only)。 file1から1行読み出すには

file1.gets

とします。getsをくり返すと後続の行を続けて読み出します。 ファイルの先頭に戻るには

file1.rewind

とします。 readme.txt を見ると、行頭に"#"が付いている行がヘッダー、 それ以外の行に風や温度などのデータが入っているようです。 そこで読み込んだ行がヘッダーなら日付を読んで改ページし、 データなら高度と温度をプロットするようにしてみます。

例: rsread.rb

正規表現については こちらこちらを見てください。 最初はややこしそうで面食らいますが、 覚えたものからだんだんに使っていけばよいでしょう。 "\d"が数字、{5}は「5回繰り返し」です。 ( )でくくった部分にマッチしたものが前から順番に $1 $2 ... に入るので、 直後の行でそれを変数に代入することができます。

テキストデータの書き出し

上のファイルから特定の年月の特定の変数だけを取り出してみます。 テキストファイルの書き出しには puts 、print あるいは printf といった メソッドを用います。 puts は引数を1行分として書き出すので最後に必ず改行が入るようになっています。 print や printf では改行も自分で制御します。

例: rsrw.rb

テキスト処理についてはたくさんのメソッドが用意されており、 連結(+)・分割(split)・比較(<=>,<,<=,>,=>,==)・置換(sub,gsub)・ 一部取り出し([ ])・大文字小文字変換(upcase,downcase) などなど充実していますので、 これらを用いればさまざまな処理が自在にできます。詳しくは マニュアルなどを参照すればよいでしょう。

5. Ruby-NetCDFでデータの読み書き

netcdfライブラリを用いると、NetCDFファイルを読み書きすることができます。 手始めにirbでNetCDFファイルを読んでみましょう。 サンプルのNetCDFファイルは ここ にあります(1月平均の気温の分布)。

require "numru/netcdf"
include NumRu
file1 = NetCDF.open("T.jan.nc")        # NetCDFファイルオブジェクトをつくる
var_temp = file1.var("T")              # 変数をオープン
temp = var_temp.get                    # 変数の値を取り出す
p temp                                 # 中身を表示
p temp[0..4,10,true]                   # 配列の一部を表示

file1は1つのNetCDFファイルに対応するオブジェクト、 var_temp は file の中の1つの変数に対応するオブジェクトです。 getメソッドを用いて、var_tempから値を取り出しています。 (file1, var_temp, temp はただの名前ですから好きな名前を使ってかまいません。) 最低限これだけの手順で読み込むことができます。 中身を表示させるとNArrayとして読み込まれていることがわかります。

NetCDFデータを読んで図を描く

上の手順でNArrayとして読み込まれたので、 これをRuby-DCLを使って描画することができます。

例: ncread.rb

この例では、平面図を下から1層ずつ描いています。 この例は簡単のため特定のNetCDFファイルの構造に依存した書き方に なっていますが、必要な事柄をオブジェクトに問い合わせる形にしてゆけば、 別のファイルにも対応できるよう拡張していくことが比較的かんたんにできます。 NetCDFファイル(等)の1つの変数とそれに付随する変数名や軸情報をまとめて 1個のオブジェクトとして取り扱うのが GPhysです。

演習問題

どの高度をとるかを実行時選べるようにする。

月平均の東西平均東西風について、緯度・高度断面を作る。

年月を変えられるようにし、ファイルがなければftpでとってくるようにする。

NetCDFファイルを新たに作る

次はNetCDFファイルを作成してみます。 まず必要なことは次元の定義(def_dim)と変数の定義(def_var)です。 この後にenddefとするとNetCDFファイルの先頭部分が作成されます。 それからputメソッドを用いて値を書き込んでいきます。 変数の一部にだけ書き込みたい場合などには、 "start"や"end"といったオプショナルな引数をつけます(これは 「ハッシュ」を用いて実現されています)。

例: nccreate.rb

演習問題

笑介くんのクラスのテスト結果をNetCDFにしなさい。

月平均の東西平均東西風を、NetCDFファイルにする。

NetCDFを読んでNetCDFで書き出す

上の演習問題のような、 NetCDFを読みこみ何らかの解析処理を施してNetCDFで書き出す、 といったことをおこなうために、 NetCDFクラスを自分用に拡張していきます。 まずはNetCDFファイルを読み込み、 それをそのままNetCDFファイルに書き出してみます。

例: nccopy.rb

each_dim, each_var, each_att といったイテレータを用いています。 イテレータに慣れていないとちょっとややこしいですが、 汎用性が高くなっています。

演習問題

指定した変数を除いて全部コピーするというメソッドに変える。

これを利用して、月平均の東西平均東西風をNetCDFファイルにする。

データ解析は応用編で。

このようなスクリプトをどんどん増やしていけば、 データ解析がかんたんにできるようになります。 欠損値を扱ったりgradsファイルを読み込んだりする ちょっとすすんだ使い方編や、 座標を持つ、格子状に離散化された物理量のクラス"GPhys"を扱ったりする GPhys チュートリアル に進んでいけば、より高度な使い方ができるようになるでしょう。

6. 流体数値計算も

Fortran(or C)ライクに数値計算

Fortranでやっていたような数値計算も、 Rubyでより簡単に書くことができます。 以下の例は、 ベナール対流でchaotic advectionが起こる様子を計算したものです。 ごく単純な周期変動をする流れ場で、流体が複雑な動きをします。

例: benard.rb

クラスを作って数値計算

上の例はFortranで書かれたアルゴリズムをそのまま用いて Rubyに書き直したものですが、 今度はクラスを作って似たようなことをしてみましょう。 次の例はソリトンの数値計算実験です。 クラスを新たにつくり、Fieldと名前を付けます。 initializeで座標系を定義し、初期値を設定、 空間微分の定義、KdV方程式、時間積分などのメソッドを作ります。

例: kdv.rb

演習問題

初期波形をいろいろ変えて実験。 Fieldクラスの中に波形を与えるメソッドを新たに定義し、 外から変えられるようにする。


地球流体電脳倶楽部 / 電脳Ruby / チュートリアル / 基礎編

Copyright (C) 2003-2006 GFD Dennou Club. All Rights Reserved.