1use std::{borrow::Borrow, collections::HashMap, hash::Hash};
2
3use crate::{prelude::*, variable::Variable};
7
8#[derive(Debug, Clone, PartialEq)]
18pub enum Antecedent {
19 Atom { var: String, term: String },
21 And(Box<Self>, Box<Self>),
23 Or(Box<Self>, Box<Self>),
25 Not(Box<Self>),
27}
28
29impl Antecedent {
30 fn iter_atoms<'a>(&'a self) -> impl Iterator<Item = (&'a str, &'a str)> {
31 AtomsIter { stack: vec![self] }
32 }
33}
34
35struct AtomsIter<'a> {
36 stack: Vec<&'a Antecedent>,
37}
38
39impl<'a> Iterator for AtomsIter<'a> {
40 type Item = (&'a str, &'a str);
41 fn next(&mut self) -> Option<Self::Item> {
42 while let Some(node) = self.stack.pop() {
43 match node {
44 Antecedent::Atom { var, term } => return Some((var.as_str(), term.as_str())),
45 Antecedent::And(a, b) | Antecedent::Or(a, b) => {
46 self.stack.push(b);
47 self.stack.push(a);
48 }
49 Antecedent::Not(a) => self.stack.push(a),
50 }
51 }
52 None
53 }
54}
55
56pub fn eval_antecedent<KI, KV>(
74 ant: &Antecedent,
75 input: &HashMap<KI, Float>,
76 vars: &HashMap<KV, Variable>,
77) -> Result<Float>
78where
79 KI: Eq + Hash + Borrow<str>,
80 KV: Eq + Hash + Borrow<str>,
81{
82 match ant {
84 Antecedent::Atom { var, term } => {
85 let v = vars.get(var.as_str()).ok_or(FuzzyError::NotFound {
86 space: crate::error::MissingSpace::Var,
87 key: var.clone(),
88 })?;
89 let x = *input.get(var.as_str()).ok_or(FuzzyError::NotFound {
90 space: crate::error::MissingSpace::Input,
91 key: var.clone(),
92 })?;
93 v.eval(term.as_str(), x)
94 }
95 Antecedent::And(a, b) => {
96 let a = eval_antecedent(a, input, vars)?;
97 let b = eval_antecedent(b, input, vars)?;
98 Ok(a.min(b))
99 }
100 Antecedent::Or(a, b) => {
101 let a = eval_antecedent(a, input, vars)?;
102 let b = eval_antecedent(b, input, vars)?;
103 Ok(a.max(b))
104 }
105 Antecedent::Not(a) => {
106 let a = eval_antecedent(a, input, vars)?;
107 Ok(1.0 - a)
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use std::collections::HashMap;
115
116 use crate::membership::triangular::Triangular;
117 use crate::prelude::*;
118 use crate::term::Term;
119 use crate::variable::Variable;
120
121 #[test]
122 fn red_antecedent_and_not_behavior() {
123 let eps = crate::Float::EPSILON;
124
125 let mut temp = Variable::new(-10.0, 10.0).unwrap();
127 temp.insert_term(
128 "cold",
129 Term::new("cold", Triangular::new(-10.0, -5.0, 0.0).unwrap()),
130 )
131 .unwrap();
132 temp.insert_term(
133 "hot",
134 Term::new("hot", Triangular::new(0.0, 5.0, 10.0).unwrap()),
135 )
136 .unwrap();
137
138 let mut vars: HashMap<&str, Variable> = HashMap::new();
139 vars.insert("temp", temp);
140
141 let mut inputs: HashMap<&str, crate::Float> = HashMap::new();
143 inputs.insert("temp", 7.5);
144
145 let ast = crate::antecedent::Antecedent::And(
147 Box::new(crate::antecedent::Antecedent::Atom {
148 var: "temp".into(),
149 term: "hot".into(),
150 }),
151 Box::new(crate::antecedent::Antecedent::Not(Box::new(
152 crate::antecedent::Antecedent::Atom {
153 var: "temp".into(),
154 term: "cold".into(),
155 },
156 ))),
157 );
158
159 let hot = Triangular::new(0.0, 5.0, 10.0).unwrap().eval(7.5);
161 let cold = Triangular::new(-10.0, -5.0, 0.0).unwrap().eval(7.5);
162 let expected = hot.min(1.0 - cold);
163
164 let y = crate::antecedent::eval_antecedent(&ast, &inputs, &vars).unwrap();
165 assert!((y - expected).abs() < eps);
166 }
167
168 #[test]
170 fn antecedent_or_behavior() {
171 let mut temp = Variable::new(-10.0, 10.0).unwrap();
173 temp.insert_term(
174 "cold",
175 Term::new("cold", Triangular::new(-10.0, -5.0, 0.0).unwrap()),
176 )
177 .unwrap();
178 temp.insert_term(
179 "hot",
180 Term::new("hot", Triangular::new(0.0, 5.0, 10.0).unwrap()),
181 )
182 .unwrap();
183
184 let mut vars: HashMap<&str, Variable> = HashMap::new();
185 vars.insert("temp", temp);
186
187 let mut inputs: HashMap<&str, crate::Float> = HashMap::new();
188 inputs.insert("temp", -5.0);
189
190 let ast = crate::antecedent::Antecedent::Or(
192 Box::new(crate::antecedent::Antecedent::Atom {
193 var: "temp".into(),
194 term: "cold".into(),
195 }),
196 Box::new(crate::antecedent::Antecedent::Atom {
197 var: "temp".into(),
198 term: "hot".into(),
199 }),
200 );
201
202 let cold = Triangular::new(-10.0, -5.0, 0.0).unwrap().eval(-5.0);
204 let hot = Triangular::new(0.0, 5.0, 10.0).unwrap().eval(-5.0);
205 let expected = cold.max(hot);
206
207 let y = crate::antecedent::eval_antecedent(&ast, &inputs, &vars).unwrap();
208 assert!((y - expected).abs() < crate::Float::EPSILON);
209 }
210}