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); } } }
初期値の選択、時間の逆転、マウスで初期値、など拡張したい。