SimExtinct.java

////////////////////////////////////////////
//
//   Evaluation of Extinction Risk of Endangered Plants
//
//   Java Implementation of the algorithm proposed by Yahara, T and Kato, T.
//   (http://risk.kan.ynu.ac.jp/matsuda/redlist.html)
//
//      Copyright May 2004 by Takenaka, A.

import java.awt.*;
import java.applet.*;
import java.awt.event.*;
import java.util.*;


//////////////////////////////////////////////////////////////
//
//  メインのクラス.Applet クラスを継承している.

public class SimExtinct
    extends Applet
{
    /////////////////////
    //  Instance variables.

    Population population;           // 各ブロック内の個体数を管理するオブジェクト
    AbundanceTransition transition;  //  ブロック内個体数の変化を計算するオブジェクト

    Graph graph;   //  個体数グラフを描画するオブジェクト


    //  個体数の各クラス(階級の意味の)のラベルと代表値
    String abundance_label[] = {"〜10", "〜100", "〜1000", "1000〜"};
    int startAbundance[] = {3, 31, 316, 3162};

    //  ブロック数入力用のテキストフィールド
    TextField abundance_tf[] = new TextField[abundance_label.length];

    //  変化率の各クラス(階級の意味の)のラベルと代表値
    String rate_label[] = {"絶滅", "〜1/100", "〜1/10", "〜1/2", "〜1", "1〜"};
    double changeRate[] = {0.001, 0.01, 0.1, 0.5, 1.0, 1.0};

    //  ブロック数入力用のテキストフィールド
    TextField rate_tf[] = new TextField[rate_label.length];

    //  各種入力・表示用テキストフィールド
    TextField rep_tf = new TextField();  //  繰り返し回数.
    TextField cum_tf = new TextField();  //  積算計算回数
    TextField ini_plants_tf = new TextField();  //  初期個体数

    int repetition;  //

    int max_years = 100;                // 全部で何年計算するか.
    int yr_step = 10;                   //  1ステップが何年相当か.
    int n_steps = max_years / yr_step;  //  何ステップ計算するか.

    int cum_run = 0;    //  積算計算回数(初期値およびリセット後はゼロ)

    //  n ステップ目までに絶滅した試行回数.
    int cum_extinct[] = new int[n_steps + 1];  

    //  絶滅試行回数から計算した絶滅確率.
    double cum_extinct_prob[] = new double[n_steps + 1];

    //  絶滅確率表示用テキストフィールド
    TextField extinct_tf[] = new TextField[n_steps + 1];

    //  アプレット領域全体の背景色
    Color background = new Color(240, 240, 255);


    /////////////////////
    //  絶滅データを初期化.

    private void initStats()
    {
        cum_run = 0;

        for (int i = 0; i <= n_steps; ++i) {
            cum_extinct[i] = 0;
        }

        showStats();
    }

    /////////////////////
    //  絶滅データの表示

    private void showStats()
    {
        Label label;
        double prob;

        for (int i = 0; i <= n_steps; ++i) {
            if (cum_run > 0) {
                prob = (float) cum_extinct[i] / (float) cum_run;
            }
            else {
                prob = 0.0;
            }
            prob = (int) (prob * 1000 + 0.5);
            prob /= 10.0;
            extinct_tf[i].setText(Double.toString(prob));
        }

        cum_tf.setText(Integer.toString(cum_run));
    }

    /////////////////////
    //  設定繰り返し回数だけ,個体数の変動を計算する.

    private void calculate()
    {
        if (transition.isReady() == false) {  //  変動率が正しく設定されてないなら
            return;                           //  計算しない.
        }

        setPopulationSize();

        if (population.isReady() == false) {  //  個体数データが正しく設定されてないなら
            return;                           //  計算しない.
        }

        // 初期個体数の表示とグラフオブジェクトへの設定.

        ini_plants_tf.setText(Integer.toString(population.getNumberOfPlants()));
        graph.setMaxPlants(population.getNumberOfPlants());

       //  以下,繰り返して試行.

        for (int i_run = 0; i_run < repetition; ++i_run) {

            setPopulationSize();  //  個体数オブジェクトの初期設定
            
            int n_extant[] = new int[n_steps + 1];  //  生存個体数.
            n_extant[0] = population.getNumberOfPlants();

            cum_extinct[0] = 0;  //  最初はまだ絶滅してない.

            for (int i_step = 1; i_step <= n_steps; ++i_step) {  // ステップごとに

                population.calcNextAbundance(transition);  //  新しい個体数を計算.

                n_extant[i_step] = population.getNumberOfPlants();

                if (population.isExtinct()) {  //  絶滅してたら…
                    //  このステップでの絶滅試行回数をひとつ増やす.
                    ++cum_extinct[i_step];  
                }
            }
            ++cum_run;  //  積算試行回数のカウントアップ

            graph.draw_run(n_extant);  //  この回の結果をグラフに表示.
        }
    }

    /////////////////////
    //  繰り返し試行回数の設定.

    private void setRepetition()
    {
        try {
            //    テキストフィールドの入力データを読む.
            repetition = Integer.parseInt(rep_tf.getText());
         }
         catch( NumberFormatException e) {
            repetition = 0;  //  ちゃんと入力されてなかったらゼロ回とする.
        }
    }

    /////////////////////
    //  初期個体数データの設定

    private void setPopulationSize()
    {
        population.reset();

        int freq[] = new int[abundance_label.length];

        for (int i = 0; i < abundance_label.length; ++i) {
            try {
                // テキストフィールドの入力データを読む.
                freq[i] = Integer.parseInt(abundance_tf[i].getText());
             }
             catch( NumberFormatException e) {  //  データ不正.
                freq[i] = 0;
            }
         }

        population.initialize(startAbundance, freq);
    }


    /////////////////////
    //  変動率データの設定.

    private void setTransitionRate()
    {
        transition.reset();

        int change[] = new int[rate_label.length];

        for (int i = 0; i < rate_label.length; ++i) {
            try {
                //  テキストフィールドの入力データを読む.
                change[i] = Integer.parseInt(rate_tf[i].getText());
             }
             catch( NumberFormatException e) {  //  データ不正.
                change[i] = 0;
            }
        }

        change[0] += 1;  //  For rare mass destruction.

        transition.setChangeRateFrequencies(change);
    }

    /////////////////
    //  アプレットの初期化.実行開始時に自動的に呼び出される.

    public void init ()
    {
        Button bt_graph, bt_go;

        population = new Population();  //  個体数管理オブジェクトを生成

        transition = new AbundanceTransition(); //  個体数変動計算オブジェクトを生成
        transition.setChangeRates(changeRate);  // 各変動クラスの代表値を教える.

        setLayout(null);
        setSize(getSize().width, getSize().height);

        //  以下,各種のコンポーネントをウインドウ内に配置.

        Label label;

        label = new Label("現存する株数(該当メッシュ数)");
        label.setBackground(background);
        label.setFont(new Font("Dialog",Font.BOLD, 16));
        label.setSize(300, 20);
        label.setLocation(50, 5);
        this.add(label);
        
        for (int i = 0; i < abundance_label.length; ++i) {
            label = new Label(abundance_label[i]);
            label.setFont(new Font("Dialog",Font.BOLD, 16));
            label.setBackground(background);
            label.setSize(100, 20);
            label.setLocation(i * 100 + 50, 30);
            this.add(label);

            abundance_tf[i] = new TextField(20);
            abundance_tf[i].setFont(new Font("Dialog",Font.BOLD, 16));
            abundance_tf[i].setSize(50, 20);
            abundance_tf[i].setLocation(i * 100 + 50, 50);
            this.add(abundance_tf[i]);
        }

        label = new Label("以前からの変化(該当メッシュ数)");
        label.setFont(new Font("Dialog",Font.BOLD, 16));
        label.setBackground(background);
        label.setSize(400, 20);
        label.setLocation(50, 90);
        this.add(label);

        for (int i = 0; i < rate_label.length; ++i) {
            label = new Label(rate_label[i]);
            label.setFont(new Font("Dialog",Font.BOLD, 16));
            label.setBackground(background);
            label.setSize(100, 20);
            label.setLocation(i * 100 + 50, 115);
            this.add(label);

            rate_tf[i] = new TextField(20);
            rate_tf[i].setFont(new Font("Dialog",Font.BOLD, 16));
            rate_tf[i].setSize(50, 20);
            rate_tf[i].setLocation(i * 100 + 50, 135);
            this.add(rate_tf[i]);
        }

        label = new Label("繰り返し回数");
        label.setFont(new Font("Dialog",Font.BOLD, 16));
        label.setBackground(background);
        label.setSize(110, 20);
        label.setLocation(650, 40);
        this.add(label);

        rep_tf = new TextField(20);
        rep_tf.setFont(new Font("Dialog",Font.BOLD, 16));
        rep_tf.setSize(60, 20);
        rep_tf.setLocation(770, 40);
        this.add(rep_tf);

        bt_go = new Button ("計算");
        bt_go.setSize(100, 25);
        bt_go.setLocation(650, 80);
        bt_go.setFont(new Font("Dialog",Font.BOLD, 14));
        this.add(bt_go);

        //  [計算]ボタンをクリックした時の動作内容の設定.

        bt_go.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                //  テキストフィールドからデータを取得.
                setRepetition();         
                setTransitionRate();
                setPopulationSize();
                graph.setMaxPlants(population.getNumberOfPlants());

                //  計算の実行と絶滅率の表示.
                calculate();
                showStats();
            }
        });

        //  [リセット]ボタンをクリックした時の動作内容の設定.

        bt_graph = new Button ("リセット");
        bt_graph.setSize(100, 25);
        bt_graph.setLocation(650, 120);
        bt_graph.setFont(new Font("Dialog",Font.BOLD, 14));
        this.add(bt_graph);

        bt_graph.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                initStats();            //  統計データをリセット
                graph.drawGraphArea();  //  グラフもまっさらに.
            }
        });

        label = new Label("% 絶滅率");
        label.setFont(new Font("Dialog",Font.BOLD, 16));
        label.setBackground(background);
        label.setSize(90, 20);
        label.setLocation(20, getSize().height - 100);
        this.add(label);

        for (int i = 1; i <= n_steps; ++i) {
            label = new Label(Integer.toString(i * yr_step) + " yr");
            label.setFont(new Font("Dialog",Font.BOLD, 16));
            label.setBackground(background);
            label.setSize(60, 20);
            label.setLocation(40 + i * 75, getSize().height - 100);
            this.add(label);
        }

        for (int i = 0; i <= n_steps; ++i) {
            extinct_tf[i] = new TextField();
            extinct_tf[i].setFont(new Font("Dialog",Font.BOLD, 16));
            extinct_tf[i].setSize(65, 20);
            extinct_tf[i].setLocation(40 + i * 75, getSize().height - 70);
            this.add(extinct_tf[i]);
            extinct_tf[i].setText("0.0");
        }

        label = new Label("現個体数");
        label.setFont(new Font("Dialog",Font.BOLD, 16));
        label.setBackground(background);
        label.setSize(70, 20);
        label.setLocation(40, getSize().height - 30);
        this.add(label);

        ini_plants_tf = new TextField();
        ini_plants_tf.setFont(new Font("Dialog",Font.BOLD, 16));
        ini_plants_tf.setSize(65, 20);
        ini_plants_tf.setLocation(120, getSize().height - 30);
        this.add(ini_plants_tf);

        label = new Label("延べ回数");
        label.setFont(new Font("Dialog",Font.BOLD, 16));
        label.setBackground(background);
        label.setSize(70, 20);
        label.setLocation(240, getSize().height - 30);
        this.add(label);

        cum_tf = new TextField();
        cum_tf.setFont(new Font("Dialog",Font.BOLD, 16));
        cum_tf.setSize(65, 20);
        cum_tf.setLocation(320, getSize().height - 30);
        this.add(cum_tf);

        //  グラフ領域オブジェクトの生成

        graph = new Graph();
        graph.setSize(getSize().width - 80, getSize().height - 290);
        graph.setLocation(40, 170);
        this.add(graph);

        graph.setMaxYear(max_years);
        graph.setYearStep(yr_step);
        graph.drawGraphArea();

        setBackground(new Color(240, 240, 255));
    }
}


///////////////////////////////////////////////////////////////////
//
//  個体数の時間変化のグラフを描画するクラス

class Graph extends Canvas
{

    // キャンバス全領域内でのグラフ領域周辺のマージンと,グラフ領域の長さ.
    int   margin_l, margin_r, max_x;  
    int   margin_t, margin_b, max_y;

    //  論理座標上のサイズをピクセル数へ.
    double scale_x;  
    double scale_y;

    //  論理座標の最大値
    int  max_years;
    int  max_plants;

    //  年のきざみ.
    int  yr_step;

    //  画像バッファ.描画はバッファに行ったあとでコピー(ちらつき防止).
    Image    buffer;

    Color backColor;
    Color graphColor;
    Color lineColor;
    Color extinctLineColor;
    Color gridColor;
    Color fontColor;

    /////////////////////
    //  Constructor.

    public Graph ()
    {
        super();  //  親クラスのコンストラクタ.

        //  仮の値.
        max_years   = 100;
        max_plants = 100;
        yr_step = 10;
        
        margin_l = 50;
        margin_r = 10;
        margin_b = 50;
        margin_t = 40;

        //  色設定.
        backColor  = new Color(250, 250, 255);
        graphColor = new Color(200, 200, 255);
        gridColor  = new Color(128, 128, 255);
        fontColor  = new Color(0, 0, 128);
        lineColor  = new Color(32, 32, 128);        //  生延びた試行の色
        extinctLineColor = new Color(255, 64, 64);  //  絶滅した試行の色
    }

    ///////////////////////////////////
    //  何年目まで計算するか.

    public void setMaxYear(int yr)
    {
        max_years = yr;
        scale_x  = (double) max_x / (double) max_years;
    }

    ///////////////////////////////////
    //  初期の個体数(個体数は単調減少するので,これが最大).

    public void setMaxPlants(int pl)
    {
        max_plants = pl;
        scale_y  = (double) max_y / (double) max_plants;
    }

    ///////////////////////////////////
    //  何年きざみで計算するか.

    public void setYearStep(int step)
    {
        yr_step = step;
    }

    ///////////////////////////////////
    //  グラフ領域の描画.軸だのグリッドだの.
    
    public void drawGraphArea()
    {
        if (buffer == null) {
            buffer = createImage(getSize().width, getSize().height);
        }

        max_x = getSize().width - (margin_l + margin_r);
        max_y = getSize().height - (margin_t + margin_b);

        scale_x  = (double) max_x / (double) max_years;
        scale_y  = (double) max_y / (double) max_plants;

        //  バッファのグラフィックコンテキスト.
        Graphics buffer_g = buffer.getGraphics();

        buffer_g.setColor(backColor);
        buffer_g.fillRect(0, 0, getSize().width, getSize().height);

        buffer_g.setColor(graphColor);
        buffer_g.fillRect(margin_l, margin_t, max_x, max_y);
        
        //  グリッド

        buffer_g.setColor(gridColor);

        int n = (int) ((double) max_years / (double) yr_step);
        for (int i = 0; i <= n; ++i) {
            buffer_g.drawLine(locToCanvasX(i * 10), 
                            locToCanvasY(0), 
                            locToCanvasX(i * 10),
                            locToCanvasY(max_plants));
        }

        for (int i = 0; i <= 10; ++i) {
            buffer_g.drawLine(locToCanvasX(0), 
                            locToCanvasY((int) (max_plants * (double) i / 10.00)), 
                            locToCanvasX(max_years),
                            locToCanvasY((int) (max_plants * (double) i / 10.00)));
        }

        //  ラベルなど.

        buffer_g.setColor(fontColor);

        buffer_g.setFont(new Font("Dialog",Font.BOLD, 20));
        buffer_g.drawString("年", margin_l + max_x / 2 - 30, margin_t + max_y + 35);
        buffer_g.drawString("生残株 (%)", 5, margin_t - 10);

        buffer_g.setFont(new Font("Dialog",Font.BOLD, 18));
        buffer_g.drawString("0", margin_l, margin_t + max_y + 25);
        buffer_g.drawString(Integer.toString(max_years), 
                         margin_l + max_x - 30, margin_t + max_y + 25);
        buffer_g.drawString("100", 10, margin_t + 15);

        buffer_g.dispose();
        repaint();
    }

    /////////////////////
    //  一回の試行結果をグラフに描画.

    public void draw_run(int[] n_extant)
    {
        //  バッファのグラフィックコンテキスト.
        Graphics buffer_g = buffer.getGraphics();

        if (n_extant[n_extant.length - 1] > 0) {  // 最後まで生き残ったか
			buffer_g.setColor(lineColor);         // 生延び色で描く.
		}
		else {
			buffer_g.setColor(extinctLineColor);  //  絶滅色で描く.
		}

        for (int i = 1; i < n_extant.length; ++i) {
            buffer_g.drawLine(locToCanvasX((i - 1) * 10), 
                            locToCanvasY(n_extant[i - 1]), 
                            locToCanvasX(i * 10),
                            locToCanvasY(n_extant[i]));
        }

        buffer_g.dispose();
        repaint();
    }

    /////////////////////
    //  論理座標から物理座標へ
    
    private int locToCanvasX(int x)
    {
        return margin_l + (int) (x * scale_x);
    }

    /////////////////////

    private int locToCanvasY(int y)
    {
        return max_y - (int) (y * scale_y) + margin_t;
    }

    /////////////////////
    // 画面消去しないように オーバーライド.

    public void update(Graphics g)
    {
        paint(g);
    }

    /////////////////////
    //  バッファのイメージをコピー

    public void paint(Graphics g)
    {
        if (buffer == null) {
            buffer = createImage(getSize().width, getSize().height);
        }

        g.drawImage(buffer, 0, 0, this);
    }
}


///////////////////////////////////////////////////////////////////
//
//  ブロック内の個体数の変化を計算するクラス.
//  
//  変化率の頻度分布データを記憶しておき,これにもとづいて次世代の個体数を
//  計算する.どの変化率を適用するかは乱数で決める.

class AbundanceTransition
{
    double changeRate[];
    double cum_probability[];
    boolean ready;

    /////////////////////
    //  Constructor.

    public AbundanceTransition()
    {
        ready = false;
    }

   /////////////////////
   //  変動率の設定
   
    public void setChangeRates(double[] rates)
    {
        changeRate = rates;
        cum_probability = new double [rates.length];
    }

    /////////////////////
    //  各変動率クラスの頻度を,より減少が激しいクラスからの積算相対頻度に
    //  計算しなおして記憶.

    public void setChangeRateFrequencies(int[] freq)
    {
        int sum = 0;

        for (int i = 0; i < freq.length; ++i) {
            sum += freq[i];
        }

        if (sum <= 0) {     //  すべての頻度がゼロだったら
            ready = false;  //  準備OKでない.
            return;
        }

        cum_probability[0] = (double) freq[0] / (double) sum;

        for (int i = 1; i < freq.length; ++i) {
            cum_probability[i] = cum_probability[i - 1] + (double) freq[i] / (double) sum;
        }

        ready = true;  //  準備OK.
    }

    /////////////////////
    //  現在の個体数を受けとり,変動後の個体数を返す.

    public int getNewAbundance(int current_)
    {
        if (current_ <= 0) {
            return 0;
        }

        //  どの変動率クラスを選ぶか.乱数で決める.

        double x = Math.random();
        int r_category = cum_probability.length - 1;

        for (int i = 0; i < cum_probability.length; ++i) {

            if (x < cum_probability[i]) {
                r_category = i;
                break;
            }
        }

        double new_n;

        if (r_category == 0) {       //  一番下(減少が激しい)クラスの場合.
            new_n = current_ * changeRate[0];
        }
        else  {

        //  それ以外の場合は,ふたつのクラスの減少率の間からランダムに選んだ値を適用.

	        double min_r = changeRate[r_category -1];
    	    double max_r = changeRate[r_category];
	        double r = Math.random() * (max_r - min_r) + min_r;
	        
	        new_n = current_ * r;
	    }

        if (new_n  < 1.) { //  個体数が1を切ったらブロック内絶滅.
            return 0;
        }
        else {
            return (int) (new_n + 0.5);  //  四捨五入.
        }
    }

    /////////////////////

    public boolean isReady()
    {
        return ready;
    }

    /////////////////////

    public void reset()
    {
        ready = false;
    }
}


///////////////////////////////////////////////////////////////////
//
//  メッシュの各ブロック内の個体数を管理するクラス

class Population
{
    int numBlocks;           //  総ブロック数
    int abundanceInBlock[];  //  各ブロック内の個体数を記憶.

    boolean ready;

    /////////////////////
    //  Constructor.

    public Population()
    {
        ready = false;
    }

    /////////////////////
    //  どの個体数クラスのブロックがいくつあるかのデータにもとづいて,
    //  各ブロックの初期の個体数を設定.

    public void initialize(int abund[], int blocks[])
    {
		//  総ブロック数を求める.

        numBlocks = 0; 

        for (int i = 0; i < blocks.length; ++i) {
            numBlocks += blocks[i];
        }

        //  各ブロック内の個体数を記録する配列を用意.

        abundanceInBlock = new int[numBlocks];

        int cum_i = 0;

        for (int i = 0; i < abund.length; ++i) {     //  各個体数クラスごとに…
            for (int j = 0; j < blocks[i]; ++j) {    //  それに属するブロック数だけ…
                abundanceInBlock[cum_i] = abund[i];  //  ブロックを用意して個体数を設定.
                ++cum_i;
            }
        }

        if (getNumberOfPlants() > 0) {  //  1個体以上いたら…
            ready = true;               //  準備OK.
        }
        else {              //  一個体もいなかったら
            ready = false;  //  準備OKでない.
        }
    }

    /////////////////////
    //  全ブロックについて,次代の個体数を計算する.

    public void calcNextAbundance(AbundanceTransition transition)
    {
        for (int i = 0; i < numBlocks; ++i) {
            if (abundanceInBlock[i] > 0) {
                abundanceInBlock[i] = transition.getNewAbundance(abundanceInBlock[i]);
            }
        }
    }

    /////////////////////
    // 総個体数

    public int getNumberOfPlants()
    {
        int plants = 0;

        for (int i = 0; i < numBlocks; ++i) {
            plants += abundanceInBlock[i];
        }

        return plants;
    }

    /////////////////////
    //  完全に絶滅したか?

    public boolean isExtinct()
    {
        for (int i = 0; i < numBlocks; ++i) {
            if (abundanceInBlock[i] > 0) {
                return false;
            }
        }
        return true;
    }

    /////////////////////

    public boolean isReady()
    {
        return ready;
    }

    /////////////////////

    public void reset()
    {
        ready = false;
    }
}