# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Fuzzy comparisons and aggregations.""" import logging import math from firmware_constants import MF DEFAULT_MEMBERSHIP_FUNCTION = { '<=': MF.Z_FUNCTION, '<': MF.Z_FUNCTION, '>=': MF.S_FUNCTION, '>': MF.S_FUNCTION, '==': MF.SINGLETON_FUNCTION, '~=': MF.PI_FUNCTION, } """Define possible score aggregators: average() and product(). A score aggregator collects all scores from every tests, and calculate a final score. """ def average(data): """The average of the elements in data.""" number = len(data) return math.fsum(data) / number if number > 0 else None def product(data): """The product of the elements in data.""" return math.exp(math.fsum([math.log(d) for d in data])) """Classes of various fuzzy member functions are defined below.""" class FuzzyMemberFunctions(object): """The base class of membership functions.""" def __init__(self, para): """Example of parameter: (0.1, 0.3).""" self.para_values = map(float, para) class FuzzySingletonMemberFunction(FuzzyMemberFunctions): """A class provides fuzzy Singleton Membership Function. Singleton Membership Function: parameters: (left, middle, right) grade(x) = 0.0, when x <= left 0.0 to 1.0, when left <= x <= middle 1.0, when x == middle 1.0 to 0.0, when middle <= x <= right 0.0, when x >= right E.g., FuzzySingletonMemberFunction((1, 1, 1)) Usage: when we want the x == 1 in the ideal condition. grade = 1.0, when x == 1 0.0, when x != 1 Note: - When x is near 'middle', the grade would be pretty close to 1. - When x becomes near 'left' or 'right', its grade may drop faster and would approach 0. - A cosine function is used to implement this behavior. """ def __init__(self, para): super(FuzzySingletonMemberFunction, self).__init__(para) self.left, self.middle, self.right = self.para_values self.width_right = self.right - self.middle self.width_left = self.middle - self.left def grade(self, x): """The grading method of the fuzzy membership function.""" if x == self.middle: return 1 elif x <= self.left or x >= self.right: return 0 elif x > self.middle: return (0.5 + 0.5 * math.cos((x - self.middle) / self.width_right * math.pi)) elif x < self.middle: return (0.5 + 0.5 * math.cos((x - self.middle) / self.width_left * math.pi)) class FuzzySMemberFunction(FuzzyMemberFunctions): """A class provides fuzzy S Membership Function. S Membership Function: parameters: (left, right) grade(x) = 1 for x >= right 0 for x <= left E.g., FuzzySMemberFunction((0.1, 0.3)) Usage: when we want the x >= 0.3 in the ideal condition. grade = 1.0, when x >= 0.3 between 0.0 and 1.0, when 0.1 <= x <= 0.3 0.0, when x <= 0.1 Note: - When x is less than but near 'right' value, the grade would be pretty close to 1. - When x becomes near 'left' value, its grade may drop faster and would approach 0. - A cosine function is used to implement this behavior. """ def __init__(self, para): super(FuzzySMemberFunction, self).__init__(para) self.left, self.right = self.para_values self.width = self.right - self.left def grade(self, x): """The grading method of the fuzzy membership function.""" if x >= self.right: return 1 elif x <= self.left: return 0 else: return 0.5 + 0.5 * math.cos((x - self.right) / self.width * math.pi) class FuzzyZMemberFunction(FuzzyMemberFunctions): """A class provides fuzzy Z Membership Function. Z Membership Function: parameters: (left, right) grade(x) = 1 for x <= left 0 for x >= right E.g., FuzzyZMemberFunction((0.1, 0.3)) Usage: when we want the x <= 0.1 in the ideal condition. grade = 1.0, when x <= 0.1 between 0.0 and 1.0, when 0.1 <= x <= 0.3 0.0, when x >= 0.3 Note: - When x is greater than but near 'left' value, the grade would be pretty close to 1. - When x becomes near 'right' value, its grade may drop faster and would approach 0. - A cosine function is used to implement this behavior. """ def __init__(self, para): super(FuzzyZMemberFunction, self).__init__(para) self.left, self.right = self.para_values self.width = self.right - self.left def grade(self, x): """The grading method of the fuzzy membership function.""" if x <= self.left: return 1 elif x >= self.right: return 0 else: return 0.5 + 0.5 * math.cos((x - self.left) / self.width * math.pi) # Mapping from membership functions to the fuzzy member function classes. MF_DICT = { # TODO(josephsih): PI, TRAPEZ, and TRIANGLE functions are to be implemented. # MF.PI_FUNCTION: FuzzyPiMemberFunction, MF.SINGLETON_FUNCTION: FuzzySingletonMemberFunction, MF.S_FUNCTION: FuzzySMemberFunction, # MF.TRAPEZ_FUNCTION: FuzzyTrapezMemberFunction, # MF.TRIANGLE_FUNCTION: FuzzyTriangleMemberFunction MF.Z_FUNCTION: FuzzyZMemberFunction, } class FuzzyCriteria: """A class to parse criteria string and build the criteria object.""" def __init__(self, criteria_str, mf=None): self.criteria_str = criteria_str self.mf_name = mf self.mf = None self.default_mf_name = None self.value_range = None self._parse_criteria_and_exit_on_failure() self._create_mf() def _parse_criteria(self, criteria_str): """Parse the criteria string. Example: Ex 1. '<= 0.05, ~ +0.07': . The ideal input value should be <= 0.05. If so, it gets the grade 1.0 . The allowable upper bound is 0.05 + 0.07 = 0.12. Anything greater than or equal to 0.12 would get a grade 0.0 . Any input value falling between 0.05 and 0.12 would get a score between 0.0 and 1.0 depending on which membership function is used. """ criteria_list = criteria_str.split(',') tolerable_delta = [] op_value = None for c in criteria_list: op, value = c.split() # TODO(josephsih): should support and '~=' later. if op in ['<=', '<', '>=', '>', '==']: primary_op = op self.default_mf_name = DEFAULT_MEMBERSHIP_FUNCTION[op] op_value = float(value) elif op == '~': tolerable_delta.append(float(value)) else: return False # Syntax error in criteria string if op_value is None: return False # Calculate the allowable range of values range_max = range_min = op_value for delta in tolerable_delta: if delta >= 0: range_max = op_value + delta else: range_min = op_value + delta if primary_op in ['<=', '<', '>=', '>']: self.value_range = (range_min, range_max) elif primary_op == '==': self.value_range = (range_min, op_value, range_max) else: self.value_range = None return True def _parse_criteria_and_exit_on_failure(self): """Call _parse_critera and exit on failure.""" if not self._parse_criteria(self.criteria_str): logging.error('Parsing criteria string error.') exit(1) def _create_mf(self): """Parse the criteria and create its membership function object.""" # If a membership function is specified in the test_conf, use it. # Otherwise, use the default one. mf_name = self.mf_name if self.mf_name else self.default_mf_name mf_class = MF_DICT[mf_name] self.mf = mf_class(self.value_range) def get_criteria_value_range(self): """Parse the criteria and return its op value.""" return self.value_range