ipfrs_tensorlogic/fuzzy_logic_engine/
functions.rs1use super::types::{FuzzyError, FuzzyExpr, FuzzyVariable};
6
7pub(super) fn universe_bounds(var: &FuzzyVariable) -> (f64, f64) {
9 if var.sets.is_empty() {
10 return (0.0, 1.0);
11 }
12 let mut u_min = f64::INFINITY;
13 let mut u_max = f64::NEG_INFINITY;
14 for s in &var.sets {
15 if s.universe_min < u_min {
16 u_min = s.universe_min;
17 }
18 if s.universe_max > u_max {
19 u_max = s.universe_max;
20 }
21 }
22 if u_min >= u_max {
23 (u_min, u_min + 1.0)
24 } else {
25 (u_min, u_max)
26 }
27}
28pub(super) fn expr_targets_var(expr: &FuzzyExpr, var_name: &str) -> bool {
30 match expr {
31 FuzzyExpr::Is { var, .. } => var == var_name,
32 FuzzyExpr::And(l, r) | FuzzyExpr::Or(l, r) => {
33 expr_targets_var(l, var_name) || expr_targets_var(r, var_name)
34 }
35 FuzzyExpr::Not(inner) | FuzzyExpr::Very(inner) | FuzzyExpr::Somewhat(inner) => {
36 expr_targets_var(inner, var_name)
37 }
38 }
39}
40pub(super) fn consequent_set_name(expr: &FuzzyExpr, var_name: &str) -> Option<String> {
43 match expr {
44 FuzzyExpr::Is { var, set } if var == var_name => Some(set.clone()),
45 _ => None,
46 }
47}
48pub(super) fn dominant_set_name(var: &FuzzyVariable, x: f64) -> String {
50 var.sets
51 .iter()
52 .max_by(|a, b| {
53 a.mf.evaluate(x)
54 .partial_cmp(&b.mf.evaluate(x))
55 .unwrap_or(std::cmp::Ordering::Equal)
56 })
57 .map(|s| s.name.clone())
58 .unwrap_or_default()
59}
60pub(super) fn centroid(agg: &[f64], u_min: f64, step: f64) -> Result<f64, FuzzyError> {
62 let mut num = 0.0_f64;
63 let mut den = 0.0_f64;
64 for (i, &mu) in agg.iter().enumerate() {
65 let x = u_min + i as f64 * step;
66 num += x * mu;
67 den += mu;
68 }
69 if den < f64::EPSILON {
70 return Err(FuzzyError::DefuzzFailed(
71 "centroid: all membership values are zero".to_string(),
72 ));
73 }
74 Ok(num / den)
75}
76pub(super) fn bisector(agg: &[f64], u_min: f64, step: f64) -> Result<f64, FuzzyError> {
78 let total: f64 = agg.iter().sum();
79 if total < f64::EPSILON {
80 return Err(FuzzyError::DefuzzFailed(
81 "bisector: total area is zero".to_string(),
82 ));
83 }
84 let half = total / 2.0;
85 let mut cum = 0.0_f64;
86 for (i, &mu) in agg.iter().enumerate() {
87 cum += mu;
88 if cum >= half {
89 return Ok(u_min + i as f64 * step);
90 }
91 }
92 Ok(u_min + (agg.len() - 1) as f64 * step)
93}
94pub(super) fn mean_of_maxima(agg: &[f64], u_min: f64, step: f64) -> Result<f64, FuzzyError> {
96 let max_val = agg.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
97 if max_val < f64::EPSILON {
98 return Err(FuzzyError::DefuzzFailed(
99 "mean_of_maxima: maximum membership is zero".to_string(),
100 ));
101 }
102 let mut sum = 0.0_f64;
103 let mut count = 0usize;
104 for (i, &mu) in agg.iter().enumerate() {
105 if (mu - max_val).abs() < 1e-9 {
106 sum += u_min + i as f64 * step;
107 count += 1;
108 }
109 }
110 if count == 0 {
111 return Err(FuzzyError::DefuzzFailed(
112 "mean_of_maxima: no maximum found".to_string(),
113 ));
114 }
115 Ok(sum / count as f64)
116}
117pub(super) fn largest_of_maxima(agg: &[f64], u_min: f64, step: f64) -> Result<f64, FuzzyError> {
119 let max_val = agg.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
120 if max_val < f64::EPSILON {
121 return Err(FuzzyError::DefuzzFailed(
122 "largest_of_maxima: maximum membership is zero".to_string(),
123 ));
124 }
125 for (i, &mu) in agg.iter().enumerate().rev() {
126 if (mu - max_val).abs() < 1e-9 {
127 return Ok(u_min + i as f64 * step);
128 }
129 }
130 Err(FuzzyError::DefuzzFailed(
131 "largest_of_maxima: no maximum found".to_string(),
132 ))
133}
134pub(super) fn smallest_of_maxima(agg: &[f64], u_min: f64, step: f64) -> Result<f64, FuzzyError> {
136 let max_val = agg.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
137 if max_val < f64::EPSILON {
138 return Err(FuzzyError::DefuzzFailed(
139 "smallest_of_maxima: maximum membership is zero".to_string(),
140 ));
141 }
142 for (i, &mu) in agg.iter().enumerate() {
143 if (mu - max_val).abs() < 1e-9 {
144 return Ok(u_min + i as f64 * step);
145 }
146 }
147 Err(FuzzyError::DefuzzFailed(
148 "smallest_of_maxima: no maximum found".to_string(),
149 ))
150}