"""
.. py:module:: rules/agent.py
:platform: Unix
The module holding :class:`RuleAgent`, an agent which evaluates artifacts using
its rules.
"""
from creamas.core.agent import CreativeAgent
from creamas.rules.rule import Rule, RuleLeaf
from creamas.util import expose
__all__ = ['RuleAgent']
[docs]class RuleAgent(CreativeAgent):
"""Base class for agents using rules to evaluate artifacts.
In addition to common attributes inherited from :class:`CreativeAgent`,
rule agents have following attributes:
:ivar list ~creamas.core.agent.CreativeAgent.R:
rules agent uses to evaluate artifacts
:ivar list ~creamas.core.agent.CreativeAgent.W:
Weight for each rule in **R**, in [-1,1].
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._R = []
self._W = []
@property
def R(self):
"""Rules agent uses to evaluate artifacts. Each rule in **R** is
expected to be a callable with a single parameter, the artifact to be
evaluated. Callable should return a float in [-1,1]; where 1 means that
rule is very prominent in the artifact; 0 means that there is none of
that rule in the artifact; -1 means that the artifact shows
traits opposite to the rule.
"""
return self._R
@property
def W(self):
"""Weights for the rules.
Each weight should be in [-1,1].
"""
return self._W
[docs] def set_weight(self, rule, weight):
"""Set weight for rule in :attr:`R`.
Adds the rule if it is not in :attr:`R`.
"""
if not issubclass(rule.__class__, (Rule, RuleLeaf)):
raise TypeError("Rule to set weight ({}) is not subclass "
"of {} or {}.".format(rule, Rule, RuleLeaf))
assert (weight >= -1.0 and weight <= 1.0)
try:
ind = self._R.index(rule)
self._W[ind] = weight
except:
self.add_rule(rule, weight)
[docs] def get_weight(self, rule):
"""Get weight for rule.
If rule is not in :attr:`R`, returns ``None``.
"""
if not issubclass(rule.__class__, (Rule, RuleLeaf)):
raise TypeError("Rule to get weight ({}) is not subclass "
"of {} or {}.".format(rule, Rule, RuleLeaf))
try:
ind = self._R.index(rule)
return self._W[ind]
except:
return None
[docs] def add_rule(self, rule, weight):
"""Add rule to :attr:`R` with initial weight.
:param rule: rule to be added
:type rule: `~creamas.core.rule.Rule`
:param float weight: initial weight for the rule
:raises TypeError: if rule is not subclass of :py:class:`Rule`
:returns: ``True`` if rule was successfully added, otherwise ``False``.
:rtype bool:
"""
if not issubclass(rule.__class__, (Rule, RuleLeaf)):
raise TypeError(
"Rule to add ({}) must be derived from {} or {}."
.format(rule.__class__, Rule, RuleLeaf))
if rule not in self._R:
self._R.append(rule)
self._W.append(weight)
return True
return False
[docs] def remove_rule(self, rule):
"""Remove rule from :attr:`R` and its corresponding weight from
:attr:`W`.
:param rule: rule to remove
:type rule:
:class:`~creamas.rules.rule.Rule` or
:class:`~creamas.rules.rule.RuleLeaf`
:raises TypeError:
If rule is not derived from :class:`Rule` or :class:`RuleLeaf`.
:returns:
``True`` if the rule was successfully removed, otherwise ``False``.
:rtype bool:
"""
if not issubclass(rule.__class__, (Rule, RuleLeaf)):
raise TypeError(
"Rule to remove ({}) is not subclass of {} or {}."
.format(rule.__class__, Rule, RuleLeaf))
try:
ind = self._R.index(rule)
del self._R[ind]
del self._W[ind]
return True
except:
return False
[docs] @expose
def evaluate(self, artifact):
r"""Evaluate artifact with agent's current rules and weights.
:param artifact:
:class:`~creamas.core.artifact.Artifact` to be evaluated
:type artifact:
:py:class:`~creamas.core.artifact.Artifact`
:returns:
Agent's evaluation of the artifact, in [-1,1], and framing. In this
basic implementation framing is always ``None``.
:rtype:
tuple
Actual evaluation formula in this basic implementation is:
.. math::
e(A) = \frac{\sum_{i=1}^{n} r_{i}(A)w_i}
{\sum_{i=1}^{n} \lvert w_i \rvert},
where :math:`r_{i}(A)` is the :math:`i` th rule's evaluation on
artifact :math:`A`, and :math:`w_i` is the weight for rule
:math:`r_i`.
"""
s = 0
w = 0.0
if len(self.R) == 0:
return 0.0, None
for i in range(len(self.R)):
s += self.R[i](artifact) * self.W[i]
w += abs(self.W[i])
if w == 0.0:
return 0.0, None
return s / w, None