pub trait Truth {
#[inline]
fn top() -> f32 {
1.0
}
#[inline]
fn bot() -> f32 {
0.0
}
fn and(a: f32, b: f32) -> f32;
fn or(a: f32, b: f32) -> f32;
fn residuum(a: f32, b: f32) -> f32;
#[inline]
fn neg(a: f32) -> f32 {
Self::residuum(a, Self::bot())
}
}
pub trait SelectiveOr: Truth {}
impl SelectiveOr for Godel {}
impl SelectiveOr for Viterbi {}
#[derive(Debug, Clone, Copy, Default)]
pub struct Godel;
impl Truth for Godel {
#[inline]
fn and(a: f32, b: f32) -> f32 {
a.min(b)
}
#[inline]
fn or(a: f32, b: f32) -> f32 {
a.max(b)
}
#[inline]
fn residuum(a: f32, b: f32) -> f32 {
if a <= b {
1.0
} else {
b
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Product;
impl Truth for Product {
#[inline]
fn and(a: f32, b: f32) -> f32 {
a * b
}
#[inline]
fn or(a: f32, b: f32) -> f32 {
a + b - a * b
}
#[inline]
fn residuum(a: f32, b: f32) -> f32 {
if a <= b {
1.0
} else {
b / a
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Viterbi;
impl Truth for Viterbi {
#[inline]
fn and(a: f32, b: f32) -> f32 {
a * b
}
#[inline]
fn or(a: f32, b: f32) -> f32 {
a.max(b)
}
#[inline]
fn residuum(a: f32, b: f32) -> f32 {
if a <= b {
1.0
} else {
b / a
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Lukasiewicz;
impl Truth for Lukasiewicz {
#[inline]
fn and(a: f32, b: f32) -> f32 {
(a + b - 1.0).max(0.0)
}
#[inline]
fn or(a: f32, b: f32) -> f32 {
(a + b).min(1.0)
}
#[inline]
fn residuum(a: f32, b: f32) -> f32 {
(1.0 - a + b).min(1.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
const EPS: f32 = 1e-5;
fn degree() -> impl Strategy<Value = f32> {
(0.0f32..=1.0).prop_map(|x| (x * 1000.0).round() / 1000.0)
}
fn check_unit<T: Truth>(a: f32) {
assert!((T::and(a, T::top()) - a).abs() < EPS, "unit ⊤ failed");
assert!(T::and(a, T::bot()).abs() < EPS, "annihilator ⊥ failed");
}
fn check_commutative<T: Truth>(a: f32, b: f32) {
assert!((T::and(a, b) - T::and(b, a)).abs() < EPS);
assert!((T::or(a, b) - T::or(b, a)).abs() < EPS);
}
fn check_associativity<T: Truth>(a: f32, b: f32, c: f32) {
assert!(
(T::and(T::and(a, b), c) - T::and(a, T::and(b, c))).abs() < EPS,
"⊗ not associative"
);
assert!(
(T::or(T::or(a, b), c) - T::or(a, T::or(b, c))).abs() < EPS,
"⊕ not associative"
);
}
fn check_or_identities<T: Truth>(a: f32) {
assert!((T::or(a, T::bot()) - a).abs() < EPS, "or unit ⊥ failed");
assert!(
(T::or(a, T::top()) - T::top()).abs() < EPS,
"or annihilator ⊤ failed"
);
}
fn check_de_morgan<T: Truth>(a: f32, b: f32) {
assert!(
(T::neg(T::and(a, b)) - T::or(T::neg(a), T::neg(b))).abs() < EPS,
"De Morgan ¬(a ⊗ b) = ¬a ⊕ ¬b failed"
);
}
fn check_residuation<T: Truth>(a: f32, b: f32, c: f32) {
assert!(
T::and(a, T::residuum(a, b)) <= b + EPS,
"modus ponens failed: {a} ⊗ ({a}→{b}) > {b}"
);
assert!(
c <= T::residuum(a, T::and(a, c)) + EPS,
"adjunction unit failed for a={a}, c={c}"
);
if a <= b {
assert!(
(T::residuum(a, b) - T::top()).abs() < EPS,
"residuum not ⊤ when {a} ≤ {b}"
);
}
}
proptest! {
#[test]
fn godel_is_a_residuated_lattice(a in degree(), b in degree(), c in degree()) {
check_unit::<Godel>(a);
check_commutative::<Godel>(a, b);
check_associativity::<Godel>(a, b, c);
check_or_identities::<Godel>(a);
check_de_morgan::<Godel>(a, b);
check_residuation::<Godel>(a, b, c);
}
#[test]
fn product_is_a_residuated_lattice(a in degree(), b in degree(), c in degree()) {
check_unit::<Product>(a);
check_commutative::<Product>(a, b);
check_associativity::<Product>(a, b, c);
check_or_identities::<Product>(a);
check_de_morgan::<Product>(a, b);
check_residuation::<Product>(a, b, c);
}
#[test]
fn lukasiewicz_is_a_residuated_lattice(a in degree(), b in degree(), c in degree()) {
check_unit::<Lukasiewicz>(a);
check_commutative::<Lukasiewicz>(a, b);
check_associativity::<Lukasiewicz>(a, b, c);
check_or_identities::<Lukasiewicz>(a);
check_de_morgan::<Lukasiewicz>(a, b);
check_residuation::<Lukasiewicz>(a, b, c);
}
#[test]
fn viterbi_is_a_residuated_lattice(a in degree(), b in degree(), c in degree()) {
check_unit::<Viterbi>(a);
check_commutative::<Viterbi>(a, b);
check_associativity::<Viterbi>(a, b, c);
check_or_identities::<Viterbi>(a);
check_de_morgan::<Viterbi>(a, b);
check_residuation::<Viterbi>(a, b, c);
}
#[test]
fn selective_or_algebras_distribute(a in degree(), b in degree(), c in degree()) {
prop_assert!((Godel::and(a, Godel::or(b, c))
- Godel::or(Godel::and(a, b), Godel::and(a, c))).abs() < EPS);
prop_assert!((Viterbi::and(a, Viterbi::or(b, c))
- Viterbi::or(Viterbi::and(a, b), Viterbi::and(a, c))).abs() < EPS);
}
#[test]
fn lukasiewicz_negation_is_involutive(a in degree()) {
prop_assert!((Lukasiewicz::neg(a) - (1.0 - a)).abs() < EPS);
prop_assert!((Lukasiewicz::neg(Lukasiewicz::neg(a)) - a).abs() < EPS);
}
#[test]
fn godel_negation_is_the_crisp_pseudo_complement(a in degree()) {
let expected = if a <= EPS { 1.0 } else { 0.0 };
prop_assert!((Godel::neg(a) - expected).abs() < EPS);
}
}
}