use ndarray::Array2;
use serde::{Deserialize, Serialize};
use std::fmt;
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(transparent)]
pub struct OriginalUnits(f64);
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(transparent)]
pub struct StandardizedUnits(f64);
impl OriginalUnits {
pub const fn new(value: f64) -> Self {
Self(value)
}
pub const fn original_value(self) -> f64 {
self.0
}
}
impl StandardizedUnits {
pub const fn new(value: f64) -> Self {
Self(value)
}
pub const fn standardized_value(self) -> f64 {
self.0
}
}
impl fmt::Display for OriginalUnits {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, formatter)
}
}
impl fmt::Display for StandardizedUnits {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, formatter)
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "f64", into = "f64")]
pub struct IsotropicScale(f64);
impl IsotropicScale {
pub const ONE: Self = Self(1.0);
pub fn new(value: f64) -> Result<Self, IsotropicScaleError> {
if value.is_finite() && value > 0.0 && value.recip().is_finite() {
Ok(Self(value))
} else {
Err(IsotropicScaleError { value })
}
}
pub fn get(self) -> f64 {
self.0
}
pub fn reciprocal(self) -> f64 {
self.0.recip()
}
pub fn to_bits(self) -> u64 {
self.0.to_bits()
}
pub fn to_standardized_units(self, value: OriginalUnits) -> StandardizedUnits {
StandardizedUnits(value.original_value() * self.reciprocal())
}
pub fn to_original_units(self, value: StandardizedUnits) -> OriginalUnits {
OriginalUnits(value.standardized_value() * self.0)
}
pub fn standardize(self, coordinates: &mut Array2<f64>) {
let reciprocal = self.reciprocal();
coordinates.mapv_inplace(|value| value * reciprocal);
}
}
impl TryFrom<f64> for IsotropicScale {
type Error = IsotropicScaleError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<IsotropicScale> for f64 {
fn from(value: IsotropicScale) -> Self {
value.get()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct IsotropicScaleError {
value: f64,
}
impl fmt::Display for IsotropicScaleError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"isotropic scale must be positive and finite with a finite reciprocal, got {}",
self.value
)
}
}
impl std::error::Error for IsotropicScaleError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn construction_enforces_the_operational_invariant() {
assert_eq!(IsotropicScale::new(1.0), Ok(IsotropicScale::ONE));
assert!(IsotropicScale::new(f64::MIN_POSITIVE).is_ok());
assert!(IsotropicScale::new(0.0).is_err());
assert!(IsotropicScale::new(-1.0).is_err());
assert!(IsotropicScale::new(f64::NAN).is_err());
assert!(IsotropicScale::new(f64::INFINITY).is_err());
assert!(IsotropicScale::new(f64::from_bits(1)).is_err());
}
#[test]
fn the_two_frames_round_trip_through_the_only_conversion() {
let scale = IsotropicScale::new(4.0).unwrap();
let original = OriginalUnits::new(500.0);
let standardized = scale.to_standardized_units(original);
assert_eq!(standardized, StandardizedUnits::new(125.0));
assert_eq!(scale.to_original_units(standardized), original);
assert_eq!(
scale.to_standardized_units(OriginalUnits::new(125.0)),
StandardizedUnits::new(31.25)
);
}
#[test]
fn wire_representation_is_a_checked_scalar() {
let encoded = serde_json::to_string(&IsotropicScale::new(2.5).unwrap()).unwrap();
assert_eq!(encoded, "2.5");
assert_eq!(
serde_json::from_str::<IsotropicScale>(&encoded).unwrap(),
IsotropicScale::new(2.5).unwrap()
);
assert!(serde_json::from_str::<IsotropicScale>("[2.5,2.5]").is_err());
assert!(serde_json::from_str::<IsotropicScale>("0.0").is_err());
assert!(serde_json::from_str::<IsotropicScale>("-1.0").is_err());
}
}