1use std::collections::HashMap;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum DefuzzificationMethod {
23 Centroid,
25 Bisector,
27 MeanOfMaximum,
29 SmallestOfMaximum,
31 LargestOfMaximum,
33 WeightedAverage,
35}
36
37#[derive(Debug, Clone)]
41pub struct FuzzySet {
42 pub min: f64,
44 pub max: f64,
46 pub memberships: Vec<f64>,
48}
49
50impl FuzzySet {
51 pub fn new(min: f64, max: f64, samples: usize) -> Self {
53 Self {
54 min,
55 max,
56 memberships: vec![0.0; samples],
57 }
58 }
59
60 pub fn from_memberships(min: f64, max: f64, memberships: Vec<f64>) -> Self {
62 Self {
63 min,
64 max,
65 memberships,
66 }
67 }
68
69 fn value_at_index(&self, index: usize) -> f64 {
71 let range = self.max - self.min;
72 let step = range / (self.memberships.len() - 1).max(1) as f64;
73 self.min + index as f64 * step
74 }
75
76 pub fn len(&self) -> usize {
78 self.memberships.len()
79 }
80
81 pub fn is_empty(&self) -> bool {
83 self.memberships.is_empty()
84 }
85
86 fn max_membership(&self) -> f64 {
88 self.memberships.iter().fold(0.0f64, |a, &b| a.max(b))
89 }
90
91 fn max_membership_indices(&self) -> Vec<usize> {
93 let max_val = self.max_membership();
94 if max_val == 0.0 {
95 return vec![];
96 }
97
98 self.memberships
99 .iter()
100 .enumerate()
101 .filter(|(_, &val)| (val - max_val).abs() < 1e-10)
102 .map(|(i, _)| i)
103 .collect()
104 }
105
106 fn area(&self) -> f64 {
108 if self.memberships.is_empty() {
109 return 0.0;
110 }
111
112 let step = (self.max - self.min) / (self.memberships.len() - 1).max(1) as f64;
113 let mut area = 0.0;
114
115 for i in 0..self.memberships.len() - 1 {
116 area += step * (self.memberships[i] + self.memberships[i + 1]) / 2.0;
118 }
119
120 area
121 }
122
123 fn centroid_numerator(&self) -> f64 {
125 if self.memberships.is_empty() {
126 return 0.0;
127 }
128
129 let step = (self.max - self.min) / (self.memberships.len() - 1).max(1) as f64;
130 let mut numerator = 0.0;
131
132 for i in 0..self.memberships.len() - 1 {
133 let x1 = self.value_at_index(i);
134 let x2 = self.value_at_index(i + 1);
135 let y1 = self.memberships[i];
136 let y2 = self.memberships[i + 1];
137
138 let x_mid = (x1 + x2) / 2.0;
140 let trap_area = step * (y1 + y2) / 2.0;
141
142 numerator += x_mid * trap_area;
143 }
144
145 numerator
146 }
147}
148
149pub fn defuzzify(fuzzy_set: &FuzzySet, method: DefuzzificationMethod) -> Option<f64> {
154 match method {
155 DefuzzificationMethod::Centroid => centroid(fuzzy_set),
156 DefuzzificationMethod::Bisector => bisector(fuzzy_set),
157 DefuzzificationMethod::MeanOfMaximum => mean_of_maximum(fuzzy_set),
158 DefuzzificationMethod::SmallestOfMaximum => smallest_of_maximum(fuzzy_set),
159 DefuzzificationMethod::LargestOfMaximum => largest_of_maximum(fuzzy_set),
160 DefuzzificationMethod::WeightedAverage => weighted_average(fuzzy_set),
161 }
162}
163
164pub fn centroid(fuzzy_set: &FuzzySet) -> Option<f64> {
168 let area = fuzzy_set.area();
169 if area == 0.0 {
170 return None;
171 }
172
173 let numerator = fuzzy_set.centroid_numerator();
174 Some(numerator / area)
175}
176
177pub fn bisector(fuzzy_set: &FuzzySet) -> Option<f64> {
182 let total_area = fuzzy_set.area();
183 if total_area == 0.0 {
184 return None;
185 }
186
187 let target_area = total_area / 2.0;
188 let step = (fuzzy_set.max - fuzzy_set.min) / (fuzzy_set.memberships.len() - 1).max(1) as f64;
189
190 let mut cumulative_area = 0.0;
191
192 for i in 0..fuzzy_set.memberships.len() - 1 {
193 let trap_area = step * (fuzzy_set.memberships[i] + fuzzy_set.memberships[i + 1]) / 2.0;
194 if cumulative_area + trap_area >= target_area {
195 let remaining = target_area - cumulative_area;
197 let fraction = remaining / trap_area;
198 let x = fuzzy_set.value_at_index(i)
199 + fraction * (fuzzy_set.value_at_index(i + 1) - fuzzy_set.value_at_index(i));
200 return Some(x);
201 }
202 cumulative_area += trap_area;
203 }
204
205 Some((fuzzy_set.min + fuzzy_set.max) / 2.0)
207}
208
209pub fn mean_of_maximum(fuzzy_set: &FuzzySet) -> Option<f64> {
213 let max_indices = fuzzy_set.max_membership_indices();
214 if max_indices.is_empty() {
215 return None;
216 }
217
218 let sum: f64 = max_indices
219 .iter()
220 .map(|&i| fuzzy_set.value_at_index(i))
221 .sum();
222
223 Some(sum / max_indices.len() as f64)
224}
225
226pub fn smallest_of_maximum(fuzzy_set: &FuzzySet) -> Option<f64> {
230 let max_indices = fuzzy_set.max_membership_indices();
231 max_indices.first().map(|&i| fuzzy_set.value_at_index(i))
232}
233
234pub fn largest_of_maximum(fuzzy_set: &FuzzySet) -> Option<f64> {
238 let max_indices = fuzzy_set.max_membership_indices();
239 max_indices.last().map(|&i| fuzzy_set.value_at_index(i))
240}
241
242pub fn weighted_average(fuzzy_set: &FuzzySet) -> Option<f64> {
246 let mut numerator = 0.0;
247 let mut denominator = 0.0;
248
249 for (i, &membership) in fuzzy_set.memberships.iter().enumerate() {
250 let x = fuzzy_set.value_at_index(i);
251 numerator += x * membership;
252 denominator += membership;
253 }
254
255 if denominator == 0.0 {
256 None
257 } else {
258 Some(numerator / denominator)
259 }
260}
261
262#[derive(Debug, Clone)]
266pub struct SingletonFuzzySet {
267 pub singletons: HashMap<String, f64>,
269}
270
271impl SingletonFuzzySet {
272 pub fn new() -> Self {
274 Self {
275 singletons: HashMap::new(),
276 }
277 }
278
279 pub fn add(&mut self, value: String, membership: f64) {
281 self.singletons.insert(value, membership.clamp(0.0, 1.0));
282 }
283
284 pub fn defuzzify(&self) -> Option<f64> {
288 let mut numerator = 0.0;
289 let mut denominator = 0.0;
290
291 for (value_str, &membership) in &self.singletons {
292 if let Ok(value) = value_str.parse::<f64>() {
293 numerator += value * membership;
294 denominator += membership;
295 }
296 }
297
298 if denominator == 0.0 {
299 None
300 } else {
301 Some(numerator / denominator)
302 }
303 }
304
305 pub fn winner_takes_all(&self) -> Option<(String, f64)> {
307 self.singletons
308 .iter()
309 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
310 .map(|(k, &v)| (k.clone(), v))
311 }
312}
313
314impl Default for SingletonFuzzySet {
315 fn default() -> Self {
316 Self::new()
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 fn create_test_fuzzy_set() -> FuzzySet {
325 FuzzySet::from_memberships(
327 0.0,
328 1.0,
329 vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.75, 0.5, 0.25, 0.0],
330 )
331 }
332
333 #[test]
334 fn test_fuzzy_set_creation() {
335 let fs = FuzzySet::new(0.0, 10.0, 11);
336 assert_eq!(fs.len(), 11);
337 assert!((fs.min - 0.0).abs() < 1e-10);
338 assert!((fs.max - 10.0).abs() < 1e-10);
339 }
340
341 #[test]
342 fn test_value_at_index() {
343 let fs = FuzzySet::new(0.0, 10.0, 11);
344 assert!((fs.value_at_index(0) - 0.0).abs() < 1e-10);
345 assert!((fs.value_at_index(5) - 5.0).abs() < 1e-10);
346 assert!((fs.value_at_index(10) - 10.0).abs() < 1e-10);
347 }
348
349 #[test]
350 fn test_max_membership() {
351 let fs = create_test_fuzzy_set();
352 assert!((fs.max_membership() - 1.0).abs() < 1e-10);
353 }
354
355 #[test]
356 fn test_max_membership_indices() {
357 let fs = create_test_fuzzy_set();
358 let indices = fs.max_membership_indices();
359 assert_eq!(indices.len(), 1);
360 assert_eq!(indices[0], 4);
361 }
362
363 #[test]
364 fn test_centroid() {
365 let fs = create_test_fuzzy_set();
366 let result = centroid(&fs).unwrap();
367 assert!((result - 0.5).abs() < 0.1);
369 }
370
371 #[test]
372 fn test_bisector() {
373 let fs = create_test_fuzzy_set();
374 let result = bisector(&fs).unwrap();
375 assert!((result - 0.5).abs() < 0.1);
377 }
378
379 #[test]
380 fn test_mean_of_maximum() {
381 let fs = create_test_fuzzy_set();
382 let result = mean_of_maximum(&fs).unwrap();
383 assert!((result - 0.5).abs() < 1e-10);
385 }
386
387 #[test]
388 fn test_smallest_of_maximum() {
389 let fs = FuzzySet::from_memberships(0.0, 1.0, vec![0.0, 0.5, 1.0, 1.0, 1.0, 0.5, 0.0]);
390 let result = smallest_of_maximum(&fs).unwrap();
391 assert!((result - 0.333).abs() < 0.05);
393 }
394
395 #[test]
396 fn test_largest_of_maximum() {
397 let fs = FuzzySet::from_memberships(0.0, 1.0, vec![0.0, 0.5, 1.0, 1.0, 1.0, 0.5, 0.0]);
398 let result = largest_of_maximum(&fs).unwrap();
399 assert!((result - 0.667).abs() < 0.05);
401 }
402
403 #[test]
404 fn test_weighted_average() {
405 let fs = FuzzySet::from_memberships(0.0, 10.0, vec![0.2, 0.5, 0.8, 0.5, 0.2]);
406 let result = weighted_average(&fs).unwrap();
407 assert!(result > 4.0 && result < 6.0);
409 }
410
411 #[test]
412 fn test_empty_fuzzy_set() {
413 let fs = FuzzySet::from_memberships(0.0, 1.0, vec![0.0, 0.0, 0.0]);
414 assert!(centroid(&fs).is_none());
415 assert!(bisector(&fs).is_none());
416 assert!(mean_of_maximum(&fs).is_none());
417 }
418
419 #[test]
420 fn test_singleton_fuzzy_set() {
421 let mut sfs = SingletonFuzzySet::new();
422 sfs.add("0.0".to_string(), 0.2);
423 sfs.add("5.0".to_string(), 0.8);
424 sfs.add("10.0".to_string(), 0.3);
425
426 let result = sfs.defuzzify().unwrap();
427 assert!((result - 5.38).abs() < 0.1);
430 }
431
432 #[test]
433 fn test_singleton_winner_takes_all() {
434 let mut sfs = SingletonFuzzySet::new();
435 sfs.add("low".to_string(), 0.3);
436 sfs.add("medium".to_string(), 0.8);
437 sfs.add("high".to_string(), 0.5);
438
439 let (winner, membership) = sfs.winner_takes_all().unwrap();
440 assert_eq!(winner, "medium");
441 assert!((membership - 0.8).abs() < 1e-10);
442 }
443
444 #[test]
445 fn test_defuzzify_dispatch() {
446 let fs = create_test_fuzzy_set();
447
448 assert!(defuzzify(&fs, DefuzzificationMethod::Centroid).is_some());
449 assert!(defuzzify(&fs, DefuzzificationMethod::Bisector).is_some());
450 assert!(defuzzify(&fs, DefuzzificationMethod::MeanOfMaximum).is_some());
451 assert!(defuzzify(&fs, DefuzzificationMethod::SmallestOfMaximum).is_some());
452 assert!(defuzzify(&fs, DefuzzificationMethod::LargestOfMaximum).is_some());
453 assert!(defuzzify(&fs, DefuzzificationMethod::WeightedAverage).is_some());
454 }
455}