Introduced ConceptsAlgebra
This commit is contained in:
@@ -53,6 +53,7 @@ class BuiltinConcepts(Enum):
|
||||
|
||||
# builtin attributes
|
||||
ISA = "is a" # when a concept is an instance of another one
|
||||
HASA = "has a" # when a concept has/owns another concept
|
||||
AUTO_EVAL = "auto eval" # when the concept must be auto evaluated
|
||||
|
||||
# object
|
||||
@@ -74,7 +75,8 @@ class BuiltinConcepts(Enum):
|
||||
IS_EMPTY = "is empty" # when a set is empty
|
||||
NO_RESULT = "no result" # no return value returned
|
||||
INVALID_RETURN_VALUE = "invalid return value" # the return value of an evaluator is not correct
|
||||
ALREADY_DEFINED = "already defined" # when you try to add the same object twice (a concept or whatever)
|
||||
CONCEPT_ALREADY_DEFINED = "concept already defined" # when you try to add the same object twice (a concept or whatever)
|
||||
PROPERTY_ALREADY_DEFINED = "property already defined" # When you try to add the same element in a property
|
||||
NOP = "no operation" # no operation concept. Does nothing
|
||||
CONCEPT_EVAL_ERROR = "concept evaluation error" # cannot evaluate a property or metadata of a concept
|
||||
ENUMERATION = "enum" # represents a list or a set
|
||||
@@ -171,7 +173,8 @@ BuiltinErrors = [str(e) for e in {
|
||||
BuiltinConcepts.TOO_MANY_ERRORS,
|
||||
BuiltinConcepts.MULTIPLE_ERRORS,
|
||||
BuiltinConcepts.INVALID_RETURN_VALUE,
|
||||
BuiltinConcepts.ALREADY_DEFINED,
|
||||
BuiltinConcepts.CONCEPT_ALREADY_DEFINED,
|
||||
BuiltinConcepts.PROPERTY_ALREADY_DEFINED,
|
||||
BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
||||
BuiltinConcepts.CONCEPT_ALREADY_IN_SET,
|
||||
BuiltinConcepts.NOT_A_SET,
|
||||
@@ -479,6 +482,33 @@ class ConceptAlreadyInSet(Concept):
|
||||
return self.get_value("concept_set")
|
||||
|
||||
|
||||
class PropertyAlreadyDefined(Concept):
|
||||
def __init__(self, property_name=None, property_value=None, concept=None):
|
||||
super().__init__(BuiltinConcepts.PROPERTY_ALREADY_DEFINED,
|
||||
True,
|
||||
False,
|
||||
BuiltinConcepts.PROPERTY_ALREADY_DEFINED)
|
||||
self.set_value(ConceptParts.BODY, property_name)
|
||||
self.set_value("property_value", property_value)
|
||||
self.set_value("concept", concept)
|
||||
self.metadata.is_evaluated = True
|
||||
|
||||
def __repr__(self):
|
||||
return f"PropertyAlreadyDefined(property={self.property_name}, value={self.property_value}, concept={self.concept})"
|
||||
|
||||
@property
|
||||
def property_name(self):
|
||||
return self.body
|
||||
|
||||
@property
|
||||
def property_value(self):
|
||||
return self.get_value("property_value")
|
||||
|
||||
@property
|
||||
def concept(self):
|
||||
return self.get_value("concept")
|
||||
|
||||
|
||||
class ConditionFailed(Concept):
|
||||
def __init__(self, condition=None, concept=None, prop=None):
|
||||
super().__init__(BuiltinConcepts.CONDITION_FAILED,
|
||||
|
||||
+16
-16
@@ -565,7 +565,7 @@ class Sheerka(Concept):
|
||||
return c
|
||||
|
||||
metadata = [(index_name, key), ("id", concept_id)] if concept_id else (index_name, key)
|
||||
return self._get_unknown(metadata)
|
||||
return self.get_unknown(metadata)
|
||||
|
||||
def resolve(self, concept):
|
||||
"""
|
||||
@@ -827,20 +827,20 @@ class Sheerka(Concept):
|
||||
|
||||
return self.parsers_prefix + name
|
||||
|
||||
def concepts(self):
|
||||
"""
|
||||
List of all known concepts (look up in sdp)
|
||||
:return:
|
||||
"""
|
||||
res = []
|
||||
lst = self.sdp.list(self.CONCEPTS_BY_ID_ENTRY)
|
||||
for item in lst:
|
||||
if isinstance(item, list):
|
||||
res.extend(item)
|
||||
else:
|
||||
res.append(item)
|
||||
|
||||
return sorted(res, key=lambda i: int(i.id))
|
||||
# def concepts(self):
|
||||
# """
|
||||
# List of all known concepts (look up in sdp)
|
||||
# :return:
|
||||
# """
|
||||
# res = []
|
||||
# lst = self.sdp.list(self.CONCEPTS_BY_ID_ENTRY)
|
||||
# for item in lst:
|
||||
# if isinstance(item, list):
|
||||
# res.extend(item)
|
||||
# else:
|
||||
# res.append(item)
|
||||
#
|
||||
# return sorted(res, key=lambda i: int(i.id))
|
||||
|
||||
def get_last_execution(self):
|
||||
return self._last_execution
|
||||
@@ -896,7 +896,7 @@ class Sheerka(Concept):
|
||||
return a.key == b_key
|
||||
|
||||
@staticmethod
|
||||
def _get_unknown(metadata):
|
||||
def get_unknown(metadata):
|
||||
"""
|
||||
Returns the concept 'UnknownConcept' for a requested id or key
|
||||
Note that I don't call the new() method to prevent cyclic call
|
||||
|
||||
@@ -139,7 +139,7 @@ class SheerkaComparisonManager(BaseService):
|
||||
co.b == comparison_obj.b and \
|
||||
co.op == comparison_obj.op and \
|
||||
co.context == comparison_obj.context:
|
||||
return self.sheerka.ret(self.NAME, False, self.sheerka.new(BuiltinConcepts.ALREADY_DEFINED))
|
||||
return self.sheerka.ret(self.NAME, False, self.sheerka.new(BuiltinConcepts.CONCEPT_ALREADY_DEFINED))
|
||||
|
||||
new.append(comparison_obj)
|
||||
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
from dataclasses import dataclass
|
||||
from operator import attrgetter
|
||||
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.concept import Concept, ensure_concept
|
||||
from core.sheerka.Sheerka import Sheerka
|
||||
from core.sheerka.services.sheerka_service import BaseService
|
||||
|
||||
PROPERTIES_TO_COMPUTE = [BuiltinConcepts.ISA, BuiltinConcepts.HASA]
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConceptScore:
|
||||
"""
|
||||
Concept a score, relative to concept b
|
||||
"""
|
||||
score: float
|
||||
a: Concept
|
||||
b: Concept
|
||||
|
||||
|
||||
class SheerkaConceptsAlgebra(BaseService):
|
||||
NAME = "ConceptsAlgebra"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
|
||||
def initialize(self):
|
||||
self.sheerka.bind_service_method(self.cadd, False)
|
||||
self.sheerka.bind_service_method(self.csub, False)
|
||||
self.sheerka.bind_service_method(self.recognize, False)
|
||||
|
||||
def cadd(self, context, *concepts):
|
||||
"""
|
||||
Concepts addition
|
||||
Returns a concept with the union of some properties
|
||||
:param context:
|
||||
:param concepts:
|
||||
:return:
|
||||
"""
|
||||
|
||||
ensure_concept(*concepts)
|
||||
|
||||
res = Concept()
|
||||
for c in concepts:
|
||||
for prop in PROPERTIES_TO_COMPUTE:
|
||||
self.add_props(res, c, prop)
|
||||
|
||||
return res
|
||||
|
||||
def csub(self, context, *concepts):
|
||||
"""
|
||||
Concepts subtraction
|
||||
returns a concept where the properties of the first concept are removed if they appear in the other concepts
|
||||
:param context:
|
||||
:param concepts:
|
||||
:return:
|
||||
"""
|
||||
|
||||
res = Concept()
|
||||
ensure_concept(*concepts)
|
||||
|
||||
if len(concepts) == 0:
|
||||
return res # ? really
|
||||
|
||||
# init res
|
||||
for prop in PROPERTIES_TO_COMPUTE:
|
||||
self.add_props(res, concepts[0], prop)
|
||||
|
||||
for concept in concepts[1:]:
|
||||
for prop in PROPERTIES_TO_COMPUTE:
|
||||
self.sub_props(res, concept, prop)
|
||||
|
||||
return res
|
||||
|
||||
def add_props(self, destination, source, key):
|
||||
"""
|
||||
Add prop 'key', from source to destination
|
||||
:param destination:
|
||||
:param source:
|
||||
:param key:
|
||||
:return:
|
||||
"""
|
||||
if key not in source.metadata.props:
|
||||
return
|
||||
|
||||
if key in destination.metadata.props:
|
||||
destination.metadata.props[key].update(source.metadata.props[key])
|
||||
else:
|
||||
destination.metadata.props[key] = source.metadata.props[key].copy()
|
||||
|
||||
def sub_props(self, destination, source, key):
|
||||
"""
|
||||
Remove the property from destination if it exists in source
|
||||
:param destination:
|
||||
:param source:
|
||||
:param key:
|
||||
:return:
|
||||
"""
|
||||
if key not in source.metadata.props or key not in destination.metadata.props:
|
||||
return
|
||||
|
||||
for item in source.metadata.props[key]:
|
||||
destination.metadata.props[key].discard(item)
|
||||
|
||||
def recognize(self, concept, all_scores=False):
|
||||
"""
|
||||
Go thru all known concepts and try to recognize the properties
|
||||
:param concept:
|
||||
:param all_scores: returns all positive scores if true, returns the top one otherwise
|
||||
:return:
|
||||
"""
|
||||
|
||||
res = []
|
||||
|
||||
nb_props = self._count_props_values(concept)
|
||||
if nb_props == 0:
|
||||
return res
|
||||
|
||||
all_concepts = self.sheerka.cache_manager.copy(Sheerka.CONCEPTS_BY_ID_ENTRY).values() \
|
||||
if self.sheerka.cache_manager.cache_only else self.sheerka.concepts()
|
||||
|
||||
for c in all_concepts:
|
||||
score = self._compute_score(c, concept, step_b=round(1 / nb_props, 2))
|
||||
if score > 0:
|
||||
res.append(ConceptScore(score, c, concept))
|
||||
|
||||
if len(res) == 0:
|
||||
props = []
|
||||
for p in [p for p in PROPERTIES_TO_COMPUTE if p in concept.metadata.props]:
|
||||
props.append((p, concept.metadata.props[p]))
|
||||
return self.sheerka.get_unknown(props)
|
||||
|
||||
res.sort(key=attrgetter('score'), reverse=True)
|
||||
if all_scores:
|
||||
return res
|
||||
|
||||
to_keep = [res[0]]
|
||||
max_score = res[0].score
|
||||
i = 1
|
||||
while i < len(res) and res[i].score == max_score:
|
||||
to_keep.append(res[i])
|
||||
i += 1
|
||||
|
||||
return to_keep if len(to_keep) > 1 else to_keep[0]
|
||||
|
||||
@staticmethod
|
||||
def _compute_score(a, b, step_a=None, step_b=None):
|
||||
"""
|
||||
Compute a's score, compared to b properties
|
||||
:param a:
|
||||
:param b:
|
||||
:param step_a:
|
||||
:param step_b:
|
||||
:return:
|
||||
"""
|
||||
score = 0
|
||||
|
||||
# adds step_b for every property that are in both a and b
|
||||
for prop in PROPERTIES_TO_COMPUTE:
|
||||
if prop in b.metadata.props and prop in a.metadata.props:
|
||||
for prop_value in b.metadata.props[prop]:
|
||||
if prop_value in a.metadata.props[prop]:
|
||||
score += step_b
|
||||
|
||||
if not step_a:
|
||||
concept_a_nb_props = SheerkaConceptsAlgebra._count_props_values(a)
|
||||
if concept_a_nb_props == 0:
|
||||
return score
|
||||
step_b = round(1.0 / concept_a_nb_props, 2)
|
||||
|
||||
# remove step_a for every property that is in a, but not in b
|
||||
for prop in PROPERTIES_TO_COMPUTE:
|
||||
if prop in a.metadata.props and prop not in a.metadata.props:
|
||||
score += step_a * len(a.metadata.props)
|
||||
elif prop in a.metadata.props and prop in a.metadata.props:
|
||||
for prop_value in a.metadata.props[prop]:
|
||||
if prop_value not in b.metadata.props[prop]:
|
||||
score -= step_b
|
||||
|
||||
return score
|
||||
|
||||
@staticmethod
|
||||
def _count_props_values(concept):
|
||||
"""
|
||||
Count the number of values
|
||||
:param concept:
|
||||
:return:
|
||||
"""
|
||||
nb_props = 0
|
||||
for prop in PROPERTIES_TO_COMPUTE:
|
||||
if prop in concept.metadata.props:
|
||||
nb_props += len(concept.metadata.props[prop])
|
||||
return nb_props
|
||||
@@ -44,7 +44,7 @@ class SheerkaCreateNewConcept(BaseService):
|
||||
return sheerka.ret(
|
||||
self.NAME,
|
||||
False,
|
||||
sheerka.new(BuiltinConcepts.ALREADY_DEFINED, body=concept),
|
||||
sheerka.new(BuiltinConcepts.CONCEPT_ALREADY_DEFINED, body=concept),
|
||||
error.args[0])
|
||||
|
||||
# set id before saving in db
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.concept import ensure_concept
|
||||
from core.sheerka.services.sheerka_service import BaseService
|
||||
|
||||
|
||||
class SheerkaHasAManager(BaseService):
|
||||
NAME = "HasAManager"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
|
||||
def initialize(self):
|
||||
self.sheerka.bind_service_method(self.set_hasa, True)
|
||||
self.sheerka.bind_service_method(self.hasa, True)
|
||||
|
||||
def set_hasa(self, context, concept_a, concept_b):
|
||||
"""
|
||||
Defines that concept has/owns another concept
|
||||
:param context:
|
||||
:param concept_a:
|
||||
:param concept_b:
|
||||
:return:
|
||||
"""
|
||||
|
||||
context.log(f"Setting concept {concept_a} has a {concept_b}", who=self.NAME)
|
||||
ensure_concept(concept_a, concept_b)
|
||||
|
||||
if (BuiltinConcepts.HASA in concept_a.metadata.props and
|
||||
concept_b in concept_a.metadata.props[BuiltinConcepts.HASA]):
|
||||
return self.sheerka.ret(
|
||||
self.NAME,
|
||||
False,
|
||||
self.sheerka.new(BuiltinConcepts.PropertyAlreadyDefined,
|
||||
body=concept_b,
|
||||
name=BuiltinConcepts.HASA,
|
||||
concept=concept_a))
|
||||
|
||||
concept_a.add_prop(BuiltinConcepts.HASA, concept_b)
|
||||
|
||||
return self.sheerka.modify_concept(context, concept_a)
|
||||
|
||||
def hasa(self, concept_a, concept_b):
|
||||
"""
|
||||
Check that concept 'a' has/owns concept 'b'
|
||||
:param context:
|
||||
:param concept_a:
|
||||
:param concept_b:
|
||||
:return:
|
||||
"""
|
||||
|
||||
ensure_concept(concept_a, concept_b)
|
||||
return (BuiltinConcepts.HASA in concept_a.metadata.props and
|
||||
concept_b in concept_a.metadata.props[BuiltinConcepts.HASA])
|
||||
@@ -44,7 +44,7 @@ class SheerkaModifyConcept(BaseService):
|
||||
return self.sheerka.ret(
|
||||
self.NAME, False,
|
||||
self.sheerka.new(
|
||||
BuiltinConcepts.ALREADY_DEFINED,
|
||||
BuiltinConcepts.CONCEPT_ALREADY_DEFINED,
|
||||
body=concept))
|
||||
|
||||
self.sheerka.cache_manager.update_concept(old_version, concept)
|
||||
|
||||
@@ -197,7 +197,7 @@ class SheerkaSetsManager(BaseService):
|
||||
for c in a.metadata.props[BuiltinConcepts.ISA]:
|
||||
if c == b:
|
||||
return True
|
||||
if self.isa(c, b):
|
||||
if self.isa(self.sheerka.get_by_id(c.id), b):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user