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