Implemented a first and basic version of a Rete rule engine

This commit is contained in:
2021-02-09 16:06:32 +01:00
parent 821dbed189
commit a2a8d5c5e5
110 changed files with 7301 additions and 1654 deletions
+80 -28
View File
@@ -1,6 +1,7 @@
import inspect
import logging
from dataclasses import dataclass
from operator import attrgetter
import core.builtin_helpers
import core.utils
@@ -10,8 +11,7 @@ from cache.IncCache import IncCache
from core.builtin_concepts import ErrorConcept, ReturnValueConcept, UnknownConcept
from core.builtin_concepts_ids import BuiltinErrors, BuiltinConcepts
from core.concept import Concept, ConceptParts, get_concept_attrs
from core.error import ErrorObj
from core.global_symbols import EVENT_USER_INPUT_EVALUATED, NotInit, NotFound
from core.global_symbols import EVENT_USER_INPUT_EVALUATED, NotInit, NotFound, ErrorObj, EVENT_ONTOLOGY_CREATED
from core.profiling import profile
from core.sheerka.ExecutionContext import ExecutionContext
from core.sheerka.SheerkaOntologyManager import SheerkaOntologyManager, OntologyAlreadyExists
@@ -31,6 +31,23 @@ EXECUTE_STEPS = [
BuiltinConcepts.AFTER_EVALUATION
]
RULES_EVALUATE_STEPS = [
BuiltinConcepts.BEFORE_RULES_EVALUATION,
BuiltinConcepts.RULES_EVALUATION,
BuiltinConcepts.AFTER_RULES_EVALUATION,
]
RULES_EXECUTE_STEPS = [
BuiltinConcepts.BEFORE_EVALUATION,
BuiltinConcepts.EVALUATION,
BuiltinConcepts.AFTER_EVALUATION
]
# when a concept is instantiated via resolve or false_resolve
# It indicate which parameter was used to recognize the concept
RECOGNIZED_BY_ID = "by_id"
RECOGNIZED_BY_NAME = "by_name"
@dataclass
class SheerkaMethod:
@@ -92,17 +109,25 @@ class Sheerka(Concept):
self.save_execution_context = True
self.enable_process_return_values = True
self.enable_process_rules = True
self.methods_with_context = {"test_using_context"} # only the names, the method is defined in sheerka_methods
self.sheerka_methods = {
"test": SheerkaMethod(self.test, False),
"test_using_context": SheerkaMethod(self.test_using_context, False),
"test_dict": SheerkaMethod(self.test_dict, False)
"test_dict": SheerkaMethod(self.test_dict, False),
"test_error": SheerkaMethod(self.test_error, False),
}
self.locals = {}
self.concepts_ids = None
def __copy__(self):
return self
def __deepcopy__(self, memodict={}):
return self
@property
def concepts_grammars(self):
"""
@@ -138,7 +163,7 @@ class Sheerka(Concept):
setattr(self, bound_method.__name__, bound_method)
def initialize(self, root_folder: str = None, save_execution_context=None, enable_process_return_values=None):
def initialize(self, root_folder: str = None, **kwargs):
"""
Starting Sheerka
Loads the current configuration
@@ -149,11 +174,10 @@ class Sheerka(Concept):
:return: ReturnValue(Success or Error)
"""
if save_execution_context is not None:
self.save_execution_context = save_execution_context
if enable_process_return_values is not None:
self.enable_process_return_values = enable_process_return_values
self.save_execution_context = kwargs.get("save_execution_context", self.save_execution_context)
self.enable_process_return_values = kwargs.get("enable_process_return_values",
self.enable_process_return_values)
self.enable_process_rules = kwargs.get("enable_process_rules", self.enable_process_rules)
try:
self.during_initialisation = True
@@ -168,6 +192,7 @@ class Sheerka(Concept):
self.get_builtin_evaluators()
self.initialize_services()
self.initialize_builtin_evaluators()
self.om.init_subscribers()
event = Event("Initializing Sheerka.", user_id=self.name)
self.om.save_event(event)
@@ -234,11 +259,12 @@ class Sheerka(Concept):
core.utils.import_module_and_sub_module('core.sheerka.services')
base_class = "core.sheerka.services.sheerka_service.BaseService"
for service in core.utils.get_sub_classes("core.sheerka.services", base_class):
instance = service(self)
if hasattr(instance, "initialize"):
instance.initialize()
self.services[service.NAME] = instance
services = [service(self) for service in core.utils.get_sub_classes("core.sheerka.services", base_class)]
services.sort(key=attrgetter("order"))
for service in services:
if hasattr(service, "initialize"):
service.initialize()
self.services[service.NAME] = service
def initialize_services_deferred(self, context, is_first_time):
"""
@@ -325,7 +351,6 @@ class Sheerka(Concept):
ontologies = self.om.current_sdp().load_ontologies()
if not ontologies:
return
for ontology_name in list(reversed(ontologies))[1:]:
self.om.push_ontology(ontology_name, False)
self.initialize_services_deferred(context, False)
@@ -360,6 +385,10 @@ class Sheerka(Concept):
ret = self.execute(execution_context, [user_input, reduce_requested], EXECUTE_STEPS)
execution_context.add_values(return_values=ret)
# rule management
if self.enable_process_rules:
ret = self.execute_rules(execution_context, ret, RULES_EVALUATE_STEPS, RULES_EXECUTE_STEPS)
if self.om.is_dirty:
self.om.commit(execution_context)
@@ -388,10 +417,14 @@ class Sheerka(Concept):
:return:
"""
def new_instances(concepts):
def add_recognized_by(c, _recognized_by):
c.set_hint(BuiltinConcepts.RECOGNIZED_BY, _recognized_by)
return c
def new_instances(concepts, _recognized_by):
if hasattr(concepts, "__iter__"):
return [self.new_from_template(c, c.key) for c in concepts]
return self.new_from_template(concepts, concepts.key)
return [add_recognized_by(self.new_from_template(c, c.key), _recognized_by) for c in concepts]
return add_recognized_by(self.new_from_template(concepts, concepts.key), _recognized_by)
if concept is None:
return None
@@ -421,10 +454,11 @@ class Sheerka(Concept):
if self.is_known(found := self.get_by_id(concept[1])):
instance = self.new_from_template(found, found.key)
instance._metadata.is_evaluated = True
instance.set_hint(BuiltinConcepts.RECOGNIZED_BY, RECOGNIZED_BY_ID)
return instance
elif concept[0]:
if self.is_known(found := self.get_by_name(concept[0])):
instances = new_instances(found)
instances = new_instances(found, RECOGNIZED_BY_NAME)
core.builtin_helpers.set_is_evaluated(instances)
return instances
else:
@@ -433,17 +467,22 @@ class Sheerka(Concept):
# otherwise search in db
if isinstance(concept, str):
if self.is_known(found := self.get_by_name(concept)):
instances = new_instances(found)
instances = new_instances(found, RECOGNIZED_BY_NAME)
core.builtin_helpers.set_is_evaluated(instances, check_nb_variables=True)
return instances
return None
def fast_resolve(self, key, return_new=True):
def new_instances(concepts):
def add_recognized_by(c, _recognized_by):
c.set_hint(BuiltinConcepts.RECOGNIZED_BY, _recognized_by)
return c
def new_instances(concepts, _recognized_by):
if hasattr(concepts, "__iter__"):
return [self.new_from_template(c, c.key) for c in concepts]
return self.new_from_template(concepts, concepts.key)
return [add_recognized_by(self.new_from_template(c, c.key), _recognized_by) for c in concepts]
return add_recognized_by(self.new_from_template(concepts, concepts.key), _recognized_by)
if isinstance(key, Token):
if key.type == TokenKind.RULE: # do not recognize rules !!!
@@ -459,14 +498,17 @@ class Sheerka(Concept):
if key[1]:
concept = self.om.get(self.CONCEPTS_BY_ID_ENTRY, key[1])
recognized_by = RECOGNIZED_BY_ID
else:
concept = self.om.get(self.CONCEPTS_BY_NAME_ENTRY, key[0])
recognized_by = RECOGNIZED_BY_NAME
else:
concept = self.om.get(self.CONCEPTS_BY_NAME_ENTRY, key)
recognized_by = RECOGNIZED_BY_NAME
if concept is NotFound:
return None
return new_instances(concept) if return_new else concept
return new_instances(concept, recognized_by) if return_new else concept
def new(self, concept_key, **kwargs):
"""
@@ -478,6 +520,8 @@ class Sheerka(Concept):
"""
if isinstance(concept_key, tuple):
concept_key, concept_id = concept_key[0], concept_key[1]
elif isinstance(concept_key, Concept):
concept_key, concept_id = concept_key.key, concept_key.id
else:
concept_id = None
@@ -547,12 +591,13 @@ class Sheerka(Concept):
if name in self.om.current_sdp().load_ontologies():
self.initialize_services_deferred(context, False)
self.om.save_ontologies()
self.om.save_ontologies_names()
self.publish(context, EVENT_ONTOLOGY_CREATED, name)
return self.ret(self.name, True, self.new(BuiltinConcepts.SUCCESS))
def pop_ontology(self):
ontology = self.om.pop_ontology()
def pop_ontology(self, context):
ontology = self.om.pop_ontology(context)
self.om.reset_sheerka_state()
for service in self.services.values():
@@ -561,7 +606,7 @@ class Sheerka(Concept):
if hasattr(service, "reset_state"):
service.reset_state()
self.om.save_ontologies()
self.om.save_ontologies_names()
return self.ret(self.name, True, self.new(BuiltinConcepts.ONTOLOGY_REMOVED, body=ontology))
def get_ontology(self, context):
@@ -699,6 +744,13 @@ class Sheerka(Concept):
return bool(obj)
@staticmethod
def is_error(obj):
"""
opposite of is_success
"""
return not Sheerka.is_success(obj)
@staticmethod
def is_known(obj):
if not isinstance(obj, Concept):