blob: 77ec58ea384c0ebc1a1c8a3a5237a86b07373ba0 [file] [log] [blame]
# 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