Source code for fuzz.memb_funcs

""" Membership function types, ranging from constant, linear or other more complex shape mapping """
# pylint: disable=fixme, invalid-name, R0903
from typing import Any, List, Tuple
import numpy as np


def _integral_approximation(a: float, b: float, f: np.ndarray):
    return (b - a) * np.mean(f)


def _call_end(data: np.ndarray) -> np.ndarray:
    return np.where(np.isnan(data), 0, data)


[docs]class MembershipFunction: """Function Meta"""
[docs] def __init__(self, min_v: float, max_v: float) -> None: self.min_v = float(min_v) self.max_v = float(max_v)
def __call__(self, data: Any, activation: float) -> np.ndarray: if not 0.0 <= activation <= 1.0: raise ValueError(f"Expected activation to be between 0 and 1. Received {activation}") data = np.asfarray(data) data = np.where(data > self.max_v, np.nan, data) data = np.where(data < self.min_v, np.nan, data) return data
[docs] def describe(self) -> Tuple[np.ndarray, np.ndarray]: """describe is an function-optimized iterator, from min_v to max_v returning a tuple of \ ndarrays as X and Y values. Useful for plotting a line graph and visualizing \ the membership function shape Raises: NotImplementedError: means the membership function is missing the describe method Returns: Tuple[np.ndarray, np.ndarray]: a tuple of X-Y value arrays """ raise NotImplementedError
[docs] def naive_describe( self, activation: float, granularity: float ) -> Tuple[np.ndarray, np.ndarray]: """same as describe, but not optmize. Draws an iterator with linear values between \ min_v and max_v and a certain granularity. Args: activation (float): max y-value for any x-value granularity (float): defines the linear iteration sample size Returns: Tuple[np.ndarray, np.ndarray]: a tuple of X-Y value arrays. """ sample_size = round((self.max_v - self.min_v) / granularity) x_range = np.linspace(self.min_v, self.max_v, sample_size) y_range = self.__call__(x_range, activation) return x_range, y_range
[docs] def optimal_integration(self, activation: float) -> List[Tuple[float, float, np.ndarray]]: """Optimal integration strategy. Defined at subclass""" raise NotImplementedError
[docs] def naive_integration( self, activation: float, granularity: float, ) -> List[Tuple[float, float, np.ndarray]]: """naive linear integration traversing the entire function. Args: granularity (float): sample size to numerical integration approximation Returns: List[Tuple[float, float, np.ndarray]]: List of Tuple object containing (a, b, f(x)) \ for numerical integration """ desc = self.naive_describe(activation, granularity) return [(self.min_v, self.max_v, desc[1])]
[docs] def area(self, activation=1.0, granularity=0.01): """Numerical integration to calculate area Args: granularity ([type]): [description] Raises: NotImplementedError: [description] """ try: res = self.optimal_integration(activation) except NotImplementedError: res = self.naive_integration(activation, granularity) area = 0.0 for v_min, v_max, y_res in res: area += _integral_approximation(v_min, v_max, y_res) return area
[docs]class Singleton(MembershipFunction): """Boolean function. Return 1 when x == value and 0 otherwise"""
[docs] def __init__(self, value: float) -> None: super().__init__(float("-inf"), float("+inf")) self.value = value
def __call__(self, data: np.ndarray, activation=1.0) -> np.ndarray: data = super().__call__(data, activation) res_val = activation if activation is not None else 1.0 return _call_end(np.where(data == self.value, res_val, 0))
[docs] def describe(self): x_array = np.array([self.value]) y_array = self.__call__(x_array) return x_array, y_array
[docs] def optimal_integration(self, activation: float) -> List[Tuple[float, float, np.ndarray]]: raise TypeError("Cannot calculate area or perform integration on singleton type")
[docs]class Constant(MembershipFunction): """Constant function. Returns the initialization value""" # def __init__(self, v_min: float, v_max: float) -> None: # super().__init__(v_min, v_max) def __call__(self, data: np.ndarray, activation=1.0) -> np.ndarray: data = super().__call__(data, activation) y_res = np.where(np.isnan(data), np.nan, activation) return _call_end(y_res)
[docs] def describe(self): x_array = np.array([self.min_v, self.max_v]) y_array = self.__call__(x_array) return x_array, y_array
[docs] def optimal_integration(self, activation) -> List[Tuple[float, float, np.ndarray]]: y_arr = self.__call__(self.min_v, activation) return [(self.min_v, self.max_v, np.full(1, y_arr))]
[docs]class Linear(MembershipFunction): """Linear function"""
[docs] def __init__(self, y_eq_zero: float, y_eq_one: float) -> None: if y_eq_zero == y_eq_one: raise ValueError("x_intersect cannot be equal to y_eq_one") if y_eq_zero < y_eq_one: super().__init__(y_eq_zero, y_eq_one) else: super().__init__(y_eq_one, y_eq_zero) if abs(float(y_eq_zero)) == float("inf") or abs(float(y_eq_one)) == float("inf"): raise ValueError("Linear function is not defined for infinite intersects") self.slope = 1 / (y_eq_one - y_eq_zero) self.b = 1 - (self.slope * y_eq_one)
def __call__(self, data: np.ndarray, activation=1.0) -> np.ndarray: data = super().__call__(data, activation) y_res = data * self.slope + self.b return _call_end(np.where(y_res > activation, activation, y_res))
[docs] def describe(self): x_array = np.array([self.min_v, self.max_v]) y_array = self.__call__(x_array) return x_array, y_array
[docs] def optimal_integration(self, activation) -> List[Tuple[float, float, np.ndarray]]: x = (activation - self.b) / self.slope if x in (self.min_v, self.max_v): y_arr = self.__call__(np.array([self.min_v, self.max_v]), activation) return [(self.min_v, self.max_v, y_arr)] if self.min_v < x < self.max_v: res = [] y_arr1 = self.__call__(np.array([self.min_v, x]), activation) res.append([self.min_v, x, y_arr1]) y_arr2 = self.__call__(np.array([x, self.max_v]), activation) res.append([x, self.max_v, y_arr2]) return res raise NotImplementedError
# class Smf(FunctionBase): # """S-shaped membership function""" # class Pimf(FunctionBase): # """Pi-shaped membership function""" # class Zmf(FunctionBase): # """Z-shaped membership function"""
[docs]class Trimf(MembershipFunction): """Triangular membership function"""
[docs] def __init__(self, bottom1: float, peak: float, bottom2: float) -> None: super().__init__(bottom1, bottom2) assert bottom1 < peak < bottom2, "Cannot draw triangule from initialization parameters" self.up = Linear(bottom1, peak) self.down = Linear(bottom2, peak)
def __call__(self, data: np.ndarray, activation=1.0) -> np.ndarray: data = np.asfarray(data) index = np.arange(data.size) indexed_data = np.column_stack((index, data)) mask = indexed_data[:, 1] <= self.up.max_v dt_up = indexed_data[mask, :] res_up = np.column_stack((dt_up[:, 0], self.up(dt_up[:, 1], activation))) dt_down = indexed_data[~mask, :] res_down = np.column_stack((dt_down[:, 0], self.down(dt_down[:, 1], activation))) all_res = np.append(res_up, res_down, axis=0) all_res_sorted = all_res[all_res[:, 0].argsort()] return _call_end(all_res_sorted[:, 1])
[docs] def describe(self): x_up, y_up = self.up.describe() x_down, y_down = self.down.describe() return np.append(x_up, x_down), np.append(y_up, y_down)
[docs] def optimal_integration(self, activation: float) -> List[Tuple[float, float, np.ndarray]]: up_opt = self.up.optimal_integration(activation) down_opt = self.down.optimal_integration(activation) return up_opt + down_opt
[docs]class Trapmf(MembershipFunction): """Trapezoidal membership function"""
[docs] def __init__(self, bottom1: float, top1: float, top2: float, bottom2: float) -> None: super().__init__(bottom1, bottom2) assert ( bottom1 < top1 < top2 < bottom2 ), "Cannot draw trapezoid from initialization parameters" self.up = Linear(bottom1, top1) self.cons = Constant(top1, top2) self.down = Linear(bottom2, top2)
def __call__(self, data: np.ndarray, activation=1.0) -> np.ndarray: data = np.asfarray(data) index = np.arange(data.size) indexed_data = np.column_stack((index, data)) mask1 = indexed_data[:, 1] <= self.up.max_v mask2 = np.logical_and( self.up.max_v < indexed_data[:, 1], indexed_data[:, 1] <= self.down.min_v ) mask3 = indexed_data[:, 1] > self.down.min_v dt_up = indexed_data[mask1, :] res_up = np.column_stack((dt_up[:, 0], self.up(dt_up[:, 1], activation))) dt_cons = indexed_data[mask2, :] res_cons = np.column_stack((dt_cons[:, 0], self.cons(dt_cons[:, 1], activation))) dt_down = indexed_data[mask3, :] res_down = np.column_stack((dt_down[:, 0], self.down(dt_down[:, 1], activation))) all_res = np.concatenate((res_up, res_cons, res_down), axis=0) all_res_sorted = all_res[all_res[:, 0].argsort()] return _call_end(all_res_sorted[:, 1])
[docs] def describe(self): x_up, y_up = self.up.describe() x_cons, y_cons = self.cons.describe() x_down, y_down = self.down.describe() xs = np.concatenate((x_up, x_cons, x_down)) ys = np.concatenate((y_up, y_cons, y_down)) return xs, ys
[docs] def optimal_integration(self, activation: float) -> List[Tuple[float, float, np.ndarray]]: up_opt = self.up.optimal_integration(activation) cons_opt = self.cons.optimal_integration(activation) down_opt = self.down.optimal_integration(activation) return up_opt + cons_opt + down_opt
# class Gaussmf(FunctionBase): # """Gaussian membership function""" # def __init__(self, min_v: float, max_v: float) -> None: # raise NotImplementedError # def __call__(self, data: Any, activation: float) -> np.ndarray: # raise NotImplementedError # def _call_end(self, data: np.ndarray) -> np.ndarray: # raise NotImplementedError # def describe(self) -> Tuple[np.ndarray, np.ndarray]: # raise NotImplementedError # def optimal_integration(self, activation: float) -> List[Tuple[float, float, np.ndarray]]: # """Optimal integration strategy. Defined at subclass""" # raise NotImplementedError # class Gauss2mf(FunctionBase): # """Gaussian combination membership function""" # def __init__(self, min_v: float, max_v: float) -> None: # raise NotImplementedError # def __call__(self, data: Any, activation: float) -> np.ndarray: # raise NotImplementedError # def _call_end(self, data: np.ndarray) -> np.ndarray: # raise NotImplementedError # def describe(self) -> Tuple[np.ndarray, np.ndarray]: # raise NotImplementedError # def optimal_integration(self, activation: float) -> List[Tuple[float, float, np.ndarray]]: # """Optimal integration strategy. Defined at subclass""" # raise NotImplementedError # class Gbellmf: # """Generalized bell-shaped membership function""" # def __init__(self, min_v: float, max_v: float) -> None: # raise NotImplementedError # def __call__(self, data: Any, activation: float) -> np.ndarray: # raise NotImplementedError # def _call_end(self, data: np.ndarray) -> np.ndarray: # raise NotImplementedError # def describe(self) -> Tuple[np.ndarray, np.ndarray]: # raise NotImplementedError # def optimal_integration(self, activation: float) -> List[Tuple[float, float, np.ndarray]]: # """Optimal integration strategy. Defined at subclass""" # raise NotImplementedError