PKH:fuzzle/plot_utils.pyimport matplotlib.pyplot as plt from fuzzle.utils import frange def plot_lvar(lvars, resolution=10): fig = plt.figure() ax = fig.add_subplot(111) colors = 'b', 'g', 'r', 'c', 'm', 'y', 'k', if not isinstance(lvars, list): lvars = [lvars] for i, lvar in enumerate(lvars, 1): start, end = lvar.domain domain = [i for i in frange(end, start, step=1. / resolution)] mfs = dict() for mf in lvar: mfs[mf] = [lvar[mf](x) for x in domain] plt.subplot(1, len(lvars), i) for j, (mf, values) in enumerate(mfs.items()): ax.plot(domain, values, colors[(j - 1) % len(colors)]) ax.grid(True) def plot_mf(mf, domain, resolution=10, color='r'): start, end = domain points = [(i, mf(i)) for i in frange(end, start, step=1. / resolution)] plt.fill( [i[0] for i in points], [i[1] for i in points], color, ) plt.show()PKH?j++fuzzle/operators.pyimport math import abc from fuzzle.exceptions import OutOfIntervalError # TODO Better comments # TODO More operators. See https://en.wikipedia.org/wiki/Construction_of_t-norms class Operator(metaclass=abc.ABCMeta): """ Represents all possible fuzzy operators. This operators will work in the [0, 1] ⊆ ℝ domain. They include both unary (complements) and binary (t-norms and s-norms). """ class Complement(Operator, metaclass=abc.ABCMeta): """ Interface for all complement operators. """ def __call__(self, x): """ Performs the operation. :param x: A value that should belong to the real interval [0, 1]. :return: The complement of the value according to the chosen complement implementation. """ if not 0. <= x <= 1.: raise OutOfIntervalError('x', 0, 1, x) f_x = self.f(float(x)) if not 0. <= f_x <= 1.: raise OutOfIntervalError('f(x)', 0, 1, f_x) return float(f_x) @abc.abstractmethod def f(self, x): """ The concrete implementation of the complement method. :param x: The value with which operate. :return: The computed complement. """ class Zadeh(Complement): """ Zadeh's complement, defined as f(x) = 1 - x. """ def f(self, x): return 1 - x class Sugeno(Complement): """ Sugeno's complement for a scalar "a". If a = 0 then Sugeno's complement is equivalent to Zadeh's complement. """ def __init__(self, a): if not 0 <= a <= 1: raise OutOfIntervalError('a', 0, 1, a) self.a = float(a) def f(self, x): return (1 - x) / (1 + x * self.a) class Yager(Complement): """ Yager's complement for a scalar "a". If a = 1 then Yager's complement is equivalent to Zadeh's complement. """ def __init__(self, a): if not 0 <= a <= 1: raise OutOfIntervalError('a', 0, 1, a) self.a = float(a) self.__inv_a = 1. / self.a def f(self, x): return pow(1. - pow(x, self.a), self.__inv_a) class Norm(Operator, metaclass=abc.ABCMeta): """ Interface for all norms (t-norm and t-conorm) operations. """ def __call__(self, x, y): """ Performs the operation. :param x: A value that should belong to the real interval [0, 1]. :return: The complement of the value according to the chosen complement implementation. """ if not 0. <= x <= 1.: raise OutOfIntervalError('x', 0, 1, x) if not 0. <= y <= 1.: raise OutOfIntervalError('y', 0, 1, y) f_x_y = self.f(float(x), float(y)) if not 0. <= f_x_y <= 1.: raise OutOfIntervalError('f(x, y)', 0, 1, f_x_y) return float(f_x_y) @abc.abstractmethod def f(self, x, y): """ The concrete implementation of a Norm. :param x: One of the values with which operate. :param y: One of the values with which operate. :return: The computed norm. """ class TNorm(Norm, metaclass=abc.ABCMeta): """Interface for all T-Norm operators.""" pass class SNorm(Norm, metaclass=abc.ABCMeta): """Interface for all S-Norm operators.""" pass class ArithmeticMean(TNorm): """ The midpoint of two given values. """ def f(self, x, y): return (x + y) / 2. class GeometricMean(TNorm): """ The squared of the product of two given values. """ def f(self, x, y): return math.sqrt(x * y) class Minimum(TNorm): """ Minimum of two given values. """ def f(self, x, y): return min(x, y) class AlgebraicProduct(TNorm): """ Algebraic product. The common product of two values. """ def f(self, x, y): return x * y class DrasticProduct(TNorm): """ Drastic product. """ def f(self, x, y): if x == 1: return y elif y == 1: return x else: return 0 class BoundedDifference(TNorm): """ The bounded difference returns x - y if x > y or 0 otherwise. """ def f(self, x, y): return max(0., x - y) class DombiTNorm(TNorm): """ Dombi T-Norm. """ def __init__(self, p): if p < 0: raise OutOfIntervalError('p', 0, '∞', p, inc_upper=False) self.p = float(p) self.__inv_p = 1 / self.p def f(self, x, y): if x == 0 or y == 0: return 0. else: def λ(z): return pow((1 - z) / z, self.p) return 1 / (1 + pow(λ(x) + λ(y), self.__inv_p)) class EinsteinTNorm(TNorm): """ Einstein T-Norm. """ def f(self, x, y): return (x * y) / (1 + (1 - x) * (1 - y)) class HamacherTNorm(TNorm): """Hamacher T-Norm. """ def f(self, x, y): if not x or not y: return 0. else: return (x * y) / (x + y - x * y) class YagerTNorm(TNorm): """Yager T-Norm. """ def __init__(self, p): if p < 0: raise OutOfIntervalError('p', 0, '∞', p, inc_upper=False) self.p = float(p) self.__inv_p = 1 / self.p def f(self, x, y): if x == 0. or y == 0.: return 0. else: def λ(z): return pow(1 - z, self.p) return 1 - min(1, pow(λ(x) + λ(y), self.__inv_p)) class Maximum(SNorm): """ Maximum of two given values. """ def f(self, x, y): return max(x, y) class AlgebraicSum(SNorm): """ The sum of two given values minus their product. """ def f(self, x, y): return x + y - x * y class BoundedSum(SNorm): """ The sum of two given values bounded above by 1. """ def f(self, x, y): return min(1, x + y) class DrasticSum(SNorm): """ Drastic sum. """ def f(self, x, y): if not x: return y elif not y: return x else: return 1 class DombiSNorm(SNorm): """Dombi's t-conorm. """ def __init__(self, p): if p < 0: raise OutOfIntervalError('p', 0, '∞', p, inc_upper=False) self.p = float(p) self.__inv_p = 1 / self.p def f(self, x, y): if x == 1 or y == 1: return 1 else: def λ(z): return pow(x / (1. - z), self.p) return 1 - 1 / (1 + pow(λ(x) + λ(y), self.__inv_p)) class EinsteinSNorm(SNorm): """ Einstein's t-conorm. """ def f(self, x, y): return (x + y) / (1 + x * y) class HamacherSNorm(SNorm): """ Hamacher's t-conorm. """ def f(self, x, y): if x == y == 1.: return 1 else: return (x + y - 2 * x * y) / (1 - x * y) class YagerSNorm(SNorm): """Yager's t-conorm. """ def __init__(self, p): if p < 0: raise OutOfIntervalError('p', 0, '∞', p, inc_upper=False) self.p = float(p) self.__inv_p = 1 / self.p def f(self, x, y): if x == y == 1: return 1 else: def λ(z): return pow(z, self.p) return min(1, pow(λ(x) + λ(y), self.__inv_p)) PKH{]//fuzzle/rules.pyimport collections import re from fuzzle import mfs from fuzzle import operators from fuzzle.exceptions import FuzzleError, ParseError class RuleBlock(dict): """ Contains all the rules of the inference engine. """ def __init__(self, and_op=operators.Minimum(), or_op=operators.Maximum(), not_op=operators.Zadeh(), agg_op=operators.AlgebraicProduct(), acc_op=operators.AlgebraicSum()): """ Initializes this rule block. :param and_op: the AND operator. Defaults to the minimum t-norm. :param or_op: the OR operator. Defaults to the maximum s-norm :param not_op: the NOT operator. Defaults to Zadeh's complement. :param agg_op: the aggregator operator (related with THEN operator). Defaults to algebraic product t-norm. :param acc_op: the accumulation operator. Defaults to algebraic sum s-norm. """ super().__init__() self.and_op = and_op self.or_op = or_op self.not_op = not_op self.agg_op = agg_op self.acc_op = acc_op self.__rule_parser = RuleParser(and_op, or_op, not_op) def eval(self, finput, ovars): """ Evaluates a fuzzy input. Evaluates one by one the rules contained in this rules block against the fuzzy inputs and the definition of the output linguistic variables. :param finput: the fuzzy input. :param ovars: the fuzzy output. :returns: the fuzzy output, i.e. the membership function for all the output variables which appears in the rules after evaluating them. """ # Evaluation rules_output = [rule.eval(finput) for rule in self.values()] # Aggregation (only those output vars whose value is greater than 0) agg_mf = collections.defaultdict(lambda: []) for output in rules_output: for o_var, f_set, val in output: if val: agg_mf[o_var].append( mfs.BinOpMF( self.agg_op, mfs.ConstantMF(val), ovars[o_var][f_set] ) ) # Accumulation foutput = {} for o_var in agg_mf: acc_mf = None for mf in agg_mf[o_var]: if acc_mf is None: acc_mf = mf else: acc_mf = mfs.BinOpMF(self.acc_op, acc_mf, mf) foutput[o_var] = acc_mf return foutput def as_dict(self): return { 'and': str(self.and_op), 'or': str(self.or_op), 'not': str(self.not_op), 'agg': str(self.agg_op), 'acc': str(self.acc_op), 'rules': {k: str(v) for (k, v) in self.rules.items()} } def __setitem__(self, name, rule): if name in self: raise FuzzleError('Rule {} exists in ruleblock'.format(name)) super().__setitem__(name, self.__rule_parser.parse(rule)) class RuleParser: """ A parser to process fuzzy rules. """ PATTERNS = ''' (?Pif|IF) |(?Pthen|THEN) |(?Pwith|WITH) |(?P[(]) |(?P[)]) |(?P[,]) |(?P[\s|\n]+) |(?Pis|IS) |(?Pnot|NOT) |(?Pand|or|AND|OR) |(?Pvery|fairly|VERY|FAIRLY) |(?P[a-zA-Z_][a-zA-Z0-9_]*) |(?P[-+]?(\d*)\.?(\d+)) ''' def __init__(self, and_op, or_op, complement_op): self.and_op = and_op self.or_op = or_op self.complement_op = complement_op self.tokens = [] def tokenize(self, value): compiler = re.compile(self.PATTERNS, re.VERBOSE) tokens = [] pos = 0 while True: m = compiler.match(value, pos) if m: pos = m.end() token_name = m.lastgroup token_value = m.group(token_name) tokens.append((token_name, token_value)) else: break if pos != len(value): raise SyntaxError('Syntax error at position {0}'.format(pos)) else: tokens = list(filter(lambda t: t[0] != 'blank', tokens)) tokens.reverse() return tokens def head(self, x): """ Checks if the next token of tokens is of a specified class. Given a class, the method will return whether or not the next element of the tokens stack is of the specified class. If there are no elements, then the method will return false. When head is called, the stack will not be modified. :param x: the class to check. :type x: str :returns: If the next item is or not of the specified class. :rtype: bool """ if self.tokens: token_type, token_value = self.tokens[len(self.tokens) - 1] return token_type == x else: return False def pop(self, x): """ Extracts the next element from the stack iff belongs to a specified class. Given a class, the method will return the next element of the stack iff that element belongs to the specified class. In that's not the case, the method will fail and an error will be raised. When pop is called, the stack will be modified, removing the top element of the stack. :param x: the class to check. :type x: str :returns: the element on the top of the stack. :rtype: str :raises: SyntaxError """ if len(self.tokens) > 0: token_type, token_value = self.tokens.pop() if token_type != x: raise ParseError(x, token_type, token_value) else: return token_value else: raise SyntaxError('Illegal end of rule') def parse(self, rule): """ Parses a rule in string form. Translates string rule (e.g "IF Speed IS High THEN Brake IS On") to a object Rule. :param rule: a rule in string format :type rule: str :returns: the rule as an instance of Rule. :rtype: Rule """ self.tokens = self.tokenize(rule) return self.__parse_rule(rule) def __parse_rule(self, rule): self.pop('if') antecedent = self.__parse_antecedent() self.pop('then') consequent = self.__parse_consequent() if self.head('with'): self.pop('with') weight = self.__parse_weight() else: weight = 1.0 return Rule(rule, antecedent, consequent, weight) def __parse_antecedent(self): if self.head('open_parenthesis'): self.pop('open_parenthesis') antecedent = self.__parse_antecedent() self.pop('close_parenthesis') elif self.head('not'): self.pop('not') antecedent = UnaryOpAntecedent(self.complement_op, self.__parse_antecedent()) else: antecedent = self.__parse_antecedent_statement() if self.head('logical_op'): op_str = self.pop('logical_op').lower() if op_str == 'and': op = self.and_op elif op_str == 'or': op = self.or_op else: raise ParseError('logical_op', op_str) return BinaryOpAntecedent(op, antecedent, self.__parse_antecedent()) else: return antecedent def __parse_antecedent_statement(self): l_var, negat, hedge, f_set = self.__parse_statement() statement = StatementAntecedent(l_var, f_set) return statement if not negat else UnaryOpAntecedent( self.complement_op, statement) def __parse_consequent(self): consequent = [] while True: consequent.append(self.__parse_consequent_statement()) if self.head('comma'): self.pop('comma') else: break return consequent def __parse_consequent_statement(self): l_var, negat, hedge, f_set = self.__parse_statement() if not negat: return StatementConsequent(l_var, f_set) else: return UnaryOpConsequent(self.complement_op, l_var, f_set) def __parse_statement(self): """Parses a statement no the form: - 'a is b', - 'a is not b' - 'a is very b' - 'a is not very b'""" negat = False hedge = False l_var = self.pop('literal') self.pop('is') if self.head('not'): self.pop('not') negat = True if self.head('hedge'): hedge = self.pop('hedge') if self.head('literal'): f_set = self.pop('literal') elif self.head('decimal'): f_set = self.pop('decimal') else: raise SyntaxError('Expected logical operator') return l_var, negat, hedge, f_set def __parse_weight(self): return float(self.pop('decimal')) class Rule: """ Represents a fuzzy rule. """ def __init__(self, rule, antecedent, consequent, weight): """ Initializes this rule. :param rule: the rule in string format. :param antecedent: The antecedent of the rule (the "IF" part) :param consequent: The consequent of the rule (the "THEN" part) :param weight: The importance of the rule (1 is normal importance) """ self.__rule = rule self.__antecedent = antecedent self.__consequent = consequent self.__weight = weight def eval(self, x): """ Evaluates the fuzzy input against this rule. :param x: the fuzzy values for the fuzzy sets to eval. :returns: the fuzzy values evaluated against the rule for all the fuzzy sets included in the consequent. """ result = self.__antecedent(x) return [c(result) for c in self.__consequent] def __repr__(self): return self.__rule class Antecedent: """ Antecedent in a 'IF antecedent THEN consequent WITH weight' rule.""" def __call__(self, x): raise NotImplementedError('Abstract class') class StatementAntecedent(Antecedent): """Represents an 'A IS B' part of a rule.""" def __init__(self, l_var, f_set): self.__l_var = l_var self.__f_set = f_set def __call__(self, x): return x[self.__l_var][self.__f_set] class BinaryOpAntecedent(Antecedent): """ Represents an antecedent in the form 'antecedent1 BOp antecedent2'. """ def __init__(self, binary_op, antecedent1, antecedent2): self.__a1, self.__op, self.__a2 = antecedent1, binary_op, antecedent2 def __call__(self, x): return self.__op(self.__a1(x), self.__a2(x)) class UnaryOpAntecedent(Antecedent): """ Represents an antecedent in the form 'NOT antecedent' """ def __init__(self, unary_op, antecedent): self.__op, self.__a = unary_op, antecedent def __call__(self, x): return self.__op(self.__a(x)) class Consequent: """ Consequent in an 'IF antecedent THEN consequent WITH weight' rule.""" def __call__(self, x): raise NotImplementedError('Abstract class') class StatementConsequent(Consequent): """Represents an 'A IS B' on a consequent.""" def __init__(self, l_var, f_set): self.__l_var = l_var self.__f_set = f_set def __call__(self, value): return self.__l_var, self.__f_set, value class UnaryOpConsequent(Consequent): """ Representa un antecedente de una regla borrosa para una sentencia del tipo 'NOT Consequente'. """ def __init__(self, unary_op, l_var, f_set): self.unary_op = unary_op self.l_var = l_var self.fuzzy_set = f_set def __call__(self, value): return self.l_var, self.fuzzy_set, self.unary_op(value) PKH fuzzle/controller.pyimport json from fuzzle.exceptions import UnexpectedTypeError, FuzzleError from fuzzle.lvars import InputLVar, OutputLVar from fuzzle.rules import RuleBlock class FuzzyController: """Fuzzy controller.""" def __init__(self, input_vars, output_vars, rule_block): """ Initializes this fuzzy controller. :param input_vars: the set of input variables available for this controller. :param output_vars: the set of output variables available for this controller. :param rule_block: the rule block with all the rules. """ if not all(isinstance(lvar, InputLVar) for lvar in input_vars): msg = 'Input vars must be {} instances'.format(InputLVar.__name__) raise FuzzleError(msg) if not all(isinstance(lvar, OutputLVar) for lvar in output_vars): msg = 'Output vars must be {} instances'.format(OutputLVar.__name__) raise FuzzleError(msg) if not isinstance(rule_block, RuleBlock): raise UnexpectedTypeError('rule_block', RuleBlock) self.__rule_block = rule_block self.__i_vars = dict([(var.name, var) for var in input_vars]) self.__o_vars = dict([(var.name, var) for var in output_vars]) self.__output = dict([(var.name, 0.) for var in output_vars]) def eval(self, c_input): """ Evaluates an input returning the output according the controller. Given a crisp input in the form: { 'input_lvar_1': val_1, ..., 'input_lvar_n': val_n } the method will return a crisp output in the form { 'output_lvar_1': val1, ..., 'output_lvar_n':val_n } after being processed by the controller. :param c_input: a crisp input. """ return self.__defuzzify(self.__infer(self.__fuzzify(c_input))) def __fuzzify(self, c_input): fuzzy_input = {} for var in self.__i_vars: fuzzy_input[var] = self.__i_vars[var].fuzzify(c_input[var]) return fuzzy_input def __infer(self, f_input): return self.__rule_block.eval(f_input, self.__o_vars) def __defuzzify(self, f_output): c_output = {} for var in self.__o_vars: if var in f_output: c_value = self.__o_vars[var].defuzzify(f_output[var]) c_output[var] = self.__output[ var] if c_value is None else c_value else: c_output[var] = self.__output[var] self.__output = c_output return c_output def as_json(self): return json.dumps(self.as_dict()) def as_dict(self): return { 'in': {k: v.as_dict() for k, v in self.__i_vars.items()}, 'out': {k: v.as_dict() for k, v in self.__o_vars.items()}, 'rb': self.__rule_block.as_dict(), } def __str__(self): return '{0}\n{1}'.format(self.__i_vars.values(), self.__o_vars.values()) PKHkT T fuzzle/defuzz.pyimport numbers import abc from fuzzle.exceptions import UnexpectedTypeError, FuzzleError from fuzzle.lvars import LinguisticVariable from fuzzle.mfs import MembershipFunction from fuzzle.utils import frange, feq class DefuzzificationMethod(metaclass=abc.ABCMeta): """ Represents a defuzzification operator. """ def __init__(self, resolution=None): """ Initializes the instance. :param resolution: The number of point the domain should be partitioned in order to get the result. """ self.resolution = float(resolution or 0) def __call__(self, mf, lvar): """ Performs the defuzzification operation. :param mf: the membership function to defuzzify. :param lvar: the linguistic variable under the fuzzy set defined for the membership function. :return: A float value with the result of the defuzzifying the function. :raises UnexpectedTypeError: If mf is not a MembershipFunction instance or if lvar is not a LinguisticVariable Instance. """ if not isinstance(mf, MembershipFunction): raise UnexpectedTypeError('mf', MembershipFunction) if not isinstance(lvar, LinguisticVariable): raise UnexpectedTypeError('lvar', LinguisticVariable) f_x = self.f(mf, lvar) if not isinstance(f_x, numbers.Number): raise FuzzleError('Expected return value to be a numeric value') return float(f_x) @abc.abstractmethod def f(self, mf, l_var): """ The method to be overriden in order to apply the function. :param mf: A membership function to defuzzify. :param l_var: A linguistic variable under the fuzzy se defined for this linguistic variable. :return: A value with the result of the defuzzifying the function. It should be any value representable as a float. """ class CoG(DefuzzificationMethod): """ Center of gravity method of defuzzification. """ def __init__(self, resolution=10000): """ Initilalizes the instance. :param resolution: The number of point the domain should be partitioned in order to get the result. The higher the resolution, the more precise is the result but the longer the method lasts. """ super().__init__(resolution=resolution) def f(self, mf, l_var): start, stop = l_var.domain step = float(stop - start) / self.resolution weighted_sum, total_sum = 0, 0 for x in frange(start=start, stop=stop, step=step): y = mf(x) if not feq(y, 0): weighted_sum += x * y total_sum += y return (stop + start) / 2 if feq(total_sum, 0) else weighted_sum / total_sum class CoGS(DefuzzificationMethod): """ Center of gravity method of singleton defuzzification. """ def f(self, mf, l_var): values = [l_var[fset].a for fset in l_var] num, den = .0, .0 for x in values: y = mf(x) num += y * x if y: den += y return None if den == 0.0 else num / den class WtSum(DefuzzificationMethod): """ Weighted sum of singleton defuzzification. """ def f(self, mf, l_var): values = [l_var[fset].a for fset in l_var] return sum([x * mf(x) for x in values]) PKH$_fuzzle/utils.pydef feq(x, y, ε=0.00000001): """ Checks if two values are almost the same. It's useful (and the only way) to compare two float numbers properly. The why is well explained in that video from: Prof. Eric Grimson and Prof. John Guttag: https://www.youtube.com/watch?v=Pfo7r6bjSqI. :param x: One number to compare. :param y: Other number to compare. :param ε: The resolution of the comparison. Defaults to 0.00000001. :return: True if both numbers are almos the same, False otherwise. """ return abs(x - y) < ε def frange(stop, start=0.0, step=1.0): """ Similar to built-in method range but for float values. :param stop: The end of the range. It'll be not included. :param step: Each of the steps until the stop value. :param start: The begining value for the range. Defaults to 0. """ return ( start + step * i for i in range(int((stop - start) / step)) ) PK]Hfuzzle/__init__.py__version__ = '0.0.4' PKH@ fuzzle/lvars.pyimport numbers from fuzzle.exceptions import FuzzleError, UnexpectedTypeError from fuzzle.mfs import MembershipFunction class LinguisticVariable(dict): """ Common behavior for linguistic vars. """ def __init__(self, name, domain): """ Initializes this membership function. :param name: The name for this linguistic variable. :param domain: The real interval where this function is defined. It should be a tuple in the form (number, number). :raises FuzzleError: In case of name being None or empty or in case domain being other than a tuple of 2 numeric values. """ super().__init__() if not name or not isinstance(name, str): raise FuzzleError('Name cannot be None or empty.') if not isinstance(domain, tuple) or len(domain) != 2: raise FuzzleError('Domain must ba a tuple like (a, b)') if not all([isinstance(x, numbers.Number) for x in domain]): raise FuzzleError('Domain elements must be numbers') self.name = name self.domain = float(min(domain)), float(max(domain)) def __setitem__(self, name, mf): """ Assigns to the fuzzy set 'name' the membership function 'mf'. :param name: the name of the fuzzy set. :param mf: the membership function that defines the set. :raises UnexpectedTypeError: If the variable mf is not a valid membership function. """ if not isinstance(mf, MembershipFunction): raise UnexpectedTypeError('mf', MembershipFunction) else: return super().__setitem__(name, mf) class InputLVar(LinguisticVariable): """ An input linguistic variable for a fuzzy controller. """ def fuzzify(self, value): """ Fuzzifies a value over all the fuzzy sets in the variable. The value (which belongs to the domain of the variable) will be applied over the membership functions of all the fuzzy sets defined in the input variable. It's expected to be anything representable as a float. That includes strings in the form '42'. :param value: The numeric value to fuzzify. :returns: A dictionary with the value fuzzified for all the fuzzy sets. :raises FuzzleError: In case the value is not representable as a float value. """ try: return dict([(name, self[name](float(value))) for name in self]) except (TypeError, ValueError)as e: raise FuzzleError(e) class OutputLVar(LinguisticVariable): """ An output linguistic variable for a fuzzy controller.""" def __init__(self, name, domain, defuzz): """ Initializes this linguistic variable. :param name: the name of the variable. :param domain: the domain of the variable. :param defuzz: the operator to defuzzify the values. """ from fuzzle.defuzz import DefuzzificationMethod LinguisticVariable.__init__(self, name, domain) if not isinstance(defuzz, DefuzzificationMethod): raise UnexpectedTypeError('defuzz', DefuzzificationMethod) self.defuzz = defuzz def defuzzify(self, fuzzy_val): return self.defuzz(fuzzy_val, self) PKHZ'c&& fuzzle/mfs.pyimport math import abc from fuzzle.exceptions import OutOfIntervalError, BadOrderError, \ IndeterminateValueError, UnexpectedTypeError from fuzzle.operators import Norm from fuzzle.utils import feq class MembershipFunction(metaclass=abc.ABCMeta): """ Base class for all the membership functions to use. """ def __call__(self, x): """ Evaluates the value against this membership function. Evaluates the membership degree of value x to the fuzzy set defined by this membership function. Delegates the behavior to the abstract method p. :param x: A value to be evaluated. :returns: The value evaluated. """ f_x = self.f(float(x)) if not 0. <= f_x <= 1.: raise OutOfIntervalError('f(x)', 0, 1, f_x) return float(f_x) @abc.abstractmethod def f(self, x): """ The method to be overridden in order to apply the function. :param x: A float value to be evaluated. :return: The value evaluated. """ class RectMF(MembershipFunction): """ Rectangular membership function. """ def __init__(self, a, b): """ Initializes this membership function. :param a: Where this membership function changes from 0 to 1. :param b: Where this membership function changes from 1 to 0. """ if not a <= b: raise BadOrderError(a, b) self.a, self.b = float(a), float(b) def f(self, x): return float(self.a <= x <= self.b) def __repr__(self): return '{}(a={}, b={})'.format(type(self).__name__, self.a, self.b) class TriMF(MembershipFunction): """ Triangular membership function. """ def __init__(self, a, b, c): """ Initializes this membership function. :param a: Where this membership function begins to grow towards 1. :param b: Where this membership function is 1. :param c: Where this membership function begins to decrease towards 0. """ if not a <= b <= c: raise BadOrderError(a, b, c) self.a, self.b, self.c = float(a), float(b), float(c) self.__b_minus_a = self.b - self.a self.__c_minus_b = self.c - self.b def f(self, x): if self.a <= x <= self.b: return (x - self.a) / self.__b_minus_a if self.__b_minus_a else 1 elif self.b <= x <= self.c: return (self.c - x) / self.__c_minus_b if self.__c_minus_b else 1 else: return 0 def __repr__(self): return '{}(a={}, b={}, c={})'.format( type(self).__name__, self.a, self.b, self.c ) class TrapMF(MembershipFunction): """ Trapezoidal membership function.""" def __init__(self, a, b, c, d): """ Initializes this membership function. :param a: value where the membership function begins to grow. :param b: value where the membership function starts being 1.0. :param c: value where the membership function ends being 1.0. :param d: value where the membership function starts decreasing. """ if not a <= b <= c <= d: raise BadOrderError(a, b, c, d) self.a, self.b, self.c, self.d = float(a), float(b), float(c), float(d) self.__b_minus_a = self.b - self.a self.__d_minus_c = self.d - self.c def f(self, x): if self.a <= x <= self.b: return (x - self.a) / self.__b_minus_a if self.__b_minus_a else 1 elif self.b <= x <= self.c: return 1 elif self.c <= x <= self.d: return (self.d - x) / self.__d_minus_c if self.__d_minus_c else 1 else: return 0 def __repr__(self): return '{}(a={}, b={}, c={}, d={})'.format( type(self).__name__, self.a, self.b, self.c, self.d, ) class SlopeMF(MembershipFunction): """ Line membership function (based on slope and independent term).""" def __init__(self, m, n): """ New line mf. based on its slope (m) and it independent term (n).""" self.m, self.n = float(m), float(n) def f(self, x): return min(max(self.m * x + self.n, 0), 1) def __repr__(self): return '{}(m={}, n={})'.format( type(self).__name__, self.m, self.n, ) class LineAscMF(MembershipFunction): """ Ascendent line membership function.""" def __init__(self, a, b): """ Initializes this membership function. :param a: value where the membership function begins to grow. :param b: value where the membership function starts being 1.0. """ if not a <= b: raise BadOrderError(a, b) self.a, self.b = float(a), float(b) self.__b_minus_a = float(b - a) def f(self, x): if x < self.a: return 0. elif self.__b_minus_a == 0: return 1 elif self.a <= x <= self.b: return min(1, max(0, (x - self.a) / self.__b_minus_a)) else: return 1. def __repr__(self): return '{}(a={}, b={})'.format( type(self).__name__, self.a, self.b, ) class LineDescMF(MembershipFunction): """ Descendent line membership function.""" def __init__(self, a, b): """ Initializes this membership function. :param a: value where the membership function begins decreasing. :param b: value where the membership function starts being 0.0. """ if not a <= b: raise BadOrderError(a, b) self.a, self.b = float(a), float(b) self.__b_minus_a = float(b - a) def f(self, x): if x < self.a: return 1 elif self.a <= x <= self.b: high = max(0, (self.b - x) / self.__b_minus_a) return min(1, high) if self.__b_minus_a else 1 else: return 0 def __repr__(self): return '{}(a={}, b={})'.format( type(self).__name__, self.a, self.b, ) class SingletonMF(MembershipFunction): """ Singleton membership function.""" def __init__(self, a): """ Initializes this membership function. :param a: value where the membership function is 1.0. """ self.a = float(a) def f(self, x): return 1 if feq(x, self.a) else 0 def __repr__(self): return '{}(a={})'.format( type(self).__name__, self.a, ) class GaussMF(MembershipFunction): """ Bell shaped membership function. """ def __init__(self, μ, σ): """ Initializes this membership function. :param μ: the average of the gauss function. :param σ: the standard deviation of the function. """ if σ == 0: raise IndeterminateValueError('σ', σ) self.μ = μ self.σ = σ self.__var = pow(σ, 2) def f(self, x): return math.exp(-0.5 * (pow(self.μ - x, 2) / self.__var)) def __repr__(self): return '{}(μ={}, σ={})'.format(type(self).__name__, self.μ, self.σ) class LogisticMF(MembershipFunction): """ Logistic membership function. """ def f(self, x): return 1 / (1 + math.exp(-x)) def __repr__(self): return '{}'.format(type(self).__name__) class CompositeMF(MembershipFunction): """ Composite membership function Given f(x) and g(x) mfs, it returns (g o f)(x) mf. """ def __init__(self, g, f): """ Initializes this membership function. :param g: one membership function. :param f: the other membership function. """ if not isinstance(g, MembershipFunction): raise UnexpectedTypeError('g', MembershipFunction) if not isinstance(f, MembershipFunction): raise UnexpectedTypeError('f', MembershipFunction) self.g = g self.f = f def f(self, x): return self.g(self.f(x)) def __repr__(self): return '{}(g={}, f={})'.format( type(self).__name__, repr(self.g), repr(self.f), ) class ConstantMF(MembershipFunction): """ Constant membership function Given a real value c it returns c for every x. """ def __init__(self, c): """ Initializes this membership function. :param c: the constant value. """ if not 0 <= c <= 1: raise OutOfIntervalError('c', 0, 1, c) self.x = float(c) def f(self, x): return self.x def __repr__(self): return '{}(c={})'.format( type(self).__name__, repr(self.x), ) class BinOpMF(MembershipFunction): def __init__(self, binary_op, f, g): """ Initializes this membership function. :param binary_op: the binary operation to apply. :param f: one membership function. :param g: other membership function. """ if not isinstance(binary_op, Norm): raise UnexpectedTypeError('binary_op', Norm) if not isinstance(f, MembershipFunction): raise UnexpectedTypeError('f', MembershipFunction) if not isinstance(g, MembershipFunction): raise UnexpectedTypeError('g', MembershipFunction) self.op = binary_op self.mf1 = f self.mf2 = g def f(self, x): return self.op(self.mf1(x), self.mf2(x)) def __repr__(self): return '{}(op={}, f={}, g={})'.format( type(self).__name__, repr(self.op), repr(self.mf1), repr(self.mf2), ) PKHyҵfuzzle/exceptions.pyclass FuzzleError(Exception): """ Base exception for all library related errors. """ pass class OutOfIntervalError(FuzzleError): """ When a value does not belong to an interval. """ def __init__( self, var_name, lower, upper, value, inc_lower=True, inc_upper=True, ): """ Initializes the exception. :param var_name: The variable name which contains the wrong value. :param lower: The lower bound of the interval. :param upper: The upper bound of the interval. :param value: The value. :param inc_lower: If the lower bound is include. Defaults to True. :param inc_upper: If the upper bound is include. Defaults to True. """ self.lower = lower self.upper = upper self.var_name = var_name self.value = value self.inc_lower = inc_lower self.inc_upper = inc_upper msg = 'Expected {} ∈ {}{}, {}{} but got {}'.format( var_name, '[' if inc_lower else '(', self.lower, self.upper, ']' if inc_upper else ')', self.value, ) super().__init__(msg) class BadOrderError(FuzzleError): """ When a list of values are expected in an ordered way. """ def __init__(self, *args): super().__init__('Bad order: {}'.format( ' <= '.join((str(x) for x in args))) ) class IndeterminateValueError(FuzzleError): """ When a value in a veriable leads to an indeterminate value. """ def __init__(self, var, value): super().__init__('{} = {}'.format(var, value)) class UnexpectedTypeError(FuzzleError): """ When a value has an unexpected type. """ def __init__(self, var, t): super().__init__('Variable {} must be of type {}'.format(var, t)) class ParseError(FuzzleError): """ When there was an error parsing something. """ def __init__(self, exp, real, value=None): msg = 'Expected {}, but {} found'.format(exp, real) if value is not None: msg = ' ("{}")'.format(value) super().__init__(msg) PKmH+&fuzzle-0.0.4.dist-info/DESCRIPTION.rst====== Fuzzle ====== ***************************************************** A library for creating and managing fuzzy controllers ***************************************************** ************ Installation ************ Installing from pip:: pip install fuzzle Installing from source:: pip install git+https://github.com/blazaid/fuzzle Requirements ============ No requirements for now. ************* Documentation ************* WIP ******* Authors ******* `fuzzle` was written by `Blazaid `_. Thanks ====== To you, for using the library, for helping me to make it faster and better, for learn with it and for releasing your code to make the knowledge and the science open for the rest of humanity. Warning ======= It has been developed with only python 3.5.X in mind, so it won't probably work on lower versions (i.e. no python 2.X support). Second warning ============== English included in both this document and the code can be devastating for the brain of an average human being. Even so we, the poor developers, are working hard to write as correctly as possible and learn along the way. The documentation will be updated as we improve our language proficency as well as we receive critical / suggestions for this. PKmH ]{{$fuzzle-0.0.4.dist-info/metadata.json{"classifiers": ["Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Scientific/Engineering :: Artificial Intelligence"], "extensions": {"python.details": {"contacts": [{"email": "alberto.da@gmail.com", "name": "blazaid", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/blazaid/fuzzle/"}}}, "generator": "bdist_wheel (0.26.0)", "license": "GNU General Public License v3", "metadata_version": "2.0", "name": "fuzzle", "platform": "any", "summary": "A fuzzy controllers library for python", "test_requires": [{"requires": []}], "version": "0.0.4"}PKmHƗ $fuzzle-0.0.4.dist-info/top_level.txtfuzzle PKmH''\\fuzzle-0.0.4.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PKmHyfuzzle-0.0.4.dist-info/METADATAMetadata-Version: 2.0 Name: fuzzle Version: 0.0.4 Summary: A fuzzy controllers library for python Home-page: http://github.com/blazaid/fuzzle/ Author: blazaid Author-email: alberto.da@gmail.com License: GNU General Public License v3 Platform: any Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3.5 Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence ====== Fuzzle ====== ***************************************************** A library for creating and managing fuzzy controllers ***************************************************** ************ Installation ************ Installing from pip:: pip install fuzzle Installing from source:: pip install git+https://github.com/blazaid/fuzzle Requirements ============ No requirements for now. ************* Documentation ************* WIP ******* Authors ******* `fuzzle` was written by `Blazaid `_. Thanks ====== To you, for using the library, for helping me to make it faster and better, for learn with it and for releasing your code to make the knowledge and the science open for the rest of humanity. Warning ======= It has been developed with only python 3.5.X in mind, so it won't probably work on lower versions (i.e. no python 2.X support). Second warning ============== English included in both this document and the code can be devastating for the brain of an average human being. Even so we, the poor developers, are working hard to write as correctly as possible and learn along the way. The documentation will be updated as we improve our language proficency as well as we receive critical / suggestions for this. PKmHsCdfuzzle-0.0.4.dist-info/RECORDfuzzle/__init__.py,sha256=-Wqu9UyJphp-2_EahEsqQvkcE0VBhm1-N7D6e5rANdY,22 fuzzle/controller.py,sha256=fs0WLAgGWvgo6mB8GdgV6N7-VgYssx874qp8PZjoeG0,3051 fuzzle/defuzz.py,sha256=3sxIgbcrf0FnuDmtixDoH4gCrmXpUtRN2xshMKJh9WU,3412 fuzzle/exceptions.py,sha256=FddDIqPwlvhXQ3SIW6YSo0RdgcsMwWe79D8NyT2RHlM,2229 fuzzle/lvars.py,sha256=vkqM8Kl21We_551LcFwADsewj9gq-AZxiAJ4CJiowyw,3290 fuzzle/mfs.py,sha256=ln4Y5fZgTJpafz8W6J_ytvCMHxXAJ0z5j4RwzT80ObY,9880 fuzzle/operators.py,sha256=22wRr55HvfxKVhFUOVDsr2pyrEYIuL6o_5C1omC1hIk,7211 fuzzle/plot_utils.py,sha256=8xnUv4Y1wn1JvCmOYBtZw2i83giQ3rNqSbwHAE9hjkg,960 fuzzle/rules.py,sha256=WEj_v6lVz-1ghCPKuLnLRiMz1n214L9VpDGalkbyuP0,12277 fuzzle/utils.py,sha256=3Vwi30e2KQLQ0n3CeEVia3afNTwKNF07ABh5p-u7V-Y,946 fuzzle-0.0.4.dist-info/DESCRIPTION.rst,sha256=ET2pouXblvEDWWK6RZa-1OvciEdD1ULP9HgJ6tQMXyI,1274 fuzzle-0.0.4.dist-info/METADATA,sha256=VXS1y79ckm5vch-qRrB78gtHWahb_XZrUoc_U0rJCok,1974 fuzzle-0.0.4.dist-info/RECORD,, fuzzle-0.0.4.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 fuzzle-0.0.4.dist-info/metadata.json,sha256=0PhKa4s0TWlwf74g45rUcJzTXeJBJR9tw4Eg1NRbQBc,891 fuzzle-0.0.4.dist-info/top_level.txt,sha256=5245cmywBlDWQNIurHCLP1PvbZ6P0dmLNF0SzQ3xrp0,7 PKH:fuzzle/plot_utils.pyPKH?j++fuzzle/operators.pyPKH{]//N fuzzle/rules.pyPKH pPfuzzle/controller.pyPKHkT T \fuzzle/defuzz.pyPKH$_jfuzzle/utils.pyPK]Hmfuzzle/__init__.pyPKH@ 4nfuzzle/lvars.pyPKHZ'c&& ;{fuzzle/mfs.pyPKHyҵfuzzle/exceptions.pyPKmH+&fuzzle-0.0.4.dist-info/DESCRIPTION.rstPKmH ]{{$#fuzzle-0.0.4.dist-info/metadata.jsonPKmHƗ $fuzzle-0.0.4.dist-info/top_level.txtPKmH''\\)fuzzle-0.0.4.dist-info/WHEELPKmHyfuzzle-0.0.4.dist-info/METADATAPKmHsCdfuzzle-0.0.4.dist-info/RECORDPKQ