1# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Fuzzy comparisons and aggregations.""" 6 7 8import logging 9import math 10 11from firmware_constants import MF 12 13 14DEFAULT_MEMBERSHIP_FUNCTION = { 15 '<=': MF.Z_FUNCTION, 16 '<': MF.Z_FUNCTION, 17 '>=': MF.S_FUNCTION, 18 '>': MF.S_FUNCTION, 19 '==': MF.SINGLETON_FUNCTION, 20 '~=': MF.PI_FUNCTION, 21} 22 23 24"""Define possible score aggregators: average() and product(). 25 26A score aggregator collects all scores from every tests, and calculate 27a final score. 28""" 29 30def average(data): 31 """The average of the elements in data.""" 32 number = len(data) 33 return math.fsum(data) / number if number > 0 else None 34 35 36def product(data): 37 """The product of the elements in data.""" 38 return math.exp(math.fsum([math.log(d) for d in data])) 39 40 41"""Classes of various fuzzy member functions are defined below.""" 42 43class FuzzyMemberFunctions(object): 44 """The base class of membership functions.""" 45 def __init__(self, para): 46 """Example of parameter: (0.1, 0.3).""" 47 self.para_values = map(float, para) 48 49 50class FuzzySingletonMemberFunction(FuzzyMemberFunctions): 51 """A class provides fuzzy Singleton Membership Function. 52 53 Singleton Membership Function: 54 parameters: (left, middle, right) 55 grade(x) = 0.0, when x <= left 56 0.0 to 1.0, when left <= x <= middle 57 1.0, when x == middle 58 1.0 to 0.0, when middle <= x <= right 59 0.0, when x >= right 60 E.g., FuzzySingletonMemberFunction((1, 1, 1)) 61 Usage: when we want the x == 1 in the ideal condition. 62 grade = 1.0, when x == 1 63 0.0, when x != 1 64 65 Note: - When x is near 'middle', the grade would be pretty close to 1. 66 - When x becomes near 'left' or 'right', its grade may drop 67 faster and would approach 0. 68 - A cosine function is used to implement this behavior. 69 """ 70 def __init__(self, para): 71 super(FuzzySingletonMemberFunction, self).__init__(para) 72 self.left, self.middle, self.right = self.para_values 73 self.width_right = self.right - self.middle 74 self.width_left = self.middle - self.left 75 76 def grade(self, x): 77 """The grading method of the fuzzy membership function.""" 78 if x == self.middle: 79 return 1 80 elif x <= self.left or x >= self.right: 81 return 0 82 elif x > self.middle: 83 return (0.5 + 0.5 * math.cos((x - self.middle) / self.width_right * 84 math.pi)) 85 elif x < self.middle: 86 return (0.5 + 0.5 * math.cos((x - self.middle) / self.width_left * 87 math.pi)) 88 89 90class FuzzySMemberFunction(FuzzyMemberFunctions): 91 """A class provides fuzzy S Membership Function. 92 93 S Membership Function: 94 parameters: (left, right) 95 grade(x) = 1 for x >= right 96 0 for x <= left 97 E.g., FuzzySMemberFunction((0.1, 0.3)) 98 Usage: when we want the x >= 0.3 in the ideal condition. 99 grade = 1.0, when x >= 0.3 100 between 0.0 and 1.0, when 0.1 <= x <= 0.3 101 0.0, when x <= 0.1 102 103 Note: - When x is less than but near 'right' value, the grade would be 104 pretty close to 1. 105 - When x becomes near 'left' value, its grade may drop faster 106 and would approach 0. 107 - A cosine function is used to implement this behavior. 108 """ 109 110 def __init__(self, para): 111 super(FuzzySMemberFunction, self).__init__(para) 112 self.left, self.right = self.para_values 113 self.width = self.right - self.left 114 115 def grade(self, x): 116 """The grading method of the fuzzy membership function.""" 117 if x >= self.right: 118 return 1 119 elif x <= self.left: 120 return 0 121 else: 122 return 0.5 + 0.5 * math.cos((x - self.right) / self.width * math.pi) 123 124 125class FuzzyZMemberFunction(FuzzyMemberFunctions): 126 """A class provides fuzzy Z Membership Function. 127 128 Z Membership Function: 129 parameters: (left, right) 130 grade(x) = 1 for x <= left 131 0 for x >= right 132 E.g., FuzzyZMemberFunction((0.1, 0.3)) 133 Usage: when we want the x <= 0.1 in the ideal condition. 134 grade = 1.0, when x <= 0.1 135 between 0.0 and 1.0, when 0.1 <= x <= 0.3 136 0.0, when x >= 0.3 137 138 Note: - When x is greater than but near 'left' value, the grade would be 139 pretty close to 1. 140 - When x becomes near 'right' value, its grade may drop faster 141 and would approach 0. 142 - A cosine function is used to implement this behavior. 143 """ 144 145 def __init__(self, para): 146 super(FuzzyZMemberFunction, self).__init__(para) 147 self.left, self.right = self.para_values 148 self.width = self.right - self.left 149 150 def grade(self, x): 151 """The grading method of the fuzzy membership function.""" 152 if x <= self.left: 153 return 1 154 elif x >= self.right: 155 return 0 156 else: 157 return 0.5 + 0.5 * math.cos((x - self.left) / self.width * math.pi) 158 159 160# Mapping from membership functions to the fuzzy member function classes. 161MF_DICT = { 162 # TODO(josephsih): PI, TRAPEZ, and TRIANGLE functions are to be implemented. 163 # MF.PI_FUNCTION: FuzzyPiMemberFunction, 164 MF.SINGLETON_FUNCTION: FuzzySingletonMemberFunction, 165 MF.S_FUNCTION: FuzzySMemberFunction, 166 # MF.TRAPEZ_FUNCTION: FuzzyTrapezMemberFunction, 167 # MF.TRIANGLE_FUNCTION: FuzzyTriangleMemberFunction 168 MF.Z_FUNCTION: FuzzyZMemberFunction, 169} 170 171 172class FuzzyCriteria: 173 """A class to parse criteria string and build the criteria object.""" 174 175 def __init__(self, criteria_str, mf=None): 176 self.criteria_str = criteria_str 177 self.mf_name = mf 178 self.mf = None 179 self.default_mf_name = None 180 self.value_range = None 181 self._parse_criteria_and_exit_on_failure() 182 self._create_mf() 183 184 def _parse_criteria(self, criteria_str): 185 """Parse the criteria string. 186 187 Example: 188 Ex 1. '<= 0.05, ~ +0.07': 189 . The ideal input value should be <= 0.05. If so, it gets 190 the grade 1.0 191 . The allowable upper bound is 0.05 + 0.07 = 0.12. Anything 192 greater than or equal to 0.12 would get a grade 0.0 193 . Any input value falling between 0.05 and 0.12 would get a 194 score between 0.0 and 1.0 depending on which membership 195 function is used. 196 """ 197 criteria_list = criteria_str.split(',') 198 tolerable_delta = [] 199 op_value = None 200 for c in criteria_list: 201 op, value = c.split() 202 # TODO(josephsih): should support and '~=' later. 203 if op in ['<=', '<', '>=', '>', '==']: 204 primary_op = op 205 self.default_mf_name = DEFAULT_MEMBERSHIP_FUNCTION[op] 206 op_value = float(value) 207 elif op == '~': 208 tolerable_delta.append(float(value)) 209 else: 210 return False 211 212 # Syntax error in criteria string 213 if op_value is None: 214 return False 215 216 # Calculate the allowable range of values 217 range_max = range_min = op_value 218 for delta in tolerable_delta: 219 if delta >= 0: 220 range_max = op_value + delta 221 else: 222 range_min = op_value + delta 223 224 if primary_op in ['<=', '<', '>=', '>']: 225 self.value_range = (range_min, range_max) 226 elif primary_op == '==': 227 self.value_range = (range_min, op_value, range_max) 228 else: 229 self.value_range = None 230 231 return True 232 233 def _parse_criteria_and_exit_on_failure(self): 234 """Call _parse_critera and exit on failure.""" 235 if not self._parse_criteria(self.criteria_str): 236 logging.error('Parsing criteria string error.') 237 exit(1) 238 239 def _create_mf(self): 240 """Parse the criteria and create its membership function object.""" 241 # If a membership function is specified in the test_conf, use it. 242 # Otherwise, use the default one. 243 mf_name = self.mf_name if self.mf_name else self.default_mf_name 244 mf_class = MF_DICT[mf_name] 245 self.mf = mf_class(self.value_range) 246 247 def get_criteria_value_range(self): 248 """Parse the criteria and return its op value.""" 249 return self.value_range 250