class Quaternion

This class Quaternion is an analogue of Complex.

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.

@see ruby-doc.org/core/Complex.html

Constants

I
J
K
RE_STRICT

Attributes

a[R]
b[R]

Public Class Methods

hrect(w, x = 0, y = 0, z = 0) click to toggle source

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
Also aliased as: hyperrectangular
hyperrectangular(w, x = 0, y = 0, z = 0)
Alias for: hrect
polar(r, theta = 0, vector = Vector[1, 0, 0]) click to toggle source

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
rect(a, b = 0) click to toggle source

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
Also aliased as: rectangular
rectangular(a, b = 0)
Alias for: rect

Private Class Methods

new(a, b) click to toggle source

@!visibility private

# File lib/quaternion_c2/base.rb, line 21
def initialize(a, b)
        @a = a.to_c
        @b = b.to_c
end
parse(str, strict = false) click to toggle source
# 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

*(other) click to toggle source

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
**(index) click to toggle source

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
+(other) click to toggle source

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
-(other) click to toggle source

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
-@() click to toggle source

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
==(other) click to toggle source

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
abs() click to toggle source

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
Also aliased as: magnitude
abs2() click to toggle source

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
angle()
Alias for: arg
arg() click to toggle source

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
Also aliased as: angle, phase
axis() click to toggle source

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
coerce(other) click to toggle source

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
complex?() click to toggle source

Returns false.

# File lib/quaternion_c2/classification.rb, line 41
def complex?
        false
end
conj() click to toggle source

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
Also aliased as: conjugate
conjugate()
Alias for: conj
denominator() click to toggle source

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
eql?(other) click to toggle source

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
finite?() click to toggle source

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
hash() click to toggle source

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
hrect() click to toggle source

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
Also aliased as: hyperrectangular
hyperrectangular()
Alias for: hrect
imag() click to toggle source

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
Also aliased as: imaginary, vector
imaginary()
Alias for: imag
infinite?() click to toggle source

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
inspect() click to toggle source

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
magnitude()
Alias for: abs
numerator() click to toggle source

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
phase()
Alias for: arg
polar() click to toggle source

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
real() click to toggle source

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
Also aliased as: scalar
real?() click to toggle source

Returns false.

# File lib/quaternion_c2/classification.rb, line 34
def real?
        false
end
rect() click to toggle source

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
Also aliased as: rectangular
rectangular()
Alias for: rect
scalar()
Alias for: real
to_c() click to toggle source

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
to_q() click to toggle source

Returns self.

@return [self]

# File lib/quaternion_c2/to_type.rb, line 17
def to_q
        self
end
to_s() click to toggle source

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
vector()
Alias for: imag

Private Instance Methods

__exact_zero__(x) click to toggle source
# File lib/quaternion_c2/unary.rb, line 72
def __exact_zero__(x)
        1 / x
rescue ZeroDivisionError
        true
else
        false
end
__format__(sym) click to toggle source
# 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
__new__(a, b) click to toggle source
# File lib/quaternion_c2/base.rb, line 31
def __new__(a, b)
        Quaternion.send(:new, a, b)
end
__reciprocal__() click to toggle source
# File lib/quaternion_c2/unary.rb, line 80
def __reciprocal__
        d2 = abs2
        __new__(@a.conj.quo(d2), @b.quo(-d2))
end