Source code for skfuzzy.fuzzymath.fuzzy_ops

"""
fuzzy_ops.py : Package of general operations on fuzzy sets, fuzzy membership
               functions, and their associated universe variables.

"""
from __future__ import division, print_function
import numpy as np


[docs]def cartadd(x, y): """ Cartesian addition of fuzzy membership vectors using the algebraic method. Parameters ---------- x : 1D array or iterable First fuzzy membership vector, of length M. y : 1D array or iterable Second fuzzy membership vector, of length N. Returns ------- z : 2D array Cartesian addition of ``x`` and ``y``, of shape (M, N). """ # Ensure rank-1 input x, y = np.asarray(x).ravel(), np.asarray(y).ravel() m, n = len(x), len(y) a = np.dot(np.atleast_2d(x).T, np.ones((1, n))) b = np.dot(np.ones((m, 1)), np.atleast_2d(y)) return a + b
[docs]def cartprod(x, y): """ Cartesian product of two fuzzy membership vectors. Uses ``min()``. Parameters ---------- x : 1D array or iterable First fuzzy membership vector, of length M. y : 1D array or iterable Second fuzzy membership vector, of length N. Returns ------- z : 2D array Cartesian product of ``x`` and ``y``, of shape (M, N). """ # Ensure rank-1 input x, y = np.asarray(x).ravel(), np.asarray(y).ravel() m, n = len(x), len(y) a = np.dot(np.atleast_2d(x).T, np.ones((1, n))) b = np.dot(np.ones((m, 1)), np.atleast_2d(y)) return np.fmin(a, b)
[docs]def classic_relation(a, b): """ Determine the classic relation matrix, ``R``, between two fuzzy sets. Parameters ---------- a : 1D array or iterable First fuzzy membership vector, of length M. b : 1D array or iterable Second fuzzy membership vector, of length N. Returns ------- R : 2D array Classic relation matrix between ``a`` and ``b``, shape (M, N) Notes ----- The classic relation is defined as:: r = [a x b] U [(1 - a) x ones(1, N)], where ``x`` represents a cartesian product and ``N`` is len(``b``). """ a = np.asarray(a) return np.fmax(cartprod(a, b), cartprod(1 - a, np.ones(len(b))))
[docs]def contrast(arr, amount=0.2, split=0.5, normalize=True): """ General contrast booster or diffuser of normalized array-like data. Parameters ---------- arr : ndarray Input array (of floats on range [0, 1] if ``normalize=False``). If values exist outside this range, with ``normalize=True`` the image will be normalized for calculation. amount : float or length-2 iterable of floats Controls the exponential contrast mechanism for values above and below ``split`` in ``I``. If positive, the curve provides added contrast; if negative, the curve provides reduced contrast. If provided as a lenth-2 iterable of floats, they control the regions (below, above) ``split`` separately. split : float Positive scalar, on range [0, 1], determining the midpoint of the exponential contrast. Default of 0.5 is reasonable for well-exposed images. normalize : bool, default True Controls normalization to the range [0, 1]. Returns ------- focused : ndarray Contrast adjusted, normalized, floating-point image on range [0, 1]. Notes ----- The result of this algorithm is like applying a Curves adjustment in the GIMP or Photoshop. Algorithm for curves adjustment at a given pixel, x, is given by:: | split * (x/split)^below, 0 <= x <= split y(x) = | | 1 - (1-split) * ((1-x) / (1-split))^above, split < x <= 1.0 See Also -------- skfuzzy.fuzzymath.sigmoid """ # Ensure scalars are floats, to avoid truncating division in Python 2.x split = float(split) im = arr.astype(float) amount_ = np.asarray(amount, dtype=np.float64).ravel() if len(amount_) == 1: # One argument -> Equal amount applied on either side of `split` above = below = amount_[0] else: # Two arguments -> Control contrast separately in light/dark regions below = amount_[0] above = amount_[1] # Normalize if required if im.max() > 1. and normalize is True: ma = float(im.max()) im /= float(im.max()) else: ma = 1. focused = np.zeros_like(im, dtype=np.float64) # Simplified array-wise algorithm using fancy indexing rather than looping focused[im <= split] = split * (im[im <= split] / split) ** below focused[im > split] = (1 - (1. - split) * ((1 - im[im > split]) / (1. - split)) ** above) # Reapply multiplicative factor return focused * ma
[docs]def fuzzy_add(x, a, y, b): """ Add fuzzy set ``a`` to fuzzy set ``b``. Parameters ---------- x : 1d array, length N Universe variable for fuzzy set ``a``. a : 1d array, length N Fuzzy set for universe ``x``. y : 1d array, length M Universe variable for fuzzy set ``b``. b : 1d array, length M Fuzzy set for universe ``y``. Returns ------- z : 1d array Output variable. mfz : 1d array Fuzzy membership set for variable ``z``. Notes ----- Uses Zadeh's Extension Principle as described in Ross, Fuzzy Logic with Engineering Applications (2010), pp. 414, Eq. 12.17. If these results are unexpected and your membership functions are convex, consider trying the ``skfuzzy.dsw_*`` functions for fuzzy mathematics using interval arithmetic via the restricted Dong, Shah, and Wong method. """ # a and x, and b and y, are formed into (MxN) matrices. The former has # identical rows; the latter identical identical columns. n = len(b) aa = np.dot(np.atleast_2d(a).T, np.ones((1, n))) xx = np.dot(np.atleast_2d(x).T, np.ones((1, n))) m = len(a) bb = np.dot(np.ones((m, 1)), np.atleast_2d(b)) yy = np.dot(np.ones((m, 1)), np.atleast_2d(y)) # Do the addition zz = (xx + yy).ravel() zz_index = np.argsort(zz) zz = np.sort(zz) # Array min() operation c = np.fmin(aa, bb).ravel() c = c[zz_index] # Initialize loop z, mfz = np.zeros(0), np.zeros(0) idx = 0 for i in range(len(c)): index = np.nonzero(zz == zz[idx])[0] z = np.hstack((z, zz[idx])) mfz = np.hstack((mfz, c[index].max())) if zz[idx] == zz.max(): break idx = index.max() + 1 return z, mfz
[docs]def fuzzy_compare(q): """ Determine the comparison matrix, ``c``, based on the fuzzy pairwise comparison matrix, ``q``, using Shimura's special relativity formula. Parameters ---------- q : 2d array, (N, N) Fuzzy pairwise comparison matrix. Returns ------- c : 2d array, (N, N) Comparison matrix. """ return q.T / np.fmax(q, q.T).astype(np.float)
[docs]def fuzzy_div(x, a, y, b): """ Divide fuzzy set ``b`` into fuzzy set ``a``. Parameters ---------- x : 1d array, length N Universe variable for fuzzy set ``a``. a : 1d array, length N Fuzzy set for universe ``x``. y : 1d array, length M Universe variable for fuzzy set ``b``. b : 1d array, length M Fuzzy set for universe ``y``. Returns ------- z : 1d array Output variable. mfz : 1d array Fuzzy membership set for variable z. Notes ----- Uses Zadeh's Extension Principle from Ross, Fuzzy Logic w/Engineering Applications, (2010), pp.414, Eq. 12.17. If these results are unexpected and your membership functions are convex, consider trying the ``skfuzzy.dsw_*`` functions for fuzzy mathematics using interval arithmetic via the restricted Dong, Shah, and Wong method. """ # a and x, and b and y, are formed into (MxN) matrices. The former has # identical rows; the latter identical identical columns. n = len(b) aa = np.dot(np.atleast_2d(a).T, np.ones((1, n))) x = np.dot(np.atleast_2d(x).T, np.ones((1, n))) m = len(a) bb = np.dot(np.ones((m, 1)), np.atleast_2d(b)) y = np.dot(np.ones((m, 1)), np.atleast_2d(y)) # Divide, adding eps to avoid potential div0 zz = (x / (y + np.finfo(float).eps)).ravel() zz_index = np.argsort(zz) zz = np.sort(zz) # Array min() operation c = np.fmin(aa, bb).ravel() c = c[zz_index] # Initialize loop z, mfz = np.zeros(0), np.zeros(0) idx = 0 for i in range(len(c)): index = np.nonzero(zz == zz[idx])[0] z = np.hstack((z, zz[idx])) mfz = np.hstack((mfz, c[index].max())) if zz[idx] == zz.max(): break idx = index.max() + 1 return z, mfz
[docs]def fuzzy_min(x, a, y, b): """ Find minimum between fuzzy set ``a`` fuzzy set ``b``. Parameters ---------- x : 1d array, length N Universe variable for fuzzy set ``a``. a : 1d array, length N Fuzzy set for universe ``x``. y : 1d array, length M Universe variable for fuzzy set ``b``. b : 1d array, length M Fuzzy set for universe ``y``. Returns ------- z : 1d array Output variable. mfz : 1d array Fuzzy membership set for variable z. Notes ----- Uses Zadeh's Extension Principle from Ross, Fuzzy Logic w/Engineering Applications, (2010), pp.414, Eq. 12.17. If these results are unexpected and your membership functions are convex, consider trying the ``skfuzzy.dsw_*`` functions for fuzzy mathematics using interval arithmetic via the restricted Dong, Shah, and Wong method. """ # a and x, and b and y, are formed into (MxN) matrices. The former has # identical rows; the latter identical identical columns. n = len(b) aa = np.dot(np.atleast_2d(a).T, np.ones((1, n))) x = np.dot(np.atleast_2d(x).T, np.ones((1, n))) m = len(a) bb = np.dot(np.ones((m, 1)), np.atleast_2d(b)) y = np.dot(np.ones((m, 1)), np.atleast_2d(y)) # Take the element-wise minimum zz = np.fmin(x, y).ravel() zz_index = np.argsort(zz) zz = np.sort(zz) # Array min() operation c = np.fmin(aa, bb).ravel() c = c[zz_index] # Initialize loop z, mfz = np.zeros(0), np.zeros(0) idx = 0 for i in range(len(c)): index = np.nonzero(zz == zz[idx])[0] z = np.hstack((z, zz[idx])) mfz = np.hstack((mfz, c[index].max())) if zz[idx] == zz.max(): break idx = index.max() + 1 return z, mfz
[docs]def fuzzy_mult(x, a, y, b): """ Multiplies fuzzy set ``a`` and fuzzy set ``b``. Parameters ---------- x : 1d array, length N Universe variable for fuzzy set ``a``. A : 1d array, length N Fuzzy set for universe ``x``. y : 1d array, length M Universe variable for fuzzy set ``b``. b : 1d array, length M Fuzzy set for universe ``y``. Returns ------- z : 1d array Output variable. mfz : 1d array Fuzzy membership set for variable z. Notes ----- Uses Zadeh's Extension Principle from Ross, Fuzzy Logic w/Engineering Applications, (2010), pp.414, Eq. 12.17. If these results are unexpected and your membership functions are convex, consider trying the ``skfuzzy.dsw_*`` functions for fuzzy mathematics using interval arithmetic via the restricted Dong, Shah, and Wong method. """ # a and x, and b and y, are formed into (MxN) matrices. The former has # identical rows; the latter identical identical columns. n = len(b) aa = np.dot(np.atleast_2d(a).T, np.ones((1, n))) x = np.dot(np.atleast_2d(x).T, np.ones((1, n))) m = len(a) bb = np.dot(np.ones((m, 1)), np.atleast_2d(b)) y = np.dot(np.ones((m, 1)), np.atleast_2d(y)) # Multiply universes zz = (x * y).ravel() zz_index = np.argsort(zz) zz = np.sort(zz) # Array min() operation c = np.fmin(aa, bb).ravel() c = c[zz_index] # Initialize loop z, mfz = np.zeros(0), np.zeros(0) idx = 0 for i in range(len(c)): index = np.nonzero(zz == zz[idx])[0] z = np.hstack((z, zz[idx])) mfz = np.hstack((mfz, c[index].max())) if zz[idx] == zz.max(): break idx = index.max() + 1 return z, mfz
[docs]def fuzzy_sub(x, a, y, b): """ Subtract fuzzy set ``b`` from fuzzy set ``a``. Parameters ---------- x : 1d array, length N Universe variable for fuzzy set ``a``. A : 1d array, length N Fuzzy set for universe ``x``. y : 1d array, length M Universe variable for fuzzy set ``b``. b : 1d array, length M Fuzzy set for universe ``y``. Returns ------- z : 1d array Output variable. mfz : 1d array Fuzzy membership set for variable z. Notes ----- Uses Zadeh's Extension Principle from Ross, Fuzzy Logic w/Engineering Applications, (2010), pp.414, Eq. 12.17. If these results are unexpected and your membership functions are convex, consider trying the ``skfuzzy.dsw_*`` functions for fuzzy mathematics using interval arithmetic via the restricted Dong, Shah, and Wong method. """ # a and x, and b and y, are formed into (MxN) matrices. The former has # identical rows; the latter identical identical columns. n = len(b) aa = np.dot(np.atleast_2d(a).T, np.ones((1, n))) x = np.dot(np.atleast_2d(x).T, np.ones((1, n))) m = len(a) bb = np.dot(np.ones((m, 1)), np.atleast_2d(b)) y = np.dot(np.ones((m, 1)), np.atleast_2d(y)) # Subtract universes zz = (x - y).ravel() zz_index = np.argsort(zz) zz = np.sort(zz) # Array min() operation c = np.fmin(aa, bb).ravel() c = c[zz_index] # Initialize loop z, mfz = np.zeros(0), np.zeros(0) idx = 0 for i in range(len(c)): index = np.nonzero(zz == zz[idx])[0] z = np.hstack((z, zz[idx])) mfz = np.hstack((mfz, c[index].max())) if zz[idx] == zz.max(): break idx = index.max() + 1 return z, mfz
[docs]def inner_product(a, b): """ Inner product (dot product) of two fuzzy sets. Parameters ---------- a : 1d array or iterable Fuzzy membership function. b : 1d array or iterable Fuzzy membership function. Returns ------- y : float Fuzzy inner product value, on range [0, 1] """ return np.max(np.fmin(np.r_[a], np.r_[b]))
[docs]def interp10(x): """ Utility function which conducts linear interpolation of any rank-1 array. Result will have 10x resolution. Parameters ---------- x : 1d array, length N Input array to be interpolated. Returns ------- y : 1d array, length 10 * N + 1 Linearly interpolated output. """ return np.interp(np.r_[0:len(x) - 0.9:0.1], range(len(x)), x)
[docs]def maxmin_composition(s, r): """ The max-min composition ``t`` of two fuzzy relation matrices. Parameters ---------- s : 2d array, (M, N) Fuzzy relation matrix #1. r : 2d array, (N, P) Fuzzy relation matrix #2. Returns ------- T ; 2d array, (M, P) Max-min composition, defined by ``T = s o r``. """ if s.ndim < 2: s = np.atleast_2d(s) if r.ndim < 2: r = np.atleast_2d(r).T m = s.shape[0] p = r.shape[1] t = np.zeros((m, p)) for pp in range(p): for mm in range(m): t[mm, pp] = (np.fmin(s[mm, :], r[:, pp].T)).max() return t
[docs]def maxprod_composition(s, r): """ The max-product composition ``t`` of two fuzzy relation matrices. Parameters ---------- s : 2d array, (M, N) Fuzzy relation matrix #1. r : 2d array, (N, P) Fuzzy relation matrix #2. Returns ------- t : 2d array, (M, P) Max-product composition matrix. """ if s.ndim < 2: s = np.atleast_2d(s) if r.ndim < 2: r = np.atleast_2d(r).T m = s.shape[0] p = r.shape[1] t = np.zeros((m, p)) for mm in range(m): for pp in range(p): t[mm, pp] = (s[mm, :] * r[:, pp].T).max() return t
[docs]def interp_membership(x, xmf, xx): """ Find the degree of membership ``u(xx)`` for a given value of ``x = xx``. Parameters ---------- x : 1d array Independent discrete variable vector. xmf : 1d array Fuzzy membership function for ``x``. Same length as ``x``. xx : float Discrete singleton value on universe ``x``. Returns ------- xxmf : float Membership function value at ``xx``, ``u(xx)``. Notes ----- For use in Fuzzy Logic, where an interpolated discrete membership function u(x) for discrete values of x on the universe of ``x`` is given. Then, consider a new value x = xx, which does not correspond to any discrete values of ``x``. This function computes the membership value ``u(xx)`` corresponding to the value ``xx`` using linear interpolation. """ # Nearest discrete x-values x1 = x[x <= xx][-1] x2 = x[x >= xx][0] idx1 = np.nonzero(x == x1)[0][0] idx2 = np.nonzero(x == x2)[0][0] xmf1 = xmf[idx1] xmf2 = xmf[idx2] if x1 == x2: xxmf = xmf[idx1] else: slope = (xmf2 - xmf1) / float(x2 - x1) xxmf = slope * (xx - x1) + xmf1 return xxmf
[docs]def interp_universe(x, xmf, y): """ Find interpolated universe value(s) for a given fuzzy membership value. Parameters ---------- x : 1d array Independent discrete variable vector. xmf : 1d array Fuzzy membership function for ``x``. Same length as ``x``. y : float Specific fuzzy membership value. Returns ------- xx : list List of discrete singleton values on universe ``x`` whose membership function value is y, ``u(xx[i])==y``. If there are not points xx[i] such that ``u(xx[i])==y`` it returns an empty list. Notes ----- For use in Fuzzy Logic, where a membership function level ``y`` is given. Consider there is some value (or set of values) ``xx`` for which ``u(xx) == y`` is true, though ``xx`` may not correspond to any discrete values on ``x``. This function computes the value (or values) of ``xx`` such that ``u(xx) == y`` using linear interpolation. """ # If y is between xmf[i] and xmf[i+1] there is a cut point. # Moreover, if y==xmf[i+1] we will interpret it as a cut point. # However, in the next iteration, we interpret it also as a cut point! # That is the reason for the `and` part. indices = np.nonzero( [True if (xmf[i]<=y<=xmf[i+1] or xmf[i]>=y>=xmf[i+1]) and (i==0 or xmf[i]!=y) else False for i in range(len(x)-1)])[0] # We have len(indices) values in ``x`` xx = [0.0] * len(indices) for i in range(len(indices)): index = indices[i] x1 = x[index] x2 = x[index + 1] xmf1 = xmf[index] xmf2 = xmf[index + 1] if x1 == x2: xx[i] = x1 elif xmf1 == xmf2: # In this case xx[i] can be any point in the range [x1,x2]. We return the first one. xx[i] = x1 else: slope = (xmf2 - xmf1) / float(x2 - x1) xx[i] = (y - xmf1)/slope + x1 return xx
[docs]def modus_ponens(a, b, ap, c=None): """ Generalized *modus ponens* deduction to make approximate reasoning in a rules-base system. Parameters ---------- a : 1d array Fuzzy set ``a`` on universe ``x`` b : 1d array Fuzzy set ``b`` on universe ``y`` ap : 1d array New fuzzy fact a' (a prime, not transpose) c : 1d array, OPTIONAL Keyword argument representing fuzzy set ``c`` on universe ``y``. Default = None, which will use ``np.ones()`` instead. Returns ------- R : 2d array Full fuzzy relation. bp : 1d array Fuzzy conclusion b' (b prime) """ if c is None: c = np.ones(len(b)) r = np.fmax(cartprod(a, b), cartprod(1 - a, c)) bp = maxmin_composition(ap, r) return r, bp.squeeze()
[docs]def outer_product(a, b): """ Outer product of two fuzzy sets. Parameters ---------- a : 1d array or iterable Fuzzy membership function. b : 1d array or iterable Fuzzy membership function. Returns ------- y : float Fuzzy outer product value, on range [0, 1] """ return np.min(np.fmax(np.r_[a], np.r_[b]))
[docs]def relation_min(a, b): """ Determine fuzzy relation matrix ``R`` using Mamdani implication for the fuzzy antecedent ``a`` and consequent ``b`` inputs. Parameters ---------- a : 1d array Fuzzy antecedent variable of length M. b : 1d array Fuzzy consequent variable of length N. Returns ------- R : 2d array Fuzzy relation between ``a`` and ``b``, of shape (M, N). """ m = len(a) n = len(b) a = np.atleast_2d(a) b = np.atleast_2d(b) return np.fmin(np.dot(a.T, np.ones((1, m))), np.dot(np.ones((n, 1)), b))
[docs]def relation_product(a, b): """ Determine the fuzzy relation matrix, ``R``, using product implication for the fuzzy antecedent ``a`` and the fuzzy consequent ``b``. Parameters ---------- a : 1d array Fuzzy antecedent variable of length M. b : 1d array Fuzzy consequent variable of length N. Returns ------- R : 2d array Fuzzy relation between ``a`` and ``b``, of shape (M, N). """ m = len(a) n = len(b) a = np.atleast_2d(a) b = np.atleast_2d(b) return np.dot(a.T, np.ones((1, n))) * np.dot(np.ones((m, 1)), b)
[docs]def fuzzy_similarity(ai, b, mode='min'): """ The fuzzy similarity between set ``ai`` and observation set ``b``. Parameters ---------- ai : 1d array Fuzzy membership function of set ``ai``. b : 1d array Fuzzy membership function of set ``b``. mode : string Controls the method of similarity calculation. * ``'min'`` : Computed by array minimum operation. * ``'avg'`` : Computed by taking the array average. Returns ------- s : float Fuzzy similarity. """ if 'min' in mode.lower(): return min(inner_product(ai, b), 1 - outer_product(ai, b)) else: return (inner_product(ai, b) + (1 - outer_product(ai, b))) / 2.
[docs]def partial_dmf(x, mf_name, mf_parameter_dict, partial_parameter): """ Calculate the *partial derivative* of a specified membership function. Parameters ---------- x : float input variable. mf_name : string Membership function name as a string. The following are supported: * ``'gaussmf'`` : parameters ``'sigma'`` or ``'mean'`` * ``'gbellmf'`` : parameters ``'a'``, ``'b'``, or ``'c'`` * ``'sigmf'`` : parameters ``'b'`` or ``'c'`` mf_parameter_dict : dict A dictionary of ``{param : key-value, ...}`` pairs for a particular membership function as defined above. partial_parameter : string Name of the parameter against which we take the partial derivative. Returns ------- d : float Partial derivative of the membership function with respect to the chosen parameter, at input point ``x``. Notes ----- Partial derivatives of fuzzy membership functions are only meaningful for continuous functions. Triangular, trapezoidal designs have no partial derivatives to calculate. The following """ if mf_name == 'gaussmf': sigma = mf_parameter_dict['sigma'] mean = mf_parameter_dict['mean'] if partial_parameter == 'sigma': result = ((2. / sigma**3) * np.exp(-(((x - mean)**2) / (sigma)**2)) * (x - mean)**2) elif partial_parameter == 'mean': result = ((2. / sigma**2) * np.exp(-(((x - mean)**2) / (sigma)**2)) * (x - mean)) elif mf_name == 'gbellmf': a = mf_parameter_dict['a'] b = mf_parameter_dict['b'] c = mf_parameter_dict['c'] # Partial result for speed and conciseness in derived eqs below d = np.abs((c - x) / a) if partial_parameter == 'a': result = ((2. * b * (c - x)**2.) * d**((2 * b) - 2) / (a**3. * (d**(2. * b) + 1)**2.)) elif partial_parameter == 'b': result = (-1 * (2 * d**(2. * b) * np.log(d)) / ((d**(2. * b) + 1)**2.)) elif partial_parameter == 'c': result = ((2. * b * (x - c) * d**((2. * b) - 2)) / (a**2. * (d**(2. * b) + 1)**2.)) elif mf_name == 'sigmf': b = mf_parameter_dict['b'] c = mf_parameter_dict['c'] if partial_parameter == 'b': # Partial result for speed and conciseness d = np.exp(c * (b + x)) result = -1 * (c * d) / (np.exp(b * c) + np.exp(c * x))**2. elif partial_parameter == 'c': # Partial result for speed and conciseness d = np.exp(c * (x - b)) result = ((x - b) * d) / (d + 1)**2. return result
[docs]def sigmoid(x, power, split=0.5): """ Intensify grayscale values in an array using a sigmoid function. Parameters ---------- x : ndarray Input vector or image array. Should be pre-normalized to range [0, 1] p : float Power of the intensification (p > 0). Experiment with small, decimal values and increase as necessary. split : float Threshold for intensification. Values above ``split`` will be intensified, while values below `split` will be deintensified. Note range for ``split`` is (0, 1). Default of 0.5 is reasonable for many well-exposed images. Returns ------- y : ndarray, same size as x Output vector or image with contrast adjusted. Notes ----- The sigmoid used herein is defined as:: y = 1 / (1 + exp(- exp(- power * (x-split)))) See Also -------- skfuzzy.fuzzymath.contrast """ return 1. / (1. + np.exp(- power * (x - split)))