quaternion_c2 0.1.1 → 1.0.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 +5 -5
- data/.github/workflows/test.yml +29 -0
- data/README.md +43 -0
- data/lib/quaternion_c2/arithmetic.rb +52 -7
- data/lib/quaternion_c2/attributes.rb +11 -0
- data/lib/quaternion_c2/base.rb +16 -0
- data/lib/quaternion_c2/classification.rb +23 -11
- data/lib/quaternion_c2/comparison.rb +55 -0
- data/lib/quaternion_c2/conversion.rb +164 -24
- data/lib/quaternion_c2/equality.rb +18 -2
- data/lib/quaternion_c2/to_type.rb +127 -15
- data/lib/quaternion_c2/unary.rb +45 -4
- data/lib/quaternion_c2/units.rb +33 -9
- data/lib/quaternion_c2/utils.rb +71 -31
- data/lib/quaternion_c2.rb +1 -0
- data/quaternion_c2.gemspec +7 -4
- metadata +40 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2d786fc0057f8807e1a12a9c55c19e124fa7edd24eccf3d686c1a927685c01bc
|
4
|
+
data.tar.gz: a98e2aaed384903bff5a13b844b5097b88f360a1ed8a182b2a1a774b45e46d8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45c7c781395f45cb8a0adc3acb2e2a6c0c6f7d58e03a14da74490080048146aee760a5835eb524f1c02a4dce77d3bfbc2f008aa88b89754394d57900c34eaca8
|
7
|
+
data.tar.gz: 3afc20e3e7b5fe2874343ade34e6550f862a1f21cb097ff3b403465f63b4364d1dc050748219fdac26b0328310eb995dd1fbf38598cd4d76cc12e5fb346ce92f
|
@@ -0,0 +1,29 @@
|
|
1
|
+
name: Test
|
2
|
+
|
3
|
+
on:
|
4
|
+
push: {}
|
5
|
+
pull_request: {}
|
6
|
+
schedule:
|
7
|
+
- cron: '45 23 1 * *' # Monthly check
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
unit-test:
|
11
|
+
strategy:
|
12
|
+
fail-fast: false
|
13
|
+
matrix:
|
14
|
+
ruby: [ '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7',
|
15
|
+
'3.0', '3.1', '3.2',
|
16
|
+
head ]
|
17
|
+
runs-on: ubuntu-latest
|
18
|
+
name: Unit test with Ruby ${{ matrix.ruby }}
|
19
|
+
steps:
|
20
|
+
- name: Checkout repository
|
21
|
+
uses: actions/checkout@v3
|
22
|
+
- name: Setup Ruby
|
23
|
+
uses: ruby/setup-ruby@v1
|
24
|
+
with:
|
25
|
+
ruby-version: ${{ matrix.ruby }}
|
26
|
+
bundler-cache: true
|
27
|
+
- name: Run RSpec
|
28
|
+
run: |
|
29
|
+
bundle exec rake spec
|
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,10 +61,30 @@ 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)
|
49
|
-
self * other.conj.send(sym, other.abs2)
|
86
|
+
n = self * other.conj.send(sym, other.abs2)
|
87
|
+
__new__(n.a / 1, n.b / 1) # Canonicalize rationals as Complex does
|
50
88
|
elsif other.kind_of?(Numeric) && other.complex?
|
51
89
|
__new__(@a.send(sym, other), @b.send(sym, other.conj))
|
52
90
|
else
|
@@ -58,10 +96,17 @@ class Quaternion
|
|
58
96
|
alias / quo
|
59
97
|
undef div, %, modulo, remainder, divmod
|
60
98
|
|
61
|
-
|
99
|
+
##
|
62
100
|
# Conversion
|
63
101
|
#
|
64
102
|
|
103
|
+
##
|
104
|
+
# Performs type conversion.
|
105
|
+
#
|
106
|
+
# @param other [Numeric]
|
107
|
+
# @return [[Quaternion, self]]
|
108
|
+
# @raise [TypeError]
|
109
|
+
#
|
65
110
|
def coerce(other)
|
66
111
|
if other.kind_of?(Quaternion)
|
67
112
|
[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
|
data/lib/quaternion_c2/base.rb
CHANGED
@@ -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
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require_relative 'arithmetic' # define Quaternion#coerce
|
3
|
+
|
4
|
+
if Complex.public_method_defined?(:<=>)
|
5
|
+
class Quaternion
|
6
|
+
##
|
7
|
+
# Compare values if both are real numbers.
|
8
|
+
#
|
9
|
+
# @param other [Object]
|
10
|
+
# @return [-1, 0, 1, or nil]
|
11
|
+
#
|
12
|
+
def <=>(other)
|
13
|
+
if other.kind_of?(Quaternion)
|
14
|
+
(@b == 0 && other.b == 0) ? (@a <=> other.a) : nil
|
15
|
+
elsif other.kind_of?(Numeric) && other.complex?
|
16
|
+
(@b == 0) ? (@a <=> other) : nil
|
17
|
+
elsif other.respond_to?(:coerce)
|
18
|
+
n1, n2 = other.coerce(self)
|
19
|
+
n1 <=> n2
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Check whether built-in Complex#<=> uses type coercions.
|
27
|
+
if (Complex.rect(0, 0) <=> Quaternion.send(:new, 0, 0)).nil?
|
28
|
+
warn "Redefine Complex#<=> to allow type coercions."
|
29
|
+
|
30
|
+
class Complex
|
31
|
+
##
|
32
|
+
# Compare values if both are real numbers.
|
33
|
+
#
|
34
|
+
# @param other [Object]
|
35
|
+
# @return [-1, 0, 1, or nil]
|
36
|
+
#
|
37
|
+
def <=>(other)
|
38
|
+
if other.kind_of?(Complex)
|
39
|
+
(imag == 0 && other.imag == 0) ? (real <=> other.real) : nil
|
40
|
+
elsif other.kind_of?(Numeric) && other.real?
|
41
|
+
(imag == 0) ? (real <=> other) : nil
|
42
|
+
elsif other.respond_to?(:coerce)
|
43
|
+
n1, n2 = other.coerce(self)
|
44
|
+
n1 <=> n2
|
45
|
+
else
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
else
|
52
|
+
class Quaternion
|
53
|
+
undef <=>
|
54
|
+
end
|
55
|
+
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
|
-
|
32
|
-
#
|
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
|
151
|
-
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
@@ -2,9 +2,14 @@ require_relative 'base'
|
|
2
2
|
require_relative 'classification'
|
3
3
|
|
4
4
|
class Quaternion
|
5
|
-
undef <=>
|
6
5
|
undef_method(*Comparable.instance_methods)
|
7
6
|
|
7
|
+
##
|
8
|
+
# Returns true if it equals to the other numerically.
|
9
|
+
#
|
10
|
+
# @param other [Object]
|
11
|
+
# @return [Boolean]
|
12
|
+
#
|
8
13
|
def ==(other)
|
9
14
|
if other.kind_of?(Quaternion)
|
10
15
|
@a == other.a && @b == other.b
|
@@ -15,6 +20,12 @@ class Quaternion
|
|
15
20
|
end
|
16
21
|
end
|
17
22
|
|
23
|
+
##
|
24
|
+
# Returns true if two quaternions have same reals.
|
25
|
+
#
|
26
|
+
# @param other [Object]
|
27
|
+
# @return [Boolean]
|
28
|
+
#
|
18
29
|
def eql?(other)
|
19
30
|
if other.kind_of?(Quaternion)
|
20
31
|
@a.eql?(other.a) && @b.eql?(other.b)
|
@@ -23,8 +34,13 @@ class Quaternion
|
|
23
34
|
end
|
24
35
|
end
|
25
36
|
|
26
|
-
|
37
|
+
##
|
38
|
+
# Returns a hash.
|
39
|
+
#
|
40
|
+
# @return [Integer]
|
41
|
+
#
|
27
42
|
def hash
|
43
|
+
# q1.eql?(q2) -> q1.hash == q2.hash
|
28
44
|
[@a, @b].hash
|
29
45
|
end
|
30
46
|
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,8 +121,8 @@ class Quaternion
|
|
53
121
|
end
|
54
122
|
|
55
123
|
class << self
|
56
|
-
|
57
|
-
|
124
|
+
RE, RE_STRICT = [false, true].collect do |strict|
|
125
|
+
%r{
|
58
126
|
\A
|
59
127
|
\s*+
|
60
128
|
(?:(?'real' [+-]?+ \g'rational' ) (?![ijk]))?+
|
@@ -62,19 +130,30 @@ class Quaternion
|
|
62
130
|
(?:(?'imag_j' \g'head_or_sign' \g'rational'?+) j )?+
|
63
131
|
(?:(?'imag_k' \g'head_or_sign' \g'rational'?+) k )?+
|
64
132
|
\s*+
|
65
|
-
|
66
|
-
(?'head_or_sign' (
|
133
|
+
|(?!)
|
134
|
+
(?'head_or_sign' (?<!\S) [+-]?+ | [+-])
|
67
135
|
(?'digits' \d++ (?:#{strict ? "_" : "_++"} \d++)*+)
|
68
|
-
(?'int_or_float' (?:\g'digits')?+ (?:\. \g'digits')?+
|
136
|
+
(?'int_or_float' (?:\g'digits')?+ (?:\. \g'digits')?+
|
137
|
+
(?<=\d) (?:e [+-]?+ \g'digits')?+)
|
69
138
|
(?'rational' \g'int_or_float' (?:/ \g'digits')?+)
|
70
139
|
}xi
|
140
|
+
end
|
141
|
+
private_constant :RE, :RE_STRICT
|
142
|
+
|
143
|
+
private
|
71
144
|
|
72
|
-
|
73
|
-
|
74
|
-
|
145
|
+
def parse(str, strict = false)
|
146
|
+
regexp = strict ? RE_STRICT : RE
|
147
|
+
|
148
|
+
# regexp.match(str) always succeeds (even if str is empty)
|
149
|
+
components = regexp.match(str).captures[0,4]
|
150
|
+
components = components.reverse.drop_while(&:nil?).reverse
|
151
|
+
if strict && (!$'.empty? || components.empty?)
|
152
|
+
raise ArgumentError,
|
153
|
+
"invalid value for convert(): #{str.inspect}"
|
75
154
|
end
|
76
155
|
|
77
|
-
|
156
|
+
components.collect! do |s|
|
78
157
|
case s
|
79
158
|
when %r{/} then s.to_r
|
80
159
|
when %r{[.eE]} then s.to_f
|
@@ -84,24 +163,57 @@ class Quaternion
|
|
84
163
|
end
|
85
164
|
end
|
86
165
|
|
87
|
-
|
166
|
+
# returns the parsed number as a preferred type
|
167
|
+
case components.size
|
168
|
+
when 0
|
169
|
+
0
|
170
|
+
when 1
|
171
|
+
components[0]
|
172
|
+
when 2
|
173
|
+
Complex.rect(*components)
|
174
|
+
else # 3 or 4
|
175
|
+
w, x, y, z = components
|
176
|
+
new(Complex.rect(w, x), Complex.rect(y, z || 0))
|
177
|
+
end
|
88
178
|
end
|
89
179
|
end
|
90
180
|
end
|
91
181
|
|
92
182
|
class Numeric
|
183
|
+
##
|
184
|
+
# Returns the value as a quaternion.
|
185
|
+
#
|
186
|
+
# @return [Quaternion]
|
187
|
+
#
|
93
188
|
def to_q
|
94
189
|
Quaternion.send(:new, self, 0)
|
95
190
|
end
|
96
191
|
end
|
97
192
|
|
98
193
|
class String
|
194
|
+
##
|
195
|
+
# Returns a quaternion which denotes the string form.
|
196
|
+
# The parser ignores leading whitespaces and trailing garbage.
|
197
|
+
# Any digit sequences can be separated by an underscore.
|
198
|
+
# Returns zero for null or garbage string.
|
199
|
+
#
|
200
|
+
# @return [Quaternion]
|
201
|
+
#
|
202
|
+
# @example
|
203
|
+
# "1-2i-3/4j+0.56k".to_q #=> (1-2i-(3/4)*j+0.56k)
|
204
|
+
# "foobarbaz".to_q #=> (0+0i+0j+0k)
|
205
|
+
#
|
99
206
|
def to_q
|
100
|
-
Quaternion.send(:parse, self, false)
|
207
|
+
Quaternion.send(:parse, self, false).to_q
|
101
208
|
end
|
102
209
|
end
|
103
210
|
|
104
211
|
class NilClass
|
212
|
+
##
|
213
|
+
# Returns zero as a quaternion.
|
214
|
+
#
|
215
|
+
# @return [Quaternion]
|
216
|
+
#
|
105
217
|
def to_q
|
106
218
|
Quaternion.send(:new, 0, 0)
|
107
219
|
end
|
data/lib/quaternion_c2/unary.rb
CHANGED
@@ -1,23 +1,59 @@
|
|
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 negation of the value.
|
14
|
+
#
|
15
|
+
# @return [Quaternion]
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# -Quaternion(1, 2, 3, 4) #=> (-1-2i-3j-4k)
|
19
|
+
#
|
20
|
+
def -@
|
21
|
+
__new__(-@a, -@b)
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Returns its conjugate.
|
26
|
+
#
|
27
|
+
# @return [Quaternion]
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# Quaternion(1, 2, 3, 4).conj #=> (1-2i-3j-4k)
|
31
|
+
#
|
12
32
|
def conj
|
13
33
|
__new__(@a.conj, -@b)
|
14
34
|
end
|
15
35
|
alias conjugate conj
|
16
36
|
|
37
|
+
##
|
38
|
+
# Returns square of the absolute value.
|
39
|
+
#
|
40
|
+
# @return [Real]
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# Quaternion(-1, 1, -1, 1).abs2 #=> 4
|
44
|
+
#
|
17
45
|
def abs2
|
18
46
|
@a.abs2 + @b.abs2
|
19
47
|
end
|
20
48
|
|
49
|
+
##
|
50
|
+
# Returns the absolute part of its polar form.
|
51
|
+
#
|
52
|
+
# @return [Real]
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# Quaternion(-1, 1, -1, 1).abs #=> 2.0
|
56
|
+
#
|
21
57
|
def abs
|
22
58
|
a_abs = @a.abs
|
23
59
|
b_abs = @b.abs
|
@@ -40,4 +76,9 @@ class Quaternion
|
|
40
76
|
else
|
41
77
|
false
|
42
78
|
end
|
79
|
+
|
80
|
+
def __reciprocal__
|
81
|
+
d2 = abs2
|
82
|
+
__new__(@a.conj.quo(d2), @b.quo(-d2))
|
83
|
+
end
|
43
84
|
end
|
data/lib/quaternion_c2/units.rb
CHANGED
@@ -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
|
-
# *
|
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
|
data/lib/quaternion_c2/utils.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
40
|
+
# rational -> integer
|
41
|
+
if index.kind_of?(Rational) && index.denominator == 1
|
42
|
+
index = index.numerator
|
43
|
+
end
|
26
44
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
data/lib/quaternion_c2.rb
CHANGED
data/quaternion_c2.gemspec
CHANGED
@@ -3,14 +3,15 @@ $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.
|
6
|
+
spec.version = "1.0.0"
|
7
7
|
spec.authors = ["Masahiro Nomoto"]
|
8
8
|
spec.email = ["hmmnrst@users.noreply.github.com"]
|
9
9
|
|
10
10
|
spec.summary = %q{Quaternion class}
|
11
|
-
spec.description = %q{
|
11
|
+
spec.description = %q{Provides a numeric class Quaternion which is similar to a built-in class Complex.}
|
12
12
|
spec.homepage = "https://github.com/hmmnrst/quaternion_c2"
|
13
13
|
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 2.1" # matrix:0.1.0 does not support ruby 2.0
|
14
15
|
|
15
16
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
17
|
f.match(%r{^(test|spec|features)/})
|
@@ -19,7 +20,9 @@ Gem::Specification.new do |spec|
|
|
19
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
21
|
spec.require_paths = ["lib"]
|
21
22
|
|
22
|
-
spec.
|
23
|
-
|
23
|
+
spec.add_dependency "matrix" # Vector
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler"
|
26
|
+
spec.add_development_dependency "rake"
|
24
27
|
spec.add_development_dependency "rspec", "~> 3.0"
|
25
28
|
end
|
metadata
CHANGED
@@ -1,68 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quaternion_c2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masahiro Nomoto
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: matrix
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
|
-
- -
|
31
|
+
- - ">="
|
18
32
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
33
|
+
version: '0'
|
20
34
|
type: :development
|
21
35
|
prerelease: false
|
22
36
|
version_requirements: !ruby/object:Gem::Requirement
|
23
37
|
requirements:
|
24
|
-
- -
|
38
|
+
- - ">="
|
25
39
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
|
-
- -
|
45
|
+
- - ">="
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
47
|
+
version: '0'
|
34
48
|
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
|
-
- -
|
52
|
+
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
|
-
- - ~>
|
59
|
+
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
61
|
version: '3.0'
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
|
-
- - ~>
|
66
|
+
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '3.0'
|
55
|
-
description:
|
56
|
-
|
69
|
+
description: Provides a numeric class Quaternion which is similar to a built-in class
|
70
|
+
Complex.
|
57
71
|
email:
|
58
72
|
- hmmnrst@users.noreply.github.com
|
59
73
|
executables: []
|
60
74
|
extensions: []
|
61
75
|
extra_rdoc_files: []
|
62
76
|
files:
|
63
|
-
- .
|
64
|
-
- .
|
65
|
-
- .
|
77
|
+
- ".github/workflows/test.yml"
|
78
|
+
- ".gitignore"
|
79
|
+
- ".rspec"
|
80
|
+
- ".travis.yml"
|
66
81
|
- Gemfile
|
67
82
|
- LICENSE.txt
|
68
83
|
- README.md
|
@@ -74,6 +89,7 @@ files:
|
|
74
89
|
- lib/quaternion_c2/attributes.rb
|
75
90
|
- lib/quaternion_c2/base.rb
|
76
91
|
- lib/quaternion_c2/classification.rb
|
92
|
+
- lib/quaternion_c2/comparison.rb
|
77
93
|
- lib/quaternion_c2/conversion.rb
|
78
94
|
- lib/quaternion_c2/equality.rb
|
79
95
|
- lib/quaternion_c2/to_type.rb
|
@@ -85,24 +101,23 @@ homepage: https://github.com/hmmnrst/quaternion_c2
|
|
85
101
|
licenses:
|
86
102
|
- MIT
|
87
103
|
metadata: {}
|
88
|
-
post_install_message:
|
104
|
+
post_install_message:
|
89
105
|
rdoc_options: []
|
90
106
|
require_paths:
|
91
107
|
- lib
|
92
108
|
required_ruby_version: !ruby/object:Gem::Requirement
|
93
109
|
requirements:
|
94
|
-
- -
|
110
|
+
- - ">="
|
95
111
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
112
|
+
version: '2.1'
|
97
113
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
114
|
requirements:
|
99
|
-
- -
|
115
|
+
- - ">="
|
100
116
|
- !ruby/object:Gem::Version
|
101
117
|
version: '0'
|
102
118
|
requirements: []
|
103
|
-
|
104
|
-
|
105
|
-
signing_key:
|
119
|
+
rubygems_version: 3.3.7
|
120
|
+
signing_key:
|
106
121
|
specification_version: 4
|
107
122
|
summary: Quaternion class
|
108
123
|
test_files: []
|