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 {
tnorms::LogicFamily::Godel.tnorm_f32(a, b)
}
#[inline]
fn or(a: f32, b: f32) -> f32 {
tnorms::LogicFamily::Godel.tconorm_f32(a, b)
}
#[inline]
fn residuum(a: f32, b: f32) -> f32 {
tnorms::LogicFamily::Godel.residuum_f32(a, b)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Product;
impl Truth for Product {
#[inline]
fn and(a: f32, b: f32) -> f32 {
tnorms::LogicFamily::Product.tnorm_f32(a, b)
}
#[inline]
fn or(a: f32, b: f32) -> f32 {
tnorms::LogicFamily::Product.tconorm_f32(a, b)
}
#[inline]
fn residuum(a: f32, b: f32) -> f32 {
tnorms::LogicFamily::Product.residuum_f32(a, b)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Viterbi;
impl Truth for Viterbi {
#[inline]
fn and(a: f32, b: f32) -> f32 {
tnorms::LogicFamily::Product.tnorm_f32(a, b)
}
#[inline]
fn or(a: f32, b: f32) -> f32 {
tnorms::LogicFamily::Godel.tconorm_f32(a, b)
}
#[inline]
fn residuum(a: f32, b: f32) -> f32 {
tnorms::LogicFamily::Product.residuum_f32(a, b)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Lukasiewicz;
impl Truth for Lukasiewicz {
#[inline]
fn and(a: f32, b: f32) -> f32 {
tnorms::LogicFamily::Lukasiewicz.tnorm_f32(a, b)
}
#[inline]
fn or(a: f32, b: f32) -> f32 {
tnorms::LogicFamily::Lukasiewicz.tconorm_f32(a, b)
}
#[inline]
fn residuum(a: f32, b: f32) -> f32 {
tnorms::LogicFamily::Lukasiewicz.residuum_f32(a, b)
}
}
#[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) {
let ac = T::and(a, c);
let imp = T::residuum(a, b);
assert_eq!(
ac <= b + EPS,
c <= imp + EPS,
"residuation adjunction failed for a={a}, b={b}, c={c}"
);
assert!(
T::and(a, imp) <= 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}"
);
}
}
#[test]
fn truth_algebras_delegate_to_tnorms() {
let a = 0.7;
let b = 0.4;
assert_eq!(Godel::and(a, b), tnorms::LogicFamily::Godel.tnorm_f32(a, b));
assert_eq!(
Godel::or(a, b),
tnorms::LogicFamily::Godel.tconorm_f32(a, b)
);
assert_eq!(
Godel::residuum(a, b),
tnorms::LogicFamily::Godel.residuum_f32(a, b)
);
assert_eq!(
Product::and(a, b),
tnorms::LogicFamily::Product.tnorm_f32(a, b)
);
assert_eq!(
Product::or(a, b),
tnorms::LogicFamily::Product.tconorm_f32(a, b)
);
assert_eq!(
Product::residuum(a, b),
tnorms::LogicFamily::Product.residuum_f32(a, b)
);
assert_eq!(
Lukasiewicz::and(a, b),
tnorms::LogicFamily::Lukasiewicz.tnorm_f32(a, b)
);
assert_eq!(
Lukasiewicz::or(a, b),
tnorms::LogicFamily::Lukasiewicz.tconorm_f32(a, b)
);
assert_eq!(
Lukasiewicz::residuum(a, b),
tnorms::LogicFamily::Lukasiewicz.residuum_f32(a, b)
);
assert_eq!(
Viterbi::and(a, b),
tnorms::LogicFamily::Product.tnorm_f32(a, b)
);
assert_eq!(
Viterbi::or(a, b),
tnorms::LogicFamily::Godel.tconorm_f32(a, b)
);
assert_eq!(
Viterbi::residuum(a, b),
tnorms::LogicFamily::Product.residuum_f32(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);
}
}
}