rust_fuzzylogic/
system.rs

1use std::{borrow::Borrow, collections::HashMap, hash::Hash};
2
3use crate::{
4    aggregate::aggregation, defuzz::defuzzification, error::MissingSpace, mamdani::Rule,
5    prelude::*, variable::Variable,
6};
7
8/// Validation: ensure all consequents/antecedents reference existing vars/terms.
9pub fn validate_rules<KV>(rules: &[Rule], vars: &HashMap<KV, Variable>) -> Result<()>
10where
11    KV: Eq + Hash + Borrow<str>,
12{
13    for r in rules {
14        r.validate(vars)?;
15    }
16    Ok(())
17}
18
19pub trait Evaluator {
20    fn evaluate<KI>(&self, input: &HashMap<KI, Float>) -> Result<HashMap<String, Float>>
21    where
22        KI: Eq + Hash + Borrow<str>;
23}
24
25/// A comprehensive struct for handling variables, rules and a uniform sampler cohesively.
26pub struct System {
27    pub vars: HashMap<String, Variable>,
28    pub rules: Vec<Rule>,
29    pub sampler: UniformSampler,
30}
31
32impl System {
33    /// Create a new system and validate rules against provided variables.
34    pub fn new(
35        vars: HashMap<String, Variable>,
36        rules: Vec<Rule>,
37        sampler: UniformSampler,
38    ) -> Result<Self> {
39        // Validate without consuming the collections first.
40        for r in &rules {
41            r.validate(&vars)?;
42        }
43        Ok(Self {
44            vars,
45            rules,
46            sampler,
47        })
48    }
49}
50
51impl Evaluator for System {
52    fn evaluate<KI>(&self, input: &HashMap<KI, Float>) -> Result<HashMap<String, Float>>
53    where
54        KI: Eq + Hash + Borrow<str>,
55    {
56        let myu = aggregation(&self.rules, input, &self.vars, &self.sampler)?;
57        defuzzification(&myu, &self.vars)
58    }
59}
60
61// Builder for a System.
62pub struct SystemBuilder {
63    vars: HashMap<String, Variable>,
64    rules: Vec<Rule>,
65    sampler_n: usize,
66}
67
68impl SystemBuilder {
69    /// Initialize a new SystemBuilder.
70    pub fn new() -> Self {
71        Self {
72            vars: HashMap::new(),
73            rules: Vec::new(),
74            sampler_n: UniformSampler::DEFAULT_N,
75        }
76    }
77
78    /// Configure the uniform sampler resolution.
79    pub fn uniform_sampler(mut self, n: usize) -> Result<Self> {
80        self.sampler_n = UniformSampler::new(n)?.n;
81        Ok(self)
82    }
83
84    /// Insert a variable by name with a numeric domain.
85    pub fn var(mut self, name: impl Into<String>, min: Float, max: Float) -> Result<Self> {
86        let name = name.into();
87        if self.vars.contains_key(&name) {
88            return Err(FuzzyError::BadArity);
89        }
90        self.vars.insert(name, Variable::new(min, max)?);
91        Ok(self)
92    }
93
94    /// Insert a term into an existing variable.
95    pub fn term(
96        mut self,
97        var: &str,
98        term: impl Into<String>,
99        mf: impl MembershipFn + Send + Sync + 'static,
100    ) -> Result<Self> {
101        let var_ref = self.vars.get_mut(var).ok_or(FuzzyError::NotFound {
102            space: MissingSpace::Var,
103            key: var.to_string(),
104        })?;
105
106        let name: String = term.into();
107        let t = Term::new(name.clone(), mf);
108        var_ref.insert_term(&name, t)?;
109
110        Ok(self)
111    }
112
113    /// Push a single rule into the builder.
114    pub fn rule(mut self, rule: Rule) -> Result<Self> {
115        self.rules.push(rule);
116        Ok(self)
117    }
118
119    /// Finalize and build a System with a `UniformSampler`.
120    pub fn build(self) -> Result<System> {
121        System::new(self.vars, self.rules, UniformSampler::new(self.sampler_n)?)
122    }
123}