numo-optimize 0.1.0 → 0.2.0
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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +2 -1
- data/ext/numo/optimize/optimize.c +252 -0
- data/ext/numo/optimize/optimize.h +2 -0
- data/lib/numo/optimize/lbfgsb.rb +38 -0
- data/lib/numo/optimize/nelder_mead.rb +132 -0
- data/lib/numo/optimize/scg.rb +38 -0
- data/lib/numo/optimize/version.rb +1 -1
- data/lib/numo/optimize.rb +46 -61
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e5bf17fec048bdf99094a8885389a99b41460947c008d213e11c371ee0025792
|
4
|
+
data.tar.gz: 50346f22f0db6c04e33b5096f243a234e645fc2690789267d853c73918555a10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99b8f8600c23d3f55695ec9baf9ddeb3e00ff294fe73a6fec3a82f201b25894202dc96f3cc49b0f8455f1a703628c70f873a683351037dcbe7093472ac6fba7b
|
7
|
+
data.tar.gz: e95043ecd9e6b497aeb6fcedfe3fe6c9bcad124d45e5cecd89a7bda4ecf2bdb81041c5400667e0607033f3d61ac3a7b773b57518a5571b425041f1d53c90809a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [[0.2.0](https://github.com/yoshoku/numo-optimize/compare/v0.1.0...v0.2.0)] - 2025-10-03
|
2
|
+
|
3
|
+
- Add the scaled conjugate gradient method for minimization method:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
result = Numo::Optimize.minimize(method: 'SCG', ...)
|
7
|
+
```
|
8
|
+
|
9
|
+
- Add the Nelder-Mead method for minimization method:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
result = Numo::Optimize.minimize(method: 'Nelder-Mead', ...)
|
13
|
+
```
|
14
|
+
|
1
15
|
## [0.1.0] - 2025-10-01
|
2
16
|
|
3
17
|
- First release.
|
data/README.md
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# Numo::Optimize
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/numo-optimize)
|
3
4
|
[](https://github.com/yoshoku/numo-optimize/actions/workflows/main.yml)
|
4
5
|
[](https://github.com/yoshoku/numo-optimize/blob/main/LICENSE.txt)
|
6
|
+
[](https://gemdocs.org/gems/numo-optimize/)
|
5
7
|
|
6
8
|
Numo::Optimize (numo-optimize) provides functions for minimizing objective functions.
|
7
9
|
This gem is based on [Lbfgsb.rb](https://github.com/yoshoku/lbfgsb.rb) and
|
8
10
|
[mopti](https://github.com/yoshoku/mopti) by the same author.
|
9
|
-
As for optimization algorithms, only L-BFGS-B is currently supported.
|
10
11
|
|
11
12
|
Please note that numo-optimize depends on [numo-narray-alt](https://github.com/yoshoku/numo-narray-alt), not Numo::NArray.
|
12
13
|
|
@@ -2,6 +2,235 @@
|
|
2
2
|
|
3
3
|
VALUE rb_mOptimize;
|
4
4
|
VALUE rb_mLbfgsb;
|
5
|
+
VALUE rb_mScg;
|
6
|
+
|
7
|
+
#define SIGMA_INIT 1e-4
|
8
|
+
#define BETA_MIN 1e-15
|
9
|
+
#define BETA_MAX 1e+15
|
10
|
+
|
11
|
+
static VALUE scg_fmin(VALUE self, VALUE fnc, VALUE x_val, VALUE jcb, VALUE args,
|
12
|
+
VALUE xtol_val, VALUE ftol_val, VALUE jtol_val, VALUE maxiter) {
|
13
|
+
F77_int inc = 1;
|
14
|
+
double xtol = NUM2DBL(xtol_val);
|
15
|
+
double ftol = NUM2DBL(ftol_val);
|
16
|
+
double jtol = NUM2DBL(jtol_val);
|
17
|
+
int32_t max_iter = NUM2INT(maxiter);
|
18
|
+
|
19
|
+
if (CLASS_OF(x_val) != numo_cDFloat) {
|
20
|
+
x_val = rb_funcall(numo_cDFloat, rb_intern("cast"), 1, x_val);
|
21
|
+
}
|
22
|
+
if (!RTEST(nary_check_contiguous(x_val))) {
|
23
|
+
x_val = nary_dup(x_val);
|
24
|
+
}
|
25
|
+
narray_t* x_nary = NULL;
|
26
|
+
GetNArray(x_val, x_nary);
|
27
|
+
if (NA_NDIM(x_nary) != 1) {
|
28
|
+
rb_raise(rb_eArgError, "x must be a 1-D array.");
|
29
|
+
return Qnil;
|
30
|
+
}
|
31
|
+
F77_int n = (F77_int)NA_SIZE(x_nary);
|
32
|
+
|
33
|
+
int32_t n_fev = 0;
|
34
|
+
int32_t n_jev = 0;
|
35
|
+
double f_prev = 0.0;
|
36
|
+
double f_curr = 0.0;
|
37
|
+
VALUE j_next_val = Qnil;
|
38
|
+
if (RB_TYPE_P(jcb, T_TRUE)) {
|
39
|
+
VALUE fg_arr = rb_funcall(self, rb_intern("fnc"), 3, fnc, x_val, args);
|
40
|
+
f_prev = NUM2DBL(rb_ary_entry(fg_arr, 0));
|
41
|
+
n_fev = 1;
|
42
|
+
f_curr = f_prev;
|
43
|
+
j_next_val = rb_ary_entry(fg_arr, 1);
|
44
|
+
n_jev = 1;
|
45
|
+
} else {
|
46
|
+
f_prev = NUM2DBL(rb_funcall(self, rb_intern("fnc"), 3, fnc, x_val, args));
|
47
|
+
n_fev = 1;
|
48
|
+
f_curr = f_prev;
|
49
|
+
j_next_val = rb_funcall(self, rb_intern("jcb"), 3, jcb, x_val, args);
|
50
|
+
n_jev = 1;
|
51
|
+
}
|
52
|
+
if (CLASS_OF(j_next_val) != numo_cDFloat) {
|
53
|
+
j_next_val = rb_funcall(numo_cDFloat, rb_intern("cast"), 1, j_next_val);
|
54
|
+
}
|
55
|
+
if (!RTEST(nary_check_contiguous(j_next_val))) {
|
56
|
+
j_next_val = nary_dup(j_next_val);
|
57
|
+
}
|
58
|
+
double* j_next_ptr = (double*)na_get_pointer_for_read(j_next_val);
|
59
|
+
double j_norm = ddot_(&n, j_next_ptr, &inc, j_next_ptr, &inc);
|
60
|
+
VALUE j_prev_val = nary_dup(j_next_val);
|
61
|
+
double* d_vec = ALLOC_N(double, n);
|
62
|
+
for (F77_int i = 0; i < n; i++) {
|
63
|
+
d_vec[i] = -j_next_ptr[i];
|
64
|
+
}
|
65
|
+
|
66
|
+
bool success = true;
|
67
|
+
int32_t n_successes = 0;
|
68
|
+
int32_t n_iter = 0;
|
69
|
+
double mu = 0.0;
|
70
|
+
double kappa = 0.0;
|
71
|
+
double theta = 0.0;
|
72
|
+
double beta = 1.0;
|
73
|
+
double* j_diff_vec = ALLOC_N(double, n);
|
74
|
+
|
75
|
+
while (n_iter < max_iter) {
|
76
|
+
if (success) {
|
77
|
+
j_next_ptr = (double*)na_get_pointer_for_read(j_next_val);
|
78
|
+
mu = ddot_(&n, d_vec, &inc, j_next_ptr, &inc);
|
79
|
+
if (mu >= 0.0) {
|
80
|
+
for (F77_int i = 0; i < n; i++) {
|
81
|
+
d_vec[i] = -j_next_ptr[i];
|
82
|
+
}
|
83
|
+
mu = ddot_(&n, d_vec, &inc, j_next_ptr, &inc);
|
84
|
+
}
|
85
|
+
kappa = ddot_(&n, d_vec, &inc, d_vec, &inc);
|
86
|
+
if (kappa < 1e-16) {
|
87
|
+
break;
|
88
|
+
}
|
89
|
+
|
90
|
+
double sigma = SIGMA_INIT / sqrt(kappa);
|
91
|
+
VALUE x_plus_val = nary_dup(x_val);
|
92
|
+
double* x_plus_ptr = (double*)na_get_pointer_for_read_write(x_plus_val);
|
93
|
+
daxpy_(&n, &sigma, d_vec, &inc, x_plus_ptr, &inc);
|
94
|
+
VALUE j_plus_val = Qnil;
|
95
|
+
if (RB_TYPE_P(jcb, T_TRUE)) {
|
96
|
+
VALUE fg_arr = rb_funcall(self, rb_intern("fnc"), 3, fnc, x_plus_val, args);
|
97
|
+
j_plus_val = rb_ary_entry(fg_arr, 1);
|
98
|
+
n_jev++;
|
99
|
+
} else {
|
100
|
+
j_plus_val = rb_funcall(self, rb_intern("jcb"), 3, jcb, x_plus_val, args);
|
101
|
+
n_jev++;
|
102
|
+
}
|
103
|
+
if (CLASS_OF(j_plus_val) != numo_cDFloat) {
|
104
|
+
j_plus_val = rb_funcall(numo_cDFloat, rb_intern("cast"), 1, j_plus_val);
|
105
|
+
}
|
106
|
+
if (!RTEST(nary_check_contiguous(j_plus_val))) {
|
107
|
+
j_plus_val = nary_dup(j_plus_val);
|
108
|
+
}
|
109
|
+
double* j_plus_ptr = (double*)na_get_pointer_for_read(j_plus_val);
|
110
|
+
for (F77_int i = 0; i < n; i++) {
|
111
|
+
j_diff_vec[i] = j_plus_ptr[i] - j_next_ptr[i];
|
112
|
+
}
|
113
|
+
theta = ddot_(&n, d_vec, &inc, j_diff_vec, &inc);
|
114
|
+
theta /= sigma;
|
115
|
+
RB_GC_GUARD(x_plus_val);
|
116
|
+
RB_GC_GUARD(j_plus_val);
|
117
|
+
}
|
118
|
+
|
119
|
+
double delta = theta + beta * kappa;
|
120
|
+
if (delta <= 0.0) {
|
121
|
+
delta = beta * kappa;
|
122
|
+
beta -= theta / kappa;
|
123
|
+
}
|
124
|
+
double alpha = -mu / delta;
|
125
|
+
|
126
|
+
VALUE x_next_val = nary_dup(x_val);
|
127
|
+
double* x_next_ptr = (double*)na_get_pointer_for_read_write(x_next_val);
|
128
|
+
daxpy_(&n, &alpha, d_vec, &inc, x_next_ptr, &inc);
|
129
|
+
double f_next = 0.0;
|
130
|
+
if (RB_TYPE_P(jcb, T_TRUE)) {
|
131
|
+
VALUE fg_arr = rb_funcall(self, rb_intern("fnc"), 3, fnc, x_next_val, args);
|
132
|
+
f_next = NUM2DBL(rb_ary_entry(fg_arr, 0));
|
133
|
+
n_fev++;
|
134
|
+
} else {
|
135
|
+
f_next = NUM2DBL(rb_funcall(self, rb_intern("fnc"), 3, fnc, x_next_val, args));
|
136
|
+
n_fev++;
|
137
|
+
}
|
138
|
+
|
139
|
+
delta = 2 * (f_next - f_prev) / (alpha * mu);
|
140
|
+
if (delta >= 0.0) {
|
141
|
+
success = true;
|
142
|
+
n_successes++;
|
143
|
+
x_val = nary_dup(x_next_val);
|
144
|
+
f_curr = f_next;
|
145
|
+
} else {
|
146
|
+
success = false;
|
147
|
+
f_curr = f_prev;
|
148
|
+
}
|
149
|
+
|
150
|
+
RB_GC_GUARD(x_next_val);
|
151
|
+
n_iter++;
|
152
|
+
|
153
|
+
if (success) {
|
154
|
+
if (fabs(f_next - f_prev) < ftol) {
|
155
|
+
break;
|
156
|
+
}
|
157
|
+
double err = 0.0;
|
158
|
+
for (F77_int i = 0; i < n; i++) {
|
159
|
+
err = fmax(err, fabs(alpha * d_vec[i]));
|
160
|
+
}
|
161
|
+
if (err < xtol) {
|
162
|
+
break;
|
163
|
+
}
|
164
|
+
|
165
|
+
f_prev = f_next;
|
166
|
+
|
167
|
+
j_prev_val = nary_dup(j_next_val);
|
168
|
+
if (RB_TYPE_P(jcb, T_TRUE)) {
|
169
|
+
VALUE fg_arr = rb_funcall(self, rb_intern("fnc"), 3, fnc, x_val, args);
|
170
|
+
j_next_val = rb_ary_entry(fg_arr, 1);
|
171
|
+
n_jev++;
|
172
|
+
} else {
|
173
|
+
j_next_val = rb_funcall(self, rb_intern("jcb"), 3, jcb, x_val, args);
|
174
|
+
n_jev++;
|
175
|
+
}
|
176
|
+
if (CLASS_OF(j_next_val) != numo_cDFloat) {
|
177
|
+
j_next_val = rb_funcall(numo_cDFloat, rb_intern("cast"), 1, j_next_val);
|
178
|
+
}
|
179
|
+
if (!RTEST(nary_check_contiguous(j_next_val))) {
|
180
|
+
j_next_val = nary_dup(j_next_val);
|
181
|
+
}
|
182
|
+
j_next_ptr = (double*)na_get_pointer_for_read(j_next_val);
|
183
|
+
j_norm = ddot_(&n, j_next_ptr, &inc, j_next_ptr, &inc);
|
184
|
+
if (j_norm <= jtol) {
|
185
|
+
break;
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
if (delta < 0.25) {
|
190
|
+
beta = fmin(beta * 4, BETA_MAX);
|
191
|
+
} else if (delta > 0.75) {
|
192
|
+
beta = fmax(beta / 4, BETA_MIN);
|
193
|
+
}
|
194
|
+
|
195
|
+
j_next_ptr = (double*)na_get_pointer_for_read(j_next_val);
|
196
|
+
if (n_successes == n) {
|
197
|
+
for (F77_int i = 0; i < n; i++) {
|
198
|
+
d_vec[i] = -j_next_ptr[i];
|
199
|
+
}
|
200
|
+
beta = 1.0;
|
201
|
+
n_successes = 0;
|
202
|
+
} else if (success) {
|
203
|
+
double* j_prev_ptr = (double*)na_get_pointer_for_read(j_prev_val);
|
204
|
+
for (F77_int i = 0; i < n; i++) {
|
205
|
+
j_diff_vec[i] = j_prev_ptr[i] - j_next_ptr[i];
|
206
|
+
}
|
207
|
+
double gamma = ddot_(&n, j_diff_vec, &inc, j_next_ptr, &inc);
|
208
|
+
gamma /= mu;
|
209
|
+
for (F77_int i = 0; i < n; i++) {
|
210
|
+
d_vec[i] = -j_next_ptr[i] + gamma * d_vec[i];
|
211
|
+
}
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
xfree(j_diff_vec);
|
216
|
+
xfree(d_vec);
|
217
|
+
|
218
|
+
VALUE ret = rb_hash_new();
|
219
|
+
rb_hash_aset(ret, ID2SYM(rb_intern("task")), Qnil);
|
220
|
+
rb_hash_aset(ret, ID2SYM(rb_intern("x")), x_val);
|
221
|
+
rb_hash_aset(ret, ID2SYM(rb_intern("fnc")), DBL2NUM(f_curr));
|
222
|
+
rb_hash_aset(ret, ID2SYM(rb_intern("jcb")), j_next_val);
|
223
|
+
rb_hash_aset(ret, ID2SYM(rb_intern("n_iter")), INT2NUM(n_iter));
|
224
|
+
rb_hash_aset(ret, ID2SYM(rb_intern("n_fev")), INT2NUM(n_fev));
|
225
|
+
rb_hash_aset(ret, ID2SYM(rb_intern("n_jev")), INT2NUM(n_jev));
|
226
|
+
rb_hash_aset(ret, ID2SYM(rb_intern("success")), n_iter < max_iter ? Qtrue : Qfalse);
|
227
|
+
|
228
|
+
RB_GC_GUARD(x_val);
|
229
|
+
RB_GC_GUARD(j_next_val);
|
230
|
+
RB_GC_GUARD(j_prev_val);
|
231
|
+
|
232
|
+
return ret;
|
233
|
+
}
|
5
234
|
|
6
235
|
static VALUE lbfgsb_fmin(VALUE self, VALUE fnc, VALUE x_val, VALUE jcb, VALUE args, VALUE l_val, VALUE u_val,
|
7
236
|
VALUE nbd_val, VALUE maxcor, VALUE ftol, VALUE gtol, VALUE maxiter, VALUE disp) {
|
@@ -195,6 +424,10 @@ Init_optimize(void) {
|
|
195
424
|
* Document-module: Numo::Optimize::Lbfgsb
|
196
425
|
*/
|
197
426
|
rb_mLbfgsb = rb_define_module_under(rb_mOptimize, "Lbfgsb");
|
427
|
+
/**
|
428
|
+
* Document-module: Numo::Optimize::Scg
|
429
|
+
*/
|
430
|
+
rb_mScg = rb_define_module_under(rb_mOptimize, "Scg");
|
198
431
|
|
199
432
|
#ifdef USE_INT64
|
200
433
|
/* The bit size of fortran integer. */
|
@@ -225,4 +458,23 @@ Init_optimize(void) {
|
|
225
458
|
* @return [Hash{Symbol => Object}]
|
226
459
|
*/
|
227
460
|
rb_define_module_function(rb_mLbfgsb, "fmin", lbfgsb_fmin, 12);
|
461
|
+
/**
|
462
|
+
* Minimize a function using the scaled conjugate gradient algorithm.
|
463
|
+
* This module function is for internal use. It is recommended to use `Numo::Optimize.minimize`.
|
464
|
+
*
|
465
|
+
* References:
|
466
|
+
* - Moller, M F., "A Scaled Conjugate Gradient Algorithm for Fast Supervised Learning," Neural Networks, Vol. 6, pp. 525--533, 1993.
|
467
|
+
*
|
468
|
+
* @overload fmin(fnc, x, jcb, args, xtol, ftol, jtol, maxiter)
|
469
|
+
* @param fnc [Method/Proc]
|
470
|
+
* @param x [Numo::DFloat]
|
471
|
+
* @param jcb [Method/Proc/boolean]
|
472
|
+
* @param args [Object]
|
473
|
+
* @param xtol [Float]
|
474
|
+
* @param ftol [Float]
|
475
|
+
* @param gtol [Float]
|
476
|
+
* @param maxiter [Integer]
|
477
|
+
* @return [Hash{Symbol => Object}]
|
478
|
+
*/
|
479
|
+
rb_define_module_function(rb_mScg, "fmin", scg_fmin, 8);
|
228
480
|
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Numo
|
4
|
+
module Optimize
|
5
|
+
# Lbfgsb module provides functions for minimization using L-BFGS-B algorithm.
|
6
|
+
module Lbfgsb
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# @!visibility private
|
10
|
+
def fnc(fnc, x, args)
|
11
|
+
if args.is_a?(Hash)
|
12
|
+
fnc.call(x, **args)
|
13
|
+
elsif args.is_a?(Array)
|
14
|
+
fnc.call(x, *args)
|
15
|
+
elsif args.nil?
|
16
|
+
fnc.call(x)
|
17
|
+
else
|
18
|
+
fnc.call(x, args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
def jcb(jcb, x, args)
|
24
|
+
if args.is_a?(Hash)
|
25
|
+
jcb.call(x, **args)
|
26
|
+
elsif args.is_a?(Array)
|
27
|
+
jcb.call(x, *args)
|
28
|
+
elsif args.nil?
|
29
|
+
jcb.call(x)
|
30
|
+
else
|
31
|
+
jcb.call(x, args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private_class_method :fnc, :jcb
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Numo
|
4
|
+
module Optimize
|
5
|
+
# NelderMead module provides functions for minimization using the Nelder-Mead simplex algorithm.
|
6
|
+
module NelderMead
|
7
|
+
# @!visibility private
|
8
|
+
ZERO_TAU = 0.00025
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
NONZERO_TAU = 0.05
|
12
|
+
|
13
|
+
module_function
|
14
|
+
|
15
|
+
# Minimize a function using the Nelder-Mead simplex algorithm.
|
16
|
+
# This module function is for internal use. It is recommended to use `Numo::Optimize.minimize`.
|
17
|
+
#
|
18
|
+
# @param f [Method/Proc]
|
19
|
+
# @param x [Numo::DFloat]
|
20
|
+
# @param args [Object]
|
21
|
+
# @param maxiter [Integer]
|
22
|
+
# @param xtol [Float]
|
23
|
+
# @param ftol [Float]
|
24
|
+
def fmin(f, x, args, maxiter = nil, xtol = 1e-6, ftol = 1e-6) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
25
|
+
n = x.size
|
26
|
+
maxiter ||= 200 * n
|
27
|
+
|
28
|
+
alpha = 1.0
|
29
|
+
beta = n > 1 ? 1 + 2.fdiv(n) : 2.0
|
30
|
+
gamma = n > 1 ? 0.75 - 1.fdiv(2 * n) : 0.5
|
31
|
+
delta = n > 1 ? 1 - 1.fdiv(n) : 0.5
|
32
|
+
|
33
|
+
sim = x.class.zeros(n + 1, n)
|
34
|
+
sim[0, true] = x
|
35
|
+
n.times do |k|
|
36
|
+
y = x.dup
|
37
|
+
y[k] = y[k].zero? ? ZERO_TAU : (1 + NONZERO_TAU) * y[k]
|
38
|
+
sim[k + 1, true] = y
|
39
|
+
end
|
40
|
+
|
41
|
+
fsim = Numo::DFloat.zeros(n + 1)
|
42
|
+
|
43
|
+
(n + 1).times { |k| fsim[k] = fnc(f, sim[k, true], args) }
|
44
|
+
n_fev = n + 1
|
45
|
+
|
46
|
+
res = {}
|
47
|
+
|
48
|
+
n_iter = 0
|
49
|
+
while n_iter < maxiter
|
50
|
+
break if ((sim[1..-1,
|
51
|
+
true] - sim[0, true]).abs.flatten.max <= xtol) && ((fsim[0] - fsim[1..]).abs.max <= ftol)
|
52
|
+
|
53
|
+
xbar = sim[0...-1, true].sum(axis: 0) / n
|
54
|
+
xr = xbar + (alpha * (xbar - sim[-1, true]))
|
55
|
+
fr = fnc(f, xr, args)
|
56
|
+
n_fev += 1
|
57
|
+
|
58
|
+
shrink = true
|
59
|
+
if fr < fsim[0]
|
60
|
+
xe = xbar + (beta * (xr - xbar))
|
61
|
+
fe = fnc(f, xe, args)
|
62
|
+
n_fev += 1
|
63
|
+
shrink = false
|
64
|
+
if fe < fr
|
65
|
+
sim[-1, true] = xe
|
66
|
+
fsim[-1] = fe
|
67
|
+
else
|
68
|
+
sim[-1, true] = xr
|
69
|
+
fsim[-1] = fr
|
70
|
+
end
|
71
|
+
elsif fr < fsim[-2]
|
72
|
+
shrink = false
|
73
|
+
sim[-1, true] = xr
|
74
|
+
fsim[-1] = fr
|
75
|
+
elsif fr < fsim[-1]
|
76
|
+
xoc = xbar + (gamma * (xr - xbar))
|
77
|
+
foc = fnc(f, xoc, args)
|
78
|
+
n_fev += 1
|
79
|
+
if foc <= fr
|
80
|
+
shrink = false
|
81
|
+
sim[-1, true] = xoc
|
82
|
+
fsim[-1] = foc
|
83
|
+
end
|
84
|
+
else
|
85
|
+
xic = xbar - (gamma * (xr - xbar))
|
86
|
+
fic = fnc(f, xic, args)
|
87
|
+
n_fev += 1
|
88
|
+
if fic < fsim[-1]
|
89
|
+
shrink = false
|
90
|
+
sim[-1, true] = xic
|
91
|
+
fsim[-1] = fic
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
if shrink
|
96
|
+
(1..n).to_a.each do |j|
|
97
|
+
sim[j, true] = sim[0, true] + (delta * (sim[j, true] - sim[0, true]))
|
98
|
+
fsim[j] = fnc(f, sim[j, true], args)
|
99
|
+
n_fev += 1
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
ind = fsim.sort_index
|
104
|
+
sim = sim[ind, true].dup
|
105
|
+
fsim = fsim[ind].dup
|
106
|
+
|
107
|
+
res[:x] = sim[0, true]
|
108
|
+
res[:fnc] = fsim[0]
|
109
|
+
res[:n_iter] = n_iter
|
110
|
+
res[:n_fev] = n_fev
|
111
|
+
|
112
|
+
n_iter += 1
|
113
|
+
end
|
114
|
+
|
115
|
+
res
|
116
|
+
end
|
117
|
+
|
118
|
+
# @!visibility private
|
119
|
+
def fnc(fnc, x, args)
|
120
|
+
if args.is_a?(Hash)
|
121
|
+
fnc.call(x, **args)
|
122
|
+
elsif args.is_a?(Array)
|
123
|
+
fnc.call(x, *args)
|
124
|
+
elsif args.nil?
|
125
|
+
fnc.call(x)
|
126
|
+
else
|
127
|
+
fnc.call(x, args)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Numo
|
4
|
+
module Optimize
|
5
|
+
# Scg module provides functions for minimization using scaled conjugate gradient (SCG) algorithm.
|
6
|
+
module Scg
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# @!visibility private
|
10
|
+
def fnc(fnc, x, args)
|
11
|
+
if args.is_a?(Hash)
|
12
|
+
fnc.call(x, **args)
|
13
|
+
elsif args.is_a?(Array)
|
14
|
+
fnc.call(x, *args)
|
15
|
+
elsif args.nil?
|
16
|
+
fnc.call(x)
|
17
|
+
else
|
18
|
+
fnc.call(x, args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
def jcb(jcb, x, args)
|
24
|
+
if args.is_a?(Hash)
|
25
|
+
jcb.call(x, **args)
|
26
|
+
elsif args.is_a?(Array)
|
27
|
+
jcb.call(x, *args)
|
28
|
+
elsif args.nil?
|
29
|
+
jcb.call(x)
|
30
|
+
else
|
31
|
+
jcb.call(x, args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private_class_method :fnc, :jcb
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/numo/optimize.rb
CHANGED
@@ -4,44 +4,14 @@ require 'numo/narray'
|
|
4
4
|
|
5
5
|
require_relative 'optimize/version'
|
6
6
|
require_relative 'optimize/optimize'
|
7
|
+
require_relative 'optimize/lbfgsb'
|
8
|
+
require_relative 'optimize/scg'
|
9
|
+
require_relative 'optimize/nelder_mead'
|
7
10
|
|
8
11
|
# Ruby/Numo (NUmerical MOdules)
|
9
12
|
module Numo
|
10
13
|
# Numo::Optimize provides functions for minimizing objective functions.
|
11
14
|
module Optimize
|
12
|
-
# Lbfgsb module provides functions for minimization using L-BFGS-B algorithm.
|
13
|
-
module Lbfgsb
|
14
|
-
module_function
|
15
|
-
|
16
|
-
# @!visibility private
|
17
|
-
def fnc(fnc, x, args)
|
18
|
-
if args.is_a?(Hash)
|
19
|
-
fnc.call(x, **args)
|
20
|
-
elsif args.is_a?(Array)
|
21
|
-
fnc.call(x, *args)
|
22
|
-
elsif args.nil?
|
23
|
-
fnc.call(x)
|
24
|
-
else
|
25
|
-
fnc.call(x, args)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# @!visibility private
|
30
|
-
def jcb(jcb, x, args)
|
31
|
-
if args.is_a?(Hash)
|
32
|
-
jcb.call(x, **args)
|
33
|
-
elsif args.is_a?(Array)
|
34
|
-
jcb.call(x, *args)
|
35
|
-
elsif args.nil?
|
36
|
-
jcb.call(x)
|
37
|
-
else
|
38
|
-
jcb.call(x, args)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
private_class_method :fnc, :jcb
|
43
|
-
end
|
44
|
-
|
45
15
|
module_function
|
46
16
|
|
47
17
|
# Minimize the given function.
|
@@ -50,23 +20,29 @@ module Numo
|
|
50
20
|
# @param x_init [Numo::DFloat] (shape: [n_elements]) Initial point.
|
51
21
|
# @param jcb [Method/Proc/Boolean] Method for calculating the gradient vector.
|
52
22
|
# If true is given, fnc is assumed to return the function value and gardient vector as [f, g] array.
|
53
|
-
# @param method [String] Type of algorithm.
|
23
|
+
# @param method [String] Type of algorithm. 'L-BFGS-B', 'SCG', or 'Nelder-Mead' is available.
|
54
24
|
# @param args [Object] Arguments pass to the 'fnc' and 'jcb'.
|
55
25
|
# @param bounds [Numo::DFloat/Nil] (shape: [n_elements, 2])
|
56
26
|
# \[lower, upper\] bounds for each element x. If nil is given, x is unbounded.
|
27
|
+
# This argument is only used 'L-BFGS-B' method.
|
57
28
|
# @param factr [Float] The iteration will be stop when
|
58
29
|
#
|
59
30
|
# (f^k - f^\{k+1\})/max{|f^k|,|f^\{k+1\}|,1} <= factr * Lbfgsb::DBL_EPSILON
|
60
31
|
#
|
61
32
|
# Typical values for factr: 1e12 for low accuracy; 1e7 for moderate accuracy; 1e1 for extremely high accuracy.
|
33
|
+
# This argument is only used 'L-BFGS-B' method.
|
62
34
|
# @param pgtol [Float] The iteration will be stop when
|
63
35
|
#
|
64
36
|
# max{|pg_i| i = 1, ..., n} <= pgtol
|
65
37
|
#
|
66
38
|
# where pg_i is the ith component of the projected gradient.
|
67
|
-
#
|
39
|
+
# This argument is only used 'L-BFGS-B' method.
|
40
|
+
# @param maxcor [Integer] The maximum number of variable metric corrections used to define the limited memory matrix. This argument is only used 'L-BFGS-B' method.
|
41
|
+
# @param xtol [Float] Tolerance for termination by the change of the optimal vector norm. This argument is used 'SCG' and 'Nelder-Mead' methods.
|
42
|
+
# @param ftol [Float] Tolerance for termination by the change of the objective function value. This argument is used 'SCG' and 'Nelder-Mead' methods
|
43
|
+
# @param jtol [Float] Tolerance for termination by the norm of the gradient vector. This argument is only used 'SCG' method.
|
68
44
|
# @param maxiter [Integer] The maximum number of iterations.
|
69
|
-
# @param verbose [Integer/Nil] If negative value or nil is given, no display output is generated.
|
45
|
+
# @param verbose [Integer/Nil] If negative value or nil is given, no display output is generated. This argument is only used 'L-BFGS-B' method.
|
70
46
|
# @return [Hash] Optimization results; { x:, n_fev:, n_jev:, n_iter:, fnc:, jcb:, task:, success: }
|
71
47
|
# - x [Numo::DFloat] Updated vector by optimization.
|
72
48
|
# - n_fev [Interger] Number of calls of the objective function.
|
@@ -76,35 +52,44 @@ module Numo
|
|
76
52
|
# - jcb [Numo::Narray] Values of the jacobian
|
77
53
|
# - task [String] Description of the cause of the termination.
|
78
54
|
# - success [Boolean] Whether or not the optimization exited successfully.
|
79
|
-
def minimize(fnc:, x_init:, jcb:, method: 'L-BFGS-B', args: nil, bounds: nil, factr: 1e7, pgtol: 1e-5,
|
80
|
-
maxcor: 10, maxiter: 15_000, verbose: nil)
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
55
|
+
def minimize(fnc:, x_init:, jcb:, method: 'L-BFGS-B', args: nil, bounds: nil, factr: 1e7, pgtol: 1e-5,
|
56
|
+
maxcor: 10, xtol: 1e-6, ftol: 1e-8, jtol: 1e-7, maxiter: 15_000, verbose: nil)
|
57
|
+
case method.downcase.delete('-')
|
58
|
+
when 'lbfgsb'
|
59
|
+
n_elements = x_init.size
|
60
|
+
l = Numo::DFloat.zeros(n_elements)
|
61
|
+
u = Numo::DFloat.zeros(n_elements)
|
62
|
+
nbd = if Numo::Optimize::Lbfgsb::SZ_F77_INTEGER == 64
|
63
|
+
Numo::Int64.zeros(n_elements)
|
64
|
+
else
|
65
|
+
Numo::Int32.zeros(n_elements)
|
66
|
+
end
|
89
67
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
68
|
+
unless bounds.nil?
|
69
|
+
n_elements.times do |n|
|
70
|
+
lower = bounds[n, 0]
|
71
|
+
upper = bounds[n, 1]
|
72
|
+
l[n] = lower
|
73
|
+
u[n] = upper
|
74
|
+
if lower.finite? && !upper.finite?
|
75
|
+
nbd[n] = 1
|
76
|
+
elsif lower.finite? && upper.finite?
|
77
|
+
nbd[n] = 2
|
78
|
+
elsif !lower.finite? && upper.finite?
|
79
|
+
nbd[n] = 3
|
80
|
+
end
|
102
81
|
end
|
103
82
|
end
|
104
|
-
end
|
105
83
|
|
106
|
-
|
107
|
-
|
84
|
+
Numo::Optimize::Lbfgsb.fmin(fnc, x_init.dup, jcb, args, l, u, nbd, maxcor,
|
85
|
+
factr, pgtol, maxiter, verbose)
|
86
|
+
when 'neldermead'
|
87
|
+
Numo::Optimize::NelderMead.fmin(fnc, x_init.dup, args, maxiter, xtol, ftol)
|
88
|
+
when 'scg'
|
89
|
+
Numo::Optimize::Scg.fmin(fnc, x_init.dup, jcb, args, xtol, ftol, jtol, maxiter)
|
90
|
+
else
|
91
|
+
raise ArgumentError, "Unknown method: #{method}"
|
92
|
+
end
|
108
93
|
end
|
109
94
|
end
|
110
95
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: numo-optimize
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yoshoku
|
@@ -48,6 +48,9 @@ files:
|
|
48
48
|
- ext/numo/optimize/src/linpack.c
|
49
49
|
- ext/numo/optimize/src/linpack.h
|
50
50
|
- lib/numo/optimize.rb
|
51
|
+
- lib/numo/optimize/lbfgsb.rb
|
52
|
+
- lib/numo/optimize/nelder_mead.rb
|
53
|
+
- lib/numo/optimize/scg.rb
|
51
54
|
- lib/numo/optimize/version.rb
|
52
55
|
homepage: https://github.com/yoshoku/numo-optimize
|
53
56
|
licenses:
|
@@ -56,7 +59,7 @@ metadata:
|
|
56
59
|
homepage_uri: https://github.com/yoshoku/numo-optimize
|
57
60
|
source_code_uri: https://github.com/yoshoku/numo-optimize
|
58
61
|
changelog_uri: https://github.com/yoshoku/numo-optimize/blob/main/CHANGELOG.md
|
59
|
-
documentation_uri: https://gemdocs.org/gems/numo-optimize/0.
|
62
|
+
documentation_uri: https://gemdocs.org/gems/numo-optimize/0.2.0/
|
60
63
|
rubygems_mfa_required: 'true'
|
61
64
|
rdoc_options: []
|
62
65
|
require_paths:
|