Javaの良いところは、GUIのプログラムがほどほどの手間で書けて、 それはシミュレーションの便利さに通じる (例えばパラメーターを調節するとか) ことでしょうか。
(2021/4/13追記) 以下のサンプル・プログラムは、Java アプレットであるが、 最近の Java ではアプレットは廃止されてしまった。 こういうのが WWW で試せるのは面白いと思ったのだけれど… (直す気はなくなった。)
/*
* ConstLinear2D.java --- 2次元定数係数線型常微分方程式
*/
// <APPLET code="ConstLinear2D.class" width=500 height=500> </APPLET>
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
class GraphCanvas extends Canvas {
static final boolean DEBUG = false; // true;
private static final String message = "graph of a function with 1 variable";
// 問題に取って基本的なパラメーター
// 係数行列
private double a, b, c, d;
// 描画範囲
private double x_max = 1.0, x_min = - 1.0, y_max = 1.0, y_min = - 1.0;
private double x_margin = (x_max - x_min) / 10;
private double y_margin = (y_max - y_min) / 10;
// 座標系の変換のためのパラメーター
private int CanvasX = 400, CanvasY = 400;
private double ratiox, ratioy, X0, Y0;
// コンストラクター
public GraphCanvas() {
super();
}
public GraphCanvas(int cx, int cy) {
super();
CanvasX = cx; CanvasY = cy;
}
public void compute(double A, double B, double C, double D) {
a = A; b = B; c = C; d = D;
repaint();
}
private boolean IsIn(double x, double y) {
return (x_min <= x && x <= x_max && y_min <= y && y <= y_max);
}
// 座標変換の準備
private void space(double x0, double y0, double x1, double y1) {
X0 = x0; Y0 = y0;
ratiox = CanvasX / (x1 - x0);
ratioy = CanvasY / (y1 - y0);
}
// ユーザー座標 (ワールド座標系) をウィンドウ座標 (デバイス座標系)
private int wx(double x) {
return (int)(ratiox * (x - X0));
}
// ユーザー座標 (ワールド座標系) をウィンドウ座標 (デバイス座標系)
private int wy(double y) {
return CanvasY - (int)(ratioy * (y - Y0));
}
// 力学系の右辺 f=(fx, fy)
private double fx(double x, double y) {
return a * x + b * y;
}
private double fy(double x, double y) {
return c * x + d * y;
}
// (x0,y0) を初期値とする解の軌道 (trajectory) を描く
private void drawTrajectory(Graphics g, double x0, double y0, double T) {
double x, y, new_x, new_y;
double k1x, k1y, k2x, k2y, k3x, k3y, k4x, k4y;
double h = 0.01 / Math.sqrt(a * a + b * b + c * c + d * d);
if (T < 0.0)
h = - h;
int iter = (int)Math.rint(Math.abs(T / h));
x = x0;
y = y0;
for (int i = 1; i <= iter; i++) {
k1x = h * fx(x, y);
k1y = h * fy(x, y);
k2x = h * fx(x + k1x / 2, y + k1y / 2);
k2y = h * fy(x + k1x / 2, y + k1y / 2);
k3x = h * fx(x + k2x / 2, y + k2y / 2);
k3y = h * fy(x + k2x / 2, y + k2y / 2);
k4x = h * fx(x + k3x, y + k3y);
k4y = h * fy(x + k3x, y + k3y);
new_x = x + (k1x + 2 * k2x + 2 * k3x + k4x) / 6;
new_y = y + (k1y + 2 * k2y + 2 * k3y + k4y) / 6;
if (IsIn(x, y) && IsIn(new_x, new_y))
g.drawLine(wx(x), wy(y), wx(new_x), wy(new_y));
x = new_x; y = new_y;
}
}
public void paint(Graphics g) {
//
space(x_min - x_margin, y_min - y_margin,
x_max + x_margin, y_max + y_margin);
//
setBackground(Color.blue);
//
g.setColor(Color.black);
g.drawLine(wx(x_min), wy(0.0), wx(x_max), wy(0.0));
g.drawLine(wx(0.0), wy(y_min), wx(0.0), wy(y_max));
//
g.setColor(Color.yellow);
int n = 36;
double dt = 2 * Math.PI / n;
double Time = 10.0 / Math.sqrt(a * a + b * b + c * c + d * d);
for (int i = 0; i < n; i++) {
double t = i * dt;
drawTrajectory(g, Math.cos(t), Math.sin(t), Time);
drawTrajectory(g, Math.cos(t), Math.sin(t), - Time);
}
}
}
public class ConstLinear2D extends Applet implements ActionListener {
private int N = 20;
private double lambda = 0.5;
private double Tmax = 0.5;
// ユーザーとのインターフェイス (パラメーターの入力)
private Label label_a, label_b, label_c, label_d;
private TextField input_a, input_b, input_c, input_d;
private double a = 1.0, b = 0.0, c = 0.0, d = 1.0;
private Button startB;
//
private GraphCanvas gc;
private void ReadFields() {
a = Double.valueOf(input_a.getText()).doubleValue();
b = Double.valueOf(input_b.getText()).doubleValue();
c = Double.valueOf(input_c.getText()).doubleValue();
d = Double.valueOf(input_d.getText()).doubleValue();
}
// 準備 (変数の用意と座標系の初期化など)
public void init() {
// ナル・レイアウト
setLayout(null);
// a, b, c, d を入力するためのテキスト・フィールド
add(label_a = new Label("a=")); label_a.setBounds(100, 30, 40, 30);
add(label_b = new Label("b=")); label_b.setBounds(250, 30, 40, 30);
add(label_c = new Label("c=")); label_c.setBounds(100, 70, 40, 30);
add(label_d = new Label("d=")); label_d.setBounds(250, 70, 40, 30);
add(input_a = new TextField("" + a)); input_a.setBounds(150, 30, 100, 30);
add(input_b = new TextField("" + b)); input_b.setBounds(300, 30, 100, 30);
add(input_c = new TextField("" + c)); input_c.setBounds(150, 70, 100, 30);
add(input_d = new TextField("" + d)); input_d.setBounds(300, 70, 100, 30);
// 再計算ボタン
startB = new Button("Restart");
add(startB);
startB.setBounds(420, 45, 50, 30);
startB.addActionListener(this);
// キャンバス
gc = new GraphCanvas();
add(gc);
gc.setBounds(50, 100, 400, 400);
ReadFields();
gc.compute(a, b, c, d);
}
// ボタンを押されたら、テキスト・フィールドの内容を読み取って、再描画
public void actionPerformed(ActionEvent e) {
if (e.getSource() == startB) {
ReadFields();
gc.compute(a, b, c, d);
}
}
}
初期値の選択、時間の逆転、マウスで初期値、など拡張したい。