use crate::{
constants::SPEED_OF_LIGHT_KM_S,
errors::{AberrationSnafu, VelocitySnafu},
math::{rotate_vector, Vector3},
};
use core::fmt;
#[cfg(feature = "analysis")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "python")]
use pyo3::prelude::*;
use snafu::ensure;
use super::PhysicsResult;
use crate::errors::PhysicsError;
#[derive(Copy, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "analysis", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "python", pyclass)]
#[cfg_attr(feature = "python", pyo3(module = "anise"))]
pub struct Aberration {
pub converged: bool,
pub stellar: bool,
pub transmit_mode: bool,
}
impl Aberration {
pub const NONE: Option<Self> = None;
pub const LT: Option<Self> = Some(Self {
converged: false,
stellar: false,
transmit_mode: false,
});
pub const LT_S: Option<Self> = Some(Self {
converged: false,
stellar: true,
transmit_mode: false,
});
pub const CN: Option<Self> = Some(Self {
converged: true,
stellar: false,
transmit_mode: false,
});
pub const CN_S: Option<Self> = Some(Self {
converged: true,
stellar: true,
transmit_mode: false,
});
pub const XLT: Option<Self> = Some(Self {
converged: false,
stellar: false,
transmit_mode: true,
});
pub const XLT_S: Option<Self> = Some(Self {
converged: false,
stellar: true,
transmit_mode: true,
});
pub const XCN: Option<Self> = Some(Self {
converged: true,
stellar: false,
transmit_mode: true,
});
pub const XCN_S: Option<Self> = Some(Self {
converged: true,
stellar: true,
transmit_mode: true,
});
pub fn new(flag: &str) -> PhysicsResult<Option<Self>> {
match flag.trim() {
"NONE" => Ok(Self::NONE),
"LT" => Ok(Self::LT),
"LT+S" => Ok(Self::LT_S),
"CN" => Ok(Self::CN),
"CN+S" => Ok(Self::CN_S),
"XLT" => Ok(Self::XLT),
"XLT+S" => Ok(Self::XLT_S),
"XCN" => Ok(Self::XCN),
"XCN+S" => Ok(Self::XCN_S),
_ => Err(PhysicsError::AberrationError {
action: "unknown aberration configuration name",
}),
}
}
}
#[cfg(feature = "python")]
#[pymethods]
impl Aberration {
#[new]
fn py_new(name: String) -> PhysicsResult<Self> {
match Self::new(&name)? {
Some(ab_corr) => Ok(ab_corr),
None => Err(PhysicsError::AberrationError {
action: "just uses `None` in Python instead",
}),
}
}
fn __eq__(&self, other: &Self) -> bool {
self == other
}
fn __str__(&self) -> String {
format!("{self}")
}
fn __repr__(&self) -> String {
format!("{self:?} (@{self:p})")
}
#[getter]
fn get_converged(&self) -> PyResult<bool> {
Ok(self.converged)
}
#[setter]
fn set_converged(&mut self, converged: bool) -> PyResult<()> {
self.converged = converged;
Ok(())
}
#[getter]
fn get_stellar(&self) -> PyResult<bool> {
Ok(self.stellar)
}
#[setter]
fn set_stellar(&mut self, stellar: bool) -> PyResult<()> {
self.stellar = stellar;
Ok(())
}
#[getter]
fn get_transmit_mode(&self) -> PyResult<bool> {
Ok(self.transmit_mode)
}
#[setter]
fn set_transmit_mode(&mut self, transmit_mode: bool) -> PyResult<()> {
self.transmit_mode = transmit_mode;
Ok(())
}
}
impl fmt::Debug for Aberration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.transmit_mode {
write!(f, "X")?;
}
if self.converged {
write!(f, "CN")?;
} else {
write!(f, "LT")?;
}
if self.stellar {
write!(f, "+S")?;
}
Ok(())
}
}
impl fmt::Display for Aberration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.converged {
write!(f, "converged ")?;
} else {
write!(f, "unconverged ")?;
}
write!(f, "light-time ")?;
if self.stellar {
write!(f, "and stellar aberration")?;
} else {
write!(f, "aberration")?;
}
if self.transmit_mode {
write!(f, " in transmit mode")?;
}
Ok(())
}
}
pub fn stellar_aberration(
target_pos_km: Vector3,
obs_wrt_ssb_vel_km_s: Vector3,
ab_corr: Aberration,
) -> PhysicsResult<Vector3> {
ensure!(
ab_corr.stellar,
AberrationSnafu {
action: "stellar correction not available for this aberration"
}
);
let obs_velocity_km_s = if ab_corr.transmit_mode {
-obs_wrt_ssb_vel_km_s
} else {
obs_wrt_ssb_vel_km_s
};
let vbyc = obs_velocity_km_s / SPEED_OF_LIGHT_KM_S;
ensure!(
vbyc.dot(&vbyc) < 1.0,
VelocitySnafu {
action: "observer moving at or faster than light, cannot compute stellar aberration"
}
);
let u = target_pos_km.normalize();
let h = u.cross(&vbyc);
let mut app_target_pos_km = target_pos_km;
let sin_phi = h.norm();
if sin_phi > f64::EPSILON {
let phi = sin_phi.asin();
app_target_pos_km = rotate_vector(&target_pos_km, &h, phi);
}
Ok(app_target_pos_km)
}
#[cfg(test)]
mod ut_aberration {
#[test]
fn test_display() {
use super::Aberration;
assert_eq!(format!("{:?}", Aberration::LT.unwrap()), "LT");
assert_eq!(format!("{:?}", Aberration::LT_S.unwrap()), "LT+S");
assert_eq!(format!("{:?}", Aberration::CN.unwrap()), "CN");
assert_eq!(format!("{:?}", Aberration::CN_S.unwrap()), "CN+S");
assert_eq!(format!("{:?}", Aberration::XLT.unwrap()), "XLT");
assert_eq!(format!("{:?}", Aberration::XLT_S.unwrap()), "XLT+S");
assert_eq!(format!("{:?}", Aberration::XCN.unwrap()), "XCN");
assert_eq!(format!("{:?}", Aberration::XCN_S.unwrap()), "XCN+S");
}
}