Line 1: Error: Invalid Blog('by Esehara' )

または私は如何にして心配するのを止めてバグを愛するようになったか

>> Zanmemo

あと何かあれは 「esehara あっと じーめーる」 か @esehara まで

「パラメータによって予期出来ない振る舞いが起こる」とかいう方程式について、プログラミングで確かめてみる

近況

f:id:nisemono_san:20150304141832j:plain

はじめに

そういえば、このブログを見ていて、こんな単純な式がパラメーターによって予測不可能な振る舞いをすることがあるんだな、ということを知って面白かったので、同じようにその振る舞いを確認できるようなものを実装しようと思った。

基本公式

まず、ここにレミングという生物がいる。レミングは{ \frac{X \times Y}{10}}の割合で毎年死ぬ。この時、{X}は前年度のレミングの個体をさし、{Y}は今年度のレミングの個体を刺す。このとき、今年度のレミングは{Y = 2X}となる。つまり、実際の計算式は {\frac{X \times 2X}{10}}となる。死ぬタイミングは、繁殖後ということになる。

例えば、前年度のレミングが2匹、今年度は2倍、つまり{2X}になるので、4匹とする。このとき、{ \frac{2 \times 4}{10} = 0.8 } となり、約1匹死ぬ。つまり、次の周期になると、前年度のレミングは { 4 - 1 = 3}となり、そして今年度は一度、6匹に繁殖するようになる。

さて、これを繰り返した場合、増殖率が{2X}の場合、5で安定してしまうという。

Haskellによる実装

せっかくなのでHaskellによって実装してみることにした:

lemming_pair :: Int -> (Int, Int)
lemming_pair frt = (frt, frt * lemming_param)

lemming :: Int -> (Int, Int) -> (Int, Int)
lemming rate (prv, nxt) = (livelemming, livelemming * rate)
  where deadlemming = round ((fromIntegral (prv * nxt)) / 10)
        livelemming = nxt - deadlemming

lemming_p :: Int -> (Int, Int) -> [(Int, Int)]
lemming_p rate first_gen =
  [first_gen] ++ lemming_p rate next_gen
  where next_gen = lemming rate first_gen

lemming_status :: (Int, Int) -> String
lemming_status (prv, now) =
  "Previous Generation:" ++ (show prv) ++ "\n" ++
  "Now Generation:" ++ (show now) ++ "\n"

lemming_param :: Int
lemming_param = 2

take_generate = take 7

main :: IO ()
main = mapM_ putStrLn $ map lemming_status
       $ take_generate $ lemming_p lemming_param first_generation
    where
      first_generation = lemming_pair 1

ポイントは、まず最初にlemming関数、つまり「前の世代と今の世代」のペアを渡してあげると、次のペアが作成される関数を作る。これによって、無限の再帰的なリストとしてlemming_pが作れるようになる。というのは、前のlemmingから出来た結果を、次のlemmingに渡してあげることによって、次々と世代が繰り上がっていくことになる。実際に:

*Main> let lemming_two = lemming 2

*Main> lemming_two $ lemming_two $ lemming_two (1, 2)
(4,8)

といったように、関数が適用されるごとに、世代が繰り上がっていくようになる。これらの結果を無限リストとして実装してあげればよいことになる。しかし、この場合、普通なら無限に評価していくことになってしまわないか、という問題が出てくる。

実際、lemming_pをそのまま実行すると、停止しなくなってしまう。しかし、Haskellの場合は、必要な分だけ評価するという方法を取るので、takeという関数を使えば、必要な世代(上のコードならば、7世代) という取り方ができる。

なので、先ほどのコードを実行したら、下のような出力が得られる。

Previous Generation:1
Now Generation:2

Previous Generation:2
Now Generation:4

Previous Generation:3
Now Generation:6

Previous Generation:4
Now Generation:8

Previous Generation:5
Now Generation:10

Previous Generation:5
Now Generation:10

Previous Generation:5
Now Generation:10

なるほど、確かに5で安定した。そこで、先ほどの { \frac{X \times Y}{10}}の割合の部分、つまり{Y}という今年度のレミングの個体をさしていた部分を{Y = PX}とし、{P}を任意の割合に置き換えてみよう。例えば、先ほどは{P = 2}だったけれども、例えば{P = 3}ならどうなるだろう。先ほどのコードだと、lemming_paramを3に変更するといい。

Previous Generation:1
Now Generation:3

Previous Generation:3
Now Generation:9

Previous Generation:6
Now Generation:18

Previous Generation:7
Now Generation:21

Previous Generation:6
Now Generation:18

Previous Generation:7
Now Generation:21

Previous Generation:6
Now Generation:18

なんと、ある部分で一定の周期で増減し始めることが判明する。この場合なら、6、7、6、7といったように周期的に変動し、決して安定することがない。さらに今度は{P = 4}としてみよう。

Previous Generation:1
Now Generation:4

Previous Generation:4
Now Generation:16

Previous Generation:10
Now Generation:40

Previous Generation:0
Now Generation:0

上のような端数を取り除いたものだと、絶滅してしまう。

このように、パラメータによって、結果の経緯が全く変わってしまうようなものが存在していることがあるということらしい。ちょっと調べたところだと、おそらくロジスティック写像の説明を簡潔化したものだと思われるので、この辺りも面白そうなのでちゃんとおさえてみたい。

まとめ

ブログのキャッチ画像を心情写真にすると、抽象的に近況を伝えられて良いと思うので、今度からそうしていきたい。