Created
February 12, 2025 13:36
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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