Class: Quaternion

Inherits:
Numeric show all
Defined in:
lib/quaternion_c2/base.rb,
lib/quaternion_c2/unary.rb,
lib/quaternion_c2/units.rb,
lib/quaternion_c2/units.rb,
lib/quaternion_c2/utils.rb,
lib/quaternion_c2/to_type.rb,
lib/quaternion_c2/equality.rb,
lib/quaternion_c2/arithmetic.rb,
lib/quaternion_c2/attributes.rb,
lib/quaternion_c2/conversion.rb,
lib/quaternion_c2/classification.rb

Overview

This class Quaternion is an analogue of Complex.

  • A subclass of Numeric

  • Immutable instances

  • Common / extended methods

Please require ‘quaternion_c2’ to load all functions.

Quaternion has many constructors to accept the various representations of a quaternion. It is recommended to use Kernel.#Quaternion and Quaternion.polar.

Constant Summary collapse

I =
new(i, 0)
J =
new(0, 1)
K =
new(0, i)

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Numeric

#j, #k

Class Method Details

.hrect(w, x = 0, y = 0, z = 0) ⇒ Quaternion

Returns a quaternion object w+xi+yj+zk, where all of w, x, y, and z are real.

Examples:

Quaternion.hrect(1, 2, 3, 4) #=> (1+2i+3j+4k)

Raises:

  • (TypeError)


48
49
50
51
52
# File 'lib/quaternion_c2/conversion.rb', line 48

def hrect(w, x = 0, y = 0, z = 0)
	a = Complex.rect(w, x)
	b = Complex.rect(y, z)
	new(a, b)
end

.hyperrectangularQuaternion

Returns a quaternion object w+xi+yj+zk, where all of w, x, y, and z are real.

Examples:

Quaternion.hrect(1, 2, 3, 4) #=> (1+2i+3j+4k)

Raises:

  • (TypeError)


53
54
55
56
57
# File 'lib/quaternion_c2/conversion.rb', line 53

def hrect(w, x = 0, y = 0, z = 0)
	a = Complex.rect(w, x)
	b = Complex.rect(y, z)
	new(a, b)
end

.polar(r, theta = 0, vector = ) ⇒ Quaternion

Returns a quaternion object which denotes the given polar form. The actual angle is recognized as theta * vector.norm.

Examples:

Quaternion.polar(1, Math::PI/3, Vector[1,1,1].normalize)
#=> (0.5000000000000001+0.5i+0.5j+0.5k)
Quaternion.polar(1, 1, Math::PI/3 * Vector[1,1,1].normalize)
#=> (0.4999999999999999+0.5i+0.5j+0.5k)

r, theta = -3, -1
Quaternion.polar(r)        == r                       #=> true
Quaternion.polar(r, theta) == Complex.polar(r, theta) #=> true

Raises:

  • (TypeError)


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/quaternion_c2/conversion.rb', line 75

def polar(r, theta = 0, vector = Vector[1, 0, 0])
	unless vector.kind_of?(Enumerable) && vector.size == 3
		raise TypeError, 'not a 3-D vector'
	end
	unless [r, theta, *vector].all? { |a| a.kind_of?(Numeric) && a.real? }
		raise TypeError, 'not a real'
	end

	vector = Vector[*vector] unless vector.kind_of?(Vector)
	norm = vector.norm
	theta *= norm

	r_cos = r * Math.cos(theta)
	r_sin = r * Math.sin(theta)
	r_sin /= norm if norm > 0
	hrect(r_cos, *(r_sin * vector))
end

.rect(a, b = 0) ⇒ Quaternion

Returns a quaternion object a+bj, where both a and b are complex (or real).

Examples:

Quaternion.rect(1, Complex::I) #=> (1+0i+0j+1k)

Raises:

  • (TypeError)


27
28
29
30
31
32
# File 'lib/quaternion_c2/conversion.rb', line 27

def rect(a, b = 0)
	unless [a, b].all? { |c| c.kind_of?(Numeric) && c.complex? }
		raise TypeError, 'not a complex'
	end
	new(a, b)
end

.rectangularQuaternion

Returns a quaternion object a+bj, where both a and b are complex (or real).

Examples:

Quaternion.rect(1, Complex::I) #=> (1+0i+0j+1k)

Raises:

  • (TypeError)


33
34
35
36
37
38
# File 'lib/quaternion_c2/conversion.rb', line 33

def rect(a, b = 0)
	unless [a, b].all? { |c| c.kind_of?(Numeric) && c.complex? }
		raise TypeError, 'not a complex'
	end
	new(a, b)
end

Instance Method Details

#*(other) ⇒ Quaternion

Performs multiplication.



51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/quaternion_c2/arithmetic.rb', line 51

def *(other)
	if other.kind_of?(Quaternion)
		_a = other.a
		_b = other.b
		__new__(@a * _a - _b.conj * @b, _b * @a + @b * _a.conj)
	elsif other.kind_of?(Numeric) && other.complex?
		__new__(@a * other, @b * other.conj)
	else
		n1, n2 = other.coerce(self)
		n1 * n2
	end
end

#**(index) ⇒ Quaternion

Performs exponentiation.

Examples:

Quaternion(1, 1, 1, 1) ** 6 #=> (64+0i+0j+0k)
Math::E.to_q ** Quaternion(Math.log(2), [Math::PI/3 / Math.sqrt(3)] * 3)
#=> (1.0000000000000002+1.0i+1.0j+1.0k)


19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/quaternion_c2/utils.rb', line 19

def **(index)
	unless index.kind_of?(Numeric)
		num1, num2 = index.coerce(self)
		return num1 ** num2
	end

	if __exact_zero__(index)
		return __new__(1, 0)
	end

	# complex -> real
	# (only if the imaginary part is exactly zero)
	unless index.real?
		begin
			index.to_f
		rescue
		else
			index = index.real
		end
	end

	# rational -> integer
	if index.kind_of?(Rational) && index.denominator == 1
		index = index.numerator
	end

	# calc and return
	if index.integer?
		# exponentiation by squaring
		x = (index >= 0) ? self : __reciprocal__
		n = index.abs

		z = __new__(1, 0)
		while true
			z *= x if n.odd?
			n >>= 1
			return z if n.zero?
			x *= x
		end
	elsif index.real?
		r, theta, vector = polar
		Quaternion.polar(r ** index, theta * index, vector)
	elsif index.complex? || index.kind_of?(Quaternion)
		# assume that log(self) commutes with index under multiplication
		r, theta, vector = polar
		q = Quaternion.hrect(Math.log(r), *(theta * vector))
		q *= index
		Quaternion.polar(Math.exp(q.real), 1, q.imag)
	else
		num1, num2 = index.coerce(self)
		num1 ** num2
	end
end

#+(other) ⇒ Quaternion

Performs addition.



17
18
19
20
21
22
23
24
25
26
# File 'lib/quaternion_c2/arithmetic.rb', line 17

def +(other)
	if other.kind_of?(Quaternion)
		__new__(@a + other.a, @b + other.b)
	elsif other.kind_of?(Numeric) && other.complex?
		__new__(@a + other, @b)
	else
		n1, n2 = other.coerce(self)
		n1 + n2
	end
end

#-(other) ⇒ Quaternion

Performs subtraction.



34
35
36
37
38
39
40
41
42
43
# File 'lib/quaternion_c2/arithmetic.rb', line 34

def -(other)
	if other.kind_of?(Quaternion)
		__new__(@a - other.a, @b - other.b)
	elsif other.kind_of?(Numeric) && other.complex?
		__new__(@a - other, @b)
	else
		n1, n2 = other.coerce(self)
		n1 - n2
	end
end

#==(other) ⇒ Boolean

Returns true if it equals to the other numerically.



14
15
16
17
18
19
20
21
22
# File 'lib/quaternion_c2/equality.rb', line 14

def ==(other)
	if other.kind_of?(Quaternion)
		@a == other.a && @b == other.b
	elsif other.kind_of?(Numeric) && other.complex?
		@a == other && @b == 0
	else
		other == self
	end
end

#absReal Also known as: magnitude

Returns the absolute part of its polar form.

Examples:

Quaternion(-1, 1, -1, 1).abs #=> 2.0


45
46
47
48
49
50
51
52
53
54
55
# File 'lib/quaternion_c2/unary.rb', line 45

def abs
	a_abs = @a.abs
	b_abs = @b.abs
	if    __exact_zero__(a_abs)
		b_abs
	elsif __exact_zero__(b_abs)
		a_abs
	else
		Math.hypot(a_abs, b_abs)
	end
end

#abs2Real

Returns square of the absolute value.

Examples:

Quaternion(-1, 1, -1, 1).abs2 #=> 4


33
34
35
# File 'lib/quaternion_c2/unary.rb', line 33

def abs2
	@a.abs2 + @b.abs2
end

#argReal Also known as: angle, phase

Returns the angle part of its polar form.

Examples:

Quaternion('1+i+j+k').arg #=> Math::PI/3


163
164
165
166
167
# File 'lib/quaternion_c2/conversion.rb', line 163

def arg
	r_cos = real
	r_sin = imag.norm
	Math.atan2(r_sin, r_cos)
end

#axisVector

Returns the axis part of its polar form.

Examples:

Quaternion('1+i+j+k').axis #=> Vector[1,1,1].normalize


179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/quaternion_c2/conversion.rb', line 179

def axis
	v = imag
	norm = v.norm
	if norm.zero?
		# imag[0] == +0.0 -> q = r exp(+I PI)
		# imag[0] ==  0/1 -> q = r exp(+I PI)
		# imag[0] == -0.0 -> q = r exp(-I PI)
		sign = (1.0 / imag[0] >= 0) ? 1 : -1
		Vector[sign, 0, 0]
	else
		v / norm
	end
end

#coerce(other) ⇒ [Quaternion, self]

Performs type conversion.

Raises:

  • (TypeError)


109
110
111
112
113
114
115
116
117
118
# File 'lib/quaternion_c2/arithmetic.rb', line 109

def coerce(other)
	if other.kind_of?(Quaternion)
		[other, self]
	elsif other.kind_of?(Numeric) && other.complex?
		[__new__(other, 0), self]
	else
		raise TypeError,
		      "#{other.class} can't be coerced into #{self.class}"
	end
end

#complex?Boolean

Returns false.



41
42
43
# File 'lib/quaternion_c2/classification.rb', line 41

def complex?
	false
end

#conjQuaternion Also known as: conjugate

Returns its conjugate.

Examples:

Quaternion(1, 2, 3, 4).conj #=> (1-2i-3j-4k)


20
21
22
# File 'lib/quaternion_c2/unary.rb', line 20

def conj
	__new__(@a.conj, -@b)
end

#denominatorInteger

Returns the denominator (lcm of all components’ denominators).

See Also:



79
80
81
82
83
# File 'lib/quaternion_c2/utils.rb', line 79

def denominator
	ad = @a.denominator
	bd = @b.denominator
	ad.lcm(bd)
end

#eql?(other) ⇒ Boolean

Returns true if two quaternions have same reals.



30
31
32
33
34
35
36
# File 'lib/quaternion_c2/equality.rb', line 30

def eql?(other)
	if other.kind_of?(Quaternion)
		@a.eql?(other.a) && @b.eql?(other.b)
	else
		false
	end
end

#fdiv(other) ⇒ Quaternion

Performs division as each part is a float, never returns a float.



83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/quaternion_c2/arithmetic.rb', line 83

[:quo, :fdiv].each do |sym|
	define_method(sym) do |other|
		if other.kind_of?(Quaternion)
			self * other.conj.send(sym, other.abs2)
		elsif other.kind_of?(Numeric) && other.complex?
			__new__(@a.send(sym, other), @b.send(sym, other.conj))
		else
			n1, n2 = other.coerce(self)
			n1.send(sym, n2)
		end
	end
end

#finite?Boolean

Returns true if its magnitude is finite, oterwise returns false.



17
18
19
# File 'lib/quaternion_c2/attributes.rb', line 17

def finite?
	abs.finite?
end

#hashInteger

Returns a hash.



43
44
45
46
# File 'lib/quaternion_c2/equality.rb', line 43

def hash
	# q1.eql?(q2) -> q1.hash == q2.hash
	[@a, @b].hash
end

#hrect[Real, Real, Real, Real] Also known as: hyperrectangular

Returns an array of four real numbers.

Examples:

Quaternion('1+2i+3j+4k').hrect #=> [1, 2, 3, 4]


119
120
121
# File 'lib/quaternion_c2/conversion.rb', line 119

def hrect
	rect.flat_map(&:rect)
end

#imagVector Also known as: imaginary, vector

Returns the imaginary part as a 3-D vector.

Examples:

Quaternion('1+2i+3j+4k').imag #=> Vector[2, 3, 4]


145
146
147
# File 'lib/quaternion_c2/conversion.rb', line 145

def imag
	Vector[*hrect.drop(1)]
end

#infinite?nil, +1

Returns values corresponding to the value of its magnitude.



29
30
31
# File 'lib/quaternion_c2/attributes.rb', line 29

def infinite?
	abs.infinite?
end

#inspectString

Returns the value as a string for inspection.

Examples:

str = '1-2i-3/4j+0.56k'
q = str.to_q #=>  (1-2i-(3/4)*j+0.56k)
q.inspect    #=> "(1-2i-(3/4)*j+0.56k)"


99
100
101
# File 'lib/quaternion_c2/to_type.rb', line 99

def inspect
	"(#{__format__(:inspect)})"
end

#numeratorQuaternion

Returns the numerator.

1   1    1    3     4-6i-12j+9k <- numerator
- - -i - -j + -k -> -----------
3   2    1    4         12      <- denominator

Examples:

q = Quaternion('1/3-1/2i-j+3/4k') #=> ((1/3)-(1/2)*i-1j+(3/4)*k)
n = q.numerator                   #=> (4-6i-12j+9k)
d = q.denominator                 #=> 12
n / d == q                        #=> true


100
101
102
103
104
105
106
107
# File 'lib/quaternion_c2/utils.rb', line 100

def numerator
	an = @a.numerator
	bn = @b.numerator
	ad = @a.denominator
	bd = @b.denominator
	abd = ad.lcm(bd)
	__new__(an * (abd / ad), bn * (abd / bd))
end

#polar[Real, Real, Vector]

Returns an array; [q.abs, q.arg, q.axis].

Examples:

Quaternion('1+i+j+k').polar
#=> [2.0, Math::PI/3, Vector[1,1,1].normalize]


202
203
204
# File 'lib/quaternion_c2/conversion.rb', line 202

def polar
	[abs, arg, axis]
end

#quo(other) ⇒ Quaternion Also known as: /

Performs division.

Raises:

  • (ZeroDivisionError)

    if other is exactly zero.



# File 'lib/quaternion_c2/arithmetic.rb', line 64

#rationalize(eps = 0) ⇒ Rational

Returns the value as a rational if possible (the imaginary part should be exactly zero).

Raises:

  • (RangeError)

    if its imaginary part is not exactly zero.



71
72
73
# File 'lib/quaternion_c2/to_type.rb', line 71

[:to_f, :to_r, :to_i, :rationalize].each do |sym|
	define_method(sym) { |*args| to_c.send(sym, *args) }
end

#realReal Also known as: scalar

Returns the real part.

Examples:

Quaternion('1+2i+3j+4k').real #=> 1


132
133
134
# File 'lib/quaternion_c2/conversion.rb', line 132

def real
	@a.real
end

#real?Boolean

Returns false.



34
35
36
# File 'lib/quaternion_c2/classification.rb', line 34

def real?
	false
end

#rect[Complex, Complex] Also known as: rectangular

Returns an array of two complex numbers.

Examples:

Quaternion('1+2i+3j+4k').rect #=> [(1+2i), (3+4i)]


106
107
108
# File 'lib/quaternion_c2/conversion.rb', line 106

def rect
	[@a, @b]
end

#to_cComplex

Returns the value as a complex if possible (the discarded part should be exactly zero).

Raises:

  • (RangeError)

    if its yj+zk part is not exactly zero.



27
28
29
30
31
32
# File 'lib/quaternion_c2/to_type.rb', line 27

def to_c
	unless __exact_zero__(@b)
		raise RangeError, "can't convert #{self} into Complex"
	end
	@a
end

#to_fFloat

Returns the value as a float if possible (the imaginary part should be exactly zero).

Raises:

  • (RangeError)

    if its imaginary part is not exactly zero.



# File 'lib/quaternion_c2/to_type.rb', line 34

#to_iInteger

Returns the value as an integer if possible (the imaginary part should be exactly zero).

Raises:

  • (RangeError)

    if its imaginary part is not exactly zero.



# File 'lib/quaternion_c2/to_type.rb', line 52

#to_qself

Returns self.



17
18
19
# File 'lib/quaternion_c2/to_type.rb', line 17

def to_q
	self
end

#to_rRational

Returns the value as a rational if possible (the imaginary part should be exactly zero).

Raises:

  • (RangeError)

    if its imaginary part is not exactly zero.



# File 'lib/quaternion_c2/to_type.rb', line 43

#to_sString

Returns the value as a string.

Examples:

str = '1-2i-3/4j+0.56k'
q = str.to_q #=> (1-2i-(3/4)*j+0.56k)
q.to_s       #=> "1-2i-3/4j+0.56k"


85
86
87
# File 'lib/quaternion_c2/to_type.rb', line 85

def to_s
	__format__(:to_s)
end