use std::fmt;
use std::hash::Hash;
use std::str::FromStr;
use crate::error::ParseError;
use crate::fst_type::WeightType;
use crate::utils::split_composite_weight;
use crate::weight::{
COMMUTATIVE, Divide, DivideType, IDEMPOTENT, LEFT_SEMIRING, PATH, RIGHT_SEMIRING, Weight,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct LexicographicWeight<W1: Weight, W2: Weight> {
pub value1: W1,
pub value2: W2,
}
impl<W1: Weight, W2: Weight> LexicographicWeight<W1, W2> {
#[inline(always)]
pub fn new(value1: W1, value2: W2) -> Self {
Self { value1, value2 }
}
}
impl<W1: Weight, W2: Weight> Weight for LexicographicWeight<W1, W2> {
type ReverseWeight = LexicographicWeight<W1::ReverseWeight, W2::ReverseWeight>;
#[inline(always)]
fn zero() -> Self {
Self {
value1: W1::zero(),
value2: W2::zero(),
}
}
#[inline(always)]
fn one() -> Self {
Self {
value1: W1::one(),
value2: W2::one(),
}
}
#[inline(always)]
fn no_weight() -> Self {
Self {
value1: W1::no_weight(),
value2: W2::no_weight(),
}
}
fn type_name() -> WeightType {
let s = format!("{}_LT_{}", W1::type_name(), W2::type_name());
WeightType::new_dynamic(s)
}
#[inline(always)]
fn properties() -> u64 {
W1::properties()
& W2::properties()
& (LEFT_SEMIRING | RIGHT_SEMIRING | PATH | IDEMPOTENT | COMMUTATIVE)
}
#[inline(always)]
fn is_member(&self) -> bool {
if !self.value1.is_member() || !self.value2.is_member() {
return false;
}
let is_z1 = self.value1 == W1::zero();
let is_z2 = self.value2 == W2::zero();
is_z1 == is_z2
}
#[inline]
fn approx_equal(&self, other: &Self, delta: f32) -> bool {
W1::approx_equal(&self.value1, &other.value1, delta)
&& W2::approx_equal(&self.value2, &other.value2, delta)
}
#[inline]
fn quantize(&self, delta: f32) -> Self {
Self {
value1: W1::quantize(&self.value1, delta),
value2: W2::quantize(&self.value2, delta),
}
}
#[inline]
fn reverse(&self) -> Self::ReverseWeight {
LexicographicWeight {
value1: W1::reverse(&self.value1),
value2: W2::reverse(&self.value2),
}
}
#[inline]
fn plus(&self, rhs: &Self) -> Self {
if !self.is_member() || !rhs.is_member() {
return Self::no_weight();
}
if self.value1 != rhs.value1 {
let p1 = W1::plus(&self.value1, &rhs.value1);
if p1 == self.value1 {
return self.clone(); }
if p1 == rhs.value1 {
return rhs.clone(); }
}
if self.value2 != rhs.value2 {
let p2 = W2::plus(&self.value2, &rhs.value2);
if p2 == self.value2 {
return self.clone(); }
if p2 == rhs.value2 {
return rhs.clone(); }
}
self.clone()
}
#[inline]
fn times(&self, rhs: &Self) -> Self {
Self {
value1: W1::times(&self.value1, &rhs.value1),
value2: W2::times(&self.value2, &rhs.value2),
}
}
}
impl<W1: Weight + Divide, W2: Weight + Divide> Divide for LexicographicWeight<W1, W2> {
#[inline]
fn divide(&self, rhs: &Self, typ: DivideType) -> Self {
Self {
value1: W1::divide(&self.value1, &rhs.value1, typ),
value2: W2::divide(&self.value2, &rhs.value2, typ),
}
}
}
impl<W1: Weight, W2: Weight> fmt::Display for LexicographicWeight<W1, W2> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{},{}", self.value1, self.value2)
}
}
impl<W1: Weight + FromStr, W2: Weight + FromStr> FromStr for LexicographicWeight<W1, W2> {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = split_composite_weight(s, ',', '(', ')')?;
if parts.len() != 2 {
return Err(ParseError::InvalidElementCount {
expected: 2,
found: parts.len(),
});
}
let value1 = parts[0].parse::<W1>().map_err(|_| {
ParseError::InvalidFormat(format!(
"Failed to parse W1 of LexicographicWeight: {}",
parts[0]
))
})?;
let value2 = parts[1].parse::<W2>().map_err(|_| {
ParseError::InvalidFormat(format!(
"Failed to parse W2 of LexicographicWeight: {}",
parts[1]
))
})?;
Ok(Self { value1, value2 })
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_satisfies_the_axioms_it_claims() {
use crate::weight::axioms;
use crate::weights::float_weight::TropicalWeight;
type Lex = LexicographicWeight<TropicalWeight, TropicalWeight>;
axioms::check(&[
Lex::new(TropicalWeight(1.0), TropicalWeight(2.0)),
Lex::new(TropicalWeight(1.0), TropicalWeight(3.0)),
Lex::new(TropicalWeight(2.0), TropicalWeight(1.0)),
]);
axioms::check_divide(&[Lex::new(TropicalWeight(1.0), TropicalWeight(2.0))]);
}
#[test]
fn plus_breaks_ties_with_the_second_component() {
use crate::weights::float_weight::TropicalWeight;
let cheaper_tail = LexicographicWeight::<TropicalWeight, TropicalWeight>::new(
TropicalWeight(1.0),
TropicalWeight(2.0),
);
let costlier_tail = LexicographicWeight::<TropicalWeight, TropicalWeight>::new(
TropicalWeight(1.0),
TropicalWeight(5.0),
);
assert_eq!(cheaper_tail.plus(&costlier_tail), cheaper_tail);
let cheaper_head = LexicographicWeight::<TropicalWeight, TropicalWeight>::new(
TropicalWeight(0.5),
TropicalWeight(9.0),
);
assert_eq!(cheaper_head.plus(&cheaper_tail), cheaper_head);
}
use super::*;
use crate::weights::float_weight::TropicalWeight;
type LexTropical = LexicographicWeight<TropicalWeight, TropicalWeight>;
#[test]
fn test_lexicographic_weight_parse_display() {
let text = "2.5,4.5";
let w: LexTropical = text.parse().unwrap();
assert_eq!(w.to_string(), text);
assert_eq!(w.value1.value(), 2.5);
assert_eq!(w.value2.value(), 4.5);
}
#[test]
fn test_lexicographic_weight_plus() {
let w1: LexTropical = "2,8".parse().unwrap();
let w2: LexTropical = "5,1".parse().unwrap();
let w3 = w1.plus(&w2);
assert_eq!(w3.to_string(), "2,8");
let w4: LexTropical = "2,8".parse().unwrap();
let w5: LexTropical = "2,5".parse().unwrap();
let w6 = w4.plus(&w5);
assert_eq!(w6.to_string(), "2,5");
}
#[test]
fn test_lexicographic_weight_times() {
let w1: LexTropical = "2,4".parse().unwrap();
let w2: LexTropical = "3,5".parse().unwrap();
let w3 = w1.times(&w2);
assert_eq!(w3.to_string(), "5,9");
}
#[test]
fn test_lexicographic_weight_divide() {
let w1: LexTropical = "8,9".parse().unwrap();
let w2: LexTropical = "2,5".parse().unwrap();
let w3 = w1.divide(&w2, DivideType::Any);
assert_eq!(w3.to_string(), "6,4");
}
#[test]
fn test_lexicographic_weight_member() {
let zero = LexTropical::zero();
let one = LexTropical::one();
assert!(zero.is_member());
assert!(one.is_member());
let invalid = LexTropical::new(TropicalWeight::zero(), TropicalWeight::one());
assert!(!invalid.is_member());
}
}