use crate::fuzzy::membership::piecewise::{LinearFunction, PiecewiseLinearFunction};
use crate::utilities;
use std::fmt::{Display, Formatter};
use super::Membership;
#[derive(Debug, PartialEq, Clone)]
pub struct Trapezoidal {
a: f32,
b: f32,
c: f32,
d: f32,
}
#[derive(Debug, PartialEq)]
pub enum TrapezoidalError {
NotEnoughValues { limits: Vec<f32> },
TooManyValues { limits: Vec<f32> },
UnorderedValues { limits: Vec<f32> },
}
impl Display for TrapezoidalError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
use TrapezoidalError::*;
match &self {
NotEnoughValues { limits } => {
write!(
f,
"Trapezoidal membership function needs at least 3 values, you provided {}.",
limits.len()
)
}
TooManyValues { limits } => {
write!(
f,
"Trapezoidal membership function needs at most 4 values, you provided {}.",
limits.len()
)
}
UnorderedValues { limits: _ } => {
write!(
f,
"Trapezoidal membership function needs an ordered array of values."
)
}
}
}
}
impl Membership for Trapezoidal {}
impl Display for Trapezoidal {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.is_triangular() {
write!(f, "({:.2}, {:.2}, {:.2})", self.a, self.b, self.d)
} else {
write!(
f,
"({:.2}, {:.2}, {:.2}, {:.2})",
self.a, self.b, self.c, self.d
)
}
}
}
impl Trapezoidal {
pub fn new(limits: Vec<f32>) -> Result<Self, TrapezoidalError> {
use TrapezoidalError::*;
let len = limits.len();
if len < 3 {
Err(NotEnoughValues { limits })
} else if len > 4 {
Err(TooManyValues { limits })
} else {
for i in 0..len - 1 {
if limits[i] > limits[i + 1] {
return Err(UnorderedValues { limits });
}
}
Ok(Self {
a: limits[0],
b: limits[1],
c: limits[len - 2],
d: limits[len - 1],
})
}
}
pub fn center(&self) -> (f32, f32) {
(self.b, self.c)
}
pub fn coverage(&self) -> (f32, f32) {
(self.a, self.d)
}
pub fn is_triangular(&self) -> bool {
self.b == self.c
}
pub fn centroid(&self) -> f32 {
let centroid_left = (self.a + (2. * self.b)) / 3.;
let centroid_center = (self.b + self.c) / 2.;
let centroid_right = ((2. * self.c) + self.d) / 3.;
let area_left = (self.b - self.a) / 2.;
let area_center = self.c - self.b;
let area_right = (self.d - self.c) / 2.;
let area_sum = area_left + area_center + area_right;
((centroid_left * area_left)
+ (centroid_center * area_center)
+ (centroid_right * area_right))
/ area_sum
}
pub fn is_symmetrical(&self) -> bool {
((self.b - self.a) - (self.d - self.c)).abs() < 0.01
}
pub fn is_symmetrical_respect_center(&self, other: &Trapezoidal, center: f32) -> bool {
let r = 2. * center;
utilities::math::approx_equal_f32(r - self.d, other.a, 5)
&& utilities::math::approx_equal_f32(r - self.c, other.b, 5)
&& utilities::math::approx_equal_f32(r - self.b, other.c, 5)
&& utilities::math::approx_equal_f32(r - self.a, other.d, 5)
}
pub fn membership_value(&self, x: f32) -> f32 {
if x <= self.a || x >= self.d {
0.
} else if x >= self.b && x <= self.c {
1.
} else if x < self.b {
(x - self.a) / (self.b - self.a)
} else {
(x - self.d) / (self.c - self.d)
}
}
pub fn max_min(&self, min: f32, max: f32) -> f32 {
if max >= self.b && min <= self.c {
1.0
} else if max < self.b {
self.membership_value(max)
} else {
self.membership_value(min)
}
}
}
impl From<&Trapezoidal> for PiecewiseLinearFunction {
fn from(t: &Trapezoidal) -> Self {
let mut result = PiecewiseLinearFunction::new();
let (a, d) = t.coverage();
let (b, c) = t.center();
let extremes = |f_0, f_1, plf: &mut PiecewiseLinearFunction| {
if f_0 != f_1 {
let slope = 1.0 / (f_1 - f_0);
let intercept = -1.0 * slope * f_0;
plf.add(
if f_0 < f_1 { f_0 } else { f_1 },
if f_0 < f_1 { f_1 } else { f_0 },
LinearFunction::new(slope as f64, intercept as f64),
)
.unwrap();
}
};
extremes(a as f64, b as f64, &mut result);
extremes(d as f64, c as f64, &mut result);
if b != c {
result
.add(b as f64, c as f64, LinearFunction::new(0.0, 1.0))
.unwrap();
}
result
}
}