Introduced ConceptsAlgebra

This commit is contained in:
2020-09-27 20:28:50 +02:00
parent 978e5a5939
commit d100b7e8b3
18 changed files with 541 additions and 50 deletions
@@ -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