use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct FuzzySet<T> {
pub category: T,
pub points: Vec<(f32, f32)>, }
impl<T> FuzzySet<T> {
pub fn new(category: T, points: Vec<(f32, f32)>) -> Self {
Self { category, points }
}
pub fn membership(&self, input: f32) -> f32 {
if self.points.is_empty() {
return 0.0;
}
if input <= self.points[0].0 {
return self.points[0].1;
}
if input >= self.points.last().unwrap().0 {
return self.points.last().unwrap().1;
}
for i in 0..self.points.len() - 1 {
let (x1, y1) = self.points[i];
let (x2, y2) = self.points[i + 1];
if input >= x1 && input <= x2 {
if x2 == x1 {
return y1;
}
return y1 + (y2 - y1) * (input - x1) / (x2 - x1);
}
}
0.0
}
}
#[derive(Debug, Clone)]
pub struct FuzzyRule<InputType, OutputType> {
pub condition: InputType,
pub consequences: HashMap<OutputType, f32>,
}
impl<InputType, OutputType> FuzzyRule<InputType, OutputType>
where
InputType: Clone,
OutputType: Clone + Eq + std::hash::Hash,
{
pub fn new(condition: InputType) -> Self {
Self {
condition,
consequences: HashMap::new(),
}
}
pub fn with_consequence(mut self, output: OutputType, value: f32) -> Self {
self.consequences.insert(output, value);
self
}
}
#[derive(Debug, Clone)]
pub struct FuzzySystem<InputType, OutputType> {
pub input_sets: Vec<FuzzySet<InputType>>,
pub rules: Vec<FuzzyRule<InputType, OutputType>>,
}
impl<InputType, OutputType> Default for FuzzySystem<InputType, OutputType> {
fn default() -> Self {
Self {
input_sets: Vec::new(),
rules: Vec::new(),
}
}
}
impl<InputType, OutputType> FuzzySystem<InputType, OutputType>
where
InputType: Clone + PartialEq,
OutputType: Clone + Eq + std::hash::Hash,
{
pub fn new() -> Self {
Self {
input_sets: Vec::new(),
rules: Vec::new(),
}
}
pub fn add_input_set(&mut self, set: FuzzySet<InputType>) {
self.input_sets.push(set);
}
pub fn add_rule(&mut self, rule: FuzzyRule<InputType, OutputType>) {
self.rules.push(rule);
}
pub fn evaluate(&self, input_value: f32) -> HashMap<OutputType, f32> {
let mut weighted_outputs: HashMap<OutputType, Vec<(f32, f32)>> = HashMap::new();
for rule in &self.rules {
let rule_strength = self
.input_sets
.iter()
.find(|set| set.category == rule.condition)
.map(|set| set.membership(input_value))
.unwrap_or(0.0);
if rule_strength > 0.0 {
for (output_param, value) in &rule.consequences {
weighted_outputs
.entry(output_param.clone())
.or_default()
.push((*value, rule_strength));
}
}
}
let mut result = HashMap::new();
for (param, weighted_values) in weighted_outputs {
let total_weight: f32 = weighted_values.iter().map(|(_, w)| w).sum();
let weighted_sum: f32 = weighted_values.iter().map(|(v, w)| v * w).sum();
if total_weight > 0.0 {
result.insert(param, weighted_sum / total_weight);
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fuzzy_set_creation() {
let points = vec![(0.0, 0.0), (5.0, 1.0), (10.0, 0.0)];
let set = FuzzySet::new("temperature", points.clone());
assert_eq!(set.category, "temperature");
assert_eq!(set.points, points);
}
#[test]
fn test_membership_empty_points() {
let set = FuzzySet::new("empty", vec![]);
assert_eq!(set.membership(5.0), 0.0);
}
#[test]
fn test_membership_single_point() {
let set = FuzzySet::new("single", vec![(5.0, 0.8)]);
assert_eq!(set.membership(3.0), 0.8); assert_eq!(set.membership(5.0), 0.8); assert_eq!(set.membership(7.0), 0.8); }
#[test]
fn test_membership_below_range() {
let points = vec![(10.0, 0.0), (20.0, 1.0), (30.0, 0.0)];
let set = FuzzySet::new("test", points);
assert_eq!(set.membership(5.0), 0.0); }
#[test]
fn test_membership_above_range() {
let points = vec![(10.0, 0.0), (20.0, 1.0), (30.0, 0.0)];
let set = FuzzySet::new("test", points);
assert_eq!(set.membership(35.0), 0.0); }
#[test]
fn test_membership_at_points() {
let points = vec![(0.0, 0.0), (5.0, 1.0), (10.0, 0.0)];
let set = FuzzySet::new("test", points);
assert_eq!(set.membership(0.0), 0.0);
assert_eq!(set.membership(5.0), 1.0);
assert_eq!(set.membership(10.0), 0.0);
}
#[test]
fn test_membership_linear_interpolation() {
let points = vec![(0.0, 0.0), (10.0, 1.0)];
let set = FuzzySet::new("test", points);
assert_eq!(set.membership(5.0), 0.5); assert_eq!(set.membership(2.5), 0.25); assert_eq!(set.membership(7.5), 0.75); }
#[test]
fn test_membership_triangular_function() {
let points = vec![(0.0, 0.0), (5.0, 1.0), (10.0, 0.0)];
let set = FuzzySet::new("test", points);
assert_eq!(set.membership(2.5), 0.5); assert_eq!(set.membership(7.5), 0.5); }
#[test]
fn test_membership_trapezoidal_function() {
let points = vec![(0.0, 0.0), (2.0, 1.0), (8.0, 1.0), (10.0, 0.0)];
let set = FuzzySet::new("test", points);
assert_eq!(set.membership(1.0), 0.5); assert_eq!(set.membership(5.0), 1.0); assert_eq!(set.membership(9.0), 0.5); }
#[test]
fn test_membership_identical_x_values() {
let points = vec![(5.0, 0.3), (5.0, 0.7), (10.0, 0.0)];
let set = FuzzySet::new("test", points);
assert_eq!(set.membership(5.0), 0.3);
}
#[test]
fn test_fuzzy_rule_creation() {
let rule: FuzzyRule<&'static str, &'static str> = FuzzyRule::new("high_temp");
assert_eq!(rule.condition, "high_temp");
assert!(rule.consequences.is_empty());
}
#[test]
fn test_fuzzy_rule_with_consequences() {
let rule: FuzzyRule<&'static str, &'static str> = FuzzyRule::new("high_temp")
.with_consequence("fan_speed", 0.8)
.with_consequence("cooling", 0.9);
assert_eq!(rule.condition, "high_temp");
assert_eq!(rule.consequences.get("fan_speed"), Some(&0.8));
assert_eq!(rule.consequences.get("cooling"), Some(&0.9));
}
#[test]
fn test_fuzzy_rule_overwrite_consequence() {
let rule = FuzzyRule::new("temp")
.with_consequence("output", 0.5)
.with_consequence("output", 0.8);
assert_eq!(rule.consequences.get("output"), Some(&0.8));
assert_eq!(rule.consequences.len(), 1);
}
#[test]
fn test_fuzzy_system_creation() {
let system: FuzzySystem<&str, &str> = FuzzySystem::new();
assert!(system.input_sets.is_empty());
assert!(system.rules.is_empty());
}
#[test]
fn test_fuzzy_system_default() {
let system: FuzzySystem<&str, &str> = FuzzySystem::default();
assert!(system.input_sets.is_empty());
assert!(system.rules.is_empty());
}
#[test]
fn test_add_input_set() {
let mut system: FuzzySystem<&str, &str> = FuzzySystem::new();
let set = FuzzySet::new("low", vec![(0.0, 1.0), (50.0, 0.0)]);
system.add_input_set(set);
assert_eq!(system.input_sets.len(), 1);
assert_eq!(system.input_sets[0].category, "low");
}
#[test]
fn test_add_rule() {
let mut system = FuzzySystem::new();
let rule = FuzzyRule::new("low").with_consequence("slow", 0.3);
system.add_rule(rule);
assert_eq!(system.rules.len(), 1);
assert_eq!(system.rules[0].condition, "low");
}
#[test]
fn test_evaluate_empty_system() {
let system: FuzzySystem<&str, &str> = FuzzySystem::new();
let result = system.evaluate(25.0);
assert!(result.is_empty());
}
#[test]
fn test_evaluate_no_matching_sets() {
let mut system = FuzzySystem::new();
let rule = FuzzyRule::new("nonexistent").with_consequence("output", 1.0);
system.add_rule(rule);
let result = system.evaluate(25.0);
assert!(result.is_empty());
}
#[test]
fn test_evaluate_zero_membership() {
let mut system = FuzzySystem::new();
let set = FuzzySet::new("high", vec![(80.0, 0.0), (100.0, 1.0)]);
let rule = FuzzyRule::new("high").with_consequence("output", 1.0);
system.add_input_set(set);
system.add_rule(rule);
let result = system.evaluate(50.0); assert!(result.is_empty());
}
#[test]
fn test_evaluate_single_rule() {
let mut system = FuzzySystem::new();
let set = FuzzySet::new("medium", vec![(0.0, 0.0), (50.0, 1.0), (100.0, 0.0)]);
let rule = FuzzyRule::new("medium").with_consequence("output", 0.7);
system.add_input_set(set);
system.add_rule(rule);
let result = system.evaluate(50.0); assert_eq!(result.get("output"), Some(&0.7));
}
#[test]
fn test_evaluate_partial_membership() {
let mut system = FuzzySystem::new();
let set = FuzzySet::new("warm", vec![(0.0, 0.0), (50.0, 1.0), (100.0, 0.0)]);
let rule = FuzzyRule::new("warm").with_consequence("fan", 0.8);
system.add_input_set(set);
system.add_rule(rule);
let result = system.evaluate(25.0); assert_eq!(result.get("fan"), Some(&0.8));
}
#[test]
fn test_evaluate_multiple_consequences() {
let mut system = FuzzySystem::new();
let set = FuzzySet::new("hot", vec![(50.0, 0.0), (100.0, 1.0)]);
let rule = FuzzyRule::new("hot")
.with_consequence("fan_speed", 0.9)
.with_consequence("cooling", 0.8);
system.add_input_set(set);
system.add_rule(rule);
let result = system.evaluate(100.0);
assert_eq!(result.get("fan_speed"), Some(&0.9));
assert_eq!(result.get("cooling"), Some(&0.8));
}
#[test]
fn test_evaluate_multiple_rules_same_output() {
let mut system = FuzzySystem::new();
let low_set = FuzzySet::new("low", vec![(0.0, 1.0), (50.0, 0.0)]);
let medium_set = FuzzySet::new("medium", vec![(25.0, 0.0), (75.0, 1.0)]);
let rule1 = FuzzyRule::new("low").with_consequence("action", 0.2);
let rule2 = FuzzyRule::new("medium").with_consequence("action", 0.8);
system.add_input_set(low_set);
system.add_input_set(medium_set);
system.add_rule(rule1);
system.add_rule(rule2);
let result = system.evaluate(37.5);
assert_eq!(result.get("action"), Some(&0.5));
}
#[test]
fn test_evaluate_complex_system() {
let mut system = FuzzySystem::new();
let cold = FuzzySet::new("cold", vec![(0.0, 1.0), (20.0, 0.0)]);
let warm = FuzzySet::new("warm", vec![(15.0, 0.0), (25.0, 1.0), (35.0, 0.0)]);
let hot = FuzzySet::new("hot", vec![(30.0, 0.0), (50.0, 1.0)]);
let rule1 = FuzzyRule::new("cold")
.with_consequence("heater", 1.0)
.with_consequence("fan", 0.0);
let rule2 = FuzzyRule::new("warm")
.with_consequence("heater", 0.3)
.with_consequence("fan", 0.3);
let rule3 = FuzzyRule::new("hot")
.with_consequence("heater", 0.0)
.with_consequence("fan", 1.0);
system.add_input_set(cold);
system.add_input_set(warm);
system.add_input_set(hot);
system.add_rule(rule1);
system.add_rule(rule2);
system.add_rule(rule3);
let result = system.evaluate(25.0); assert_eq!(result.get("heater"), Some(&0.3));
assert_eq!(result.get("fan"), Some(&0.3));
}
#[test]
fn test_evaluate_weighted_average_calculation() {
let mut system = FuzzySystem::new();
let set1 = FuzzySet::new("A", vec![(0.0, 0.0), (10.0, 1.0)]);
let set2 = FuzzySet::new("B", vec![(5.0, 0.0), (15.0, 1.0)]);
let rule1 = FuzzyRule::new("A").with_consequence("output", 0.2);
let rule2 = FuzzyRule::new("B").with_consequence("output", 0.8);
system.add_input_set(set1);
system.add_input_set(set2);
system.add_rule(rule1);
system.add_rule(rule2);
let result = system.evaluate(10.0);
assert!((result.get("output").unwrap() - 0.4).abs() < 1e-6);
}
#[test]
fn test_evaluate_with_numeric_types() {
let mut system: FuzzySystem<i32, i32> = FuzzySystem::new();
let set = FuzzySet::new(1, vec![(0.0, 0.0), (100.0, 1.0)]);
let rule = FuzzyRule::new(1).with_consequence(42, 0.75);
system.add_input_set(set);
system.add_rule(rule);
let result = system.evaluate(50.0);
assert_eq!(result.get(&42), Some(&0.75));
}
#[test]
fn test_membership_precision() {
let points = vec![(0.0, 0.0), (1.0, 1.0)];
let set = FuzzySet::new("test", points);
let membership = set.membership(0.001);
assert!((membership - 0.001).abs() < 1e-6);
let membership = set.membership(0.999);
assert!((membership - 0.999).abs() < 1e-6);
}
#[test]
fn test_system_with_no_rules_matching_input() {
let mut system = FuzzySystem::new();
let set1 = FuzzySet::new("low", vec![(0.0, 1.0), (10.0, 0.0)]);
let set2 = FuzzySet::new("high", vec![(90.0, 0.0), (100.0, 1.0)]);
let rule = FuzzyRule::new("medium").with_consequence("output", 1.0);
system.add_input_set(set1);
system.add_input_set(set2);
system.add_rule(rule);
let result = system.evaluate(50.0);
assert!(result.is_empty());
}
}