class Quaternion
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
.
Constants
- I
- J
- K
- RE_STRICT
Attributes
Public Class Methods
Returns a quaternion object w+xi+yj+zk, where all of w, x, y, and z are real.
@param w [Real] @param x [Real] @param y [Real] @param z [Real] @return [Quaternion] @raise [TypeError]
@example
Quaternion.hrect(1, 2, 3, 4) #=> (1+2i+3j+4k)
# 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
Returns a quaternion object which denotes the given polar form. The actual angle is recognized as +theta * vector.norm+.
@param r [Real] absolute value @param theta [Real] angle in radians @param vector [Enumerable] 3-D vector @return [Quaternion] @raise [TypeError]
@example
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
# 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
Returns a quaternion object a+bj, where both a and b are complex (or real).
@param a [Complex] @param b [Complex] @return [Quaternion] @raise [TypeError]
@example
Quaternion.rect(1, Complex::I) #=> (1+0i+0j+1k)
# 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
Private Class Methods
@!visibility private
# File lib/quaternion_c2/base.rb, line 21 def initialize(a, b) @a = a.to_c @b = b.to_c end
# File lib/quaternion_c2/to_type.rb, line 145 def parse(str, strict = false) regexp = strict ? RE_STRICT : RE # regexp.match(str) always succeeds (even if str is empty) components = regexp.match(str).captures[0,4] components = components.reverse.drop_while(&:nil?).reverse if strict && (!$'.empty? || components.empty?) raise ArgumentError, "invalid value for convert(): #{str.inspect}" end components.collect! do |s| case s when %r{/} then s.to_r when %r{[.eE]} then s.to_f when '+', '' then 1 when '-' then -1 else s.to_i # Integer or nil end end # returns the parsed number as a preferred type case components.size when 0 0 when 1 components[0] when 2 Complex.rect(*components) else # 3 or 4 w, x, y, z = components new(Complex.rect(w, x), Complex.rect(y, z || 0)) end end
Public Instance Methods
Performs multiplication.
@param other [Numeric] @return [Quaternion]
# 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
Performs exponentiation.
@param index [Numeric] @return [Quaternion]
@example
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)
# 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
Performs addition.
@param other [Numeric] @return [Quaternion]
# 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
Performs subtraction.
@param other [Numeric] @return [Quaternion]
# 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
Returns negation of the value.
@return [Quaternion]
@example
-Quaternion(1, 2, 3, 4) #=> (-1-2i-3j-4k)
# File lib/quaternion_c2/unary.rb, line 20 def -@ __new__(-@a, -@b) end
Returns true if it equals to the other numerically.
@param other [Object] @return [Boolean]
# 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
Returns the absolute part of its polar form.
@return [Real]
@example
Quaternion(-1, 1, -1, 1).abs #=> 2.0
# File lib/quaternion_c2/unary.rb, line 57 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
Returns square of the absolute value.
@return [Real]
@example
Quaternion(-1, 1, -1, 1).abs2 #=> 4
# File lib/quaternion_c2/unary.rb, line 45 def abs2 @a.abs2 + @b.abs2 end
Returns the angle part of its polar form.
@return [Real]
@example
Quaternion('1+i+j+k').arg #=> Math::PI/3
# File lib/quaternion_c2/conversion.rb, line 163 def arg r_cos = real r_sin = imag.norm Math.atan2(r_sin, r_cos) end
Returns the axis part of its polar form.
@return [Vector] normalized 3-D vector
@example
Quaternion('1+i+j+k').axis #=> Vector[1,1,1].normalize
# 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
Performs type conversion.
@param other [Numeric] @return [[Quaternion, self]] @raise [TypeError]
# 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
Returns false.
# File lib/quaternion_c2/classification.rb, line 41 def complex? false end
Returns its conjugate.
@return [Quaternion]
@example
Quaternion(1, 2, 3, 4).conj #=> (1-2i-3j-4k)
# File lib/quaternion_c2/unary.rb, line 32 def conj __new__(@a.conj, -@b) end
Returns the denominator (lcm of all components' denominators). @see numerator
@return [Integer]
# File lib/quaternion_c2/utils.rb, line 79 def denominator ad = @a.denominator bd = @b.denominator ad.lcm(bd) end
Returns true if two quaternions have same reals.
@param other [Object] @return [Boolean]
# 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
Returns true if its magnitude is finite, oterwise returns false.
@return [Boolean]
# File lib/quaternion_c2/attributes.rb, line 17 def finite? abs.finite? end
Returns a hash.
@return [Integer]
# File lib/quaternion_c2/equality.rb, line 43 def hash # q1.eql?(q2) -> q1.hash == q2.hash [@a, @b].hash end
Returns an array of four real numbers.
@return [[Real, Real, Real, Real]]
@example
Quaternion('1+2i+3j+4k').hrect #=> [1, 2, 3, 4]
# File lib/quaternion_c2/conversion.rb, line 119 def hrect rect.flat_map(&:rect) end
Returns the imaginary part as a 3-D vector.
@return [Vector] 3-D vector
@example
Quaternion('1+2i+3j+4k').imag #=> Vector[2, 3, 4]
# File lib/quaternion_c2/conversion.rb, line 145 def imag Vector[*hrect.drop(1)] end
Returns values corresponding to the value of its magnitude.
@return [nil] if finite. @return [+1] if positive infinity.
# File lib/quaternion_c2/attributes.rb, line 29 def infinite? abs.infinite? end
Returns the value as a string for inspection.
@return [String]
@example
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)"
# File lib/quaternion_c2/to_type.rb, line 99 def inspect "(#{__format__(:inspect)})" end
Returns the numerator.
1 1 1 3 4-6i-12j+9k <- numerator - - -i - -j + -k -> ----------- 3 2 1 4 12 <- denominator
@return [Quaternion]
@example
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
# 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
Returns an array; +[q.abs, q.arg, q.axis]+.
@return [[Real, Real, Vector]]
@example
Quaternion('1+i+j+k').polar #=> [2.0, Math::PI/3, Vector[1,1,1].normalize]
# File lib/quaternion_c2/conversion.rb, line 202 def polar [abs, arg, axis] end
Returns the real part.
@return [Real]
@example
Quaternion('1+2i+3j+4k').real #=> 1
# File lib/quaternion_c2/conversion.rb, line 132 def real @a.real end
Returns false.
# File lib/quaternion_c2/classification.rb, line 34 def real? false end
Returns an array of two complex numbers.
@return [[Complex, Complex]]
@example
Quaternion('1+2i+3j+4k').rect #=> [(1+2i), (3+4i)]
# File lib/quaternion_c2/conversion.rb, line 106 def rect [@a, @b] end
Returns the value as a complex if possible (the discarded part should be exactly zero).
@return [Complex] @raise [RangeError] if its yj+zk part is not exactly zero.
# 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
Returns self.
@return [self]
# File lib/quaternion_c2/to_type.rb, line 17 def to_q self end
Returns the value as a string.
@return [String]
@example
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"
# File lib/quaternion_c2/to_type.rb, line 85 def to_s __format__(:to_s) end
Private Instance Methods
# File lib/quaternion_c2/unary.rb, line 72 def __exact_zero__(x) 1 / x rescue ZeroDivisionError true else false end
# File lib/quaternion_c2/to_type.rb, line 105 def __format__(sym) w, *xyz = [@a, @b].flat_map(&:rect) sw = w.send(sym) sx, sy, sz = xyz.zip(%w[i j k]).collect do |num,base| if num.kind_of?(Float) str = num.send(sym) str[0,0] = '+' if str[0] != '-' elsif num < 0 str = '-' + (-num).send(sym) else str = '+' + num.send(sym) end str << '*' if str[-1] !~ /\d/ str << base end [sw, sx, sy, sz].join end
# File lib/quaternion_c2/base.rb, line 31 def __new__(a, b) Quaternion.send(:new, a, b) end
# File lib/quaternion_c2/unary.rb, line 80 def __reciprocal__ d2 = abs2 __new__(@a.conj.quo(d2), @b.quo(-d2)) end