differentiation 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f929d2f761f2c899c992ee5c1c5298c9309571832e53785b05fa363ee8e7184
4
- data.tar.gz: 83ee712e1b681bbf9ce607ba14e6fdb2bc5b03e5e54fc6345711a94011abba12
3
+ metadata.gz: ac6ecb62757c2673747b141af828086090e2cc42addb0ae5445503908f108f47
4
+ data.tar.gz: 39b075aca09020073e28b34aced45567b41a92bd9ddd755e2de60515ecd4299e
5
5
  SHA512:
6
- metadata.gz: 22129a89a99a38eed921c69e8b84e7da61c6a89e102b9e8090d35faf2e8bc8b81df404f894f3e002fb43f65f8e06a49d30784962ff8039a3b6bee35b222cc0d1
7
- data.tar.gz: 3f1c0c90283778a6a7add7cbbc4b61f07bce594aeb84241a80e0bb56b03345d42ec39b981aa016d9b09c2945221169b5d28b9ecabd78712415d8d717f3547e4f
6
+ metadata.gz: 6971cae699fbcdb58f86d36cb1e92908dec436d270d4f99d04c1480826674dbc016955510bf3172b96a924691c2fb9cdee2f9c895939742f239465ce24100fb3
7
+ data.tar.gz: e7d079380efccc25f93be4331f9e5ee18df5bb78b3f70cfc678ec43720439c77f76639bdff178627f8c99a0557c17a2e80a5a6078af8e6616c769c757c6a3db7
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Tomoyuki Chikanaga
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,24 +1,43 @@
1
1
  # fronzen_string_literal: true
2
2
 
3
+ require "differentiation/dual_number"
4
+
3
5
  module Differentiation
4
6
 
5
7
  def self.differentiable?(o)
6
- # TODO: support Vector and Matrix
7
- o.is_a?(Numeric)
8
+ o.is_a?(Numeric) or (defined?(Matrix) and o.is_a?(Matrix))
8
9
  end
9
10
 
10
- def self.convert_to_dual_number(o, key: nil)
11
+ def self.convert_to_dual_number(o, key: nil, named_variables: {})
11
12
  if o.is_a?(DualNumber)
12
- o
13
+ if key and o.named_variables.empty?
14
+ DualNumber.new(o.n, o.diff, key: key)
15
+ else
16
+ o
17
+ end
13
18
  elsif differentiable?(o)
14
- DualNumber.new(o, lambda{|_key| _key == key ? 1 : 0})
19
+ if defined?(Matrix) and o.is_a?(Matrix)
20
+ if key
21
+ vars = Matrix.build(o.row_size, o.column_size){ nil }
22
+ named_variables = { key => vars }
23
+ end
24
+ Matrix.build(o.row_size, o.column_size) do |i, j|
25
+ v = self.convert_to_dual_number(o[i, j], named_variables: named_variables)
26
+ if key
27
+ vars.__send__(:[]=, i, j, v)
28
+ end
29
+ v
30
+ end
31
+ else
32
+ DualNumber.new(o, key: key, named_variables: named_variables)
33
+ end
15
34
  else
16
35
  o
17
36
  end
18
37
  end
19
38
 
20
39
  def self.differential(f)
21
- unless f.is_a?(Proc) or f.is_a?(Method)
40
+ unless f.is_a?(Proc) or f.is_a?(Method) or f.is_a?(UnboundMethod)
22
41
  raise TypeError, "Only Proc or Method can be differentiable"
23
42
  end
24
43
 
@@ -35,6 +54,9 @@ module Differentiation
35
54
  end
36
55
  end
37
56
  f_prime = lambda {|*args, **kwargs|
57
+ if f.is_a?(UnboundMethod)
58
+ f = f.bind(self)
59
+ end
38
60
  args.map!.with_index do |a, i|
39
61
  Differentiation.convert_to_dual_number(a, key: positional[i])
40
62
  end
@@ -46,119 +68,9 @@ module Differentiation
46
68
  end
47
69
  }
48
70
  end
49
-
50
- class DualNumber
51
- include Comparable
52
-
53
- def initialize(n, diff=lambda{|_| 0})
54
- @n = n
55
- @diff = diff
56
- end
57
-
58
- attr_reader :n, :diff
59
-
60
- def derivative(key)
61
- @diff.call(key)
62
- end
63
-
64
- def gradients(*keys)
65
- keys.each_with_object({}) do |k, o|
66
- o[k] = @diff.call(k)
67
- end
68
- end
69
-
70
- def to_i
71
- @n.to_i
72
- end
73
-
74
- def to_int
75
- @n.to_int
76
- end
77
-
78
- def to_f
79
- @n.to_f
80
- end
81
-
82
- def coerce(other)
83
- if Differentiation.differentiable?(other)
84
- [DualNumber.new(other), self]
85
- else
86
- super
87
- end
88
- end
89
-
90
- def <=>(other)
91
- if other.is_a?(DualNumber)
92
- @n <=> other.n
93
- else
94
- @n <=> other
95
- end
96
- end
97
-
98
- def +(other)
99
- if other.is_a?(DualNumber)
100
- n = @n + other.n
101
- diff = ->(key) { @diff.call(key) + other.diff.call(key) }
102
- else
103
- n = @n + other
104
- diff = @diff
105
- end
106
- DualNumber.new(n, diff)
107
- end
108
-
109
- def -(other)
110
- if other.is_a?(DualNumber)
111
- n = @n - other.n
112
- diff = ->(key) { @diff.call(key) - other.diff.call(key) }
113
- else
114
- n = @n - other
115
- diff = @diff
116
- end
117
- DualNumber.new(n, diff)
118
- end
119
-
120
- def *(other)
121
- if other.is_a?(DualNumber)
122
- n = @n * other.n
123
- diff = ->(key) { @n * other.diff.call(key) + @diff.call(key) * other.n }
124
- else
125
- n = @n * other
126
- diff = ->(key) { @diff.call(key) * other }
127
- end
128
- DualNumber.new(n, diff)
129
- end
130
-
131
- def /(other)
132
- if other.is_a?(DualNumber)
133
- n = @n / other.n
134
- diff = ->(key) { (@diff.call(key) / other) + (@n * other.diff.call(key)) / (other.n ** 2) }
135
- else
136
- n = @n / other
137
- diff = ->(key) { @diff.call(key) / other }
138
- end
139
- DualNumber.new(n, diff)
140
- end
141
-
142
- def **(other)
143
- if other.is_a?(DualNumber)
144
- n = @n ** other.n
145
- diff = ->(key) { (@n ** other.n) * (other.diff.call(key) * Math.log(@n) + (other.n / @n)) }
146
- else
147
- n = @n ** other
148
- diff = ->(key) { ((@n ** (other-1)) * other) * @diff.call(key) }
149
- end
150
- DualNumber.new(n, diff)
151
- end
152
-
153
- def inspect
154
- if $DEBUG
155
- "<DualNumber: #{@n} >"
156
- else
157
- @n.inspect
158
- end
159
- end
160
- end
161
71
  end
162
72
 
163
73
  require "differentiation/ext/kernel"
74
+ require "differentiation/ext/integer"
75
+ require "differentiation/ext/float"
164
76
 
@@ -0,0 +1,168 @@
1
+ # fronzen_string_literal: true
2
+
3
+ module Differentiation
4
+ class DualNumber
5
+ include Comparable
6
+
7
+ def initialize(n, diff=lambda{|var| var.equal?(self) ? 1.0 : 0.0 }, named_variables: {}, key: nil)
8
+ @n = n
9
+ @diff = diff
10
+ if key
11
+ @named_variables = { key => self }.freeze
12
+ else
13
+ @named_variables = named_variables.dup.freeze
14
+ end
15
+ end
16
+
17
+ attr_reader :n, :diff, :named_variables
18
+
19
+ def derivative(var)
20
+ if var.equal?(self)
21
+ 1.0
22
+ else
23
+ @diff.call(var)
24
+ end
25
+ end
26
+
27
+ def gradients(*keys)
28
+ keys = keys.flatten(1)
29
+ return_hash = false
30
+ if keys.empty?
31
+ return_hash = true
32
+ keys = @named_variables.keys
33
+ vars = @named_variables.values
34
+ else
35
+ vars = keys.map do |k|
36
+ if k.is_a?(DualNumber) or (defined?(::Matrix) and k.is_a?(Matrix))
37
+ k
38
+ else
39
+ @named_variables[k]
40
+ end
41
+ end
42
+ end
43
+ if return_hash
44
+ keys.each_with_object({}) do |k, o|
45
+ v = @named_variables[k]
46
+ if v.nil?
47
+ o[k] = 0.0
48
+ elsif defined?(::Matrix) and v.is_a?(::Matrix)
49
+ o[k] = Matrix.build(v.row_size, v.column_size){|i, j| derivative(v[i, j]) }
50
+ else
51
+ o[k] = derivative(v)
52
+ end
53
+ end
54
+ else
55
+ vars.map do |v|
56
+ if v.nil?
57
+ 0.0
58
+ elsif defined?(::Matrix) and v.is_a?(::Matrix)
59
+ Matrix.build(v.row_size, v.column_size){|i, j| derivative(v[i, j]) }
60
+ else
61
+ derivative(v)
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def to_i
68
+ @n.to_i
69
+ end
70
+
71
+ def to_int
72
+ @n.to_int
73
+ end
74
+
75
+ def to_f
76
+ @n.to_f
77
+ end
78
+
79
+ def coerce(other)
80
+ if Differentiation.differentiable?(other)
81
+ [Differentiation.convert_to_dual_number(other), self]
82
+ else
83
+ super
84
+ end
85
+ end
86
+
87
+ def <=>(other)
88
+ if other.is_a?(DualNumber)
89
+ @n <=> other.n
90
+ else
91
+ @n <=> other
92
+ end
93
+ end
94
+
95
+ def +(other)
96
+ if other.is_a?(DualNumber)
97
+ n = @n + other.n
98
+ diff = ->(var) { self.derivative(var) + other.derivative(var) }
99
+ named_variables = @named_variables.merge(other.named_variables)
100
+ else
101
+ n = @n + other
102
+ diff = @diff
103
+ named_variables = @named_variables
104
+ end
105
+ DualNumber.new(n, diff, named_variables: named_variables)
106
+ end
107
+
108
+ def -(other)
109
+ if other.is_a?(DualNumber)
110
+ n = @n - other.n
111
+ diff = ->(var) { self.derivative(var) - other.derivative(var) }
112
+ named_variables = @named_variables.merge(other.named_variables)
113
+ else
114
+ n = @n - other
115
+ diff = @diff
116
+ named_variables = @named_variables
117
+ end
118
+ DualNumber.new(n, diff, named_variables: named_variables)
119
+ end
120
+
121
+ def *(other)
122
+ if other.is_a?(DualNumber)
123
+ n = @n * other.n
124
+ diff = ->(var) { @n * other.derivative(var) + self.derivative(var) * other.n }
125
+ named_variables = @named_variables.merge(other.named_variables)
126
+ else
127
+ n = @n * other
128
+ diff = ->(var) { self.derivative(var) * other }
129
+ named_variables = @named_variables
130
+ end
131
+ DualNumber.new(n, diff, named_variables: named_variables)
132
+ end
133
+
134
+ def /(other)
135
+ if other.is_a?(DualNumber)
136
+ n = @n / other.n
137
+ diff = ->(var) { (self.derivative(var) / other) + (@n * other.derivative(var)) / (other.n ** 2) }
138
+ named_variables = @named_variables.merge(other.named_variables)
139
+ else
140
+ n = @n / other
141
+ diff = ->(var) { self.derivative(var) / other }
142
+ named_variables = @named_variables
143
+ end
144
+ DualNumber.new(n, diff, named_variables: named_variables)
145
+ end
146
+
147
+ def **(other)
148
+ if other.is_a?(DualNumber)
149
+ n = @n ** other.n
150
+ diff = ->(var) { (@n ** other.n) * (other.derivative(var) * Math.log(@n) + (other.n / @n)) }
151
+ named_variables = @named_variables.merge(other.named_variables)
152
+ else
153
+ n = @n ** other
154
+ diff = ->(var) { ((@n ** (other-1)) * other) * self.derivative(var) }
155
+ named_variables = @named_variables
156
+ end
157
+ DualNumber.new(n, diff, named_variables: named_variables)
158
+ end
159
+
160
+ def inspect
161
+ if $DEBUG
162
+ "<DualNumber: #{@n} >"
163
+ else
164
+ @n.inspect
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Float
4
+ def to_dual_number(key: nil)
5
+ Differentiation.convert_to_dual_number(self, key: key)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Integer
4
+ def to_dual_number(key: nil)
5
+ Differentiation.convert_to_dual_number(self, key: key)
6
+ end
7
+ end
@@ -3,11 +3,15 @@
3
3
  module Kernel
4
4
  def differential(m)
5
5
  if m.is_a?(Symbol)
6
- f = self.method(m)
7
- elsif m.is_a?(Proc) or m.is_a?(Method)
6
+ if self.is_a?(Module)
7
+ f = self.instance_method(m)
8
+ else
9
+ f = self.method(m)
10
+ end
11
+ elsif m.is_a?(Proc) or m.is_a?(Method) or m.is_a?(UnboundMethod)
8
12
  f = m
9
13
  else
10
- raise TypeError, "differential requires Method/Proc/Symbol argument."
14
+ raise TypeError, "differential requires Method/UnboundMethod/Proc/Symbol argument."
11
15
  end
12
16
  f = Differentiation.differential(f)
13
17
  if m.is_a?(Symbol)
@@ -1,3 +1,3 @@
1
1
  module Differentiation
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: differentiation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - nagachika
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-03-31 00:00:00.000000000 Z
11
+ date: 2019-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -62,12 +62,16 @@ extra_rdoc_files: []
62
62
  files:
63
63
  - ".gitignore"
64
64
  - Gemfile
65
+ - LICENSE
65
66
  - README.md
66
67
  - Rakefile
67
68
  - bin/console
68
69
  - bin/setup
69
70
  - differentiation.gemspec
70
71
  - lib/differentiation.rb
72
+ - lib/differentiation/dual_number.rb
73
+ - lib/differentiation/ext/float.rb
74
+ - lib/differentiation/ext/integer.rb
71
75
  - lib/differentiation/ext/kernel.rb
72
76
  - lib/differentiation/version.rb
73
77
  homepage: https://github.com/nagachika/differentiation
@@ -89,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
93
  version: '0'
90
94
  requirements: []
91
95
  rubyforge_project:
92
- rubygems_version: 2.7.6.2
96
+ rubygems_version: 2.7.6
93
97
  signing_key:
94
98
  specification_version: 4
95
99
  summary: Make Ruby Differentiable.