最適化(準 Newton 法)

<?php

/**********************************/
/* 準Newton法によるの最小値の計算 */
/*      coded by Y.Suganuma       */
/**********************************/

	$sw = 0;
					// データの入力
	fscanf(STDIN, "%*s %d %*s %d %*s %d %*s %d", $fun, $n, $max, $opt_1);
	fscanf(STDIN, "%*s %d %*s %lf %*s %lf", $method, $eps, $step);
	$x    = array($n);
	$dx   = array(0.0, 0.0);

	$H   = array($n);
	$str = trim(fgets(STDIN));
	strtok($str, " ");
	for ($i1 = 0; $i1 < $n; $i1++) {
		$x[$i1] = floatval(strtok(" "));
		$H[$i1] = array($n);
	}
					// 実行
	switch ($fun) {
		case 1:
			$sw = DFP_BFGS($method, $opt_1, $max, $n, $eps, $step, $y, $x, $dx, $H, "snx1", "dsnx1");
			break;
		case 2:
			$sw = DFP_BFGS($method, $opt_1, $max, $n, $eps, $step, $y, $x, $dx, $H, "snx2", "dsnx2");
			break;
		case 3:
			$sw = DFP_BFGS($method, $opt_1, $max, $n, $eps, $step, $y, $x, $dx, $H, "snx3", "dsnx3");
			break;
	}
					// 結果の出力
	if ($sw < 0) {
		printf("   収束しませんでした!");
		switch ($sw) {
			case -1:
				printf("(収束回数)\n");
				break;
			case -2:
				printf("(1次元最適化の区間)\n");
				break;
			case -3:
				printf("(黄金分割法)\n");
				break;
		}
	}
	else {
		printf("   結果=");
		for ($i1 = 0; $i1 < $n; $i1++)
			printf("%f ", $x[$i1]);
		printf(" 最小値=%f  回数=%d\n", $y, $sw);
	}

/********************************************************/
/* DFP法 or BFGS法                                      */
/*      method : =0 : DFP法                             */
/*               =1 : BFGS法                            */
/*      opt_1 : =0 : 1次元最適化を行わない             */
/*              =1 : 1次元最適化を行う                 */
/*      max : 最大繰り返し回数                          */
/*      n : 次元                                        */
/*      eps : 収束判定条件                              */
/*      step : きざみ幅                                 */
/*      y : 最小値                                      */
/*      x1 : x(初期値と答え)                            */
/*      dx : 関数の微分値                               */
/*      H : Hesse行列の逆行列                           */
/*      snx : 関数値を計算する関数名                    */
/*      dsnx : 関数の微分を計算する関数名(符号を変える) */
/*      return : >=0 : 正常終了(収束回数)               */
/*               =-1 : 収束せず                         */
/*               =-2 : 1次元最適化の区間が求まらない   */
/*               =-3 : 黄金分割法が失敗                 */
/********************************************************/

function DFP_BFGS($method, $opt_1, $max, $n, $eps, $step, &$y, &$x1, $dx, $H, $snx, $dsnx)
{
	$count = 0;
	$sw    = 0;
	$x2    = array($n);
	$g     = array($n);
	$g1    = array($n);
	$g2    = array($n);
	$s     = array($n);
	$w     = array($n);

	$y1 = $snx(0.0, $x1, $dx);
	$dsnx($x1, $g1);

	$H1 = array($n);
	$H2 = array($n);
	$I  = array($n);
	for ($i1 = 0; $i1 < $n; $i1++) {
		$H1[$i1] = array($n);
		$H2[$i1] = array($n);
		$I[$i1]  = array($n);
		for ($i2 = 0; $i2 < $n; $i2++) {
			$H[$i1][$i2] = 0.0;
			$I[$i1][$i2] = 0.0;
		}
		$H[$i1][$i1] = 1.0;
		$I[$i1][$i1] = 1.0;
	}

	while ($count < $max && $sw == 0) {
		$count++;
					// 方向の計算
		for ($i1 = 0; $i1 < $n; $i1++) {
			$dx[$i1] = 0.0;
			for ($i2 = 0; $i2 < $n; $i2++)
				$dx[$i1] -= $H[$i1][$i2] * $g1[$i2];
		}
					// 1次元最適化を行わない
		if ($opt_1 == 0) {
						// 新しい点
			for ($i1 = 0; $i1 < $n; $i1++)
				$x2[$i1] = $x1[$i1] + $step * $dx[$i1];
						// 新しい関数値
			$y2 = $snx(0.0, $x2, $dx);
		}
					// 1次元最適化を行う
		else {
						// 区間を決める
			$sw1 = 0;
			$f1  = $y1;
			$sp  = $step;
			$f2  = $snx($sp, $x1, $dx);
			if ($f2 > $f1)
				$sp = -$step;
			for ($i1 = 0; $i1 < $max && $sw1 == 0; $i1++) {
				$f2 = $snx($sp, $x1, $dx);
				if ($f2 > $f1)
					$sw1 = 1;
				else {
					$sp *= 2.0;
					$f1  = $f2;
				}
			}
						// 区間が求まらない
			if ($sw1 == 0)
				$sw = -2;
						// 区間が求まった
			else {
							// 黄金分割法
				$k = gold(0.0, $sp, $eps, $y2, $sw1, $max, $x1, $dx, $snx);
							// 黄金分割法が失敗
				if ($sw1 < 0)
					$sw = -3;
							// 黄金分割法が成功
				else {
								// 新しい点
					for ($i1 = 0; $i1 < $n; $i1++)
						$x2[$i1] = $x1[$i1] + $k * $dx[$i1];
				}
			}
		}

		if ($sw == 0) {
					// 収束(関数値の変化<eps)
			if (abs($y2-$y1) < $eps) {
				$sw = $count;
				$y  = $y2;
				for ($i1 = 0; $i1 < $n; $i1++)
					$x1[$i1] = $x2[$i1];
			}
					// 関数値の変化が大きい
			else {
						// 傾きの計算
				$dsnx($x2, $g2);
				$sm = 0.0;
				for ($i1 = 0; $i1 < $n; $i1++)
					$sm += $g2[$i1] * $g2[$i1];
				$sm = sqrt($sm);
						// 収束(傾き<eps)
				if ($sm < $eps) {
					$sw = $count;
					$y  = $y2;
					for ($i1 = 0; $i1 < $n; $i1++)
						$x1[$i1] = $x2[$i1];
				}
						// 収束していない
				else {
					for ($i1 = 0; $i1 < $n; $i1++) {
						$g[$i1] = $g2[$i1] - $g1[$i1];
						$s[$i1] = $x2[$i1] - $x1[$i1];
					}
					$sm1 = 0.0;
					for ($i1 = 0; $i1 < $n; $i1++)
						$sm1 += $s[$i1] * $g[$i1];
							// DFP法
					if ($method == 0) {
						for ($i1 = 0; $i1 < $n; $i1++) {
							$w[$i1] = 0.0;
							for ($i2 = 0; $i2 < $n; $i2++)
								$w[$i1] += $g[$i2] * $H[$i2][$i1];
						}
						$sm2 = 0.0;
						for ($i1 = 0; $i1 < $n; $i1++)
							$sm2 += $w[$i1] * $g[$i1];
						for ($i1 = 0; $i1 < $n; $i1++) {
							$w[$i1] = 0.0;
							for ($i2 = 0; $i2 < $n; $i2++)
								$w[$i1] += $H[$i1][$i2] * $g[$i2];
						}
						for ($i1 = 0; $i1 < $n; $i1++) {
							for ($i2 = 0; $i2 < $n; $i2++)
								$H1[$i1][$i2] = $w[$i1] * $g[$i2];
						}
						for ($i1 = 0; $i1 < $n; $i1++) {
							for ($i2 = 0; $i2 < $n; $i2++) {
								$H2[$i1][$i2] = 0.0;
								for ($i3 = 0; $i3 < $n; $i3++)
									$H2[$i1][$i2] += $H1[$i1][$i3] * $H[$i3][$i2];
							}
						}
						for ($i1 = 0; $i1 < $n; $i1++) {
							for ($i2 = 0; $i2 < $n; $i2++)
								$H[$i1][$i2] = $H[$i1][$i2]  + $s[$i1] * $s[$i2] / $sm1 - $H2[$i1][$i2] / $sm2;
						}
					}
							// BFGS法
					else {
						for ($i1 = 0; $i1 < $n; $i1++) {
							for ($i2 = 0; $i2 < $n; $i2++)
								$H1[$i1][$i2] = $I[$i1][$i2] - $s[$i1] * $g[$i2] / $sm1;
						}
						for ($i1 = 0; $i1 < $n; $i1++) {
							for ($i2 = 0; $i2 < $n; $i2++) {
								$H2[$i1][$i2] = 0.0;
								for ($i3 = 0; $i3 < $n; $i3++)
									$H2[$i1][$i2] += $H1[$i1][$i3] * $H[$i3][$i2];
							}
						}
						for ($i1 = 0; $i1 < $n; $i1++) {
							for ($i2 = 0; $i2 < $n; $i2++) {
								$H[$i1][$i2] = 0.0;
								for ($i3 = 0; $i3 < $n; $i3++)
									$H[$i1][$i2] += $H2[$i1][$i3] * $H1[$i3][$i2];
							}
						}
						for ($i1 = 0; $i1 < $n; $i1++) {
							for ($i2 = 0; $i2 < $n; $i2++)
								$H[$i1][$i2] += $s[$i1] * $s[$i2] / $sm1;
						}
					}
					$y1 = $y2;
					for ($i1 = 0; $i1 < $n; $i1++) {
						$g1[$i1] = $g2[$i1];
						$x1[$i1] = $x2[$i1];
					}
				}
			}
		}
	}

	if ($sw == 0)
		$sw = -1;

	return $sw;
}

/*********************************************/
/* 与えられた点xから,dx方向へk*dxだけ進んだ */
/* 点における関数値を計算する                */
/*********************************************/
					// 関数1
function snx1($k, $x, $dx)
{
	$w  = array(2);

	$w[0] = $x[0] + $k * $dx[0];
	$w[1] = $x[1] + $k * $dx[1];
	$x1   = $w[0] - 1.0;
	$y1   = $w[1] - 2.0;
	$y    = $x1 * $x1 + $y1 * $y1;

	return $y;
}
					// 関数2
function snx2($k, $x, $dx)
{
	$w  = array(2);

	$w[0] = $x[0] + $k * $dx[0];
	$w[1] = $x[1] + $k * $dx[1];
	$x1   = $w[1] - $w[0] * $w[0];
	$y1   = 1.0 - $w[0];
	$y    = 100.0 * $x1 * $x1 + $y1 * $y1;

	return $y;
}
					// 関数3
function snx3($k, $x, $dx)
{
	$w = array(2);

	$w[0] = $x[0] + $k * $dx[0];
	$w[1] = $x[1] + $k * $dx[1];
	$x1   = 1.5 - $w[0] * (1.0 - $w[1]);
	$y1   = 2.25 - $w[0] * (1.0 - $w[1] * $w[1]);
	$z1   = 2.625 - $w[0] * (1.0 - $w[1] * $w[1] * $w[1]);
	$y    = $x1 * $x1 + $y1 * $y1 + $z1 * $z1;

	return $y;
}

/********************/
/* 微係数を計算する */
/********************/
					// 関数1
function dsnx1($x, &$dx)
{
	$dx[0] = -2.0 * ($x[0] - 1.0);
	$dx[1] = -2.0 * ($x[1] - 2.0);
}
					// 関数2
function dsnx2($x, &$dx)
{
	$dx[0] = 400.0 * $x[0] * ($x[1] - $x[0] * $x[0]) + 2.0 * (1.0 - $x[0]);
	$dx[1] = -200.0 * ($x[1] - $x[0] * $x[0]);
}
					// 関数3
function dsnx3($x, &$dx)
{
	$dx[0] = 2.0 * (1.0 - $x[1]) * (1.5 - $x[0] * (1.0 - $x[1])) +
            2.0 * (1.0 - $x[1] * $x[1]) * (2.25 - $x[0] * (1.0 - $x[1] * $x[1])) +
            2.0 * (1.0 - $x[1] * $x[1] * $x[1]) * (2.625 - $x[0] * (1.0 - $x[1] * $x[1] * $x[1]));
	$dx[1] = -2.0 * $x[0] * (1.5 - $x[0] * (1.0 - $x[1])) -
            4.0 * $x[0] * $x[1] * (2.25 - $x[0] * (1.0 - $x[1] * $x[1])) -
            6.0 * $x[0] * $x[1] * $x[1] * (2.625 - $x[0] * (1.0 - $x[1] * $x[1] * $x[1]));
}

/****************************************************************/
/* 黄金分割法(与えられた方向での最小値)                         */
/*      a,b : 初期区間 a < b                                    */
/*      eps : 許容誤差                                          */
/*      val : 関数値                                            */
/*      ind : 計算状況                                          */
/*              >= 0 : 正常終了(収束回数)                       */
/*              = -1 : 収束せず                                 */
/*      max : 最大試行回数                                      */
/*      w : 位置                                                */
/*      dw : 傾きの成分                                         */
/*      snx : 関数値を計算する関数の名前                        */
/*      return : 結果(w+y*dwのy)                              */
/****************************************************************/
function gold($a, $b, $eps, &$val, &$ind, $max, $w, $dw, $snx)
{
					// 初期設定
	$tau   = (sqrt(5.0) - 1.0) / 2.0;
	$x     = 0.0;
	$count = 0;
	$ind   = -1;
	$x1    = $b - $tau * ($b - $a);
	$x2    = $a + $tau * ($b - $a);
	$f1    = $snx($x1, $w, $dw);
	$f2    = $snx($x2, $w, $dw);
					// 計算
	while ($count < $max && $ind < 0) {
		$count += 1;
		if ($f2 > $f1) {
			if (abs($b-$a) < $eps) {
				$ind = 0;
				$x   = $x1;
				$val = $f1;
			}
			else {
				$b  = $x2;
				$x2 = $x1;
				$x1 = $a + (1.0 - $tau) * ($b - $a);
				$f2 = $f1;
				$f1 = $snx($x1, $w, $dw);
			}
		}
		else {
			if (abs($b-$a) < $eps) {
				$ind = 0;
				$x   = $x2;
				$val = $f2;
				$f1  = $f2;
			}
			else {
				$a = $x1;
				$x1 = $x2;
				$x2 = $b - (1.0 - $tau) * ($b - $a);
				$f1 = $f2;
				$f2 = $snx($x2, $w, $dw);
			}
		}
	}
					// 収束した場合の処理
	if ($ind == 0) {
		$ind = $count;
		$fa  = $snx($a, $w, $dw);
		$fb  = $snx($b, $w, $dw);
		if ($fb < $fa) {
			$a  = $b;
			$fa = $fb;
		}
		if ($fa < $f1) {
			$x   = $a;
			$val = $fa;
		}
	}

	return $x;
}

?>