use super::Perplex;
use num_traits::{Float, Num, One, Pow};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub enum HyperbolicSector<T> {
#[default]
Right,
Up,
Left,
Down,
Diagonal(T),
}
impl<T: Copy + Float> From<Perplex<T>> for HyperbolicSector<T> {
#[inline]
fn from(z: Perplex<T>) -> Self {
let Perplex { t, x } = z;
let (t_abs, x_abs) = (t.abs(), x.abs());
if t_abs == x_abs {
Self::Diagonal(t)
} else if t_abs > x_abs {
if t > T::zero() {
Self::Right
} else {
Self::Left
}
} else if x > T::zero() {
Self::Up
} else {
Self::Down
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct HyperbolicPolar<T> {
pub rho: T,
pub theta: T,
pub sector: HyperbolicSector<T>,
}
impl<T: Copy + Num> Default for HyperbolicPolar<T> {
#[inline]
fn default() -> Self {
Self {
rho: T::one(),
theta: T::zero(),
sector: HyperbolicSector::Right,
}
}
}
impl<T: Copy + Float> From<Perplex<T>> for HyperbolicPolar<T> {
#[inline]
fn from(z: Perplex<T>) -> Self {
Self {
rho: z.norm(),
theta: z.arg(),
sector: HyperbolicSector::from(z),
}
}
}
impl<T: Copy + Float> From<HyperbolicPolar<T>> for Perplex<T> {
#[inline]
fn from(polar: HyperbolicPolar<T>) -> Self {
let HyperbolicPolar { rho, theta, sector } = polar;
match sector {
HyperbolicSector::Right => Self::new(rho * theta.cosh(), rho * theta.sinh()),
HyperbolicSector::Up => Self::new(rho * theta.sinh(), rho * theta.cosh()),
HyperbolicSector::Left => Self::new(-rho * theta.cosh(), -rho * theta.sinh()),
HyperbolicSector::Down => Self::new(-rho * theta.sinh(), -rho * theta.cosh()),
HyperbolicSector::Diagonal(t) => {
if theta == T::infinity() {
Self::new(t, t)
} else {
Self::new(t, -t)
}
}
}
}
}
impl<T: Copy + Float> Perplex<T> {
#[inline]
pub fn cis(theta: T) -> Self {
Self::new(theta.cosh(), theta.sinh())
}
#[inline]
pub fn arg(self) -> T {
let Self { t, x } = self;
let (t_abs, x_abs) = (t.abs(), x.abs());
if t_abs == x_abs {
if t == x {
T::infinity()
} else {
T::neg_infinity()
}
} else if t_abs > x_abs {
(x / t).atanh()
} else {
(t / x).atanh()
}
}
#[inline]
pub fn klein(self) -> Option<Self> {
let Self { t, x } = self;
let (t_abs, x_abs) = (t.abs(), x.abs());
if t_abs == x_abs {
None
} else if t_abs > x_abs {
if t > T::zero() {
Some(Self::one())
} else {
Some(-Self::one())
}
} else if x > T::zero() {
Some(Self::h())
} else {
Some(-Self::h())
}
}
#[inline]
pub fn sector(&self) -> HyperbolicSector<T> {
(*self).into()
}
#[inline]
pub fn polar(&self) -> HyperbolicPolar<T> {
(*self).into()
}
}
impl<T: Copy + Float> Pow<u32> for HyperbolicPolar<T> {
type Output = Self;
#[inline]
fn pow(self, exp: u32) -> Self::Output {
match exp {
0 => Self::default(),
1 => self,
_ => {
let n = exp as i32;
let Self { rho, theta, sector } = self;
if let HyperbolicSector::Diagonal(t) = sector {
let t_new = t * (t + t).powi(n - 1); HyperbolicPolar {
rho,
theta,
sector: HyperbolicSector::Diagonal(t_new),
}
} else {
let new_sector = if n % 2 == 0 {
HyperbolicSector::Right } else {
sector
};
HyperbolicPolar {
rho: rho.powi(n), theta: T::from(n).unwrap() * theta,
sector: new_sector,
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use num_traits::*;
#[test]
fn test_polar() {
assert_abs_diff_eq!(
Perplex::<f64>::default(),
Perplex::from(HyperbolicPolar::default()),
epsilon = 0.0001
);
let z = Perplex::new(1.0, 1.0); assert!(z.is_light_like(), "1 + h is light-like!");
assert_eq!(z.arg(), f64::infinity(), "Argument of 1 + h is infinity!");
assert!(
z.klein().is_none(),
"Klein is not defined for light-like numbers!"
);
assert_eq!(
HyperbolicPolar::from(z),
HyperbolicPolar {
rho: 0.0,
theta: f64::infinity(),
sector: HyperbolicSector::Diagonal(1.0)
},
"Polar form of 1 + h!"
);
assert_abs_diff_eq!(z, Perplex::from(HyperbolicPolar::from(z)), epsilon = 0.0001);
let z = Perplex::new(1.0, -1.0); assert!(z.is_light_like(), "1 - h is light-like!");
assert_eq!(
z.arg(),
f64::neg_infinity(),
"Argument of 1 - h is negative infinity!"
);
assert!(
z.klein().is_none(),
"Klein is not defined for light-like numbers!"
);
assert_eq!(
HyperbolicPolar::from(z),
HyperbolicPolar {
rho: 0.0,
theta: f64::neg_infinity(),
sector: HyperbolicSector::Diagonal(1.0)
},
"Polar form of 1 - h!"
);
assert_abs_diff_eq!(z, Perplex::from(HyperbolicPolar::from(z)), epsilon = 0.0001);
let z = Perplex::new(2.0, 1.0); assert!(z.is_time_like(), "2 + h is time-like!");
assert_ne!(z.arg(), 0.0, "2 + h has a non-zero argument!");
assert_eq!(
z.klein().unwrap(),
Perplex::new(1.0, 0.0),
"2 + h is in the right-sector!"
);
assert_abs_diff_eq!(z, Perplex::from(HyperbolicPolar::from(z)), epsilon = 0.0001);
let z = Perplex::new(-2.0, 1.0); assert!(z.is_time_like(), "-2 + h is time-like!");
assert_ne!(z.arg(), 0.0, "-2 + h has a non-zero argument!");
assert_eq!(
z.klein().unwrap(),
Perplex::new(-1.0, 0.0),
"-2 + h is in the left-sector!"
);
assert_abs_diff_eq!(z, Perplex::from(HyperbolicPolar::from(z)), epsilon = 0.0001);
let z = Perplex::new(1.0, 2.0); assert!(z.is_space_like(), "1 + 2h is space-like!");
assert_ne!(z.arg(), 0.0, "1 + 2h has a non-zero argument!");
assert_eq!(
z.klein().unwrap(),
Perplex::new(0.0, 1.0),
"1 + 2h is in the up-sector!"
);
assert_abs_diff_eq!(z, Perplex::from(HyperbolicPolar::from(z)), epsilon = 0.0001);
let z = Perplex::new(1.0, -2.0); assert!(z.is_space_like(), "1 - 2h is space-like!");
assert_ne!(z.arg(), 0.0, "1 - 2h has a non-zero argument!");
assert_eq!(
z.klein().unwrap(),
Perplex::new(0.0, -1.0),
"1 - 2h is in the down-sector!"
);
assert_abs_diff_eq!(z, Perplex::from(HyperbolicPolar::from(z)), epsilon = 0.0001);
let z = Perplex::cis(f64::PI() / 2.0);
assert_abs_diff_eq!(z, Perplex::from(HyperbolicPolar::from(z)), epsilon = 0.0001);
}
fn polar_mul_test_loop(z: Perplex<f64>) {
let polar = HyperbolicPolar::from(z);
assert_abs_diff_eq!(Perplex::default(), Perplex::from(polar.pow(0)));
assert_abs_diff_eq!(z, Perplex::from(polar.pow(1)), epsilon = 0.0001);
assert_abs_diff_eq!(z * z, Perplex::from(polar.pow(2)), epsilon = 0.0001);
assert_abs_diff_eq!(z * z * z, Perplex::from(polar.pow(3)), epsilon = 0.0001);
assert_abs_diff_eq!(z * z * z * z, Perplex::from(polar.pow(4)), epsilon = 0.0001);
}
#[test]
fn test_polar_multiplication() {
let z = Perplex::new(1.0, 1.0); polar_mul_test_loop(z);
let z = Perplex::new(1.0, -1.0); polar_mul_test_loop(z);
let z = Perplex::new(2.0, 1.0); polar_mul_test_loop(z);
polar_mul_test_loop(z.inv().unwrap());
let z = Perplex::new(-2.0, 1.0); polar_mul_test_loop(z);
polar_mul_test_loop(z.inv().unwrap());
let z = Perplex::new(1.0, 2.0); polar_mul_test_loop(z);
polar_mul_test_loop(z.inv().unwrap());
let z = Perplex::new(1.0, -2.0); polar_mul_test_loop(z);
polar_mul_test_loop(z.inv().unwrap());
let z = Perplex::cis(f64::PI() / 2.0);
polar_mul_test_loop(z);
polar_mul_test_loop(z.inv().unwrap());
}
#[test]
fn test_polar_sector() {
let perplex = Perplex::new(1.0, 0.5);
assert_eq!(perplex.sector(), HyperbolicSector::Right);
let polar = perplex.polar();
assert_eq!(polar.rho, f64::sqrt(0.75));
assert_eq!(polar.theta, perplex.arg());
assert_eq!(polar.sector, HyperbolicSector::Right);
}
}