mopti 0.1.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 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: []