Javaで学ぶアルゴリズム　第8弾：数値積分

# #Javaで学ぶアルゴリズム < 数値積分 >

##はじめに
Javaは授業で触れただけで全くの初心者であり，何もわかっていない．なので，基本的なアルゴリズムをJavaで実装し，アルゴリズムの理解を深めるとともに，Javaにも慣れていきたい．
その第8弾として数値積分を扱う．

##数値積分
数学的計算であれば，数値積分を使うことも少ないのかもしれない．しかしながら，システム的制御など，動的な系の中で，積分を行うとなれば，数値積分が非常に重要となってくる．その数値積分にもいくつかの方法が存在する．ここでは，台形則・シンプソン則の2つほど例に詳しく見ていき，その他は名前とちょっとした特徴に触れる程度とする．

###原理
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/39b777fe-4ca8-0f38-f022-91a77b72c70a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/6c68cb8e-32de-0007-f17f-902caa67a06a.png)

###比較
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/28efddd1-394a-eb1b-c922-b4011e163233.png)

違いとしては，台形則が直線での近似であるのに対して，シンプソン則は2次曲線での近似である．これにより，計算上は，シンプソン則の方が，誤差を小さくできるという．詳しくは後に記述する．

##実装
先ほどの説明に従い，Javaでの実装を行う．ここでは，被積分関数を$\sqrt{4-x^2}$として，積分により$\pi$を求めるものとする．以下にソースコードとそのときの出力を示す．

#####ソースコード
```java:integral1.java
//2021/02/27
//@Yuya Shimizu
//---------------------------
//*		数値積分（台形則）	*
//---------------------------

import java.awt.*;
import java.awt.event.*;


//フレームの設定
class integral1_Frame extends Frame{
	private TextArea ta;
	private TextField tf1, tf2;
	double f(double x){		//被積分関数
		return Math.sqrt(4 - x*x);
	}
	public integral1_Frame(){
		setSize(300, 200);//300×200の大きさでフレームを作成
		//×ボタンを押したら終了
		addWindowListener(new WindowAdapter(){
			public void windowClosing(WindowEvent e){
				System.exit(0);
			}
		});
		Panel p;	//パネル変数
		Button bt;	//ボタン変数
		
		add(p = new Panel(), "North");	//フレーム上側にパネルを貼り付け
		p.add(new Label("積分区間"));	//先ほどのパネルにラベルを貼り付け
		p.add(tf1 = new TextField("", 6));	//先ほどのパネルにテキストフィールドを貼り付け
		p.add(tf2 = new TextField("", 6));	//先ほどのパネルにテキストフィールドを貼り付け
		
		add(ta = new TextArea(5, 40), "Center");	//フレーム中央にテキストエリアを貼り付け
		add(bt = new Button("計算"), "South");	//フレーム中央にテキストエリアを貼り付け
		
		//ボタンの設定
		bt.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				int k;
				double a, b, n, h, x, s, sum;
				a = Double.parseDouble(tf1.getText());
				b = Double.parseDouble(tf2.getText());
				n = 50;		//a~bの分割数
				h = (b-a)/n;	//区間幅
				x = a;
				s = 0;
				
				for (k=1; k<=n-1; k++){
					x = x + h;
					s = s + f(x);
				}
				sum = h * ((f(a) + f(b))/2 + s);
				String result = "     /" + b + "\n" + 
								"     | sqrt(4-x*x) =" + sum + "\n"+
								"     /" + a;
				ta.setText(result);
			}
		});
	}
}

//クラス
public class integral1{
	//メイン関数
	public static void main(String[] args){
		Frame w=new integral1_Frame();
		w.show();
	}
}
```

#####出力
**実行前**
![before.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/4ab5dc7d-f09e-eda6-3ebb-16dabfeabd7e.png)

**実行後**
![after.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/2c5f6860-03b3-1966-b53f-2ead93385c10.png)


#####ソースコード（シンプソン則）
```java:integral2.java
//2021/02/27
//@Yuya Shimizu
//-----------------------------------
//*		数値積分（シンプソン則）	*
//-----------------------------------

import java.awt.*;
import java.awt.event.*;


//フレームの設定
class integral2_Frame extends Frame{
	private TextArea ta;
	private TextField tf1, tf2;
	double f(double x){		//被積分関数
		return Math.sqrt(4 - x*x);
	}
	public integral2_Frame(){
		setSize(300, 200);//300×200の大きさでフレームを作成
		//×ボタンを押したら終了
		addWindowListener(new WindowAdapter(){
			public void windowClosing(WindowEvent e){
				System.exit(0);
			}
		});
		Panel p;	//パネル変数
		Button bt;	//ボタン変数
		
		add(p = new Panel(), "North");	//フレーム上側にパネルを貼り付け
		p.add(new Label("積分区間"));	//先ほどのパネルにラベルを貼り付け
		p.add(tf1 = new TextField("", 6));	//先ほどのパネルにテキストフィールドを貼り付け
		p.add(tf2 = new TextField("", 6));	//先ほどのパネルにテキストフィールドを貼り付け
		
		add(ta = new TextArea(5, 40), "Center");	//フレーム中央にテキストエリアを貼り付け
		add(bt = new Button("計算"), "South");	//フレーム中央にテキストエリアを貼り付け
		
		//ボタンの設定
		bt.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				int k;
				double a, b, n, h, fo, fe, sum;
				a = Double.parseDouble(tf1.getText());
				b = Double.parseDouble(tf2.getText());
				n = 50;		//a~bの分割数
				h = (b-a)/(2*n);	//区間幅
				fo = 0;
				fe = 0;
				
				for (k=1; k<=2*n-3; k=k+2){
					fo = fo + f(a+h*k);
					fe = fe + f(a+h*(k+1));
				}
				sum = (f(a)+f(b)+4*(fo+f(b-h))+2*fe)*h/3;
				String result = "     /" + b + "\n" + 
								"     | sqrt(4-x*x) =" + sum + "\n"+
								"     /" + a;
				ta.setText(result);
			}
		});
	}
}

//クラス
public class integral2{
	//メイン関数
	public static void main(String[] args){
		Frame w=new integral2_Frame();
		w.show();
	}
}
```

#####出力
**実行前**
![before2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/b97c5355-a02a-cb97-7ce0-7b6e24aae9e2.png)

**実行後**
![after2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/864d3cd5-c4ec-ff84-12f3-7d8c3a0fab60.png)


##注意
はじめに少し触れたが，ここで数値積分の誤差についてもう少し詳しく見ていくこととする．単純には，誤差は（台形則）>（中点則$^{(1)}$）>（シンプソン則）のようになる．ただし，周期関数の1周期にわたる積分では，台形則・中点則はきわめて高い精度が得られる．さらに，理論上は，台形則・中点則の誤差オーダーは分割数の2乗に反比例するので，分割数を10倍にすれば，1/100オーダーで精度が高くなる．**しかし，コンピュータの有効桁数を超す精度は得られないので，分割数を過度に大きくしすぎると，逆に丸め誤差$^{\(2)}$に蓄積され，逆に精度が落ちることに注意**．

---
(1) 中点則：台形則とほとんど同じだが，台形則でh幅で計算していた各面積をさらに半分のh/2幅で計算する方法．
(2) 丸め誤差：有効桁数になるよう四捨五入するときに，計算機内部で発生する誤差．

##その他の数値積分
先ほどの台形則・中点則・シンプソン則はまとめて，**ニュートン・コーツ(Newton-Cotes)系の公式**として知られ，**区間を等間隔に分割**する方法である．その他2つをここでは示すこととする．

####チェビシェフ(Chebyshev)の公式
**区間を不等間隔で重みを一定**にする方法

####ガウス(Gauss)の公式
**区間を不等間隔で重みも一定でない**方法．
**もっとも精度が高い**

##感想
今までに，大学の授業でも数値積分を扱ったことはあった．そのときは台形則を用いていた．今回は，それを改めて復習できたことに加えて，ほかの数値積分の方法を学ぶことができた．ロボットの制御，特に移動を伴うような場合には，数値積分にお世話になることも多くなるかもしれないので，そのときにいくつかの選択があるということを意識しておこうと思う．（それぞれの方法に特徴があるため．）
数値計算についての学習はもうしばらく続く．数学的な部分が多いが，これらも制御系では役立つと思って，士気を高めて頑張っていきたい．

##参考文献
Javaによるはじめてのアルゴリズム入門　　河西 朝雄　著　技術評論社