最適化(最急降下法)

<?php

/********************************/
/* 最急降下法による最小値の計算 */
/*      coded by Y.Suganuma     */
/********************************/

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

	$str = trim(fgets(STDIN));
	strtok($str, " ");
	for ($i1 = 0; $i1 < $n; $i1++)
		$x[$i1] = floatval(strtok(" "));
					// 実行
	switch ($fun) {
		case 1:
			$sw = steep($opt_1, $max, $n, $eps, $step, $y, $x, $dx, "snx1", "dsnx1");
			break;
		case 2:
			$sw = steep($opt_1, $max, $n, $eps, $step, $y, $x, $dx, "snx2", "dsnx2");
			break;
		case 3:
			$sw = steep($opt_1, $max, $n, $eps, $step, $y, $x, $dx, "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);
	}

/********************************************************/
/* 最急降下法(steepest descent method)                  */
/*      opt_1 : =0 : 1次元最適化を行わない             */
/*              =1 : 1次元最適化を行う                 */
/*      max : 最大繰り返し回数                          */
/*      n : 次元                                        */
/*      eps : 収束判定条件                              */
/*      step : きざみ幅                                 */
/*      y : 最小値                                      */
/*      x : x(初期値と答え)                             */
/*      dx : 関数の微分値                               */
/*      snx : 関数値を計算する関数名                    */
/*      dsnx : 関数の微分を計算する関数名(符号を変える) */
/*      return : >=0 : 正常終了(収束回数)               */
/*               =-1 : 収束せず                         */
/*               =-2 : 1次元最適化の区間が求まらない   */
/*               =-3 : 黄金分割法が失敗                 */
/********************************************************/
function steep($opt_1, $max, $n, $eps, $step, &$y, &$x, $dx, $snx, $dsnx)
{
	$count = 0;
	$sw    = 0;
	$y1    = $snx(0.0, $x, $dx);

	while ($count < $max && $sw == 0) {
					// 傾きの計算
		$dsnx($x, $dx);
		$count++;
					// 1次元最適化を行わない
		if ($opt_1 == 0) {
						// 新しい点
			for ($i1 = 0; $i1 < $n; $i1++)
				$x[$i1] += $step * $dx[$i1];
						// 新しい関数値
			$y2 = $snx(0.0, $x, $dx);
							// 関数値の変化が大きい
			if (abs($y2-$y1) > $eps)
				$y1 = $y2;
							// 収束(関数値の変化<eps)
			else {
				$sw = $count;
				$y  = $y2;
			}
		}
					// 1次元最適化を行う
		else {
						// 区間を決める
			$sw1 = 0;
			$f1  = $y1;
			$sp  = $step;
			$f2  = $snx($sp, $x, $dx);
			if ($f2 > $f1)
				$sp = -$step;
			for ($i1 = 0; $i1 < $max && $sw1 == 0; $i1++) {
				$f2 = $snx($sp, $x, $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, $x, $dx, $snx);
							// 黄金分割法が失敗
				if ($sw1 < 0)
					$sw = -3;
							// 黄金分割法が成功
				else {
								// 新しい点
					for ($i1 = 0; $i1 < $n; $i1++)
						$x[$i1] += $k * $dx[$i1];
									// 関数値の変化が大きい
					if (abs($y1-$y2) > $eps)
						$y1 = $y2;
									// 収束(関数値の変化<eps)
					else {
						$sw = $count;
						$y  = $y2;
					}
				}
			}
		}
	}

	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;
}

?>