minimization 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|