倭算数理研究所

科学・数学・学習関連の記事を、「倭マン日記」とは別に書いていくのだ!

2.4 True Basic のプログラム : プログラミング演習 2.1 関数値の計算 b

今回は『プログラミング演習 2.1 関数値の計算 b』です。 演習問題はこちら:

プログラムを修正して、区間 { 1 \le x \le 20 } について、関数

  { \displaystyle
\begin{align*}
    f_1(x) &= x \\
    f_2(x) &= x^2 \\
    f_3(x) &= 2^x - 1 \\
    f_4(x) &= 1 + \ln x
\end{align*}
}

の各値を計算して画面の同じ行に表示せよ。

Groovy

まずは Groovy による実装。 関数をクロージャで表しているのは今までと同じです。 他に、敢えて Groovy に慣れてないと分かりにくそうな記法で書いてるところもあります:

double xmin = -5d, xmax = 5d, dx = 0.01d

def functions = [
    { double x -> x },
    { double x -> x*x },
    { double x -> 2d**x - 1d },    // 2d**x <=> 2d.pow(x) <=> Math.pow(2d, x)
    { double x -> 1d + Math.log(x)}
]

for(double x = xmin; x <= xmax; x += dx){
    def line = '' << ''    // line は StringBuffer のオブジェクト
    line << x               // line.append(x) と同じ
    functions.each{ f ->
        line << ', ' << f(x)    // line.appned(", ").append(f(x)) と同じ
    }
    println line
}

StringBuffer の << 演算子は見やすくて使い勝手がイイですが、String オブジェクトの << によって StringBuffer オブジェクトを生成するのは、知らないと何をしてるのかよく分からないので敢えて使う必要はないかと思います。 書いてるコード見たときに何してるか分かれば問題ナシ。

Groops

では Groops による実装。 問題の4つの関数1つ1つを「物理系」とみなしてシミュレーションを行いますが、Groops で複数の物理系を扱うには以下の2つの方法があります(今のところ):

  • 複合系 (Composite System)
  • 並行シミュレーション (Parallel Simulation)

それぞれの特徴は以下で見ていきます。

複合系のシミュレーション : compositeSystem ノード
まずは複合系 (composite system) を使う方法。 複合系は、複数の PhysicalSystem オブジェクトを集めて1つの物理系として扱えるようにした PhysicalSystem のようなものです*1

複合系のシミュレーション・コードは以下のようになります:

@GrabResolver('https://github.com/waman/groops-core/tree/master/repo')
@Grab('org.waman.groops:groops-core:0.1-beta')

import org.waman.groops.builder.SimulationBuilder

Map samplingInfo = [xmin:0d, dx:0.1d]

new SimulationBuilder().simulation{

    compositeSystem{
        function(y:{ double x -> x }   , *:samplingInfo)    // インデックス 0
        function(y:{ double x -> x*x }, *:samplingInfo)    // インデックス 1
    }
    count(total:200)
    png(fileName:'exercise2_1b.png', overwrite:true){
        chart(title:'exercise 2.1 b', domainLabel:'x', rangeLabel:'y'){
            line(label:'y = x',   x:'0@x', y:'0@y')    // インデックス 0 の物理系の物理量
            line(label:'y = x^2', x:'1@x', y:'1@y')    // インデックス 1 の物理系の物理量
            // もしくは
            // line(label:'y = x',   x:{ it[0].x }, y:{ it[0].y })
            // line(label:'y = x^2', x:{ it[1].x }, y:{ it[1].y })
            // クロージャ内 it[0] でインデックス 0 の物理系の DataStore を取得できる
        }
    }
}.simulate()
  • 複合系は、system ノードの代わりに compositeSystem ノードを書いて、その下に物理系を構築するノードを複数書きます。 compositeSystem ノード以外に使えるノードは今までと同じです。
  • 反復条件やデータ出力で物理量にアクセスする場合は、「《物理系のインデックス》@《物理量の名前》」の形式で指定します。 上記の例で line ノードに指定している「0@x」や「1@y」などがそうです。
  • Map オブジェクト samplingInfo は物理系に指定するパラメータです。 複数の関数に同じ値を指定するので別に定義しています。 function ノードでは「マップ展開演算子 *:」によってノードの属性に展開しています。

複合系が使える条件は「全ての物理系で共通の反復条件を使用する」ことです。 言い換えると、全ての物理系を一緒に状態更新しなければいけません。 足並みがそろわない物理系を複合系として扱うことはできません。

並行シミュレーション : parallel ノード
さて、次は並行シミュレーション (parallel simulation)。 これはその名の通り複数のシミュレーションを別スレッドで並行して実行します。 並行シミュレーションを行うには、parallel ノード下に通常の simulation ノードを書きます。 simulation ノードは今までと同様に書けます:

import org.waman.groops.builder.SimulationBuilder

new SimulationBuilder().parallel{
    
    simulation{
        function(y:{ double x -> x }, xmin:0d, xmax:20d, dx:0.1d)
        console()
    }

    simulation{
        function(y:{ double x -> x*x }, xmin:0d, xmax:20d, dx:0.1d)
        console()
    }
}.simulate()

並行シミュレーションで面倒なのは、別スレッドで実行しているシミュレーションの結果を(1つのチャートなどに)まとめる場合です。 今のところチャートに出力する場合しかサポートしてませんで、しかも少々分かりにくい設定方法になってます。 こんな感じ:

import org.waman.groops.builder.SimulationBuilder

new SimulationBuilder().parallel{

    def pngout = png(fileName:'exercise2_1b.png', overwrite:true){
        chart(title:'exercise 2.1 b'){
            line(label:'y = x', x:'x', y:'y')
            line(label:'y = x^2', x:'x', y:'y')
        }
    }
    
    simulation{
        function(y:{ double x -> x }, xmin:0d, xmax:20d, dx:0.1d)
        dataOutputter(pngout['y = x'])
        // pntout['y = x'] の返り値は line(label:'y = x', x:'x', y:'y') にデータを出力する DataOutputter
    }
    simulation{
        function(y:{ double x -> x*x }, xmin:0d, xmax:20d, dx:0.1d)
        dataOutputter(pngout['y = x^2'])
        // pntout['y = x^2'] の返り値は line(label:'y = x', x:'x', y:'y')
        // にデータを出力する DataOutputter
    }
}.simulate()
  • parallel ノード下で、png ノードによってチャートを画像ファイルに出力する DataOutputter インスタンスを生成しています。 このノードの内容はいつもと同じです。
  • parallel ノード下の2つの simulation ノードはそれぞれ別スレッドで実行するシミュレーションです。 反復条件が書かれていませんが、これは parallel ノードとは関係なく、function ノードによって生成される物理系に定義されているデフォルトの反復条件が使われるためです。
  • simulation ノード下の dataOutputter ノードは、引数の DataOutputter オブジェクトを、親ノードが表すシミュレーションに付け加えます。
  • pngout (png ノードによって返される DataOutputter) は [int] や [String] によって出力したい系列(png ノード下の point, dot, line ノードによって指定されているもの)の DataOutputter を取得することができます。

ふぅ、分かりにくいですね(^ ^;) そのうちザックリ変えるかも知れません。

4つの関数をプロットする
並行シミュレーションのサンプルコードを踏まえて、問題にある4つの関数をプロットするシミュレーションを書いてみましょう。 こんな感じです:

@GrabResolver('https://github.com/waman/groops-core/tree/master/repo')
@Grab('org.waman.groops:groops-core:0.1-beta')

import org.waman.groops.builder.SimulationBuilder

def functions = [
    // key          :  value
    'y = x'          : { double x -> x },
    'y = x^2'      : { double x -> x*x },
    'y = 2^x - 1' : { double x -> 2d**x - 1d },
    'y = 1 + ln x' : { double x -> 1d + Math.log(x) }
]

new SimulationBuilder().parallel{

    def pngout = png(fileName:'exercise2_1b.png', overwrite:true){
        chart(title:'exercise 2.1 b', range:[-2.5d, 20d]){    // 値域(y の範囲)を指定
            for(f in functions){
                line(label:f.key, x:'x', y:'y')    // f.key は関数名('y = x' など)
            }
        }
    }
    
    for(f in functions){
        simulation{
            function(y:f.value, xmin:0d, xmax:20d, dx:0.1d)
            // f.value は関数を表すクロージャ({ double x -> x } など)
            dataOutputter(pngout[f.key])
        }
    }
}.simulate()

まず最初に関数を表すクロージャを値に持つ Map を作り、シミュレーションを構築するノードで各関数に対応するものが必要なところ (line, simulation ノード) は for 文によって書いています。 まぁ、やってることは上記の2つの関数の場合と同じです。

出力されるグラフは以下のようになります:


計算物理学入門

計算物理学入門

  • 作者: ハーベイゴールド,ジャントボチニク,Harvey Gould,Jan Tobochnik,鈴木増雄,石川正勝,溜渕継博,宮島佐介
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2000/12
  • メディア: 単行本
  • 購入: 1人 クリック: 28回
  • この商品を含むブログ (45件) を見る

*1:正確には少し違って、物理系 (PhiscalSystem) 、初期条件、物理量(観測可能量 ObservableSet)、データストア (DataStore) を1組にしたものを「シミュレーション系 (SimulationSystem)」としたとき、複数のシミュレーション系を1つのシミュレーション系として扱えるようにしたものです。