use approx::AbsDiffEq;
use num_traits::float::FloatCore;
use num_traits::{Float, Num, One, Zero};
use std::fmt;
use std::ops::Neg;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Perplex<T> {
pub t: T,
pub x: T,
}
impl<T> Perplex<T> {
#[inline]
pub fn new(t: T, x: T) -> Self {
Self { t, x }
}
}
impl<T: Copy + Neg<Output = T> + PartialOrd + Num + fmt::Display> fmt::Display for Perplex<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (x, sign) = if self.x < T::zero() {
(-self.x, "-")
} else {
(self.x, "+")
};
match f.precision() {
Some(p) => write!(f, "{:.*} {sign} {:.*} h", p, self.t, p, x,),
None => {
let t_pretty = format!("{:.1$}", self.t, 2);
let x_pretty = format!("{:.1$}", x, 2);
write!(f, "{} {sign} {} h", t_pretty, x_pretty)
}
}
}
}
impl<T: AbsDiffEq> AbsDiffEq for Perplex<T>
where
T::Epsilon: Copy,
{
type Epsilon = T::Epsilon;
#[inline]
fn default_epsilon() -> Self::Epsilon {
T::default_epsilon()
}
#[inline]
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
T::abs_diff_eq(&self.t, &other.t, epsilon) && T::abs_diff_eq(&self.x, &other.x, epsilon)
}
}
impl<T: Copy + Num> Default for Perplex<T> {
#[inline]
fn default() -> Self {
Self::new(T::one(), T::zero())
}
}
impl<T: Copy + Num> From<T> for Perplex<T> {
#[inline]
fn from(t: T) -> Self {
Self::new(t, T::zero())
}
}
impl<T: Copy + Num> Perplex<T> {
#[inline]
pub fn h() -> Self {
Self::new(T::zero(), T::one())
}
#[inline]
pub fn real(&self) -> T {
self.t
}
#[inline]
pub fn hyperbolic(&self) -> T {
self.x
}
#[inline]
pub fn squared_distance(&self) -> T {
self.t * self.t - self.x * self.x
}
#[inline]
pub fn scale(&self, factor: T) -> Self {
Self::new(factor * self.t, factor * self.x)
}
}
impl<T: Copy + Num + PartialOrd> Perplex<T> {
#[inline]
pub fn is_time_like(&self) -> bool {
self.squared_distance() > T::zero()
}
#[inline]
pub fn is_space_like(&self) -> bool {
self.squared_distance() < T::zero()
}
#[inline]
pub fn is_light_like(&self) -> bool {
self.squared_distance() == T::zero()
}
}
impl<T: Copy + Num + Neg<Output = T>> Perplex<T> {
#[inline]
pub fn conj(&self) -> Self {
Self::new(self.t, -self.x)
}
#[inline]
pub fn try_inverse(&self) -> Option<Self> {
let squared_distance = self.squared_distance();
if squared_distance == T::zero() {
None
} else {
Some(Self::new(
self.t / squared_distance,
-self.x / squared_distance,
))
}
}
}
impl<T: Copy + Float> Perplex<T> {
#[inline]
pub fn l1_norm(&self) -> T {
self.t.abs() + self.x.abs()
}
#[inline]
pub fn l2_norm(&self) -> T {
(self.t * self.t + self.x * self.x).sqrt()
}
#[inline]
pub fn max_norm(&self) -> T {
self.t.abs().max(self.x.abs())
}
#[inline]
pub fn modulus(self) -> T {
let d_z = self.squared_distance();
d_z.abs().sqrt()
}
#[inline]
pub fn norm(self) -> T {
self.modulus()
}
#[inline]
pub fn magnitude(self) -> T {
self.modulus()
}
#[inline]
pub fn exp(self) -> Self {
let k = self.klein().unwrap_or(Perplex::one());
let Self { t, x } = k * self;
let t_exp = t.exp();
k * Self::new(t_exp * x.cosh(), t_exp * x.sinh())
}
#[inline]
pub fn ln(self) -> Option<Self> {
self.klein().map(|k| {
let Self { t, x } = k * self;
let squared_distance = t * t - x * x;
let two = T::one() + T::one();
let t_new = squared_distance.ln() / two;
let x_new = (x / t).atanh();
k * Self::new(t_new, x_new)
})
}
#[inline]
pub fn log(self, base: T) -> Option<Self> {
self.ln().map(|z| z / base.ln())
}
#[inline]
pub fn sqrt(self) -> Option<Self> {
let t_x_add = self.t + self.x;
let t_x_sub = self.t - self.x;
if t_x_add >= T::zero() && t_x_sub >= T::zero() {
let sqrt_add = t_x_add.sqrt();
let sqrt_sub = t_x_sub.sqrt();
let two = T::one() + T::one();
let t = (sqrt_add + sqrt_sub) / two;
let x = (sqrt_add - sqrt_sub) / two;
Some(Perplex::new(t, x))
} else {
None
}
}
#[inline]
pub fn sin(self) -> Self {
Self::new(self.t.sin() * self.x.cos(), self.t.cos() * self.x.sin())
}
#[inline]
pub fn cos(self) -> Self {
Self::new(self.t.cos() * self.x.cos(), self.t.sin() * self.x.sin())
}
#[inline]
pub fn tan(self) -> Option<Self> {
self.sin() / self.cos()
}
#[inline]
pub fn sinh(self) -> Self {
Self::new(self.t.sinh() * self.x.cosh(), self.t.cosh() * self.x.sinh())
}
#[inline]
pub fn cosh(self) -> Self {
Self::new(self.t.cosh() * self.x.cosh(), self.t.sinh() * self.x.sinh())
}
#[inline]
pub fn tanh(self) -> Option<Self> {
self.sinh() / self.cosh()
}
}
impl<T: FloatCore> Perplex<T> {
#[inline]
pub fn is_nan(self) -> bool {
self.t.is_nan() || self.x.is_nan()
}
#[inline]
pub fn is_infinite(self) -> bool {
!self.is_nan() && (self.t.is_infinite() || self.x.is_infinite())
}
#[inline]
pub fn is_finite(self) -> bool {
self.t.is_finite() && self.x.is_finite()
}
#[inline]
pub fn is_normal(self) -> bool {
self.t.is_normal() && self.x.is_normal()
}
}
impl<T: Copy + Num> Zero for Perplex<T> {
#[inline]
fn zero() -> Self {
Self::new(Zero::zero(), Zero::zero())
}
#[inline]
fn is_zero(&self) -> bool {
self.t.is_zero() && self.x.is_zero()
}
#[inline]
fn set_zero(&mut self) {
self.t.set_zero();
self.x.set_zero();
}
}
impl<T: Copy + Num> One for Perplex<T> {
#[inline]
fn one() -> Self {
Self::new(One::one(), Zero::zero())
}
#[inline]
fn is_one(&self) -> bool {
self.t.is_one() && self.x.is_zero()
}
#[inline]
fn set_one(&mut self) {
self.t.set_one();
self.x.set_zero();
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use num_traits::*;
#[test]
fn test_display() {
let z = Perplex::new(1.1235, 1.10);
assert_eq!(
format!("{:.3}", z),
String::from("1.123 + 1.100 h"),
"Precision specifier produces 3 decimal places!"
);
assert_eq!(
format!("{:.1}", z),
String::from("1.1 + 1.1 h"),
"Precision specifier produces 1 decimal place!"
);
let z = Perplex::new(2.0, -1.0);
assert_eq!(z.to_string(), String::from("2.00 - 1.00 h"), "Negation sign is used for negative space component! Per default, fmt produces two decimal places!");
}
#[test]
fn test_components() {
let z = Perplex::new(1.1, 2.2);
assert_eq!(z.real(), 1.1);
assert_eq!(z.hyperbolic(), 2.2);
assert_eq!(z.scale(2.0), Perplex::new(2.2, 4.4));
assert_eq!(Perplex::from(2.0), Perplex::new(2.0, 0.0), "Converting a number t into a Perplex yields time-component t and zero space component!")
}
#[test]
fn test_norm() {
let z = Perplex::new(2.0, -1.0);
assert!(z.is_time_like());
assert_eq!(z.modulus(), f64::sqrt(3.0), "2 - h has a norm of √3");
let z = Perplex::new(1.0, -1.0);
assert!(z.is_light_like());
assert_eq!(z.magnitude(), f64::zero(), "1 - h has a norm of zero");
let z = Perplex::new(-1.0, 2.0);
assert!(z.is_space_like());
assert_eq!(z.norm(), f64::sqrt(3.0), "-1 + 2h has a norm of √3");
assert_eq!(z.l1_norm(), 3.0, "-1 + 2h has a l1 norm of 3");
assert_eq!(z.l2_norm(), f64::sqrt(5.0), "-1 + 2h has a l2 norm of √5");
assert_eq!(z.max_norm(), 2.0, "-1 + 2h has a max norm of 2");
}
#[test]
fn test_log() {
let z = Perplex::new(2.0, 1.0);
let z_ln = z.ln().unwrap();
let z_log = z.log(2.0).unwrap();
assert_eq!(z_log, z_ln / f64::ln(2.0));
}
#[test]
fn test_logarithm_exponential() {
let z = Perplex::new(2.0, 1.0); let ln_result = z.ln();
assert!(
ln_result.is_some(),
"Natural logarithm is defined for time-like 2 + h!"
);
let z_ln_exp = ln_result.unwrap().exp();
assert_abs_diff_eq!(z_ln_exp, z);
let z = Perplex::new(-2.0, 1.0); let ln_result = z.ln();
assert!(
ln_result.is_some(),
"Natural logarithm is defined for time-like -2 + h!"
);
let z_ln_exp = ln_result.unwrap().exp();
assert_abs_diff_eq!(z_ln_exp, z);
let z = Perplex::new(1.0, 2.0); let ln_result = z.ln();
assert!(
ln_result.is_some(),
"Natural logarithm is defined for space-like 1 + 2h!"
);
let z_ln_exp = ln_result.unwrap().exp();
assert_abs_diff_eq!(z_ln_exp, z);
let z = Perplex::new(1.0, -2.0); let ln_result = z.ln();
assert!(
ln_result.is_some(),
"Natural logarithm is defined for space-like 1 - 2h!"
);
let z_ln_exp = ln_result.unwrap().exp();
assert_abs_diff_eq!(z_ln_exp, z);
}
#[test]
fn test_exponential_logarithm() {
let z = Perplex::new(2.0, 1.0); assert_abs_diff_eq!(z.exp().ln().unwrap(), z, epsilon = 0.00001);
let z = Perplex::new(-2.0, 1.0); assert_abs_diff_eq!(z.exp().ln().unwrap(), z, epsilon = 0.00001);
let z = Perplex::new(1.0, 2.0); assert_abs_diff_eq!(z.exp().ln().unwrap(), z, epsilon = 0.00001);
let z = Perplex::new(1.0, -2.0); assert_abs_diff_eq!(z.exp().ln().unwrap(), z, epsilon = 0.00001);
}
#[test]
fn test_trigonometric() {
let pi = f64::PI();
let z = Perplex::new(pi, pi / 2.0).sin();
assert_abs_diff_eq!(z, Perplex::new(0.0, -1.0));
assert!(
z.tan().is_some(),
"Tangens of z should be defined since cos(z) is not light-like!"
);
let zero: Perplex<f64> = Perplex::zero();
assert_abs_diff_eq!(zero.sinh(), zero);
let z = Perplex::new(1.0, 0.0);
let expected_tanh = Perplex::new(z.t.tanh(), 0.0);
assert_eq!(
z.tanh(),
Some(expected_tanh),
"Tanh of z should be defined since cosh(z) is not light-like!"
);
}
#[test]
fn test_sqrt() {
let z_right = Perplex::new(2.0, 1.0);
assert!(
z_right.sqrt().is_some(),
"Sqrt should be defined for Perplex numbers in the Right sector."
);
if let Some(sqrt_z) = z_right.sqrt() {
assert_abs_diff_eq!(sqrt_z.powu(2), z_right, epsilon = 1e-10);
}
let z_left = Perplex::new(-2.0, 1.0);
assert!(
z_left.sqrt().is_none(),
"Sqrt should not be defined for Perplex numbers in the Left sector."
);
}
#[test]
fn test_core() {
let z = Perplex::new(1.0, 2.0);
assert!(
z.is_finite(),
"Perplex number with finite components is finite!"
);
assert!(
z.is_normal(),
"Perplex number with finite components is normal!"
);
assert!(
!z.is_infinite(),
"Perplex number with finite components is not infinite!"
);
assert!(
!z.is_nan(),
"Perplex number with finite components is not NAN!"
);
let z = Perplex::new(f64::NAN, 1.0);
assert!(z.is_nan(), "Perplex number with a NaN component is NAN!")
}
#[test]
fn test_const() {
let mut z = Perplex::new(0.0, 2.0);
assert!(!z.is_one(), "Perplex number a zero component is not one!");
assert!(
!z.is_zero(),
"Perplex number with non-zero component is not zero!"
);
z.set_one();
assert_eq!(Perplex::one(), z, "Perplex number set to one equals one!");
assert!(z.is_one(), "Perplex number set to one is one!");
z.set_zero();
assert_eq!(
Perplex::zero(),
z,
"Perplex number set to zero equals zero!"
);
assert!(z.is_zero(), "Perplex number set to zero is zero!");
assert!(
!z.is_infinite(),
"Perplex number with finite components is not infinite!"
);
assert!(
!z.is_nan(),
"Perplex number with finite components is not NAN!"
);
let z = Perplex::new(f64::NAN, 1.0);
assert!(z.is_nan(), "Perplex number with a NaN component is NAN!")
}
}