Source code for catfish_sim.compatibility

[docs] class CompatibilityCalculator: def __init__(self): pass
[docs] def get_compatibility(self, judger_attributes, judged_attributes): """Calculates the compatibility between two agents using two dictionaries of attributes. Since the compatibility is calculated from the judging agent's perspective, it is not necessarily symmetrical. Also, when used by agents themselves, judger_attributes consists of both reported and hidden attributes, and hidden attributes have the precedence over the reported ones to consider the agent's hidden attributes and preferences. judged_attributes are always the reported ones. When used by a matcher, both attributes are reported attributes. Args: judger_attributes (dict): Dictionary of the judging agent's attributes. Consists of both reported and hidden attributes where the hidden ones have the precedence when the compatibility is calculated by the agent. Otherwise, judger_attributes is a dictionary of reported attributes. judged_attributes (dict): Dictionary of the judged agent's reported attributes. Returns: float: Compatibility score. """ if type(judger_attributes) is dict: judger_attributes = [ judger_attributes[key] for key in sorted(judger_attributes.keys(), reverse=True) ] judged_attributes = [ judged_attributes[key] for key in sorted(judged_attributes.keys(), reverse=True) ] compatibility = 0 total_weight = 0 for i in range(len(judger_attributes)): compatibility += ( judger_attributes[i].preference.evaluate_attribute( judged_attributes[i].value ) * judger_attributes[i].preference.compatibility_weight ) total_weight += judger_attributes[i].preference.compatibility_weight compatibility /= total_weight return compatibility
[docs] class Attribute: def __init__(self, name, value, preference): """Initializes the Attribute object with the given properties. Args: name (str): Attribute name. value (str|float|int): Attribute value. preference (Preference): A Preference object related to the attribute. """ self.name = name self.value = value self.preference = preference def __str__(self): """Returns human-readable representation of the attribute. Returns: str: Human-readable attribute details. """ return f"Attribute(name={self.name}, value={self.value}, preference={self.preference})" def __repr__(self): """Like __str__, returns human-readable representation of the attribute. Returns: str: Human-readable attribute details. """ return f"Attribute(name={self.name}, value={self.value}, preference={self.preference})"
[docs] class Preference: def __init__( self, preferred_values=None, preferred_range=None, allowed_values=None, allowed_range=None, preferred_score=1, nonpreferred_score=0, distance_sensitive=False, compatibility_weight=1, compatibility_fn=None, ): """Initializes the Preference object with the given properties. Args: preferred_values (list, optional): A list of preferred categorical attributes. Defaults to None. preferred_range (list, optional): A list of preferred range, as [min, max], for continuous attributes. Defaults to None. allowed_values (list, optional): A list of allowed categorical values. Defaults to None. allowed_range (list, optional): A list of the allowed range, as [min, max], for continuous attributes. Defaults to None. preferred_score (float, optional): The attribute-specific compatibility score when the preference is met. Defaults to 1. nonpreferred_score (float, optional): The attribute-specific compatibility score when the preference is not met. Defaults to 0. distance_sensitive (bool, optional): Indicates whether the compatibility is mapped according to the allowed range and the difference between the closest preferred value and the compared attribute. Only usable for continuous attributes. Otherwise, all non-met preferences yield the same non-preferred score. Defaults to False. compatibility_weight (float, optional): Weight multiplier used to set the attribute preference's weight in the overall compatibility. Defaults to 1. compatibility_fn (callable, optional): A compatibility function to be used in distance-sensitive (relative) compatibility for when the compared attribute does not satisfy the preferred range. Defaults to None. """ self.preferred_values = preferred_values if preferred_range and len(preferred_range) == 1: preferred_range = preferred_range * 2 # [x] -> [x, x] self.preferred_range = preferred_range self.preferred_score = preferred_score self.nonpreferred_score = nonpreferred_score self.allowed_values = allowed_values self.allowed_range = allowed_range self.distance_sensitive = distance_sensitive self.compatibility_weight = compatibility_weight if ( distance_sensitive and compatibility_fn is None and compatibility_weight and self.allowed_range ): max_diff = abs(self.allowed_range[1] - self.allowed_range[0]) min_diff = 0 self.compatibility_fn = lambda a, b: 1 + (nonpreferred_score - 1) / ( max_diff - min_diff ) * abs(a - b)
[docs] def evaluate_attribute(self, value): pass
[docs] class CategoricalPreference(Preference): def __init__( self, preferred_values, allowed_values, preferred_score, nonpreferred_score, compatibility_weight=1, ignore_unrecognized=True, ): """Initializes the categorical preference with the provided properties. Args: preferred_values (list): A list of preferred categorical attribute values. allowed_values (list): A list of all possible categorical attribute values. It is used to throw an error with unrecognized values if ignore_unrecognized is set to False. preferred_score (float): Compatibility score when the attribute is preferred. nonpreferred_score (float): Compatibility score when the attribute is not preferred. compatibility_weight (float, optional): Weight multiplier used to set the attribute preference's weight in the overall compatibility. Defaults to 1. ignore_unrecognized (bool, optional): Specifies whether evaluating a value that is not included in allowed_values should be ignored and yield the nonpreferred_score. Defaults to True. """ super().__init__( preferred_values=preferred_values, allowed_values=allowed_values, preferred_score=preferred_score, nonpreferred_score=nonpreferred_score, compatibility_weight=compatibility_weight, ) self.ignore_unrecognized = ignore_unrecognized
[docs] def evaluate_attribute(self, value): """Evaluates a candidate's attribute, comparing it with the preferred values and returning an attribute-specific compatibility score. Args: value (str|float|int): Candidate's attribute value. Returns: float: Attribute-specific compatibility score. """ if isinstance(self.preferred_score, dict) and value in self.preferred_score: return self.preferred_score[value] elif value in self.preferred_values: return self.preferred_score elif self.ignore_unrecognized: return self.nonpreferred_score else: raise ValueError( f'The evaluated value "{value}" is not recognized (it is not listed in allowed_values).' )
def __str__(self): """Returns human-readable representation of the preference. Returns: str: Human-readable preference details. """ return f"CategoricalPreference(\n\tpreferred_values={self.preferred_values}, \n\tpreferred_score={self.preferred_score}, \n\tnonpreferred_score={self.nonpreferred_score}\n)" def __repr__(self): """Like __str__, returns human-readable representation of the preference. Returns: str: Human-readable preference details. """ return f"CategoricalPreference(\n\tpreferred_values={self.preferred_values}, \n\tpreferred_score={self.preferred_score}, \n\tnonpreferred_score={self.nonpreferred_score}\n)"
[docs] class NumericalPreference(Preference): def __init__( self, preferred_range, allowed_range, preferred_score, nonpreferred_score, distance_sensitive=None, compatibility_weight=1, compatibility_fn=None, ): """Initializes the numerical preference with the provided properties. Args: preferred_range (list): A list of preferred numerical attribute range, as [min, max]. allowed_range (list): A list of the possible numerical attribute range, as [min, max]. preferred_score (float): Compatibility score when the attribute is preferred. nonpreferred_score (float): Compatibility score when the attribute is not preferred. distance_sensitive (bool, optional): Indicates whether the compatibility is mapped according to the allowed range and the difference between the closest preferred value and the compared attribute. Only usable for continuous attributes. Otherwise, all non-met preferences yield the same non-preferred score. Defaults to False. compatibility_weight (float, optional): Weight multiplier used to set the attribute preference's weight in the overall compatibility. Defaults to 1. compatibility_fn (callable, optional): A compatibility function to be used in distance-sensitive (relative) compatibility for when the compared attribute does not satisfy the preferred range. Defaults to None. """ super().__init__( preferred_range=preferred_range, allowed_range=allowed_range, preferred_score=preferred_score, nonpreferred_score=nonpreferred_score, distance_sensitive=distance_sensitive, compatibility_weight=compatibility_weight, compatibility_fn=compatibility_fn, )
[docs] def evaluate_attribute(self, value): """Evaluates a candidate's attribute, comparing it with the preferred value range and returning an attribute-specific compatibility score. Args: value (float): Candidate's attribute value. Returns: float: Attribute-specific compatibility score. """ if self.preferred_range[0] <= value <= self.preferred_range[1]: return self.preferred_score elif self.distance_sensitive is False: return self.nonpreferred_score else: # relative loss based on the preferred range if value < self.preferred_range[0]: return self.compatibility_fn(value, self.preferred_range[0]) else: return self.compatibility_fn(value, self.preferred_range[1])
def __str__(self): """Returns human-readable representation of the preference. Returns: str: Human-readable preference details. """ return f"NumericalPreference(\n\tpreferred_range={self.preferred_range}, \n\tpreferred_score={self.preferred_score}, \n\tnonpreferred_score={self.nonpreferred_score}, \n\tdistance_sensitive={self.distance_sensitive}, \n\tcompatibility_weight={self.compatibility_weight}, \n\tcompatibility_fn={self.compatibility_fn if hasattr(self, 'compatibility_fn') else None}\n)" def __repr__(self): """Like __str__, returns human-readable representation of the preference. Returns: str: Human-readable preference details. """ return f"NumericalPreference(\n\tpreferred_range={self.preferred_range}, \n\tpreferred_score={self.preferred_score}, \n\tnonpreferred_score={self.nonpreferred_score}, \n\tdistance_sensitive={self.distance_sensitive}, \n\tcompatibility_weight={self.compatibility_weight}, \n\tcompatibility_fn={self.compatibility_fn if hasattr(self, 'compatibility_fn') else None}\n)"
[docs] class DictBasedPreference(Preference): def __init__( self, compatibility_dict, default_value=None, compatibility_weight=1, ): """Initializes the dictionary-based preference with the provided properties. Args: compatibility_dict (dict): A dictionary that maps attribute values to compatibility scores. default_value (Any, optional): A default compatibility value to be returned when the evaluated value does not exist in the dictionary. Defaults to None. compatibility_weight (float, optional): Weight multiplier used to set the attribute preference's weight in the overall compatibility. Defaults to 1. """ self.compatibility_dict = compatibility_dict self.default_value = default_value self.compatibility_weight = compatibility_weight
[docs] def evaluate_attribute(self, value): """Evaluates a candidate's attribute, comparing it with the preferred value range and returning an attribute-specific compatibility score. Args: value (float): Candidate's attribute value. Returns: float: Attribute-specific compatibility score. """ return self.compatibility_dict.get(value, self.default_value)
def __str__(self): """Returns human-readable representation of the preference. Returns: str: Human-readable preference details. """ return f"DictPreference(\n\tcompatibility_dict={self.compatibility_dict}, \n\tdefault_value={self.default_value}, \n\tcompatibility_weight={self.compatibility_weight})" def __repr__(self): """Like __str__, returns human-readable representation of the preference. Returns: str: Human-readable preference details. """ return f"DictPreference(\n\tcompatibility_dict={self.compatibility_dict}, \n\tdefault_value={self.default_value}, \n\tcompatibility_weight={self.compatibility_weight})"