田代航平君の渦糸のプログラム (2003年2月11日) は やはり http://nalab.mind.meiji.ac.jp/~mk/labo/java/ から入手できる。 実行は http://nalab.mind.meiji.ac.jp/~mk/labo/java/uzu.htmlで出来る。
// 渦糸系の力学系のシミュレーション
// (c)kou
// 2003.02.11完成
// <applet code="vortex.class" width=600 height=600></applet>
// modified by mk (2003/3/19) Runge-Kutta 法を使うように変更
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
final public class vortex extends Applet implements Runnable, ActionListener {
private Image off; // オフスクリーン
private Graphics grf; // オフスクリーンのグラフィックス
private Thread th = null; // スレッド
private Dimension d; // アプレットのサイズ
private Dimension imgD; // オフスクリーンのサイズ
private double dt = 0.2; // 時刻の増分 (刻み幅)
private int N = 5; // 渦糸の個数
private int MaxN = 12;
private double scale = 20;// 拡大率
private int sleepTime=10; // スレッドの待ち時間
private double[] Gamma; // 渦の強さ (ガンマ)
private double[] Z; // 渦糸の位置 (k番目の渦の位置は (Z[2*k],Z[2*k+1]))
private double [] Z2, f, k1, k2, k3, k4; // Runge-Kutta 法の計算用変数
private String[] btString = {"Start", "End"}; // ボタンの名前
private Button[] bt; // ボタン
private String[] laString1={"dt","N","scale","sleepTime"}; // ラベルの名前
private String[] laString2={"k","Γ","X","Y"}; // ラベルの名前
private Label[] la1, la2, la3; // ラベル
private TextField[] tf1; // ラベルに対するテキストフィールド
private TextField[] tfGamma; // Γの値のテキストフィールド
private TextField[] tfX; // 渦糸の初期位置のx座標のテキストフィールド
private TextField[] tfY; // 渦糸の初期位置のy座標のテキストフィールド
private Color[] color1; // 色合いの薄い色 (未使用!)
private Color[] color2; // 色合いが中くらいの色
public void init() {
int i; // カウンタ
setBackground(Color.white);//背景色の設定
d=getSize();//アプレットのサイズの取得
imgD=new Dimension(d.width-200,d.height);//オフスクリーンの色の設定
// 色の作成
color1 = new Color[MaxN];
color2 = new Color[MaxN];
for (i = 0; i < MaxN;i++) {
float a = i / (float)MaxN;
color1[i]=Color.getHSBColor(a, 0.2f, 1.0f);
color2[i]=Color.getHSBColor(a, 0.5f, 1.0f);
}
// オフスクリーンの準備
off = createImage(imgD.width,imgD.height); // オフスクリーンの作成
grf = off.getGraphics(); // グラフィックスの取得
// オフスクリーンのクリア
grf.setColor(Color.black); // 背景色の設定
grf.fillRect(0,0,imgD.width,imgD.height); // 塗りつぶす
// オフスクリーンに座標軸を描く
grf.setColor(Color.gray); // 軸の色の設定
grf.drawLine(0,imgD.height/2,imgD.width,imgD.height/2); // x軸
grf.drawLine(imgD.width/2,0,imgD.width/2,imgD.height); // y軸
// レイアウトマネージャを使わない (自分で設定する))
setLayout(null);
// Start, End ボタンの準備
bt= new Button[btString.length]; // ボタンの配列の生成
for (i = 0; i < bt.length; i++) {
bt[i] = new Button(btString[i]); // ボタンの生成
bt[i].addActionListener(this); // リスナーに関連付け
bt[i].setBounds(imgD.width,20*i,d.width-imgD.width,20);//ボタンの位置決め
add(bt[i]); //ボタンの登録
}
// dt, N, scale, sleepTime のラベル、テキスト・フィールドの準備
la1 = new Label[laString1.length]; // ラベルの宣言
tf1 = new TextField[laString1.length]; // テキストフィールドの宣言
for (i = 0; i < la1.length; i++) {
la1[i] = new Label(laString1[i]); // ラベルの生成
la1[i].setBounds(imgD.width,
20*(i+bt.length),
(d.width-imgD.width)/2,
20); // ラベルの位置決め
add(la1[i]); // ラベルの登録
tf1[i]=new TextField(); // テキストフィールドの生成
tf1[i].setBounds(imgD.width+(d.width-imgD.width)/2,
20*(i+bt.length),
(d.width-imgD.width)/2,
20); // テキストフィールドの位置決め
add(tf1[i]); // テキストフィールドの登録
}
// k, Γ, X, Y というラベルの準備
la2 = new Label[laString2.length]; // ラベルの配列の生成
for (i = 0; i < laString2.length; i++) {
la2[i] = new Label(laString2[i]); // ラベルの生成
la2[i].setBounds(imgD.width+(d.width-imgD.width)*i/4,
nnn 20*(bt.length+la1.length),
(d.width-imgD.width)/4,
20); // ラベルの位置決め
add(la2[i]); // ラベルの登録
}
// 各渦糸のデータ (強さ、初期位置) のラベル、テキストフィールド
la3 = new Label[MaxN]; // ラベルの配列の生成
tfGamma = new TextField[MaxN]; // テキストフィールドの生成
tfX = new TextField[MaxN]; // テキストフィールドの生成
tfY = new TextField[MaxN]; // テキストフィールドの生成
for (i = 0; i < la3.length; i++) {
la3[i] = new Label("" + (i + 1)); // ラベルの生成
la3[i].setBounds(imgD.width+(d.width-imgD.width)*0/4,
20*(bt.length+la1.length+1+i),
(d.width-imgD.width)/4,
20); // ラベルの位置決め
la3[i].setBackground(color2[i]); // 背景の設定
add(la3[i]); // ラベルの登録
tfGamma[i] = new TextField(); // テキストフィールドの生成
tfGamma[i].setBounds(imgD.width+(d.width-imgD.width)*1/4,
20*(bt.length+la1.length+1+i),
(d.width-imgD.width)/4,
20); // ラベルの位置決め
add(tfGamma[i]); // テキストフィールドの登録
tfX[i]=new TextField(); // テキストフィールドの生成
tfX[i].setBounds(imgD.width+(d.width-imgD.width)*2/4,
20*(bt.length+la1.length+1+i),
(d.width-imgD.width)/4,
20); // ラベルの位置決め
add(tfX[i]); // テキストフィールドの登録
tfY[i]=new TextField(); // テキストフィールドの生成
tfY[i].setBounds(imgD.width+(d.width-imgD.width)*3/4,
20*(bt.length+la1.length+1+i),
(d.width-imgD.width)/4,
20); // ラベルの位置決め
add(tfY[i]); // テキストフィールドの登録
}
// 計算用の配列を確保する
Gamma = new double[MaxN];
Z = new double[2*MaxN];
Z2 = new double[2*MaxN];
f = new double[2*MaxN];
k1 = new double[2*MaxN];
k2 = new double[2*MaxN];
k3 = new double[2*MaxN];
k4 = new double[2*MaxN];
// 渦の強さ, 初期位置の決定
for (i = 0; i < MaxN; i++) {
Gamma[i]=1;
Z[2*i] = Math.pow(-1.0,i)*i;
Z[2*i+1] = 0;
}
// 渦の強さ, 初期位置をテキストフィールドに表示
tf1[0].setText("" + dt);
tf1[1].setText("" + N);
tf1[2].setText("" + scale);
tf1[3].setText("" + sleepTime);
for (i = 0; i < tfGamma.length; i++) {
tfGamma[i].setText("" + Gamma[i]);
tfX[i].setText("" + Z[2*i]);
tfY[i].setText("" + Z[2*i+1]);
}
// スタートボタンを無効化
bt[0].setEnabled(false);
}
public void paint(Graphics g) {
update(g);
}
public void update(Graphics g) {
g.drawImage(off, 0, 0, this); // オフスクリーンの表示
}
// スレッドを生成してスタート
public void start() {
if (th == null) {
th = new Thread(this);
th.start();
}
}
// スレッドを止める
public void stop() {
if (th != null) {
th=null;
}
}
// 座標変換 (x座標)
private int scx(double x) {
return (int)(scale * x) + imgD.width / 2;
}
// 座標変換 (y座標)
private int scy(double y) {
return - (int)(scale * y) + imgD.height / 2;
}
// ユーザーの座標系で線分を描く
private void MydrawLine(double x1, double y1, double x2, double y2) {
grf.drawLine(scx(x1), scy(y1), scx(x2), scy(y2));
}
// 渦糸系のベクトル場 f(z) の計算
public void aux(double []z, double []f, int N) {
double doublePI = 2.0 * Math.PI;
double sumx, sumy;
for (int i = 0; i< N; i++) {
sumx= 0; sumy= 0;
for (int j = 0; j < N; j++)
if (j != i) {
double seki = Gamma[j] / (sqr(z[2*i]-z[2*j])+sqr(z[2*i+1]-z[2*j+1]));
sumx -= seki * (z[2*i+1]-z[2*j+1]);
sumy += seki * (z[2*i]-z[2*j]);
}
f[2*i+1] = sumy / doublePI;
f[2*i] = sumx / doublePI;
}
}
// 計算スレッド
public void run() {
int i;
int n = 2 * N; // 力学系の次元
// オリジナルではここで配列の宣言をしていたが、それでは遅すぎる
// double [] Z2 = new double[n];
// double [] f = new double[n];
// double [] k1 = new double[n];
// double [] k2 = new double[n];
// double [] k3 = new double[n];
// double [] k4 = new double[n];
while (th != null) {
// Runge-Kutta: next Z = Z + (k1 + 2 k2 + 2 k3 + k4) / 6
// k1
aux(Z, f, N);
for (i = 0; i < n; i++)
k1[i] = dt * f[i];
// k2
for (i = 0; i < n; i++)
Z2[i] = Z[i] + 0.5 * k1[i];
aux(Z2, f, N);
for (i = 0; i < n; i++)
k2[i] = dt * f[i];
// k3
for (i = 0; i < n; i++)
Z2[i] = Z[i] + 0.5 * k2[i];
aux(Z2, f, N);
for (i = 0; i < n; i++)
k3[i] = dt * f[i];
// k4
for (i = 0; i < n; i++)
Z2[i] = Z[i] + k3[i];
aux(Z2, f, N);
for (i = 0; i < n; i++)
k4[i] = dt * f[i];
// 次のステップの値 (Runge-Kutta)
for (i = 0; i < n; i++)
Z2[i] = Z[i] + (k1[i] + 2 * (k2[i] + k3[i]) + k4[i]) / 6;
// 軌跡を描く
for (int k = 0; k < N; k++) {
grf.setColor(color2[k]);
MydrawLine(Z[2*k], Z[2*k+1], Z2[2*k], Z2[2*k+1]);
}
// 値の更新
for (i = 0; i < n; i++)
Z[i] = Z2[i];
// オフスクリーンを描画する
repaint();
// スレッドの処理 (田代君の真似)
if (sleepTime > 0) {
try {
th.sleep(sleepTime); //スレッドを一時停止
}
catch(InterruptedException e) {};
}
th.yield(); //スレッドを一時譲る
}
}
// イベントの処理
public void actionPerformed(ActionEvent e) {
if (e.getSource() == bt[0]) {
// Startボタンが押されたら
//スタートボタンを無効化, エンドボタンを有効化してフォーカスを移す
bt[0].setEnabled(false);
bt[1].setEnabled(true);
bt[1].requestFocus();
// テキストフィールドの値を変数に代入
dt = Double.valueOf(tf1[0].getText()).doubleValue();
N = Integer.parseInt(tf1[1].getText());
scale = Double.valueOf(tf1[2].getText()).doubleValue();
sleepTime = Integer.parseInt(tf1[3].getText());
for (int i = 0; i < MaxN; i++) {
Gamma[i] = Double.valueOf(tfGamma[i].getText()).doubleValue();
Z[2*i] = Double.valueOf(tfX[i].getText()).doubleValue();
Z[2*i+1] = Double.valueOf(tfY[i].getText()).doubleValue();
}
// N の範囲をチェックする
if (N > MaxN) {
// N が最大値を超えていたら MaxN にする
N = MaxN;
tf1[1].setText("" + N); //数値をテキストフィールドに表示
}
if (N < 1) {
// N が最大値を超えていたら MaxN にする
N = 1;
tf1[1].setText("" + N); //数値をテキストフィールドに表示
}
// オフスクリーンのクリア
grf.setColor(Color.black); // 背景色の設定
grf.fillRect(0,0,imgD.width,imgD.height); // 塗りつぶす (クリア)
grf.setColor(Color.gray); // 軸の色の設定
grf.drawLine(0,imgD.height/2,imgD.width,imgD.height/2); // x軸
grf.drawLine(imgD.width/2,0,imgD.width/2,imgD.height); // y軸
// スレッドの開始
start();
}
else if (e.getSource() == bt[1]) {
// Endボタンが押されたら
bt[0].setEnabled(true); // スタートボタンを有効化
bt[0].requestFocus(); // スタートボタンにフォーカスを移す
bt[1].setEnabled(false); // エンドボタンを無効化
// スレッドをとめる
stop();
}
}
// 二乗を返す
private double sqr(double a) {
return a * a;
}
}