diff --git a/pytholog/knowledge_base.py b/pytholog/knowledge_base.py index cd5bc88..2a60cc1 100644 --- a/pytholog/knowledge_base.py +++ b/pytholog/knowledge_base.py @@ -10,7 +10,7 @@ ## the knowledge base object where we will store the facts and rules ## it's a dictionary of dictionaries where main keys are the predicates -## to speed up searching by looking only into relevant buckets rather than looping over +## to speed up searching by looking only into relevant buckets rather than looping over ## the whole database class KnowledgeBase(object): __id = 0 @@ -22,7 +22,7 @@ def __init__(self, name = None): KnowledgeBase.__id += 1 self.name = name self._cache = {} - + ## the main function that adds new entries or append existing ones ## it creates "facts", "goals" and "terms" buckets for each predicate def add_kn(self, kn): @@ -45,15 +45,15 @@ def add_kn(self, kn): self.db[i.lh.predicate]["terms"].push(i.terms) #self.db[i.lh.predicate]["goals"] = [g] #self.db[i.lh.predicate]["terms"] = [i.terms] - + def __call__(self, args): self.add_kn(args) ## query method will only call rule_query which will call the decorators chain - ## it is only to be user intuitive readable method - def query(self, expr, cut = False, show_path = False): - return rule_query(self, expr, cut, show_path) - + ## it is only to be user intuitive readable method + def query(self, expr, cut = False, show_path = False, cache = True): + return rule_query(self, expr, cut, show_path, cache=cache) + def rule_search(self, expr): if expr.predicate not in self.db: return "Rule does not exist!" @@ -70,12 +70,12 @@ def from_file(self, file): def __str__(self): return "KnowledgeBase: " + self.name - + def clear_cache(self): self._cache.clear() __repr__ = __str__ - + class DeprecationHelper(object): def __init__(self, new_target): @@ -94,4 +94,3 @@ def __getattr__(self, attr): return getattr(self.new_target, attr) knowledge_base = DeprecationHelper(KnowledgeBase) - \ No newline at end of file diff --git a/pytholog/querizer.py b/pytholog/querizer.py index 8479628..9efdd87 100644 --- a/pytholog/querizer.py +++ b/pytholog/querizer.py @@ -12,11 +12,11 @@ def memory(querizer): #cache = {} @wraps(querizer) - def memorize_query(kb, arg1, cut, show_path): + def memorize_query(kb, arg1, cut, show_path, cache=True): temp_cache = {} #original, look_up = term_checker(arg1) indx, look_up = term_checker(arg1) - if look_up in kb._cache: + if look_up in kb._cache and cache: #return cache[look_up] temp_cache = kb._cache[look_up] ## if it already exists return it else: @@ -31,8 +31,8 @@ def memorize_query(kb, arg1, cut, show_path): old = list(d.keys()) #for i in range(len(arg1.terms)): for i,j in zip(indx, range(len(old))): - d[arg1.terms[i]] = d.pop(old[j]) - return temp_cache + d[arg1.terms[i]] = d.pop(old[j]) + return temp_cache return memorize_query ## querizer decorator is called whenever there's a new query @@ -43,7 +43,7 @@ def memorize_query(kb, arg1, cut, show_path): def querizer(simple_query): def wrap(rule_query): @wraps(rule_query) - def prepare_query(kb, arg1, cut, show_path): + def prepare_query(kb, arg1, cut, show_path, **kwargs): pred = arg1.predicate if pred in kb.db: goals_len = 0.0 @@ -53,8 +53,8 @@ def prepare_query(kb, arg1, cut, show_path): return simple_query(kb, arg1) else: return rule_query(kb, arg1, cut, show_path) - return prepare_query - return wrap + return prepare_query + return wrap ## simple function it unifies the query with the corresponding facts def simple_query(kb, expr): @@ -67,7 +67,7 @@ def simple_query(kb, expr): first, last = fact_binary_search(search_base, key) else: first, last = (0, len(search_base)) - + for i in range(first, last): res = {} uni = unify(expr, Expr(search_base[i].to_string()), res) @@ -80,7 +80,7 @@ def simple_query(kb, expr): ## rule_query() is the main search function @memory @querizer(simple_query) -def rule_query(kb, expr, cut, show_path): +def rule_query(kb, expr, cut, show_path, cache=True): #pdb.set_trace() # I used to trace every step in the search that consumed me to figure out :D rule = Fact(expr.to_string()) # change expr to rule class answer = [] @@ -94,32 +94,32 @@ def rule_query(kb, expr, cut, show_path): while not queue.empty: ## keep searching until it is empty meaning nothing left to be searched current_goal = queue.pop() if current_goal.ind >= len(current_goal.fact.rhs): ## all rule goals have been searched - if current_goal.parent == None: ## no more parents + if current_goal.parent == None: ## no more parents if current_goal.domain: ## if there is an answer return it answer.append(current_goal.domain) if cut: break - else: + else: answer.append("Yes") ## if no returns Yes - continue ## if no answer found go back to the parent a step above again - + continue ## if no answer found go back to the parent a step above again + ## father which is the main rule takes unified child's domain from facts child_to_parent(current_goal, queue) if show_path: path.append(current_goal.domain) continue - + ## get the rh expr from the current goal to look for its predicate in database rule = current_goal.fact.rhs[current_goal.ind] - + ## Probabilities and numeric evaluation if rule.predicate == "": ## if there is no predicate prob_calc(current_goal, rule, queue) continue - + # inequality if rule.predicate == "neq": filter_eq(rule, current_goal, queue) continue - + elif rule.predicate in kb.db: ## search relevant buckets so it speeds up search rule_f = kb.db[rule.predicate]["facts"] @@ -129,11 +129,11 @@ def rule_query(kb, expr, cut, show_path): else: # a child to search facts in kb child_assigned(rule, rule_f, current_goal, queue) - + answer = answer_handler(answer) - - if show_path: + + if show_path: path = get_path(kb.db, expr, path) return answer, path else: - return answer \ No newline at end of file + return answer diff --git a/test_pytholog.py b/test_pytholog.py index 351fe4a..7979685 100644 --- a/test_pytholog.py +++ b/test_pytholog.py @@ -23,7 +23,7 @@ def test_dishes(): answer = {"What": "cookie"} query = food_kb.query(pl.Expr("food_flavor(What, sweet)")) assert answer in query - + def test_friends(): friends_kb = pl.KnowledgeBase("friends") friends_kb([ @@ -46,10 +46,10 @@ def test_friends(): assert david in friends_kb.query(pl.Expr("to_smoke(Who, P)")) dan_reb = [{'Who': 'rebecca', 'P': '0.4'}, {'Who': 'daniel', 'P': 0.024000000000000004}] assert all(i in friends_kb.query(pl.Expr("to_have_asthma(Who, P)")) for i in dan_reb) - + def test_iris(): iris_kb = pl.KnowledgeBase("iris") - iris_kb(["species(setosa, Truth) :- petal_width(W), Truth is W <= 0.80", + iris_kb(["species(setosa, Truth) :- petal_width(W), Truth is W <= 0.80", "species(versicolor, Truth) :- petal_width(W), petal_length(L), Truth is W > 0.80 and L <= 4.95", "species(virginica, Truth) :- petal_width(W), petal_length(L), Truth is W > 0.80 and L > 4.95", "petal_length(5.1)", @@ -69,4 +69,34 @@ def test_graph(): query = graph.query(pl.Expr("path(a, e, W)"), cut = True) assert [d.get("W") for d in query][0] == 10 - + + + +def test_memory_off(): + class_kb = pl.KnowledgeBase("classmates") + class_kb([ + "person(noor)", + "person(melissa)", + "person(dmitry)", + ]) + + # we can confirm that the entries are cached based on the sam erequest + assert len(class_kb.query(pl.Expr("person(X)"))) == 3 + class_kb.add_kn(["person(abdel)"]) + assert len(class_kb.query(pl.Expr("person(X)"))) == 3 + + + new_class = pl.KnowledgeBase("classmates") + new_class([ + "person(noor)", + "person(melissa)" + ]) + assert len(new_class.query(pl.Expr("person(X)"))) == 2 + new_class.add_kn(["person(abdel)"]) + assert len(new_class.query(pl.Expr("person(X)"), cache=False)) == 3 + new_class.add_kn(["person(mohammed)"]) + assert len(new_class.query(pl.Expr("person(X)"), cache=False)) == 4 + + # we set cache=True to test that a new entry but using cache + new_class.add_kn(["person(rico)"]) + assert len(new_class.query(pl.Expr("person(X)"))) == 4