use super::traits::*;
use core::fmt;
use core::ops::{Add, Mul};
use num_traits::{One, Zero};
use ordered_float::OrderedFloat;
use std::str::FromStr;
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RealWeight(OrderedFloat<f64>);
impl RealWeight {
pub fn new(value: f64) -> Self {
Self(OrderedFloat(value))
}
pub fn from_int(value: i32) -> Self {
Self::new(value as f64)
}
pub fn from_fraction(numerator: i32, denominator: i32) -> Self {
assert_ne!(denominator, 0, "Denominator cannot be zero");
Self::new(numerator as f64 / denominator as f64)
}
pub fn as_f64(&self) -> f64 {
*self.0
}
pub fn is_infinite(&self) -> bool {
self.0.is_infinite()
}
pub fn is_nan(&self) -> bool {
self.0.is_nan()
}
pub fn abs(&self) -> Self {
Self::new(self.0.abs())
}
pub fn sqrt(&self) -> Option<Self> {
if *self.0 < 0.0 {
None
} else {
Some(Self::new(self.0.sqrt()))
}
}
pub fn pow(&self, exponent: f64) -> Self {
Self::new(self.0.powf(exponent))
}
}
impl fmt::Display for RealWeight {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.is_infinite() {
if self.0.is_sign_positive() {
write!(f, "∞")
} else {
write!(f, "-∞")
}
} else if self.0.is_nan() {
write!(f, "NaN")
} else {
let value = self.0;
write!(f, "{value}")
}
}
}
impl Zero for RealWeight {
fn zero() -> Self {
Self::new(0.0)
}
fn is_zero(&self) -> bool {
*self.0 == 0.0
}
}
impl One for RealWeight {
fn one() -> Self {
Self::new(1.0)
}
}
impl Add for RealWeight {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::new(*self.0 + *rhs.0)
}
}
impl Mul for RealWeight {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self::new(*self.0 * *rhs.0)
}
}
impl Semiring for RealWeight {
type Value = f64;
fn new(value: Self::Value) -> Self {
Self::new(value)
}
fn value(&self) -> &Self::Value {
&self.0
}
fn properties() -> SemiringProperties {
SemiringProperties {
left_semiring: true,
right_semiring: true,
commutative: true,
idempotent: false, path: false, }
}
fn approx_eq(&self, other: &Self, epsilon: f64) -> bool {
(*self.0 - *other.0).abs() < epsilon
}
}
impl DivisibleSemiring for RealWeight {
fn divide(&self, other: &Self) -> Option<Self> {
if Zero::is_zero(other) {
None
} else {
Some(Self::new(*self.0 / *other.0))
}
}
}
impl InvertibleSemiring for RealWeight {
fn inverse(&self) -> Option<Self> {
if Zero::is_zero(self) {
None
} else {
Some(Self::new(1.0 / *self.0))
}
}
}
impl NaturallyOrderedSemiring for RealWeight {}
impl FromStr for RealWeight {
type Err = std::num::ParseFloatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<f64>().map(Self::new)
}
}
impl Default for RealWeight {
fn default() -> Self {
Self::zero()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_construction() {
let w1 = RealWeight::new(3.5);
assert_eq!(w1.as_f64(), 3.5);
assert_eq!(*w1.value(), 3.5);
let w2 = RealWeight::from_int(42);
assert_eq!(w2, RealWeight::new(42.0));
let w3 = RealWeight::from_fraction(3, 4);
assert_eq!(w3, RealWeight::new(0.75));
}
#[test]
#[should_panic(expected = "Denominator cannot be zero")]
fn test_fraction_zero_denominator() {
RealWeight::from_fraction(1, 0);
}
#[test]
fn test_semiring_identities() {
let zero = RealWeight::zero();
let one = RealWeight::one();
assert_eq!(zero, RealWeight::new(0.0));
assert_eq!(one, RealWeight::new(1.0));
assert!(Zero::is_zero(&zero));
assert!(!Zero::is_zero(&one));
}
#[test]
fn test_arithmetic_operations() {
let a = RealWeight::new(6.0);
let b = RealWeight::new(4.0);
let sum = a.plus(&b);
assert_eq!(sum, RealWeight::new(10.0));
let product = a.times(&b);
assert_eq!(product, RealWeight::new(24.0));
let sum2 = a + b;
let product2 = a * b;
assert_eq!(sum, sum2);
assert_eq!(product, product2);
}
#[test]
fn test_semiring_properties() {
let a = RealWeight::new(2.0);
let b = RealWeight::new(3.0);
let c = RealWeight::new(5.0);
let zero = RealWeight::zero();
let one = RealWeight::one();
let left_assoc = (a.plus(&b)).plus(&c);
let right_assoc = a.plus(&(b.plus(&c)));
assert!(left_assoc.approx_eq(&right_assoc, 1e-10));
let ab = a.plus(&b);
let ba = b.plus(&a);
assert_eq!(ab, ba);
let a_plus_zero = a.plus(&zero);
assert_eq!(a_plus_zero, a);
let left_mult_assoc = (a.times(&b)).times(&c);
let right_mult_assoc = a.times(&(b.times(&c)));
assert!(left_mult_assoc.approx_eq(&right_mult_assoc, 1e-10));
let ab_mult = a.times(&b);
let ba_mult = b.times(&a);
assert_eq!(ab_mult, ba_mult);
let a_times_one = a.times(&one);
assert_eq!(a_times_one, a);
let a_times_zero = a.times(&zero);
assert_eq!(a_times_zero, zero);
let left_dist = a.times(&(b.plus(&c)));
let right_dist = (a.times(&b)).plus(&(a.times(&c)));
assert!(left_dist.approx_eq(&right_dist, 1e-10));
}
#[test]
fn test_properties_structure() {
let props = RealWeight::properties();
assert!(props.left_semiring);
assert!(props.right_semiring);
assert!(props.commutative);
assert!(!props.idempotent); assert!(!props.path); }
#[test]
fn test_divisible_semiring() {
let a = RealWeight::new(15.0);
let b = RealWeight::new(3.0);
let zero = RealWeight::zero();
let quotient = a.divide(&b).unwrap();
assert_eq!(quotient, RealWeight::new(5.0));
let verification = quotient.times(&b);
assert!(verification.approx_eq(&a, 1e-10));
assert_eq!(a.divide(&zero), None);
}
#[test]
fn test_invertible_semiring() {
let a = RealWeight::new(4.0);
let zero = RealWeight::zero();
let one = RealWeight::one();
let inv_a = a.inverse().unwrap();
assert_eq!(inv_a, RealWeight::new(0.25));
let identity_check = a.times(&inv_a);
assert!(identity_check.approx_eq(&one, 1e-10));
assert_eq!(zero.inverse(), None);
let inv_one = one.inverse().unwrap();
assert_eq!(inv_one, one);
}
#[test]
fn test_inverse_properties() {
let values = [0.1, 0.5, 1.0, 2.0, 10.0, 100.0];
let one = RealWeight::one();
for &val in &values {
let w = RealWeight::new(val);
if let Some(inv_w) = w.inverse() {
let product = w.times(&inv_w);
assert!(
product.approx_eq(&one, 1e-10),
"Failed for value {val}: {w} * {inv_w} = {product}"
);
if let Some(inv_inv_w) = inv_w.inverse() {
assert!(
inv_inv_w.approx_eq(&w, 1e-10),
"Double inverse failed for value {val}"
);
}
}
}
}
#[test]
fn test_mathematical_operations() {
let a = RealWeight::new(-5.0);
assert_eq!(a.abs(), RealWeight::new(5.0));
let b = RealWeight::new(16.0);
assert_eq!(b.sqrt().unwrap(), RealWeight::new(4.0));
let negative = RealWeight::new(-4.0);
assert_eq!(negative.sqrt(), None);
let base = RealWeight::new(2.0);
assert_eq!(base.pow(3.0), RealWeight::new(8.0));
}
#[test]
fn test_special_values() {
let inf = RealWeight::new(f64::INFINITY);
let neg_inf = RealWeight::new(f64::NEG_INFINITY);
let nan = RealWeight::new(f64::NAN);
assert!(inf.is_infinite());
assert!(neg_inf.is_infinite());
assert!(nan.is_nan());
assert_eq!(format!("{inf}"), "∞");
assert_eq!(format!("{neg_inf}"), "-∞");
assert_eq!(format!("{nan}"), "NaN");
assert_eq!(inf.inverse(), Some(RealWeight::new(0.0)));
assert_eq!(RealWeight::new(0.0).inverse(), None);
}
#[test]
fn test_normalization_example() {
let weights = [
RealWeight::new(10.0),
RealWeight::new(20.0),
RealWeight::new(30.0),
];
let total = weights
.iter()
.fold(RealWeight::zero(), |acc, w| acc.plus(w));
assert_eq!(total, RealWeight::new(60.0));
if let Some(inv_total) = total.inverse() {
let normalized: Vec<_> = weights.iter().map(|w| w.times(&inv_total)).collect();
assert!(normalized[0].approx_eq(&RealWeight::new(1.0 / 6.0), 1e-10));
assert!(normalized[1].approx_eq(&RealWeight::new(2.0 / 6.0), 1e-10));
assert!(normalized[2].approx_eq(&RealWeight::new(3.0 / 6.0), 1e-10));
let sum = normalized
.iter()
.fold(RealWeight::zero(), |acc, w| acc.plus(w));
assert!(sum.approx_eq(&RealWeight::one(), 1e-10));
}
}
#[test]
fn test_linear_system_solving() {
let a = RealWeight::new(5.0);
let b = RealWeight::new(15.0);
if let Some(inv_a) = a.inverse() {
let x = b.times(&inv_a);
assert_eq!(x, RealWeight::new(3.0));
let verification = a.times(&x);
assert!(verification.approx_eq(&b, 1e-10));
}
}
#[test]
fn test_approximate_equality() {
let a = RealWeight::new(1.0);
let b = RealWeight::new(1.000_000_1);
let c = RealWeight::new(1.1);
assert!(a.approx_eq(&b, 1e-6));
assert!(!a.approx_eq(&b, 1e-8));
assert!(!a.approx_eq(&c, 1e-6));
}
#[test]
fn test_ordering() {
let a = RealWeight::new(1.0);
let b = RealWeight::new(2.0);
let c = RealWeight::new(1.0);
assert!(a < b);
assert!(b > a);
assert_eq!(a, c);
assert!(a <= c);
assert!(a >= c);
}
#[test]
fn test_string_parsing() {
assert_eq!("3.5".parse::<RealWeight>().unwrap(), RealWeight::new(3.5));
assert_eq!("-2.5".parse::<RealWeight>().unwrap(), RealWeight::new(-2.5));
assert!("not_a_number".parse::<RealWeight>().is_err());
}
#[test]
fn test_default() {
let default_weight = RealWeight::default();
assert_eq!(default_weight, RealWeight::zero());
}
#[test]
fn test_in_place_operations() {
let mut a = RealWeight::new(5.0);
let b = RealWeight::new(3.0);
a.plus_assign(&b);
assert_eq!(a, RealWeight::new(8.0));
a.times_assign(&b);
assert_eq!(a, RealWeight::new(24.0));
}
#[test]
fn test_numerical_stability() {
let small = RealWeight::new(1e-100);
let large = RealWeight::new(1e100);
if let Some(inv_small) = small.inverse() {
assert!(inv_small.is_infinite() || inv_small.as_f64() > 1e90);
}
if let Some(inv_large) = large.inverse() {
assert!(inv_large.as_f64() < 1e-90);
}
let very_large = RealWeight::new(f64::MAX);
let very_small = RealWeight::new(f64::MIN_POSITIVE);
assert!(!very_large.is_infinite());
assert!(!Zero::is_zero(&very_small));
}
#[test]
fn test_bayesian_example() {
let prior = RealWeight::new(0.3);
let likelihood = RealWeight::new(0.8);
let evidence = RealWeight::new(0.6);
let numerator = prior.times(&likelihood);
assert_eq!(numerator, RealWeight::new(0.24));
if let Some(inv_evidence) = evidence.inverse() {
let posterior = numerator.times(&inv_evidence);
assert!(posterior.approx_eq(&RealWeight::new(0.4), 1e-10));
}
}
#[test]
fn test_edge_cases() {
let zero = RealWeight::zero();
let _one = RealWeight::one();
let inf = RealWeight::new(f64::INFINITY);
let tiny = RealWeight::new(f64::MIN_POSITIVE);
if let Some(inv_tiny) = tiny.inverse() {
assert!(inv_tiny.as_f64() > 1e100);
}
let finite = RealWeight::new(42.0);
let inf_plus_finite = inf.plus(&finite);
assert!(inf_plus_finite.is_infinite());
let inf_times_finite = inf.times(&finite);
assert!(inf_times_finite.is_infinite());
if let Some(divided) = inf.divide(&finite) {
assert!(divided.is_infinite());
}
if let Some(inv_inf) = inf.inverse() {
assert_eq!(inv_inf, zero);
}
}
}