#*****************************/
# Newton法による最小値の計算 */
# coded by Y.Suganuma */
#*****************************/
#******************************************************/
# 線形連立方程式を解く(逆行列を求める) */
# w : 方程式の左辺及び右辺 */
# n : 方程式の数 */
# m : 方程式の右辺の列の数 */
# eps : 正則性を判定する規準 */
# return : =0 : 正常 */
# =1 : 逆行列が存在しない */
#******************************************************/
def Gauss(w, n, m, eps)
ind = 0
nm = n + m
for i1 in 0 ... n
y1 = 0.0
m1 = i1 + 1
m2 = 0
for i2 in i1 ... n
y2 = w[i2][i1].abs()
if y1 < y2
y1 = y2
m2 = i2
end
end
if y1 < eps
ind = 1
break
else
for i2 in i1 ... nm
y1 = w[i1][i2]
w[i1][i2] = w[m2][i2]
w[m2][i2] = y1
end
y1 = 1.0 / w[i1][i1]
for i2 in m1 ... nm
w[i1][i2] *= y1
end
for i2 in 0 ... n
if i2 != i1
for i3 in m1 ... nm
w[i2][i3] -= w[i2][i1] * w[i1][i3]
end
end
end
end
end
w[0][nm] = ind
return w
end
#******************************************************/
# 与えられた点xから,dx方向へk*dxだけ進んだ点における */
# 関数値,微係数,及び,Hesse行列の逆行列を計算する */
#******************************************************/
# 関数1
snx1 = Proc.new { |sw, k, x, dx, h, eps|
if sw == 0
w = Array.new(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
x1 * x1 + y1 * y1
elsif sw == 1
dx[0] = -2.0 * (x[0] - 1.0)
dx[1] = -2.0 * (x[1] - 2.0)
else
h = [[2.0, 0.0, 1.0, 0.0, 0.0], [0.0, 2.0, 0.0, 1.0, 0.0]]
Gauss(h, 2, 2, eps)
end
}
# 関数2
snx2 = Proc.new { |sw, k, x, dx, h, eps|
if sw == 0
w = Array.new(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]
100.0 * x1 * x1 + y1 * y1
elsif sw == 1
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])
else
h[0][0] = 400.0 * (3.0 * x[0] * x[0] - x[1]) + 2.0
h[0][1] = -400.0 * x[0]
h[1][0] = h[0][1]
h[1][1] = 200.0
h[0][2] = 1.0
h[0][3] = 0.0
h[1][2] = 0.0
h[1][3] = 1.0
Gauss(h, 2, 2, eps)
end
}
# 関数3
snx3 = Proc.new { |sw, k, x, dx, h, eps|
if sw == 0
w = Array.new(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])
x1 * x1 + y1 * y1 + z1 * z1
elsif sw == 1
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]))
else
x1 = 1.0 - x[1]
x2 = 1.0 - x[1] * x[1]
x3 = 1.0 - x[1] * x[1] * x[1]
h[0][0] = 2.0 * x1 * x1 + 2.0 * x2 * x2 + 2.0 * x3 * x3
h[0][1] = 2.0 * (1.5 - x[0] * x1) - 2.0 * x[0] * x1 +
4.0 * x[1] * (2.25 - x[0] * x2) - 4.0 * x[0] * x[1] * x2 +
6.0 * x[1] * x[1] * (2.625 - x[0] * x3) - 6.0 * x[0] * x[1] * x[1] * x3
h[1][0] = h[0][1]
h[1][1] = 2.0 * x[0] * x[0] +
4.0 * x[0] * (2.25 - x[0] * x2) + 8.0 * x[0] * x[0] * x[1] * x[1] +
12.0 * x[0] * x[1] * (2.625 - x[0] * x3) +
18.0 * x[0] * x[0] * x[1] * x[1] * x[1] * x[1]
h[0][2] = 1.0
h[0][3] = 0.0
h[1][2] = 0.0
h[1][3] = 1.0
Gauss(h, 2, 2, eps)
end
}
#***************************************************************/
# 黄金分割法(与えられた方向での最小値) */
# a,b : 初期区間 a < b */
# eps : 許容誤差 */
# val : 関数値 */
# ind : 計算状況 */
# >= 0 : 正常終了(収束回数) */
# = -1 : 収束せず */
# max : 最大試行回数 */
# w : 位置 */
# dw : 傾きの成分 */
# h : Hesse行列の逆行列 */
# snx : 関数値を計算する関数の名前 */
# (微分は,その符号を変える) */
# return : 結果(w+y*dwのy) */
#***************************************************************/
def gold(a, b, eps, val, ind, max, w, dw, h, &snx)
# 初期設定
tau = (Math.sqrt(5.0) - 1.0) / 2.0
x = 0.0
count = 0
ind[0] = -1
x1 = b - tau * (b - a)
x2 = a + tau * (b - a)
f1 = snx.call(0, x1, w, dw, h, eps)
f2 = snx.call(0, x2, w, dw, h, eps)
# 計算
while count < max && ind[0] < 0
count += 1
if f2 > f1
if (b-a).abs() < eps
ind[0] = 0
x = x1
val[0] = f1
else
b = x2
x2 = x1
x1 = a + (1.0 - tau) * (b - a)
f2 = f1
f1 = snx.call(0, x1, w, dw, h, eps)
end
else
if (b-a).abs() < eps
ind[0] = 0
x = x2
val[0] = f2
f1 = f2
else
a = x1
x1 = x2
x2 = b - (1.0 - tau) * (b - a)
f1 = f2
f2 = snx.call(0, x2, w, dw, h, eps)
end
end
end
# 収束した場合の処理
if ind[0] == 0
ind[0] = count
fa = snx.call(0, a, w, dw, h, eps)
fb = snx.call(0, b, w, dw, h, eps)
if fb < fa
a = b
fa = fb
end
if fa < f1
x = a
val[0] = fa
end
end
return x
end
#*******************************************************/
# Newton法 */
# opt_1 : =0 : 1次元最適化を行わない */
# =1 : 1次元最適化を行う */
# max : 最大繰り返し回数 */
# n : 次元 */
# eps : 収束判定条件 */
# step : きざみ幅 */
# y : 最小値 */
# x : x(初期値と答え) */
# dx : 関数の微分値 */
# h : Hesse行列の逆行列 */
# snx : 関数値,その微分,及び,Hesse行列の逆行列 */
# return : >=0 : 正常終了(収束回数) */
# =-1 : 収束せず */
# =-2 : 1次元最適化の区間が求まらない */
# =-3 : 黄金分割法が失敗 */
#*******************************************************/
def Newton(opt_1, max, n, eps, step, y, x, dx, h, &snx)
sw1 = Array.new(n)
y2 = Array.new(n)
wk = Array.new(n)
for i1 in 0 ... n
wk[i1] = 0.0
end
count = 0
sw = 0
y1 = snx.call(0, 0.0, x, dx, h, eps)
while count < max && sw == 0
# 傾きの計算
snx.call(1, 0.0, x, wk, h, eps)
# Hesse行列の逆行列の計算
h = snx.call(2, 0.0, x, wk, h, eps)
sw1[0] = h[0][2*n] # もっと良い方法はあるか?
# 収束していない
if sw1[0] == 0
# 方向の計算
count += 1
for i1 in 0 ... n
dx[i1] = 0.0
for i2 in 0 ... n
dx[i1] += h[i1][n+i2] * wk[i2]
end
end
# 1次元最適化を行わない
if opt_1 == 0
# 新しい点
for i1 in 0 ... n
x[i1] += dx[i1]
end
# 新しい関数値
y2[0] = snx.call(0, 0.0, x, dx, h, eps)
# 関数値の変化が大きい
if (y2[0]-y1).abs() > eps
y1 = y2[0]
# 収束(関数値の変化<eps)
else
sw = count
y[0] = y2[0]
end
# 1次元最適化を行う
else
# 区間を決める
sw1[0] = 0
f1 = y1
sp = step
f2 = snx.call(0, sp, x, dx, h, eps)
if f2 > f1
sp = -step
end
for i1 in 0 ... max
f2 = snx.call(0, sp, x, dx, h, eps)
if f2 > f1
sw1[0] = 1
break
else
sp *= 2.0
f1 = f2
end
end
# 区間が求まらない
if sw1[0] == 0
sw = -2
# 区間が求まった
else
# 黄金分割法
k = gold(0.0, sp, eps, y2, sw1, max, x, dx, h, &snx)
# 黄金分割法が失敗
if sw1[0] < 0
sw = -3
# 黄金分割法が成功
else
# 新しい点
for i1 in 0 ... n
x[i1] += k * dx[i1]
end
# 関数値の変化が大きい
if (y1-y2[0]).abs() > eps
y1 = y2[0]
# 収束(関数値の変化<eps)
else
sw = count
y[0] = y2[0]
end
end
end
end
# 収束(傾き<eps)
else
sw = count
y[0] = y1
end
end
if sw == 0
sw = -1
end
return sw
end
# データの入力
str = gets()
a = str.split(" ")
fun = Integer(a[1])
n = Integer(a[3])
max = Integer(a[5])
opt_1 = Integer(a[7])
str = gets()
a = str.split(" ")
eps = Float(a[1])
step = Float(a[3])
x = Array.new(n)
dx = Array.new(n)
y = Array.new(1)
sw = 0
str = gets()
a = str.split(" ")
h = Array.new(n)
for i1 in 0 ... n
x[i1] = Float(a[i1+1])
dx[i1] = 0.0
h[i1] = Array.new(2*n+1)
end
# 実行
case fun
when 1
sw = Newton(opt_1, max, n, eps, step, y, x, dx, h, &snx1)
when 2
sw = Newton(opt_1, max, n, eps, step, y, x, dx, h, &snx2)
when 3
sw = Newton(opt_1, max, n, eps, step, y, x, dx, h, &snx3)
end
# 結果の出力
if sw < 0
printf(" 収束しませんでした!")
case sw
when -1
printf("(収束回数)\n")
when -2
printf("(1次元最適化の区間)\n")
when -3
printf("(黄金分割法)\n")
end
else
printf(" 結果=")
for i1 in 0 ... n
printf("%f ", x[i1])
end
printf(" 最小値=%f 回数=%d\n", y[0], sw)
end