use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DefuzzificationMethod {
Centroid,
Bisector,
MeanOfMaximum,
SmallestOfMaximum,
LargestOfMaximum,
WeightedAverage,
}
#[derive(Debug, Clone)]
pub struct FuzzySet {
pub min: f64,
pub max: f64,
pub memberships: Vec<f64>,
}
impl FuzzySet {
pub fn new(min: f64, max: f64, samples: usize) -> Self {
Self {
min,
max,
memberships: vec![0.0; samples],
}
}
pub fn from_memberships(min: f64, max: f64, memberships: Vec<f64>) -> Self {
Self {
min,
max,
memberships,
}
}
fn value_at_index(&self, index: usize) -> f64 {
let range = self.max - self.min;
let step = range / (self.memberships.len() - 1).max(1) as f64;
self.min + index as f64 * step
}
pub fn len(&self) -> usize {
self.memberships.len()
}
pub fn is_empty(&self) -> bool {
self.memberships.is_empty()
}
fn max_membership(&self) -> f64 {
self.memberships.iter().fold(0.0f64, |a, &b| a.max(b))
}
fn max_membership_indices(&self) -> Vec<usize> {
let max_val = self.max_membership();
if max_val == 0.0 {
return vec![];
}
self.memberships
.iter()
.enumerate()
.filter(|(_, &val)| (val - max_val).abs() < 1e-10)
.map(|(i, _)| i)
.collect()
}
fn area(&self) -> f64 {
if self.memberships.is_empty() {
return 0.0;
}
let step = (self.max - self.min) / (self.memberships.len() - 1).max(1) as f64;
let mut area = 0.0;
for i in 0..self.memberships.len() - 1 {
area += step * (self.memberships[i] + self.memberships[i + 1]) / 2.0;
}
area
}
fn centroid_numerator(&self) -> f64 {
if self.memberships.is_empty() {
return 0.0;
}
let step = (self.max - self.min) / (self.memberships.len() - 1).max(1) as f64;
let mut numerator = 0.0;
for i in 0..self.memberships.len() - 1 {
let x1 = self.value_at_index(i);
let x2 = self.value_at_index(i + 1);
let y1 = self.memberships[i];
let y2 = self.memberships[i + 1];
let x_mid = (x1 + x2) / 2.0;
let trap_area = step * (y1 + y2) / 2.0;
numerator += x_mid * trap_area;
}
numerator
}
}
pub fn defuzzify(fuzzy_set: &FuzzySet, method: DefuzzificationMethod) -> Option<f64> {
match method {
DefuzzificationMethod::Centroid => centroid(fuzzy_set),
DefuzzificationMethod::Bisector => bisector(fuzzy_set),
DefuzzificationMethod::MeanOfMaximum => mean_of_maximum(fuzzy_set),
DefuzzificationMethod::SmallestOfMaximum => smallest_of_maximum(fuzzy_set),
DefuzzificationMethod::LargestOfMaximum => largest_of_maximum(fuzzy_set),
DefuzzificationMethod::WeightedAverage => weighted_average(fuzzy_set),
}
}
pub fn centroid(fuzzy_set: &FuzzySet) -> Option<f64> {
let area = fuzzy_set.area();
if area == 0.0 {
return None;
}
let numerator = fuzzy_set.centroid_numerator();
Some(numerator / area)
}
pub fn bisector(fuzzy_set: &FuzzySet) -> Option<f64> {
let total_area = fuzzy_set.area();
if total_area == 0.0 {
return None;
}
let target_area = total_area / 2.0;
let step = (fuzzy_set.max - fuzzy_set.min) / (fuzzy_set.memberships.len() - 1).max(1) as f64;
let mut cumulative_area = 0.0;
for i in 0..fuzzy_set.memberships.len() - 1 {
let trap_area = step * (fuzzy_set.memberships[i] + fuzzy_set.memberships[i + 1]) / 2.0;
if cumulative_area + trap_area >= target_area {
let remaining = target_area - cumulative_area;
let fraction = remaining / trap_area;
let x = fuzzy_set.value_at_index(i)
+ fraction * (fuzzy_set.value_at_index(i + 1) - fuzzy_set.value_at_index(i));
return Some(x);
}
cumulative_area += trap_area;
}
Some((fuzzy_set.min + fuzzy_set.max) / 2.0)
}
pub fn mean_of_maximum(fuzzy_set: &FuzzySet) -> Option<f64> {
let max_indices = fuzzy_set.max_membership_indices();
if max_indices.is_empty() {
return None;
}
let sum: f64 = max_indices
.iter()
.map(|&i| fuzzy_set.value_at_index(i))
.sum();
Some(sum / max_indices.len() as f64)
}
pub fn smallest_of_maximum(fuzzy_set: &FuzzySet) -> Option<f64> {
let max_indices = fuzzy_set.max_membership_indices();
max_indices.first().map(|&i| fuzzy_set.value_at_index(i))
}
pub fn largest_of_maximum(fuzzy_set: &FuzzySet) -> Option<f64> {
let max_indices = fuzzy_set.max_membership_indices();
max_indices.last().map(|&i| fuzzy_set.value_at_index(i))
}
pub fn weighted_average(fuzzy_set: &FuzzySet) -> Option<f64> {
let mut numerator = 0.0;
let mut denominator = 0.0;
for (i, &membership) in fuzzy_set.memberships.iter().enumerate() {
let x = fuzzy_set.value_at_index(i);
numerator += x * membership;
denominator += membership;
}
if denominator == 0.0 {
None
} else {
Some(numerator / denominator)
}
}
#[derive(Debug, Clone)]
pub struct SingletonFuzzySet {
pub singletons: HashMap<String, f64>,
}
impl SingletonFuzzySet {
pub fn new() -> Self {
Self {
singletons: HashMap::new(),
}
}
pub fn add(&mut self, value: String, membership: f64) {
self.singletons.insert(value, membership.clamp(0.0, 1.0));
}
pub fn defuzzify(&self) -> Option<f64> {
let mut numerator = 0.0;
let mut denominator = 0.0;
for (value_str, &membership) in &self.singletons {
if let Ok(value) = value_str.parse::<f64>() {
numerator += value * membership;
denominator += membership;
}
}
if denominator == 0.0 {
None
} else {
Some(numerator / denominator)
}
}
pub fn winner_takes_all(&self) -> Option<(String, f64)> {
self.singletons
.iter()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
.map(|(k, &v)| (k.clone(), v))
}
}
impl Default for SingletonFuzzySet {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_fuzzy_set() -> FuzzySet {
FuzzySet::from_memberships(
0.0,
1.0,
vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.75, 0.5, 0.25, 0.0],
)
}
#[test]
fn test_fuzzy_set_creation() {
let fs = FuzzySet::new(0.0, 10.0, 11);
assert_eq!(fs.len(), 11);
assert!((fs.min - 0.0).abs() < 1e-10);
assert!((fs.max - 10.0).abs() < 1e-10);
}
#[test]
fn test_value_at_index() {
let fs = FuzzySet::new(0.0, 10.0, 11);
assert!((fs.value_at_index(0) - 0.0).abs() < 1e-10);
assert!((fs.value_at_index(5) - 5.0).abs() < 1e-10);
assert!((fs.value_at_index(10) - 10.0).abs() < 1e-10);
}
#[test]
fn test_max_membership() {
let fs = create_test_fuzzy_set();
assert!((fs.max_membership() - 1.0).abs() < 1e-10);
}
#[test]
fn test_max_membership_indices() {
let fs = create_test_fuzzy_set();
let indices = fs.max_membership_indices();
assert_eq!(indices.len(), 1);
assert_eq!(indices[0], 4);
}
#[test]
fn test_centroid() {
let fs = create_test_fuzzy_set();
let result = centroid(&fs).expect("unwrap");
assert!((result - 0.5).abs() < 0.1);
}
#[test]
fn test_bisector() {
let fs = create_test_fuzzy_set();
let result = bisector(&fs).expect("unwrap");
assert!((result - 0.5).abs() < 0.1);
}
#[test]
fn test_mean_of_maximum() {
let fs = create_test_fuzzy_set();
let result = mean_of_maximum(&fs).expect("unwrap");
assert!((result - 0.5).abs() < 1e-10);
}
#[test]
fn test_smallest_of_maximum() {
let fs = FuzzySet::from_memberships(0.0, 1.0, vec![0.0, 0.5, 1.0, 1.0, 1.0, 0.5, 0.0]);
let result = smallest_of_maximum(&fs).expect("unwrap");
assert!((result - 0.333).abs() < 0.05);
}
#[test]
fn test_largest_of_maximum() {
let fs = FuzzySet::from_memberships(0.0, 1.0, vec![0.0, 0.5, 1.0, 1.0, 1.0, 0.5, 0.0]);
let result = largest_of_maximum(&fs).expect("unwrap");
assert!((result - 0.667).abs() < 0.05);
}
#[test]
fn test_weighted_average() {
let fs = FuzzySet::from_memberships(0.0, 10.0, vec![0.2, 0.5, 0.8, 0.5, 0.2]);
let result = weighted_average(&fs).expect("unwrap");
assert!(result > 4.0 && result < 6.0);
}
#[test]
fn test_empty_fuzzy_set() {
let fs = FuzzySet::from_memberships(0.0, 1.0, vec![0.0, 0.0, 0.0]);
assert!(centroid(&fs).is_none());
assert!(bisector(&fs).is_none());
assert!(mean_of_maximum(&fs).is_none());
}
#[test]
fn test_singleton_fuzzy_set() {
let mut sfs = SingletonFuzzySet::new();
sfs.add("0.0".to_string(), 0.2);
sfs.add("5.0".to_string(), 0.8);
sfs.add("10.0".to_string(), 0.3);
let result = sfs.defuzzify().expect("unwrap");
assert!((result - 5.38).abs() < 0.1);
}
#[test]
fn test_singleton_winner_takes_all() {
let mut sfs = SingletonFuzzySet::new();
sfs.add("low".to_string(), 0.3);
sfs.add("medium".to_string(), 0.8);
sfs.add("high".to_string(), 0.5);
let (winner, membership) = sfs.winner_takes_all().expect("unwrap");
assert_eq!(winner, "medium");
assert!((membership - 0.8).abs() < 1e-10);
}
#[test]
fn test_defuzzify_dispatch() {
let fs = create_test_fuzzy_set();
assert!(defuzzify(&fs, DefuzzificationMethod::Centroid).is_some());
assert!(defuzzify(&fs, DefuzzificationMethod::Bisector).is_some());
assert!(defuzzify(&fs, DefuzzificationMethod::MeanOfMaximum).is_some());
assert!(defuzzify(&fs, DefuzzificationMethod::SmallestOfMaximum).is_some());
assert!(defuzzify(&fs, DefuzzificationMethod::LargestOfMaximum).is_some());
assert!(defuzzify(&fs, DefuzzificationMethod::WeightedAverage).is_some());
}
}