Source code for fuzzy_expert.mf

"""
Membership Functions
==============================================================================

Functions in this module returns a standard membership function specificaion as a list of points (x_i, u_i).

"""
from __future__ import annotations
from typing import Tuple, List
import numpy as np


## pag. 27, FuzzyCLIPS


[docs]class MembershipFunction: """Membership function constructor. :param n_points: Number base point for building the approximations. >>> from fuzzy_expert.mf import MembershipFunction >>> mf = MembershipFunction(n_points=3) >>> mf(('gaussmf', 5, 1)) [(2, 0), (3.0, 0.1353352832366127), (3.8, 0.48675225595997157), (4.6, 0.9231163463866356), (5.0, 1.0), (5.4, 0.9231163463866356), (6.2, 0.48675225595997157), (7.0, 0.1353352832366127), (8, 0)] """ def __init__(self, n_points: int = 9): self.n_points: int = n_points def __call__(self, mfspec: tuple): """Generates a list of poinnts representing the membership function. >>> from fuzzy_expert.mf import MembershipFunction >>> mf = MembershipFunction(n_points=3) >>> mf(('gaussmf', 5, 1)) [(2, 0), (3.0, 0.1353352832366127), (3.8, 0.48675225595997157), (4.6, 0.9231163463866356), (5.0, 1.0), (5.4, 0.9231163463866356), (6.2, 0.48675225595997157), (7.0, 0.1353352832366127), (8, 0)] """ fn, *params = mfspec fn = { "gaussmf": self.gaussmf, "gbellmf": self.gbellmf, "pimf": self.pimf, "sigmf": self.sigmf, "smf": self.smf, "trapmf": self.trapmf, "trimf": self.trimf, "zmf": self.zmf, }[fn] return fn(*params)
[docs] def gaussmf(self, center: float, sigma: float) -> List[Tuple[float, float]]: """Gaussian membership function. :param center: Defines the center of the membership function. :param sigma: Defines the width of the membership function, where a larger value creates a wider membership function. >>> from fuzzy_expert.mf import MembershipFunction >>> mf = MembershipFunction(n_points=3) >>> mf.gaussmf(center=5, sigma=1) [(2, 0), (3.0, 0.1353352832366127), (3.8, 0.48675225595997157), (4.6, 0.9231163463866356), (5.0, 1.0), (5.4, 0.9231163463866356), (6.2, 0.48675225595997157), (7.0, 0.1353352832366127), (8, 0)] """ xp: np.ndarray = np.linspace( start=center - 2 * sigma, stop=center + 2 * sigma, num=2 * self.n_points, ) xp = np.append(xp, center) xp = np.unique(xp) xp.sort() fp: np.ndarray = np.exp(-((xp - center) ** 2) / (2 * sigma)) return ( [(center - 3 * sigma, 0)] + [(x, f) for x, f in zip(xp, fp)] + [(center + 3 * sigma, 0)] )
[docs] def gbellmf( self, center: float, width: float, shape: float, ) -> List[Tuple[float, float]]: """Generalized bell-shaped membership function. :param center: Defines the center of the membership function. :param width: Defines the width of the membership function, where a larger value creates a wider membership function. :param shape: Defines the shape of the curve on either side of the central plateau, where a larger value creates a more steep transition. >>> from fuzzy_expert.mf import MembershipFunction >>> mf = MembershipFunction(n_points=3) >>> mf.gbellmf(center=5, width=1, shape=0.5) [(-1, 0), (0.0, 0.16666666666666666), (1.0, 0.2), (2.0, 0.25), (3.0, 0.3333333333333333), (3.8, 0.45454545454545453), (4.0, 0.5), (4.6, 0.7142857142857141), (5.0, 1.0), (5.4, 0.7142857142857141), (6.0, 0.5), (6.2, 0.45454545454545453), (7.0, 0.3333333333333333), (8.0, 0.25), (9.0, 0.2), (10.0, 0.16666666666666666), (11, 0)] """ xp: np.ndarray = np.linspace( start=center - 2 * width, stop=center + 2 * width, num=2 * self.n_points ) delta = center + width * np.linspace(start=-5, stop=5, num=11) xp: np.ndarray = np.append(xp, delta) xp = np.unique(xp) xp.sort() fp: np.ndarray = 1 / (1 + np.abs((xp - center) / width) ** (2 * shape)) return ( [(center - 6 * width, 0)] + [(x, f) for x, f in zip(xp, fp)] + [(center + 6 * width, 0)] )
[docs] def pimf( self, left_feet: float, left_peak: float, right_peak: float, right_feet: float, ) -> List[Tuple[float, float]]: """Pi-shaped membership function. :param left_feet: Defines the left feet of the membership function. :param left_peak: Defines the left peak of the membership function. :param right_peak: Defines the right peak of the membership function. :param right_feet: Defines the right feet of the membership function. >>> from fuzzy_expert.mf import MembershipFunction >>> mf = MembershipFunction(n_points=4) >>> mf.pimf(left_feet=1, left_peak=2, right_peak=3, right_feet=4) [(1.0, 0.0), (1.3333333333333333, 0.22222222222222213), (1.6666666666666665, 0.7777777777777776), (2.0, 1.0), (3.0, 1.0), (3.3333333333333335, 0.7777777777777776), (3.6666666666666665, 0.22222222222222243), (4.0, 0.0)] """ return self.smf(foot=left_feet, shoulder=left_peak) + self.zmf( shoulder=right_peak, feet=right_feet )
[docs] def sigmf( self, center: float, width: float, ) -> List[Tuple[float, float]]: """Sigmoidal membership function. :param center: Defines the center of the membership function. :param width: Defines the width of the membership function. >>> from fuzzy_expert.mf import MembershipFunction >>> mf = MembershipFunction(n_points=3) >>> mf.sigmf(center=5, width=1) [(-1, 0), (0.0, 0.0066928509242848554), (2.0, 0.04742587317756678), (4.0, 0.2689414213699951), (5.0, 0.5), (6.0, 0.7310585786300049), (8.0, 0.9525741268224334), (10.0, 0.9933071490757153), (11, 1)] """ xp: np.ndarray = np.linspace( start=center - 5 * width, stop=center + 5 * width, num=2 * self.n_points ) xp: np.ndarray = np.append(xp, center) xp = np.unique(xp) xp.sort() fp: np.ndarray = 1 / (1 + np.exp(-np.abs(width) * (xp - center))) return ( [(center - 6 * width, 0)] + [(x, f) for x, f in zip(xp, fp)] + [(center + 6 * width, 1)] )
[docs] def smf( self, foot: float, shoulder: float, ) -> List[Tuple[float, float]]: """S-shaped membership function. :param foot: Defines the foot of the membership function. :param shoulder: Defines the shoulder of the membership function. >>> from fuzzy_expert.mf import MembershipFunction >>> mf = MembershipFunction(n_points=4) >>> mf.smf(foot=1, shoulder=2) [(1.0, 0.0), (1.3333333333333333, 0.22222222222222213), (1.6666666666666665, 0.7777777777777776), (2.0, 1.0)] """ xp: np.ndarray = np.linspace(start=foot, stop=shoulder, num=self.n_points) fp: np.ndarray = np.where( xp <= foot, 0, np.where( xp <= (foot + shoulder) / 2, 2 * ((xp - foot) / (shoulder - foot)) ** 2, np.where( xp <= shoulder, 1 - 2 * ((xp - shoulder) / (shoulder - foot)) ** 2, 1, ), ), ) return [(x, f) for x, f in zip(xp, fp)]
[docs] def trapmf( self, left_feet: float, left_peak: float, right_peak: float, right_feet: float, ) -> List[Tuple[float, float]]: """Trapezoidal membership function. :param left_feet: Defines the left feet of the membership function. :param left_peak: Defines the left peak of the membership function. :param right_peak: Defines the right peak of the membership function. :param right_feet: Defines the right feet of the membership function. >>> from fuzzy_expert.mf import MembershipFunction >>> mf = MembershipFunction(n_points=4) >>> mf.trapmf(left_feet=1, left_peak=2, right_peak=3, right_feet=4) [(1.0, 0.0), (2.0, 1.0), (3.0, 1.0), (4.0, 0.0)] """ left_feet: np.ndarray = np.where( left_feet == left_peak, left_feet - 1e-4, left_feet ) right_feet: np.ndarray = np.where( right_feet == right_peak, right_feet + 1e-4, right_feet ) xp: np.ndarray = np.array([left_feet, left_peak, right_peak, right_feet]) fp: np.ndarray = np.where( xp <= left_feet, 0, np.where( xp <= left_peak, (xp - left_feet) / (left_peak - left_feet), np.where( xp <= right_peak, 1, np.where( xp <= right_feet, (right_feet - xp) / (right_feet - right_peak), 0, ), ), ), ) return [(x, f) for x, f in zip(xp, fp)]
[docs] def trimf( self, left_feet: float, peak: float, right_feet: float, ) -> List[Tuple[float, float]]: """Triangular membership function. :param left_feet: Defines the left feet of the membership function. :param peak: Defines the peak of the membership function. :param right_feet: Defines the right feet of the membership function. >>> from fuzzy_expert.mf import MembershipFunction >>> mf = MembershipFunction(n_points=4) >>> mf.trimf(left_feet=1, peak=2, right_feet=4) [(1.0, 0.0), (2.0, 1.0), (4.0, 0.0)] """ left_feet: np.ndarray = np.where(left_feet == peak, left_feet - 1e-4, left_feet) right_feet: np.ndarray = np.where( peak == right_feet, right_feet + 1e-4, right_feet ) xp: np.ndarray = np.array([left_feet, peak, right_feet]) fp: np.ndarray = np.where( xp <= left_feet, 0, np.where( xp <= peak, (xp - left_feet) / (peak - left_feet), np.where(xp <= right_feet, (right_feet - xp) / (right_feet - peak), 0), ), ) return [(x, f) for x, f in zip(xp, fp)]
[docs] def zmf( self, shoulder: float, feet: float, ) -> List[Tuple[float, float]]: """Z-shaped membership function. :param shoulder: Defines the shoulder of the membership function. :param feet: Defines the feet of the membership function. >>> from fuzzy_expert.mf import MembershipFunction >>> mf = MembershipFunction(n_points=4) >>> mf.zmf(shoulder=1, feet=2) [(1.0, 1.0), (1.3333333333333333, 0.7777777777777779), (1.6666666666666665, 0.22222222222222243), (2.0, 0.0)] """ xp: np.ndarray = np.linspace(start=shoulder, stop=feet, num=self.n_points) fp: np.ndarray = np.where( xp <= shoulder, 1, np.where( xp <= (shoulder + feet) / 2, 1 - 2 * ((xp - shoulder) / (feet - shoulder)) ** 2, np.where(xp <= feet, 2 * ((xp - feet) / (feet - shoulder)) ** 2, 0), ), ) return [(x, f) for x, f in zip(xp, fp)]