use super::traits::*;
use core::fmt;
use core::ops::{Add, Mul};
use core::str::FromStr;
use num_traits::{One, Zero};
use ordered_float::OrderedFloat;
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TropicalWeight(OrderedFloat<f32>);
impl TropicalWeight {
pub const INFINITY: Self = Self(OrderedFloat(f32::INFINITY));
pub fn new(value: f32) -> Self {
Self(OrderedFloat(value))
}
}
impl fmt::Display for TropicalWeight {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.is_infinite() {
write!(f, "∞")
} else {
let value = self.0;
write!(f, "{value}")
}
}
}
impl Zero for TropicalWeight {
fn zero() -> Self {
Self::INFINITY
}
fn is_zero(&self) -> bool {
self.0.is_infinite()
}
}
impl One for TropicalWeight {
fn one() -> Self {
Self::new(0.0)
}
}
impl Add for TropicalWeight {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0.min(rhs.0))
}
}
impl Mul for TropicalWeight {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
if <Self as num_traits::Zero>::is_zero(&self) || <Self as num_traits::Zero>::is_zero(&rhs) {
Self::zero()
} else {
Self(self.0 + rhs.0)
}
}
}
impl Semiring for TropicalWeight {
type Value = f32;
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: true,
path: true,
}
}
fn approx_eq(&self, other: &Self, epsilon: f64) -> bool {
if <Self as num_traits::Zero>::is_zero(self) && <Self as num_traits::Zero>::is_zero(other) {
true
} else {
(self.0 - other.0).abs() < epsilon as f32
}
}
}
impl NaturallyOrderedSemiring for TropicalWeight {}
impl DivisibleSemiring for TropicalWeight {
fn divide(&self, other: &Self) -> Option<Self> {
if <Self as num_traits::Zero>::is_zero(other) {
None
} else if <Self as num_traits::Zero>::is_zero(self) {
Some(Self::zero())
} else {
Some(Self(self.0 - other.0))
}
}
}
impl StarSemiring for TropicalWeight {
fn star(&self) -> Self {
if <Self as num_traits::Zero>::is_zero(self) {
Self::zero()
} else if *self.value() < 0.0 {
*self
} else {
Self::one()
}
}
}
impl FromStr for TropicalWeight {
type Err = std::num::ParseFloatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "∞" || s == "inf" || s == "infinity" {
Ok(Self::INFINITY)
} else {
s.parse::<f32>().map(Self::new)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_traits::{One, Zero};
#[test]
fn test_tropical_weight_creation() {
let w = TropicalWeight::new(5.0);
assert_eq!(*w.value(), 5.0);
}
#[test]
fn test_tropical_zero_one() {
let zero = TropicalWeight::zero();
let one = TropicalWeight::one();
assert!(Semiring::is_zero(&zero));
assert!(Semiring::is_one(&one));
assert_eq!(*one.value(), 0.0);
assert!(zero.value().is_infinite());
}
#[test]
fn test_tropical_addition() {
let w1 = TropicalWeight::new(3.0);
let w2 = TropicalWeight::new(5.0);
let result = w1.plus(&w2);
assert_eq!(*result.value(), 3.0); }
#[test]
fn test_tropical_multiplication() {
let w1 = TropicalWeight::new(3.0);
let w2 = TropicalWeight::new(5.0);
let result = w1.times(&w2);
assert_eq!(*result.value(), 8.0); }
#[test]
fn test_tropical_zero_multiplication() {
let w = TropicalWeight::new(5.0);
let zero = TropicalWeight::zero();
let result = w.times(&zero);
assert!(Semiring::is_zero(&result));
}
#[test]
fn test_tropical_one_multiplication() {
let w = TropicalWeight::new(5.0);
let one = TropicalWeight::one();
let result = w.times(&one);
assert_eq!(result, w);
}
#[test]
fn test_tropical_display() {
let w = TropicalWeight::new(5.0);
let zero = TropicalWeight::zero();
assert_eq!(format!("{w}"), "5");
assert_eq!(format!("{zero}"), "∞");
}
#[test]
fn test_tropical_division() {
let w1 = TropicalWeight::new(8.0);
let w2 = TropicalWeight::new(3.0);
let result = w1.divide(&w2).unwrap();
assert_eq!(*result.value(), 5.0);
let zero = TropicalWeight::zero();
assert!(w1.divide(&zero).is_none());
}
#[test]
fn test_tropical_properties() {
let props = TropicalWeight::properties();
assert!(props.left_semiring);
assert!(props.right_semiring);
assert!(props.commutative);
assert!(props.idempotent);
assert!(props.path);
}
#[test]
fn test_tropical_approx_eq() {
let w1 = TropicalWeight::new(5.000_001);
let w2 = TropicalWeight::new(5.0);
assert!(w1.approx_eq(&w2, 0.001));
assert!(!w1.approx_eq(&w2, 0.000_000_1));
}
#[test]
fn test_tropical_from_str() {
assert_eq!(
TropicalWeight::from_str("5.0").unwrap(),
TropicalWeight::new(5.0)
);
assert_eq!(
TropicalWeight::from_str("∞").unwrap(),
TropicalWeight::INFINITY
);
assert_eq!(
TropicalWeight::from_str("inf").unwrap(),
TropicalWeight::INFINITY
);
assert_eq!(
TropicalWeight::from_str("infinity").unwrap(),
TropicalWeight::INFINITY
);
}
#[test]
fn test_tropical_operator_overloads() {
let w1 = TropicalWeight::new(3.0);
let w2 = TropicalWeight::new(5.0);
assert_eq!(w1 + w2, TropicalWeight::new(3.0));
assert_eq!(w1 * w2, TropicalWeight::new(8.0));
}
#[test]
fn test_tropical_identity_laws() {
let w = TropicalWeight::new(5.0);
let zero = TropicalWeight::zero();
let one = TropicalWeight::one();
assert_eq!(w + zero, w);
assert_eq!(zero + w, w);
assert_eq!(w * one, w);
assert_eq!(one * w, w);
assert!(Semiring::is_zero(&(w * zero)));
assert!(Semiring::is_zero(&(zero * w)));
}
#[test]
fn test_tropical_star_operation() {
use crate::semiring::StarSemiring;
let w1 = TropicalWeight::new(5.0);
assert_eq!(w1.star(), TropicalWeight::one());
let w2 = TropicalWeight::new(0.0);
assert_eq!(w2.star(), TropicalWeight::one());
let w3 = TropicalWeight::new(-2.0);
assert_eq!(w3.star(), w3);
let w4 = TropicalWeight::zero();
assert_eq!(w4.star(), TropicalWeight::zero());
let w5 = TropicalWeight::new(3.0);
let star = w5.star();
assert_eq!(star, TropicalWeight::one());
assert_eq!(star.plus(&w5), star);
}
#[test]
fn test_tropical_semiring_axioms() {
let a = TropicalWeight::new(2.0);
let b = TropicalWeight::new(3.0);
let c = TropicalWeight::new(4.0);
assert_eq!((a + b) + c, a + (b + c));
assert_eq!((a * b) * c, a * (b * c));
assert_eq!(a + b, b + a);
assert_eq!(a * b, b * a);
assert_eq!((a + b) * c, (a * c) + (b * c));
}
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn test_tropical_associativity_property(a in -100.0..100.0f32, b in -100.0..100.0f32, c in -100.0..100.0f32) {
let w1 = TropicalWeight::new(a);
let w2 = TropicalWeight::new(b);
let w3 = TropicalWeight::new(c);
let left = w1.plus(&w2).plus(&w3);
let right = w1.plus(&w2.plus(&w3));
prop_assert!(left.approx_eq(&right, 1e-4));
let left = w1.times(&w2).times(&w3);
let right = w1.times(&w2.times(&w3));
prop_assert!(left.approx_eq(&right, 1e-4));
}
#[test]
fn test_tropical_identity_property(a in -100.0..100.0f32) {
let w = TropicalWeight::new(a);
prop_assert!(w.plus(&TropicalWeight::zero()).approx_eq(&w, 1e-4));
prop_assert!(w.times(&TropicalWeight::one()).approx_eq(&w, 1e-4));
}
}
}
}