Files
Sheerka-Old/src/core/sheerka/services/SheerkaEvaluateRules.py
T
kodjo 89e1f20975 Fixed #131 : Implement ExprToConditions
Fixed #130 : ArithmeticOperatorParser
Fixed #129 : python_wrapper : create_namespace
Fixed #128 : ExpressionParser: Cannot parse func(x) infixed concept 'xxx'
2021-10-13 16:06:57 +02:00

196 lines
7.7 KiB
Python

from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import expect_one
from core.global_symbols import EVENT_RULE_CREATED, EVENT_RULE_DELETED, EVENT_RULE_ID_DELETED
from core.sheerka.services.sheerka_service import BaseService
from sheerkarete.network import ReteNetwork
DISABLED_RULES = "#disabled#"
LOW_PRIORITY_RULES = "#low_priority#"
class SheerkaEvaluateRules(BaseService):
NAME = "EvaluateRules"
def __init__(self, sheerka):
# order must be before RuleManager because of event subscription
super().__init__(sheerka, 4)
self.evaluators_by_name = None
self.network = ReteNetwork()
def initialize(self):
self.sheerka.bind_service_method(self.NAME, self.evaluate_format_rules, False, visible=False)
self.sheerka.bind_service_method(self.NAME, self.evaluate_exec_rules, False, visible=False)
self.reset_evaluators()
self.sheerka.subscribe(EVENT_RULE_CREATED, self.on_rule_created)
self.sheerka.subscribe(EVENT_RULE_DELETED, self.on_rule_deleted)
self.sheerka.subscribe(EVENT_RULE_ID_DELETED, self.on_rule_deleted)
self.sheerka.register_debug_vars(self.NAME, "evaluate_rules", "results")
self.sheerka.register_debug_rules(self.NAME, "evaluate_rule", "*")
def reset_evaluators(self):
# instantiate evaluators, once for all, only keep when it's enabled
evaluators = [e_class() for e_class in self.sheerka.evaluators]
evaluators = [e for e in evaluators if e.enabled]
self.evaluators_by_name = {e.short_name: e for e in evaluators}
def evaluate_exec_rules(self, context, return_values):
# self.network.add_obj("__rets", return_values)
for ret in return_values:
self.network.add_obj("__ret", ret)
results = [] # list of return values, for activated rules
for match in self.network.matches:
for rule in match.pnode.rules:
body = context.sheerka.new(BuiltinConcepts.RULE_EVALUATION_RESULT, rule=rule)
return_value = context.sheerka.ret(self.NAME, True, body)
results.append(return_value)
return results
def evaluate_format_rules(self, context, bag, disabled):
return self.evaluate_rules(context, self.sheerka.get_format_rules(), bag, disabled)
def evaluate_rules(self, context, rules, bag, disabled):
"""
evaluate the format rules, in the context of 'bag'
CAUTION : the rules MUST be sorted by priority
:param context:
:param rules:
:param bag:
:param disabled: disabled rules (because they have already been fired or whatever)
:return: { True : list of success, False :list of failed, '#disabled"': list of disabled...}
"""
with context.push(BuiltinConcepts.RULES_EVALUATION, bag, desc="Evaluating rules...") as sub_context:
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_WHERE_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_QUESTION_REQUESTED)
sub_context.add_inputs(bag=bag)
debugger = sub_context.get_debugger(SheerkaEvaluateRules.NAME, "evaluate_rules")
debugger.debug_entering(bag=bag)
results = {}
sub_context.sheerka.add_many_to_short_term_memory(sub_context, bag)
success_priority = None
for rule in rules:
if not rule.metadata.is_enabled or rule.id in disabled:
results.setdefault(DISABLED_RULES, []).append(rule)
continue
if success_priority and rule.priority != success_priority:
results.setdefault(LOW_PRIORITY_RULES, []).append(rule)
continue
res = self.evaluate_rule(sub_context, rule, bag)
ok = res.status and self.sheerka.is_success(self.sheerka.objvalue(res))
results.setdefault(ok, []).append(rule)
if ok and success_priority is None:
success_priority = rule.priority
debugger.debug_var("results", self.get_debug_format(results))
sub_context.add_values(rules_result=results)
return results
def evaluate_rule(self, context, rule, bag):
"""
Evaluate the conditions
:param context:
:param rule:
:param bag:
:return:
"""
results = self.evaluate_conditions(context, rule.compiled_conditions, bag)
debugger = context.get_debugger(SheerkaEvaluateRules.NAME, "evaluate_rule", new_debug_id=False)
debugger.debug_rule(rule, results)
return expect_one(context, results)
def evaluate_conditions(self, context, conditions, bag, missing_vars=None):
"""
Evaluate the conditions
:param context:
:param conditions:
:param bag: variables that are supposed to be in short term memory
:param missing_vars: if initialized to a set, keeps tracks of the missing variables
:return:
"""
bag_variables = set(bag.keys())
for k, v in bag.items():
context.add_to_short_term_memory(k, v)
results = []
for compiled_condition in conditions:
if compiled_condition.variables.intersection(bag_variables) != compiled_condition.variables:
if isinstance(missing_vars, set):
missing_vars.update(compiled_condition.variables - bag_variables)
continue
if compiled_condition.not_variables.intersection(bag_variables):
continue
if compiled_condition.return_value is None:
# We only want to test the existence of a data
results.append(context.sheerka.ret(self.NAME, True, True))
else:
# do not forget to reset the 'is_evaluated' in the case of a concept
for concept in compiled_condition.concepts_to_reset:
concept.get_hints().is_evaluated = False
evaluator = self.evaluators_by_name[compiled_condition.evaluator_type]
res = evaluator.eval(context, compiled_condition.return_value)
value = context.sheerka.objvalue(res.body)
if res.status and isinstance(value, bool) and value:
# one successful value found. No need to look any further
results = [res] # don't we care about the other failing results ?
break
else:
results.append(res)
return results
def remove_from_rete_memory(self, lst):
if lst is None:
return
for obj in lst:
self.network.remove_obj(obj)
def on_rule_created(self, context, rule):
"""
When a new rule is added to the system, update the network
"""
if rule.metadata.is_enabled and rule.rete_disjunctions:
self.network.add_rule(rule)
def on_rule_deleted(self, context, rule):
"""
When a rule is deleted from the system, remove it from the network
"""
if isinstance(rule, str):
rule = self.sheerka.get_rule_by_id(rule)
if not self.sheerka.is_known(rule):
return
self.network.remove_rule(rule)
@staticmethod
def get_debug_format(result):
"""
Return the same dictionary, the with the short formatting of the rules
eg without the action clause
:param result:
:return:
"""
return {key: [str(r) if key == True else r.short_str() for r in rules] for key, rules in result.items()}