Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions pytholog/knowledge_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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!"
Expand All @@ -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):
Expand All @@ -94,4 +94,3 @@ def __getattr__(self, attr):
return getattr(self.new_target, attr)

knowledge_base = DeprecationHelper(KnowledgeBase)

42 changes: 21 additions & 21 deletions pytholog/querizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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):
Expand All @@ -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)
Expand All @@ -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 = []
Expand All @@ -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"]
Expand All @@ -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
return answer
38 changes: 34 additions & 4 deletions test_pytholog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand All @@ -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)",
Expand All @@ -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