use num::{traits::Inv, One};
use ordered_float::NotNan;
use proptest::prelude::*;
use std::{
fmt::{Debug, Display},
ops::{Add, Deref, Mul, MulAssign, Neg, Sub},
};
use crate::ATol;
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum CoefficientError {
#[error("Coefficient must be non-zero")]
Zero,
#[error("Coefficient must be finite")]
Infinite,
#[error("Coefficient must be not NaN")]
NaN,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Coefficient(NotNan<f64>);
impl Debug for Coefficient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Coefficient({})", self.0)
}
}
impl Display for Coefficient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Coefficient {
pub fn into_inner(self) -> f64 {
self.0.into_inner()
}
pub fn abs(&self) -> Self {
Self(self.0.abs().try_into().unwrap())
}
}
impl TryFrom<f64> for Coefficient {
type Error = CoefficientError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
if value.is_nan() {
return Err(CoefficientError::NaN);
}
if !value.is_finite() {
return Err(CoefficientError::Infinite);
}
if value == 0.0 {
return Err(CoefficientError::Zero);
}
Ok(Self(NotNan::new(value).unwrap())) }
}
impl From<Coefficient> for f64 {
fn from(coefficient: Coefficient) -> Self {
coefficient.into_inner()
}
}
impl Deref for Coefficient {
type Target = f64;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Add for Coefficient {
type Output = Option<Self>;
fn add(self, rhs: Self) -> Self::Output {
let sum = self.0 + rhs.0;
if sum == 0.0 {
None
} else {
Some(Self(sum))
}
}
}
impl Mul for Coefficient {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self(self.0 * rhs.0)
}
}
impl MulAssign for Coefficient {
fn mul_assign(&mut self, rhs: Self) {
self.0 *= rhs.0;
}
}
impl Neg for Coefficient {
type Output = Self;
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
impl Sub for Coefficient {
type Output = Option<Self>;
fn sub(self, rhs: Self) -> Self::Output {
self + (-rhs)
}
}
impl One for Coefficient {
fn one() -> Self {
Coefficient(NotNan::new(1.0).unwrap())
}
}
impl Inv for Coefficient {
type Output = Self;
fn inv(self) -> Self::Output {
Self(self.0.into_inner().recip().try_into().unwrap())
}
}
impl Arbitrary for Coefficient {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
prop_oneof![Just(1.0), Just(-1.0), -10.0..10.0]
.prop_filter("nonzero", |x: &f64| x.abs() > f64::EPSILON)
.prop_map(|x| Coefficient::try_from(x).unwrap())
.boxed()
}
}
impl PartialEq<f64> for Coefficient {
fn eq(&self, other: &f64) -> bool {
if let Ok(other) = TryInto::<Coefficient>::try_into(*other) {
*self == other
} else {
false
}
}
}
impl PartialOrd<f64> for Coefficient {
fn partial_cmp(&self, other: &f64) -> Option<std::cmp::Ordering> {
Some(self.into_inner().total_cmp(other))
}
}
impl PartialEq<ATol> for Coefficient {
fn eq(&self, other: &ATol) -> bool {
self.into_inner() == other.into_inner()
}
}
impl PartialOrd<ATol> for Coefficient {
fn partial_cmp(&self, other: &ATol) -> Option<std::cmp::Ordering> {
self.into_inner().partial_cmp(&other.into_inner())
}
}