quaternion_c2 0.1.1 → 0.1.2

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
  SHA1:
3
- metadata.gz: 11a23c8bee71f2eac9cdd28569ddbd060e1c78d6
4
- data.tar.gz: 2da47444cd5b3c7f50da7f9cb0dfc281dcd187a1
3
+ metadata.gz: 6990b36ad863bcfeb0d147530f06461f634b13cc
4
+ data.tar.gz: 04be92b14b2149067953ac61c73151aafa42edea
5
5
  SHA512:
6
- metadata.gz: 3504d452e7841683a890fb01c8ca3a82ce44c9c334aa3309b50329dd17415a99d021c6d2b56cf0358a5c04d9d09b1aaf44508d030cc75072a32626d4c1a8d1eb
7
- data.tar.gz: 6348e89acb1f9a31923c03d9ea1fac21d10d2a849d55c198fd757021ad35243dce10c411502f4ea4b6e622d331e4e66dfd4b014e46c2774d290320a77507265b
6
+ metadata.gz: 4d62049c41e69f64f427932b91499399e122890472d963e2a2185959c197533f9fdac296301a8dd277866a74f710f6020b019fce30d584ae2f5c874b77c82593
7
+ data.tar.gz: b3dfa3a4691263ad104b5d9fc5669bd39708b1ad55ef680205f305f2012abc6512a6c93ebf93e9bb6ba660e1dc14b70a74b0e7f258da1aee3f02969ecfd4e9b2
data/README.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Quaternion
2
2
 
3
+ This gem provides a `Quaternion` class highly compatible with other numeric classes.
4
+
5
+ The [quaternions](https://en.wikipedia.org/wiki/Quaternion) are an extension of complex numbers.
6
+ They have an important application to calculations of spatial rotations, such as in 3DCG.
7
+
8
+ A quaternion can be represented in several forms:
9
+
10
+ * Four real numbers: `$w + xi + yj + zk$`
11
+ * Two complex numbers: `$a + bj$`
12
+ * Scalar and 3-D vector: `$s + \vec{v}$`
13
+ * Polar form: `$r \exp{(\vec{n} \theta)}$`
14
+
15
+ where i, j, and k are imaginary units which satisfy $i^2 = j^2 = k^2 = ijk = -1$.
16
+ A scalar is a real quaternion and a vector is a pure imaginary quaternion.
17
+
18
+ The multiplication of quaternions is noncommutative unlike complex numbers.
19
+
3
20
  ## Installation
4
21
 
5
22
  Add this line to your application's Gemfile:
@@ -45,6 +62,32 @@ Complex::I * Quaternion::J #=> (0+0i+0j+1k)
45
62
  Quaternion(1, 1, 1, 1) ** 6 #=> (64+0i+0j+0k)
46
63
  ```
47
64
 
65
+ ### Spatial rotations
66
+
67
+ Unit quaternions are often used to calculate 3-D rotations around arbitrary axes.
68
+ They have a compact form, avoid gimbal lock, and easily interpolate between themselves (see [Slerp](https://en.wikipedia.org/wiki/Slerp)).
69
+
70
+ ```ruby
71
+ # theta = 120 * (Math::PI / 180)
72
+ # axis = Vector[1, 1, 1].normalize
73
+ # q = Quaternion.polar(1, theta / 2, axis)
74
+ q = Quaternion(0.5, 0.5, 0.5, 0.5)
75
+ r = Quaternion('2i+3j+4k')
76
+
77
+ r_new = q * r / q #=> (0.0+4.0i+2.0j+3.0k)
78
+ r_new = q * r * q.conj #=> (0.0+4.0i+2.0j+3.0k) # q.abs must be 1
79
+ ```
80
+
81
+ More generally, a pair of unit quaternions represents a [4-D rotation](https://en.wikipedia.org/wiki/Rotations_in_4-dimensional_Euclidean_space).
82
+
83
+ ```ruby
84
+ ql = Quaternion(0.5, 0.5, -0.5, 0.5)
85
+ qr = Quaternion(0.5, -0.5, 0.5, 0.5)
86
+ r = Quaternion('1+2i+3j+4k')
87
+
88
+ r_new = ql * r * qr #=> (-4.0-3.0i-2.0j+1.0k)
89
+ ```
90
+
48
91
  ## Development
49
92
 
50
93
  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.
@@ -2,12 +2,18 @@ require_relative 'base'
2
2
  require_relative 'classification'
3
3
  require_relative 'unary'
4
4
 
5
- #
6
- # Basic arithmetic operators (#+, #-, #*, #/)
7
- # and related methods
8
- #
9
-
10
5
  class Quaternion
6
+ ##
7
+ # Basic arithmetic operators (#+, #-, #*, #/)
8
+ # and related methods
9
+ #
10
+
11
+ ##
12
+ # Performs addition.
13
+ #
14
+ # @param other [Numeric]
15
+ # @return [Quaternion]
16
+ #
11
17
  def +(other)
12
18
  if other.kind_of?(Quaternion)
13
19
  __new__(@a + other.a, @b + other.b)
@@ -19,6 +25,12 @@ class Quaternion
19
25
  end
20
26
  end
21
27
 
28
+ ##
29
+ # Performs subtraction.
30
+ #
31
+ # @param other [Numeric]
32
+ # @return [Quaternion]
33
+ #
22
34
  def -(other)
23
35
  if other.kind_of?(Quaternion)
24
36
  __new__(@a - other.a, @b - other.b)
@@ -30,6 +42,12 @@ class Quaternion
30
42
  end
31
43
  end
32
44
 
45
+ ##
46
+ # Performs multiplication.
47
+ #
48
+ # @param other [Numeric]
49
+ # @return [Quaternion]
50
+ #
33
51
  def *(other)
34
52
  if other.kind_of?(Quaternion)
35
53
  _a = other.a
@@ -43,6 +61,25 @@ class Quaternion
43
61
  end
44
62
  end
45
63
 
64
+ ##
65
+ # @!method quo(other)
66
+ #
67
+ # Performs division.
68
+ #
69
+ # @param other [Numeric]
70
+ # @return [Quaternion]
71
+ # @raise [ZeroDivisionError] if +other+ is exactly zero.
72
+ #
73
+
74
+ ##
75
+ # @!method fdiv(other)
76
+ #
77
+ # Performs division as each part is a float, never returns a float.
78
+ #
79
+ # @param other [Numeric]
80
+ # @return [Quaternion]
81
+ #
82
+
46
83
  [:quo, :fdiv].each do |sym|
47
84
  define_method(sym) do |other|
48
85
  if other.kind_of?(Quaternion)
@@ -58,10 +95,17 @@ class Quaternion
58
95
  alias / quo
59
96
  undef div, %, modulo, remainder, divmod
60
97
 
61
- #
98
+ ##
62
99
  # Conversion
63
100
  #
64
101
 
102
+ ##
103
+ # Performs type conversion.
104
+ #
105
+ # @param other [Numeric]
106
+ # @return [[Quaternion, self]]
107
+ # @raise [TypeError]
108
+ #
65
109
  def coerce(other)
66
110
  if other.kind_of?(Quaternion)
67
111
  [other, self]
@@ -9,12 +9,23 @@ class Quaternion
9
9
  defined_methods = public_instance_methods
10
10
 
11
11
  if defined_methods.include?(:finite?)
12
+ ##
13
+ # Returns true if its magnitude is finite, oterwise returns false.
14
+ #
15
+ # @return [Boolean]
16
+ #
12
17
  def finite?
13
18
  abs.finite?
14
19
  end
15
20
  end
16
21
 
17
22
  if defined_methods.include?(:infinite?)
23
+ ##
24
+ # Returns values corresponding to the value of its magnitude.
25
+ #
26
+ # @return [nil] if finite.
27
+ # @return [+1] if positive infinity.
28
+ #
18
29
  def infinite?
19
30
  abs.infinite?
20
31
  end
@@ -1,7 +1,23 @@
1
+ ##
2
+ # This class +Quaternion+ is an analogue of +Complex+.
3
+ # * A subclass of +Numeric+
4
+ # * Immutable instances
5
+ # * Common / extended methods
6
+ # Please +require 'quaternion_c2'+ to load all functions.
7
+ #
8
+ # +Quaternion+ has many constructors to accept
9
+ # the various representations of a quaternion.
10
+ # It is recommended to use +Kernel.#Quaternion+ and +Quaternion.polar+.
11
+ #
12
+ # @see https://ruby-doc.org/core/Complex.html
13
+ #
1
14
  class Quaternion < Numeric
2
15
  attr_reader :a, :b
3
16
  protected :a, :b
4
17
 
18
+ ##
19
+ # @!visibility private
20
+ #
5
21
  def initialize(a, b)
6
22
  @a = a.to_c
7
23
  @b = b.to_c
@@ -1,31 +1,43 @@
1
1
  require_relative 'base'
2
2
 
3
- #
4
- # Number system classification
5
- # (N <) Z < Q < R < C < H (< ...)
6
- #
7
- # * quaternion? : [H] Quaternion (*undefined*)
8
- # * && complex? : [C] Complex
9
- # * && real? : [R] Float, BigDecimal, Numeric
10
- # * && rational? : [Q] Rational (*undefined*)
11
- # * && integer? : [Z] Fixnum, Bignum, Integer
12
- #
13
-
14
3
  class Numeric
4
+ ##
5
+ # Number system classification
6
+ # (N <) Z < Q < R < C < H (< ...)
7
+ #
8
+ # * quaternion? : [H] Quaternion (*undefined*)
9
+ # * && complex? : [C] Complex
10
+ # * && real? : [R] Float, BigDecimal, Numeric
11
+ # * && rational? : [Q] Rational (*undefined*)
12
+ # * && integer? : [Z] Fixnum, Bignum, Integer
13
+ #
14
+
15
15
  # defined:
16
16
  # * integer? #=> false
17
17
  # * real? #=> true
18
18
 
19
+ ##
20
+ # Returns true.
21
+ #
19
22
  def complex?
20
23
  true
21
24
  end
22
25
  end
23
26
 
24
27
  class Quaternion
28
+ # defined by Numeric:
29
+ # * integer? #=> false
30
+
31
+ ##
32
+ # Returns false.
33
+ #
25
34
  def real?
26
35
  false
27
36
  end
28
37
 
38
+ ##
39
+ # Returns false.
40
+ #
29
41
  def complex?
30
42
  false
31
43
  end
@@ -8,11 +8,22 @@ require_relative 'to_type'
8
8
  require 'matrix'
9
9
 
10
10
  class Quaternion
11
- #
11
+ ##
12
12
  # Constructors
13
13
  #
14
14
 
15
15
  class << self
16
+ ##
17
+ # Returns a quaternion object a+bj, where both a and b are complex (or real).
18
+ #
19
+ # @param a [Complex]
20
+ # @param b [Complex]
21
+ # @return [Quaternion]
22
+ # @raise [TypeError]
23
+ #
24
+ # @example
25
+ # Quaternion.rect(1, Complex::I) #=> (1+0i+0j+1k)
26
+ #
16
27
  def rect(a, b = 0)
17
28
  unless [a, b].all? { |c| c.kind_of?(Numeric) && c.complex? }
18
29
  raise TypeError, 'not a complex'
@@ -21,6 +32,19 @@ class Quaternion
21
32
  end
22
33
  alias rectangular rect
23
34
 
35
+ ##
36
+ # Returns a quaternion object w+xi+yj+zk, where all of w, x, y, and z are real.
37
+ #
38
+ # @param w [Real]
39
+ # @param x [Real]
40
+ # @param y [Real]
41
+ # @param z [Real]
42
+ # @return [Quaternion]
43
+ # @raise [TypeError]
44
+ #
45
+ # @example
46
+ # Quaternion.hrect(1, 2, 3, 4) #=> (1+2i+3j+4k)
47
+ #
24
48
  def hrect(w, x = 0, y = 0, z = 0)
25
49
  a = Complex.rect(w, x)
26
50
  b = Complex.rect(y, z)
@@ -28,8 +52,26 @@ class Quaternion
28
52
  end
29
53
  alias hyperrectangular hrect
30
54
 
31
- # Quaternion.polar(r) == r
32
- # Quaternion.polar(r, theta) == Complex.polar(r, theta)
55
+ ##
56
+ # Returns a quaternion object which denotes the given polar form.
57
+ # The actual angle is recognized as +theta * vector.norm+.
58
+ #
59
+ # @param r [Real] absolute value
60
+ # @param theta [Real] angle in radians
61
+ # @param vector [Enumerable] 3-D vector
62
+ # @return [Quaternion]
63
+ # @raise [TypeError]
64
+ #
65
+ # @example
66
+ # Quaternion.polar(1, Math::PI/3, Vector[1,1,1].normalize)
67
+ # #=> (0.5000000000000001+0.5i+0.5j+0.5k)
68
+ # Quaternion.polar(1, 1, Math::PI/3 * Vector[1,1,1].normalize)
69
+ # #=> (0.4999999999999999+0.5i+0.5j+0.5k)
70
+ #
71
+ # r, theta = -3, -1
72
+ # Quaternion.polar(r) == r #=> true
73
+ # Quaternion.polar(r, theta) == Complex.polar(r, theta) #=> true
74
+ #
33
75
  def polar(r, theta = 0, vector = Vector[1, 0, 0])
34
76
  unless vector.kind_of?(Enumerable) && vector.size == 3
35
77
  raise TypeError, 'not a 3-D vector'
@@ -49,25 +91,57 @@ class Quaternion
49
91
  end
50
92
  end
51
93
 
52
- #
94
+ ##
53
95
  # Accessors
54
96
  #
55
97
 
98
+ ##
99
+ # Returns an array of two complex numbers.
100
+ #
101
+ # @return [[Complex, Complex]]
102
+ #
103
+ # @example
104
+ # Quaternion('1+2i+3j+4k').rect #=> [(1+2i), (3+4i)]
105
+ #
56
106
  def rect
57
107
  [@a, @b]
58
108
  end
59
109
  alias rectangular rect
60
110
 
111
+ ##
112
+ # Returns an array of four real numbers.
113
+ #
114
+ # @return [[Real, Real, Real, Real]]
115
+ #
116
+ # @example
117
+ # Quaternion('1+2i+3j+4k').hrect #=> [1, 2, 3, 4]
118
+ #
61
119
  def hrect
62
120
  rect.flat_map(&:rect)
63
121
  end
64
122
  alias hyperrectangular hrect
65
123
 
124
+ ##
125
+ # Returns the real part.
126
+ #
127
+ # @return [Real]
128
+ #
129
+ # @example
130
+ # Quaternion('1+2i+3j+4k').real #=> 1
131
+ #
66
132
  def real
67
133
  @a.real
68
134
  end
69
135
  alias scalar real
70
136
 
137
+ ##
138
+ # Returns the imaginary part as a 3-D vector.
139
+ #
140
+ # @return [Vector] 3-D vector
141
+ #
142
+ # @example
143
+ # Quaternion('1+2i+3j+4k').imag #=> Vector[2, 3, 4]
144
+ #
71
145
  def imag
72
146
  Vector[*hrect.drop(1)]
73
147
  end
@@ -78,6 +152,14 @@ class Quaternion
78
152
  # * abs
79
153
  # * magnitude
80
154
 
155
+ ##
156
+ # Returns the angle part of its polar form.
157
+ #
158
+ # @return [Real]
159
+ #
160
+ # @example
161
+ # Quaternion('1+i+j+k').arg #=> Math::PI/3
162
+ #
81
163
  def arg
82
164
  r_cos = real
83
165
  r_sin = imag.norm
@@ -86,6 +168,14 @@ class Quaternion
86
168
  alias angle arg
87
169
  alias phase arg
88
170
 
171
+ ##
172
+ # Returns the axis part of its polar form.
173
+ #
174
+ # @return [Vector] normalized 3-D vector
175
+ #
176
+ # @example
177
+ # Quaternion('1+i+j+k').axis #=> Vector[1,1,1].normalize
178
+ #
89
179
  def axis
90
180
  v = imag
91
181
  norm = v.norm
@@ -100,24 +190,55 @@ class Quaternion
100
190
  end
101
191
  end
102
192
 
193
+ ##
194
+ # Returns an array; +[q.abs, q.arg, q.axis]+.
195
+ #
196
+ # @return [[Real, Real, Vector]]
197
+ #
198
+ # @example
199
+ # Quaternion('1+i+j+k').polar
200
+ # #=> [2.0, Math::PI/3, Vector[1,1,1].normalize]
201
+ #
103
202
  def polar
104
203
  [abs, arg, axis]
105
204
  end
106
205
  end
107
206
 
108
- #
109
- # Generic constructor
110
- #
111
- # This accepts various arguments:
112
- # * (Numeric) -> real quaternion
113
- # * (Numeric, Numeric) -> a+bj
114
- # * (Numeric, Vector) -> scalar and 3-D vector
115
- # * (Numeric, Numeric, Numeric[, Numeric]) -> w+xi+yj+zk
116
- # and String instead of Numeric.
117
- #
118
207
  module Kernel
119
208
  module_function
120
209
 
210
+ ##
211
+ # Returns a quaternion.
212
+ #
213
+ # This function accepts various arguments except polar form.
214
+ # Strings are parsed to +Numeric+.
215
+ #
216
+ # @overload Quaternion(w, x, y, z)
217
+ # @param w [Numeric]
218
+ # @param x [Numeric]
219
+ # @param y [Numeric]
220
+ # @param z [Numeric]
221
+ # @return [Quaternion] w+xi+yj+zk
222
+ # @overload Quaternion(a, b)
223
+ # @param a [Numeric]
224
+ # @param b [Numeric]
225
+ # @return [Quaternion] a+bj
226
+ # @overload Quaternion(s, v)
227
+ # @param s [Numeric] scalar part
228
+ # @param v [Enumerable] vector part
229
+ # @return [Quaternion] $s+\\vec!{v}$
230
+ # @overload Quaternion(str)
231
+ # @param str [String]
232
+ # @return [Quaternion] +str.to_q+
233
+ # @raise [ArgumentError] if its format is inexact.
234
+ #
235
+ # @example
236
+ # Quaternion(1) #=> (1+0i+0j+0k)
237
+ # Quaternion(1, 2) #=> (1+0i+2j+0k) # not (1+2i+0j+0k)
238
+ # Quaternion(1, 2, 3, 4) #=> (1+2i+3j+4k)
239
+ # Quaternion(1, [2, 3, 4]) #=> (1+2i+3j+4k)
240
+ # Quaternion('1+2i+3j+4k') #=> (1+2i+3j+4k)
241
+ #
121
242
  def Quaternion(*args)
122
243
  argc = args.size
123
244
 
@@ -141,26 +262,45 @@ module Kernel
141
262
  end
142
263
 
143
264
  case argc
265
+ when 1
266
+ arg = args[0]
267
+ if arg.kind_of?(Numeric)
268
+ # a quaternion (or an octonion, etc.)
269
+ return arg.complex? ? arg.to_q : arg
270
+ else
271
+ raise TypeError,
272
+ "can't convert #{arg.class} into Quaternion"
273
+ end
144
274
  when 2
145
275
  if args[1].kind_of?(Enumerable)
146
- # scalar and 3-D vector
276
+ # scalar and 3-D vector -> expand
147
277
  if args[1].size != 3
148
278
  raise TypeError, "not a 3-D vector"
149
279
  end
150
- args = [args[0], *args[1]]
151
- else
280
+ args.flatten!(1)
281
+ elsif args.all? { |x| x.kind_of?(Numeric) && x.complex? }
152
282
  # a pair of complex numbers
283
+ return Quaternion.send(:new, *args)
284
+ else
153
285
  return args[0] + args[1] * Quaternion::J
154
286
  end
155
287
  end
156
288
 
157
- # maximum four real numbers
158
- i = Quaternion::I
159
- j = Quaternion::J
160
- k = Quaternion::K
161
- zero = Quaternion.send(:new, 0, 0)
162
- args.zip([1, i, j, k]).inject(zero) do |sum,(num,base)|
163
- sum + num * base
289
+ w, x, y, z = args
290
+ z ||= 0
291
+ if args.all? { |x| x.kind_of?(Numeric) && x.real? }
292
+ # 3 or 4 real numbers
293
+ a = Complex.rect(w, x)
294
+ b = Complex.rect(y, z)
295
+ Quaternion.send(:new, a, b)
296
+ else
297
+ a = Complex(w, x)
298
+ b = Complex(y, z)
299
+ if [a, b].all? { |x| x.kind_of?(Numeric) && x.complex? }
300
+ Quaternion.send(:new, a, b)
301
+ else
302
+ a + b * Quaternion::J
303
+ end
164
304
  end
165
305
  end
166
306
  end
@@ -5,6 +5,12 @@ class Quaternion
5
5
  undef <=>
6
6
  undef_method(*Comparable.instance_methods)
7
7
 
8
+ ##
9
+ # Returns true if it equals to the other numerically.
10
+ #
11
+ # @param other [Object]
12
+ # @return [Boolean]
13
+ #
8
14
  def ==(other)
9
15
  if other.kind_of?(Quaternion)
10
16
  @a == other.a && @b == other.b
@@ -15,6 +21,12 @@ class Quaternion
15
21
  end
16
22
  end
17
23
 
24
+ ##
25
+ # Returns true if two quaternions have same reals.
26
+ #
27
+ # @param other [Object]
28
+ # @return [Boolean]
29
+ #
18
30
  def eql?(other)
19
31
  if other.kind_of?(Quaternion)
20
32
  @a.eql?(other.a) && @b.eql?(other.b)
@@ -23,8 +35,13 @@ class Quaternion
23
35
  end
24
36
  end
25
37
 
26
- # q1.eql?(q2) => q1.hash == q2.hash
38
+ ##
39
+ # Returns a hash.
40
+ #
41
+ # @return [Integer]
42
+ #
27
43
  def hash
44
+ # q1.eql?(q2) -> q1.hash == q2.hash
28
45
  [@a, @b].hash
29
46
  end
30
47
  end
@@ -1,18 +1,29 @@
1
1
  require_relative 'base'
2
2
  require_relative 'unary'
3
3
 
4
- #
5
- # to_xxx
6
- #
7
-
8
4
  class Quaternion
5
+ ##
6
+ # to_xxx
7
+ #
8
+
9
9
  # defined by Numeric:
10
10
  # * to_int #=> to_i
11
11
 
12
+ ##
13
+ # Returns self.
14
+ #
15
+ # @return [self]
16
+ #
12
17
  def to_q
13
18
  self
14
19
  end
15
20
 
21
+ ##
22
+ # Returns the value as a complex if possible (the discarded part should be exactly zero).
23
+ #
24
+ # @return [Complex]
25
+ # @raise [RangeError] if its yj+zk part is not exactly zero.
26
+ #
16
27
  def to_c
17
28
  unless __exact_zero__(@b)
18
29
  raise RangeError, "can't convert #{self} into Complex"
@@ -20,14 +31,71 @@ class Quaternion
20
31
  @a
21
32
  end
22
33
 
34
+ ##
35
+ # @!method to_f
36
+ #
37
+ # Returns the value as a float if possible (the imaginary part should be exactly zero).
38
+ #
39
+ # @return [Float]
40
+ # @raise [RangeError] if its imaginary part is not exactly zero.
41
+ #
42
+
43
+ ##
44
+ # @!method to_r
45
+ #
46
+ # Returns the value as a rational if possible (the imaginary part should be exactly zero).
47
+ #
48
+ # @return [Rational]
49
+ # @raise [RangeError] if its imaginary part is not exactly zero.
50
+ #
51
+
52
+ ##
53
+ # @!method to_i
54
+ #
55
+ # Returns the value as an integer if possible (the imaginary part should be exactly zero).
56
+ #
57
+ # @return [Integer]
58
+ # @raise [RangeError] if its imaginary part is not exactly zero.
59
+ #
60
+
61
+ ##
62
+ # @!method rationalize(eps = 0)
63
+ #
64
+ # Returns the value as a rational if possible (the imaginary part should be exactly zero).
65
+ #
66
+ # @param eps [Real]
67
+ # @return [Rational]
68
+ # @raise [RangeError] if its imaginary part is not exactly zero.
69
+ #
70
+
23
71
  [:to_f, :to_r, :to_i, :rationalize].each do |sym|
24
72
  define_method(sym) { |*args| to_c.send(sym, *args) }
25
73
  end
26
74
 
75
+ ##
76
+ # Returns the value as a string.
77
+ #
78
+ # @return [String]
79
+ #
80
+ # @example
81
+ # str = '1-2i-3/4j+0.56k'
82
+ # q = str.to_q #=> (1-2i-(3/4)*j+0.56k)
83
+ # q.to_s #=> "1-2i-3/4j+0.56k"
84
+ #
27
85
  def to_s
28
86
  __format__(:to_s)
29
87
  end
30
88
 
89
+ ##
90
+ # Returns the value as a string for inspection.
91
+ #
92
+ # @return [String]
93
+ #
94
+ # @example
95
+ # str = '1-2i-3/4j+0.56k'
96
+ # q = str.to_q #=> (1-2i-(3/4)*j+0.56k)
97
+ # q.inspect #=> "(1-2i-(3/4)*j+0.56k)"
98
+ #
31
99
  def inspect
32
100
  "(#{__format__(:inspect)})"
33
101
  end
@@ -53,6 +121,9 @@ class Quaternion
53
121
  end
54
122
 
55
123
  class << self
124
+ ##
125
+ # @!visibility private
126
+ #
56
127
  def parse(str, strict = false)
57
128
  regexp = %r{
58
129
  \A
@@ -65,16 +136,20 @@ class Quaternion
65
136
  |\z
66
137
  (?'head_or_sign' (?<=^|\s) [+-]?+ | [+-])
67
138
  (?'digits' \d++ (?:#{strict ? "_" : "_++"} \d++)*+)
68
- (?'int_or_float' (?:\g'digits')?+ (?:\. \g'digits')?+ (?<=\d) (?:e [+-]?+ \g'digits')?+)
139
+ (?'int_or_float' (?:\g'digits')?+ (?:\. \g'digits')?+
140
+ (?<=\d) (?:e [+-]?+ \g'digits')?+)
69
141
  (?'rational' \g'int_or_float' (?:/ \g'digits')?+)
70
142
  }xi
71
143
 
72
- match_data = regexp.match(str)
73
- if strict && (!$'.empty? || match_data.captures[0,4].all?(&:nil?))
74
- raise ArgumentError, "invalid value for convert(): #{str.inspect}"
144
+ # regexp.match(str) always succeeds (even if str is empty)
145
+ components = regexp.match(str).captures[0,4]
146
+ components = components.reverse.drop_while(&:nil?).reverse
147
+ if strict && (!$'.empty? || components.empty?)
148
+ raise ArgumentError,
149
+ "invalid value for convert(): #{str.inspect}"
75
150
  end
76
151
 
77
- w, x, y, z = match_data.captures[0,4].collect do |s|
152
+ components.collect! do |s|
78
153
  case s
79
154
  when %r{/} then s.to_r
80
155
  when %r{[.eE]} then s.to_f
@@ -84,24 +159,57 @@ class Quaternion
84
159
  end
85
160
  end
86
161
 
87
- new(Complex.rect(w, x), Complex.rect(y, z))
162
+ # returns the parsed number as a preferred type
163
+ case components.size
164
+ when 0
165
+ 0
166
+ when 1
167
+ components[0]
168
+ when 2
169
+ Complex.rect(*components)
170
+ else # 3 or 4
171
+ w, x, y, z = components
172
+ new(Complex.rect(w, x), Complex.rect(y, z || 0))
173
+ end
88
174
  end
89
175
  end
90
176
  end
91
177
 
92
178
  class Numeric
179
+ ##
180
+ # Returns the value as a quaternion.
181
+ #
182
+ # @return [Quaternion]
183
+ #
93
184
  def to_q
94
185
  Quaternion.send(:new, self, 0)
95
186
  end
96
187
  end
97
188
 
98
189
  class String
190
+ ##
191
+ # Returns a quaternion which denotes the string form.
192
+ # The parser ignores leading whitespaces and trailing garbage.
193
+ # Any digit sequences can be separated by an underscore.
194
+ # Returns zero for null or garbage string.
195
+ #
196
+ # @return [Quaternion]
197
+ #
198
+ # @example
199
+ # "1-2i-3/4j+0.56k".to_q #=> (1-2i-(3/4)*j+0.56k)
200
+ # "foobarbaz".to_q #=> (0+0i+0j+0k)
201
+ #
99
202
  def to_q
100
- Quaternion.send(:parse, self, false)
203
+ Quaternion.send(:parse, self, false).to_q
101
204
  end
102
205
  end
103
206
 
104
207
  class NilClass
208
+ ##
209
+ # Returns zero as a quaternion.
210
+ #
211
+ # @return [Quaternion]
212
+ #
105
213
  def to_q
106
214
  Quaternion.send(:new, 0, 0)
107
215
  end
@@ -1,23 +1,47 @@
1
1
  require_relative 'base'
2
2
 
3
- #
4
- # Unary operations
5
- #
6
-
7
3
  class Quaternion
4
+ ##
5
+ # Unary operations
6
+ #
7
+
8
8
  # defined by Numeric:
9
9
  # * +@ #=> self
10
10
  # * -@ #=> 0 - self
11
11
 
12
+ ##
13
+ # Returns its conjugate.
14
+ #
15
+ # @return [Quaternion]
16
+ #
17
+ # @example
18
+ # Quaternion(1, 2, 3, 4).conj #=> (1-2i-3j-4k)
19
+ #
12
20
  def conj
13
21
  __new__(@a.conj, -@b)
14
22
  end
15
23
  alias conjugate conj
16
24
 
25
+ ##
26
+ # Returns square of the absolute value.
27
+ #
28
+ # @return [Real]
29
+ #
30
+ # @example
31
+ # Quaternion(-1, 1, -1, 1).abs2 #=> 4
32
+ #
17
33
  def abs2
18
34
  @a.abs2 + @b.abs2
19
35
  end
20
36
 
37
+ ##
38
+ # Returns the absolute part of its polar form.
39
+ #
40
+ # @return [Real]
41
+ #
42
+ # @example
43
+ # Quaternion(-1, 1, -1, 1).abs #=> 2.0
44
+ #
21
45
  def abs
22
46
  a_abs = @a.abs
23
47
  b_abs = @b.abs
@@ -40,4 +64,9 @@ class Quaternion
40
64
  else
41
65
  false
42
66
  end
67
+
68
+ def __reciprocal__
69
+ d2 = abs2
70
+ __new__(@a.conj.quo(d2), @b.quo(-d2))
71
+ end
43
72
  end
@@ -1,33 +1,57 @@
1
1
  require_relative 'base'
2
2
 
3
- #
4
- # Fundamental quaternion units
5
- #
6
-
7
3
  class Quaternion
4
+ ##
5
+ # Fundamental quaternion units
6
+ #
7
+
8
8
  i = Complex::I
9
9
  I = new(i, 0)
10
10
  J = new(0, 1)
11
11
  K = new(0, i)
12
12
  end
13
13
 
14
- #
15
- # Convert to an imaginary part
16
- #
17
-
18
14
  class Numeric
15
+ ##
16
+ # Convert to an imaginary part
17
+ #
18
+
19
19
  # defined:
20
- # * #i #=> Complex.rect(0, self)
20
+ # * i #=> Complex.rect(0, self)
21
21
 
22
+ ##
23
+ # Returns the corresponding imaginary number.
24
+ # Not available for quaternions.
25
+ #
26
+ # @return [Quaternion] +self * Quaternion::J+
27
+ # @raise [NoMethodError] if +self+ is not a complex.
28
+ #
29
+ # @example
30
+ # 3.j #=> (0+0i+3j+0k)
31
+ # Complex(3, 4).j #=> (0+0i+3j+4k)
32
+ #
22
33
  def j
23
34
  Quaternion.send(:new, 0, self)
24
35
  end
25
36
 
37
+ ##
38
+ # Returns the corresponding imaginary number.
39
+ # Not available for complex numbers.
40
+ #
41
+ # @return [Quaternion] +self * Quaternion::K+
42
+ # @raise [NoMethodError] if +self+ is not a real.
43
+ #
44
+ # @example
45
+ # 4.k #=> (0+0i+0j+4k)
46
+ #
26
47
  def k
27
48
  Quaternion.send(:new, 0, self.i)
28
49
  end
29
50
  end
30
51
 
52
+ ##
53
+ # @!parse class Complex < Numeric; end
54
+ #
31
55
  class Complex
32
56
  # undefined:
33
57
  # * #i
@@ -5,58 +5,98 @@ require_relative 'arithmetic'
5
5
  require_relative 'conversion'
6
6
 
7
7
  class Quaternion
8
+ ##
9
+ # Performs exponentiation.
10
+ #
11
+ # @param index [Numeric]
12
+ # @return [Quaternion]
13
+ #
14
+ # @example
15
+ # Quaternion(1, 1, 1, 1) ** 6 #=> (64+0i+0j+0k)
16
+ # Math::E.to_q ** Quaternion(Math.log(2), [Math::PI/3 / Math.sqrt(3)] * 3)
17
+ # #=> (1.0000000000000002+1.0i+1.0j+1.0k)
18
+ #
8
19
  def **(index)
9
- if index.kind_of?(Numeric)
10
- if __exact_zero__(index)
11
- return __new__(1, 0)
12
- end
20
+ unless index.kind_of?(Numeric)
21
+ num1, num2 = index.coerce(self)
22
+ return num1 ** num2
23
+ end
24
+
25
+ if __exact_zero__(index)
26
+ return __new__(1, 0)
27
+ end
13
28
 
14
- # complex -> real
29
+ # complex -> real
30
+ # (only if the imaginary part is exactly zero)
31
+ unless index.real?
15
32
  begin
16
33
  index.to_f
17
34
  rescue
18
35
  else
19
36
  index = index.real
20
37
  end
38
+ end
21
39
 
22
- # rational -> integer
23
- if index.kind_of?(Rational) && index.denominator == 1
24
- index = index.numerator
25
- end
40
+ # rational -> integer
41
+ if index.kind_of?(Rational) && index.denominator == 1
42
+ index = index.numerator
43
+ end
26
44
 
27
- if index.integer?
28
- # binary method
29
- x = (index >= 0) ? self : 1 / self
30
- n = index.abs
45
+ # calc and return
46
+ if index.integer?
47
+ # exponentiation by squaring
48
+ x = (index >= 0) ? self : __reciprocal__
49
+ n = index.abs
31
50
 
32
- z = __new__(1, 0)
33
- while true
34
- n, i = n.divmod(2)
35
- z *= x if i == 1
36
- return z if n == 0
37
- x *= x
38
- end
39
- elsif index.real?
40
- r, theta, vector = polar
41
- return Quaternion.polar(r ** index, theta * index, vector)
42
- elsif index.complex? || index.kind_of?(Quaternion)
43
- r, theta, vector = polar
44
- q = Math.log(r) + Quaternion.hrect(0, *(theta * vector))
45
- q *= index
46
- return Quaternion.polar(Math.exp(q.real), 1, q.imag)
51
+ z = __new__(1, 0)
52
+ while true
53
+ z *= x if n.odd?
54
+ n >>= 1
55
+ return z if n.zero?
56
+ x *= x
47
57
  end
58
+ elsif index.real?
59
+ r, theta, vector = polar
60
+ Quaternion.polar(r ** index, theta * index, vector)
61
+ elsif index.complex? || index.kind_of?(Quaternion)
62
+ # assume that log(self) commutes with index under multiplication
63
+ r, theta, vector = polar
64
+ q = Quaternion.hrect(Math.log(r), *(theta * vector))
65
+ q *= index
66
+ Quaternion.polar(Math.exp(q.real), 1, q.imag)
67
+ else
68
+ num1, num2 = index.coerce(self)
69
+ num1 ** num2
48
70
  end
49
-
50
- num1, num2 = other.coerce(self)
51
- num1 ** num2
52
71
  end
53
72
 
73
+ ##
74
+ # Returns the denominator (lcm of all components' denominators).
75
+ # @see numerator
76
+ #
77
+ # @return [Integer]
78
+ #
54
79
  def denominator
55
80
  ad = @a.denominator
56
81
  bd = @b.denominator
57
82
  ad.lcm(bd)
58
83
  end
59
84
 
85
+ ##
86
+ # Returns the numerator.
87
+ #
88
+ # 1 1 1 3 4-6i-12j+9k <- numerator
89
+ # - - -i - -j + -k -> -----------
90
+ # 3 2 1 4 12 <- denominator
91
+ #
92
+ # @return [Quaternion]
93
+ #
94
+ # @example
95
+ # q = Quaternion('1/3-1/2i-j+3/4k') #=> ((1/3)-(1/2)*i-1j+(3/4)*k)
96
+ # n = q.numerator #=> (4-6i-12j+9k)
97
+ # d = q.denominator #=> 12
98
+ # n / d == q #=> true
99
+ #
60
100
  def numerator
61
101
  an = @a.numerator
62
102
  bn = @b.numerator
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  Gem::Specification.new do |spec|
5
5
  spec.name = "quaternion_c2"
6
- spec.version = "0.1.1"
6
+ spec.version = "0.1.2"
7
7
  spec.authors = ["Masahiro Nomoto"]
8
8
  spec.email = ["hmmnrst@users.noreply.github.com"]
9
9
 
metadata CHANGED
@@ -1,55 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quaternion_c2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masahiro Nomoto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-31 00:00:00.000000000 Z
11
+ date: 2017-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.14'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.14'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '10.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '3.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
55
  description: This provides a numeric class Quaternion which is similar to a built-in
@@ -60,9 +60,9 @@ executables: []
60
60
  extensions: []
61
61
  extra_rdoc_files: []
62
62
  files:
63
- - .gitignore
64
- - .rspec
65
- - .travis.yml
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".travis.yml"
66
66
  - Gemfile
67
67
  - LICENSE.txt
68
68
  - README.md
@@ -91,17 +91,17 @@ require_paths:
91
91
  - lib
92
92
  required_ruby_version: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '>='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  required_rubygems_version: !ruby/object:Gem::Requirement
98
98
  requirements:
99
- - - '>='
99
+ - - ">="
100
100
  - !ruby/object:Gem::Version
101
101
  version: '0'
102
102
  requirements: []
103
103
  rubyforge_project:
104
- rubygems_version: 2.5.0
104
+ rubygems_version: 2.6.8
105
105
  signing_key:
106
106
  specification_version: 4
107
107
  summary: Quaternion class