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;
}
}