Source code for fuzzy_expert.operators

"""
Modifiers and operators
===============================================================================

"""
from __future__ import annotations

import numpy as np
import numpy.typing as npt
from typing import List

# #############################################################################
#
#
# Unary operators
#
#
# #############################################################################


[docs]def apply_modifiers(membership: npt.ArrayLike, modifiers: List[str]) -> npt.ArrayLike: """ Apply a list of modifiers or hedges to a numpy array. :param membership: Membership values to be modified. :param modifiers: List of modifiers or hedges. >>> from fuzzy_expert.operators import apply_modifiers >>> x = [0.0, 0.25, 0.5, 0.75, 1] >>> apply_modifiers(x, ('not', 'very')) array([1. , 0.9375, 0.75 , 0.4375, 0. ]) """ if modifiers is None: return membership fn = { "EXTREMELY": extremely, "INTENSIFY": intensify, "MORE_OR_LESS": more_or_less, "NORM": norm, "NOT": not_, "PLUS": plus, "SLIGHTLY": slightly, "SOMEWHAT": somewhat, "VERY": very, } membership = membership.copy() modifiers = list(modifiers) modifiers.reverse() for modifier in modifiers: membership = fn[modifier.upper()](membership) return membership
[docs]def extremely(membership: npt.ArrayLike) -> npt.ArrayLike: """ Applies the element-wise function fn(u) = u^3. :param membership: Membership function to be modified. >>> from fuzzy_expert.operators import extremely >>> extremely([0, 0.25, 0.5, 0.75, 1]) array([0. , 0.015625, 0.125 , 0.421875, 1. ]) """ return np.power(membership, 3)
[docs]def intensify(membership: npt.ArrayLike) -> npt.ArrayLike: """ Applies the element-wise function fn(u) = u^2 if u <= 0.5 else 1 - 2 * (1 - u)^2. :param membership: Membership function to be modified. >>> from fuzzy_expert.operators import intensify >>> intensify([0, 0.25, 0.5, 0.75, 1]) array([0. , 0.0625, 0.25 , 0.875 , 1. ]) """ membership = np.array(membership) return np.where( membership <= 0.5, np.power(membership, 2), 1 - 2 * np.power(1 - membership, 2), )
[docs]def more_or_less(membership: npt.ArrayLike) -> npt.ArrayLike: """ Applies the element-wise function fn(u) = u^(1/2). :param membership: Membership function to be modified. >>> from fuzzy_expert.operators import more_or_less >>> more_or_less([0, 0.25, 0.5, 0.75, 1]) array([0. , 0.5 , 0.70710678, 0.8660254 , 1. ]) """ return np.power(membership, 0.5)
[docs]def norm(membership: npt.ArrayLike) -> npt.ArrayLike: """ Applies the element-wise function fn(u) = u / max(u). :param membership: Membership function to be modified. >>> from fuzzy_expert.operators import norm >>> norm([0, 0.25, 0.5]) array([0. , 0.5, 1. ]) """ return membership / np.max(membership)
[docs]def not_(membership: npt.ArrayLike) -> npt.ArrayLike: """ Applies the element-wise function fn(u) = 1 - u. :param membership: Membership function to be modified. >>> from fuzzy_expert.operators import not_ >>> not_([0, 0.25, 0.5, 0.75, 1]) array([1. , 0.75, 0.5 , 0.25, 0. ]) """ return 1 - np.array(membership)
[docs]def plus(membership: npt.ArrayLike) -> npt.ArrayLike: """ Applies the element-wise function fn(u) = u^1.25. :param membership: Membership function to be modified. >>> from fuzzy_expert.operators import plus >>> plus([0, 0.25, 0.5, 0.75, 1]) array([0. , 0.1767767 , 0.42044821, 0.69795364, 1. ]) """ return np.power(membership, 1.25)
[docs]def somewhat(membership: npt.ArrayLike) -> npt.ArrayLike: """ Applies the element-wise function fn(u) = u^(1/3). :param membership: Membership function to be modified. >>> from fuzzy_expert.operators import somewhat >>> somewhat([0, 0.25, 0.5, 0.75, 1]) array([0. , 0.62996052, 0.79370053, 0.9085603 , 1. ]) """ return np.power(membership, 1.0 / 3.0)
[docs]def very(membership: npt.ArrayLike) -> npt.ArrayLike: """ Applies the element-wise function fn(u) = u^2. :param membership: Membership function to be modified. >>> from fuzzy_expert.operators import very >>> very([0, 0.25, 0.5, 0.75, 1]) array([0. , 0.0625, 0.25 , 0.5625, 1. ]) """ return np.power(membership, 2)
[docs]def slightly(membership: npt.ArrayLike) -> npt.ArrayLike: """ Applies the element-wise function fn(u) = u^(1/2). :param membership: Membership function to be modified. >>> from fuzzy_expert.operators import slightly >>> slightly([0, 0.25, 0.5, 0.75, 1]) array([0. , 0.16326531, 0.99696182, 1. , 0. ]) """ plus_membership: npt.ArrayLike = np.power(membership, 1.25) not_very_membership: npt.ArrayLike = 1 - np.power(membership, 2) membership: npt.ArrayLike = np.where( membership < not_very_membership, plus_membership, not_very_membership ) membership: npt.ArrayLike = membership / np.max(membership) return np.where(membership <= 0.5, membership ** 2, 1 - 2 * (1 - membership) ** 2)
# ############################################################################# # # # Advanced operators # # # #############################################################################
[docs]def prob_or(memberships: List[npt.ArrayLike]) -> npt.ArrayLike: """ Applies the element-wise function fn(u, v) = u + v - u * v. Also known as algebraic-sum. :param memberships: List of arrays of membership values. >>> from fuzzy_expert.operators import prob_or >>> x = [0.1, 0.25, 0.5, 0.75, 0.3] >>> y = [0, 0.75, 0.5, 0.25, 0] >>> prob_or([x, y]) array([0.1 , 0.8125, 0.75 , 0.8125, 0.3 ]) """ result: npt.ArrayLike = np.array(memberships[0]) for membership in memberships[1:]: membership = np.array(membership) result: npt.ArrayLike = result + membership - result * membership return np.maximum(0, np.minimum(1, result))
[docs]def bounded_prod(memberships: List[npt.ArrayLike]) -> npt.ArrayLike: """ Applies the element-wise function max(0, u + v - 1). :param memberships: List of arrays of membership values. >>> from fuzzy_expert.operators import bounded_prod >>> x = [0.1, 0.25, 0.5, 0.75, 1] >>> bounded_prod([x, x]) array([0. , 0. , 0. , 0.5, 1. ]) """ result: npt.ArrayLike = np.array(memberships[0]) for membership in memberships[1:]: membership: npt.ArrayLike = np.array(membership) result: npt.ArrayLike = np.maximum(0, result + membership - 1) return result
[docs]def bounded_sum(memberships: List[npt.ArrayLike]) -> npt.ArrayLike: """ Applies the element-wise function min(1, u + v). :param memberships: List of arrays of membership values. >>> from fuzzy_expert.operators import bounded_sum >>> x = [0, 0.25, 0.5, 0.75, 1] >>> bounded_sum([x, x, x]) array([0. , 0.75, 1. , 1. , 1. ]) """ result: npt.ArrayLike = np.array(memberships[0]) for membership in memberships[1:]: membership: npt.ArrayLike = np.array(membership) result: npt.ArrayLike = np.minimum(1, result + membership) return result
[docs]def bounded_diff(memberships: List[npt.ArrayLike]) -> npt.ArrayLike: """ Applies the element-wise function max(0, u - v). :param memberships: List of arrays of membership values. >>> from fuzzy_expert.operators import bounded_diff >>> x = [0, 0.25, 0.5, 0.75, 1] >>> y = [0, 0.25, 0.5, 0.6, 0.7] >>> bounded_diff([x, y]) array([0. , 0. , 0. , 0.15, 0.3 ]) """ result: npt.ArrayLike = np.array(memberships[0]) for membership in memberships[1:]: membership = np.array(membership) result: npt.ArrayLike = np.maximum(0, result - membership) return result
[docs]def drastic_prod(memberships: List[npt.ArrayLike]) -> npt.ArrayLike: """ Applies the element-wise function f(u, v) = u if v == 0 else v if u == 1 else 0. :param memberships: List of arrays of membership values. >>> from fuzzy_expert.operators import drastic_prod >>> x = [0, 0.25, 0.5, 0.75, 1] >>> y = [1, 0.75, 0.5, 0.25, 0] >>> drastic_prod([x, y]) array([0., 0., 0., 0., 1.]) """ result: npt.ArrayLike = np.array(memberships[0]) for membership in memberships[1:]: membership = np.array(membership) result: npt.ArrayLike = np.where( membership == np.float64(0), result, np.where(result == np.float64(1), membership, 0), ) return result
[docs]def drastic_sum(memberships: List[npt.ArrayLike]) -> npt.ArrayLike: """ Applies the element-wise function f(u, v) = u if v == 0 else v if u == 0 else 1. :param memberships: List of arrays of membership values. >>> from fuzzy_expert.operators import drastic_sum >>> x = [0.1, 0.25, 0.5, 0.75, 0.3] >>> y = [0, 0.75, 0.5, 0.25, 0] >>> drastic_sum([x, y]) array([0.1, 1. , 1. , 1. , 0.3]) """ result: npt.ArrayLike = memberships[0] for membership in memberships[1:]: result: npt.ArrayLike = np.where( membership == np.float64(0), result, np.where(result == np.float64(0), membership, 1), ) return result
[docs]def product(memberships: List[npt.ArrayLike]) -> npt.ArrayLike: """ Applies the element-wise function f(u, v) = u * v. :param memberships: List of arrays of membership values. >>> from fuzzy_expert.operators import product >>> x = [0, 0.25, 0.5, 0.75, 1] >>> product([x, x, x]) array([0. , 0.015625, 0.125 , 0.421875, 1. ]) """ result: npt.ArrayLike = np.array(memberships[0]) for membership in memberships[1:]: membership = np.array(membership) result: npt.ArrayLike = result * membership return result
[docs]def maximum(memberships: List[npt.ArrayLike]) -> npt.ArrayLike: """ Applies the element-wise function f(u, v) = max(u, v). :param memberships: List of arrays of membership values. >>> from fuzzy_expert.operators import maximum >>> x = [0.1, 0.25, 0.5, 0.75, 0.3] >>> y = [0, 0.75, 0.5, 0.25, 0] >>> maximum([x, y]) array([0.1 , 0.75, 0.5 , 0.75, 0.3 ]) """ result: npt.ArrayLike = np.array(memberships[0]) for membership in memberships[1:]: membership = np.array(membership) result: npt.ArrayLike = np.maximum(result, membership) return result
[docs]def minimum(memberships: List[npt.ArrayLike]) -> npt.ArrayLike: """ Applies the element-wise function f(u, v) = min(u, v). :param memberships: List of arrays of membership values. >>> from fuzzy_expert.operators import minimum >>> x = [0.1, 0.25, 0.5, 0.75, 0.3] >>> y = [0, 0.75, 0.5, 0.25, 0] >>> minimum([x, y]) array([0. , 0.25, 0.5 , 0.25, 0. ]) """ result: npt.ArrayLike = np.array(memberships[0]) for membership in memberships[1:]: membership = np.array(membership) result: npt.ArrayLike = np.minimum(result, membership) return result
[docs]def defuzzificate(universe, membership, operator="cog") -> dict: """Computes a representative crisp value for the fuzzy set. :param universe: Array of values representing the universe of discourse. :param membership: Array of values representing the membership function. :param operator: Method used for computing the crisp representative value of the fuzzy set. * `"cog"`: Center of gravity. * `"boa"`: Bisector of area. * `"mom"`: Mean of the values for which the membership function is maximum. * `"lom"`: Largest value for which the membership function is maximum. * `"som"`: Smallest value for which the membership function is minimum. >>> from fuzzy_expert.operators import defuzzificate >>> u = [0, 1, 2, 3, 4] >>> m = [0, 0, 0.5, 1, 1] >>> defuzzificate(u, m, "cog") 2.9166666666666665 >>> defuzzificate(u, m, "boa") 3.0 >>> defuzzificate(u, m, "mom") 3.5 >>> defuzzificate(u, m, "lom") 4 >>> defuzzificate(u, m, "som") 3 """ def cog(): n_areas = len(universe) - 1 areas = [] centroids = [] for i in range(n_areas): base = universe[i + 1] - universe[i] area_rect = np.minimum(membership[i], membership[i + 1]) * base centr_rect = universe[i] + base / 2.0 if membership[i + 1] == membership[i]: area_tria = 0 centr_tri = 0 elif membership[i + 1] > membership[i]: area_tria = base * np.abs(membership[i + 1] - membership[i]) / 2.0 centr_tri = universe[i] + 2.0 / 3.0 * base else: area_tria = base * np.abs(membership[i + 1] - membership[i]) / 2.0 centr_tri = universe[i] + 1.0 / 3.0 * base area = area_rect + area_tria if area == np.float64(0): centr = np.float64(0) else: centr = (area_rect * centr_rect + area_tria * centr_tri) / ( area_rect + area_tria ) areas.append(area) centroids.append(centr) num = sum([area * centroid for area, centroid in zip(areas, centroids)]) den = sum(areas) return num / den def boa(): n_areas = len(universe) - 1 areas = [] for i_area in range(n_areas): base = universe[i_area + 1] - universe[i_area] area = (membership[i_area] + membership[i_area + 1]) * base / 2.0 areas.append(area) total_area = sum(areas) target = total_area / 2.0 cum_area = 0 for i_area in range(n_areas): cum_area += areas[i_area] if cum_area >= target: break xp = [universe[i_area], universe[i_area + 1]] fp = [cum_area - areas[i_area], cum_area] return np.interp(target, xp=fp, fp=xp) def mom(): maximum = np.max(membership) maximum = np.array([u for u, m in zip(universe, membership) if m == maximum]) return np.mean(maximum) def lom(): maximum = np.max(membership) maximum = np.array([u for u, m in zip(universe, membership) if m == maximum]) return np.max(maximum) def som(): maximum = np.max(membership) maximum = np.array([u for u, m in zip(universe, membership) if m == maximum]) return np.min(maximum) universe = np.array(universe) membership = np.array(membership) if np.sum(membership) == 0.0: return np.mean(universe) return { "cog": cog, "boa": boa, "mom": mom, "lom": lom, "som": som, }[operator]()