<?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 = conjugate($opt_1, $max, $n, $eps, $step, $y, $x, $dx, "snx1", "dsnx1");
break;
case 2:
$sw = conjugate($opt_1, $max, $n, $eps, $step, $y, $x, $dx, "snx2", "dsnx2");
break;
case 3:
$sw = conjugate($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);
}
/********************************************************/
/* 共役勾配法 */
/* opt_1 : =0 : 1次元最適化を行わない */
/* =1 : 1次元最適化を行う */
/* max : 最大繰り返し回数 */
/* n : 次元 */
/* eps : 収束判定条件 */
/* step : きざみ幅 */
/* y : 最小値 */
/* x : x(初期値と答え) */
/* dx : 関数の微分値 */
/* snx : 関数値を計算する関数名 */
/* dsnx : 関数の微分を計算する関数名(符号を変える) */
/* return : >=0 : 正常終了(収束回数) */
/* =-1 : 収束せず */
/* =-2 : 1次元最適化の区間が求まらない */
/* =-3 : 黄金分割法が失敗 */
/********************************************************/
function conjugate($opt_1, $max, $n, $eps, $step, &$y, &$x, $dx, $snx, $dsnx)
{
$b = 0.0;
$s1 = 1.0;
$count = 0;
$sw = 0;
$g = array($n);
$y1 = $snx(0.0, $x, $dx);
while ($count < $max && $sw == 0) {
// 傾きの計算
$dsnx($x, $g);
// 傾きのチェック
$s2 = 0.0;
for ($i1 = 0; $i1 < $n; $i1++)
$s2 += $g[$i1] * $g[$i1];
// 収束していない
if (sqrt($s2) > $eps) {
// 方向の計算
$count++;
if ($count%$n == 1)
$b = 0.0;
else
$b = $s2 / $s1;
for ($i1 = 0; $i1 < $n; $i1++)
$dx[$i1] = $g[$i1] + $b * $dx[$i1];
// 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;
$s1 = $s2;
}
// 収束(関数値の変化<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;
$s1 = $s2;
}
// 収束(関数値の変化<eps)
else {
$sw = $count;
$y = $y2;
}
}
}
}
}
// 収束(傾き<eps)
else {
$sw = $count;
$y = $y1;
}
}
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;
}
?>