rust_fuzzylogic/
ops.rs

1// Operators for fuzzy logic antecedents and inference.
2// Provides a trait (`FuzzyOps`) and concrete families (`Ops`) implementing AND/OR/NOT.
3use crate::Float;
4
5/// Common interface for fuzzy logic operators (T-norm, S-norm, complement).
6pub trait FuzzyOps {
7    /// T-norm (logical AND) combining two degrees in [0, 1].
8    fn t(&self, a: Float, b: Float) -> Float;
9
10    /// S-norm (logical OR) combining two degrees in [0, 1].
11    fn s(&self, a: Float, b: Float) -> Float;
12
13    /// Complement (logical NOT) of a degree in [0, 1].
14    fn c(&self, a: Float) -> Float;
15}
16
17#[cfg(feature = "ops-minmax")]
18pub struct MinMax;
19#[cfg(feature = "ops-minmax")]
20impl FuzzyOps for MinMax {
21    fn t(&self, a: Float, b: Float) -> Float {
22        a.min(b)
23    }
24
25    fn s(&self, a: Float, b: Float) -> Float {
26        a.max(b)
27    }
28
29    fn c(&self, a: Float) -> Float {
30        1.0 - a
31    }
32}
33
34#[cfg(feature = "ops-product")]
35pub struct MinMax;
36#[cfg(feature = "ops-product")]
37impl FuzzyOps for MinMax {
38    fn t(&self, a: Float, b: Float) -> Float {
39        a * b
40    }
41
42    fn s(&self, a: Float, b: Float) -> Float {
43        a + b - a * b
44    }
45
46    fn c(&self, a: Float) -> Float {
47        1.0 - a
48    }
49}
50
51#[cfg(feature = "ops-lukasiewicz")]
52pub struct MinMax;
53#[cfg(feature = "ops-lukasiewicz")]
54impl FuzzyOps for MinMax {
55    fn t(&self, a: Float, b: Float) -> Float {
56        (a + b - 1.0).max(0.0)
57    }
58
59    fn s(&self, a: Float, b: Float) -> Float {
60        (a + b).min(1.0)
61    }
62
63    fn c(&self, a: Float) -> Float {
64        1.0 - a
65    }
66}
67
68#[cfg(feature = "ops-dyn")]
69#[derive(Clone, Copy, Debug)]
70/// Built-in operator families providing AND/OR/NOT over degrees.
71pub enum Ops {
72    /// Min–Max family
73    /// - T: `min(a, b)`
74    /// - S: `max(a, b)`
75    /// - C: `1 - a`
76    MinMax,
77    /// Product family
78    /// - T: `a * b`
79    /// - S: `a + b - a * b` (not algebraic sum; may exceed 1.0)
80    /// - C: `1 - a`
81    Product,
82    /// Łukasiewicz family
83    /// - T: `max(0, a + b - 1)`
84    /// - S: `min(1, a + b)`
85    /// - C: `1 - a`
86    Lukasiewicz,
87}
88#[cfg(feature = "ops-dyn")]
89/// Implements `FuzzyOps` for each `Ops` variant using the formulas above.
90impl FuzzyOps for Ops {
91    /// T-norm (AND) per family.
92    fn t(&self, a: Float, b: Float) -> Float {
93        match self {
94            Ops::MinMax => a.min(b),
95            Ops::Product => a * b,
96            Ops::Lukasiewicz => (a + b - 1.0).max(0.0),
97        }
98    }
99
100    /// S-norm (OR) per family.
101    fn s(&self, a: Float, b: Float) -> Float {
102        match self {
103            Ops::MinMax => a.max(b),
104            Ops::Product => a + b - a * b,
105            Ops::Lukasiewicz => (a + b).min(1.0),
106        }
107    }
108
109    /// Complement (NOT) shared by all families: `1 - a`.
110    fn c(&self, a: Float) -> Float {
111        1.0 - a
112    }
113}
114
115#[cfg(feature = "ops-dyn")]
116#[cfg(test)]
117mod tests_dyn_ops {
118    use crate::ops::*;
119
120    // RED: expected default operator behavior (min/max/1-x).
121    // This test references the intended API and should fail right now
122    // because the operators are not implemented yet.
123    #[test]
124    fn red_minmax_defaults_and_or_not() {
125        let v = crate::ops::Ops::MinMax;
126
127        // Mixed values
128        assert_eq!(v.t(0.2, 0.8), 0.2);
129        assert_eq!(v.s(0.2, 0.8), 0.8);
130        assert_eq!(v.c(0.2), 0.8);
131
132        // Boundaries
133        assert_eq!(v.t(0.0, 1.0), 0.0);
134        assert_eq!(v.s(0.0, 1.0), 1.0);
135        assert_eq!(v.c(0.0), 1.0);
136        assert_eq!(v.c(1.0), 0.0);
137    }
138
139    #[test]
140    fn product_ops_and_or_not_matches_code() {
141        let v = Ops::Product;
142        let eps = crate::Float::EPSILON;
143        // t = a*b
144        assert!((v.t(0.2, 0.8) - 0.16).abs() < eps);
145        // s = a + b (per current code)
146        assert!((v.s(0.1, 0.2) - 0.28).abs() < eps);
147        // c = 1 - a
148        assert!((v.c(0.2) - 0.8).abs() < eps);
149    }
150
151    #[test]
152    fn lukasiewicz_ops_and_or_not_matches_code() {
153        let v = Ops::Lukasiewicz;
154        let eps = crate::Float::EPSILON;
155        // t = max(0, a + b - 1)
156        assert!((v.t(0.2, 0.8) - 0.0).abs() < eps);
157        assert!((v.t(0.8, 0.3) - 0.1).abs() < eps);
158        // s = min(1, a + b)
159        assert!((v.s(0.2, 0.8) - 1.0).abs() < eps);
160        assert!((v.s(0.4, 0.4) - 0.8).abs() < eps);
161        // c = 1 - a
162        assert!((v.c(0.2) - 0.8).abs() < eps);
163    }
164}