ブラウン運動の可視化プログラムをC++とHTML5でそれぞれ作ってみた

はじめに

久々にC++書きたいなー、とか、物理現象を可視化したいなー、とか、HTML5弄ってみたいなー、とか思っていました。

そんな折、数学ガールを読んでいて、ふと目に付いた「ブラウン運動」の文字。

そんなわけで「ブラウン運動の可視化」を題材にして、C++HTML5でそれぞれ実装してみました。

実行結果

とりあえず実行結果を載せます。

C++

まずは二次元のブラウン運動から。要素数は100で、10000ステップまで50ステップごとに表示しています。周期境界条件を採用しており、端まで行った点は反対の端から出てきます。

gifアニメとYouTubeでどうぞ。最初は中心に集まっていた点が、ステップが進むごとに領域全体に広がっていくのが確認できます。

http://cdn-ak.f.st-hatena.com/images/fotolife/t/takahirox/20110821/20110821171748.gif


続いて三次元。角度が悪くて、z方向への散らばりがわかりづらいかも。

http://cdn-ak.f.st-hatena.com/images/fotolife/t/takahirox/20110821/20110821171700.gif


HTML5

http://gachapin.jp/brown2d.html

canvasに対応したブラウザでご覧ください。環境によっては動作が重いかもしれません。

素数の変更やx方向へのバイアスがかけられます。

ブラウン運動とは

後で書く

とりえあずWikiPediaを参照してください。

今回は結晶中の電子(電子が粒子であるとしています)の動きを想定しています。なのでHTML5版では「x方向にバイアス=X方向に電界を与える」ことができるようにし、電子が流れる様(=電流が流れる様)を再現してみました。

C++での実装

最初に二次元の実装をしました。

C++の正しい書き方」をあまり知らないので、変なところがあればご指摘ください。

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <ctime>
using namespace std;

#define CONSTRAINT 100

class Electron {

protected:

  int x ;
  int y ;

  virtual void checkConstraint( ) ;
  virtual unsigned int getRandom( ) { return rand( ) % 4 ; }

public:

  Electron( ) { x = 0; y = 0 ; }
  virtual void move( ) ;
  virtual void display( ostream &os ) ;
  static void initSeed( ) { srand( ( unsigned ) time( NULL ) ) ; }
  static void initSeed( unsigned int seed ) { srand( seed ) ; }

} ;

void Electron::move( ) {

  switch( getRandom( ) ) {
    case 0:
      x++ ;
      break ;
    case 1:
      x-- ;
      break ;
    case 2:
      y++ ;
      break ;
    case 3:
      y-- ;
      break ;
    default:
      break ;
  }

  checkConstraint( ) ;

}

void Electron::checkConstraint( ) {

  if( x > CONSTRAINT )
    x = - CONSTRAINT ;
  if( x < - CONSTRAINT )
    x = CONSTRAINT ;
  if( y > CONSTRAINT )
    y = - CONSTRAINT ;
  if( y < - CONSTRAINT )
    y = CONSTRAINT ;

}

void Electron::display( ostream &os ) {
  os << x << "," << y << endl ;
}

void output( Electron *e[], unsigned int element_num, unsigned int step ) {

  char output_file[20] ;

  sprintf( output_file, "%06d.csv", step ) ;
  ofstream ofs( output_file ) ;
  if( ofs ) {
    for( unsigned int i = 0; i < element_num; i++ )
      e[ i ]->display( ofs ) ;
    cout << "[info] " << output_file << " was generated." << endl ;
  } else {
    cout << "[error] " << output_file << " can't open." << endl ;
  }

}

int main( int argc, char **argv ) {

  if( argc != 4 ) {
    cout << "usage : " << argv[ 0 ] << " <element#> <loop#> <step>" << endl ;
    return -1 ;
  }

  char output_file[20] ;

  // need to validate arguments.
  unsigned int element_num = atoi( argv[ 1 ] ) ;
  unsigned int loop = atoi( argv[ 2 ] ) ;
  unsigned int step = atoi( argv[ 3 ] ) ;

  Electron *e[ element_num ] ;
  unsigned int i, j ;

  Electron::initSeed( ) ;

  for( i = 0; i < element_num; i++ )
    e[ i ] = new Electron[ element_num ] ;

  output( e, element_num, 0 ) ;

  for( i = 0; i < loop + 1; i++ ) {

    for( j = 0; j < element_num; j++ ) {
      e[ j ]->move( ) ;
    }

    if( i % step == step - 1 ) {
      output( e, element_num, i + 1 ) ;
    }

  }

  for( i = 0; i < element_num; i++ )
    delete e[ i ] ;

  return 0 ;

}


つぎにこんなスクリプトを書いて、gifアニメとFLVファイルを出力しました。

#!/bin/bash

ELEMENT_NUM=100
COUNT=10000
STEP=50

make brown2d

./brown2d $ELEMENT_NUM $COUNT $STEP

i=1
for step in $( seq 0 $STEP $COUNT )
do

  echo set datafile separator \",\"              >  .tmp
  echo set xrange[-100:100]                      >> .tmp
  echo set yrange[-100:100]                      >> .tmp
  echo set term png                              >> .tmp
  echo set output \"$( printf %06d $i ).png\"    >> .tmp
  echo plot \"$( printf %06d $step ).csv\"       >> .tmp
  echo set output                                >> .tmp
  gnuplot .tmp
  echo [log] $step step done.

  i=$(( i + 1 ))
  rm -f .tmp
  rm -f $( printf %06d $step ).csv

done

convert *.png brown2d.gif
echo [log] brown2d.gif was generated.

ffmpeg -r 6 -i %06d.png brown2d.flv
echo [log] brown2d.flv was generated.

rm *.png


このスクリプトを実行して出来上がったのが、上で貼ったgifアニメとYouTubeの動画です。


続いて三次元バージョンの実装をしました。二次元のものを継承して作成しました。

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <ctime>
using namespace std;

#define CONSTRAINT 100

class Electron {

... snip ...

class Electron3D : public Electron {

protected:

  int z ;

  virtual void checkConstraint( ) ;
  virtual unsigned int getRandom( ) { return rand( ) % 6 ; }

public:

  Electron3D( ) { z = 0; }
  virtual void move( ) ;
  virtual void display( ostream &os ) ;

} ;

void Electron3D::move( ) {

  switch( getRandom( ) ) {
    case 0:
      x++ ;
      break ;
    case 1:
      x-- ;
      break ;
    case 2:
      y++ ;
      break ;
    case 3:
      y-- ;
      break ;
    case 4:
      z++ ;
      break ;
    case 5:
      z-- ;
      break ;
    default:
      break ;
  }

  checkConstraint( ) ;

}

void Electron3D::checkConstraint( ) {

  Electron::checkConstraint( ) ;

  if( z > CONSTRAINT )
    z = - CONSTRAINT ;
  if( z < - CONSTRAINT )
    z = CONSTRAINT ;

}

void Electron3D::display( ostream &os ) {
  os << x << "," << y << "," << z << endl ;
}

... snip ...

int main( int argc, char **argv ) {

... snip ...

  Electron *e[ element_num ] ;
  unsigned int i, j ;

  Electron3D::initSeed( ) ;

  for( i = 0; i < element_num; i++ )
    e[ i ] = new Electron3D ;

... snip ...

}


二次元のElectronクラスをもっとうまく書けば、Electron3Dの実装量は減るはずです。

二次元のものと同様にこんなスクリプトを書いてgifアニメとFLVファイルを作成しました。

#!/bin/bash

ELEMENT_NUM=100
COUNT=10000
STEP=50

make brown3d

./brown3d $ELEMENT_NUM $COUNT $STEP

i=1
for step in $( seq 0 $STEP $COUNT )
do

... snip ...

  echo set zrange[-100:100]                      >> .tmp

... snip ...

done

convert *.png brown3d.gif
echo [log] brown3d.gif was generated.

ffmpeg -r 6 -i %06d.png brown3d.flv
echo [log] brown3d.flv was generated.

rm *.png

HTML5での実装

続いてHTML5で実装。cavnvasの三次元対応が面倒そうだったので、とりあえず二次元のみ実装。

C++からjavascripへの移植に加えて、パラメータを動的に設定できるようにしました。さらにx方向へバイアスを加えられるようにしました。

<html>
<head>
<script type="text/javascript">
var Width = 300, Height = 300 ;
var element_num ;
var surface ;
var img ;
var electrons ;
var interval ;
var bias ;

function object( o ) {
  var f = object.f, i, len, n, prop ;
  f.prototype = o ;
  n = new f ;
  for( i = 1, len = arguments.length; i < len; i++ )
    for( prop in arguments[ i ] )
      n[ prop ] = arguments[ i ][ prop ] ;
    return n ;
}
object.f = function( ){ } ;

var Electron = {

  x: Width / 2,
  y: Height / 2,

  checkConstraint: function( ) {

    if( this.x > Width )
      this.x = 0 ;
    if( this.x < 0 )
      this.x = Width ;
    if( this.y > Height )
      this.y = 0 ;
    if( this.y < 0 )
      this.y = Height ;

  },

  getRandom: function( ) {
    return parseInt( Math.random( ) * 100 ) ;
  },

  move: function( ) {

    var r = this.getRandom( ) ;

    if( r < 24 - bias )
      this.x-- ;
    else if( r < 49 )
      this.x++ ;
    else if( r < 74 )
      this.y-- ;
    else
      this.y++ ;

    this.checkConstraint( ) ;

  },

  beRandom: function( ) {
    this.x = parseInt( Math.random( ) * Width ) ;
    this.y = parseInt( Math.random( ) * Height ) ;
  },

  display: function( ) {
    surface.drawImage( img, this.x - 1 , this.y - 1) ;
  }

} ;

function init( ) {

  surface = document.getElementById( "canvas" ).getContext( "2d" ) ;
  initBias( ) ;
  initImage( ) ;
  initElectrons( ) ;

}

function initImage( ) {

  stop( ) ;

  if( ! img )
    img = new Image( ) ;
  img.src = document.getElementById( "color" ).value ;

  start( ) ;

}

function update( ) {

  surface.clearRect( 0, 0, Width, Height ) ;

  for( var i = 0; i < element_num; i++ ) {
    var e = electrons[ i ] ;
    e.move( ) ;
    e.display( )
  }

}

function initBias( ) {
  bias = document.getElementById( "bias" ).value ;
}

function initElectrons( ) {

  stop( ) ;

  element_num = document.getElementById( "element_num" ).value ;
  electrons = new Array( ) ;
  for( var i = 0; i < element_num; i++ ) {
    var e = object( Electron ) ;
    electrons.push( e ) ;
  }

  start( ) ;

}

function stop( ) {
  if( interval )
    clearInterval( interval ) ;
}

function start( ) {
  interval = setInterval( update, 1000 / 60 ) ;
}

function beRandom( ) {

  stop( ) ;

  for( var i = 0; i < element_num; i++ )
    electrons[ i ].beRandom( ) ;

  start( ) ;

}

</script>
</head>
<body onLoad="init( )">

<p>
<label>Electrons#</label>
<select id="element_num" name="element_num" onChange="initElectrons( )">
<option value="1">1</option>
<option value="5">5</option>
<option value="10">10</option>
<option value="50">50</option>
<option value="100" selected>100</option>
<option value="500">500</option>
<option value="1000">1000</option>
<option value="5000">5000</option>
<option value="10000">10000</option>
</select>
</p>

<p>
<label>Color</label>
<select id="color" name="color" onChange="initImage( )">
<option value="black.png" selected>black</option>
<option value="red.png">red</option>
<option value="blue.png">blue</option>
</select>
</p>

<p>
<button type=button onClick="beRandom( )">beRandom</button>
</p>

<p>
<label>Bias</label>
<select id="bias" name="bias" onChange="initBias( )">
<option value="25">+25</option>
<option value="20">+20</option>
<option value="15">+15</option>
<option value="10">+10</option>
<option value="5">+5</option>
<option value="1">+1</option>
<option value="0" selected>0</option>
<option value="-1">-1</option>
<option value="-5">-5</option>
<option value="-10">-10</option>
<option value="-15">-15</option>
<option value="-20">-20</option>
<option value="-25">-25</option>
</select>
</p>

<canvas id="canvas" width="300" height="300" style="border:medium solid #000000" />

</body>
</html> 

ソースコード

ソースコードGoogle codeでダウンロードできます。

終わりに

久々にC++を書きました。「そういやこういう仕様だったなぁ」などと思い出しながら実装しました。

HTML5は面白いですね。色々作ってみたくなりました。

なお、弱い乱数では結果が収束してしまうことがあります。お使いの環境によってはそれが見えてしまうかもしれません。