関数名の受け渡し(ニュートン法)

  ニュートン法は汎用的な手法です.従って,Math クラスに含まれる sqrt や sin などのように,ニュートン法を含むクラスを作成しておき,その具体的な処理方法を知らなくても,簡単に使用できることが望まれます.プログラム例 7.14 に示した C/C++ の場合は,関数値及び関数値の微分を計算する関数を作成し,それらの関数名を引数としてニュートン法を処理する関数に渡してやれば簡単に計算できます.しかし,Java においては,ポインタが明示的に定義されていないため,C/C++ と全く同じ方法では不可能です.ニュートン法で呼ぶ関数値及び関数値の微分を計算する関数名を固定しておけば,それらの名前を引数としてニュートン法を処理する関数に渡す必要がないため,C/C++ と似たようなプログラムで記述可能です.しかし,同じプログラム内で,複数の非線形方程式の解を求めたいような場合は,その数だけ,ニュートン法を処理する関数を作成してやらなければなりません.ニュートン法のような一般的手法に対して,使用する目的ごとにプログラムを書き換えなければならないことは,好ましい状況とは言えません.そこで,他の方法によって,関数値及び関数値の微分を計算する関数に関する情報を,ニュートン法を処理する関数に受け渡すことを考えてみます.

  クラスに関する説明を十分行っていませんので,以下に示すプログラムの意味が分かりにくいかと思います.クラスに関して学んだ後,もう一度見直してください.この例のように記述すれば,C/C++ のプログラムと同様に,newton という関数(ニュートン法を処理する関数)を修正することなしに,任意の非線形方程式(クラス Kansu や Test は修正してやる必要がある)の解を求めることができます.クラス Kansu というクラス名は固定されますが,関数値およびその微分を計算する関数名は変更することが可能であり,同じプログラム内において複数の非線形方程式を解く場合にも対応可能です.

  なお,クラス App は,技術計算用の手法を集め,Math クラスのような感覚で使用できるようにするためのクラスです.そのため,すべての関数(メソッド)を static で定義しています.この例では,ニュートン法しか入っていませんが,以下に示してある例をこの中に付加していけば,充実したものができあがると思います.
/****************************/
/* ニュートン法             */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

public class Test {
	public static void main(String args[]) throws IOException
	{
		double eps1, eps2, x, x0;
		int max, ind[] = new int [1];

		eps1 = 1.0e-7;
		eps2 = 1.0e-10;
		max  = 30;
		x0   = 0.0;
					// 関数値を計算するクラス
		Kansu kn1 = new Kansu(0);
		Kansu kn2 = new Kansu(1);
					// ニュートン法の実行
		x = App.newton(x0, eps1, eps2, max, ind, kn1, kn2);
					// 出力
		System.out.println("ind=" + ind[0] + "  x=" + x + "  f=" + kn1.snx(x) + "  df=" + kn2.snx(x));
	}
}

/******************************/
/* 関数値およびその微分の計算 */
/******************************/
class Kansu {
	private int sw;
					// コンストラクタ
	Kansu (int s) {sw = s;}
					// double型関数
	double snx(double x)
	{

		double y = 0.0;

		switch (sw) {
						// 関数値(f(x))の計算
			case 0:
				y = Math.exp(x) - 3.0 * x;
				break;
						// 関数の微分の計算
			case 1:
				y = Math.exp(x) - 3.0;
				break;
		}

		return y;
	}
}

/************************
/* 科学技術系算用の手法 */
/************************/
class App {

	/*****************************************************/
	/* Newton法による非線形方程式(f(x)=0)の解            */
	/*      x1 : 初期値 	                             */
	/*      eps1 : 終了条件1(|x(k+1)-x(k)|<eps1)   */
	/*      eps2 : 終了条件2(|f(x(k))|<eps2)       */
	/*      max : 最大試行回数                           */
	/*      ind : 実際の試行回数                         */
	/*            (負の時は解を得ることができなかった) */
	/*      kn1 : 関数を計算するクラスオブジェクト       */
	/*      kn2 : 関数の微分を計算するクラスオブジェクト */
	/*      return : 解                                  */
	/*****************************************************/
	static double newton(double x1, double eps1, double eps2, int max,
                         int ind[], Kansu kn1, Kansu kn2)
	{
		double g, dg, x;
		int sw;

		x      = x1;
		ind[0] = 0;
		sw     = 0;

		while (sw == 0 && ind[0] >= 0) {

			ind[0]++;
			sw = 1;
			g  = kn1.snx(x1);

			if (Math.abs(g) > eps2) {
				if (ind[0] <= max) {
					dg = kn2.snx(x1);
					if (Math.abs(dg) > eps2) {
						x = x1 - g / dg;
						if (Math.abs(x-x1) > eps1 && Math.abs(x-x1) > eps1*Math.abs(x)) {
							x1 = x;
							sw = 0;
						}
					}
					else
						ind[0] = -1;
				}
				else
					ind[0] = -1;
			}
		}

		return x;
	}
}
		
  たしかに,上に述べた方法によっても,C/C++ と似たような結果になりました.しかし,関数値等を計算するクラス Kansu を通して記述しなければならず,多少複雑になります.さらに,上で述べた方法には大きな問題があります.クラス App 内で,関数名を必要としないメソッドと必要とするメソッドを定義した場合,関数名を必要としないメソッドだけを利用する場合においても,クラス Kansu を定義しておく必要があります.また,関数名を必要とするメソッドにおいて,異なる引数や戻り値を持つ snx 等のメソッドが必要になった場合は,いずれのメソッドを利用する際にも,それらすべてに対応できるようにクラス Kansu を作成しておく必要があります.

  そこで,あと一つの方法は,以下に示すようにニュートン法を記述してあるクラスのサブクラスとして,関数値を計算するクラスを定義する方法です.このようにすれば,異なる非線形関数の解を同じプログラム内で計算しようとする場合は,対象とする非線形関数ごとに異なるクラスを定義することによって可能となります.この解説書に出てくる以下の例では,ニュートン法等を Math クラスの関数のように使用したいため,上で述べた方法を利用していきますが,この方法で記述することもすべて可能です.
/****************************/
/* ニュートン法             */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

public class Test {
	public static void main(String args[]) throws IOException
	{
		double eps1, eps2, x, x0;
		int max, ind[] = new int [1];

		eps1 = 1.0e-7;
		eps2 = 1.0e-10;
		max  = 30;
		x0   = 0.0;
					// 関数値を計算するクラス
		Kansu kn = new Kansu();
					// ニュートン法の実行
		x = kn.newton(x0, eps1, eps2, max, ind);
					// 出力
		System.out.println("ind=" + ind[0] + "  x=" + x + "  f=" + kn.snx(x) + "  df=" + kn.dsnx(x));
	}
}

/******************************/
/* 関数値およびその微分の計算 */
/******************************/
class Kansu extends Newton   // クラスNewtonを継承
{
			// 関数値(f(x))の計算
	double snx(double x)   // クラスNewtonの関数のオーバーライド
	{
		double y;
		y = Math.exp(x) - 3.0 * x;
		return y;
	}
			// 関数の微分の計算
	double dsnx(double x)   // クラスNewtonの関数のオーバーライド
	{
		double y;
		y = Math.exp(x) - 3.0;
		return y;
	}
}

/*****************************************************/
/* Newton法による非線形方程式(f(x)=0)の解            */
/*      x1 : 初期値                                  */
/*      eps1 : 終了条件1(|x(k+1)-x(k)|<eps1)   */
/*      eps2 : 終了条件2(|f(x(k))|<eps2)       */
/*      max : 最大試行回数                           */
/*      ind : 実際の試行回数                         */
/*            (負の時は解を得ることができなかった) */
/*      return : 解                                  */
/*****************************************************/
abstract class Newton {

	abstract double snx(double x);   // 定義しておく必要あり
	abstract double dsnx(double x);   // 定義しておく必要あり

	double newton(double x1, double eps1, double eps2, int max, int ind[])
	{
		double g, dg, x;
		int sw;

		x      = x1;
		ind[0] = 0;
		sw     = 0;

		while (sw == 0 && ind[0] >= 0) {

			ind[0]++;
			sw = 1;
			g  = snx(x1);

			if (Math.abs(g) > eps2) {
				if (ind[0] <= max) {
					dg = dsnx(x1);
					if (Math.abs(dg) > eps2) {
						x = x1 - g / dg;
						if (Math.abs(x-x1) > eps1 && Math.abs(x-x1) > eps1*Math.abs(x)) {
							x1 = x;
							sw = 0;
						}
					}
					else
						ind[0] = -1;
				}
				else
					ind[0] = -1;
			}
		}

		return x;
	}
}