Added SheerkaComparisonManager
This commit is contained in:
@@ -59,6 +59,8 @@ class BuiltinConcepts(Enum):
|
||||
CHICKEN_AND_EGG = "chicken and egg" # infinite recursion when declaring concept
|
||||
ISA = "is a" # builtin concept to express that a concept is an instance of another one
|
||||
EXPLANATION = "explanation"
|
||||
PRECEDENCE = "precedence" # use to set priority among concepts when parsing
|
||||
ASSOCIATIVITY = "associativity" # use to set priority among concepts when parsing
|
||||
|
||||
NODE = "node"
|
||||
GENERIC_NODE = "generic node"
|
||||
|
||||
+15
-5
@@ -342,20 +342,30 @@ class Concept:
|
||||
|
||||
return self
|
||||
|
||||
def add_prop(self, concept_key, value):
|
||||
def add_prop(self, property_name, value):
|
||||
"""
|
||||
Set or add a behaviour to a concept
|
||||
A behaviour is a value from another concept (ex BuiltinConcepts.ISA
|
||||
:param concept_key: Concept key
|
||||
:param property_name: Concept key
|
||||
:param value:
|
||||
:return:
|
||||
"""
|
||||
if concept_key in self.metadata.props:
|
||||
self.metadata.props[concept_key].add(value)
|
||||
if property_name in self.metadata.props:
|
||||
self.metadata.props[property_name].add(value)
|
||||
else:
|
||||
self.metadata.props[concept_key] = {value} # a set
|
||||
self.metadata.props[property_name] = {value} # a set
|
||||
return self
|
||||
|
||||
def set_prop(self, property_name, value):
|
||||
"""
|
||||
Set a behaviour to a concept
|
||||
A behaviour is a value from another concept (ex BuiltinConcepts.ISA
|
||||
:param property_name: Concept key
|
||||
:param value:
|
||||
:return:
|
||||
"""
|
||||
self.metadata.props[property_name] = value
|
||||
|
||||
def get_prop(self, concept_key):
|
||||
"""
|
||||
Gets a behaviour of a concept
|
||||
|
||||
@@ -3,7 +3,7 @@ import time
|
||||
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.concept import Concept
|
||||
from core.sheerka.Services.SheerkaExecute import NO_MATCH
|
||||
from core.sheerka.services.SheerkaExecute import NO_MATCH
|
||||
from core.sheerka_logger import get_logger
|
||||
from sdp.sheerkaDataProvider import Event
|
||||
|
||||
|
||||
+48
-10
@@ -12,14 +12,14 @@ from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConc
|
||||
UnknownConcept
|
||||
from core.concept import Concept, ConceptParts, PROPERTIES_FOR_NEW
|
||||
from core.sheerka.ExecutionContext import ExecutionContext
|
||||
from core.sheerka.Services.SheerkaCreateNewConcept import SheerkaCreateNewConcept
|
||||
from core.sheerka.Services.SheerkaDump import SheerkaDump
|
||||
from core.sheerka.Services.SheerkaEvaluateConcept import SheerkaEvaluateConcept
|
||||
from core.sheerka.Services.SheerkaExecute import SheerkaExecute
|
||||
from core.sheerka.Services.SheerkaHistoryManager import SheerkaHistoryManager
|
||||
from core.sheerka.Services.SheerkaModifyConcept import SheerkaModifyConcept
|
||||
from core.sheerka.Services.SheerkaSetsManager import SheerkaSetsManager
|
||||
from core.sheerka.Services.SheerkaVariableManager import SheerkaVariableManager
|
||||
from core.sheerka.services.SheerkaCreateNewConcept import SheerkaCreateNewConcept
|
||||
from core.sheerka.services.SheerkaDump import SheerkaDump
|
||||
from core.sheerka.services.SheerkaEvaluateConcept import SheerkaEvaluateConcept
|
||||
from core.sheerka.services.SheerkaExecute import SheerkaExecute
|
||||
from core.sheerka.services.SheerkaHistoryManager import SheerkaHistoryManager
|
||||
from core.sheerka.services.SheerkaModifyConcept import SheerkaModifyConcept
|
||||
from core.sheerka.services.SheerkaSetsManager import SheerkaSetsManager
|
||||
from core.sheerka.services.SheerkaVariableManager import SheerkaVariableManager
|
||||
from core.sheerka_logger import console_handler
|
||||
from printer.SheerkaPrinter import SheerkaPrinter
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event
|
||||
@@ -72,6 +72,8 @@ class Sheerka(Concept):
|
||||
self.sdp: SheerkaDataProvider = None # SheerkaDataProvider
|
||||
self.cache_manager = CacheManager(cache_only)
|
||||
|
||||
self.services = {} # sheerka plugins
|
||||
|
||||
self.builtin_cache = {} # cache for builtin concepts
|
||||
self.parsers = {} # cache for builtin parsers
|
||||
self.evaluators = [] # cache for builtin evaluators
|
||||
@@ -114,6 +116,20 @@ class Sheerka(Concept):
|
||||
def concepts_grammars(self):
|
||||
return self.cache_manager.caches[self.CONCEPTS_GRAMMARS_ENTRY].cache
|
||||
|
||||
def bind_service_method(self, instance, method, as_name=None):
|
||||
"""
|
||||
Bind service method to sheerka instance for ease to use
|
||||
:param instance:
|
||||
:param method:
|
||||
:param as_name:
|
||||
:return:
|
||||
"""
|
||||
if as_name is None:
|
||||
as_name = method.__name__
|
||||
|
||||
bound_method = method.__get__(instance, instance.__class__)
|
||||
setattr(self, as_name, bound_method)
|
||||
|
||||
def initialize(self, root_folder: str = None, save_execution_context=True):
|
||||
"""
|
||||
Starting Sheerka
|
||||
@@ -139,6 +155,7 @@ class Sheerka(Concept):
|
||||
if self.sdp.first_time:
|
||||
self.first_time_initialisation(exec_context)
|
||||
|
||||
self.initialize_services()
|
||||
self.initialize_builtin_parsers()
|
||||
self.initialize_builtin_evaluators()
|
||||
self.initialize_builtin_concepts()
|
||||
@@ -212,6 +229,21 @@ class Sheerka(Concept):
|
||||
self.cache_manager.put(self.CONCEPTS_KEYS_ENTRY, self.USER_CONCEPTS_KEYS, 1000)
|
||||
self.variable_handler.record(context, self.name, "save_execution_context", True)
|
||||
|
||||
def initialize_services(self):
|
||||
"""
|
||||
Introspect to find services and bind them
|
||||
:return:
|
||||
"""
|
||||
self.init_log.debug("Initializing services")
|
||||
|
||||
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
|
||||
|
||||
def initialize_builtin_concepts(self):
|
||||
"""
|
||||
Initializes the builtin concepts
|
||||
@@ -300,8 +332,14 @@ class Sheerka(Concept):
|
||||
self.cache_manager.put(self.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, res.body)
|
||||
|
||||
def reset(self, cache_only=False):
|
||||
self.cache_manager.clear()
|
||||
self.cache_manager.cache_only = cache_only
|
||||
if self.cache_manager.cache_only != cache_only:
|
||||
self.cache_manager.reset(cache_only)
|
||||
self.initialize_caching()
|
||||
for service in self.services.values():
|
||||
if hasattr(service, "initialize"):
|
||||
service.initialize()
|
||||
else:
|
||||
self.cache_manager.clear()
|
||||
self.printer_handler.reset()
|
||||
self.sdp.reset()
|
||||
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cache.Cache import Cache
|
||||
from cache.ListCache import ListCache
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.sheerka.services.sheerka_service import ServiceObj, BaseService
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComparisonObj(ServiceObj):
|
||||
"""
|
||||
Order to store
|
||||
"""
|
||||
property: str # property to compare
|
||||
a: int # id of concept a
|
||||
b: int # id of concept b
|
||||
op: str # comparison operation
|
||||
context: str = "#" # context when the comparison is right
|
||||
|
||||
|
||||
class SheerkaComparisonManager(BaseService):
|
||||
"""
|
||||
Manage partitioning of concepts
|
||||
"""
|
||||
NAME = "ComparisonManager"
|
||||
COMPARISON_ENTRY = "Comparison"
|
||||
RESOLVED_COMPARISON_ENTRY = "Resolved_Comparison"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
|
||||
@staticmethod
|
||||
def _compute_key(prop_name, comparison_context):
|
||||
return f"{prop_name}|{comparison_context}"
|
||||
|
||||
@staticmethod
|
||||
def _compute_weights(comparison_objs):
|
||||
"""
|
||||
For every element in greater_than_s, give it a weight
|
||||
if weight(a) > weight(b) it means that a > b
|
||||
:param comparison_objs: list of greater than objects
|
||||
:return:
|
||||
"""
|
||||
|
||||
values = {}
|
||||
for comparison_obj in comparison_objs:
|
||||
values[comparison_obj.a] = 1
|
||||
values[comparison_obj.b] = 1
|
||||
|
||||
for _ in range(len(comparison_objs)):
|
||||
for comparison_obj in comparison_objs:
|
||||
if comparison_obj.op == ">":
|
||||
values[comparison_obj.a] = values[comparison_obj.b] + 1
|
||||
else:
|
||||
values[comparison_obj.b] = values[comparison_obj.a] + 1
|
||||
|
||||
return values
|
||||
|
||||
@staticmethod
|
||||
def _get_partition(weighted_concepts):
|
||||
|
||||
res = {}
|
||||
for k, v in weighted_concepts.items():
|
||||
res.setdefault(v, []).append(k)
|
||||
return res
|
||||
|
||||
def _inner_add_comparison(self, comparison_obj):
|
||||
key = self._compute_key(comparison_obj.property, comparison_obj.context)
|
||||
previous = self.sheerka.cache_manager.get(self.COMPARISON_ENTRY, key)
|
||||
|
||||
new = previous.copy() if previous else []
|
||||
new.append(comparison_obj)
|
||||
|
||||
cycles = self.detect_cycles(new)
|
||||
if cycles:
|
||||
concepts_in_cycle = [self.sheerka.get_by_id(c) for c in cycles]
|
||||
chicken_an_egg = self.sheerka.new(BuiltinConcepts.CHICKEN_AND_EGG, body=concepts_in_cycle)
|
||||
return self.sheerka.ret(self.NAME, False, chicken_an_egg)
|
||||
|
||||
self.sheerka.cache_manager.put(self.RESOLVED_COMPARISON_ENTRY, key, self._compute_weights(new))
|
||||
self.sheerka.cache_manager.put(self.COMPARISON_ENTRY, key, comparison_obj)
|
||||
|
||||
return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
|
||||
|
||||
def initialize(self):
|
||||
cache = ListCache(default=lambda k: self.sheerka.sdp.get(self.COMPARISON_ENTRY, k))
|
||||
self.sheerka.cache_manager.register_cache(self.COMPARISON_ENTRY, cache, True, True)
|
||||
|
||||
cache = Cache()
|
||||
self.sheerka.cache_manager.register_cache(self.RESOLVED_COMPARISON_ENTRY, cache, persist=False)
|
||||
|
||||
self.sheerka.bind_service_method(self, SheerkaComparisonManager.is_greater_than)
|
||||
self.sheerka.bind_service_method(self, SheerkaComparisonManager.is_less_than)
|
||||
self.sheerka.bind_service_method(self, SheerkaComparisonManager.get_partition)
|
||||
self.sheerka.bind_service_method(self, SheerkaComparisonManager.get_concepts_weights)
|
||||
|
||||
def is_greater_than(self, context, prop_name, concept_a, concept_b, comparison_context="#"):
|
||||
"""
|
||||
Records that the property of concept a is greater than concept b's one
|
||||
:param context:
|
||||
:param prop_name:
|
||||
:param concept_a:
|
||||
:param concept_b:
|
||||
:param comparison_context:
|
||||
:return:
|
||||
"""
|
||||
context.log(f"Setting concept {concept_a} is greater than {concept_b}", who=self.NAME)
|
||||
|
||||
event_digest = context.event.get_digest()
|
||||
comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, ">", comparison_context)
|
||||
return self._inner_add_comparison(comparison_obj)
|
||||
|
||||
def is_less_than(self, context, prop_name, concept_a, concept_b, comparison_context="#"):
|
||||
"""
|
||||
Records that the property of concept a is lesser than concept b's one
|
||||
:param context:
|
||||
:param prop_name:
|
||||
:param concept_a:
|
||||
:param concept_b:
|
||||
:param comparison_context:
|
||||
:return:
|
||||
"""
|
||||
context.log(f"Setting concept {concept_a} is less than {concept_b}", who=self.NAME)
|
||||
|
||||
event_digest = context.event.get_digest()
|
||||
comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, "<", comparison_context)
|
||||
return self._inner_add_comparison(comparison_obj)
|
||||
|
||||
def get_partition(self, prop_name, comparison_context="#"):
|
||||
weighted_concept = self.get_concepts_weights(prop_name, comparison_context)
|
||||
|
||||
return self._get_partition(weighted_concept)
|
||||
|
||||
def get_concepts_weights(self, prop_name, comparison_context="#"):
|
||||
weighted_concept = self.sheerka.cache_manager.get(
|
||||
self.RESOLVED_COMPARISON_ENTRY,
|
||||
self._compute_key(prop_name, comparison_context))
|
||||
|
||||
if weighted_concept is None:
|
||||
key = self._compute_key(prop_name, comparison_context)
|
||||
entries = self.sheerka.cache_manager.get(self.COMPARISON_ENTRY, key)
|
||||
|
||||
if entries is None:
|
||||
return {}
|
||||
else:
|
||||
weighted_concept = self._compute_weights(entries)
|
||||
self.sheerka.cache_manager.put(self.RESOLVED_COMPARISON_ENTRY, key, weighted_concept)
|
||||
|
||||
return weighted_concept
|
||||
|
||||
@staticmethod
|
||||
def detect_cycles(comparison_objs):
|
||||
"""
|
||||
# Thanks to Divyanshu Mehta for contributing this code
|
||||
# https://www.geeksforgeeks.org/detect-cycle-in-a-graph/?ref=lbp
|
||||
:param comparison_objs:
|
||||
:return:
|
||||
"""
|
||||
latest = comparison_objs[-1]
|
||||
if latest.op == "=":
|
||||
return None
|
||||
|
||||
def get_graph_and_vertices():
|
||||
_graph = {}
|
||||
_vertices = set()
|
||||
for obj in comparison_objs:
|
||||
if obj.op == "=":
|
||||
continue
|
||||
|
||||
_vertices.add(obj.a)
|
||||
_vertices.add(obj.b)
|
||||
if obj.op == ">":
|
||||
_graph.setdefault(obj.a, []).append(obj.b)
|
||||
else:
|
||||
_graph.setdefault(obj.b, []).append(obj.a)
|
||||
return _graph, _vertices
|
||||
|
||||
def is_cyclic(v):
|
||||
# Mark current node as visited and
|
||||
# adds to recursion stack
|
||||
visited[v] = True
|
||||
rec_stack[v] = True
|
||||
|
||||
# Recur for all neighbours
|
||||
# if any neighbour is visited and in
|
||||
# recStack then graph is cyclic
|
||||
if v in graph:
|
||||
for neighbour in graph[v]:
|
||||
if not visited[neighbour]:
|
||||
if is_cyclic(neighbour):
|
||||
return True
|
||||
elif rec_stack[neighbour]:
|
||||
return True
|
||||
|
||||
# The node needs to be poped from
|
||||
# recursion stack before function ends
|
||||
rec_stack[v] = False
|
||||
return False
|
||||
|
||||
graph, vertices = get_graph_and_vertices()
|
||||
visited = {k: False for k in vertices}
|
||||
rec_stack = {k: False for k in vertices}
|
||||
|
||||
if is_cyclic(latest.a): # only need to check from the latest add, since the graph was not cyclic before
|
||||
return [k for k, v in rec_stack.items() if v]
|
||||
return None
|
||||
+1
-1
@@ -14,7 +14,7 @@ class SheerkaCreateNewConcept:
|
||||
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
self.logger_name = self.create_new_concept.__name__
|
||||
self.logger_name = "CreateNewConcept"
|
||||
self.bnp = core.utils.get_class(BASE_NODE_PARSER_CLASS) # BaseNodeParser
|
||||
|
||||
def create_new_concept(self, context, concept: Concept):
|
||||
+4
-4
@@ -11,7 +11,7 @@ CONCEPT_EVALUATION_STEPS = [
|
||||
class SheerkaEvaluateConcept:
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
self.logger_name = self.evaluate_concept.__name__
|
||||
self.logger_name = "EvaluateConcept"
|
||||
|
||||
@staticmethod
|
||||
def infinite_recursion_detected(context, concept):
|
||||
@@ -88,7 +88,7 @@ class SheerkaEvaluateConcept:
|
||||
if source.strip() == "":
|
||||
concept.compiled[part_key] = DoNotResolve(source)
|
||||
else:
|
||||
with context.push(desc=f"Initializing compiled for {part_key}") as sub_context:
|
||||
with context.push(desc=f"Initializing *compiled* for {part_key}") as sub_context:
|
||||
sub_context.add_inputs(source=source)
|
||||
to_parse = self.sheerka.ret(context.who, True,
|
||||
self.sheerka.new(BuiltinConcepts.USER_INPUT, body=source))
|
||||
@@ -107,11 +107,11 @@ class SheerkaEvaluateConcept:
|
||||
if default_value.strip() == "":
|
||||
concept.compiled[var_name] = DoNotResolve(default_value)
|
||||
else:
|
||||
with context.push(desc=f"Initializing AST for property {var_name}") as sub_context:
|
||||
with context.push(desc=f"Initializing *compiled* for property {var_name}") as sub_context:
|
||||
sub_context.add_inputs(source=default_value)
|
||||
to_parse = self.sheerka.ret(context.who, True,
|
||||
self.sheerka.new(BuiltinConcepts.USER_INPUT, body=default_value))
|
||||
res = self.sheerka.execute(context, to_parse, steps)
|
||||
res = self.sheerka.execute(sub_context, to_parse, steps)
|
||||
only_success = only_successful(sub_context, res)
|
||||
concept.compiled[var_name] = only_success.body.body if is_only_successful(only_success) else res
|
||||
sub_context.add_values(return_values=res)
|
||||
+1
-1
@@ -4,7 +4,7 @@ from core.builtin_concepts import BuiltinConcepts
|
||||
class SheerkaModifyConcept:
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
self.logger_name = self.modify_concept.__name__
|
||||
self.logger_name = "ModifyConcept"
|
||||
|
||||
def modify_concept(self, context, concept):
|
||||
old_version = self.sheerka.get_by_id(concept.id)
|
||||
+1
-1
@@ -9,7 +9,7 @@ GROUP_PREFIX = 'All_'
|
||||
class SheerkaSetsManager:
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
self.logger_name = self.add_concept_to_set.__name__
|
||||
self.logger_name = "SetsManager"
|
||||
|
||||
def set_isa(self, context, concept, concept_set):
|
||||
"""
|
||||
+6
-5
@@ -1,20 +1,21 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from core.sheerka.services.sheerka_service import ServiceObj
|
||||
|
||||
|
||||
@dataclass
|
||||
class Variable:
|
||||
class Variable(ServiceObj):
|
||||
"""
|
||||
Variable to store
|
||||
"""
|
||||
event_id: str # event where the variable is modified
|
||||
who: str # who is the modifier
|
||||
key: str # key of the variable
|
||||
value: object # value
|
||||
parents: List[str] # previous references of the variable (Note that there should be only one parent)
|
||||
|
||||
def get_key(self):
|
||||
return f"{self.who}.{self.key}"
|
||||
return f"{self.who}|{self.key}"
|
||||
|
||||
|
||||
class SheerkaVariableManager:
|
||||
@@ -36,11 +37,11 @@ class SheerkaVariableManager:
|
||||
self.sheerka.cache_manager.put(self.sheerka.VARIABLES_ENTRY, variable.get_key(), variable)
|
||||
|
||||
def load(self, who, key):
|
||||
variable = self.sheerka.cache_manager.get(self.sheerka.VARIABLES_ENTRY, who + "." + key)
|
||||
variable = self.sheerka.cache_manager.get(self.sheerka.VARIABLES_ENTRY, who + "|" + key)
|
||||
if variable is None:
|
||||
return None
|
||||
|
||||
return variable.value
|
||||
|
||||
def delete(self, context, who, key):
|
||||
self.sheerka.cache_manager.delete(self.sheerka.VARIABLES_ENTRY, who + "." + key)
|
||||
self.sheerka.cache_manager.delete(self.sheerka.VARIABLES_ENTRY, who + "|" + key)
|
||||
@@ -0,0 +1,14 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceObj:
|
||||
event_id: str # event where the object is created / modified
|
||||
|
||||
|
||||
class BaseService:
|
||||
"""
|
||||
Base class for services
|
||||
"""
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
+28
-1
@@ -29,6 +29,14 @@ def sysarg_to_string(argv):
|
||||
return result
|
||||
|
||||
|
||||
def get_all_loaded_modules(prefix):
|
||||
import sys
|
||||
if prefix:
|
||||
return [m for m in sys.modules.keys() if m.startswith(prefix)]
|
||||
else:
|
||||
return sys.modules.keys()
|
||||
|
||||
|
||||
def get_class(qname):
|
||||
"""
|
||||
Loads a class from its full qualified name
|
||||
@@ -117,12 +125,31 @@ def get_classes_from_package(package_name):
|
||||
def init_package_import(package_name):
|
||||
pkg = __import__(package_name)
|
||||
prefix = pkg.__name__ + "."
|
||||
# prefix = package_name + "."
|
||||
for (module_loader, name, ispkg) in pkgutil.iter_modules(pkg.__path__, prefix):
|
||||
importlib.import_module(name)
|
||||
importlib.import_module(package_name)
|
||||
|
||||
|
||||
def import_module_and_sub_module(module_name):
|
||||
"""
|
||||
Import the module, and one sub level
|
||||
:param module_name:
|
||||
:return:
|
||||
"""
|
||||
mod = get_module(module_name)
|
||||
for (module_loader, name, ispkg) in pkgutil.iter_modules(mod.__path__, module_name + "."):
|
||||
importlib.import_module(name)
|
||||
|
||||
|
||||
def get_sub_classes(package_name, base_class):
|
||||
base_class = get_class(base_class) if isinstance(base_class, str) else base_class
|
||||
def _get_class(name):
|
||||
modname, _, clsname = name.rpartition('.')
|
||||
mod = importlib.import_module(modname)
|
||||
cls = getattr(mod, clsname)
|
||||
return cls
|
||||
|
||||
base_class = _get_class(base_class) if isinstance(base_class, str) else base_class
|
||||
all_class = set(base_class.__subclasses__()).union(
|
||||
[s for c in base_class.__subclasses__() for s in get_sub_classes(package_name, c)])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user