dual_number 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 061ec78cf2429f13404c06c8e8a4bdb36af51462
4
+ data.tar.gz: 695f42175ea733d454f68ae949282cecf7ffd96b
5
+ SHA512:
6
+ metadata.gz: c015b6626198929c32cbf8fa8ee9b0d2998db960e751939ba74c0aec214871e7266d2c134603212fb7bff8ab5a5aeb606dc34b0510f1825d42634145a25ca538
7
+ data.tar.gz: a076c9c95b69cc4c1d1e06e58468fd3e28683dc1bb331d03f4d86636729109cbe042b68f9163319b1f644c65fbf368f03ba9f89e2493a13b23c8083173e8ae18
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org/'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,116 @@
1
+ CC0 1.0 Universal
2
+
3
+ Statement of Purpose
4
+
5
+ The laws of most jurisdictions throughout the world automatically confer
6
+ exclusive Copyright and Related Rights (defined below) upon the creator and
7
+ subsequent owner(s) (each and all, an "owner") of an original work of
8
+ authorship and/or a database (each, a "Work").
9
+
10
+ Certain owners wish to permanently relinquish those rights to a Work for the
11
+ purpose of contributing to a commons of creative, cultural and scientific
12
+ works ("Commons") that the public can reliably and without fear of later
13
+ claims of infringement build upon, modify, incorporate in other works, reuse
14
+ and redistribute as freely as possible in any form whatsoever and for any
15
+ purposes, including without limitation commercial purposes. These owners may
16
+ contribute to the Commons to promote the ideal of a free culture and the
17
+ further production of creative, cultural and scientific works, or to gain
18
+ reputation or greater distribution for their Work in part through the use and
19
+ efforts of others.
20
+
21
+ For these and/or other purposes and motivations, and without any expectation
22
+ of additional consideration or compensation, the person associating CC0 with a
23
+ Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24
+ and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25
+ and publicly distribute the Work under its terms, with knowledge of his or her
26
+ Copyright and Related Rights in the Work and the meaning and intended legal
27
+ effect of CC0 on those rights.
28
+
29
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
30
+ protected by copyright and related or neighboring rights ("Copyright and
31
+ Related Rights"). Copyright and Related Rights include, but are not limited
32
+ to, the following:
33
+
34
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
35
+ and translate a Work;
36
+
37
+ ii. moral rights retained by the original author(s) and/or performer(s);
38
+
39
+ iii. publicity and privacy rights pertaining to a person's image or likeness
40
+ depicted in a Work;
41
+
42
+ iv. rights protecting against unfair competition in regards to a Work,
43
+ subject to the limitations in paragraph 4(a), below;
44
+
45
+ v. rights protecting the extraction, dissemination, use and reuse of data in
46
+ a Work;
47
+
48
+ vi. database rights (such as those arising under Directive 96/9/EC of the
49
+ European Parliament and of the Council of 11 March 1996 on the legal
50
+ protection of databases, and under any national implementation thereof,
51
+ including any amended or successor version of such directive); and
52
+
53
+ vii. other similar, equivalent or corresponding rights throughout the world
54
+ based on applicable law or treaty, and any national implementations thereof.
55
+
56
+ 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58
+ unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59
+ and Related Rights and associated claims and causes of action, whether now
60
+ known or unknown (including existing as well as future claims and causes of
61
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
62
+ duration provided by applicable law or treaty (including future time
63
+ extensions), (iii) in any current or future medium and for any number of
64
+ copies, and (iv) for any purpose whatsoever, including without limitation
65
+ commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66
+ the Waiver for the benefit of each member of the public at large and to the
67
+ detriment of Affirmer's heirs and successors, fully intending that such Waiver
68
+ shall not be subject to revocation, rescission, cancellation, termination, or
69
+ any other legal or equitable action to disrupt the quiet enjoyment of the Work
70
+ by the public as contemplated by Affirmer's express Statement of Purpose.
71
+
72
+ 3. Public License Fallback. Should any part of the Waiver for any reason be
73
+ judged legally invalid or ineffective under applicable law, then the Waiver
74
+ shall be preserved to the maximum extent permitted taking into account
75
+ Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76
+ is so judged Affirmer hereby grants to each affected person a royalty-free,
77
+ non transferable, non sublicensable, non exclusive, irrevocable and
78
+ unconditional license to exercise Affirmer's Copyright and Related Rights in
79
+ the Work (i) in all territories worldwide, (ii) for the maximum duration
80
+ provided by applicable law or treaty (including future time extensions), (iii)
81
+ in any current or future medium and for any number of copies, and (iv) for any
82
+ purpose whatsoever, including without limitation commercial, advertising or
83
+ promotional purposes (the "License"). The License shall be deemed effective as
84
+ of the date CC0 was applied by Affirmer to the Work. Should any part of the
85
+ License for any reason be judged legally invalid or ineffective under
86
+ applicable law, such partial invalidity or ineffectiveness shall not
87
+ invalidate the remainder of the License, and in such case Affirmer hereby
88
+ affirms that he or she will not (i) exercise any of his or her remaining
89
+ Copyright and Related Rights in the Work or (ii) assert any associated claims
90
+ and causes of action with respect to the Work, in either case contrary to
91
+ Affirmer's express Statement of Purpose.
92
+
93
+ 4. Limitations and Disclaimers.
94
+
95
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
96
+ surrendered, licensed or otherwise affected by this document.
97
+
98
+ b. Affirmer offers the Work as-is and makes no representations or warranties
99
+ of any kind concerning the Work, express, implied, statutory or otherwise,
100
+ including without limitation warranties of title, merchantability, fitness
101
+ for a particular purpose, non infringement, or the absence of latent or
102
+ other defects, accuracy, or the present or absence of errors, whether or not
103
+ discoverable, all to the greatest extent permissible under applicable law.
104
+
105
+ c. Affirmer disclaims responsibility for clearing rights of other persons
106
+ that may apply to the Work or any use thereof, including without limitation
107
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
108
+ disclaims responsibility for obtaining any necessary consents, permissions
109
+ or other rights required for any use of the Work.
110
+
111
+ d. Affirmer understands and acknowledges that Creative Commons is not a
112
+ party to this document and has no duty or obligation with respect to this
113
+ CC0 or use of the Work.
114
+
115
+ For more information, please see
116
+ <http://creativecommons.org/publicdomain/zero/1.0/>
@@ -0,0 +1,51 @@
1
+ # dual_number
2
+
3
+ This library provides a Ruby implementation of [dual numbers](https://en.wikipedia.org/wiki/Automatic_differentiation), which are useful for forward mode [automatic differentiation](https://en.wikipedia.org/wiki/Automatic_differentiation).
4
+
5
+ It is intended to accompany the article [“Automatic differentiation in Ruby”](http://codon.com/automatic-differentiation-in-ruby).
6
+
7
+ That article explains the details, but here’s a brief demonstration:
8
+
9
+ ```irb
10
+ $ irb -Ilib
11
+ >> require 'dual_number'
12
+ => true
13
+
14
+ >> x = DualNumber(1, 2)
15
+ => (1+2ε)
16
+
17
+ >> y = DualNumber(3, 4)
18
+ => (3+4ε)
19
+
20
+ >> x + y
21
+ => (4+6ε)
22
+
23
+ >> x * y
24
+ => (3+10ε)
25
+
26
+ >> (x + 3) * 4
27
+ => (16+8ε)
28
+
29
+ >> 3 + (4 * x)
30
+ => (7+8ε)
31
+ ```
32
+
33
+ One application of dual numbers is to use the second (“dual”) component to represent the derivative of the first (“real”) component. This lets us find the derivative of a function at a particular value by just passing in a dual number instead of a normal number:
34
+
35
+ ```irb
36
+ >> def distance(time:)
37
+ time * Math.sin(time * time) + 1
38
+ end
39
+ => :distance
40
+
41
+ >> value_and_derivative = distance(time: DualNumber(3, 1))
42
+ => (2.2363554557252696-15.988226228682427ε)
43
+
44
+ >> value_and_derivative.real
45
+ => 2.2363554557252696
46
+
47
+ >> value_and_derivative.dual
48
+ => -15.988226228682427
49
+ ```
50
+
51
+ If you have any questions, please get in touch via [Twitter](http://twitter.com/tomstuart) or [email](mailto:tom@codon.com). If you find any bugs or other problems with the code, please [open an issue](https://github.com/tomstuart/dual_number/issues/new).
@@ -0,0 +1,20 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'dual_number/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'dual_number'
7
+ spec.version = DualNumber::VERSION
8
+ spec.author = 'Tom Stuart'
9
+ spec.email = 'tom@codon.com'
10
+
11
+ spec.summary = 'A Ruby implementation of dual numbers.'
12
+ spec.homepage = 'https://github.com/tomstuart/dual_number'
13
+ spec.license = 'CC0-1.0'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_development_dependency 'rspec', '~> 3.4'
20
+ end
@@ -0,0 +1,129 @@
1
+ class DualNumber
2
+ attr_accessor :real, :dual
3
+ private :real=, :dual=
4
+
5
+ def initialize(real:, dual:)
6
+ self.real = real
7
+ self.dual = dual
8
+ end
9
+
10
+ def to_s
11
+ [real, (dual < 0 ? '-' : '+'), dual.abs, 'ε'].join
12
+ end
13
+
14
+ def inspect
15
+ "(#{to_s})"
16
+ end
17
+
18
+ def ==(other)
19
+ other.instance_of?(DualNumber) && [real, dual] == [other.real, other.dual]
20
+ end
21
+
22
+ def +(argument)
23
+ with_dual_number(argument) do |other|
24
+ DualNumber.new \
25
+ real: real + other.real,
26
+ dual: dual + other.dual
27
+ end
28
+ end
29
+
30
+ def -(argument)
31
+ with_dual_number(argument) do |other|
32
+ DualNumber.new \
33
+ real: real - other.real,
34
+ dual: dual - other.dual
35
+ end
36
+ end
37
+
38
+ def *(argument)
39
+ with_dual_number(argument) do |other|
40
+ DualNumber.new \
41
+ real: real * other.real,
42
+ dual: real * other.dual + dual * other.real
43
+ end
44
+ end
45
+
46
+ def /(argument)
47
+ with_dual_number(argument) do |other|
48
+ DualNumber.new \
49
+ real: real / other.real,
50
+ dual: (dual * other.real - real * other.dual) / (other.real * other.real)
51
+ end
52
+ end
53
+
54
+ def -@
55
+ self * -1
56
+ end
57
+
58
+ def **(power)
59
+ DualNumber.new \
60
+ real: real ** power,
61
+ dual: power * dual * (real ** (power - 1))
62
+ end
63
+
64
+ def coerce(other)
65
+ [DualNumber(other), self]
66
+ end
67
+
68
+ private
69
+
70
+ def with_dual_number(argument)
71
+ yield DualNumber(argument)
72
+ end
73
+ end
74
+
75
+ module Kernel
76
+ def DualNumber(real, dual = 0)
77
+ case real
78
+ when DualNumber
79
+ real
80
+ else
81
+ DualNumber.new(real: real, dual: dual)
82
+ end
83
+ end
84
+ end
85
+
86
+ Math.singleton_class.prepend Module.new {
87
+ def self.def_for_dual_number(method_name, &block)
88
+ define_method method_name do |arg, *args|
89
+ case arg
90
+ when DualNumber
91
+ instance_exec arg, *args, &block
92
+ else
93
+ super arg, *args
94
+ end
95
+ end
96
+ end
97
+
98
+ def_for_dual_number :sin do |x|
99
+ DualNumber.new \
100
+ real: sin(x.real),
101
+ dual: x.dual * cos(x.real)
102
+ end
103
+
104
+ def_for_dual_number :cos do |x|
105
+ DualNumber.new \
106
+ real: cos(x.real),
107
+ dual: -x.dual * sin(x.real)
108
+ end
109
+
110
+ def_for_dual_number :exp do |x|
111
+ DualNumber.new \
112
+ real: exp(x.real),
113
+ dual: exp(x.real) * x.dual
114
+ end
115
+
116
+ def_for_dual_number :log do |x, base = Math::E|
117
+ DualNumber.new \
118
+ real: log(x.real, base),
119
+ dual: x.dual / (x.real * log(base))
120
+ end
121
+
122
+ def_for_dual_number :sqrt do |x|
123
+ DualNumber.new \
124
+ real: sqrt(x.real),
125
+ dual: x.dual / (2 * sqrt(x.real))
126
+ end
127
+ }
128
+
129
+ require 'dual_number/version'
@@ -0,0 +1,3 @@
1
+ class DualNumber
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,290 @@
1
+ require 'dual_number'
2
+
3
+ RSpec.describe 'dual numbers' do
4
+ describe 'constructor (and getters)' do
5
+ context 'with a real argument' do
6
+ let(:result) { DualNumber(2) }
7
+
8
+ it 'creates a dual number' do
9
+ expect(result).to be_a DualNumber
10
+ end
11
+
12
+ it 'uses the real part' do
13
+ expect(result.real).to eq 2
14
+ end
15
+
16
+ it 'uses zero for the dual part' do
17
+ expect(result.dual).to be_zero
18
+ end
19
+ end
20
+
21
+ context 'with real and dual arguments' do
22
+ let(:result) { DualNumber(2, 3) }
23
+
24
+ it 'creates a dual number' do
25
+ expect(result).to be_a DualNumber
26
+ end
27
+
28
+ it 'uses the real part' do
29
+ expect(result.real).to eq 2
30
+ end
31
+
32
+ it 'uses the dual part' do
33
+ expect(result.dual).to eq 3
34
+ end
35
+ end
36
+
37
+ context 'with an argument that’s already a dual number' do
38
+ let(:result) { DualNumber(DualNumber(2, 3)) }
39
+
40
+ it 'creates a dual number' do
41
+ expect(result).to be_a DualNumber
42
+ end
43
+
44
+ it 'uses the real part' do
45
+ expect(result.real).to eq 2
46
+ end
47
+
48
+ it 'uses the dual part' do
49
+ expect(result.dual).to eq 3
50
+ end
51
+ end
52
+ end
53
+
54
+ describe 'string representation' do
55
+ specify { expect(DualNumber(2, 3).to_s).to eq '2+3ε' }
56
+ specify { expect(DualNumber(2, -3).to_s).to eq '2-3ε' }
57
+
58
+ specify { expect(DualNumber(2, 3).inspect).to eq '(2+3ε)' }
59
+ specify { expect(DualNumber(2, -3).inspect).to eq '(2-3ε)' }
60
+ end
61
+
62
+ describe 'equality' do
63
+ context 'when the real and dual parts are equal' do
64
+ it 'returns true' do
65
+ expect(DualNumber(2, 3)).to eq DualNumber(2, 3)
66
+ end
67
+ end
68
+
69
+ context 'when the real parts are not equal' do
70
+ it 'returns false' do
71
+ expect(DualNumber(2, 3)).not_to eq DualNumber(5, 3)
72
+ end
73
+ end
74
+
75
+ context 'when the dual parts are not equal' do
76
+ it 'returns false' do
77
+ expect(DualNumber(2, 3)).not_to eq DualNumber(2, 5)
78
+ end
79
+ end
80
+
81
+ context 'when the other object is not a dual number' do
82
+ it 'returns false' do
83
+ expect(DualNumber(2, 3)).not_to eq 5
84
+ end
85
+ end
86
+ end
87
+
88
+ describe 'arithmetic' do
89
+ describe 'addition' do
90
+ let(:result) { DualNumber(2, 3) + DualNumber(5, 7) }
91
+
92
+ it 'adds the real parts' do
93
+ expect(result.real).to eq 2 + 5
94
+ end
95
+
96
+ it 'adds the dual parts' do
97
+ expect(result.dual).to eq 3 + 7
98
+ end
99
+ end
100
+
101
+ describe 'subtraction' do
102
+ let(:result) { DualNumber(2, 3) - DualNumber(5, 7) }
103
+
104
+ it 'subtracts the real parts' do
105
+ expect(result.real).to eq 2 - 5
106
+ end
107
+
108
+ it 'subtracts the dual parts' do
109
+ expect(result.dual).to eq 3 - 7
110
+ end
111
+ end
112
+
113
+ describe 'multiplication' do
114
+ let(:result) { DualNumber(2, 3) * DualNumber(5, 7) }
115
+
116
+ it 'multiplies the real parts' do
117
+ expect(result.real).to eq 2 * 5
118
+ end
119
+
120
+ it 'multiplies opposite parts and sums them to get the dual part' do
121
+ expect(result.dual).to eq (2 * 7) + (3 * 5)
122
+ end
123
+ end
124
+
125
+ describe 'division' do
126
+ let(:result) { DualNumber(2.0, 3.0) / DualNumber(5.0, 7.0) }
127
+
128
+ it 'divides the real parts' do
129
+ expect(result.real).to be_roughly 2.0 / 5.0
130
+ end
131
+
132
+ it 'multiplies opposite parts, subtracts them and divides by the square of the real divisor to get the dual part' do
133
+ expect(result.dual).to be_roughly ((3.0 * 5.0) - (2.0 * 7.0)) / (5.0 * 5.0)
134
+ end
135
+ end
136
+
137
+ describe 'unary negation' do
138
+ let(:result) { -DualNumber(2, 3) }
139
+
140
+ it 'negates the real part' do
141
+ expect(result.real).to eq(-2)
142
+ end
143
+
144
+ it 'negates the dual part' do
145
+ expect(result.dual).to eq(-3)
146
+ end
147
+ end
148
+
149
+ describe 'exponentiation' do
150
+ let(:result) { DualNumber(2, 3) ** 5 }
151
+
152
+ it 'raises the real part to the power' do
153
+ expect(result.real).to eq 32
154
+ end
155
+
156
+ it 'multiplies the power, the dual part and the real part raised to the previous power to get the dual part' do
157
+ expect(result.dual).to eq 240
158
+ end
159
+ end
160
+
161
+ describe 'automatic coercion' do
162
+ context 'of the left operand' do
163
+ let(:a) { 2.0 }
164
+ let(:b) { DualNumber(3.0, 5.0) }
165
+
166
+ specify { expect(a + b).to eq DualNumber(a) + b }
167
+ specify { expect(a - b).to eq DualNumber(a) - b }
168
+ specify { expect(a * b).to eq DualNumber(a) * b }
169
+ specify { expect(a / b).to eq DualNumber(a) / b }
170
+ end
171
+
172
+ context 'of the right operand' do
173
+ let(:a) { DualNumber(2.0, 3.0) }
174
+ let(:b) { 5.0 }
175
+
176
+ specify { expect(a + b).to eq a + DualNumber(b) }
177
+ specify { expect(a - b).to eq a - DualNumber(b) }
178
+ specify { expect(a * b).to eq a * DualNumber(b) }
179
+ specify { expect(a / b).to eq a / DualNumber(b) }
180
+ end
181
+ end
182
+ end
183
+
184
+ describe 'mathematical functions' do
185
+ describe 'sine' do
186
+ def position_at(time:)
187
+ Math.sin(time)
188
+ end
189
+
190
+ specify { expect(position_at(time: 0)).to be_roughly 0 }
191
+ specify { expect(position_at(time: Math::PI / 2)).to be_roughly 1 }
192
+ specify { expect(position_at(time: Math::PI)).to be_roughly 0 }
193
+ specify { expect(position_at(time: 3 * (Math::PI / 2))).to be_roughly(-1) }
194
+
195
+ specify { expect(position_at(time: DualNumber(0, 1)).real).to be_roughly 0 }
196
+ specify { expect(position_at(time: DualNumber(Math::PI / 2, 1)).real).to be_roughly 1 }
197
+ specify { expect(position_at(time: DualNumber(Math::PI, 1)).real).to be_roughly 0 }
198
+ specify { expect(position_at(time: DualNumber(3 * (Math::PI / 2), 1)).real).to be_roughly(-1) }
199
+
200
+ specify { expect(position_at(time: DualNumber(0, 1)).dual).to be_roughly 1 }
201
+ specify { expect(position_at(time: DualNumber(Math::PI / 2, 1)).dual).to be_roughly 0 }
202
+ specify { expect(position_at(time: DualNumber(Math::PI, 1)).dual).to be_roughly(-1) }
203
+ specify { expect(position_at(time: DualNumber(3 * (Math::PI / 2), 1)).dual).to be_roughly 0 }
204
+ end
205
+
206
+ describe 'cosine' do
207
+ def position_at(time:)
208
+ Math.cos(time)
209
+ end
210
+
211
+ specify { expect(position_at(time: 0)).to be_roughly 1 }
212
+ specify { expect(position_at(time: Math::PI / 2)).to be_roughly 0 }
213
+ specify { expect(position_at(time: Math::PI)).to be_roughly(-1) }
214
+ specify { expect(position_at(time: 3 * (Math::PI / 2))).to be_roughly 0 }
215
+
216
+ specify { expect(position_at(time: DualNumber(0, 1)).real).to be_roughly 1 }
217
+ specify { expect(position_at(time: DualNumber(Math::PI / 2, 1)).real).to be_roughly 0 }
218
+ specify { expect(position_at(time: DualNumber(Math::PI, 1)).real).to be_roughly(-1) }
219
+ specify { expect(position_at(time: DualNumber(3 * (Math::PI / 2), 1)).real).to be_roughly 0 }
220
+
221
+ specify { expect(position_at(time: DualNumber(0, 1)).dual).to be_roughly 0 }
222
+ specify { expect(position_at(time: DualNumber(Math::PI / 2, 1)).dual).to be_roughly(-1) }
223
+ specify { expect(position_at(time: DualNumber(Math::PI, 1)).dual).to be_roughly 0 }
224
+ specify { expect(position_at(time: DualNumber(3 * (Math::PI / 2), 1)).dual).to be_roughly 1 }
225
+ end
226
+
227
+ describe 'natural exponential' do
228
+ def position_at(time:)
229
+ Math.exp(time)
230
+ end
231
+
232
+ specify { expect(position_at(time: 0)).to be_roughly Math::E ** 0 }
233
+ specify { expect(position_at(time: 1)).to be_roughly Math::E ** 1 }
234
+ specify { expect(position_at(time: 2)).to be_roughly Math::E ** 2 }
235
+ specify { expect(position_at(time: 3)).to be_roughly Math::E ** 3 }
236
+
237
+ specify { expect(position_at(time: DualNumber(0, 1)).real).to be_roughly Math::E ** 0 }
238
+ specify { expect(position_at(time: DualNumber(1, 1)).real).to be_roughly Math::E ** 1 }
239
+ specify { expect(position_at(time: DualNumber(2, 1)).real).to be_roughly Math::E ** 2 }
240
+ specify { expect(position_at(time: DualNumber(3, 1)).real).to be_roughly Math::E ** 3 }
241
+
242
+ specify { expect(position_at(time: DualNumber(0, 1)).dual).to be_roughly Math::E ** 0 }
243
+ specify { expect(position_at(time: DualNumber(1, 1)).dual).to be_roughly Math::E ** 1 }
244
+ specify { expect(position_at(time: DualNumber(2, 1)).dual).to be_roughly Math::E ** 2 }
245
+ specify { expect(position_at(time: DualNumber(3, 1)).dual).to be_roughly Math::E ** 3 }
246
+ end
247
+
248
+ describe 'natural logarithm' do
249
+ def position_at(time:)
250
+ Math.log(time)
251
+ end
252
+
253
+ specify { expect(position_at(time: Math::E ** 0)).to be_roughly 0 }
254
+ specify { expect(position_at(time: Math::E ** 1)).to be_roughly 1 }
255
+ specify { expect(position_at(time: Math::E ** 2)).to be_roughly 2 }
256
+ specify { expect(position_at(time: Math::E ** 3)).to be_roughly 3 }
257
+
258
+ specify { expect(position_at(time: DualNumber(Math::E ** 0, 1)).real).to be_roughly 0 }
259
+ specify { expect(position_at(time: DualNumber(Math::E ** 1, 1)).real).to be_roughly 1 }
260
+ specify { expect(position_at(time: DualNumber(Math::E ** 2, 1)).real).to be_roughly 2 }
261
+ specify { expect(position_at(time: DualNumber(Math::E ** 3, 1)).real).to be_roughly 3 }
262
+
263
+ specify { expect(position_at(time: DualNumber(Math::E ** 0, 1)).dual).to be_roughly Math::E ** 0 }
264
+ specify { expect(position_at(time: DualNumber(Math::E ** 1, 1)).dual).to be_roughly Math::E ** -1 }
265
+ specify { expect(position_at(time: DualNumber(Math::E ** 2, 1)).dual).to be_roughly Math::E ** -2 }
266
+ specify { expect(position_at(time: DualNumber(Math::E ** 3, 1)).dual).to be_roughly Math::E ** -3 }
267
+ end
268
+
269
+ describe 'square root' do
270
+ def position_at(time:)
271
+ Math.sqrt(time)
272
+ end
273
+
274
+ specify { expect(position_at(time: 0 ** 2)).to be_roughly 0 }
275
+ specify { expect(position_at(time: 1 ** 2)).to be_roughly 1 }
276
+ specify { expect(position_at(time: 2 ** 2)).to be_roughly 2 }
277
+ specify { expect(position_at(time: 3 ** 2)).to be_roughly 3 }
278
+
279
+ specify { expect(position_at(time: DualNumber(0 ** 2, 1)).real).to be_roughly 0 }
280
+ specify { expect(position_at(time: DualNumber(1 ** 2, 1)).real).to be_roughly 1 }
281
+ specify { expect(position_at(time: DualNumber(2 ** 2, 1)).real).to be_roughly 2 }
282
+ specify { expect(position_at(time: DualNumber(3 ** 2, 1)).real).to be_roughly 3 }
283
+
284
+ specify { expect(position_at(time: DualNumber(0 ** 2, 1)).dual).to eq 1.0 / 0 }
285
+ specify { expect(position_at(time: DualNumber(1 ** 2, 1)).dual).to be_roughly 1.0 / 2 }
286
+ specify { expect(position_at(time: DualNumber(2 ** 2, 1)).dual).to be_roughly 1.0 / 4 }
287
+ specify { expect(position_at(time: DualNumber(3 ** 2, 1)).dual).to be_roughly 1.0 / 6 }
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,10 @@
1
+ RSpec.configure do |config|
2
+ config.disable_monkey_patching!
3
+ config.warnings = true
4
+
5
+ config.include Module.new {
6
+ def be_roughly(expected)
7
+ be_within(1e-10).of(expected)
8
+ end
9
+ }
10
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dual_number
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Stuart
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.4'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.4'
27
+ description:
28
+ email: tom@codon.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - ".gitignore"
34
+ - ".rspec"
35
+ - Gemfile
36
+ - LICENSE
37
+ - README.md
38
+ - dual_number.gemspec
39
+ - lib/dual_number.rb
40
+ - lib/dual_number/version.rb
41
+ - spec/dual_number_spec.rb
42
+ - spec/spec_helper.rb
43
+ homepage: https://github.com/tomstuart/dual_number
44
+ licenses:
45
+ - CC0-1.0
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.5.1
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: A Ruby implementation of dual numbers.
67
+ test_files:
68
+ - spec/dual_number_spec.rb
69
+ - spec/spec_helper.rb