振り子の運動

/******************************/
/* 振り子の運動               */
/*      coded by Y.Suganuma   */
/******************************/
import java.io.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class Test {
	public static void main(String args[]) throws IOException
	{
		Data dt = new Data();
	}
}

/********************/
/* クラスDataの定義 */
/********************/
class Data extends JFrame implements ItemListener {

	double k;   // 摩擦係数
	double k1;   // フィードバックゲイン
	double l;   // 棒の長さ(m)
	double m;   // 棒の質量(kg)
	double x0;   // 初期角度(deg)
	double dx0;   // 初期角速度(deg/sec)
	int sw = -1;   // =-1 : 振り子
                   // =1 : 倒立振り子
	int err = 0;   // エラー番号

	JTextField length, weight, friction, feed, ang, ang_v;
	JComboBox  choice;
	Err_p ep;

	/******************/
	/* コンストラクタ */
	/******************/
	Data()
	{
						// Frameクラスのコンストラクタの呼び出し
		super("データの入力");
						// レイアウトの変更
		Container cp = getContentPane();
		cp.setBackground(Color.white);
		cp.setLayout(null);
		JPanel jp = new JPanel();
		jp.setLayout(new BorderLayout(5, 10));
		jp.setBackground(Color.white);
		cp.add(jp);
		JPanel np = new JPanel();
		np.setLayout(new GridLayout(8, 3, 5, 10));
		np.setBackground(Color.white);
		jp.add(np, BorderLayout.CENTER);
		ep = new Err_p(this);
		ep.setBackground(Color.white);
		cp.add(ep);
		Font f = new Font("TimesRoman", Font.BOLD, 20);
						// スクロールリスト(振り子の種類)
		JLabel fill1, fill2;

		fill1 = new JLabel();
		np.add(fill1);
		choice = new JComboBox  ();
		choice.setBackground(Color.white);
		choice.setFont(f);
		choice.addItem("振り子");
		choice.addItem("倒立振り子");
		np.add(choice);
		choice.addItemListener(this);
		fill2 = new JLabel();
		np.add(fill2);
						// テキストフィールドの設定
		JLabel length_t, length_u, weight_t, weight_u, friction_t, friction_u,
               feed_t, feed_u, ang_t, ang_u, ang_v_t, ang_v_u, fill3, fill4;
							// 長さの入力
		length_t = new JLabel("長さ");
		length_t.setFont(f);
		np.add(length_t);
		length = new JTextField(10);
		length.setFont(f);
		length.setHorizontalAlignment(JTextField.RIGHT);
		np.add(length);
		length_u = new JLabel("meter(>0)");
		length_u.setFont(f);
		np.add(length_u);
							// 質量の入力
		weight_t = new JLabel("質量");
		weight_t.setFont(f);
		np.add(weight_t);
		weight = new JTextField(10);
		weight.setFont(f);
		weight.setHorizontalAlignment(JTextField.RIGHT);
		np.add(weight);
		weight_u = new JLabel("kg(>0)");
		weight_u.setFont(f);
		np.add(weight_u);
							// 摩擦係数の入力
		friction_t = new JLabel("摩擦係数");
		friction_t.setFont(f);
		np.add(friction_t);
		friction = new JTextField("0.0", 10);
		friction.setFont(f);
		friction.setHorizontalAlignment(JTextField.RIGHT);
		np.add(friction);
		friction_u = new JLabel("(>=0)");
		friction_u.setFont(f);
		np.add(friction_u);
							// フィードバックゲインの入力
		feed_t = new JLabel("フィードバック");
		feed_t.setFont(f);
		np.add(feed_t);
		feed = new JTextField("0.0", 10);
		feed.setFont(f);
		feed.setHorizontalAlignment(JTextField.RIGHT);
		np.add(feed);
		feed_u = new JLabel("(>=0)");
		feed_u.setFont(f);
		np.add(feed_u);
							// 初期角度の入力
		ang_t = new JLabel("初期角度");
		ang_t.setFont(f);
		np.add(ang_t);
		ang = new JTextField(10);
		ang.setFont(f);
		ang.setHorizontalAlignment(JTextField.RIGHT);
		np.add(ang);
		ang_u = new JLabel("degree");
		ang_u.setFont(f);
		np.add(ang_u);
							// 初期角速度の入力
		ang_v_t = new JLabel("初期角速度");
		ang_v_t.setFont(f);
		np.add(ang_v_t);
		ang_v = new JTextField("0.0", 10);
		ang_v.setFont(f);
		ang_v.setHorizontalAlignment(JTextField.RIGHT);
		np.add(ang_v);
		ang_v_u = new JLabel("degree/sec");
		ang_v_u.setFont(f);
		np.add(ang_v_u);
						// OK ボタンの設定
		fill3 = new JLabel();
		np.add(fill3);
		JButton bt = new JButton("OK");
		bt.setFont(f);
		bt.addMouseListener(new ClickMouse());
		np.add(bt);
		fill4 = new JLabel();
		np.add(fill4);
						// Windowサイズを設定
		setSize(500, 400);
						// ウィンドウを表示
		setVisible(true);
		Insets in = getInsets();
		jp.setLocation(10, 10);
		jp.setSize(500-in.left-in.right-20, 350-in.top-in.bottom-20);
		ep.setLocation(10, 350-in.top-in.bottom);
		ep.setSize(500-in.left-in.right-20, 50);
						// イベントアダプタ
		addWindowListener(new WinEnd());
	}

	/******************************/
	/* チョイスコントロールの処理 */
	/******************************/
	public void itemStateChanged(ItemEvent e)
	{
		if (e.getItemSelectable() == choice) {
			sw = choice.getSelectedIndex();
			sw = (sw <= 0) ? -1 : 1;
		}
	}

	/********************************/
	/* OKボタンが押されたときの処理 */
	/********************************/
	class ClickMouse extends MouseAdapter
	{
		/************************************/
		/* マウスがクリックされたときの処理 */
		/************************************/
		public void mouseClicked(MouseEvent e)
		{
			err = 0;

			if ((length.getText()).length() <= 0)
				err = 1;
			else {
				l = Double.parseDouble(length.getText());
				if (l <= 0.0)
					err = 1;
			}
			if (err > 0)
				ep.repaint();

			else {
				if ((weight.getText()).length() <= 0)
					err = 2;
				else {
					m = Double.parseDouble(weight.getText());
					if (m <= 0.0)
						err = 2;
				}
				if (err > 0)
					repaint();

				else {
					k   = Double.parseDouble(friction.getText());
					k1  = Double.parseDouble(feed.getText());
					if (k < 0.0 || k1 < 0.0) {
						err = 3;
						repaint();
					}

					else {
						if ((ang.getText()).length() <= 0)
							x0 = 0.0;
						else
							x0  = Double.parseDouble(ang.getText());
						dx0 = Double.parseDouble(ang_v.getText());

						Link link = new Link(sw, k, k1, l, m, x0, dx0);
					}
				}
			}
		}
	}

	/************/
	/* 終了処理 */
	/************/
	class WinEnd extends WindowAdapter
	{
		public void windowClosing(WindowEvent e) {
			System.exit(0);
		}
	}
}

class Err_p extends JPanel {

	Data dt;

	Err_p(Data dt_i)
	{
		dt = dt_i;
	}

	/**************************/
	/* エラーメッセージの表示 */
	/**************************/
	public void paintComponent (Graphics g)
	{
		super.paintComponent(g);   // 親クラスの描画(必ず必要)
						// フォントの設定
		Font f = new Font("TimesRoman", Font.BOLD, 20);
		g.setFont(f);
						// エラーメッセージの出力
		switch (dt.err) {
			case 0:
				g.drawString("                         ", 10, 30);
				break;
			case 1:
				g.setColor(Color.red);
				g.drawString("***Error   length <= 0***", 10, 30);
				g.setColor(Color.black);
				break;
			case 2:
				g.setColor(Color.red);
				g.drawString("***Error   weight <= 0***", 10, 30);
				g.setColor(Color.black);
				break;
			case 3:
				g.setColor(Color.red);
				g.drawString("***Error   k < 0 or k1 < 0***", 10, 30);
				g.setColor(Color.black);
				break;
			default:
				g.drawString("                         ", 10, 30);
				break;
		}
	}
}

/**********************/
/* クラス Link の定義 */
/**********************/
class Link {
	
	private double a[][] = new double [2][2];   // 微分方程式の係数
	private double b[] = new double [2];   // 微分方程式の右辺の係数

	double h;   // 微分方程式を解く際の刻み幅
	double x[] = new double [2];   // 角度と角速度
	double l;   // 棒の長さ(m)
	int per;   // 表示間隔
	int slp;   // sleep時間(ms)
	int sw;   // =-1 : 振り子
              // =1 : 倒立振り子

	/**********************************/
	/* コンストラクタ                 */
	/*      sw_i : =-1 : 振り子       */
	/*             =1 : 倒立振り子    */
	/*      k : 摩擦係数              */
	/*      k1 : フィードバックゲイン */
	/*      l_i : 棒の長さ(m)         */
	/*      m : 棒の質量(kg)          */
	/*      x0 : 初期角度(deg)        */
	/*      dx0 : 初期角速度(deg/sec) */
	/**********************************/
	Link(int sw_i, double k, double k1, double l_i, double m, double x0, double dx0)
	{
		double a1, b1, c1, D, i, i_mrr, r, mrg, ut, p1, p2;
		String name;
	/*
	          微分方程式の決定
	*/
		l       = l_i;
		sw      = sw_i;
		r       = 0.5 * l;
		i       = l * l / 12.0;
		mrg     = m * r * 9.8;
		i_mrr   = i + m * r * r;
		ut      = Math.PI / 180.0;
		slp     = 200;

		a[0][0] = 0.0;
		a[0][1] = 1.0;
		a[1][0] = sw * mrg / i_mrr;
		a[1][1] = -k / i_mrr;

		b[0]    = -k1 / i_mrr;
		b[1]    = 0.0;
	/*
	          初期設定
	*/
		x[0] = x0 * ut;
		x[1] = dx0 * ut;
	/*
	          刻み幅の決定
	*/
		a1 = 1.0;
		b1 = -a[0][0] - a[1][1];
		c1 = -a[0][1] * (a[1][0] + b[0]);
		D  = b1 * b1 - 4.0 * a1 * c1;
							// 複素根
		if (D < 0.0) {
			p1 = -b1 / (2.0 * a1);
			p2 = Math.sqrt(-D) / (2.0 * a1);
			h  = 2.0 * Math.PI / p2 / 100.0;
		}
							// 実根
		else {
			D  = Math.sqrt(D);
			p1 = Math.abs((-b1 + D) / (2.0 * a1));
			p2 = Math.abs((-b1 - D) / (2.0 * a1));
			h  = (p1 > p2) ? 1.0 / p1 / 50.0 : 1.0 / p2 / 50.0;
		}
	/*
	          表示間隔の決定
	*/
		per = (int)(slp / 1000.0 / h);
	/*
	          Windowの生成
	*/
		if (sw < 0)
			name = "振り子の運動";
		else
			name = "倒立振り子の運動";
		Display dis = new Display(name, this);
	}
	
	/**************************/
	/* 微分方程式             */
	/*      time : 時間       */
	/*      x    : 位置と速度 */
	/*      dx   : xの微分    */
	/**************************/
	void link(double time, double [] x, double [] dx)
	{
		dx[0] = a[0][0] * x[0] + a[0][1] * x[1];
		dx[1] = a[1][0] * Math.sin(x[0]) + b[0] * x[0] + a[1][1] * x[1];
	}

	/*****************************************/
	/* ルンゲ・クッタ・ギル法                */
	/*      time : 現在の時間                */
	/*      h : 時間刻み幅                   */
	/*      x : 現在の状態                   */
	/*      dx : 微係数(snxで計算する)     */
	/*      w4 : 作業域                      */
	/*      n : 微分方程式の次数(int)        */
	/*      return : time+h                  */
	/*****************************************/
	double rkg(double time, double h, double [] x, double [] dx, double [] w4, int n)
	{
		int i1, i2;
		double t0, x1, x2, w3[] = new double [4];
		double w1[] = {0.5, 0.29289322, 1.70710678, 0.16666667};
		double w2[] = {2.0, 1.0, 1.0, 2.0};

		w3[0] = .0;
		w3[1] = 0.5 * h;
		w3[2] = w3[1];
		w3[3] = h;
		t0    = time;

		for (i1 = 0; i1 < n; i1++)
			w4[i1] = 0.0;

		for (i1 = 0; i1 < 4; i1++) {
			time = t0 + w3[i1];
			link(time, x, dx);
			for (i2 = 0; i2 < n; i2++) {
				x1    = h * dx[i2];
				x2    = w1[i1] * (x1 - w2[i1] * w4[i2]);
				x[i2] += x2;
				if (i1 != 3) 
					w4[i2] += 3 * x2 - w1[i1] * x1;
			}
		}

		return(time);
	}
}

/***********************/
/* クラスDisplayの定義 */
/***********************/
class Display extends JFrame implements Runnable {

	private double time;   // 時刻
	double ritu;   // 表示倍率
	int size;   // Windowの大きさ
	int h;   // 左右上下の余裕
	int w;   // Windowのタイトル幅
	private boolean win_state = true;   // Windowの状態
	private Link link;   // Linkクラスのオブジェクト
	private Thread th;   // Threadクラスのオブジェクト

	Display ds = this;
	Draw_p dp;

	/******************************************/
	/* コンストラクタ                         */
	/*      name : タイトル                   */
	/*      link_i : Linkクラスのオブジェクト */
	/******************************************/
	Display(String name, Link link_i)
	{
					// Frameクラスのコンストラクタの呼び出し
		super(name);
					// データの設定
		link = link_i;
		size = 500;
		h    = 50;
		w    = 10;
		ritu = (0.5 * size - 10) / link.l;
		time = 0.0;
		Container cp = getContentPane();
		cp.setBackground(Color.white);
		cp.setLayout(null);
		dp = new Draw_p(link, this);
		dp.setBackground(Color.white);
		cp.add(dp);
					// Windowの大きさの設定
		setSize(size+2*h+2*w, size+2*h+2*w);
					// ウィンドウを表示
		setVisible(true);
		dp.setLocation(h, h);
		dp.setSize(500, 500);
					// イベントアダプタ
		addWindowListener(new WinEnd());
					// スレッドの定義と開始
		th = new Thread(this);
		th.start();
	}

	/******************/
	/* スレッドの実行 */
	/******************/
	public void run()
	{
		double dx[] = new double [2];
		double wk[] = new double [2];
		int i1;

		repaint();

		while (win_state) {
			for (i1 = 0; i1 < link.per; i1++)
				time = link.rkg(time, link.h, link.x, dx, wk, 2);
			try {
				th.sleep(link.slp);
			}
			catch (InterruptedException e) {}
			dp.repaint();
		}
	}

	/************/
	/* 終了処理 */
	/************/
	class WinEnd extends WindowAdapter
	{
		public void windowClosing(WindowEvent e) {
			win_state = false;
			ds.setVisible(false);
		}
	}
}

class Draw_p extends JPanel {

	private Link link;
	private Display ds;

	Draw_p(Link link_i, Display ds_i)
	{
		link = link_i;
		ds   = ds_i;
	}

	/********/
	/* 描画 */
	/********/
	public void paintComponent (Graphics g)
	{
		super.paintComponent(g);   // 親クラスの描画(必ず必要)
		int b_x = ds.size / 2, b_y = ds.size / 2, x, y;
					// 中心
		g.setColor(Color.black);
		g.fillOval(b_x-5, b_y-5, 10, 10);
					// 棒
		g.setColor(Color.blue);
		if (link.sw < 0) {
			x = b_x + (int)(link.l * Math.sin(link.x[0]) * ds.ritu + 0.5);
			y = b_y + (int)(link.l * Math.cos(link.x[0]) * ds.ritu + 0.5);
		}
		else {
			x = b_x + (int)(link.l * Math.sin(link.x[0]) * ds.ritu + 0.5);
			y = b_y - (int)(link.l * Math.cos(link.x[0]) * ds.ritu + 0.5);
		}

		g.drawLine(b_x, b_y, x, y);
	}
}