Subscribed unsubscribe Subscribe Subscribe

random walk

Haskell

分ける・詰め込む・塗り分ける―読んで身につく数学的思考法より、

偏りのないコインを投げつづけよう。ここで、「偏りのない」とは、「表」と「裏」が等しく0.5の確率で出る事を意味している。投げるごとに、表がでたか裏が出たかを記録する。それぞれの回数はどのように増えていくのだろうか?たとえば、ある時点で表の出た回数が裏を大きく引き離し、100回の差がついたら、それ以降は裏が「巻き返し」をはじめるのか?

と始まる節に、ランダムウォークについて書いてあった。

本にはコンピュータを用いてランダムウォークを描いてあったので、実際にやってみた。

ランダムな試行

コインをランダムに投げる様は、標準ライブラリのSystem.Randomを使う。ランダムに生成したいデータ型をEnum/Boundedのインスタンスにしておくことで、Randomのメソッドを簡単に実装できる。

data Coin = Head | Tail deriving (Eq, Enum, Bounded, Show)

instance Random Coin where
  randomR (a, b) g = let (x, g') = randomR (fromEnum a, fromEnum b) g
                     in  (toEnum x, g')
  random g         = randomR (minBound, maxBound) g

実際に動かすとこんな感じ。

*Main> g <- newStdGen
*Main> take 5 $ randoms g :: [Coin]
[Head,Head,Tail,Head,Head]

Diagrams

次にグラフィックス。Haskellにはいくつか2Dグラフィックスライブラリがある。一つ目に、Diagramsを試してみた。

乱数生成器を引数に渡していくのは嬉しくないので、StateTを使って隠している。

type Rand = StateT StdGen

tosses :: Monad m => Rand m [Coin]
tosses = randoms <$> get

plot :: [Coin] -> [Double]
plot = scanl go 0
  where go !a Head = a + 1
        go !a Tail = a - 1

randomWalk :: Monad m => Rand m Diagram
randomWalk = do
  tossLine <- plot <$> tosses
  return $ straight $ pathFromVertices $ zip [0.01, 0.02 ..] (take 50000 tossLine)

main :: IO ()
main = getStdGen >>=
       evalStateT randomWalk >>=
       renderAs PNG "random-walk-diagrams.png" Auto

出力された図はこちら。

複数本のランダムウォークを出力する場合、簡単に原点を合わせる方法が見あたらなかった。あとでコードを読んでみよう。

newSeed :: Rand IO ()
newSeed = liftIO newStdGen >>= put

randomWalks :: Int -> Rand IO Diagram
randomWalks = foldl1 (liftM2 (##)) . flip replicate (newSeed >> randomWalk)

main' :: IO ()
main' = getStdGen >>=
        evalStateT (newSeed >> randomWalks 5) >>=
        renderAs PNG "random-walk-diagrams-multi.png" Auto

Hieroglyph

続いて、Hieroglyphを試してみた。

Coinは全く同じ。randomWalkもやっていることは同じ。

data Coin = Head | Tail deriving (Eq, Enum, Bounded, Show)

instance Random Coin where
  randomR (a, b) g = let (x, g') = randomR (fromEnum a, fromEnum b) g
                     in  (toEnum x, g')
  random g         = randomR (minBound, maxBound) g

type Rand = StateT StdGen

tosses :: Monad m => Rand m [Coin]
tosses = randoms <$> get

plot :: [Coin] -> [Double]
plot = scanl go 0
  where go !a Head = a + 1
        go !a Tail = a - 1

randomWalk :: Monad m => Rand m Primitive
randomWalk = do
  (p0:ps) <- take 50000 . zipWith Point [0.01, 0.02 ..] . plot <$> tosses
  rgba <- colour . fst . random <$> get
  return path { begin    = p0
              , segments = map Line ps
              , attribs  = plain { astrokeRGBA = rgba }
              }

Hieroglyphでは簡単に原点を合わせることができたので、はじめから複数本を出力する。出力が同じ色だと見にくいので色もランダムに決まるようにする。HaskellではたぶんデファクトスタンダードとなっているData.Colourを使う。

data NamedColor = Coral | DeepPink | LightGreen | MediumAquaMarine | Orchid | Plum | Salmon | Tan | Crimson
                deriving (Eq, Enum, Bounded, Show)
  
instance Random NamedColor where
  randomR (a, b) g = let (x, g') = randomR (fromEnum a, fromEnum b) g
                     in  (toEnum x, g')
  random g         = randomR (minBound, maxBound) g
  
colour :: (Ord a, Floating a) => NamedColor -> AlphaColour a
colour Coral            = opaque coral
colour DeepPink         = opaque deeppink
colour LightGreen       = opaque lightgreen
colour MediumAquaMarine = opaque mediumaquamarine
colour Orchid           = opaque orchid
colour Plum             = opaque plum
colour Salmon           = opaque salmon
colour Tan              = opaque tan
colour Crimson          = opaque crimson

最後に、レンダリング部分。描画サイズの自動指定(DiagramsでいうところのAuto)がないみたい。

newSeed :: Rand IO ()
newSeed = liftIO newStdGen >>= put
  
randomWalks :: Int -> Rand IO BaseVisual
randomWalks = liftM (foldl1 (#/#)) . flip replicateM (newSeed >> (:[]) <$> randomWalk)
  
main :: IO ()
main = getStdGen >>=
       liftM (settranslatey 300) <$> evalStateT (randomWalks 5) >>=
       renderToPNG "random-walk-hieroglyph.png" 600 600

出力はこちら。はみ出ちゃってるけどガマンの子。

所感

  • Diagramsの方がHieroglyphより扱いやすいというかインタフェースが使いやすい
  • Hieroglyphの方がコンパイル時間がかかる。かなり。
  • Diagramsで原点を合わせる方法を調べよう
  • Reactive作者のConal Elliottが言っているように、DiagramsとFRPを組み合わせて、functional reactive animationにしたら面白そう
  • HieroglyphはFRPライブラリのBusterというのを持っているらしい