一様分布乱数の応用

  1. モンテカルロシミュレーション
    1. モンテカルロシミュレーション
      シミュレーションではよく乱数を利用して動きを決定します。たとえば、交通における動作シミュレーションでは、車が右に回る、直進する、停止する、などのイベントを、乱数を発生させその数でイベントの種類を定めます。 この手法を、賭博で有名な場所の名前を冠して、モンテカルロシミュレーションと呼びます。

    2. 円の面積
      ここでは、[0..1]の範囲の一様乱数x,yを二つ発生させます。乱数を発生し、原点から(x,y)窓の距離が1以下となる数を調べます。これは、x,yが正の範囲の1/4円の内部の面積に比例しますから、πの値を推定することができます。

    3. 一様乱数の生成
      数学関数ライブラリに [0..1]区間の一様乱数を生成する関数があります。
       Math.random();

    4. グラフィック関数
      Colorは色を定義するクラスです。Color(220,50,50) で、赤、青、緑、の色の強さを最大255の値で定義します。setColor(cred) で以後利用する色を指定します。

       Color cred=new Color(220,50,50);
       g.setColor(cred);

      塗りつぶしの矩形を表示するには
       g.fillRect(ox,oy,rx,ry);
      を利用します。ox,oyは左上原点、rx,ry は幅と高さを指定します。

      円弧は矩形と同じ枠の中で、角度saからeaまでの楕円弧を表示します。
       g.drawArc(ox,oy,rx,ry,sa,ea);

  2. プログラム

    1. 画面レイアウト
       乱数を発生する回数を設定する箱(textField1)、と実行ボタン(button1)、計算したπの値を表示するラベル(label2)を用意します。

    2. 実行の流れ
       「指向」ボタンで呼び出される関数 button1_actionPerformed() で、repaint() を呼び出し、paint()を実行します。すべての処理は paint() で行っています。点を表示する関数はないので、同じ点を表示する 線表示関数 で点を描画しています。

    3. paint()
       乱数の生成個数をrndNumに読みとります。白で背景、黒で円弧を描きます。円弧は drawArc()で表示します。
       g.drawArc(ox-rx,oy,rx*2,ry*2,0,90)
      最後の、0,90は円弧を表示する始角と終角です。
       次に、rndNum 個の乱数の点(fx,fy)を発生し、点が円の外部なら赤色の点を表示し、内部なら緑の色を表示します。内部の場合、inCircl の値を一つ増します。
          for (i=0;i<rndNum;i++){
            fx=(float) Math.random();
            fy=(float) Math.random();
            if ((fx*fx + fy*fy)>1.0) {
              g.setColor(cgreen);//円外部
            }
            else {
              inCircl++;
              g.setColor(cred);//円内部
            }
            g.drawLine((int)(rx*fx)+ox,(int)(ry-(int)(ry*fy))+oy,
                         (int)(rx*fx)+ox,(int)(ry-(int)(ry*fy))+oy);
          }
      最後に、(double)inCircl/(double)rndNum)の4倍の値を表示します。これはπの値に相当します。
       label2.setText(Double.toString(4.0*((double)inCircl/(double)rndNum))

    4. ソース
       このプログラムのソースを示します。
      import java.awt.*;
      import java.awt.event.*;
      import java.applet.*;
      
      
      public class monteSim extends Applet {
        private boolean isStandalone = false;
      
        boolean BultIn=true;
        long arp;
      
        private Button button1 = new Button();
        private TextField textField1 = new TextField();
        private Label label1 = new Label();
        private Label label2 = new Label();
        private Label label3 = new Label();
        //引数値の取得
        public String getParameter(String key, String def) {
        return isStandalone ? System.getProperty(key, def) :
          (getParameter(key) != null ? getParameter(key) : def);
        }
      
      
       //コンポーネントの初期化
        public void init()  {
        this.setLayout(null);
        button1.setLabel("試行");
        button1.setBounds(new Rectangle(160, 250, 60, 20));
        
        button1.addActionListener(new java.awt.event.ActionListener() {
          public void actionPerformed(ActionEvent e) {
          button1_actionPerformed(e);
          }
        });
        
        textField1.setText("1000");
        textField1.setBounds(new Rectangle(80, 250, 60, 20));
        label1.setText("回数");
        label1.setBounds(new Rectangle(90, 230, 450, 20));
        
        label2.setBackground(SystemColor.activeCaptionText);
        label2.setText("label2");
        label2.setBounds(new Rectangle(20, 250, 60, 20));
        label3.setText("πの値");
        label3.setBounds(new Rectangle(20, 230, 40, 20));
        
        this.add(label2, null);
        this.add(label3, null);
        this.add(textField1, null);
        this.add(label1, null);
        this.add(button1, null);
        }
      
      
        public void paint(Graphics g)
        {
      
        float fx,fy,sumr=0.0f,sumd=0.0f;
        int i,inCircl=0;
        int ox=10,oy=10;//表示原点
        int rx=200,ry=200;//半径
        int rndNum;//乱数の個数
      
        Color cred=new Color(220,50,50);
        Color cgreen=new Color(50,220,50);
      
        rndNum=Integer.parseInt(textField1.getText());//回数を読む
      
        g.setColor(new Color(255,255,255)); //白色
        g.fillRect(ox,oy,rx,ry);  //背景
        g.setColor(new Color(0,0,0)); //黒色
        g.drawArc(ox-rx,oy,rx*2,ry*2,0,90); //円弧を表示
      
        for (i=0;i<rndNum;i++){
         //乱数を発生
          fx=(float) Math.random();
          fy=(float) Math.random();
          if ((fx*fx + fy*fy)>1.0) {
          g.setColor(cgreen);//円の外部外部
          }
          else {
          inCircl++;
          g.setColor(cred);//円の内部内部
          }
          //点を表示 原点:(ox,oy)
          g.drawLine((int)(rx*fx)+ox,(int)(ry-(int)(ry*fy))+oy,
                 (int)(rx*fx)+ox,(int)(ry-(int)(ry*fy))+oy);
        }
         //πの表示
        label2.setText(Double.toString(4.0*((double)inCircl/(double)rndNum)));
        }
      
        void button1_actionPerformed(ActionEvent e) {
          //シミュレーションを行うため再表示
        repaint();
        }
      
      }

    5. 実行
       試行回数を入力し、再試行ボタンを押します。点をランダムに生成し、円の外部の点は赤色、内部の点を緑で表示します。内部の点の数から、円の面積を推定し、πの値を表示します。



  3. 演習
     
    1. 演習1
      プログラムのソースをファイルに保存し、コンパイル&実行をして下さい。また、計算されるπの値と実際の値を比較して下さい。

    2. 演習2
       同じ方法で、円の替わりに、2次関数 y=x2 と x軸の[0,1]区間で囲まれる面積を計算してください。これは[0,1]区間の一様分布の分散を求める場合の、二乗期待値になります。
      関数表示は
       g.drawArc(ox-rx,oy,rx*2,ry*2,0,90);
      のかわりに、こんな感じになります。

      for(double qx=0.0;qx<0.9;qx=qx+0.1){
        g.drawLine(ox+(int)(qx*rx),oy+(int)((1-qx*qx)*rx),
        ox+(int)((qx+0.1)*rx),(int)(oy+(1-(qx+0.1)*(qx+0.1))*rx));
      }

      qx のx座標に対し、その2乗の qx*qx をy座標として表示すればよいのです。ただし、この値は 0..1 ですから、これにrx倍しoxを加えます。また、y座標は、上下が逆ですから 1-y をy座標とします。