fuzzy_expert/
ops.rs

1use std::iter::Sum;
2use std::ops::AddAssign;
3
4use num::Float;
5
6use crate::math::interp;
7
8/// And operator method for combining the compositions of propositions
9/// in a fuzzy rule premise.
10#[derive(Clone, Copy, Debug)]
11pub enum AndOp {
12    Min,
13    Prod,
14    BoundedProd,
15    DrasticProd,
16}
17
18impl AndOp {
19    pub fn call<F: Float>(
20        self,
21        u: impl IntoIterator<Item = F>,
22        v: impl IntoIterator<Item = F>,
23    ) -> impl IntoIterator<Item = F> {
24        match self {
25            Self::Min => ProductionLink::Min.call(u, v),
26            Self::Prod => ProductionLink::Prod.call(u, v),
27            Self::BoundedProd => ProductionLink::BoundedProd.call(u, v),
28            Self::DrasticProd => ProductionLink::DrasticProd.call(u, v),
29        }
30    }
31}
32
33/// Or operator method for combining the compositions of propositions
34/// in a fuzzy rule premise.
35#[derive(Clone, Copy, Debug)]
36pub enum OrOp {
37    Max,
38    ProbOr,
39    BoundedSum,
40    DrasticSum,
41}
42
43impl OrOp {
44    pub fn call<F: Float>(
45        self,
46        u: impl IntoIterator<Item = F>,
47        v: impl IntoIterator<Item = F>,
48    ) -> impl IntoIterator<Item = F> {
49        match self {
50            Self::Max => ProductionLink::Max.call(u, v),
51            Self::ProbOr => ProductionLink::ProbOr.call(u, v),
52            Self::BoundedSum => ProductionLink::BoundedSum.call(u, v),
53            Self::DrasticSum => ProductionLink::DrasticSum.call(u, v),
54        }
55    }
56}
57
58pub enum CompositionOp {
59    MaxMin,
60    MaxProd,
61}
62
63/// Implication operator method for computing the compisitions of propositions
64/// in a fuzzy rule premise.
65#[derive(Clone, Copy, Debug)]
66pub enum ImplicationOp {
67    Ra,
68    Rm,
69    Rc,
70    Rb,
71    Rs,
72    Rg,
73    Rsg,
74    Rgs,
75    Rgg,
76    Rss,
77}
78
79impl ImplicationOp {
80    pub fn call<F: Float>(
81        self,
82        u: impl IntoIterator<Item = F>,
83        v: impl IntoIterator<Item = F>,
84    ) -> impl IntoIterator<Item = F> {
85        u.into_iter().zip(v.into_iter()).map(move |(u, v)| match self {
86            Self::Ra => F::min(F::one(), F::one() - u + v),
87            Self::Rm => F::max(F::min(u, v), F::one() - u),
88            Self::Rc => F::min(u, v),
89            Self::Rb => F::max(F::one() - u, v),
90            Self::Rs => {
91                if u <= v {
92                    F::one()
93                } else {
94                    F::zero()
95                }
96            },
97            Self::Rg => {
98                if u <= v {
99                    F::one()
100                } else {
101                    v
102                }
103            },
104            Self::Rsg => F::min(
105                Self::Rs.call(Some(u), Some(v)).into_iter().next().expect("unreachable"),
106                Self::Rg
107                    .call(Some(F::one() - u), Some(F::one() - v))
108                    .into_iter()
109                    .next()
110                    .expect("unreachable"),
111            ),
112            Self::Rgs => F::min(
113                Self::Rg.call(Some(u), Some(v)).into_iter().next().expect("unreachable"),
114                Self::Rs
115                    .call(Some(F::one() - u), Some(F::one() - v))
116                    .into_iter()
117                    .next()
118                    .expect("unreachable"),
119            ),
120            Self::Rgg => F::min(
121                Self::Rg.call(Some(u), Some(v)).into_iter().next().expect("unreachable"),
122                Self::Rg
123                    .call(Some(F::one() - u), Some(F::one() - v))
124                    .into_iter()
125                    .next()
126                    .expect("unreachable"),
127            ),
128            Self::Rss => F::min(
129                Self::Rs.call(Some(u), Some(v)).into_iter().next().expect("unreachable"),
130                Self::Rs
131                    .call(Some(F::one() - u), Some(F::one() - v))
132                    .into_iter()
133                    .next()
134                    .expect("unreachable"),
135            ),
136        })
137    }
138}
139
140/// Method for aggregating the consequences of the fuzzy rules
141#[derive(Clone, Copy, Debug)]
142pub enum ProductionLink {
143    Min,
144    Prod,
145    BoundedProd,
146    DrasticProd,
147    Max,
148    ProbOr,
149    BoundedSum,
150    DrasticSum,
151}
152
153impl ProductionLink {
154    pub fn call<F: Float>(
155        self,
156        u: impl IntoIterator<Item = F>,
157        v: impl IntoIterator<Item = F>,
158    ) -> impl IntoIterator<Item = F> {
159        u.into_iter().zip(v.into_iter()).map(move |(u, v)| match self {
160            Self::Max => F::max(u, v),
161            Self::ProbOr => u + v - u * v,
162            Self::BoundedSum => F::min(F::one(), u + v),
163            Self::DrasticSum => {
164                if v == F::zero() {
165                    u
166                } else if u == F::zero() {
167                    v
168                } else {
169                    F::one()
170                }
171            },
172            Self::Min => F::min(u, v),
173            Self::Prod => u * v,
174            Self::BoundedProd => F::max(F::zero(), u + v - F::one()),
175            Self::DrasticProd => {
176                if v == F::zero() {
177                    u
178                } else if u == F::one() {
179                    v
180                } else {
181                    F::zero()
182                }
183            },
184        })
185    }
186}
187
188/// Method for defuzzifcating the resulting membership function.
189#[derive(Clone, Copy, Debug)]
190pub enum DefuzzificationOp {
191    /// Center of Gravity
192    Cog,
193    /// Bisector of Area
194    Boa,
195    /// Mean of the values for which the membership function is maximum
196    Mom,
197    /// Largest value for which the membership function is maximum
198    Lom,
199    /// Smallest value for which the membership function is minimum
200    Som,
201}
202
203impl DefuzzificationOp {
204    pub fn call<F: Float + Sum + AddAssign>(self, universe: &[F], membership: &[F]) -> F {
205        match self {
206            Self::Cog => {
207                let n_areas = universe.len() - 1;
208                let mut areas = Vec::with_capacity(n_areas);
209                let mut centroids = Vec::with_capacity(n_areas);
210                let two = F::one() + F::one();
211                let three = two + F::one();
212
213                for i in 0..n_areas {
214                    let base = universe[i + 1] - universe[i];
215                    let area_rect = F::min(membership[i], membership[i + 1]) * base;
216                    let center_rect = universe[i] + base / two;
217                    let (area_tria, center_tri) = if membership[i + 1] == membership[i] {
218                        (F::zero(), F::zero())
219                    } else if membership[i + 1] > membership[i] {
220                        (
221                            base * F::abs(membership[i + 1] - membership[i]) / two,
222                            universe[i] + two / three * base,
223                        )
224                    } else {
225                        (
226                            base * F::abs(membership[i + 1] - membership[i]) / two,
227                            universe[i] + F::one() / three * base,
228                        )
229                    };
230                    let area = area_rect + area_tria;
231                    let center = if area == F::zero() {
232                        F::zero()
233                    } else {
234                        (area_rect * center_rect + area_tria * center_tri) / (area_rect + area_tria)
235                    };
236
237                    areas.push(area);
238                    centroids.push(center);
239                }
240
241                let den = areas.iter().copied().sum::<F>();
242                let num = areas
243                    .into_iter()
244                    .zip(centroids.into_iter())
245                    .map(|(area, cent)| area * cent)
246                    .sum::<F>();
247
248                num / den
249            },
250            Self::Boa => {
251                let n_areas = universe.len() - 1;
252                let mut areas = Vec::with_capacity(n_areas);
253                let two = F::one() + F::one();
254
255                for i_area in 0..n_areas {
256                    let base = universe[i_area + 1] - universe[i_area];
257                    let area = (membership[i_area] + membership[i_area + 1]) * base / two;
258                    areas.push(area);
259                }
260
261                let total_area = areas.iter().copied().sum::<F>();
262                let target = total_area / two;
263                let mut cum_area = F::zero();
264                let mut i_area = 0;
265
266                for i in 0..=n_areas {
267                    cum_area += areas[i];
268                    i_area = i;
269                    if cum_area >= target {
270                        break;
271                    }
272                }
273
274                let xp = [universe[i_area], universe[i_area + 1]];
275                let fp = [cum_area - areas[i_area], cum_area];
276
277                interp(Some(target), xp.into_iter().zip(fp.into_iter()))
278                    .into_iter()
279                    .next()
280                    .expect("unreachable")
281            },
282            Self::Mom => {
283                let maximum = membership.iter().copied().reduce(F::max).unwrap();
284                let (len, sum) = universe
285                    .iter()
286                    .copied()
287                    .zip(membership.iter().copied())
288                    .filter_map(|(u, m)| if m == maximum { Some(u) } else { None })
289                    .enumerate()
290                    .fold((0usize, F::zero()), |(_i, accum), (i, next)| (i + 1, accum + next));
291
292                sum / F::from(len).unwrap()
293            },
294            Self::Lom => {
295                let maximum = membership.iter().copied().reduce(F::max).unwrap();
296                universe
297                    .iter()
298                    .copied()
299                    .zip(membership.iter().copied())
300                    .filter_map(|(u, m)| if m == maximum { Some(u) } else { None })
301                    .reduce(F::max)
302                    .unwrap()
303            },
304            Self::Som => {
305                let maximum = membership.iter().copied().reduce(F::max).unwrap();
306                universe
307                    .iter()
308                    .copied()
309                    .zip(membership.iter().copied())
310                    .filter_map(|(u, m)| if m == maximum { Some(u) } else { None })
311                    .reduce(F::min)
312                    .unwrap()
313            },
314        }
315    }
316}