mopti 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a94c3e3b4ecd74032308bd83bd3b018f23469b90cee573456638888c88649c72
4
+ data.tar.gz: bd9c811610325eba50c2ba7659fa514ad6c84c3dd6b1ee7559999a08d6928812
5
+ SHA512:
6
+ metadata.gz: 759379528285a342e0228fa6199a1f4babf217e173b8f836b701e3faa5203af8783bf4f97e6d873a1af38d7d915f98e94c1021618d39503f505b341bb46f87b0
7
+ data.tar.gz: 6f2f010305f6b217da688f7ab0b4ed7328b06e97fbc8ccd792ec9f0360448883584652474cb55d59f63ce36d6e4668a39359c58c33dcba194589d09509888352
@@ -0,0 +1,24 @@
1
+ name: Ruby
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+
8
+ runs-on: ubuntu-latest
9
+
10
+ strategy:
11
+ matrix:
12
+ ruby: ['2.4.x', '2.5.x', '2.6.x', '2.7.x']
13
+
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - name: Set up Ruby
17
+ uses: actions/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby }}
20
+ - name: Build and test with Rake
21
+ run: |
22
+ gem install bundler
23
+ bundle install --jobs 4 --retry 3
24
+ bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ *.swp
14
+ *.bundle
15
+ tags
16
+ .DS_Store
17
+ .ruby-version
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,36 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rspec
4
+
5
+ Layout/LineLength:
6
+ Max: 145
7
+ IgnoredPatterns: ['(\A|\s)#']
8
+
9
+ Metrics/ModuleLength:
10
+ Max: 200
11
+
12
+ Metrics/ClassLength:
13
+ Max: 200
14
+
15
+ Metrics/MethodLength:
16
+ Max: 100
17
+
18
+ Metrics/BlockLength:
19
+ Max: 40
20
+ Exclude:
21
+ - 'spec/**/*'
22
+
23
+ Metrics/AbcSize:
24
+ Enabled: false
25
+
26
+ Metrics/CyclomaticComplexity:
27
+ Enabled: false
28
+
29
+ Metrics/PerceivedComplexity:
30
+ Enabled: false
31
+
32
+ Metrics/ParameterLists:
33
+ Enabled: false
34
+
35
+ Naming/MethodParameterName:
36
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ # 0.1.0
2
+ - First release.
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at yoshoku@outlook.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in mopti.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
data/LICENSE.txt ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2020 Atsushi Tatsuma
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the copyright holder nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # Mopti
2
+
3
+ ![Ruby](https://github.com/yoshoku/mopti/workflows/Ruby/badge.svg)
4
+ [![Gem Version](https://badge.fury.io/rb/mopti.svg)](https://badge.fury.io/rb/mopti)
5
+ [![BSD 3-Clause License](https://img.shields.io/badge/License-BSD%203--Clause-orange.svg)](https://github.com/yoshoku/mopti/blob/master/LICENSE.txt)
6
+ [![Documentation](http://img.shields.io/badge/api-reference-blue.svg)](https://yoshoku.github.io/mopti/doc/)
7
+
8
+ Mopti is a multivariate optimization library in Ruby.
9
+ Mopti supports Nelder-Mead simplex method and Scaled Conjugate Gradient.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'mopti'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle install
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install mopti
26
+
27
+ ## Documentation
28
+
29
+ - [Mopti API Documentation](https://yoshoku.github.io/mopti/doc/)
30
+
31
+ ## Usage
32
+
33
+ Example 1. Linear Regression with Nelder-Mead simplex method
34
+
35
+ ```ruby
36
+ require 'numo/narray'
37
+ require 'numo/gnuplot'
38
+ require 'mopti'
39
+
40
+ # Define objective function.
41
+ obj_fnc = proc do |w, x, y|
42
+ n_samples = x.shape[0]
43
+ ((y - x.dot(w))**2).sum.fdiv(n_samples)
44
+ end
45
+
46
+ # Explanatory variables.
47
+ x = Numo::DFloat[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
48
+
49
+ # Dependent variables.
50
+ y = 3 * x + 2
51
+
52
+ # Extend variable for intercept.
53
+ xb = Numo::DFloat.vstack([x, [1] * x.size]).transpose.dup
54
+
55
+ # Optimize parameter vectors.
56
+ res = Mopti::minimize(algorithm: 'Nelder-Mead', fnc: obj_fnc, x_init: Numo::DFloat.zeros(2), args: [xb, y])
57
+
58
+ # Output result.
59
+ pp res
60
+
61
+ a, b = res[:x].to_a
62
+ Numo.gnuplot do
63
+ set(terminal: 'png')
64
+ set(output: 'example1.png')
65
+ plot [[0, 10], [a * x[0] + b, a * x[-1] + b], w: :lines, lw: 1, t: 'parameters'],
66
+ [x, y, pt: 6, ps: 2, t: 'data']
67
+ end
68
+ ```
69
+
70
+ ```
71
+ $ brew install gnuplot
72
+ $ gem install mopti numo-narray numo-gnuplot
73
+ $ ruby example1.rb
74
+ {:x=>Numo::DFloat(view)#shape=[2]
75
+ [3, 2],
76
+ :n_fev=>177,
77
+ :n_iter=>91,
78
+ :fnc=>2.290874014308807e-13}
79
+ ```
80
+
81
+ ![example1](https://user-images.githubusercontent.com/5562409/74737586-79c9db00-5298-11ea-9063-90e4655f878a.png)
82
+
83
+
84
+ ## Development
85
+
86
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
87
+
88
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
89
+
90
+ ## Contributing
91
+
92
+ Bug reports and pull requests are welcome on GitHub at https://github.com/yoshoku/mopti. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/yoshoku/mopti/blob/master/CODE_OF_CONDUCT.md).
93
+
94
+
95
+ ## Code of Conduct
96
+
97
+ Everyone interacting in the Mopti project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/yoshoku/mopti/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/lib/mopti.rb ADDED
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'numo/narray'
4
+
5
+ require 'mopti/version'
6
+ require 'mopti/nelder_mead'
7
+ require 'mopti/scaled_conjugate_gradient'
8
+
9
+ # Mopti is a multivariate optimization library in Ruby.
10
+ module Mopti
11
+ module_function
12
+
13
+ # Perform minization of the objective function.
14
+ #
15
+ # @param algorithm [String] Type of optimizer.
16
+ # - 'SCG': ScaledConjugateGradient
17
+ # - 'Nelder-Mead': NelderMead
18
+ # @return [Hash] Result of optimization.
19
+ def minimize(algorithm:, **args)
20
+ optimizer = case algorithm
21
+ when 'SCG'
22
+ ScaledConjugateGradient.new(**args)
23
+ when 'Nelder-Mead'
24
+ NelderMead.new(**args)
25
+ else
26
+ raise ArgumentError, 'A non-existent algorithm is specified'
27
+ end
28
+ res = nil
29
+ optimizer.each { |params| res = params }
30
+ res
31
+ end
32
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mopti
4
+ # NelderMead is a class that implements multivariate optimization using the Nelder-Mead simplex method.
5
+ #
6
+ # @example
7
+ # require 'numo/narray'
8
+ # require 'mopti'
9
+ #
10
+ # args = Numo::DFloat[2, 3]
11
+ #
12
+ # f = proc { |x, a| (8 * (x - a)**2).sum }
13
+ #
14
+ # x0 = Numo::DFloat.zeros(2)
15
+ #
16
+ # optimizer = Mopti::NelderMead.new(fnc: f, x_init: x0, args: args)
17
+ # result = optimizer.map { |params| params }.last
18
+ #
19
+ # pp result
20
+ #
21
+ # # {:x=>Numo::DFloat(view)#shape=[2]
22
+ # # [2, 3],
23
+ # # :n_fev=>165,
24
+ # # :n_iter=>84,
25
+ # # :fnc=>5.694864987422661e-13}
26
+ #
27
+ # *Reference*
28
+ # 1. F. Gao and L. Han, "Implementing the Nelder-Mead simplex algorithm with adaptive parameters," Computational Optimization and Applications, vol. 51 (1), pp. 259--277, 2012.
29
+ class NelderMead
30
+ include Enumerable
31
+
32
+ # Create a new optimizer with the Nelder-Mead simplex method.
33
+ #
34
+ # @param fnc [Method/Proc] Method for calculating the objective function to be minimized.
35
+ # @param args [Array/Hash] Arguments pass to the 'fnc' and 'jcb'.
36
+ # @param x_init [Numo::NArray] Initial point.
37
+ # @param max_iter [Integer] Maximum number of iterations.
38
+ # If nil is given, max_iter sets to 200 * number of dimensions.
39
+ # @param xtol [Float] Tolerance for termination for the optimal vector norm.
40
+ # @param ftol [Float] Tolerance for termination for the objective function value.
41
+ def initialize(fnc:, args: nil, x_init:, max_iter: nil, xtol: 1e-6, ftol: 1e-6)
42
+ @fnc = fnc
43
+ @args = args
44
+ @x_init = x_init
45
+ @max_iter = max_iter
46
+ @xtol = xtol
47
+ @ftol = ftol
48
+ end
49
+
50
+ # Iteration for optimization.
51
+ #
52
+ # @overload each(&block) -> Object
53
+ # @yield [Hash] { x:, n_fev:, n_jev:, n_iter:, fnc:, jcb: }
54
+ # - x [Numo::NArray] Updated vector by optimization.
55
+ # - n_fev [Interger] Number of calls of the objective function.
56
+ # - n_iter [Integer] Number of iterations.
57
+ # - fnc [Float] Value of the objective function.
58
+ # @return [Enumerator] If block is not given, this method returns Enumerator.
59
+ def each
60
+ return to_enum(__method__) unless block_given?
61
+
62
+ x = @x_init.dup
63
+ n = x.size
64
+ max_iter = @max_iter || 200 * n
65
+
66
+ alpha = 1.0
67
+ beta = n > 1 ? 1 + 2.fdiv(n) : 2.0
68
+ gamma = n > 1 ? 0.75 - 1.fdiv(2 * n) : 0.5
69
+ delta = n > 1 ? 1 - 1.fdiv(n) : 0.5
70
+
71
+ sim = x.class.zeros(n + 1, n)
72
+ sim[0, true] = x
73
+ n.times do |k|
74
+ y = x.dup
75
+ y[k] = y[k] != 0 ? (1 + NON_ZERO_TAU) * y[k] : ZERO_TAU
76
+ sim[k + 1, true] = y
77
+ end
78
+
79
+ fsim = Numo::DFloat.zeros(n + 1)
80
+
81
+ (n + 1).times { |k| fsim[k] = func(sim[k, true]) }
82
+ n_fev = n + 1
83
+
84
+ ind = fsim.sort_index
85
+ fsim = fsim[ind].dup
86
+ sim = sim[ind, true].dup
87
+
88
+ n_iter = 0
89
+ while n_iter < max_iter
90
+ break if ((sim[1..-1, true] - sim[0, true]).abs.flatten.max <= @xtol) && ((fsim[0] - fsim[1..-1]).abs.max <= @ftol)
91
+
92
+ xbar = sim[0...-1, true].sum(0) / n
93
+ xr = xbar + alpha * (xbar - sim[-1, true])
94
+ fr = func(xr)
95
+ n_fev += 1
96
+
97
+ shrink = true
98
+ if fr < fsim[0]
99
+ xe = xbar + beta * (xr - xbar)
100
+ fe = func(xe)
101
+ n_fev += 1
102
+ shrink = false
103
+ if fe < fr
104
+ sim[-1, true] = xe
105
+ fsim[-1] = fe
106
+ else
107
+ sim[-1, true] = xr
108
+ fsim[-1] = fr
109
+ end
110
+ elsif fr < fsim[-2]
111
+ shrink = false
112
+ sim[-1, true] = xr
113
+ fsim[-1] = fr
114
+ elsif fr < fsim[-1]
115
+ xoc = xbar + gamma * (xr - xbar)
116
+ foc = func(xoc)
117
+ n_fev += 1
118
+ if foc <= fr
119
+ shrink = false
120
+ sim[-1, true] = xoc
121
+ fsim[-1] = foc
122
+ end
123
+ else
124
+ xic = xbar - gamma * (xr - xbar)
125
+ fic = func(xic)
126
+ n_fev += 1
127
+ if fic < fsim[-1]
128
+ shrink = false
129
+ sim[-1, true] = xic
130
+ fsim[-1] = fic
131
+ end
132
+ end
133
+
134
+ if shrink
135
+ (1..n).times do |j|
136
+ sim[j, true] = sim[0, true] + delta * (sim[j, true] - sim[0, true])
137
+ fsim[j] = func(sim[j, true])
138
+ n_fev += 1
139
+ end
140
+ end
141
+
142
+ ind = fsim.sort_index
143
+ sim = sim[ind, true].dup
144
+ fsim = fsim[ind].dup
145
+
146
+ n_iter += 1
147
+
148
+ yield({ x: sim[0, true], n_fev: n_fev, n_iter: n_iter, fnc: fsim.min })
149
+ end
150
+ end
151
+
152
+ NON_ZERO_TAU = 0.05
153
+ ZERO_TAU = 0.00025
154
+
155
+ private_constant :NON_ZERO_TAU, :ZERO_TAU
156
+
157
+ private
158
+
159
+ def func(x)
160
+ if @args.is_a?(Hash)
161
+ @fnc.call(x, **@args)
162
+ elsif @args.is_a?(Array)
163
+ @fnc.call(x, *@args)
164
+ elsif @args.nil?
165
+ @fnc.call(x)
166
+ else
167
+ @fnc.call(x, @args)
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mopti
4
+ # ScaledConjugateGradient is a class that implements multivariate optimization using scaled conjugate gradient method.
5
+ #
6
+ # @example
7
+ # # Seek the minimum value of the expression a*u**2 + b*u*v + c*v**2 + d*u + e*v + f for
8
+ # # given values of the parameters and an initial guess (u, v) = (0, 0).
9
+ # # https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_cg.html#scipy.optimize.fmin_cg
10
+ # require 'numo/narray'
11
+ # require 'mopti'
12
+ #
13
+ # args = [2, 3, 7, 8, 9, 10]
14
+ #
15
+ # f = proc do |x, a, b, c, d, e, f|
16
+ # u = x[0]
17
+ # v = x[1]
18
+ # a * u**2 + b * u * v + c * v**2 + d * u + e * v + f
19
+ # end
20
+ #
21
+ # g = proc do |x, a, b, c, d, e, f|
22
+ # u = x[0]
23
+ # v = x[1]
24
+ # gu = 2 * a * u + b * v + d
25
+ # gv = b * u + 2 * c * v + e
26
+ # Numo::DFloat[gu, gv]
27
+ # end
28
+ #
29
+ # x0 = Numo::DFloat.zeros(2)
30
+ #
31
+ # optimizer = Mopti::ScaledConjugateGradient.new(fnc: f, jcb: g, x_init: x0, args: args)
32
+ # result = optimizer.map { |params| params }.last
33
+ #
34
+ # pp result
35
+ #
36
+ # # {:x=>Numo::DFloat#shape=[2]
37
+ # # [-1.80847, -0.25533],
38
+ # # :n_fev=>10,
39
+ # # :n_jev=>18,
40
+ # # :n_iter=>9,
41
+ # # :fnc=>1.6170212789006122,
42
+ # # :jcb=>1.8698188678645777e-07}
43
+ #
44
+ # *Reference*
45
+ # 1. M F. Moller, "A Scaled Conjugate Gradient Algorithm for Fast Supervised Learning," Neural Networks, Vol. 6, pp. 525--533, 1993.
46
+ class ScaledConjugateGradient
47
+ include Enumerable
48
+
49
+ # Create a new optimizer with scaled conjugate gradient.
50
+ #
51
+ # @param fnc [Method/Proc] Method for calculating the objective function to be minimized.
52
+ # @param jcb [Method/Proc] Method for calculating the gradient vector.
53
+ # @param args [Array/Hash] Arguments pass to the 'fnc' and 'jcb'.
54
+ # @param x_init [Numo::NArray] Initial point.
55
+ # @param max_iter [Integer] Maximum number of iterations.
56
+ # @param xtol [Float] Tolerance for termination for the optimal vector norm.
57
+ # @param ftol [Float] Tolerance for termination for the objective function value.
58
+ # @param jtol [Float] Tolerance for termination for the gradient norm.
59
+ def initialize(fnc:, jcb:, args: nil, x_init:, max_iter: 200, xtol: 1e-6, ftol: 1e-8, jtol: 1e-7)
60
+ @fnc = fnc
61
+ @jcb = jcb
62
+ @x_init = x_init
63
+ @args = args
64
+ @max_iter = max_iter
65
+ @ftol = ftol
66
+ @jtol = jtol
67
+ @xtol = xtol
68
+ end
69
+
70
+ # Iteration for optimization.
71
+ #
72
+ # @overload each(&block) -> Object
73
+ # @yield [Hash] { x:, n_fev:, n_jev:, n_iter:, fnc:, jcb: }
74
+ # - x [Numo::NArray] Updated vector by optimization.
75
+ # - n_fev [Interger] Number of calls of the objective function.
76
+ # - n_jev [Integer] Number of calls of the jacobian.
77
+ # - n_iter [Integer] Number of iterations.
78
+ # - fnc [Float] Value of the objective function.
79
+ # - jcb [Numo::Narray] Values of the jacobian
80
+ # @return [Enumerator] If block is not given, this method returns Enumerator.
81
+ def each
82
+ return to_enum(__method__) unless block_given?
83
+
84
+ x = @x_init
85
+ f_prev = func(x)
86
+ n_fev = 1
87
+ f_curr = f_prev
88
+ j_next = jacb(x)
89
+ n_jev = 1
90
+
91
+ j_curr = j_next.dot(j_next)
92
+ j_prev = j_next.dup
93
+ d = -j_next
94
+ success = true
95
+ n_successes = 0
96
+ beta = 1.0
97
+
98
+ n_iter = 0
99
+
100
+ while n_iter < @max_iter
101
+ if success
102
+ mu = d.dot(j_next)
103
+ if mu >= 0.0
104
+ d = -j_next
105
+ mu = d.dot(j_next)
106
+ end
107
+ kappa = d.dot(d)
108
+ break if kappa < 1e-16
109
+
110
+ sigma = SIGMA_INIT / Math.sqrt(kappa)
111
+ x_plus = x + sigma * d
112
+ j_plus = jacb(x_plus)
113
+ n_jev += 1
114
+ theta = d.dot(j_plus - j_next) / sigma
115
+ end
116
+
117
+ delta = theta + beta * kappa
118
+ if delta <= 0
119
+ delta = beta * kappa
120
+ beta -= theta / kappa
121
+ end
122
+ alpha = -mu / delta
123
+
124
+ x_next = x + alpha * d
125
+ f_next = func(x_next)
126
+ n_fev += 1
127
+
128
+ delta = 2 * (f_next - f_prev) / (alpha * mu)
129
+ if delta >= 0
130
+ success = true
131
+ n_successes += 1
132
+ x = x_next
133
+ f_curr = f_next
134
+ else
135
+ success = false
136
+ f_curr = f_prev
137
+ end
138
+
139
+ n_iter += 1
140
+ yield({ x: x, n_fev: n_fev, n_jev: n_jev, n_iter: n_iter, fnc: f_curr, jcb: j_curr })
141
+
142
+ if success
143
+ break if (f_next - f_prev).abs < @ftol
144
+ break if (alpha * d).abs.max < @xtol
145
+
146
+ f_prev = f_next
147
+
148
+ j_prev = j_next
149
+ j_next = jacb(x)
150
+ n_jev += 1
151
+
152
+ j_curr = j_next.dot(j_next)
153
+ break if j_curr <= @jtol
154
+ end
155
+
156
+ beta = [beta * 4, BETA_MAX].min if delta < 0.25
157
+ beta = [beta / 4, BETA_MIN].max if delta > 0.75
158
+
159
+ if n_successes == x.size
160
+ d = -j_next
161
+ beta = 1.0
162
+ n_successes = 0
163
+ elsif success
164
+ gamma = (j_prev - j_next).dot(j_next) / mu
165
+ d = -j_next + gamma * d
166
+ end
167
+ end
168
+ end
169
+
170
+ SIGMA_INIT = 1e-4
171
+ BETA_MIN = 1e-15
172
+ BETA_MAX = 1e+15
173
+
174
+ private_constant :SIGMA_INIT, :BETA_MIN, :BETA_MAX
175
+
176
+ private
177
+
178
+ def func(x)
179
+ if @args.is_a?(Hash)
180
+ @fnc.call(x, **@args)
181
+ elsif @args.is_a?(Array)
182
+ @fnc.call(x, *@args)
183
+ elsif @args.nil?
184
+ @fnc.call(x)
185
+ else
186
+ @fnc.call(x, @args)
187
+ end
188
+ end
189
+
190
+ def jacb(x)
191
+ if @args.is_a?(Hash)
192
+ @jcb.call(x, **@args)
193
+ elsif @args.is_a?(Array)
194
+ @jcb.call(x, *@args)
195
+ elsif @args.nil?
196
+ @jcb.call(x)
197
+ else
198
+ @jcb.call(x, @args)
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mopti
4
+ # The version of Mopti you are using.
5
+ VERSION = '0.1.0'
6
+ end
data/mopti.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ require_relative 'lib/mopti/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'mopti'
5
+ spec.version = Mopti::VERSION
6
+ spec.authors = ['yoshoku']
7
+ spec.email = ['yoshoku@outlook.com']
8
+
9
+ spec.summary = 'Multivariate Optimization Library in Ruby.'
10
+ spec.description = <<~MSG
11
+ Multivariate Optimization Library in Ruby.
12
+ Mopti supports Nelder-Mead simplex method and Scaled Conjugate Gradient.
13
+ MSG
14
+ spec.homepage = 'https://github.com/yoshoku/mopti'
15
+ spec.license = 'BSD-3-Clause'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/yoshoku/mopti'
19
+ spec.metadata['changelog_uri'] = 'https://github.com/yoshoku/mopti/blob/master/CHANGELOG.md'
20
+ spec.metadata['documentation_url'] = 'https://yoshoku.github.io/mopti/doc/'
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ end
27
+ spec.bindir = 'exe'
28
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+
31
+ spec.add_runtime_dependency 'numo-narray', '~> 0.9.1'
32
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mopti
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - yoshoku
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-02-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: numo-narray
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.1
27
+ description: |
28
+ Multivariate Optimization Library in Ruby.
29
+ Mopti supports Nelder-Mead simplex method and Scaled Conjugate Gradient.
30
+ email:
31
+ - yoshoku@outlook.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".github/workflows/build.yml"
37
+ - ".gitignore"
38
+ - ".rspec"
39
+ - ".rubocop.yml"
40
+ - CHANGELOG.md
41
+ - CODE_OF_CONDUCT.md
42
+ - Gemfile
43
+ - LICENSE.txt
44
+ - README.md
45
+ - Rakefile
46
+ - lib/mopti.rb
47
+ - lib/mopti/nelder_mead.rb
48
+ - lib/mopti/scaled_conjugate_gradient.rb
49
+ - lib/mopti/version.rb
50
+ - mopti.gemspec
51
+ homepage: https://github.com/yoshoku/mopti
52
+ licenses:
53
+ - BSD-3-Clause
54
+ metadata:
55
+ homepage_uri: https://github.com/yoshoku/mopti
56
+ source_code_uri: https://github.com/yoshoku/mopti
57
+ changelog_uri: https://github.com/yoshoku/mopti/blob/master/CHANGELOG.md
58
+ documentation_url: https://yoshoku.github.io/mopti/doc/
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.1.2
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: Multivariate Optimization Library in Ruby.
78
+ test_files: []