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