Skip to content

Instantly share code, notes, and snippets.

@macroxela
Created February 12, 2025 13:36
Show Gist options
  • Save macroxela/a5a31dfd3ab0f0ad6da6ff950b690f85 to your computer and use it in GitHub Desktop.
Save macroxela/a5a31dfd3ab0f0ad6da6ff950b690f85 to your computer and use it in GitHub Desktop.
Class implementation of a Quaternion i.e. extension of the Complex numbers. Includes arithmetic & comparision operators as well as additional utility methods.
import math
class Quaternion():
a, b, c, d = 0, 0, 0, 0
def __init__(self, real, i = 0, j = 0, k = 0):
if all(isinstance(number, (int, float)) for number in (real, i, j, k)):
self.a = real
self.b = i
self.c = j
self.d = k
else:
raise TypeError(f"Invalid data types: cannot initialize {type(self).__name__} using types other than int or float.")
#String Representations
def __str__(self):
terms = [str(self.a)]
for coeff, basis in zip([self.b, self.c, self.d], ["i", "j", "k"]):
sign = "+" if coeff >= 0 else ""
terms.append(f"{sign}{coeff}{basis}")
return "".join(terms)
def __repr__(self):
return f"Quaternion({self.a}, {self.b}, {self.c}, {self.d})"
#Left Arithmetic Operations
def __add__(self, number):
if isinstance(number, Quaternion):
return Quaternion(self.a + number.a, self.b + number.b, self.c + number.c, self.d + number.d)
if isinstance(number, (int, float)):
return Quaternion(self.a + number, self.b, self.c, self.d)
if isinstance(number, complex):
return Quaternion(self.a + number.real, self.b + number.imag, self.c, self.d)
return NotImplemented
def __sub__(self, number):
if isinstance(number, Quaternion):
return Quaternion(self.a - number.a, self.b - number.b, self.c - number.c, self.d - number.d)
if isinstance(number, (int, float)):
return Quaternion(self.a - number, self.b, self.c, self.d)
if isinstance(number, complex):
return Quaternion(self.a - number.real, self.b - number.imag, self.c, self.d)
return NotImplemented
def __mul__(self, number):
if isinstance(number, Quaternion):
a = self.a * number.a - self.b * number.b - self.c * number.c - self.d * number.d
i = self.a * number.b + self.b * number.a + self.c * number.d - self.d * number.c
j = self.a * number.c - self.b * number.d + self.c * number.a + self.d * number.b
k = self.a * number.d + self.b * number.c - self.c * number.b + self.d * number.a
return Quaternion(a, i, j, k)
if isinstance(number, (int, float)):
return Quaternion(self.a*number, self.b*number, self.c*number, self.d*number)
if isinstance(number, complex):
return self*Quaternion(number.real, number.imag, 0, 0)
return NotImplemented
def __truediv__(self, other):
if isinstance(other, Quaternion):
return self*other.inverse()
if isinstance(other, (int, float)):
return Quaternion(self.a/other, self.b/other, self.c/other, self.d/other)
return NotImplemented
def __neg__(self):
return Quaternion(-self.a, -self.b, -self.c, -self.d)
#Right arithmetic operators
def __radd__(self, other):
return self + other
def __rsub__(self, other):
return -self + other
def __rmul__(self, other):
if isinstance(other, (int, float)):
return Quaternion(self.a*other, self.b*other, self.c*other, self.d*other)
if isinstance(other, complex):
return Quaternion(other.real, other.imag, 0, 0) * self
return NotImplemented
def __rtruediv__(self, other):
if isinstance(other, Quaternion):
return self*other.inverse()
if isinstance(other, (int, float)):
return other*self.inverse()
return NotImplemented
#In-place operators
def __iadd__(self, other):
self = self + other
return self
def __isub__(self, other):
self = self - other
return self
def __imul__(self, other):
self = self * other
return self
def __itruediv__(self, other):
self = self / other
return self
#Comparison Operators
def __lt__(self, other):
if isinstance(other, Quaternion):
return self.magnitude() < other.magnitude()
return NotImplemented
def __gt__(self, other):
if isinstance(other, Quaternion):
return self.magnitude() > other.magnitude()
return NotImplemented
def __le__(self, other):
if isinstance(other, Quaternion):
return self.magnitude() <= other.magnitude()
return NotImplemented
def __ge__(self, other):
if isinstance(other, Quaternion):
return self.magnitude() >= other.magnitude()
return NotImplemented
def __eq__(self, other):
if isinstance(other, Quaternion):
return self.a == other.a and self.b == other.b and self.c == other.c and self.d == other.d
return NotImplemented
def __ne__(self, other):
if isinstance(other, Quaternion):
return self.a != other.a and self.b != other.b and self.c != other.c and self.d != other.d
return NotImplemented
#Type Conversions
def __complex__(self):
if self.c != 0 or self.d != 0:
warnings.simplefilter("once")
warnings.warn("Data may be lost during conversion, nonzero coefficients in j or k", stacklevel = 2)
return complex(self.a, self.b)
def __int__(self):
if self.b != 0 or self.c != 0 or self.d != 0:
warnings.simplefilter("once")
warnings.warn("Data may be lost during conversion, nonzero coefficients in i, j or k", stacklevel = 2)
if isinstance(self.a, float):
warnings.simplefilter("once")
warnings.warn("Data may be lost during conversion, float value in the real component", stacklevel = 2)
return int(self.a)
def __float__(self):
if self.b != 0 or self.c != 0 or self.d != 0:
warnings.simplefilter("once")
warnings.warn("Data may be lost during conversion, nonzero coefficients in i, j or k", stacklevel = 2)
return float(self.a)
#Additional useful methods
def magnitude(self): #Magnitude of the vector representing the quaternion
return math.sqrt(self.a**2 + self.b**2 + self.c**2 + self.d**2)
def inverse(self): #Computes the multiplicative inverse of a quaternion
norm_sq = self.magnitude()**2
return Quaternion(self.a / norm_sq, -self.b / norm_sq, -self.c / norm_sq, -self.d / norm_sq)
def conjugate(self): #Conjugate of a quaternion
return Quaternion(self.a, -1*self.b, -1*self.c, -1*self.d)
def dot(self, other: "Quaternion"): #Dot product of 2 quaternions
return self.a*other.a + self.b*other.b + self.c*other.c + self.d*other.d
def norm(self): #Computes the norm which is the same as magnitude
return self.magnitude()
def normalize(self): #Normalizes a quaternion by making sure its magnitude/norm is 1
norm = self.norm()
if norm == 0:
raise ValueError("Cannot normalize a zero quaternion")
return self * (1 / norm)
#Euler Angles based on XYZ order
def roll(self):
return math.atan2(2 * (self.a * self.b + self.c * self.d), 1 - 2 * (self.b**2 + self.c**2))
def pitch(self):
return math.asin(2*(self.a*self.c - self.b*self.d))
def yaw(self):
return math.atan2(2*(self.a*self.d + self.b*self.c), 1 - 2 * (self.c**2 + self.d**2))
def EulerAngles(self):
return (self.roll(), self.pitch(), self.yaw())
@classmethod
def toQuaternion(cls, other):
if isinstance(other, (int, float)):
return Quaternion(other, 0, 0, 0)
if isinstance(other, complex):
return Quaternion(other.real, other.imag, 0, 0)
raise TypeError(f"Unsupported type, cannot convert {type(other).__name__} to Quaternion. Make sure the values are int, float, complex, or Quaternion")
@classmethod
def identity(cls):
return Quaternion(1, 0, 0, 0)
@classmethod
def zero(cls):
return Quaternion(0, 0, 0, 0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment