Previous ToC Next

19. ちょっとしたツール

赤木 :プラマーモデル作って時間積分だけではあんまり動きがないから、 もうちょっと動くのを作ってみない?

学生C:というと?

赤木 :簡単なのはプラマーモデル2個の衝突ね。もっと沢山でもいいわ。

学生C:時間積分のプログラムに2つ読む機能とかつけますか?

赤木 :そこは、処理速度としては問題あったりするけど、複数の単純な プログラムの組合せで実現する、っていう、まあ、Unix の精神でどうかしら? つまり、

を作るわけ。位置、速度をずらすのは、パーサ使ってなんかもっと変なことを するのでもいいけど、まあ単純にベクトル与えてシフトでいいでしょう。 複数のスナップショットは、文字列をコンマで区切って与えるのでどうかしら?

学生C: とりあえずそれで2つとか3つぶつけるのができるわけですね。 やってみます。

ずらすのは、hackcode1 から読むとこと書くとこ残して、あとオプションで もってきたベクトルを位置とか速度に足すのか。えーと、、、こんなかな。

まずずらすほうだけ。こんな感じです。

    1:require "clop"
    2:require "./nacsio.cr"
    3:include Math
    4:include Nacsio
    5:
    6:optionstr= <<-END
    7:  Description: program to shift the position and velocity of a snapshot
    8:  Long description: program to shift the position and velocity of a snapshot
    9:
   10:  Short name:-x
   11:  Long name:  --shift-pos
   12:  Value type:  float vector
   13:  Variable name:dx
   14:  Default value:0.0,0.0,0.0
   15:  Description:Shift value for position
   16:  Long description:     Shift value for position
   17:
   18:  Short name:-v
   19:  Long name:  --shift-vel
   20:  Value type:  float vector
   21:  Variable name:dv
   22:  Default value:0.0,0.0,0.0
   23:  Description:Shift value for velocity
   24:  Long description:     Shift value for velocity
   25:END
   26:
   27:clop_init(__LINE__, __FILE__, __DIR__, "optionstr")
   28:options=CLOP.new(optionstr,ARGV)
   29:
   30:class Body
   31:  YAML.mapping(
   32:    pos: {type: Vector3, key: "r",default: [0.0,0.0,0.0].to_v,},
   33:    vel: {type: Vector3, key: "v",default: [0.0,0.0,0.0].to_v,},
   34:  )  
   35:end
   36:
   37:Nacsio.update_commandlog
   38:Nacsio.repeat_on_snapshots(Body.from_yaml(""), true){|pp|
   39:  pp.each{|p|
   40:    p.p.pos += options.dx.to_v
   41:    p.p.vel += options.dv.to_v
   42:    p.print_particle
   43:  }
   44:}
赤木 :じゃあまずは説明ね。

学生C:はい。25行目までは基本的にオプションの文字列で、 -r, -v で位置、 速度のシフト量です。変数名は dx. dv です。

30行目からは粒子型の宣言で、今回位置と速度だけ使うので、それだけの 粒子にしてます。

37行目は update_commandlog で、標準入力からスナップショットファイルの ヘッダ部分読んで、自分のコマンド追加して書くものです。

38行目の repeat_on_snapshots は、nacsio.cr に追加した関数で、これ自体 は

 1:  def repeat_on_snapshots(p = Particle.new, read_all = false)
 2:    sp= CP(typeof(p)).read_particle
 3:    no_time = (sp.y["t"] == nil )
 4:    while sp.y != nil
 5:      pp=[sp]
 6:      time = sp.y["t"].as_f  unless read_all
 7:      while (sp= CP(typeof(p)).read_particle).y != nil &&
 8:            (read_all || no_time || sp.y["t"].as_f == time)
 9:        pp.push sp
 10:     end
 11:     yield pp
 12:   end
 13: end
こういうものです。スナップショットを1個読んだら、というのは、時刻が変わった ら次とみなすとして、なんかする、という処理一般に使える関数があると便利、 ということで、 nacsplot2.cr とかであったループ構造を関数にしてます。

2行目でまずは粒子1つ読み込みます。それから、 一般的に使えるように、ということで、スナップショットの中に時刻データが なかったら、というのを3行目でチェックしてます。4行目の while は、粒子が読める限り読むものですが、ここでは読んでないことに 注意して下さい。ここでは、最初は sp には2行目で読んだものが入ってます。

while の下で配列 pp を作って、最初の要素をさっき読んだsp にして、6行目で時刻 も設定します。但し、引数の read_all が真なら時刻無視して全部読むので時 刻設定しません。7行目からの while で、粒子読んで、 時刻見る時は同じ時刻かどうかチェックします。で、チェックしないか、 同じ時刻なら pp に粒子追加します。 この while 抜けるのは、読んだ粒子の時刻が違うか、あるいは 全部読んだ時ですね。そこで、11行目の

 11:     yield pp
が実行されます。この yield は、この関数呼び出し側の

 Nacsio.repeat_on_snapshots(Body.from_yaml(""), true){|pp|
   pp.each{|p|
     p.p.pos += options.dx.to_v
     p.p.vel += options.dv.to_v
     p.print_particle
   }
 }
のブロックの {} の中に「置き換えられる」ということのようです。但し、 {} の中はこの関数の中というより呼び出し側の中で、呼び出し側の 変数とか使えるみたいです。 a が配列だとして、

  s=1
  a.each{|x| x +=s}
とか書くのと同じ感じですね。 repeat_on_snapshots は粒子と、 read_all の2つの引数ですが、 粒子は CP(T) 型を T に型いれて実体化したもので、これは実は typeof(p) とするだけなので型だけあればよいので Body.from_yaml("") を渡してます。 new でなくて from_yaml なのは、 Body クラスの中身が YAML.mapping だけなので、from_yaml しか 粒子作る方法がないからです。

で、 {|pp| と書いておくと、この pp に yield pp の中身が入って そのあとの } までが実行されて、それが終わると repeat_on_snapshots の次の行に戻るわけです。

で、処理本体は

  pp.each{|p|
    p.p.pos += options.dx.to_v
    p.p.vel += options.dv.to_v
    p.print_particle
  }
で、粒子の、宣言した構造体になってる側で pos, vel に それぞれコマンドラインオプションで指定された値を加えて、 そのあとで yaml 側に残っているデータと合わせて出力、をくり返します。

赤木 :なるほど。プログラム本体は repeat_on_snapshots の中身が ほぼ全部ということね。わりと全体短くていい感じね。 実行結果は?

学生C:例えばこんな感じですね。

 gravity> ./mkplummer -n 3 -Y -s 1
 --- !CommandLog
 command: ./mkplummer -n 3 -Y -s 1
 log: Plummer model created
 --- !Particle
 id: 0
 t: 0.0
 m: 0.3333333333333333
 r:
   x: 0.6959348429044219
   y: 0.5014594064614821
   z: -0.05509228685899097
 v:
   x: -0.07083562690974746
   y: 0.369915247866214
   z: -0.4293037912428686
 --- !Particle
 id: 1
 t: 0.0
 m: 0.3333333333333333
 r:
   x: -0.3818939282329637
   y: -0.10402804359047872
   z: 0.10433845010474857
 v:
   x: 0.5625890492844403
   y: 0.14162824345185604
   z: 0.5622358681719519
 --- !Particle
 id: 2
 t: 0.0
 m: 0.3333333333333333
 r:
   x: -0.31404091467145817
   y: -0.39743136287100317
   z: -0.04924616324575757
 v:
   x: -0.49175342237469283
   y: -0.51154349131807
   z: -0.13293207692908335

 gravity> ./mkplummer -n 3 -Y -s 1|./nacsshift -x 2,3,4
 --- !CommandLog
 command: './mkplummer -n 3 -Y -s 1
 
   ./nacsshift -x 2,3,4'
 log: Plummer model created
 Unhandled exception: Expected sequence, not YAML::Nodes::Mapping at line 7, column 3 (YAML::ParseException)
   from /usr/share/crystal/src/yaml/nodes/nodes.cr:30:9 in 'raise'
   from /usr/share/crystal/src/yaml/from_yaml.cr:104:5 in 'new'
   from nacsio.cr:13:7 in 'initialize'
   from nacsio.cr:12:3 in 'new'
   from nacsshift.cr:45:3 in 'initialize'
   from nacsshift.cr:45:3 in 'new'
   from /usr/share/crystal/src/yaml/from_yaml.cr:12:3 in 'from_yaml'
   from nacsio.cr:108:30 in 'read_particle'
   from nacsio.cr:96:5 in 'read_particle'
   from nacsio.cr:149:5 in '__crystal_main'
   from /usr/share/crystal/src/crystal/main.cr:105:5 in 'main_user_code'
   from /usr/share/crystal/src/crystal/main.cr:91:7 in 'main'
   from /usr/share/crystal/src/crystal/main.cr:114:3 in 'main'
   from __libc_start_main
   from _start
   from ???
座標の数字が 2,3,4 増えてるのがわかるかと。

赤木 :なかなか素晴らしいわね。コマンドも何を実行したかが残るのね。

学生C:そうです。次は複数スナップショットですが、、、これ、 今の nacsio ではできないですよね?そもそもスナップショットを 標準入力からしか読まないじゃないですか?

赤木 :そうねえ、まあ、だから、複数ファイルをつなげて標準入力から読ん で1つに、でもいいんだけど、一応複数ファイルを、でやってみて。 なので、 nacsio を色々な修正していいから。

学生C:わかりました。

といっても、えーと、どうするんだ? read_particle はファイル引数にして、 今までのが動くようにするには STDIN がデフォルト値ならいいか。これは簡 単だな。

update_commandlog は、、、これは今、入力からログ読んで、書くまでやっ ちゃってるけど、そうじゃなくて読んでその値が入ってるインスタンス変数が 返るようにすればいいと。じゃあそういう関数を read_commandlog で作っ て、で、 read もファイルをもらって、今の配列に追加、、、するように もうなってるか。だから

  c = CommandLog.new
  fnames.each{|fn|
    File.open(fn,"r"){|f|
      c1 = read_commandlog(f)
      read(body,ybody,f)
      c.command +=  "\n"+c1.command
      c.log += "\n"+c1.log
    }
  }
こんなので、粒子もコマンドログも追加するだけかな。

というわけで、、、なんかできたかな。

こんなふうです。

    1:require "clop"
    2:require "./nacsio.cr"
    3:include Math
    4:include Nacsio
    5:
    6:optionstr= <<-END
    7:  Description: program to add multiple snapshots to create one file
    8:  Long description: program to add multiple snapshots to create one file
    9:
   10:  Short name:-i
   11:  Long name:  --input-files
   12:  Value type:  string
   13:  Variable name:fnames
   14:  Default value:none
   15:  Description:comma-separated list of input files
   16:  Long description:     comma-separated list of input files
   17:
   18:  Short name:-r
   19:  Long name:  --reset-id
   20:  Value type:  bool
   21:  Variable name:reset_id
   22:  Description:if true, reset id of particles
   23:  Long description:     if true, reset id of particles
   24:
   25:END
   26:
   27:clop_init(__LINE__, __FILE__, __DIR__, "optionstr")
   28:options=CLOP.new(optionstr,ARGV)
   29:def write(body, ybody)
   30:  body.size.times{|i|
   31:    CP.new(body[i], ybody[i]).print_particle
   32:  }
   33:end
   34:def read(body,ybody, f)
   35:  while (sp= CP(typeof(body[0])).read_particle(f)).y != nil
   36:    body.push sp.p
   37:    ybody.push sp.y
   38:  end
   39:end
   40:
   41:class Body
   42:  YAML.mapping(
   43:    id: {type: Int64, default: 0i64,},
   44:  )  
   45:end
   46:
   47:body = Array(Body).new
   48:ybody= Array(YAML::Any).new
   49:
   50:c = CommandLog.new
   51:options.fnames.split(",").each{|fn|
   52:  File.open(fn,"r"){|f|
   53:    c1 = read_commandlog(f)
   54:    c.command +=  "\n"+c1.command
   55:    c.log += "\n"+c1.log
   56:    read(body,ybody,f)
   57:  }
   58:}
   59:print c.add_command.to_nacs
   60:body.each_with_index{|b,i| b.id=i.to_i64} if options.reset_id
   61:write(body,ybody)
赤木 :あら、割合いい感じ?では「解説お願いね。

学生C:まずオプションですが、 -i, -r の2つで、 -i でファイルネーム、 -r は、これいれると id を連続番号にします。

29行目からの write はさっきと同じですが、 34行目からの read は、 update_commandlog をなくして、入力ファイルをを引数にしてそこから読んで ます。

41行目からの Body は、 id だけですね。あとのデータさわらないので。

50行目で、空のコマンドログを作っておきます。

で、 body, ybody を初期化して、 fnames を配列にしてそれぞれから読むのが 51行目です、52行目の File.open(fn, "r") のあとに {|f| ...}がくるのは、

   f= File.open(fn, "r")
   ...
と変わらないですが、このブロック抜けたらファイル閉じるとかがあります。 で、53行目で コマンドログを読んで、54,55 行目で最初空のコマンドログに今読んだのを追加していきます。それから56行目で粒子を読んで配列に追加です。

ファイル読むのが終わったら、59 行目で、コマンドログに現在のコマンド名 を追加して出力です。で、オプションで指定があれば60行目でidふりなおして、 最後に write で粒子を出力でおしまいです。

赤木 :2048粒子のプラマーモデルちょっと離したところからぶつけるとかしてみて。

学生C:コマンドはこんなのですかね。

 ./mkplummer -n 2048 -Y -s 1 > p2k.yml
 nacsshift < p2k.yml > tmp0 -x -3,0,0 -v 0.3,-0.2,0
 nacsshift < p2k.yml > tmp1 -x 3,0,0 -v -0.3,0.2,0
 nacsadd -r -i tmp0,tmp1 > 4k.yml
 env LD_LIBRARY_PATH=../../src/fdps-crystal/ ~/src/fdps-crystal/fdpscr -O 4k-out.yml -t 20 -d 1 -T 0.5 < 4k.yml
 env GKS_WSTYPE=jpg nacsplot3 < 4k-out.yml -w 10
結果の絵はこんな感じです。

Figure 27: 初期条件

Figure 28: t=6

Figure 29: t=12

Figure 30: t=18

赤木 :まあそれっぽいわよね。

19.1. 課題

この章は息抜きなので課題はありません。

19.2. まとめ

  1. スナップショットファイル加工するいくつかのツールを作ってみた。

19.3. 参考資料

特になし。この文書で作っているプログラムの github
Previous ToC Next