differentiation 0.1.1 → 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: 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.