#![allow(clippy::suspicious_op_assign_impl, clippy::suspicious_arithmetic_impl)]
use std::cmp;
use std::fmt;
use std::ops;
use crate::fraction::{Fraction, ZERO};
pub struct Quaternion {
pub r: Fraction,
pub x: Fraction,
pub y: Fraction,
pub z: Fraction,
}
impl Quaternion {
pub fn new(r: Fraction, x: Fraction, y: Fraction, z: Fraction) -> Quaternion {
Quaternion { r, x, y, z }
}
pub fn conjugate(&self) -> Quaternion {
Quaternion::new(self.r, -self.x, -self.y, -self.z)
}
pub fn norm(&self) -> Fraction {
(self.r * self.r + self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn scale(&self, scale: Fraction) -> Quaternion {
Quaternion::new(
self.r * scale,
self.x * scale,
self.y * scale,
self.z * scale,
)
}
}
impl PartialEq for Quaternion {
fn eq(&self, other: &Quaternion) -> bool {
self.r == other.r && self.x == other.x && self.y == other.y && self.z == other.z
}
}
impl Eq for Quaternion {}
impl PartialOrd for Quaternion {
fn partial_cmp(&self, other: &Quaternion) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Quaternion {
fn cmp(&self, other: &Quaternion) -> cmp::Ordering {
if (self.r >= other.r && self.x >= other.x && self.y >= other.y && self.z > other.z)
|| (self.r >= other.r && self.x >= other.x && self.y > other.y && self.z >= other.z)
|| (self.r >= other.r && self.x > other.x && self.y >= other.y && self.z >= other.z)
|| (self.r > other.r && self.x >= other.x && self.y >= other.y && self.z >= other.z)
{
return cmp::Ordering::Greater;
} else if (self.r <= other.r && self.x <= other.x && self.y <= other.y && self.z < other.z)
|| (self.r <= other.r && self.x <= other.x && self.y < other.y && self.z <= other.z)
|| (self.r <= other.r && self.x < other.x && self.y <= other.y && self.z <= other.z)
|| (self.r < other.r && self.x <= other.x && self.y <= other.y && self.z <= other.z)
{
return cmp::Ordering::Less;
}
cmp::Ordering::Equal
}
}
impl fmt::Debug for Quaternion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut string = String::from("[");
if self.r != ZERO {
string.push_str(&self.r.to_string());
}
if self.x != ZERO {
string.push_str(&self.x.to_string());
string.push('i');
}
if self.y != ZERO {
string.push_str(&self.y.to_string());
string.push('j');
}
if self.z != ZERO {
string.push_str(&self.z.to_string());
string.push('k');
}
string.push(']');
write!(f, "{}", string)
}
}
impl fmt::Display for Quaternion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut string = String::from("(");
if self.r != ZERO {
string.push_str(&self.r.to_string());
}
if self.x != ZERO {
string.push_str(&self.x.to_string());
string.push('i');
}
if self.y != ZERO {
string.push_str(&self.y.to_string());
string.push('j');
}
if self.z != ZERO {
string.push_str(&self.z.to_string());
string.push('k');
}
string.push(')');
write!(f, "{}", string)
}
}
impl ops::Add for Quaternion {
type Output = Quaternion;
fn add(self, other: Quaternion) -> Quaternion {
Quaternion::new(
self.r + other.r,
self.x + other.x,
self.y + other.y,
self.z + other.z,
)
}
}
impl ops::AddAssign for Quaternion {
fn add_assign(&mut self, other: Quaternion) {
self.r += other.r;
self.x += other.x;
self.y += other.y;
self.z += other.z;
}
}
impl ops::Sub for Quaternion {
type Output = Quaternion;
fn sub(self, other: Quaternion) -> Quaternion {
Quaternion::new(
self.r - other.r,
self.x - other.x,
self.y - other.y,
self.z - other.z,
)
}
}
impl ops::SubAssign for Quaternion {
fn sub_assign(&mut self, other: Quaternion) {
self.r -= other.r;
self.x -= other.x;
self.y -= other.y;
self.z -= other.z;
}
}
impl ops::Mul for Quaternion {
type Output = Quaternion;
fn mul(self, other: Quaternion) -> Quaternion {
Quaternion::new(
self.r * other.r - self.x * other.x - self.y * other.y - self.z * other.z,
self.r * other.x + self.x * other.r + self.y * other.z - self.z * other.y,
self.r * other.y + self.y * other.r - self.x * other.z + self.z * other.x,
self.r * other.z + self.z * other.r + self.x * other.y - self.y * other.x,
)
}
}
impl ops::MulAssign for Quaternion {
fn mul_assign(&mut self, other: Quaternion) {
let temp = *self * other;
self.r = temp.r;
self.x = temp.x;
self.y = temp.y;
self.z = temp.z;
}
}
impl ops::Div for Quaternion {
type Output = Quaternion;
fn div(self, other: Quaternion) -> Quaternion {
self * (other.conjugate().scale(other.norm().reciprocal()))
}
}
impl ops::DivAssign for Quaternion {
fn div_assign(&mut self, other: Quaternion) {
let temp = *self / other;
self.r = temp.r;
self.x = temp.x;
self.y = temp.y;
self.z = temp.z;
}
}
impl ops::Neg for Quaternion {
type Output = Quaternion;
fn neg(self) -> Quaternion {
Quaternion::new(-self.r, -self.x, -self.y, -self.z)
}
}
impl Copy for Quaternion {}
impl Clone for Quaternion {
fn clone(&self) -> Quaternion {
*self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_test() {
let quaternion1 = Quaternion {
r: Fraction::new_denom(7, 1),
x: Fraction::new_denom(5, 1),
y: Fraction::new_denom(4, 1),
z: Fraction::new_denom(2, 1),
};
let quaternion2 = Quaternion::new(
Fraction::new_denom(7, 1),
Fraction::new_denom(5, 1),
Fraction::new_denom(4, 1),
Fraction::new_denom(2, 1),
);
assert_eq!(quaternion1, quaternion2);
}
#[test]
fn addition_test() {
let quaternion1 = Quaternion::new(
Fraction::new_denom(7, 1),
Fraction::new_denom(5, 1),
Fraction::new_denom(4, 1),
Fraction::new_denom(2, 1),
);
let quaternion2 = Quaternion::new(
Fraction::new_denom(3, 1),
Fraction::new_denom(2, 1),
Fraction::new_denom(1, 1),
Fraction::new_denom(6, 1),
);
let result_quaternion = Quaternion::new(
Fraction::new_denom(10, 1),
Fraction::new_denom(7, 1),
Fraction::new_denom(5, 1),
Fraction::new_denom(8, 1),
);
assert_eq!(result_quaternion, quaternion1 + quaternion2);
assert_eq!(result_quaternion, quaternion2 + quaternion1);
}
#[test]
fn subtraction_test() {
let quaternion1 = Quaternion::new(
Fraction::new_denom(7, 1),
Fraction::new_denom(5, 1),
Fraction::new_denom(4, 1),
Fraction::new_denom(2, 1),
);
let quaternion2 = Quaternion::new(
Fraction::new_denom(3, 1),
Fraction::new_denom(2, 1),
Fraction::new_denom(1, 1),
Fraction::new_denom(6, 1),
);
let result_quaternion1 = Quaternion::new(
Fraction::new_denom(4, 1),
Fraction::new_denom(3, 1),
Fraction::new_denom(3, 1),
Fraction::new_denom(-4, 1),
);
let result_quaternion2 = Quaternion::new(
Fraction::new_denom(-4, 1),
Fraction::new_denom(-3, 1),
Fraction::new_denom(-3, 1),
Fraction::new_denom(4, 1),
);
assert_eq!(result_quaternion1, quaternion1 - quaternion2);
assert_eq!(result_quaternion2, quaternion2 - quaternion1);
}
#[test]
fn multiplication_test() {
let quaternion1 = Quaternion::new(
Fraction::new_denom(7, 1),
Fraction::new_denom(5, 1),
Fraction::new_denom(4, 1),
Fraction::new_denom(2, 1),
);
let quaternion2 = Quaternion::new(
Fraction::new_denom(3, 1),
Fraction::new_denom(2, 1),
Fraction::new_denom(1, 1),
Fraction::new_denom(6, 1),
);
let result_quaternion1 = Quaternion::new(
Fraction::new_denom(-5, 1),
Fraction::new_denom(51, 1),
Fraction::new_denom(-7, 1),
Fraction::new_denom(45, 1),
);
let result_quaternion2 = Quaternion::new(
Fraction::new_denom(-5, 1),
Fraction::new_denom(7, 1),
Fraction::new_denom(45, 1),
Fraction::new_denom(51, 1),
);
assert_eq!(result_quaternion1, quaternion1 * quaternion2);
assert_eq!(result_quaternion2, quaternion2 * quaternion1);
}
#[test]
fn division_test() {
let quaternion1 = Quaternion::new(
Fraction::new_denom(7, 1),
Fraction::new_denom(5, 1),
Fraction::new_denom(4, 1),
Fraction::new_denom(2, 1),
);
let quaternion2 = Quaternion::new(
Fraction::new_denom(3, 1),
Fraction::new_denom(2, 1),
Fraction::new_denom(1, 1),
Fraction::new_denom(6, 1),
);
let result_quaternion1 = Quaternion::new(
Fraction::new_denom(4_700_000, 707_107),
Fraction::new_denom(-2_100_000, 707_107),
Fraction::new_denom(3_100_000, 707_107),
Fraction::new_denom(-3_300_000, 707_107),
);
let result_quaternion2 = Quaternion::new(
Fraction::new_denom(146_875, 30_298),
Fraction::new_denom(65_625, 30_298),
Fraction::new_denom(-96_875, 30_298),
Fraction::new_denom(103_125, 30_298),
);
assert_eq!(result_quaternion1, quaternion1 / quaternion2);
assert_eq!(result_quaternion2, quaternion2 / quaternion1);
}
#[test]
fn scale_test() {
let quaternion = Quaternion::new(
Fraction::new_denom(3, 1),
Fraction::new_denom(4, 1),
Fraction::new_denom(3, 1),
Fraction::new_denom(4, 1),
);
let scale = Fraction::new_denom(5, 1);
let result_quaternion = Quaternion::new(
Fraction::new_denom(3 * 5, 1),
Fraction::new_denom(4 * 5, 1),
Fraction::new_denom(3 * 5, 1),
Fraction::new_denom(4 * 5, 1),
);
assert_eq!(result_quaternion, quaternion.scale(scale));
}
#[test]
fn conjugate_test() {
let quaternion = Quaternion::new(
Fraction::new_denom(3, 1),
Fraction::new_denom(4, 1),
Fraction::new_denom(3, 1),
Fraction::new_denom(4, 1),
);
let result_quaternion = Quaternion::new(
Fraction::new_denom(3, 1),
Fraction::new_denom(-4, 1),
Fraction::new_denom(-3, 1),
Fraction::new_denom(-4, 1),
);
assert_eq!(result_quaternion, quaternion.conjugate());
}
#[test]
fn norm_test() {
let quaternion = Quaternion::new(
Fraction::new_denom(3, 1),
Fraction::new_denom(4, 1),
Fraction::new_denom(3, 1),
Fraction::new_denom(4, 1),
);
let a = Fraction::new_denom(3, 1);
let b = Fraction::new_denom(4, 1);
let c = Fraction::new_denom(3, 1);
let d = Fraction::new_denom(4, 1);
let result = (a * a + b * b + c * c + d * d).sqrt();
assert_eq!(result, quaternion.norm());
}
}