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
29pub fn eval_antecedent<KI, KV>(
47 ant: &Antecedent,
48 input: &HashMap<KI, Float>,
49 vars: &HashMap<KV, Variable>,
50) -> Result<Float>
51where
52 KI: Eq + Hash + Borrow<str>,
53 KV: Eq + Hash + Borrow<str>,
54{
55 match ant {
57 Antecedent::Atom { var, term } => {
58 let v = vars.get(var.as_str()).ok_or(FuzzyError::NotFound {
59 space: crate::error::MissingSpace::Var,
60 key: var.clone(),
61 })?;
62 let x = *input.get(var.as_str()).ok_or(FuzzyError::NotFound {
63 space: crate::error::MissingSpace::Input,
64 key: term.clone(),
65 })?;
66 v.eval(term.as_str(), x)
67 }
68 Antecedent::And(a, b) => {
69 let a = eval_antecedent(a, input, vars)?;
70 let b = eval_antecedent(b, input, vars)?;
71 Ok(a.min(b))
72 }
73 Antecedent::Or(a, b) => {
74 let a = eval_antecedent(a, input, vars)?;
75 let b = eval_antecedent(b, input, vars)?;
76 Ok(a.max(b))
77 }
78 Antecedent::Not(a) => {
79 let a = eval_antecedent(a, input, vars)?;
80 Ok(1.0 - a)
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use std::collections::HashMap;
88
89 use crate::membership::triangular::Triangular;
90 use crate::prelude::*;
91 use crate::term::Term;
92 use crate::variable::Variable;
93
94 #[test]
95 fn red_antecedent_and_not_behavior() {
96 let eps = crate::Float::EPSILON;
97
98 let mut temp = Variable::new(-10.0, 10.0).unwrap();
100 temp.insert_term(
101 "cold",
102 Term::new("cold", Triangular::new(-10.0, -5.0, 0.0).unwrap()),
103 )
104 .unwrap();
105 temp.insert_term(
106 "hot",
107 Term::new("hot", Triangular::new(0.0, 5.0, 10.0).unwrap()),
108 )
109 .unwrap();
110
111 let mut vars: HashMap<&str, Variable> = HashMap::new();
112 vars.insert("temp", temp);
113
114 let mut inputs: HashMap<&str, crate::Float> = HashMap::new();
116 inputs.insert("temp", 7.5);
117
118 let ast = crate::antecedent::Antecedent::And(
120 Box::new(crate::antecedent::Antecedent::Atom {
121 var: "temp".into(),
122 term: "hot".into(),
123 }),
124 Box::new(crate::antecedent::Antecedent::Not(Box::new(
125 crate::antecedent::Antecedent::Atom {
126 var: "temp".into(),
127 term: "cold".into(),
128 },
129 ))),
130 );
131
132 let hot = Triangular::new(0.0, 5.0, 10.0).unwrap().eval(7.5);
134 let cold = Triangular::new(-10.0, -5.0, 0.0).unwrap().eval(7.5);
135 let expected = hot.min(1.0 - cold);
136
137 let y = crate::antecedent::eval_antecedent(&ast, &inputs, &vars).unwrap();
138 assert!((y - expected).abs() < eps);
139 }
140
141 #[test]
143 fn antecedent_or_behavior() {
144 let mut temp = Variable::new(-10.0, 10.0).unwrap();
146 temp.insert_term(
147 "cold",
148 Term::new("cold", Triangular::new(-10.0, -5.0, 0.0).unwrap()),
149 )
150 .unwrap();
151 temp.insert_term(
152 "hot",
153 Term::new("hot", Triangular::new(0.0, 5.0, 10.0).unwrap()),
154 )
155 .unwrap();
156
157 let mut vars: HashMap<&str, Variable> = HashMap::new();
158 vars.insert("temp", temp);
159
160 let mut inputs: HashMap<&str, crate::Float> = HashMap::new();
161 inputs.insert("temp", -5.0);
162
163 let ast = crate::antecedent::Antecedent::Or(
165 Box::new(crate::antecedent::Antecedent::Atom {
166 var: "temp".into(),
167 term: "cold".into(),
168 }),
169 Box::new(crate::antecedent::Antecedent::Atom {
170 var: "temp".into(),
171 term: "hot".into(),
172 }),
173 );
174
175 let cold = Triangular::new(-10.0, -5.0, 0.0).unwrap().eval(-5.0);
177 let hot = Triangular::new(0.0, 5.0, 10.0).unwrap().eval(-5.0);
178 let expected = cold.max(hot);
179
180 let y = crate::antecedent::eval_antecedent(&ast, &inputs, &vars).unwrap();
181 assert!((y - expected).abs() < crate::Float::EPSILON);
182 }
183}