Previous ToC Next

10. 多体問題

10.1. 多体問題の設定と単位系

赤木 : 今回は多体問題ということで。まあその、本当に大規模な数値計算す るなら最近だと全部自分でプログラム書くってだいぶ無理で、並列化のところ にパッケージ使うとか、初めから全部人が書いたのをもらってきて使うか なんだけど、基本的な仕掛けは自分でわかってて欲しいのね。

学生C:そういうものですか?

赤木 :そういうものよ。ここではあんまり科学的な意味とかはを追求しないこ とにして、初期条件としては沢山星がある、例えば半径1の球の中の一様分布 とかにしてみて。

学生C: 質量と速度はどうしますか?あと1ってどういう単位ですか?光年とかですか?

赤木 :ケプラー問題では太陽質量を1、重力定数 G も1にしたじゃない? そういう感じで、星団の質量を 1、重力定数 G も1としてみて。 星の質量は、まずは N 個ならみんな等しく ね。

学生C:ケプラー問題だと、長さの単位は天文単位で、 が1年という ことだったじゃないですか?

赤木 :あら、別にそんなことはないわよ。それだと地球の軌道しかし計算で きないみたいじゃない?

学生C:あれ、そうじゃないんですか?木星なら軌道半径を 5.2 にしないと、周期が12年とかにならないですよね?

赤木 :あー、これ割合説明がというか理解が難しい?1が1天文単位だと思う と地球の軌道だけど、1が 5.2 天文単位だと思うと、半径1の軌道が木星の軌 道になるの。

学生C:?

赤木 :うーん、これ意外に説明難しいわね。運動方程式は

だったわけでしょ?まず、単位系をとりなおす、というのがどういう意味か、 ということね。長さの単位って、 1m とか 1cm とか、あるいは1天文単位とか、 あと星団とか銀河だと1パーセク、あ、何故か知らないけど天文学ではパーセ ク使って光年は使わないのね、とか色々あるでしょ?

今、物理学では単位といえば SI単位のことで、それ以外駄目みたいな話になっ てるから、単位系を勝手にとるってあんまり馴染みがないかもしれないけど、 天文学では系の大きさにみあった単位使わないとすごく計算間違いしやす くなるし、計算機でも上手く計算できなかったりするから、単位系とりなおす 話は重要なの。

SI だと、Gの値は、1 kg のものが1m 離れたところにある時にそれから受ける 重力加速度を を単位にして測ったものでしょ?そうすると、 例えば を単位としたらどうなるかしら?加速度の単位も になるわけね。

学生C: のところにあるから、 1m の時より に小さくなりま すね。で、加速度が1というのも を1と思うわけですね。だか らもうひとつ X がはいって G はに小さくなります。

赤木 :多分あってるんじゃないかしら。じゃあ時間の単位のほうを、 1秒でなくて秒にしたら?

学生C: 加速度の単位が 1秒あたり、「1秒あたり動く距離」の変化だったの が、秒あたり、「秒あたり動く距離」の変化に変わるわけですね。例えば なら、1m/s は 2m/(2s) だから、 秒後に で、それはで、だから で大きくな ればいいわけですね。

赤木 :そう。そうすると、を上手くとれば が変わらない でしょ?

学生C: ということですね。だから ですね。

赤木 :そう。これケプラーの第三法則と同じ形でしょ?

学生C:第三法則ってなんでしたっけ?

赤木 : 軌道周期が軌道長半径の 3/2乗ってやつ。

学生C:確かに同じですが、でも?

赤木 :つまり、天文単位を1、1年をにとる代わりに、 木星の軌道長半径を1、木曜の軌道周期をにする単位系でもいいし、 他の惑星でもなんでもいいわけ。

星団でも、例えば1パーセクの中に合計で太陽質量の100倍があると思って、それを で計算するなら、時間の単位が決まるわけ。

あ、質量の単位を 倍にしたらどうなるかしら?

学生C:加速度は 倍だから、 倍?

赤木 :そうね。だから、地球の周りの人工衛星でも月でも、木星の衛星でも、 土星のリングでも、 として、あと長さの単位を決めると、それから 時間の単位が決まるわけ。で、それはもちろん、円運動の周期が になるように、つまり、その長さのところでの円運動 の角速度が1になるように、ということになるわけ。

これは太陽系でなくてお互いの重力で運動する系でも式変形の原理は同じだか ら、同じように として、あと長さの単位を決めると、それから 時間の単位が決まるわけ。

学生C:でも星団みたいなのだと星は円運動するわけじゃないし、ケプラー運 動でもないですよね?時間の意味ってなんですか?

赤木 :まず、形式的にはとにかく、実際の何年というのに対応するわけ。あと、星 の運動は色々だけど、平均の速度とか、系の特徴的な半径とか、そういうのは定義できるわね。

学生C:うーん、そういうものですか。

赤木 :まあ、実際に星動かしてみるともうちょっと気分わかるかも。位置は 半径1の球の中の乱数でやるとして、速度はなんかパラメータ与えてやっぱり その半径の球の中で一様とかしてみて。熱力学的にはボルツマン分布とかがもっ ともらしいんだけど、星団でボルツマン分布って重力で束縛されない(無限遠 までいっちゃう)粒子がでてきてちょっと取り扱いが厄介なの。

学生C: だからといって速度分布や粒子分布を適当に作ることになんか意味あるんですか?

赤木 :うん、そうね、これ、わりと意味ないわけでもないの。これは要する に、重力多体系というものはどう進化するか、という話ね。まず、理論的には、 沢山の粒子でできてるわけだから、統計力学で記述されるはずで、古典統計だ からボルツマン分布になるんだけど、さっきの話でボルツマン分布だと粒子が 無限遠にいっちゃうからそうならないわけ。でも、1つ1つの星についてみると、 ランダムに速度変化して、速度というかエネルギーの空間の中で拡散していく のね。なので、そのうちに、初期条件がなんでも、同じような分布になるとい うか、同じような進化をするようになるの。

学生C:はあ。

赤木 :まあ、ちょっと実際にやってみて。

学生C:了解です。

赤木 :あ、あとね、ケプラー問題では、軌道が楕円軌道で変化しなかったけ ど、多体問題ではみんな勝手に動くわけで、2つの粒子がすごく近くを通るこ とがあるのね。そうすると、タイムステップいくら短くしておいても破綻する の。

この、近くを通るのを、英語では close encounter っていうの。ちょっと余 談だけど、君くらい若いと知らないかもだけど、スピルバーグの映画に 「未知との遭遇」というのがあって、英語の原題は Close encounters of the third kind なのね。この第三種というのは誰かが勝手に作った宇宙人だかと の遭遇の分類だけど、 close encounter 自体は普通につかう言葉なのね。

学生C:はい?えーと、、、

赤木 :なので、今回、昔から使われている方法なんだけど、重力ポテンシャ ルの式を、 じゃなくて としてみて。 は定数で、パラメータとして入力できるようにして。

学生C:そうすると、軌道とか変わるわけで、なんか違うものを計算している ことにならないですか?

赤木 :もちろん違うんだけど、統計的には同じ性質になると信じましょうみ たいな感じ。まあ、 いくつか変えてみて、同じような性質がで てきたら大丈夫とかね。ちなみにこの を重力 softening (ソフトニング)というの。

学生C:そんなものでしょうか?

赤木 :あ、もちろん、もうちょっと色々もっともらしい議論はできるし、じゃ あ結果が を0にもっていった極限で収束するのかとかそういう 話はできるし、しないといけないわけよ。まあそういうのはそのうちにね。

学生C:わかりました。では実際に作ってみます。

(このあと無限に色々あったが全部省略)

10.2. 多体問題のプログラム

学生C:あ、なんか動いてるような気がします。プログラム particle0.cr と いうのを作りました。入力パラメータはこんな感じです

  -n     --numner-of-particles          int        2 Number of particles
  -s               --softening        float      0.0 Size of softening
  -e            --eccentricity        float      0.0 Eccentricity. Used only when n=2
  -d               --step-size        float     0.01 Size of timestep
  -T                --end-time        float      1.0 Time to stop integration
  -w             --window-size        float      1.5 Window size for plotting
  -e                     --ecc        float      0.0 Initial eccentricity of the orbit
  -v          --velocity-scale        float      0.5 Scaling factor for the initial velocity

粒子数、ソフトニングを指定できて、あとn=2の時には離心率指定して、そう でない時には velocity scale のほうを与えます。後は時間刻みと、シミュレー ションやめる時刻と、絵書くのでその座標範囲いれます。この辺はケプラー問 題の時のを流用してます。

で、アニメーションがでるんですが、2体だとそれっぽく回って、3とかそれ以 上だとなんか動いてました。

赤木 :んーと、計算正しいかどうかってなんかわからない?まあその前にプ ログラム見せて。

学生C:はい。

    1:require "grlib"
    2:require "clop"
    3:include Math
    4:include GR
    5:
    6:optionstr= <<-END
    7:  Description: Test integrator  driver for Kepler problem
    8:  Long description:
    9:    Test integrator driver for Kepker problem
   10:    (c) 2020, Jun Makino
   11:
   12:  Short name:           -n
   13:  Long name:  --numner-of-particles
   14:  Value type:int
   15:  Default value: 2
   16:  Variable name: n
   17:  Description:Number of particles
   18:  Long description:     Number of particles
   19:
   20:  Short name:           -s
   21:  Long name:  --softening
   22:  Value type:float
   23:  Default value: 0.0
   24:  Variable name: eps
   25:  Description:Size of softening
   26:  Long description:     Size of softening
   27:
   28:  Short name:           -e
   29:  Long name:  --eccentricity
   30:  Value type:float
   31:  Default value: 0.0
   32:  Variable name: ecc
   33:  Description:Eccentricity. Used only when n=2
   34:  Long description:     Eccentricity. Used only when n=2
   35:
   36:  Short name:           -d
   37:  Long name:  --step-size
   38:  Value type:float
   39:  Default value: 0.01
   40:  Variable name: h
   41:  Description:Size of timestep
   42:  Long description:     Size of timestep
   43:
   44:  Short name: -T
   45:  Long name:  --end-time
   46:  Value type:int
   47:  Default value:1.0
   48:  Value type:float
   49:  Variable name:tend
   50:  Description:Time to stop integration
   51:  Long description:     Time to stop integration
   52:
   53:  Short name:-w
   54:  Long name:  --window-size
   55:  Value type:  float
   56:  Variable name:wsize
   57:  Default value:1.5
   58:  Description:Window size for plotting
   59:  Long description:
   60:    Window size for plotting orbit. Window is [-wsize, wsize] for both of
   61:    x and y coordinates
   62:
   63:  Short name:-e
   64:  Long name:--ecc
   65:  Value type:float
   66:  Default value:0.0
   67:  Variable name:ecc
   68:  Description:Initial eccentricity of the orbit
   69:  Long description:     Initial eccentricity of the orbit
   70:
   71:
   72:  Short name:-v
   73:  Long name:--velocity-scale
   74:  Value type:float
   75:  Default value:0.5
   76:  Variable name:vscale
   77:  Description:Scaling factor for the initial velocity
   78:  Long description:
   79:    Scaling factor for the initial velocity.
   80:    positions and velocities are set from random vectors within
   81:    spheres of radius 1 and vscale.
   82:
   83:END
   84:
   85:clop_init(__LINE__, __FILE__, __DIR__, "optionstr")
   86:options=CLOP.new(optionstr,ARGV)
   87:
   88:require "./vector3.cr"
   89:class Particle
   90:  property :m, :x, :v, :acc, :phi
   91:  def initialize(m : Float64=0, x : Vector3=Vector3.new(0,0,0),
   92:                 v : Vector3=Vector3.new(0,0,0))
   93:    @m = m; @x = x;  @v = v
   94:    @acc = Vector3.new(0,0,0); @phi=0.0
   95:  end
   96:  def self.random(m, rx, rv)
   97:    Particle.new(m.to_f, randomvector(rx), randomvector(rv))
   98:  end
   99:  def calc_gravity(p,eps2)
  100:    dr = @x - p.x
  101:    r2inv = 1.0/(dr*dr+eps2)
  102:    rinv = sqrt(r2inv)
  103:    r3inv = r2inv*rinv
  104:    @phi -= p.m*rinv
  105:    p.phi -= @m*rinv
  106:    @acc -= p.m*r3inv*dr
  107:    p.acc += @m*r3inv*dr
  108:  end
  109:    
  110:end
  111:
  112:class ParticleSystem
  113:  property :particles
  114:  def initialize()
  115:    @particles = Array(Particle).new
  116:  end
  117:  def +(p : Particle)
  118:    @particles.push p
  119:    self
  120:  end
  121:  def self.random(n, rx, rv)
  122:    ps=ParticleSystem.new
  123:    m = 1.0/n
  124:    n.times{ ps += Particle.random(m,rx,rv)}
  125:    ps.adjust_cm
  126:    ps
  127:  end
  128:  def self.twobody(x,v)
  129:    ps=ParticleSystem.new
  130:    n=2
  131:    m = 1.0/n
  132:    ps += Particle.new(m,x,v)
  133:    ps += Particle.new(m,-x,-v)
  134:    ps
  135:  end
  136:  def clear_gravity
  137:    @particles.each{|p|p.acc=Vector3.new; p.phi=0.0}
  138:  end
  139:  def calc_gravity(eps2)
  140:    @particles.each_with_index{|p,i|
  141:      ((i+1)..(particles.size-1)).each{|j|
  142:        p.calc_gravity(@particles[j], eps2)
  143:      }
  144:    }
  145:  end
  146:  def leapfrog(h,eps2)
  147:    @particles.each{|p|p.x += p.v*h + p.acc*(h*h*0.5)}
  148:    @particles.each{|p|p.v += p.acc*(h*0.5)}
  149:    clear_gravity
  150:    calc_gravity(eps2)
  151:    @particles.each{|p|p.v += p.acc*(h*0.5)}
  152:    self
  153:  end
  154:  def calc_cm
  155:    sumx=Vector3.new
  156:    sumv=Vector3.new
  157:    summ=0.0
  158:    @particles.each{|p|
  159:      sumx += p.m*p.x
  160:      sumv += p.m*p.v
  161:      summ +=p.m
  162:    }
  163:    {sumx*(1/summ),sumv*(1/summ)}
  164:  end
  165:  def adjust_cm
  166:    cmx,cmv = calc_cm
  167:    @particles.each{|p|
  168:      p.x -= cmx
  169:      p.v -= cmv
  170:    }
  171:  end
  172:end
  173:  def randomvector(r)
  174:    sqsum= r*r*2
  175:    v=Vector3.new
  176:    while sqsum > r*r
  177:      v = Array.new(3){rand(r)*2-r}.to_v
  178:      sqsum = v*v
  179:    end
  180:    v
  181:  end
  182:
  183:
  184:ENV["GKS_DOUBLE_BUF"]= "true" 
  185:
  186:#ps = ParticleSystem.random(2,1.0,0.4)
  187:if options.n == 2
  188:  ps = ParticleSystem.twobody([0.5*(1+options.ecc),0.0,0.0].to_v,
  189:                              [0.0,0.5*sqrt((1-options.ecc)/(1+options.ecc)),0.0].to_v)
  190:else
  191:  ps =ParticleSystem.random(options.n,1.0,options.vscale)
  192:end
  193:eps2= options.eps*options.eps
  194:ps.calc_gravity(eps2)
  195:wsize=options.wsize
  196:setwindow(-wsize, wsize,-wsize, wsize)
  197:
  198:time=0
  199:while time < options.tend+options.h/2
  200:  ps = ps.leapfrog(options.h, eps2)
  201:  time += options.h
  202:  clearws() 
  203:  box
  204:  setcharheight(0.05)
  205:  mathtex(0.5, 0.06, "x")
  206:  mathtex(0.06, 0.5, "y")
  207:  text(0.6,0.91,"t="+sprintf("%.3f",time))
  208:  setmarkertype(4)
  209:  setmarkersize(1)
  210:  polymarker(ps.particles.map{|p| p.x[0]}, ps.particles.map{|p| p.x[1]})
  211:  updatews() 
  212:end
  213:c=gets 
  214:
です。解説しますか?

赤木 :お願い。

学生C:最初の4行はグラフィクスとかコマンドラインとか数学ライブラリ使い ますですね。6から83行目まではオプション定義です。上の -n から -v まで。 88行は前に作ってもらった3次元ベクトルを使います。

89行から粒子クラスです。こういうの作るのがいいかどうかよくわかってない ですが、質量 m、 位置 x、速度 v、加速度 acc、ポテンシャル phi がメンバー 変数としてあって、それら全部普通に p.phi みたいなふうにアクセスできる としてます。

initialize は粒子作る時に Particle.new するわけですが、それから呼ばれ る関数でした。なにも引数なければ全部0に、ということでデフォルト値と、 あとここでは引数に型を与えます。

その次の self.random は、クラスメソッドというやつですね。 Particle.random(m, rx, rv) で、質量 m、位置と速度はそれぞれ半径 rx, rv の球の中の一様乱数です。

中では randomvector っていう関数で球の中の乱数生成します。

赤木 :あら、その randomvector ってどこにあるの?

学生C:173行からですね。これは、球に外接する立方体の中の乱数は、3成分 独立に作ればいいのでそうして、半径1の外側ならその乱数使わないで、球の 中に入るまで繰り返す、という方法です。わりとこれ標準的みたいです。

赤木 :そうね。 rejection method ね。乱数の半分くらい捨てるわけだから 計算量的にはもったいないけど、まあ、ちゃんと書けてる可能性が高いから。 そういえば、これ Crystal の面白いとこで、関数定義ってどこにあってもい いのね。

これが普通のコンパイル言語だと、よっぽど古いのでない限り、あるところで 使う関数はその前で定義がでてこないといけないのね。で、その定義って、 関数自体の型と引数の型なの。古い Fortran (Fortran77 とかその前)だと、 使うところで関数自体の型は宣言できるけど、引数は特に宣言する方法とか ないから、呼ぶところと定義するところでちゃんとつじつまがあってるかどう かはプログラム書く人任せで、もちろん間違えるわけ。で、 Algol 系の言語 のほとんどでは、関数定義と使うところで矛盾がないかどうかチェックするよ うになってるの。でも、そうすると、コンパイラが、ある関数が呼ばれるところを処 理するにはあらかじめその関数がどういうものか知らないといけないわけ。

単純には、これ、関数の定義が関数使うところより前にあれば いいわけで、Pascal とかは基本的にそうなってるのね。でも、 例えば2つの関数がお互いを自分の中で呼びたいとかあるとこれでは 不足だから、そういう時のために関数と引数の型だけ宣言できるような 文法があるの。で、最近の言語、特に C や C++ では、関数の引数と型の 宣言のことをプロトタイプ宣言といって、それが書いてあるファイルを、その 関数使うところの前で読み込む、というふうにするわけ。 C だと

  #include <stdio.h>
みたいなのとか、C++ だと

 #include <iostream>
とかね。

学生C: Crystal でも require でなんかインクルードしてるからおんなじじゃないですか?

赤木 : あ、でも、それ、必ずしも使うところの前にないといけないわけでは ないの。普通

  require "./vector3.cr"
  pp! Vector3.new(0)
みたいに使う前に require するけど、実は

  pp! Vector3.new(0)
  require "./vector3.cr"
でもコンパイル通って動くの。これなんか気持ち悪いし、Ruby だと最初の実 行文のところで Vector3 知りません、になんるんだけ、Crystal はあとでも かまわないのね。これは、コンパイラが、プログラム全体読み込んでから、 実行箇所の最初から辿り直す、みたいことをするからなのね。 呼ばれる関数のほうでは型書いてなくてもいいから、そうしないと逆にコンパ イルできないわけ。

C++ なんかだと、そういう、コンパイル時に呼び出し側から型がつたわってく る関数はテンプレート関数というものになって、「これはテンプレート関数で す」という形の宣言が必要ですごく複雑になって、それをもうちょっと簡単に するための新しい文法ができたりして一層わけがわからなくなんるんだけど、 Crystal だとその辺コンパイラが何とかするように頑張って作ってあって、見 かけ上実行時に型がわたってくる Ruby と概ね同じなんだけど、コンパイル言 語である、ということからこんなふうに Ruby でもできない気持ちが悪いこと もできるのね。

学生C:(あんまり良くわかってない)はあ、、、

赤木 :と、ちょっと余談よねこれ。次の関数は?

学生C:次は calc_gravity で、2粒子間の重力を計算します。式の通りであん まりいうことないですが、自分と、もうひとつの同じ型の粒子で相互重力計算 して、両方の acc と phi にそれぞれが受ける加速度とポテンシャルを加算し ていきます。粒子クラスはこれだけです。

赤木 :その次は?

学生C: 112行目からは ParticleSystem です。これ基本的には単に粒子の配 列をもっているクラスで、その配列に particles という名前をつけてます。 initialize は、なので、particles を長さ0のParticleクラスの配列、として ます。

その次の self.random と self.twobody は、それぞれ乱数でn個の粒子を作る のと、指定した位置、速度(一方は符号ひっくり返して)で2粒子、という関数 です。2粒子のほうは、このプログラムでちゃんとケプラー問題できるかどう かのチェック用です。

で、次の clear_gravity は、関数でなくてもいいんですが、各粒子の acc, phi をゼロにして、 calc_gravity は粒子間重力の計算です。

10.3. 数値積分法ライブラリと系をあらわすクラス

赤木 : その次は?

学生C: はい、これがなんかちょっと嫌なんですが、この ParticleSystem ク ラス用のリープフロッグ積分関数になってます。

赤木 :嫌ってのは?

学生C:時間積分法としては同じなのに、折角作った integratorlib.cr の中 の leapflog とはちがうものをもう一度書いてるわけで、なんか無駄というか 格好悪くないですか?

赤木 :うーん、そうねえ、これ確かにちょっと難しいところね。 intergratorlib.cr の leapfrog は

  def leapfrog(x,v,h,f)
    f0 = f.call(x)
    x+= v*h + f0*(h*h/2)
    f1 = f.call(x)
    v+= (f0+f1)*(h/2)
    {x,v}
  end
で、ParticleSystem のは

  def leapfrog(h,eps2)
    @particles.each{|p|p.x += p.v*h + p.acc*(h*h*0.5)}
    @particles.each{|p|p.v += p.acc*(h*0.5)}
    clear_gravity
    calc_gravity(eps2)
    @particles.each{|p|p.v += p.acc*(h*0.5)}
    self
  end
だから、なんかプログラムの見かけは全然違うわね。でも、やってることはも ちろん本当は同じよね。intergratorlib.cr のほうはプログラム適当に 書いてあるから、加速度2回計算しているけど、加速度の変数別にもってれば もちろんそれ省略できるし。

数学的というか、古典力学の定式化のほうからみると、シンプレクティック公 式だから、ハミルトニアンに対して定義されるわけで、ハミルトニアンって じゃない? が一般化運動量で が一般化座標で。 そう思うと、intergratorlib.cr の leapfrog は割とそれに近い形で、 f が正準方程式の になってるわけね。 ParticleSystem のは、粒子、つまり Particle クラスのデータ構造が、 ハミルトニアンの型式とはあってないのね。

じゃあ、粒子クラスなんてのは止めてしまって、x とか v を 3N 次元のベク トルにしちゃえばいいか、っていうと、それもなんだかなあというところがあっ て。だって、「1つの粒子」ってやっぱり実体としてある気がするわけで、 それがデータ構造として存在しないのはなんか気持ちが悪いし、 初期条件を作るとか結果を解析するとかの時にはやっぱり粒子を単位に扱いた いじゃない。

学生C:はい、でも、それと今のシンプレクティック公式のライブラリは上手 くあわないですよね?

赤木 :そうねえ、、、いろいろ考え方があると思うんだけど、一つは、 ParticleSystem みたいな物理系をあらわすクラスのほうに、 リープフロッグ公式を作るのに必要なだけのメソッドを用意する、ということ ね。つまり、リープフロッグで必要なのは

だから、まあ、あんまり難しいことを考えないで、クラスが決めうちの名前で そういう関数提供するとすれば、リープフロッグ公式のほうは例えば

 def leapfrog(s, h)
   s.inc_vel(h*0.5)
   s.inc_pos(h)
   s.calc_accel
   s.inc_vel(h*0.5)
 end
でいいし、高次のシンプレクティック公式もこれ使って書けるわけね。 名前決めうちは美しくないと思うなら、3つ関数渡すのでもいいけど かえってややこしくなるだけだと思うわ。

学生C: じゃあやってみます。

(次の日くらい)

一応できて動いてると思いますが、、、

赤木 :あら、速いわね。プログラムは?

    1:require "grlib"
    2:require "clop"
    3:require "./integratorlib.cr"
    4:include Math
    5:include GR
    6:
    7:optionstr= <<-END
    8:  Description: Test integrator  driver for Kepler problem
    9:  Long description:
   10:    Test integrator driver for Kepker problem
   11:    (c) 2020, Jun Makino
   12:
   13:  Short name:           -n
   14:  Long name:  --numner-of-particles
   15:  Value type:int
   16:  Default value: 2
   17:  Variable name: n
   18:  Description:Number of particles
   19:  Long description:     Number of particles
   20:
   21:  Short name:           -s
   22:  Long name:  --softening
   23:  Value type:float
   24:  Default value: 0.0
   25:  Variable name: eps
   26:  Description:Size of softening
   27:  Long description:     Size of softening
   28:
   29:  Short name:           -e
   30:  Long name:  --eccentricity
   31:  Value type:float
   32:  Default value: 0.0
   33:  Variable name: ecc
   34:  Description:Eccentricity. Used only when n=2
   35:  Long description:     Eccentricity. Used only when n=2
   36:
   37:  Short name:           -d
   38:  Long name:  --step-size
   39:  Value type:float
   40:  Default value: 0.01
   41:  Variable name: h
   42:  Description:Size of timestep
   43:  Long description:     Size of timestep
   44:
   45:  Short name: -T
   46:  Long name:  --end-time
   47:  Value type:int
   48:  Default value:1.0
   49:  Value type:float
   50:  Variable name:tend
   51:  Description:Time to stop integration
   52:  Long description:     Time to stop integration
   53:
   54:  Short name:-w
   55:  Long name:  --window-size
   56:  Value type:  float
   57:  Variable name:wsize
   58:  Default value:1.5
   59:  Description:Window size for plotting
   60:  Long description:
   61:    Window size for plotting orbit. Window is [-wsize, wsize] for both of
   62:    x and y coordinates
   63:
   64:  Short name:-e
   65:  Long name:--ecc
   66:  Value type:float
   67:  Default value:0.0
   68:  Variable name:ecc
   69:  Description:Initial eccentricity of the orbit
   70:  Long description:     Initial eccentricity of the orbit
   71:
   72:
   73:  Short name:-v
   74:  Long name:--velocity-scale
   75:  Value type:float
   76:  Default value:0.5
   77:  Variable name:vscale
   78:  Description:Scaling factor for the initial velocity
   79:  Long description:
   80:    Scaling factor for the initial velocity.
   81:    positions and velocities are set from random vectors within
   82:    spheres of radius 1 and vscale.
   83:
   84:END
   85:
   86:clop_init(__LINE__, __FILE__, __DIR__, "optionstr")
   87:options=CLOP.new(optionstr,ARGV)
   88:
   89:require "./vector3.cr"
   90:class Particle
   91:  property :m, :x, :v, :acc, :phi
   92:  def initialize(m : Float64=0, x : Vector3=Vector3.new(0,0,0),
   93:                 v : Vector3=Vector3.new(0,0,0))
   94:    @m = m; @x = x;  @v = v
   95:    @acc = Vector3.new(0,0,0); @phi=0.0
   96:  end
   97:  def self.random(m, rx, rv)
   98:    Particle.new(m.to_f, randomvector(rx), randomvector(rv))
   99:  end
  100:  def calc_gravity(p,eps2)
  101:    dr = @x - p.x
  102:    r2inv = 1.0/(dr*dr+eps2)
  103:    rinv = sqrt(r2inv)
  104:    r3inv = r2inv*rinv
  105:    @phi -= p.m*rinv
  106:    p.phi -= @m*rinv
  107:    @acc -= p.m*r3inv*dr
  108:    p.acc += @m*r3inv*dr
  109:  end
  110:end
  111:
  112:class ParticleSystem
  113:  property :particles, :eps2
  114:  def initialize(eps2 : Float64 = 0.0 )
  115:    @particles = Array(Particle).new
  116:    @eps2 = eps2
  117:  end
  118:  def +(p : Particle)
  119:    @particles.push p
  120:    self
  121:  end
  122:  def self.random(n, rx, rv)
  123:    ps=ParticleSystem.new
  124:    m = 1.0/n
  125:    n.times{ ps += Particle.random(m,rx,rv)}
  126:    ps.adjust_cm
  127:    ps
  128:  end
  129:  def self.twobody(x,v)
  130:    ps=ParticleSystem.new
  131:    n=2
  132:    m = 1.0/n
  133:    ps += Particle.new(m,x,v)
  134:    ps += Particle.new(m,-x,-v)
  135:    ps
  136:  end
  137:  def calc_accel
  138:    @particles.each{|p|p.acc=Vector3.new; p.phi=0.0}
  139:    @particles.each_with_index{|p,i|
  140:      ((i+1)..(particles.size-1)).each{|j|
  141:        p.calc_gravity(@particles[j], @eps2)
  142:      }
  143:    }
  144:  end
  145:  def inc_vel(h)
  146:    @particles.each{|p|p.v += p.acc*(h)}
  147:  end
  148:  def inc_pos(h)
  149:    @particles.each{|p|p.x += p.v*h}
  150:  end
  151:  def calc_cm
  152:    sumx=Vector3.new
  153:    sumv=Vector3.new
  154:    summ=0.0
  155:    @particles.each{|p|
  156:      sumx += p.m*p.x
  157:      sumv += p.m*p.v
  158:      summ +=p.m
  159:    }
  160:    {sumx*(1/summ),sumv*(1/summ)}
  161:  end
  162:  def adjust_cm
  163:    cmx,cmv = calc_cm
  164:    @particles.each{|p|
  165:      p.x -= cmx
  166:      p.v -= cmv
  167:    }
  168:  end
  169:end
  170:  def randomvector(r)
  171:    sqsum= r*r*2
  172:    v=Vector3.new
  173:    while sqsum > r*r
  174:      v = Array.new(3){rand(r)*2-r}.to_v
  175:      sqsum = v*v
  176:    end
  177:    v
  178:  end
  179:
  180:
  181:ENV["GKS_DOUBLE_BUF"]= "true" 
  182:
  183:if options.n == 2
  184:  ps = ParticleSystem.twobody([0.5*(1+options.ecc),0.0,0.0].to_v,
  185:                              [0.0,0.5*sqrt((1-options.ecc)/(1+options.ecc)),0.0].to_v)
  186:else
  187:  ps =ParticleSystem.random(options.n,1.0,options.vscale)
  188:end
  189:ps.eps2= options.eps*options.eps
  190:ps.calc_accel
  191:wsize=options.wsize
  192:setwindow(-wsize, wsize,-wsize, wsize)
  193:
  194:time=0
  195:while time < options.tend-options.h/2
  196:#  SymplecticIntegrators.leapfrog(ps,options.h)
  197:  SymplecticIntegrators.yoshida4(ps,options.h)
  198:  time += options.h
  199:  clearws() 
  200:  box
  201:  setcharheight(0.05)
  202:  mathtex(0.5, 0.06, "x")
  203:  mathtex(0.06, 0.5, "y")
  204:  text(0.6,0.91,"t="+sprintf("%.3f",time))
  205:  setmarkertype(4)
  206:  setmarkersize(1)
  207:  polymarker(ps.particles.map{|p| p.x[0]}, ps.particles.map{|p| p.x[1]})
  208:  updatews() 
  209:end
  210:c=gets 
  211:
です。とりあえず前のと違うところを順番に説明します。diff の出力だしますね。

  gravity> diff particle0.cr particle1.cr
  2a3
  > require "./integratorlib.cr"
  109d109
  <     
  113,114c113,114
  <   property :particles
  <   def initialize()
  ---
  >   property :particles, :eps2
  >   def initialize(eps2 : Float64 = 0.0 )
  115a116
  >     @eps2 = eps2
  136c137
  <   def clear_gravity
  ---
  >   def calc_accel
  138,139d138
  <   end
  <   def calc_gravity(eps2)
  142c141
  <         p.calc_gravity(@particles[j], eps2)
  ---
  >         p.calc_gravity(@particles[j], @eps2)
  146,152c145,149
  <   def leapfrog(h,eps2)
  <     @particles.each{|p|p.x += p.v*h + p.acc*(h*h*0.5)}
  <     @particles.each{|p|p.v += p.acc*(h*0.5)}
  <     clear_gravity
  <     calc_gravity(eps2)
  <     @particles.each{|p|p.v += p.acc*(h*0.5)}
  <     self
  ---
  >   def inc_vel(h)
  >     @particles.each{|p|p.v += p.acc*(h)}
  >   end
  >   def inc_pos(h)
  >     @particles.each{|p|p.x += p.v*h}
  186d182
  < #ps = ParticleSystem.random(2,1.0,0.4)
  193,194c189,190
  < eps2= options.eps*options.eps
  < ps.calc_gravity(eps2)
  ---
  > ps.eps2= options.eps*options.eps
  > ps.calc_accel
  199,200c195,197
  < while time < options.tend+options.h/2
  <   ps = ps.leapfrog(options.h, eps2)
  ---
  > while time < options.tend-options.h/2
  > #  SymplecticIntegrators.leapfrog(ps,options.h)
  >   SymplecticIntegrators.yoshida4(ps,options.h)
これだと、下にでているほうが新しいバージョンですね。 最初の、新しいほう、えーと、全部行数は新しいほうでいきますが、 3行目は、integratorlib.cr にリープフロッグとかいれたので、それを require するようにしてます。 integratorlib.cr に追加したのは、

  module SymplecticIntegrators
    extend self
    def leapfrog(s, h)
      s.inc_vel(h*0.5)
      s.inc_pos(h)
      s.calc_accel
      s.inc_vel(h*0.5)
    end
    D1 = 1.0 / (2-exp(log(2.0)/3))
    D2 = 1 - 2*D1

   def yoshida4(s,h)
      leapfrog(s,h*D1)
      leapfrog(s,h*D2)
      leapfrog(s,h*D1)
    end
    
    def  yoshida6(x,v,h,f)
      d = {0.784513610477560, 0.235573213359357,
           -1.17767998417887, 1.31518632068391};
      4.times{|i|leapfrog(s,h*d[i])}
      3.times{|i|leapfrog(s,h*d[2-i])}
    end
  end
です。積分公式は新しい名前のモジュールにして、前に作ったのと同じ名前で すが違う引数で違う動作にしてます。やることはあんまり変わらないです。 なので、同じようなことをする関数が2種類ある、というのは変わらないです。 でもまあ、その気になれば
9 章のプログラムもこっち使う ようにはできるので、、、

赤木 :そうねえ。まあ、前のと同じではできない、というところから始めた から、どうしてもそうなるわね。

学生C:はい。その次、113-114、116行目は、 eps2 を ParticleSustem のメンバー 変数にしたのでこうなってます。相互作用計算する関数がこのクラスの中の calc_accel として定義されるので。calc_accel に Procオブジェクトを渡す とかして中身変更できてもいいかもしれないですが、そんな変なのが必要とも 思えないのでこうしてます。 137, 138 辺りは、元々 clear_gravity と calc_gravity の2つあったのを calc_accel 1つにしたから ですね。 141行目は eps2 がメンバー変数になった関係です。

146-149 行目は、元々 leapfrog があったところに inc_vel、inc_pos をいれ てます。そもそもここから後はまだ話してないですね?

赤木 :あ、そうね。

学生C:その次の calc_cm と adjust_cm は、乱数で粒子ばらまいただけだと 系の重心速度が0じゃなのので、時間が立つと系全体がどっかにいっちゃうの で、そうならないように系の重心の位置、速度を0にしてます。

赤木 :なるほど。これ、 x, v に同じことするじゃない?その辺もうちょっ と工夫できなかった?

学生C:えー、どうでしょう?例えば

   summ = @particles.sum{|p| p.m}
   sumx = @particles.sum{|p| p.m*p.x}
   sumv = @particles.sum{|p| p.m*p.v}
とかですか?作者こっちが好きな感じですね。

赤木 : そうね。もとのよりこっちのほうが、間違えないで書けてる可能性が 高いし、いいと思うわ。あと、こっちの形なら、別に sumx とか sumv に一旦 代入しなくても

   invsumm = 1.0/@particles.sum{|p| p.m}
   {@particles.sum{|p| p.m*p.x}*invsumm,
    @particles.sum{|p| p.m*p.v}*invsumm}
でいいわけ。これでもプログラムの意味はわかりにくくはならないでしょ?元 の君のだと9行あったのが3行だし。

学生C:そうですね。(なんか悔しい、、、)

赤木 :あ、えーと、ごめんなさい、これは、格好良く書けるとかいうことじゃ なくて、間違える可能性が少ない、というのが大事なの。1から10まで合計し ます、というプログラムを

  1から10まで合計します
と書くのと

 最初に sum を 0 にします
 次に i を 1 から1から10まで変えながら
  sum に i を加えます
 おしまい
みたいなのを書くのは手間も違うしどっかで間違えるかどうかも違うから。 まあ、Crystal とかだと 「次に i を 1 から1から10まで変えながら」のところが、

  @particles.each{|p| ...}
みたいなのですむから、最初を忘れるとか最後の次までいっちゃうとかが割合 起こりにくいけど、 CとかFortran だとそういうことにも注意しないといけな いから。

学生C:えーと、次いっていいですか?

赤木 :いいわよ。

学生C:181行目のGKSなんたらはこれやるそアニメーションがチラツかないで 動く、というやつです。GR 結構ドキュメントわかりにくくて、これ見つける の苦労しました。

赤木 :まあ大抵なんでもドキュメントってわかりにくいのよね、、、

学生C:はい。で、183-188 は初期条件で、今まで作った関数を呼ぶだけです。 まああと大体そうなので、リープフロッグ、あ、4次公式使ってみてますね、 それ呼んでるのが 197行目です。あとは図を書くとかなので省略で。

赤木 :で、これ動かすと何が起こるの?

学生C:

と、単に

 particle1
で実行すれば、2粒子が円軌道で動くはずです。

 particle1 -T 100
とかすれば沢山回ります。

 particle1 -n 100 -s 0.01
とかすれば、なんか沢山粒子がでてうにょうにょ動きます。

10.4. シミュレーション結果の「検証」

赤木 :動くのはいいとしてして、これちゃんと正しく計算できてる?

学生C:と思いますが、、、

赤木 :こういうのは思いますでは駄目で、根拠がいるのよ。だって、 シミュ レーションしてこう答がでました、っていっても、それだけではそれが本当か どうかわからないでしょ?

学生C:でも、元々解析解がなくて答がわからないからシミュレーションする んだから、その答が正しいかどうかってわからないんではないですか?

赤木 :それはそうなんだけど、それでも、全然間違ってればわかるチェック、 というのはあるの。例えば、古典力学系だから保存量はあるでしょ?

学生C:3次元なので、エネルギー、全体の運動量、全体の角運動量ですね?

赤木 :そう。だから、その辺のチェックは最低限して欲しいの。あ、あと、 恒星系だと、「重力ポテンシャルエネルギーと運動エネルギーの比」が大事な 量なの。これは「ビリアル比」っていって、自己重力系の定常状態だと変化し ない、というのが証明できるの。

学生C:すみません、定常状態ってなんですか?

赤木 :分布関数が、、、あ、えーと、つまり、統計力学と同じで、星の数が 無限に大きな極限を考えると、恒星系って、位置と速度の6次元空間の中での 密度分布とみなせるでしょ?

学生C:でしょって言われても、、、統計力学なら原子はみんな同じですが、 星はひとつひとつ違うじゃないですか?統計力学って成り立たつんですか?

赤木 :今は星は重力相互作用する質点だから、違うのは質量だけじゃない? そうすると、星の数が無限に多いなら、分布関数が質量にも依存するとして 統計力学の式をたてればいいわけ。ただ、まずここで問題なのは、 統計力学的な平衡、熱平衡ね、それじゃなくて、力学平衡というものなの。

学生C:どう違うんですか?

赤木 :統計力学的平衡はエントロピー最大なんだけど、力学平衡は単に分布 関数が定常というだけね。普通の気体だと、力学平衡は圧力がつりあってる状 態、静水圧平衡にあたるものね。静水圧平衡だと温度分布はあってもいいわけ。

学生C: でも星は動くわけじゃないですか?

赤木 :そう。だから、割合その辺違うのね。気体分子運動論から流体力学を 導く時には、分子同士は頻繁に衝突するから、局所的には統計力学的に平衡だ として、局所的なマクロな物理量を導くんだけど、星同士は滅多にぶつからな いから局所的にも大局的にも熱平衡にならないの。

で、今は、熱平衡は問題じゃなくて、その前の静水圧平衡とか力学平衡ね。 この辺、ここで説明するとそれだけで何十時間かかかるから、 作者の講義ノートみといてね。3回目くらいまででとりあえずいいから。

学生C:はあ、、、、

赤木 :で、以下、一応見たという仮定のもとで話をするけど、ビリアル比は 自己重力系の定常状態だと -1/2 になるから、その数字もだしてね、というの が話ね。

学生C: すみません、理屈わかってないですが計算はしてみます。

プログラムの変更したところをだします。

  gravity> diff particle1.cr particle1a.cr
  3a4
  > require "./mathvector.cr"
  44a46,62
  >   Short name:           -D
  >   Long name:  --diagnostics-interval
  >   Value type:int 
  >   Default value: 5
  >   Variable name: diaginterval
  >   Description:Interval for diagnostics
  >   Long description:     Interval for diagnostics
  > 
  >   Short name:           -G
  >   Long name:  --graphics-interval
  >   Value type:int 
  >   Default value: 5
  >   Variable name: graphicinterval
  >   Description:Interval for graphics
  >   Long description:     Interval for graphics
  >                         No output if 0 or negative
  > 
  76c94
  <   Default value:0.5
  ---
  >   Default value:1
  150a169
  > 
  152,160c171,173
  <     sumx=Vector3.new
  <     sumv=Vector3.new
  <     summ=0.0
  <     @particles.each{|p|
  <       sumx += p.m*p.x
  <       sumv += p.m*p.v
  <       summ +=p.m
  <     }
  <     {sumx*(1/summ),sumv*(1/summ)}
  ---
  >    invsumm = 1.0/@particles.sum{|p| p.m}
  >    {@particles.sum{|p| p.m*p.x}*invsumm,
  >     @particles.sum{|p| p.m*p.v}*invsumm}
  168a182,187
  > 
  >   def energies
  >     ke = @particles.sum{|p| p.v*p.v*p.m}*0.5
  >     pe = @particles.sum{|p| p.phi*p.m}*0.5
  >     [pe+ke, pe, ke].to_mathv
  >   end
  190a210
  > e0=ps.energies
  194a215,216
  > istep=0
  > pp! options
  196,197c218,219
  < #  SymplecticIntegrators.leapfrog(ps,options.h)
  <   SymplecticIntegrators.yoshida4(ps,options.h)
  ---
  >   SymplecticIntegrators.leapfrog(ps,options.h)
  > #  SymplecticIntegrators.yoshida4(ps,options.h)
  199,208c221,239
  <   clearws() 
  <   box
  <   setcharheight(0.05)
  <   mathtex(0.5, 0.06, "x")
  <   mathtex(0.06, 0.5, "y")
  <   text(0.6,0.91,"t="+sprintf("%.3f",time))
  <   setmarkertype(4)
  <   setmarkersize(1)
  <   polymarker(ps.particles.map{|p| p.x[0]}, ps.particles.map{|p| p.x[1]})
  <   updatews() 
  ---
  >   istep+=1
  >   if options.graphicinterval > 0 && (istep % options.graphicinterval) == 0
  >     clearws() 
  >     box
  >     setcharheight(0.05)
  >     mathtex(0.5, 0.06, "x")
  >     mathtex(0.06, 0.5, "y")
  >     text(0.6,0.91,"t="+sprintf("%.3f",time))
  >     setmarkertype(4)
  >     setmarkersize(1)
  >     polymarker(ps.particles.map{|p| p.x[0]}, ps.particles.map{|p| p.x[1]})
  >     updatews()
  >   end
  >   if (istep % options.diaginterval) == 0
  >     e=ps.energies
  >     de=e - e0
  >     print sprintf("T= %e E, de, Q = %e %e %e\n",time, e[0], de[0], e[2]/e[1])
  >   end
  >   
  210c241,242
  < c=gets 
  ---
  > c=gets if options.graphicinterval > 0
  > 
新しいので4行目は、mathvector も使うようにしたのでいれてます。 46-61 行は、それぞれ、エネルギーとかの値を何ステップに1度書くかと、 絵を何ステップに1度書くかです。時間ステップ毎に書くと遅いのとやたら 沢山出力でるので。

赤木 :なるほど。

学生C:次は、さっきの sum のところですね。まあ、こっちのほうが わかりやすいですね、、、、 energies も関数作って、同じような感じで計算 します。 pe にも0.5 かけてるのは、そうしないと2重になるからです。

エネルギーは209行で最初に値を計算して、230行あたりで 新しい値だして誤差とビリアル比もだしますか。 何ステップかに1度にするために、 istep という変数でステップ数えておいて、 それとオプションで指定した値を比べます。 絵書くのも同様ですが、こちらは 0だったら書かないようにしてみました。

赤木 :実行するとどんな感じ?

学生C:

 gravity> particle1a -n 100 -s 0.01 -T 10 -d 0.004 -D 250 -G 0 -v 1
 options # => #<CLOP:0x7fa0ba0f5f60
  @diaginterval=250,
  @ecc=0.0,
  @eps=0.01,
  @graphicinterval=0,
  @h=0.004,
  @help=false,
  @n=100,
  @tend=10.0,
  @vscale=1.0,
  @wsize=1.5>
 T= 1.000000e+00 E, de, Q = -2.830613e-01 7.050285e-06 -5.559780e-01
 T= 2.000000e+00 E, de, Q = -2.830182e-01 5.014634e-05 -5.256826e-01
 T= 3.000000e+00 E, de, Q = -2.830166e-01 5.175141e-05 -4.638142e-01
 T= 4.000000e+00 E, de, Q = -2.829957e-01 7.265288e-05 -4.312053e-01
 T= 5.000000e+00 E, de, Q = -2.829971e-01 7.126654e-05 -4.588986e-01
 T= 6.000000e+00 E, de, Q = -2.829861e-01 8.219981e-05 -5.098401e-01
 T= 7.000000e+00 E, de, Q = -2.830054e-01 6.293601e-05 -5.600901e-01
 T= 8.000000e+00 E, de, Q = -2.831348e-01 -6.646909e-05 -5.405158e-01
 T= 9.000000e+00 E, de, Q = -2.831985e-01 -1.301983e-04 -4.998459e-01
 T= 1.000000e+01 E, de, Q = -2.833241e-01 -2.557347e-04 -4.648533e-01

 gravity> particle1a -n 100 -s 0.01 -T 10 -d 0.002 -D 500 -G 0 -v 1
 options # => #<CLOP:0x7f4a62768f60
  @diaginterval=500,
  @ecc=0.0,
  @eps=0.01,
  @graphicinterval=0,
  @h=0.002,
  @help=false,
  @n=100,
  @tend=10.0,
  @vscale=1.0,
  @wsize=1.5>
 T= 1.000000e+00 E, de, Q = -3.198745e-01 4.815522e-06 -5.196970e-01
 T= 2.000000e+00 E, de, Q = -3.198789e-01 4.272255e-07 -4.991211e-01
 T= 3.000000e+00 E, de, Q = -3.198814e-01 -2.091424e-06 -4.925752e-01
 T= 4.000000e+00 E, de, Q = -3.198680e-01 1.134991e-05 -5.230837e-01
 T= 5.000000e+00 E, de, Q = -3.198777e-01 1.683257e-06 -5.008569e-01
 T= 6.000000e+00 E, de, Q = -3.198889e-01 -9.555253e-06 -4.881346e-01
 T= 7.000000e+00 E, de, Q = -3.198835e-01 -4.139537e-06 -4.936635e-01
 T= 8.000000e+00 E, de, Q = -3.198830e-01 -3.661377e-06 -5.141768e-01
 T= 9.000000e+00 E, de, Q = -3.198830e-01 -3.670672e-06 -4.875870e-01
 T= 1.000000e+01 E, de, Q = -3.198813e-01 -1.992989e-06 -5.101963e-01

 gravity> particle1a -n 100 -s 0.01 -T 10 -d 0.001 -D 1000 -G 0 -v 1
 options # => #<CLOP:0x7f7bd14bcf60
  @diaginterval=1000,
  @ecc=0.0,
  @eps=0.01,
  @graphicinterval=0,
  @h=0.001,
  @help=false,
  @n=100,
  @tend=10.0,
  @vscale=1.0,
  @wsize=1.5>
 T= 1.000000e+00 E, de, Q = -2.758487e-01 -1.174598e-08 -5.388441e-01
 T= 2.000000e+00 E, de, Q = -2.758487e-01 1.426974e-08 -4.996477e-01
 T= 3.000000e+00 E, de, Q = -2.758487e-01 -2.688223e-08 -4.523273e-01
 T= 4.000000e+00 E, de, Q = -2.758482e-01 4.253572e-07 -4.554793e-01
 T= 5.000000e+00 E, de, Q = -2.758483e-01 3.516836e-07 -4.778742e-01
 T= 6.000000e+00 E, de, Q = -2.758486e-01 6.555026e-08 -5.203627e-01
 T= 7.000000e+00 E, de, Q = -2.758475e-01 1.177574e-06 -5.261572e-01
 T= 8.000000e+00 E, de, Q = -2.758487e-01 4.194047e-10 -5.097420e-01
 T= 9.000000e+00 E, de, Q = -2.758488e-01 -1.080674e-07 -5.242654e-01
 T= 1.000000e+01 E, de, Q = -2.758479e-01 7.414149e-07 -4.756147e-01

 gravity> particle1a -n 100 -s 0.01 -T 10 -d 0.0005 -D 2000 -G 0 -v 1
 options # => #<CLOP:0x7fe35e947f60
  @diaginterval=2000,
  @ecc=0.0,
  @eps=0.01,
  @graphicinterval=0,
  @h=0.0005,
  @help=false,
  @n=100,
  @tend=10.0,
  @vscale=1.0,
  @wsize=1.5>
 T= 1.000000e+00 E, de, Q = -3.002496e-01 3.747335e-08 -5.194435e-01
 T= 2.000000e+00 E, de, Q = -3.002499e-01 -2.811658e-07 -5.076344e-01
 T= 3.000000e+00 E, de, Q = -3.002496e-01 3.280493e-09 -4.759571e-01
 T= 4.000000e+00 E, de, Q = -3.002496e-01 2.598876e-08 -4.845913e-01
 T= 5.000000e+00 E, de, Q = -3.002496e-01 1.395994e-08 -4.925808e-01
 T= 6.000000e+00 E, de, Q = -3.002496e-01 7.810084e-08 -5.017783e-01
 T= 7.000000e+00 E, de, Q = -3.002496e-01 2.506281e-08 -4.982760e-01
 T= 8.000000e+00 E, de, Q = -3.002497e-01 -4.287642e-08 -5.087724e-01
 T= 9.000000e+00 E, de, Q = -3.002496e-01 1.037569e-09 -5.386381e-01
 T= 1.000000e+01 E, de, Q = -3.002497e-01 -4.343700e-08 -5.284962e-01
すみません、エネルギーしかみてないですが、一応タイムステップ小さくする とそれないに小さくなっていて大丈夫ではないかと、、、

赤木 :その辺は読者の練習問題ね。

10.5. 課題

  1. 粒子数 100 程度で、時刻 10 程度まで積分した時のエネルギーの誤差の絶 対値の最大値を、タイムステップの関数としてプロットしてみよ。ソフト ニングは 0.01 で固定でよいが、変化させたらどのように変わるかも 余力があれば調べてみよ。

  2. 全運動量、全角運動量について、保存精度を調べよ。

  3. 4次シンプレクティック公式も選択できるようにプログラムを拡張し、 課題 1, 2 についてどうなるか調べよ。

10.6. まとめ

  1. シンプレクティック積分公式を、ハミルトン力学系を表現するクラスに対 して一般的に適用できる形のライブラリとして作成した。
  2. 重力多体問題を例に、上の公式を適用して系の性質を学んだ。
  3. 重力多体問題では、ソフトニングを導入してポテンシャルの発散をふせいだ。

10.7. 参考資料

http://jun-makino.sakura.ne.jp/kougi/stellar_dynamics/index.html|
Previous ToC Next