use ordered_float::OrderedFloat;
use crate::semiring::traits::{
CommutativeTimesSemiring, IdempotentSemiring, NonnegativeSemiring, QuantizableSemiring,
Semiring, StochasticSemiring, TotallyOrderedSemiring, ZeroSumFreeSemiring,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)]
pub struct GodelWeight(pub OrderedFloat<f64>);
impl GodelWeight {
#[inline]
pub fn new(value: f64) -> Self {
let clamped = value.clamp(0.0, 1.0);
GodelWeight(OrderedFloat(clamped))
}
#[inline]
pub(crate) const fn new_unchecked(value: f64) -> Self {
GodelWeight(OrderedFloat(value))
}
#[inline]
pub fn value(self) -> f64 {
self.0.into_inner()
}
#[inline]
pub fn is_one_membership(self) -> bool {
(self.0.into_inner() - 1.0).abs() < f64::EPSILON
}
#[inline]
pub fn is_zero_membership(self) -> bool {
self.0.into_inner().abs() < f64::EPSILON
}
}
impl From<f64> for GodelWeight {
#[inline]
fn from(value: f64) -> Self {
GodelWeight::new(value)
}
}
impl From<GodelWeight> for f64 {
#[inline]
fn from(weight: GodelWeight) -> Self {
weight.value()
}
}
impl Default for GodelWeight {
#[inline]
fn default() -> Self {
Self::one()
}
}
impl Semiring for GodelWeight {
#[inline]
fn zero() -> Self {
GodelWeight::new_unchecked(0.0)
}
#[inline]
fn one() -> Self {
GodelWeight::new_unchecked(1.0)
}
#[inline]
fn plus(&self, other: &Self) -> Self {
GodelWeight(self.0.max(other.0))
}
#[inline]
fn times(&self, other: &Self) -> Self {
GodelWeight(self.0.min(other.0))
}
#[inline]
fn is_zero(&self) -> bool {
self.is_zero_membership()
}
#[inline]
fn is_one(&self) -> bool {
self.is_one_membership()
}
fn approx_eq(&self, other: &Self, epsilon: f64) -> bool {
(self.0.into_inner() - other.0.into_inner()).abs() <= epsilon
}
fn natural_less(&self, other: &Self) -> Option<bool> {
Some(self.0 < other.0)
}
fn to_bytes(&self) -> Vec<u8> {
self.0.into_inner().to_le_bytes().to_vec()
}
}
impl IdempotentSemiring for GodelWeight {}
impl ZeroSumFreeSemiring for GodelWeight {}
impl CommutativeTimesSemiring for GodelWeight {}
impl TotallyOrderedSemiring for GodelWeight {}
impl NonnegativeSemiring for GodelWeight {}
impl QuantizableSemiring for GodelWeight {
fn quantize(&self, epsilon: f64) -> i64 {
let v = self.value();
if v.is_nan() {
i64::MIN
} else {
(v / epsilon).round() as i64
}
}
}
impl StochasticSemiring for GodelWeight {
fn to_probability(&self) -> f64 {
self.value()
}
}
impl crate::semiring::traits::NumericalWeight for GodelWeight {
#[inline]
fn numerical_value(&self) -> f64 {
self.value()
}
}
impl std::ops::Add for GodelWeight {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self {
self.plus(&other)
}
}
impl std::ops::Mul for GodelWeight {
type Output = Self;
#[inline]
fn mul(self, other: Self) -> Self {
self.times(&other)
}
}
impl std::ops::AddAssign for GodelWeight {
#[inline]
fn add_assign(&mut self, other: Self) {
*self = self.plus(&other);
}
}
impl std::ops::MulAssign for GodelWeight {
#[inline]
fn mul_assign(&mut self, other: Self) {
*self = self.times(&other);
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for GodelWeight {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.into_inner().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for GodelWeight {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
f64::deserialize(deserializer).map(GodelWeight::new)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::semiring::traits::tests::{
verify_commutative_times_semiring, verify_idempotent_semiring, verify_quantizable_semiring,
verify_semiring_axioms, verify_stochastic_semiring, verify_totally_ordered_semiring,
verify_zero_sum_free_semiring,
};
use proptest::prelude::*;
#[test]
fn test_basic_operations() {
let a = GodelWeight::new(0.3);
let b = GodelWeight::new(0.7);
assert!((a.plus(&b).value() - 0.7).abs() < 1e-10);
assert!((b.plus(&a).value() - 0.7).abs() < 1e-10);
assert!((a.times(&b).value() - 0.3).abs() < 1e-10);
assert!((b.times(&a).value() - 0.3).abs() < 1e-10);
}
#[test]
fn test_identities() {
let a = GodelWeight::new(0.5);
assert!(a.plus(&GodelWeight::zero()).approx_eq(&a, 1e-10));
assert!(GodelWeight::zero().plus(&a).approx_eq(&a, 1e-10));
assert!(a.times(&GodelWeight::one()).approx_eq(&a, 1e-10));
assert!(GodelWeight::one().times(&a).approx_eq(&a, 1e-10));
}
#[test]
fn test_annihilation() {
let a = GodelWeight::new(0.5);
assert!(a.times(&GodelWeight::zero()).is_zero());
assert!(GodelWeight::zero().times(&a).is_zero());
}
#[test]
fn test_clamping() {
let neg = GodelWeight::new(-0.5);
assert!((neg.value() - 0.0).abs() < 1e-10);
let big = GodelWeight::new(1.5);
assert!((big.value() - 1.0).abs() < 1e-10);
let mid = GodelWeight::new(0.5);
assert!((mid.value() - 0.5).abs() < 1e-10);
}
#[test]
fn test_idempotence() {
let a = GodelWeight::new(0.5);
assert!(a.plus(&a).approx_eq(&a, 1e-10));
}
#[test]
fn test_boundary_values() {
let zero = GodelWeight::zero();
let one = GodelWeight::one();
assert!(zero.plus(&one).approx_eq(&one, 1e-10));
assert!(zero.times(&one).approx_eq(&zero, 1e-10));
assert!(one.plus(&one).approx_eq(&one, 1e-10));
assert!(zero.times(&zero).approx_eq(&zero, 1e-10));
}
#[test]
fn test_natural_ordering() {
let low = GodelWeight::new(0.3);
let high = GodelWeight::new(0.7);
assert_eq!(low.natural_less(&high), Some(true));
assert_eq!(high.natural_less(&low), Some(false));
assert_eq!(low.natural_less(&low), Some(false));
}
#[test]
fn test_fuzzy_conjunction() {
let tall = GodelWeight::new(0.8); let heavy = GodelWeight::new(0.6);
let conjunction = tall.times(&heavy);
assert!((conjunction.value() - 0.6).abs() < 1e-10);
}
#[test]
fn test_fuzzy_disjunction() {
let tall = GodelWeight::new(0.8); let heavy = GodelWeight::new(0.6);
let disjunction = tall.plus(&heavy);
assert!((disjunction.value() - 0.8).abs() < 1e-10);
}
proptest! {
#[test]
fn proptest_semiring_axioms(
a in 0.0f64..=1.0,
b in 0.0f64..=1.0,
c in 0.0f64..=1.0
) {
let wa = GodelWeight::new(a);
let wb = GodelWeight::new(b);
let wc = GodelWeight::new(c);
verify_semiring_axioms(wa, wb, wc, 1e-10);
}
#[test]
fn proptest_idempotent_semiring(a in 0.0f64..=1.0) {
let wa = GodelWeight::new(a);
verify_idempotent_semiring(wa, 1e-10);
}
#[test]
fn proptest_zero_sum_free_semiring(
a in 0.0f64..=1.0,
b in 0.0f64..=1.0
) {
let wa = GodelWeight::new(a);
let wb = GodelWeight::new(b);
verify_zero_sum_free_semiring(wa, wb, 1e-10);
}
#[test]
fn proptest_commutative_times_semiring(
a in 0.0f64..=1.0,
b in 0.0f64..=1.0
) {
let wa = GodelWeight::new(a);
let wb = GodelWeight::new(b);
verify_commutative_times_semiring(wa, wb, 1e-10);
}
#[test]
fn proptest_totally_ordered_semiring(
a in 0.0f64..=1.0,
b in 0.0f64..=1.0,
c in 0.0f64..=1.0
) {
let wa = GodelWeight::new(a);
let wb = GodelWeight::new(b);
let wc = GodelWeight::new(c);
verify_totally_ordered_semiring(wa, wb, wc);
}
#[test]
fn proptest_quantizable_semiring(a in 0.0f64..=1.0) {
let wa = GodelWeight::new(a);
verify_quantizable_semiring(wa, 1e-10);
}
#[test]
fn proptest_stochastic_semiring(a in 0.0f64..=1.0) {
let wa = GodelWeight::new(a);
verify_stochastic_semiring(wa);
}
#[test]
fn proptest_clamping(value in -10.0f64..10.0) {
let w = GodelWeight::new(value);
let v = w.value();
prop_assert!(v >= 0.0 && v <= 1.0, "Value {} should be in [0, 1]", v);
}
#[test]
fn proptest_min_max_consistency(
a in 0.0f64..=1.0,
b in 0.0f64..=1.0
) {
let wa = GodelWeight::new(a);
let wb = GodelWeight::new(b);
let sum = wa.plus(&wb);
prop_assert!(sum.value() >= a - 1e-10);
prop_assert!(sum.value() >= b - 1e-10);
let product = wa.times(&wb);
prop_assert!(product.value() <= a + 1e-10);
prop_assert!(product.value() <= b + 1e-10);
}
#[test]
fn proptest_absorption_law(
a in 0.0f64..=1.0,
b in 0.0f64..=1.0
) {
let wa = GodelWeight::new(a);
let wb = GodelWeight::new(b);
let inner = wa.times(&wb); let result = wa.plus(&inner); prop_assert!(result.approx_eq(&wa, 1e-10), "Absorption law failed");
}
}
}