Source code for skfuzzy.defuzzify.defuzz

"""
defuzz.py : Various methods for defuzzification and lambda-cuts, to convert
            'fuzzy' systems back into 'crisp' values for decisions.
"""
import numpy as np

from .exceptions import EmptyMembershipError, InconsistentMFDataError
from ..image.arraypad import pad


[docs]def arglcut(ms, lambdacut): """ Determines the subset of indices `mi` of the elements in an N-point resultant fuzzy membership sequence `ms` that have a grade of membership >= lambdacut. Parameters ---------- ms : 1d array Fuzzy membership sequence. lambdacut : float Value used for lambda cutting. Returns ------- lidx : 1d array Indices corresponding to the lambda-cut subset of `ms`. Notes ----- This is a convenience function for `np.nonzero(lambdacut <= ms)` and only half of the indexing operation that can be more concisely accomplished via:: ms[lambdacut <= ms] """ return np.nonzero(lambdacut <= ms)
[docs]def centroid(x, mfx): """ Defuzzification using centroid (`center of gravity`) method. Parameters ---------- x : 1d array, length M Independent variable mfx : 1d array, length M Fuzzy membership function Returns ------- u : 1d array, length M Defuzzified result See also -------- skfuzzy.defuzzify.defuzz, skfuzzy.defuzzify.dcentroid """ ''' As we suppose linearity between each pair of points of x, we can calculate the exact area of the figure (a triangle or a rectangle). ''' sum_moment_area = 0.0 sum_area = 0.0 # If the membership function is a singleton fuzzy set: if len(x) == 1: return (x[0] * mfx[0] / np.fmax(mfx[0], np.finfo(float).eps).astype(float)) # else return the sum of moment*area/sum of area for i in range(1, len(x)): x1 = x[i - 1] x2 = x[i] y1 = mfx[i - 1] y2 = mfx[i] # if y1 == y2 == 0.0 or x1==x2: --> rectangle of zero height or width if not (y1 == y2 == 0.0 or x1 == x2): if y1 == y2: # rectangle moment = 0.5 * (x1 + x2) area = (x2 - x1) * y1 elif y1 == 0.0 and y2 != 0.0: # triangle, height y2 moment = 2.0 / 3.0 * (x2 - x1) + x1 area = 0.5 * (x2 - x1) * y2 elif y2 == 0.0 and y1 != 0.0: # triangle, height y1 moment = 1.0 / 3.0 * (x2 - x1) + x1 area = 0.5 * (x2 - x1) * y1 else: moment = ((2.0 / 3.0 * (x2 - x1) * (y2 + 0.5 * y1)) / (y1 + y2) + x1) area = 0.5 * (x2 - x1) * (y1 + y2) sum_moment_area += moment * area sum_area += area return (sum_moment_area / np.fmax(sum_area, np.finfo(float).eps).astype(float))
[docs]def dcentroid(x, mfx, x0): """ Defuzzification using a differential centroidal method about `x0`. Parameters ---------- x : 1d array or iterable Independent variable. mfx : 1d array or iterable Fuzzy membership function. x0 : float Central value to calculate differential centroid about. Returns ------- u : 1d array Defuzzified result. See also -------- skfuzzy.defuzzify.defuzz, skfuzzy.defuzzify.centroid """ x = x - x0 return x0 + centroid(x, mfx)
def bisector(x, mfx): """ Defuzzification using bisector, or division of the area in two equal parts. Parameters ---------- x : 1d array, length M Independent variable mfx : 1d array, length M Fuzzy membership function Returns ------- u : 1d array, length M Defuzzified result See also -------- skfuzzy.defuzzify.defuzz """ ''' As we suppose linearity between each pair of points of x, we can calculate the exact area of the figure (a triangle or a rectangle). ''' sum_area = 0.0 accum_area = [0.0] * (len(x) - 1) # If the membership function is a singleton fuzzy set: if len(x) == 1: return x[0] # else return the sum of moment*area/sum of area for i in range(1, len(x)): x1 = x[i - 1] x2 = x[i] y1 = mfx[i - 1] y2 = mfx[i] # if y1 == y2 == 0.0 or x1==x2: --> rectangle of zero height or width if not (y1 == y2 == 0. or x1 == x2): if y1 == y2: # rectangle area = (x2 - x1) * y1 elif y1 == 0. and y2 != 0.: # triangle, height y2 area = 0.5 * (x2 - x1) * y2 elif y2 == 0. and y1 != 0.: # triangle, height y1 area = 0.5 * (x2 - x1) * y1 else: area = 0.5 * (x2 - x1) * (y1 + y2) sum_area += area accum_area[i - 1] = sum_area # index to the figure which cointains the x point that divide the area of # the whole fuzzy set in two index = np.nonzero(np.array(accum_area) >= sum_area / 2.)[0][0] # subarea will be the area in the left part of the bisection for this set if index == 0: subarea = 0 else: subarea = accum_area[index - 1] x1 = x[index] x2 = x[index + 1] y1 = mfx[index] y2 = mfx[index + 1] # We are interested only in the subarea inside the figure in which the # bisection is present. subarea = sum_area / 2. - subarea x2minusx1 = x2 - x1 if y1 == y2: # rectangle u = subarea / y1 + x1 elif y1 == 0.0 and y2 != 0.0: # triangle, height y2 root = np.sqrt(2. * subarea * x2minusx1 / y2) u = (x1 + root) elif y2 == 0.0 and y1 != 0.0: # triangle, height y1 root = np.sqrt(x2minusx1 * x2minusx1 - (2. * subarea * x2minusx1 / y1)) u = (x2 - root) else: m = (y2 - y1) / x2minusx1 root = np.sqrt(y1 * y1 + 2.0 * m * subarea) u = (x1 - (y1 - root) / m) return u
[docs]def defuzz(x, mfx, mode): """ Defuzzification of a membership function, returning a defuzzified value of the function at x, using various defuzzification methods. Parameters ---------- x : 1d array or iterable, length N Independent variable. mfx : 1d array of iterable, length N Fuzzy membership function. mode : string Controls which defuzzification method will be used. * 'centroid': Centroid of area * 'bisector': bisector of area * 'mom' : mean of maximum * 'som' : min of maximum * 'lom' : max of maximum Returns ------- u : float or int Defuzzified result. Raises ------ - EmptyMembershipError : When the membership function area is empty. - InconsistentMFDataError : When the length of the 'x' and the fuzzy membership function arrays are not equal. See Also -------- skfuzzy.defuzzify.centroid, skfuzzy.defuzzify.dcentroid """ mode = mode.lower() x = x.ravel() mfx = mfx.ravel() n = len(x) if n != len(mfx): raise InconsistentMFDataError() if 'centroid' in mode or 'bisector' in mode: if mfx.sum() == 0: # Approximation of total area raise EmptyMembershipError() if 'centroid' in mode: return centroid(x, mfx) elif 'bisector' in mode: return bisector(x, mfx) elif 'mom' in mode: return np.mean(x[mfx == mfx.max()]) elif 'som' in mode: return np.min(x[mfx == mfx.max()]) elif 'lom' in mode: return np.max(x[mfx == mfx.max()]) else: raise ValueError("The input for `mode`, {}, was incorrect." .format(mode))
def _interp_universe(x, xmf, mf_val): """ Find the universe variable corresponding to membership `mf_val`. Parameters ---------- x : 1d array Independent discrete variable vector. xmf : 1d array Fuzzy membership function for x. Same length as x. mf_val : float Discrete singleton value on membership function mfx. Returns ------- x_interp : float Universe variable value corresponding to `mf_val`. """ slope = (xmf[1] - xmf[0]) / float(x[1] - x[0]) x_interp = (mf_val - xmf[0]) / slope return x_interp
[docs]def lambda_cut_series(x, mfx, n): """ Determine a series of lambda-cuts in a sweep from 0+ to 1.0 in n steps. Parameters ---------- x : 1d array Universe function for fuzzy membership function mfx. mfx : 1d array Fuzzy membership function for x. n : int Number of steps. Returns ------- z : 2d array, (n, 3) Lambda cut intevals. """ x = np.asarray(x) mfx = np.asarray(mfx) step = (mfx.max() - mfx.min()) / float(n - 1) lambda_cuts = np.arange(mfx.min(), mfx.max() + np.finfo(float).eps, step) z = np.zeros((n, 3)) z[:, 0] = lambda_cuts.T z[0, [1, 2]] = _support(x, mfx) for ii in range(1, n): xx = _lcutinterval(x, mfx, lambda_cuts[ii]) z[ii, [1, 2]] = xx return z
def _lcutinterval(x, mfx, lambdacut): """ Determine upper & lower interval limits of the lambda-cut for membership function u(x) [here mfx]. Parameters ---------- x : 1d array Independent variable. mfx : 1d array Fuzzy membership function for x. lambdacut : float Value used for lambda-cut. Returns ------- z : 1d array Lambda-cut output. Notes ----- Membership function mfx must be convex and monotonic in rise or fall. """ z = x[lambdacut - 1e-6 <= mfx] return np.hstack((z.min(), z.max()))
[docs]def lambda_cut(ms, lcut): """ The crisp (binary) lambda-cut set of the membership sequence `ms` with membership >= `lcut`. Parameters ---------- ms : 1d array Fuzzy membership set. lcut : float Value used for lambda-cut, on range [0, 1.0]. Returns ------- mlambda : 1d array Lambda-cut set of `ms`: ones if ms[i] >= lcut, zeros otherwise. """ if lcut == 1: return (ms >= lcut) * 1 else: return (ms > lcut) * 1
[docs]def lambda_cut_boundaries(x, mfx, lambdacut): """ Find exact boundaries where `mfx` crosses `lambdacut` using interpolation. Parameters ---------- x : 1d array, length N Universe variable mfx : 1d array, length N Fuzzy membership function lambdacut : float Floating point value on range [0, 1]. Returns ------- boundaries : 1d array Floating point values of `x` where `mfx` crosses `lambdacut`. Calculated using linear interpolation. Notes ----- The values returned by this function can be thought of as intersections between a hypothetical horizontal line at ``lambdacut`` and the membership function ``mfx``. This function assumes the end values of ``mfx`` continue on forever in positive and negative directions. This means there will NOT be crossings found exactly at the bounds of ``x`` unless the value of ``mfx`` at the boundary is exactly ``lambdacut``. """ # Pad binary set two values by extension mfxx = pad(mfx, [2, 2], 'edge') # Find binary lambda cut set lcutset = lambda_cut(mfxx, lambdacut) # Detect crossings with convolution, cutting off one padded value crossings = np.convolve(lcutset, [1, -1])[1:-1] argcrossings = np.where(np.abs(crossings) > 0)[0] - 1 # Calculate exact crossing points, removing the last padded value boundaries = [] for cross in argcrossings: idx = slice(cross - 1, cross + 1) boundaries.append( x[cross - 1] + _interp_universe(x[idx], mfx[idx], lambdacut)) # Eliminate degenerate points at peaks with np.unique return np.unique(np.r_[boundaries])
def _support(x, mfx): """ Determine lower & upper limits of the support interval. Parameters ---------- x : 1d array Independent variable. mfx : 1d array Fuzzy membership function for x; must be convex, continuous, and monotonic (rise XOR fall). Returns ------- z : 1d array, length 2 Interval representing lower & upper limits of the support interval. """ apex = mfx.max() m = np.nonzero(mfx == apex)[0][0] n = len(x) xx = x[0:m + 1] mfxx = mfx[0:m + 1] z = xx[mfxx == mfxx.min()].max() xx = x[m:n] mfxx = mfx[m:n] return np.r_[z, xx[mfxx == mfxx.min()].min()]