use super::traits::*;
use core::fmt;
use core::ops::{Add, Mul};
use num_traits::{One, Zero};
use std::sync::OnceLock;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProductWeight<W1: Semiring, W2: Semiring> {
pub w1: W1,
pub w2: W2,
#[cfg_attr(feature = "serde", serde(skip))]
cached_value: OnceLock<(W1::Value, W2::Value)>,
}
impl<W1: Semiring, W2: Semiring> ProductWeight<W1, W2> {
pub fn new(w1: W1, w2: W2) -> Self {
Self {
w1,
w2,
cached_value: OnceLock::new(),
}
}
pub fn from_values(v1: W1::Value, v2: W2::Value) -> Self {
Self::new(W1::new(v1), W2::new(v2))
}
pub fn components(&self) -> (&W1, &W2) {
(&self.w1, &self.w2)
}
pub fn into_components(self) -> (W1, W2) {
(self.w1, self.w2)
}
pub fn map_first<F>(self, f: F) -> Self
where
F: FnOnce(W1) -> W1,
{
Self::new(f(self.w1), self.w2)
}
pub fn map_second<F>(self, f: F) -> Self
where
F: FnOnce(W2) -> W2,
{
Self::new(self.w1, f(self.w2))
}
pub fn map_both<F1, F2>(self, f1: F1, f2: F2) -> Self
where
F1: FnOnce(W1) -> W1,
F2: FnOnce(W2) -> W2,
{
Self::new(f1(self.w1), f2(self.w2))
}
fn get_or_init_value(&self) -> &(W1::Value, W2::Value)
where
W1::Value: Clone,
W2::Value: Clone,
{
self.cached_value
.get_or_init(|| (self.w1.value().clone(), self.w2.value().clone()))
}
}
impl<W1: Semiring, W2: Semiring> fmt::Display for ProductWeight<W1, W2> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let w1 = &self.w1;
let w2 = &self.w2;
write!(f, "({w1}, {w2})")
}
}
impl<W1: Semiring, W2: Semiring> PartialEq for ProductWeight<W1, W2> {
fn eq(&self, other: &Self) -> bool {
self.w1 == other.w1 && self.w2 == other.w2
}
}
impl<W1: Semiring, W2: Semiring> Eq for ProductWeight<W1, W2> {}
impl<W1: Semiring, W2: Semiring> PartialOrd for ProductWeight<W1, W2> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match self.w1.partial_cmp(&other.w1) {
Some(std::cmp::Ordering::Equal) => self.w2.partial_cmp(&other.w2),
other => other,
}
}
}
impl<W1, W2> Ord for ProductWeight<W1, W2>
where
W1: Semiring + Ord,
W2: Semiring + Ord,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.w1.cmp(&other.w1) {
std::cmp::Ordering::Equal => self.w2.cmp(&other.w2),
other => other,
}
}
}
impl<W1, W2> std::hash::Hash for ProductWeight<W1, W2>
where
W1: Semiring + std::hash::Hash,
W2: Semiring + std::hash::Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.w1.hash(state);
self.w2.hash(state);
}
}
impl<W1: Semiring, W2: Semiring> Zero for ProductWeight<W1, W2> {
fn zero() -> Self {
Self::new(W1::zero(), W2::zero())
}
fn is_zero(&self) -> bool {
Semiring::is_zero(&self.w1) && Semiring::is_zero(&self.w2)
}
}
impl<W1: Semiring, W2: Semiring> One for ProductWeight<W1, W2> {
fn one() -> Self {
Self::new(W1::one(), W2::one())
}
}
impl<W1: Semiring, W2: Semiring> Add for ProductWeight<W1, W2> {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::new(self.w1 + rhs.w1, self.w2 + rhs.w2)
}
}
impl<W1: Semiring, W2: Semiring> Mul for ProductWeight<W1, W2> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self::new(self.w1 * rhs.w1, self.w2 * rhs.w2)
}
}
impl<W1, W2> Semiring for ProductWeight<W1, W2>
where
W1: Semiring,
W2: Semiring,
W1::Value: Clone + Send + Sync,
W2::Value: Clone + Send + Sync,
{
type Value = (W1::Value, W2::Value);
fn new(value: Self::Value) -> Self {
Self::new(W1::new(value.0), W2::new(value.1))
}
fn value(&self) -> &Self::Value {
self.get_or_init_value()
}
fn plus(&self, other: &Self) -> Self {
Self::new(self.w1.plus(&other.w1), self.w2.plus(&other.w2))
}
fn times(&self, other: &Self) -> Self {
Self::new(self.w1.times(&other.w1), self.w2.times(&other.w2))
}
fn plus_assign(&mut self, other: &Self) {
self.w1.plus_assign(&other.w1);
self.w2.plus_assign(&other.w2);
*self = Self::new(self.w1.clone(), self.w2.clone());
}
fn times_assign(&mut self, other: &Self) {
self.w1.times_assign(&other.w1);
self.w2.times_assign(&other.w2);
*self = Self::new(self.w1.clone(), self.w2.clone());
}
fn properties() -> SemiringProperties {
let p1 = W1::properties();
let p2 = W2::properties();
SemiringProperties {
left_semiring: p1.left_semiring && p2.left_semiring,
right_semiring: p1.right_semiring && p2.right_semiring,
commutative: p1.commutative && p2.commutative,
idempotent: p1.idempotent && p2.idempotent,
path: p1.path && p2.path,
}
}
fn approx_eq(&self, other: &Self, epsilon: f64) -> bool {
self.w1.approx_eq(&other.w1, epsilon) && self.w2.approx_eq(&other.w2, epsilon)
}
}
impl<W1, W2> DivisibleSemiring for ProductWeight<W1, W2>
where
W1: DivisibleSemiring,
W2: DivisibleSemiring,
W1::Value: Clone + Send + Sync,
W2::Value: Clone + Send + Sync,
{
fn divide(&self, other: &Self) -> Option<Self> {
match (self.w1.divide(&other.w1), self.w2.divide(&other.w2)) {
(Some(w1), Some(w2)) => Some(Self::new(w1, w2)),
_ => None,
}
}
}
impl<W1, W2> StarSemiring for ProductWeight<W1, W2>
where
W1: StarSemiring,
W2: StarSemiring,
W1::Value: Clone + Send + Sync,
W2::Value: Clone + Send + Sync,
{
fn star(&self) -> Self {
Self::new(self.w1.star(), self.w2.star())
}
}
impl<W1, W2> NaturallyOrderedSemiring for ProductWeight<W1, W2>
where
W1: NaturallyOrderedSemiring,
W2: NaturallyOrderedSemiring,
W1::Value: Clone + Send + Sync,
W2::Value: Clone + Send + Sync,
{
}
impl<W1: Semiring + Default, W2: Semiring + Default> Default for ProductWeight<W1, W2> {
fn default() -> Self {
Self::new(W1::default(), W2::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::semiring::{ProbabilityWeight, TropicalWeight};
use num_traits::{One, Zero};
#[test]
fn test_product_weight_creation() {
let w = ProductWeight::new(TropicalWeight::new(2.0), ProbabilityWeight::new(0.5));
assert_eq!(*w.w1.value(), 2.0);
assert_eq!(*w.w2.value(), 0.5);
}
#[test]
fn test_product_zero_one() {
let zero = ProductWeight::<TropicalWeight, ProbabilityWeight>::zero();
let one = ProductWeight::<TropicalWeight, ProbabilityWeight>::one();
assert!(Semiring::is_zero(&zero));
assert!(Semiring::is_one(&one));
assert!(Semiring::is_zero(&zero.w1));
assert!(Semiring::is_zero(&zero.w2));
assert!(Semiring::is_one(&one.w1));
assert!(Semiring::is_one(&one.w2));
}
#[test]
fn test_product_operations() {
let w1 = ProductWeight::new(TropicalWeight::new(2.0), ProbabilityWeight::new(0.3));
let w2 = ProductWeight::new(TropicalWeight::new(3.0), ProbabilityWeight::new(0.5));
let add_result = w1.plus(&w2);
let mul_result = w1.times(&w2);
assert_eq!(*add_result.w1.value(), 2.0); assert_eq!(*add_result.w2.value(), 0.8);
assert_eq!(*mul_result.w1.value(), 5.0); assert_eq!(*mul_result.w2.value(), 0.15); }
#[test]
fn test_product_display() {
let w = ProductWeight::new(TropicalWeight::new(2.0), ProbabilityWeight::new(0.5));
assert_eq!(format!("{w}"), "(2, 0.5)");
let zero = ProductWeight::<TropicalWeight, ProbabilityWeight>::zero();
assert_eq!(format!("{zero}"), "(∞, 0)");
}
#[test]
fn test_product_division() {
let w1 = ProductWeight::new(TropicalWeight::new(5.0), ProbabilityWeight::new(0.6));
let w2 = ProductWeight::new(TropicalWeight::new(2.0), ProbabilityWeight::new(0.3));
let result = w1.divide(&w2).unwrap();
assert_eq!(*result.w1.value(), 3.0); assert_eq!(*result.w2.value(), 2.0);
let zero = ProductWeight::<TropicalWeight, ProbabilityWeight>::zero();
assert!(w1.divide(&zero).is_none());
}
#[test]
fn test_product_properties() {
let props = ProductWeight::<TropicalWeight, ProbabilityWeight>::properties();
assert!(props.left_semiring);
assert!(props.right_semiring);
assert!(props.commutative); assert!(!props.idempotent); assert!(!props.path); }
#[test]
fn test_product_approx_eq() {
let w1 = ProductWeight::new(TropicalWeight::new(2.0001), ProbabilityWeight::new(0.5001));
let w2 = ProductWeight::new(TropicalWeight::new(2.0), ProbabilityWeight::new(0.5));
assert!(w1.approx_eq(&w2, 0.001));
assert!(!w1.approx_eq(&w2, 0.00001));
}
#[test]
fn test_product_operator_overloads() {
let w1 = ProductWeight::new(TropicalWeight::new(2.0), ProbabilityWeight::new(0.3));
let w2 = ProductWeight::new(TropicalWeight::new(3.0), ProbabilityWeight::new(0.5));
let sum = w1 + w2;
assert_eq!(*sum.w1.value(), 2.0); assert_eq!(*sum.w2.value(), 0.8);
let w1 = ProductWeight::new(TropicalWeight::new(2.0), ProbabilityWeight::new(0.3));
let w2 = ProductWeight::new(TropicalWeight::new(3.0), ProbabilityWeight::new(0.5));
let product = w1 * w2;
assert_eq!(*product.w1.value(), 5.0); assert_eq!(*product.w2.value(), 0.15); }
#[test]
fn test_product_identity_laws() {
let w = ProductWeight::new(TropicalWeight::new(2.0), ProbabilityWeight::new(0.5));
let zero = ProductWeight::<TropicalWeight, ProbabilityWeight>::zero();
let one = ProductWeight::<TropicalWeight, ProbabilityWeight>::one();
assert_eq!(w.clone() + zero.clone(), w);
assert_eq!(zero.clone() + w.clone(), w);
assert_eq!(w.clone() * one.clone(), w);
assert_eq!(one.clone() * w.clone(), w);
assert!(Semiring::is_zero(&(w.clone() * zero.clone())));
assert!(Semiring::is_zero(&(zero * w)));
}
#[test]
fn test_product_semiring_axioms() {
let a = ProductWeight::new(TropicalWeight::new(1.0), ProbabilityWeight::new(0.2));
let b = ProductWeight::new(TropicalWeight::new(2.0), ProbabilityWeight::new(0.3));
let c = ProductWeight::new(TropicalWeight::new(3.0), ProbabilityWeight::new(0.4));
let tolerance = 1e-10;
assert!(((a.clone() + b.clone()) + c.clone())
.approx_eq(&(a.clone() + (b.clone() + c.clone())), tolerance));
assert!(((a.clone() * b.clone()) * c.clone())
.approx_eq(&(a.clone() * (b.clone() * c.clone())), tolerance));
assert_eq!(a.clone() + b.clone(), b.clone() + a.clone());
assert_eq!(a.clone() * b.clone(), b.clone() * a.clone());
assert!(((a.clone() + b.clone()) * c.clone()).approx_eq(
&((a.clone() * c.clone()) + (b.clone() * c.clone())),
tolerance
));
}
}