minimization 0.2.3 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +2 -2
- data/History.txt +8 -0
- data/LICENSE.txt +23 -87
- data/Manifest.txt +18 -0
- data/README.md +19 -16
- data/lib/minimization.rb +9 -1
- data/lib/minimization/version.rb +1 -1
- data/lib/multidim/brent_root_finder.rb +139 -0
- data/lib/multidim/conjugate_gradient.rb +284 -0
- data/lib/multidim/nelder_mead.rb +326 -0
- data/lib/multidim/point_value_pair.rb +22 -0
- data/lib/multidim/powell.rb +319 -0
- data/minimization.gemspec +1 -1
- data/spec/minimization_conjugate_gradient_fletcher_reeves_spec.rb +68 -0
- data/spec/minimization_conjugate_gradient_polak_ribiere_spec.rb +54 -0
- data/spec/minimization_nelder_mead_spec.rb +57 -0
- data/spec/minimization_powell_spec_spec.rb +57 -0
- data/spec/spec_helper.rb +5 -3
- metadata +15 -8
@@ -0,0 +1,326 @@
|
|
1
|
+
# = nelder_mead.rb -
|
2
|
+
# Minimization- Minimization algorithms on pure Ruby
|
3
|
+
# Copyright (C) 2010 Claudio Bustos
|
4
|
+
#
|
5
|
+
# This program is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU General Public License
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
8
|
+
# of the License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program; if not, write to the Free Software
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
18
|
+
#
|
19
|
+
# This algorith was adopted and ported into Ruby from Apache-commons
|
20
|
+
# Math library's NelderMead.java file. Therefore this file is under
|
21
|
+
# Apache License Version 2.
|
22
|
+
#
|
23
|
+
# Nelder Mead Algorithm for Multidimensional minimization
|
24
|
+
|
25
|
+
require "#{File.expand_path(File.dirname(__FILE__))}/point_value_pair.rb"
|
26
|
+
|
27
|
+
module Minimization
|
28
|
+
|
29
|
+
class DirectSearchMinimizer
|
30
|
+
|
31
|
+
EPSILON_DEFAULT = 1e-6
|
32
|
+
MAX_ITERATIONS_DEFAULT = 1000000
|
33
|
+
|
34
|
+
attr_reader :x_minimum
|
35
|
+
attr_reader :f_minimum
|
36
|
+
attr_reader :epsilon
|
37
|
+
|
38
|
+
def initialize(f, start_point, iterate_simplex_ref)
|
39
|
+
@epsilon = EPSILON_DEFAULT
|
40
|
+
# Default number of maximum iterations
|
41
|
+
@max_iterations = MAX_ITERATIONS_DEFAULT
|
42
|
+
# proc which iterates the simplex
|
43
|
+
@iterate_simplex_ref = iterate_simplex_ref
|
44
|
+
@relative_threshold = 100 * @epsilon
|
45
|
+
@absolute_threshold = @epsilon
|
46
|
+
@x_minimum = nil
|
47
|
+
@f_minimum = nil
|
48
|
+
@f = f
|
49
|
+
|
50
|
+
# create and initializ start configurations
|
51
|
+
if @start_configuration == nil
|
52
|
+
# sets the start configuration point as unit
|
53
|
+
self.start_configuration = Array.new(start_point.length) { 1.0 }
|
54
|
+
end
|
55
|
+
|
56
|
+
@iterations = 0
|
57
|
+
@evaluations = 0
|
58
|
+
# create the simplex for the first time
|
59
|
+
build_simplex(start_point)
|
60
|
+
evaluate_simplex
|
61
|
+
end
|
62
|
+
|
63
|
+
def f(x)
|
64
|
+
return @f.call(x)
|
65
|
+
end
|
66
|
+
|
67
|
+
def iterate_simplex
|
68
|
+
return iterate_simplex_ref.call
|
69
|
+
end
|
70
|
+
|
71
|
+
# increment iteration counter by 1
|
72
|
+
def increment_iterations_counter
|
73
|
+
@iterations += 1
|
74
|
+
raise "iteration limit reached" if @iterations > @max_iterations
|
75
|
+
end
|
76
|
+
|
77
|
+
# compares 2 PointValuePair points
|
78
|
+
def compare(v1, v2)
|
79
|
+
if v1.value == v2.value
|
80
|
+
return 0
|
81
|
+
elsif v1.value > v2.value
|
82
|
+
return 1
|
83
|
+
else
|
84
|
+
return -1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# checks whether the function is converging
|
89
|
+
def converging?
|
90
|
+
# check the convergence in a given direction comparing the previous and current values
|
91
|
+
def point_converged?(previous, current)
|
92
|
+
pre = previous.value
|
93
|
+
curr = current.value
|
94
|
+
diff = (pre - curr).abs
|
95
|
+
size = [pre.abs, curr.abs].max
|
96
|
+
return !((diff <= (size * @relative_threshold)) and (diff <= @absolute_threshold))
|
97
|
+
end
|
98
|
+
|
99
|
+
# returns true if converging is possible atleast in one direction
|
100
|
+
if @iterations > 0
|
101
|
+
# given direction is converged
|
102
|
+
converged = true
|
103
|
+
0.upto(@simplex.length - 1) do |i|
|
104
|
+
converged &= !point_converged?(@previous[i], @simplex[i])
|
105
|
+
end
|
106
|
+
return !converged
|
107
|
+
end
|
108
|
+
|
109
|
+
# if no iterations were done, convergence undefined
|
110
|
+
return true
|
111
|
+
end
|
112
|
+
|
113
|
+
# only the relative position of the n vertices with respect
|
114
|
+
# to the first one are stored
|
115
|
+
def start_configuration=(steps)
|
116
|
+
n = steps.length
|
117
|
+
@start_configuration = Array.new(n) { Array.new(n, 0) }
|
118
|
+
0.upto(n - 1) do |i|
|
119
|
+
vertex_i = @start_configuration[i]
|
120
|
+
0.upto(i) do |j|
|
121
|
+
raise "equals vertices #{j} and #{j+1} in simplex configuration" if steps[j] == 0.0
|
122
|
+
0.upto(j) do |k|
|
123
|
+
vertex_i[k] = steps[k]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Build an initial simplex
|
130
|
+
# == Parameters:
|
131
|
+
# * <tt>start_point</tt>: starting point of the minimization search
|
132
|
+
#
|
133
|
+
def build_simplex(start_point)
|
134
|
+
n = start_point.length
|
135
|
+
raise "dimension mismatch" if n != @start_configuration.length
|
136
|
+
# set first vertex
|
137
|
+
@simplex = Array.new(n+1)
|
138
|
+
@simplex[0] = PointValuePair.new(start_point, Float::NAN)
|
139
|
+
|
140
|
+
# set remaining vertices
|
141
|
+
0.upto(n - 1) do |i|
|
142
|
+
conf_i = @start_configuration[i]
|
143
|
+
vertex_i = Array.new(n)
|
144
|
+
0.upto(n - 1) do |k|
|
145
|
+
vertex_i[k] = start_point[k] + conf_i[k]
|
146
|
+
end
|
147
|
+
@simplex[i + 1] = PointValuePair.new(vertex_i, Float::NAN)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Evaluate all the non-evaluated points of the simplex
|
152
|
+
def evaluate_simplex
|
153
|
+
# evaluate the objective function at all non-evaluated simplex points
|
154
|
+
0.upto(@simplex.length - 1) do |i|
|
155
|
+
vertex = @simplex[i]
|
156
|
+
point = vertex.point
|
157
|
+
if vertex.value.nan?
|
158
|
+
@simplex[i] = PointValuePair.new(point, f(point))
|
159
|
+
end
|
160
|
+
end
|
161
|
+
# sort the simplex from best to worst
|
162
|
+
@simplex.sort!{ |x1, x2| x1.value <=> x2.value }
|
163
|
+
end
|
164
|
+
|
165
|
+
# Replace the worst point of the simplex by a new point
|
166
|
+
# == Parameters:
|
167
|
+
# * <tt>point_value_pair</tt>: point to insert
|
168
|
+
#
|
169
|
+
def replace_worst_point(point_value_pair)
|
170
|
+
n = @simplex.length - 1
|
171
|
+
0.upto(n - 1) do |i|
|
172
|
+
if (compare(@simplex[i], point_value_pair) > 0)
|
173
|
+
point_value_pair, @simplex[i] = @simplex[i], point_value_pair
|
174
|
+
end
|
175
|
+
end
|
176
|
+
@simplex[n] = point_value_pair
|
177
|
+
end
|
178
|
+
|
179
|
+
# Convenience method to minimize
|
180
|
+
# == Parameters:
|
181
|
+
# * <tt>start_point</tt>: Starting points
|
182
|
+
# * <tt>f</tt>: Function to minimize
|
183
|
+
# == Usage:
|
184
|
+
# minimizer=Minimization::NelderMead.minimize(proc{|x| (x[0] - 1) ** 2 + (x[1] - 5) ** 2}, [0, 0])
|
185
|
+
#
|
186
|
+
def self.minimize(f, start_point)
|
187
|
+
min=Minimization::NelderMead.new(f, start_point)
|
188
|
+
while min.converging?
|
189
|
+
min.iterate
|
190
|
+
end
|
191
|
+
return min
|
192
|
+
end
|
193
|
+
|
194
|
+
# Iterate the simplex one step. Use this when iteration needs to be done manually
|
195
|
+
# == Usage:
|
196
|
+
# minimizer=Minimization::NelderMead.new(proc{|x| (x[0] - 1) ** 2 + (x[1] - 5) ** 2}, [0, 0])
|
197
|
+
# while minimizer.converging?
|
198
|
+
# minimizer.Iterate
|
199
|
+
# end
|
200
|
+
# minimizer.x_minimum
|
201
|
+
# minimizer.f_minimum
|
202
|
+
#
|
203
|
+
def iterate
|
204
|
+
# set previous simplex as the current simplex
|
205
|
+
@previous = Array.new(@simplex.length)
|
206
|
+
0.upto(@simplex.length - 1) do |i|
|
207
|
+
point = @simplex[i].point # clone require?
|
208
|
+
@previous[i] = PointValuePair.new(point, f(point))
|
209
|
+
end
|
210
|
+
# iterate simplex
|
211
|
+
iterate_simplex
|
212
|
+
# set results
|
213
|
+
@x_minimum = @simplex[0].point
|
214
|
+
@f_minimum = @simplex[0].value
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# = Nelder Mead Minimizer.
|
219
|
+
# A multidimensional minimization methods.
|
220
|
+
# == Usage.
|
221
|
+
# require 'minimization'
|
222
|
+
# min=Minimization::NelderMead.new(proc {|x| (x[0] - 2)**2 + (x[1] - 5)**2}, [1, 2])
|
223
|
+
# while min.converging?
|
224
|
+
# min.iterate
|
225
|
+
# end
|
226
|
+
# min.x_minimum
|
227
|
+
# min.f_minimum
|
228
|
+
#
|
229
|
+
class NelderMead < DirectSearchMinimizer
|
230
|
+
def initialize(f, start_point)
|
231
|
+
# Reflection coefficient
|
232
|
+
@rho = 1.0
|
233
|
+
# Expansion coefficient
|
234
|
+
@khi = 2.0
|
235
|
+
# Contraction coefficient
|
236
|
+
@gamma = 0.5
|
237
|
+
# Shrinkage coefficient
|
238
|
+
@sigma = 0.5
|
239
|
+
super(f, start_point, proc{iterate_simplex})
|
240
|
+
end
|
241
|
+
|
242
|
+
def iterate_simplex
|
243
|
+
increment_iterations_counter
|
244
|
+
n = @simplex.length - 1
|
245
|
+
# the simplex has n+1 point if dimension is n
|
246
|
+
best = @simplex[0]
|
247
|
+
secondBest = @simplex[n - 1]
|
248
|
+
worst = @simplex[n]
|
249
|
+
x_worst = worst.point
|
250
|
+
centroid = Array.new(n, 0)
|
251
|
+
# compute the centroid of the best vertices
|
252
|
+
# (dismissing the worst point at index n)
|
253
|
+
0.upto(n - 1) do |i|
|
254
|
+
x = @simplex[i].point
|
255
|
+
0.upto(n - 1) do |j|
|
256
|
+
centroid[j] += x[j]
|
257
|
+
end
|
258
|
+
end
|
259
|
+
scaling = 1.0 / n
|
260
|
+
0.upto(n - 1) do |j|
|
261
|
+
centroid[j] *= scaling
|
262
|
+
end
|
263
|
+
xr = Array.new(n)
|
264
|
+
# compute the reflection point
|
265
|
+
0.upto(n - 1) do |j|
|
266
|
+
xr[j] = centroid[j] + @rho * (centroid[j] - x_worst[j])
|
267
|
+
end
|
268
|
+
reflected = PointValuePair.new(xr, f(xr))
|
269
|
+
if ((compare(best, reflected) <= 0) && (compare(reflected, secondBest) < 0))
|
270
|
+
# accept the reflected point
|
271
|
+
replace_worst_point(reflected)
|
272
|
+
elsif (compare(reflected, best) < 0)
|
273
|
+
xe = Array.new(n)
|
274
|
+
# compute the expansion point
|
275
|
+
0.upto(n - 1) do |j|
|
276
|
+
xe[j] = centroid[j] + @khi * (xr[j] - centroid[j])
|
277
|
+
end
|
278
|
+
expanded = PointValuePair.new(xe, f(xe))
|
279
|
+
if (compare(expanded, reflected) < 0)
|
280
|
+
# accept the expansion point
|
281
|
+
replace_worst_point(expanded)
|
282
|
+
else
|
283
|
+
# accept the reflected point
|
284
|
+
replace_worst_point(reflected)
|
285
|
+
end
|
286
|
+
else
|
287
|
+
if (compare(reflected, worst) < 0)
|
288
|
+
xc = Array.new(n)
|
289
|
+
# perform an outside contraction
|
290
|
+
0.upto(n - 1) do |j|
|
291
|
+
xc[j] = centroid[j] + @gamma * (xr[j] - centroid[j])
|
292
|
+
end
|
293
|
+
out_contracted = PointValuePair.new(xc, f(xc))
|
294
|
+
if (compare(out_contracted, reflected) <= 0)
|
295
|
+
# accept the contraction point
|
296
|
+
replace_worst_point(out_contracted)
|
297
|
+
return
|
298
|
+
end
|
299
|
+
else
|
300
|
+
xc = Array.new(n)
|
301
|
+
# perform an inside contraction
|
302
|
+
0.upto(n - 1) do |j|
|
303
|
+
xc[j] = centroid[j] - @gamma * (centroid[j] - x_worst[j])
|
304
|
+
end
|
305
|
+
in_contracted = PointValuePair.new(xc, f(xc))
|
306
|
+
|
307
|
+
if (compare(in_contracted, worst) < 0)
|
308
|
+
# accept the contraction point
|
309
|
+
replace_worst_point(in_contracted)
|
310
|
+
return
|
311
|
+
end
|
312
|
+
end
|
313
|
+
# perform a shrink
|
314
|
+
x_smallest = @simplex[0].point
|
315
|
+
0.upto(@simplex.length - 1) do |i|
|
316
|
+
x = @simplex[i].get_point_clone
|
317
|
+
0.upto(n - 1) do |j|
|
318
|
+
x[j] = x_smallest[j] + @sigma * (x[j] - x_smallest[j])
|
319
|
+
end
|
320
|
+
@simplex[i] = PointValuePair.new(x, Float::NAN)
|
321
|
+
end
|
322
|
+
evaluate_simplex
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Minimization
|
2
|
+
# class which holds the point,value pair
|
3
|
+
class PointValuePair
|
4
|
+
attr_reader :value
|
5
|
+
attr_accessor :value
|
6
|
+
attr_reader :point
|
7
|
+
|
8
|
+
# == Parameters:
|
9
|
+
# * <tt>point</tt>: Coordinates of the point
|
10
|
+
# * <tt>value</tt>: Function value at the point
|
11
|
+
#
|
12
|
+
def initialize(point, value)
|
13
|
+
@point = point.clone
|
14
|
+
@value = value
|
15
|
+
end
|
16
|
+
|
17
|
+
# returns a copy of the point
|
18
|
+
def get_point_clone
|
19
|
+
return @point.clone
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,319 @@
|
|
1
|
+
# = powell.rb -
|
2
|
+
# Minimization- Minimization algorithms on pure Ruby
|
3
|
+
# Copyright (C) 2010 Claudio Bustos
|
4
|
+
#
|
5
|
+
# This program is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU General Public License
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
8
|
+
# of the License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program; if not, write to the Free Software
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
18
|
+
#
|
19
|
+
# This algorith was adopted and ported into Ruby from Apache-commons
|
20
|
+
# Math library's PowellOptimizer.java and
|
21
|
+
# BaseAbstractMultivariateVectorOptimizer.java
|
22
|
+
# files. Therefore this file is under Apache License Version 2.
|
23
|
+
#
|
24
|
+
# Powell's Algorithm for Multidimensional minimization
|
25
|
+
require "#{File.expand_path(File.dirname(__FILE__))}/point_value_pair.rb"
|
26
|
+
require "#{File.expand_path(File.dirname(__FILE__))}/../minimization.rb"
|
27
|
+
|
28
|
+
module Minimization
|
29
|
+
class ConjugateDirectionMinimizer
|
30
|
+
attr_accessor :max_iterations
|
31
|
+
attr_accessor :max_brent_iterations
|
32
|
+
attr_accessor :x_minimum
|
33
|
+
attr_accessor :f_minimum
|
34
|
+
|
35
|
+
# default maximum Powell's iteration value
|
36
|
+
Max_Iterations_Default = 100
|
37
|
+
# default Brent iteration value
|
38
|
+
MAX_BRENT_ITERATION_DEFAULT = 10 # give a suitable value
|
39
|
+
|
40
|
+
def initialize(f, initial_guess, lower_bound, upper_bound)
|
41
|
+
@iterations = 0
|
42
|
+
@max_iterations = Max_Iterations_Default
|
43
|
+
@evaluations = 0
|
44
|
+
@max_brent_iterations = MAX_BRENT_ITERATION_DEFAULT
|
45
|
+
@converging = true
|
46
|
+
|
47
|
+
# set minimizing function
|
48
|
+
@f = f
|
49
|
+
@start = initial_guess
|
50
|
+
@lower_bound = lower_bound
|
51
|
+
@upper_bound = upper_bound
|
52
|
+
|
53
|
+
# set maximum and minimum coordinate value a point can have
|
54
|
+
# while minimization process
|
55
|
+
@min_coordinate_val = lower_bound.min
|
56
|
+
@max_coordinate_val = upper_bound.max
|
57
|
+
|
58
|
+
# validate input parameters
|
59
|
+
check_parameters
|
60
|
+
end
|
61
|
+
|
62
|
+
# return the convergence of the search
|
63
|
+
def converging?
|
64
|
+
return @converging
|
65
|
+
end
|
66
|
+
|
67
|
+
# set minimization function
|
68
|
+
def f(x)
|
69
|
+
@f.call(x)
|
70
|
+
end
|
71
|
+
|
72
|
+
# validate input parameters
|
73
|
+
def check_parameters
|
74
|
+
if (!@start.nil?)
|
75
|
+
dim = @start.length
|
76
|
+
if (!@lower_bound.nil?)
|
77
|
+
# check for dimension mismatches
|
78
|
+
raise "dimension mismatching #{@lower_bound.length} and #{dim}" if @lower_bound.length != dim
|
79
|
+
# check whether start point exeeds the lower bound
|
80
|
+
0.upto(dim - 1) do |i|
|
81
|
+
v = @start[i]
|
82
|
+
lo = @lower_bound[i]
|
83
|
+
raise "start point is lower than lower bound" if v < lo
|
84
|
+
end
|
85
|
+
end
|
86
|
+
if (!@upper_bound.nil?)
|
87
|
+
# check for dimension mismatches
|
88
|
+
raise "dimension mismatching #{@upper_bound.length} and #{dim}" if @upper_bound.length != dim
|
89
|
+
# check whether strating point exceeds the upper bound
|
90
|
+
0.upto(dim - 1) do |i|
|
91
|
+
v = @start[i]
|
92
|
+
hi = @upper_bound[i]
|
93
|
+
raise "start point is higher than the upper bound" if v > hi
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if (@lower_bound.nil?)
|
98
|
+
@lower_bound = Array.new(dim)
|
99
|
+
0.upto(dim - 1) do |i|
|
100
|
+
@lower_bound[i] = Float::INFINITY # eventually this will occur an error
|
101
|
+
end
|
102
|
+
end
|
103
|
+
if (@upper_bound.nil?)
|
104
|
+
@upper_bound = Array.new(dim)
|
105
|
+
0.upto(dim - 1) do |i|
|
106
|
+
@upper_bound[i] = -Float::INFINITY # eventually this will occur an error
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# line minimization using Brent's minimization
|
113
|
+
# == Parameters:
|
114
|
+
# * <tt>point</tt>: Starting point
|
115
|
+
# * <tt>direction</tt>: Search direction
|
116
|
+
#
|
117
|
+
def brent_search(point, direction)
|
118
|
+
n = point.length
|
119
|
+
# Create a proc to minimize using brent search
|
120
|
+
# Function value varies with alpha value and represent a point
|
121
|
+
# of the minimizing function which is on the given plane
|
122
|
+
func = proc{ |alpha|
|
123
|
+
x = Array.new(n)
|
124
|
+
0.upto(n - 1) do |i|
|
125
|
+
# create a point according to the given alpha value
|
126
|
+
x[i] = point[i] + alpha * direction[i]
|
127
|
+
end
|
128
|
+
# return the function value of the obtained point
|
129
|
+
f(x)
|
130
|
+
}
|
131
|
+
|
132
|
+
# create Brent minimizer
|
133
|
+
line_minimizer = Minimization::Brent.new(@min_coordinate_val, @max_coordinate_val, func)
|
134
|
+
# iterate Brent minimizer for given number of iteration value
|
135
|
+
0.upto(@max_brent_iterations) do
|
136
|
+
line_minimizer.iterate
|
137
|
+
end
|
138
|
+
# return the minimum point
|
139
|
+
return {:alpha_min => line_minimizer.x_minimum, :f_val => line_minimizer.f_minimum}
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
# = Powell's Minimizer.
|
145
|
+
# A multidimensional minimization methods
|
146
|
+
# == Usage.
|
147
|
+
# require 'minimization'
|
148
|
+
# f = proc{ |x| (x[0] - 1)**2 + (2*x[1] - 5)**2 + (x[2]-3.3)**2}
|
149
|
+
# min = Minimization::Powell.minimize(f, [1, 2, 3], [0, 0, 0], [5, 5, 5])
|
150
|
+
# min.f_minimum
|
151
|
+
# min.x_minimum
|
152
|
+
#
|
153
|
+
class Powell < ConjugateDirectionMinimizer
|
154
|
+
|
155
|
+
attr_accessor :relative_threshold
|
156
|
+
attr_accessor :absolute_threshold
|
157
|
+
|
158
|
+
# default of relative threshold
|
159
|
+
RELATIVE_THRESHOLD_DEFAULT = 0.1
|
160
|
+
# default of absolute threshold
|
161
|
+
ABSOLUTE_THRESHOLD_DEFAULT =0.1
|
162
|
+
|
163
|
+
# == Parameters:
|
164
|
+
# * <tt>f</tt>: Minimization function
|
165
|
+
# * <tt>initial_guess</tt>: Initial position of Minimization
|
166
|
+
# * <tt>lower_bound</tt>: Lower bound of the minimization
|
167
|
+
# * <tt>upper_bound</tt>: Upper bound of the minimization
|
168
|
+
#
|
169
|
+
def initialize(f, initial_guess, lower_bound, upper_bound)
|
170
|
+
super(f, initial_guess.clone, lower_bound, upper_bound)
|
171
|
+
@relative_threshold = RELATIVE_THRESHOLD_DEFAULT
|
172
|
+
@absolute_threshold = ABSOLUTE_THRESHOLD_DEFAULT
|
173
|
+
end
|
174
|
+
|
175
|
+
# Obtain new point and direction from the previous point,
|
176
|
+
# previous direction and a parameter value
|
177
|
+
# == Parameters:
|
178
|
+
# * <tt>point</tt>: Previous point
|
179
|
+
# * <tt>direction</tt>: Previous direction
|
180
|
+
# * <tt>minimum</tt>: parameter value
|
181
|
+
#
|
182
|
+
def new_point_and_direction(point, direction, minimum)
|
183
|
+
n = point.length
|
184
|
+
new_point = Array.new(n)
|
185
|
+
new_dir = Array.new(n)
|
186
|
+
0.upto(n - 1) do |i|
|
187
|
+
new_dir[i] = direction[i] * minimum
|
188
|
+
new_point[i] = point[i] + new_dir[i]
|
189
|
+
end
|
190
|
+
return {:point => new_point, :dir => new_dir}
|
191
|
+
end
|
192
|
+
|
193
|
+
# Iterate Powell's minimizer one step
|
194
|
+
# == Parameters:
|
195
|
+
# * <tt>f</tt>: Function to minimize
|
196
|
+
# * <tt>starting_point</tt>: starting point
|
197
|
+
# * <tt>lower_bound</tt>: Lowest possible values of each direction
|
198
|
+
# * <tt>upper_bound</tt>: Highest possible values of each direction
|
199
|
+
# == Usage:
|
200
|
+
# minimizer = Minimization::Powell.new(proc{|x| (x[0] - 1)**2 + (x[1] -1)**2},
|
201
|
+
# [0, 0, 0], [-5, -5, -5], [5, 5, 5])
|
202
|
+
# while minimizer.converging?
|
203
|
+
# minimizer.iterate
|
204
|
+
# end
|
205
|
+
# minimizer.x_minimum
|
206
|
+
# minimizer.f_minimum
|
207
|
+
#
|
208
|
+
def iterate
|
209
|
+
@iterations += 1
|
210
|
+
|
211
|
+
# set initial configurations
|
212
|
+
if(@iterations <= 1)
|
213
|
+
guess = @start
|
214
|
+
@n = guess.length
|
215
|
+
# initialize all to 0
|
216
|
+
@direc = Array.new(@n) { Array.new(@n) {0} }
|
217
|
+
0.upto(@n - 1) do |i|
|
218
|
+
# set diagonal values to 1
|
219
|
+
@direc[i][i] = 1
|
220
|
+
end
|
221
|
+
|
222
|
+
@x = guess
|
223
|
+
@f_val = f(@x)
|
224
|
+
@x1 = @x.clone
|
225
|
+
end
|
226
|
+
|
227
|
+
fx = @f_val
|
228
|
+
fx2 = 0
|
229
|
+
delta = 0
|
230
|
+
big_ind = 0
|
231
|
+
alpha_min = 0
|
232
|
+
|
233
|
+
0.upto(@n - 1) do |i|
|
234
|
+
direction = @direc[i].clone
|
235
|
+
fx2 = @f_val
|
236
|
+
# Find line minimum
|
237
|
+
minimum = brent_search(@x, direction)
|
238
|
+
@f_val = minimum[:f_val]
|
239
|
+
alpha_min = minimum[:alpha_min]
|
240
|
+
# Obtain new point and direction
|
241
|
+
new_pnd = new_point_and_direction(@x, direction, alpha_min)
|
242
|
+
new_point = new_pnd[:point]
|
243
|
+
new_dir = new_pnd[:dir]
|
244
|
+
@x = new_point
|
245
|
+
|
246
|
+
if ((fx2 - @f_val) > delta)
|
247
|
+
delta = fx2 - @f_val
|
248
|
+
big_ind = i
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# convergence check
|
253
|
+
@converging = !(2 * (fx - @f_val) <= (@relative_threshold * (fx.abs + @f_val.abs) + @absolute_threshold))
|
254
|
+
|
255
|
+
# storing results
|
256
|
+
if((@f_val < fx))
|
257
|
+
@x_minimum = @x
|
258
|
+
@f_minimum = @f_val
|
259
|
+
else
|
260
|
+
@x_minimum = @x1
|
261
|
+
@f_minimum = fx
|
262
|
+
end
|
263
|
+
|
264
|
+
direction = Array.new(@n)
|
265
|
+
x2 = Array.new(@n)
|
266
|
+
0.upto(@n -1) do |i|
|
267
|
+
direction[i] = @x[i] - @x1[i]
|
268
|
+
x2[i] = 2 * @x[i] - @x1[i]
|
269
|
+
end
|
270
|
+
|
271
|
+
@x1 = @x.clone
|
272
|
+
fx2 = f(x2)
|
273
|
+
|
274
|
+
if (fx > fx2)
|
275
|
+
t = 2 * (fx + fx2 - 2 * @f_val)
|
276
|
+
temp = fx - @f_val - delta
|
277
|
+
t *= temp * temp
|
278
|
+
temp = fx - fx2
|
279
|
+
t -= delta * temp * temp
|
280
|
+
|
281
|
+
if (t < 0.0)
|
282
|
+
minimum = brent_search(@x, direction)
|
283
|
+
@f_val = minimum[:f_val]
|
284
|
+
alpha_min = minimum[:alpha_min]
|
285
|
+
# Obtain new point and direction
|
286
|
+
new_pnd = new_point_and_direction(@x, direction, alpha_min)
|
287
|
+
new_point = new_pnd[:point]
|
288
|
+
new_dir = new_pnd[:dir]
|
289
|
+
@x = new_point
|
290
|
+
|
291
|
+
last_ind = @n - 1
|
292
|
+
@direc[big_ind] = @direc[last_ind]
|
293
|
+
@direc[last_ind] = new_dir
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# Convenience method to minimize
|
299
|
+
# == Parameters:
|
300
|
+
# * <tt>f</tt>: Function to minimize
|
301
|
+
# * <tt>starting_point</tt>: starting point
|
302
|
+
# * <tt>lower_bound</tt>: Lowest possible values of each direction
|
303
|
+
# * <tt>upper_bound</tt>: Highest possible values of each direction
|
304
|
+
# == Usage:
|
305
|
+
# minimizer = Minimization::Powell.minimize(proc{|x| (x[0] - 1)**2 + (x[1] -1)**2},
|
306
|
+
# [0, 0, 0], [-5, -5, -5], [5, 5, 5])
|
307
|
+
# minimizer.x_minimum
|
308
|
+
# minimizer.f_minimum
|
309
|
+
#
|
310
|
+
def self.minimize(f, starting_point, lower_bound, upper_bound)
|
311
|
+
min = Minimization::Powell.new(f, starting_point, lower_bound, upper_bound)
|
312
|
+
while min.converging?
|
313
|
+
min.iterate
|
314
|
+
end
|
315
|
+
return min
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
319
|
+
end
|