def gen(self, store: Store) -> Iterable[Concept]: keep_searching = True while keep_searching: keep_searching = False # todo: do not explicitly list() items (need to handle concurrent store modifications) classes = list( filter(lambda c: c.relation == Relation.Class, store.concepts())) for class_concept in classes: # type: Concept source = class_concept.parents[0] target = class_concept.parents[1] logger.debug("Using class concept %s" % class_concept) target_children = collect_children(target) mapping = {} for tc in target_children: # type: Concept clone_concept_with_replacing_parent( tc, mapping, target, source) for template, clone in mapping.items(): if store.get_concept(clone) is None: keep_searching = True logger.debug("Generated concept %s" % clone) yield store.integrate(clone)
def test_concept_storing(self): s = Store() s.add_concept(1) s.add_concept(2) l = set(s.concepts()) self.assertEqual(l, {1, 2})
def test_concept_iterator_includes_parent_concepts(self): parent = Store() child = Store(parent) parent.add_concept(Concept.word("p")) child.add_concept(Concept.word("c")) l = set(map(lambda c: c.name, child.concepts())) self.assertEqual(l, {"p", "c"})
def graph(store: Store, words: bool = False, simplify: bool = True): s = "{ nodes: [" if words: for word in store.words(): s += "\n" + node(id(word), word.word, 50, '#F5A45D', 'octagon') for concept in store.concepts(): if concept.relation == Relation.Word: s += "\n" + node(id(concept), concept.name, 50, "#6FB1FC", 'ellipse') else: if not simplify or not is_simple_2arg_concept(concept): l = concept.name if l is None: l = str(concept) s += "\n" + node(id(concept), l, 50, "#86B342", 'triangle') s += "], edges: [" if words: for word in store.words(): for meaning in word.meanings(): s += "\n" + edge(id(word), id(meaning), None, "#6FB1FC", 10) for concept in store.concepts(): if simplify and is_simple_2arg_concept(concept): p = '' if concept.probability < 1.0: p = ' (p=' + str(concept.probability) + ')' s += "\n" + edge(id(concept.parents[0]), id(concept.parents[1]), concept.relation.name + p, "#EDA1ED", 50) else: for parent in concept.parents: s += "\n" + edge(id(concept), id(parent), None, "#EDA1ED", 50) s += "] }" return s
class NLU: def __init__(self): self.working_memory = Store(label='WM') self.question_store = Store(self.working_memory, label='Question') self.concepts = [] # type: list[Concept] self.text_parser = SpacyTranslator() def is_mentalase(self, expression: str): if '(' in expression and ')' in expression: return True if ' ' in expression: return False #single word return True def integrate(self, expression: str, label: str = None, probability: float = 1.0) -> List[Concept]: # for now, only reset question_store when new statements are integrated into the working memory self.question_store = Store(self.working_memory, label='Question') parsed = [] if self.is_mentalase(expression): concept = parse(expression, self.working_memory, label, probability) parsed.append(concept) self.working_memory.integrate(concept) else: for concept in self.text_parser.parse(expression): parsed.append(concept) self.working_memory.integrate(concept) return parsed def ask( self, question: str ) -> (Concept, Optional[Mapping[Concept, Concept]], Optional[Concept]): if self.is_mentalase(question): q = parse(question, self.question_store) # type: Concept else: q = next(self.text_parser.parse(question)) for concept in self.working_memory.concepts(): # type: Concept match = concept.matches(q) if match is not None: return q, match, concept # todo: handle available generators in a more generic way generator = InheritFromParentClass() for concept in generator.gen(self.question_store): # type: Concept match = concept.matches(q) if match is not None and abs(concept.probability - 0.5) > 0.0: return q, match, concept # another pass at all generated concepts to see if result was generated as part of larger compound concept # or, probability of result changed during generating concepts # todo: replace this with the ability to mark processed items in the store to be more efficient for concept in self.question_store.own_concepts(): # type: Concept match = concept.matches(q) if match is not None: return q, match, concept return q, None, None @staticmethod def create_answer(question: Concept, mapping: Mapping[Concept, Concept]) -> Concept: if mapping is None: return None # simple if question.is_simple(): if question in mapping: return mapping[question] return question # compound ap = [] # type: list[Concept] for p in question.parents: ap.append(NLU.create_answer(p, mapping)) return Concept(question.name, question.relation, ap) def ask_question(self, question: str): parsed, match, matched_concept = self.ask(question) answer = self.create_answer(parsed, match) print("Question: %s" % parsed) direct = str(answer) p = '' if matched_concept is not None: p = '(p=%r)' % matched_concept.probability print("Answer: %s %s" % (direct, p)) if matched_concept is not None: full = str(matched_concept) if direct != full: print("Full answer: %s" % full)