rust_fuzzylogic/
variable.rs1use crate::{error::FuzzyError, membership::MembershipFn, term::Term, Float};
4
5use std::collections::HashMap;
6
7pub struct Variable {
9 min: Float,
11
12 max: Float,
14
15 pub terms: HashMap<String, Term>,
17}
18impl Variable {
19 pub fn new(min: Float, max: Float) -> crate::error::Result<Self> {
21 if min >= max {
23 Err(FuzzyError::OutOfBounds)
24 } else {
25 Ok(Self {
27 min: min,
28 max: max,
29 terms: HashMap::new(),
30 })
31 }
32 }
33
34 pub fn insert_term(&mut self, name: &str, t: Term) -> crate::error::Result<()> {
39 if name == "" {
41 Err(FuzzyError::EmptyInput)
42 }
43 else if self.get(name).is_some() {
45 Err(FuzzyError::TypeMismatch)
46 } else {
47 self.terms.insert(name.to_string(), t);
49 Ok(())
50 }
51 }
52
53 pub fn get(&self, name: &str) -> Option<&Term> {
55 self.terms.get(name)
56 }
57
58 pub fn eval(&self, name: &str, x: Float) -> crate::error::Result<Float> {
63 let v = &self.terms.get(name).ok_or(FuzzyError::TypeMismatch)?;
65 if self.max < x || self.min > x {
67 Err(FuzzyError::OutOfBounds)
68 }
69 else {
71 Ok(v.eval(x))
72 }
73 }
74
75 pub fn domain(&self) -> (Float, Float) {
79 (self.min, self.max)
80 }
81 }
85
86#[cfg(test)]
88mod tests {
89 use crate::error::FuzzyError;
90 use crate::membership::triangular::Triangular;
91 use crate::prelude::*;
92 use crate::term::Term;
93
94 #[test]
96 fn test_new_rejects_invalid_domain() {
97 assert!(matches!(
98 crate::variable::Variable::new(1.0, 1.0),
99 Err(FuzzyError::OutOfBounds)
100 ));
101 assert!(matches!(
102 crate::variable::Variable::new(2.0, 1.0),
103 Err(FuzzyError::OutOfBounds)
104 ));
105 }
106
107 #[test]
109 fn test_insert_and_eval_by_name() {
110 let mut v = crate::variable::Variable::new(-10.0, 10.0).unwrap();
111
112 let cold_tri = Triangular::new(-10.0, -5.0, 0.0).unwrap();
114 let hot_tri = Triangular::new(0.0, 5.0, 10.0).unwrap();
115 let cold = Term::new("cold", cold_tri);
116 let hot = Term::new("hot", hot_tri);
117
118 v.insert_term("cold", cold).unwrap();
120 v.insert_term("hot", hot).unwrap();
121
122 assert!(v.get("cold").is_some());
123 assert!(v.get("warm").is_none());
124
125 let x1 = -5.0;
127 let x2 = 7.5;
128
129 let expected_cold_x1 = Triangular::new(-10.0, -5.0, 0.0).unwrap().eval(x1);
130 let expected_hot_x2 = Triangular::new(0.0, 5.0, 10.0).unwrap().eval(x2);
131
132 let eps = crate::Float::EPSILON;
133 let y_cold_x1 = v.eval("cold", x1).unwrap();
134 let y_hot_x2 = v.eval("hot", x2).unwrap();
135 assert!((y_cold_x1 - expected_cold_x1).abs() < eps);
136 assert!((y_hot_x2 - expected_hot_x2).abs() < eps);
137
138 assert!(v.eval("cold", -10.0).is_ok());
140 assert!(v.eval("hot", 10.0).is_ok());
141 }
142
143 #[test]
145 fn test_duplicate_term_rejected() {
146 let mut v = crate::variable::Variable::new(0.0, 1.0).unwrap();
147 let t1 = Term::new("x", Triangular::new(0.0, 0.5, 1.0).unwrap());
148 let t2 = Term::new("x", Triangular::new(0.0, 0.25, 0.5).unwrap());
149
150 v.insert_term("x", t1).unwrap();
151 assert!(matches!(
153 v.insert_term("x", t2),
154 Err(FuzzyError::TypeMismatch)
155 ));
156 }
157
158 #[test]
160 fn test_eval_unknown_term_errors() {
161 let v = crate::variable::Variable::new(0.0, 1.0).unwrap();
162 assert!(matches!(
164 v.eval("missing", 0.3),
165 Err(FuzzyError::TypeMismatch)
166 ));
167 }
168
169 #[test]
171 fn test_eval_out_of_domain_errors() {
172 let mut v = crate::variable::Variable::new(0.0, 1.0).unwrap();
173 v.insert_term("x", Term::new("x", Triangular::new(0.0, 0.5, 1.0).unwrap()))
174 .unwrap();
175
176 assert!(matches!(v.eval("x", -0.1), Err(FuzzyError::OutOfBounds)));
178 assert!(matches!(v.eval("x", 1.1), Err(FuzzyError::OutOfBounds)));
179 }
180}