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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f81cd59ec9e2ae660daef989ae28a5e5a075063897598e39a1457e5d5107037
4
- data.tar.gz: 304c618243188c812c8a37a9d7060bf40526c8bce3f306f21f3a75cdfb917532
3
+ metadata.gz: e5bf17fec048bdf99094a8885389a99b41460947c008d213e11c371ee0025792
4
+ data.tar.gz: 50346f22f0db6c04e33b5096f243a234e645fc2690789267d853c73918555a10
5
5
  SHA512:
6
- metadata.gz: 64f1624de1f310cbb58fc390f0108e94dfbfcfd714c6f1c1a012af626bb96bf7da713470bfe076b1b0bf64a7016e5925801e6eb2e5ea014bb669ae16ab5b952d
7
- data.tar.gz: 373769fc7093dc7b429f296a5f4b2361489cede752618ec6c280a612bf13c64bca2fb9d4dd027c0b147a9691f032d41ccb7c498782e850986ec8c485de1233ed
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
+ [![Gem Version](https://badge.fury.io/rb/numo-optimize.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/numo-optimize)
3
4
  [![Build Status](https://github.com/yoshoku/numo-optimize/actions/workflows/main.yml/badge.svg)](https://github.com/yoshoku/numo-optimize/actions/workflows/main.yml)
4
5
  [![BSD 3-Clause License](https://img.shields.io/badge/License-BSD%203--Clause-orange.svg)](https://github.com/yoshoku/numo-optimize/blob/main/LICENSE.txt)
6
+ [![Documentation](https://img.shields.io/badge/api-reference-blue.svg)](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
  }
@@ -2,12 +2,14 @@
2
2
  #define NUMO_OPTIMIZE_H 1
3
3
 
4
4
  #include <float.h>
5
+ #include <stdbool.h>
5
6
 
6
7
  #include <ruby.h>
7
8
 
8
9
  #include <numo/narray.h>
9
10
  #include <numo/template.h>
10
11
 
12
+ #include "src/blas.h"
11
13
  #include "src/lbfgsb.h"
12
14
 
13
15
  #endif /* NUMO_OPTIMIZE_H */
@@ -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
@@ -5,6 +5,6 @@ module Numo
5
5
  # Numo::Optimize provides functions for minimizing objective functions.
6
6
  module Optimize
7
7
  # The version of numo-optimize you install.
8
- VERSION = '0.1.0'
8
+ VERSION = '0.2.0'
9
9
  end
10
10
  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. Now only 'L-BFGS-B' is supported.
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
- # @param maxcor [Integer] The maximum number of variable metric corrections used to define the limited memory matrix.
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, # rubocop:disable Lint/UnusedMethodArgument
80
- maxcor: 10, maxiter: 15_000, verbose: nil)
81
- n_elements = x_init.size
82
- l = Numo::DFloat.zeros(n_elements)
83
- u = Numo::DFloat.zeros(n_elements)
84
- nbd = if Numo::Optimize::Lbfgsb::SZ_F77_INTEGER == 64
85
- Numo::Int64.zeros(n_elements)
86
- else
87
- Numo::Int32.zeros(n_elements)
88
- end
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
- unless bounds.nil?
91
- n_elements.times do |n|
92
- lower = bounds[n, 0]
93
- upper = bounds[n, 1]
94
- l[n] = lower
95
- u[n] = upper
96
- if lower.finite? && !upper.finite?
97
- nbd[n] = 1
98
- elsif lower.finite? && upper.finite?
99
- nbd[n] = 2
100
- elsif !lower.finite? && upper.finite?
101
- nbd[n] = 3
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
- Numo::Optimize::Lbfgsb.fmin(fnc, x_init.dup, jcb, args, l, u, nbd, maxcor,
107
- factr, pgtol, maxiter, verbose)
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.1.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.1.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: