Skip to main content

ipfrs_tensorlogic/fuzzy_logic_engine/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::types::{FuzzyError, FuzzyExpr, FuzzyVariable};
6
7/// Determine the universe bounds [min, max] from the sets of an output variable.
8pub(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}
28/// Check whether an expression references `var_name` in its consequent position.
29pub(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}
40/// Extract the set name from a consequent expression targeting `var_name`.
41/// Returns `None` if the expression is not a direct `Is { var_name, set }`.
42pub(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}
48/// Name of the output set with highest membership at `x`.
49pub(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}
60/// Centroid (centre-of-gravity) defuzzification.
61pub(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}
76/// Bisector defuzzification — x where cumulative area = half total area.
77pub(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}
94/// Mean of maxima defuzzification.
95pub(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}
117/// Largest of maxima defuzzification.
118pub(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}
134/// Smallest of maxima defuzzification.
135pub(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}