fuzzy_control/
defuzzification.rs

1//! Defuzzification methods for converting fuzzy outputs to crisp values.
2
3use crate::*;
4
5/// Trait for defuzzification methods.
6pub trait Defuzzifier<D: Float, M: Float> {
7    /// Defuzzifies an aggregated fuzzy output into a crisp value.
8    fn defuzzify(
9        &self,
10        output_variable: &LinguisticVariable<D, M>,
11        activations: &HashMap<String, MembershipDegree<M>>,
12    ) -> Result<D, MembershipError>;
13}
14
15/// Centroid (Center of Area/Gravity) defuzzification.
16pub struct Centroid {
17    samples: usize,
18}
19
20impl Centroid {
21    /// Creates a new centroid defuzzifier with the specified sample count.
22    pub fn new(samples: usize) -> Result<Self, MembershipError> {
23        if samples == 0 {
24            return Err(MembershipError::InvalidConfiguration {
25                message: "Sample count must be greater than 0".to_string(),
26            });
27        }
28        Ok(Centroid { samples })
29    }
30}
31
32impl<D: Float, M: Float> Defuzzifier<D, M> for Centroid {
33    fn defuzzify(
34        &self,
35        output_variable: &LinguisticVariable<D, M>,
36        activations: &HashMap<String, MembershipDegree<M>>,
37    ) -> Result<D, MembershipError> {
38        let (min, max) = output_variable.range();
39        let step = (max - min) / D::from(self.samples).unwrap();
40        
41        let mut numerator = D::zero();
42        let mut denominator = D::zero();
43        
44        for i in 0..=self.samples {
45            let x = min + step * D::from(i).unwrap();
46            let mut max_activation = M::zero();
47            
48            for set in &output_variable.sets {
49                if let Some(activation) = activations.get(set.name()) {
50                    let membership = set.evaluate(x);
51                    let clipped = membership.get().min(activation.get());
52                    max_activation = max_activation.max(clipped);
53                }
54            }
55            
56            let activation_d = D::from(max_activation).unwrap_or(D::zero());
57            numerator = numerator + x * activation_d;
58            denominator = denominator + activation_d;
59        }
60        
61        if denominator == D::zero() {
62            Ok((min + max) / D::from(2.0).unwrap())
63        } else {
64            Ok(numerator / denominator)
65        }
66    }
67}
68
69/// Bisector defuzzification.
70pub struct Bisector {
71    samples: usize,
72}
73
74impl Bisector {
75    /// Creates a new bisector defuzzifier.
76    pub fn new(samples: usize) -> Result<Self, MembershipError> {
77        if samples == 0 {
78            return Err(MembershipError::InvalidConfiguration {
79                message: "Sample count must be greater than 0".to_string(),
80            });
81        }
82        Ok(Bisector { samples })
83    }
84}
85
86impl<D: Float, M: Float> Defuzzifier<D, M> for Bisector {
87    fn defuzzify(
88        &self,
89        output_variable: &LinguisticVariable<D, M>,
90        activations: &HashMap<String, MembershipDegree<M>>,
91    ) -> Result<D, MembershipError> {
92        let (min, max) = output_variable.range();
93        let step = (max - min) / D::from(self.samples).unwrap();
94        
95        let mut total_area = D::zero();
96        let mut areas = Vec::new();
97        
98        for i in 0..=self.samples {
99            let x = min + step * D::from(i).unwrap();
100            let mut max_activation = M::zero();
101            
102            for set in &output_variable.sets {
103                if let Some(activation) = activations.get(set.name()) {
104                    let membership = set.evaluate(x);
105                    let clipped = membership.get().min(activation.get());
106                    max_activation = max_activation.max(clipped);
107                }
108            }
109            
110            let activation_d = D::from(max_activation).unwrap_or(D::zero());
111            areas.push((x, activation_d));
112            total_area = total_area + activation_d;
113        }
114        
115        if total_area == D::zero() {
116            return Ok((min + max) / D::from(2.0).unwrap());
117        }
118        
119        let half_area = total_area / D::from(2.0).unwrap();
120        let mut accumulated_area = D::zero();
121        
122        for (x, area) in areas {
123            accumulated_area = accumulated_area + area;
124            if accumulated_area >= half_area {
125                return Ok(x);
126            }
127        }
128        
129        Ok(max)
130    }
131}
132
133/// Mean of Maximum defuzzification.
134pub struct MeanOfMaximum {
135    samples: usize,
136}
137
138impl MeanOfMaximum {
139    /// Creates a new mean of maximum defuzzifier.
140    pub fn new(samples: usize) -> Result<Self, MembershipError> {
141        if samples == 0 {
142            return Err(MembershipError::InvalidConfiguration {
143                message: "Sample count must be greater than 0".to_string(),
144            });
145        }
146        Ok(MeanOfMaximum { samples })
147    }
148}
149
150impl<D: Float, M: Float> Defuzzifier<D, M> for MeanOfMaximum {
151    fn defuzzify(
152        &self,
153        output_variable: &LinguisticVariable<D, M>,
154        activations: &HashMap<String, MembershipDegree<M>>,
155    ) -> Result<D, MembershipError> {
156        let (min, max) = output_variable.range();
157        let step = (max - min) / D::from(self.samples).unwrap();
158        
159        let mut max_membership = M::zero();
160        let mut max_points = Vec::new();
161        
162        for i in 0..=self.samples {
163            let x = min + step * D::from(i).unwrap();
164            let mut aggregated = M::zero();
165            
166            for set in &output_variable.sets {
167                if let Some(activation) = activations.get(set.name()) {
168                    let membership = set.evaluate(x);
169                    let clipped = membership.get().min(activation.get());
170                    aggregated = aggregated.max(clipped);
171                }
172            }
173            
174            if aggregated > max_membership {
175                max_membership = aggregated;
176                max_points.clear();
177                max_points.push(x);
178            } else if aggregated == max_membership && aggregated > M::zero() {
179                max_points.push(x);
180            }
181        }
182        
183        if max_points.is_empty() {
184            Ok((min + max) / D::from(2.0).unwrap())
185        } else {
186            let sum: D = max_points.iter().fold(D::zero(), |acc, &x| acc + x);
187            Ok(sum / D::from(max_points.len()).unwrap())
188        }
189    }
190}
191
192/// Smallest of Maximum defuzzification.
193pub struct SmallestOfMaximum {
194    samples: usize,
195}
196
197impl SmallestOfMaximum {
198    /// Creates a new smallest of maximum defuzzifier.
199    pub fn new(samples: usize) -> Result<Self, MembershipError> {
200        if samples == 0 {
201            return Err(MembershipError::InvalidConfiguration {
202                message: "Sample count must be greater than 0".to_string(),
203            });
204        }
205        Ok(SmallestOfMaximum { samples })
206    }
207}
208
209impl<D: Float, M: Float> Defuzzifier<D, M> for SmallestOfMaximum {
210    fn defuzzify(
211        &self,
212        output_variable: &LinguisticVariable<D, M>,
213        activations: &HashMap<String, MembershipDegree<M>>,
214    ) -> Result<D, MembershipError> {
215        let (min, max) = output_variable.range();
216        let step = (max - min) / D::from(self.samples).unwrap();
217        
218        let mut max_membership = M::zero();
219        let mut smallest_max = max;
220        
221        for i in 0..=self.samples {
222            let x = min + step * D::from(i).unwrap();
223            let mut aggregated = M::zero();
224            
225            for set in &output_variable.sets {
226                if let Some(activation) = activations.get(set.name()) {
227                    let membership = set.evaluate(x);
228                    let clipped = membership.get().min(activation.get());
229                    aggregated = aggregated.max(clipped);
230                }
231            }
232            
233            if aggregated > max_membership {
234                max_membership = aggregated;
235                smallest_max = x;
236            }
237        }
238        
239        Ok(smallest_max)
240    }
241}
242
243/// Largest of Maximum defuzzification.
244pub struct LargestOfMaximum {
245    samples: usize,
246}
247
248impl LargestOfMaximum {
249    /// Creates a new largest of maximum defuzzifier.
250    pub fn new(samples: usize) -> Result<Self, MembershipError> {
251        if samples == 0 {
252            return Err(MembershipError::InvalidConfiguration {
253                message: "Sample count must be greater than 0".to_string(),
254            });
255        }
256        Ok(LargestOfMaximum { samples })
257    }
258}
259
260impl<D: Float, M: Float> Defuzzifier<D, M> for LargestOfMaximum {
261    fn defuzzify(
262        &self,
263        output_variable: &LinguisticVariable<D, M>,
264        activations: &HashMap<String, MembershipDegree<M>>,
265    ) -> Result<D, MembershipError> {
266        let (min, max) = output_variable.range();
267        let step = (max - min) / D::from(self.samples).unwrap();
268        
269        let mut max_membership = M::zero();
270        let mut largest_max = min;
271        
272        for i in 0..=self.samples {
273            let x = min + step * D::from(i).unwrap();
274            let mut aggregated = M::zero();
275            
276            for set in &output_variable.sets {
277                if let Some(activation) = activations.get(set.name()) {
278                    let membership = set.evaluate(x);
279                    let clipped = membership.get().min(activation.get());
280                    aggregated = aggregated.max(clipped);
281                }
282            }
283            
284            if aggregated >= max_membership {
285                max_membership = aggregated;
286                largest_max = x;
287            }
288        }
289        
290        Ok(largest_max)
291    }
292}
293
294/// Weighted Average defuzzification (for Sugeno-style inference).
295pub struct WeightedAverage;
296
297impl<D: Float, M: Float> Defuzzifier<D, M> for WeightedAverage {
298    fn defuzzify(
299        &self,
300        output_variable: &LinguisticVariable<D, M>,
301        activations: &HashMap<String, MembershipDegree<M>>,
302    ) -> Result<D, MembershipError> {
303        let mut weighted_sum = D::zero();
304        let mut weight_sum = M::zero();
305        
306        for set in &output_variable.sets {
307            if let Some(activation) = activations.get(set.name()) {
308                let (min, max) = output_variable.range();
309                let samples = 100;
310                let step = (max - min) / D::from(samples).unwrap();
311                
312                let mut peak_value = min;
313                let mut peak_membership = M::zero();
314                
315                for i in 0..=samples {
316                    let x = min + step * D::from(i).unwrap();
317                    let membership = set.evaluate(x).get();
318                    if membership > peak_membership {
319                        peak_membership = membership;
320                        peak_value = x;
321                    }
322                }
323                
324                weighted_sum = weighted_sum + peak_value * D::from(activation.get()).unwrap();
325                weight_sum = weight_sum + activation.get();
326            }
327        }
328        
329        if weight_sum == M::zero() {
330            let (min, max) = output_variable.range();
331            Ok((min + max) / D::from(2.0).unwrap())
332        } else {
333            Ok(weighted_sum / D::from(weight_sum).unwrap())
334        }
335    }
336}