├── statements_kb3.txt ├── KnowledgeBase ├── statements_kb3.txt ├── statements_kb4.txt ├── statements_kb2.txt ├── statements_kb.txt ├── main_v2.py ├── read.py ├── util.py ├── main.py ├── function.py ├── README.md └── logical_classes.py ├── statements_kb4.txt ├── statements_kb2.txt ├── statements_kb.txt ├── main_v2.py ├── read.py ├── util.py ├── main.py ├── function.py ├── README.md └── logical_classes.py /statements_kb3.txt: -------------------------------------------------------------------------------- 1 | fact: (hero a) 2 | fact: (person a) 3 | 4 | fact: (goodman a) 5 | 6 | rule: ((hero ?y) (person ?y)) -> (goodman ?y) 7 | -------------------------------------------------------------------------------- /KnowledgeBase/statements_kb3.txt: -------------------------------------------------------------------------------- 1 | fact: (hero a) 2 | fact: (person a) 3 | 4 | fact: (goodman a) 5 | 6 | rule: ((hero ?y) (person ?y)) -> (goodman ?y) 7 | -------------------------------------------------------------------------------- /statements_kb4.txt: -------------------------------------------------------------------------------- 1 | fact: (motherof ada bing) 2 | fact: (motherof bing chen) 3 | fact: (motherof dolores chen) 4 | fact: (sisters ada eva) 5 | fact: (grandmotherof ada felix) 6 | fact: (motherof greta felix) 7 | 8 | rule: ((motherof ?x ?y)) -> (parentof ?x ?y) 9 | rule: ((parentof ?x ?y) (sisters ?x ?z)) -> (auntof ?z ?y) 10 | rule: ((parentof ?x ?y) (motherof ?z ?x)) -> (grandmotherof ?z ?y) -------------------------------------------------------------------------------- /KnowledgeBase/statements_kb4.txt: -------------------------------------------------------------------------------- 1 | fact: (motherof ada bing) 2 | fact: (motherof bing chen) 3 | fact: (motherof dolores chen) 4 | fact: (sisters ada eva) 5 | fact: (grandmotherof ada felix) 6 | fact: (motherof greta felix) 7 | 8 | rule: ((motherof ?x ?y)) -> (parentof ?x ?y) 9 | rule: ((parentof ?x ?y) (sisters ?x ?z)) -> (auntof ?z ?y) 10 | rule: ((parentof ?x ?y) (motherof ?z ?x)) -> (grandmotherof ?z ?y) -------------------------------------------------------------------------------- /statements_kb2.txt: -------------------------------------------------------------------------------- 1 | fact: (attacked Ai Nosliw) 2 | fact: (diamonds Loot) 3 | fact: (possesses Ai Loot) 4 | fact: (needs Sarorah Loot) 5 | fact: (hero Ai) 6 | 7 | fact: (inst Sarorah Sorceress) 8 | fact: (isa Sorceress Wizard) 9 | 10 | fact: (inst Nosliw Dragon) 11 | 12 | 13 | rule: ((inst ?x ?y) (isa ?y ?z)) -> (inst ?x ?z) 14 | rule: ((isa ?x ?y) (isa ?y ?z)) -> (isa ?x ?z) 15 | 16 | rule: ((dead Nosliw)) -> (safe HappyDale) 17 | rule: ((attacked ?a ?d) (defeatable ?d)) -> (dead ?d) 18 | 19 | rule: ((wielding ?a ?s) (strong ?a) (inst ?d Dragon)) -> (defeatable ?d) 20 | rule: ((magicCastUpon ?a)) -> (strong ?a) 21 | 22 | rule: ((inst ?w Wizard) (gives ?a ?w ?d) (diamonds ?d)) -> (magicCastUpon ?a) 23 | rule: ((hero ?a)) -> (wielding ?a Weapon) 24 | 25 | rule: ((possesses ?a ?it) (needs ?p ?it)) -> (gives ?a ?p ?it) -------------------------------------------------------------------------------- /KnowledgeBase/statements_kb2.txt: -------------------------------------------------------------------------------- 1 | fact: (attacked Ai Nosliw) 2 | fact: (diamonds Loot) 3 | fact: (possesses Ai Loot) 4 | fact: (needs Sarorah Loot) 5 | fact: (hero Ai) 6 | 7 | fact: (inst Sarorah Sorceress) 8 | fact: (isa Sorceress Wizard) 9 | 10 | fact: (inst Nosliw Dragon) 11 | 12 | 13 | rule: ((inst ?x ?y) (isa ?y ?z)) -> (inst ?x ?z) 14 | rule: ((isa ?x ?y) (isa ?y ?z)) -> (isa ?x ?z) 15 | 16 | rule: ((dead Nosliw)) -> (safe HappyDale) 17 | rule: ((attacked ?a ?d) (defeatable ?d)) -> (dead ?d) 18 | 19 | rule: ((wielding ?a ?s) (strong ?a) (inst ?d Dragon)) -> (defeatable ?d) 20 | rule: ((magicCastUpon ?a)) -> (strong ?a) 21 | 22 | rule: ((inst ?w Wizard) (gives ?a ?w ?d) (diamonds ?d)) -> (magicCastUpon ?a) 23 | rule: ((hero ?a)) -> (wielding ?a Weapon) 24 | 25 | rule: ((possesses ?a ?it) (needs ?p ?it)) -> (gives ?a ?p ?it) -------------------------------------------------------------------------------- /statements_kb.txt: -------------------------------------------------------------------------------- 1 | fact: (isa cube block) 2 | fact: (isa pyramid block) 3 | fact: (isa sphere block) 4 | 5 | fact: (isa box container) 6 | 7 | fact: (inst bigbox box) 8 | fact: (size bigbox big) 9 | fact: (color bigbox red) 10 | 11 | fact: (inst littlebox box) 12 | fact: (size littlebox small) 13 | fact: (color littlebox blue) 14 | 15 | fact: (inst pyramid1 pyramid) 16 | fact: (size pyramid1 small) 17 | fact: (color pyramid1 blue) 18 | 19 | fact: (inst pyramid2 pyramid) 20 | fact: (size pyramid2 small) 21 | fact: (color pyramid2 green) 22 | 23 | fact: (inst pyramid3 pyramid) 24 | fact: (size pyramid3 big) 25 | fact: (color pyramid3 red) 26 | 27 | fact: (inst pyramid4 pyramid) 28 | fact: (size pyramid4 big) 29 | fact: (color pyramid4 red) 30 | 31 | fact: (inst cube1 cube) 32 | fact: (inst cube2 cube) 33 | fact: (inst cube3 cube) 34 | fact: (inst cube4 cube) 35 | 36 | fact: (inst sphere1 sphere) 37 | 38 | rule: ((inst ?x ?y) (isa ?y ?z)) -> (inst ?x ?z) 39 | rule: ((inst ?x cube)) -> (flat ?x) 40 | rule: ((on ?x ?y) (bigger ?x ?y)) -> (covered ?y) 41 | rule: ((married ?x ?y) (love ?x ?y)) -> (happy ?x) 42 | rule: ((married ?x ?y) (love ?x ?y)) -> (happy ?y) -------------------------------------------------------------------------------- /KnowledgeBase/statements_kb.txt: -------------------------------------------------------------------------------- 1 | fact: (isa cube block) 2 | fact: (isa pyramid block) 3 | fact: (isa sphere block) 4 | 5 | fact: (isa box container) 6 | 7 | fact: (inst bigbox box) 8 | fact: (size bigbox big) 9 | fact: (color bigbox red) 10 | 11 | fact: (inst littlebox box) 12 | fact: (size littlebox small) 13 | fact: (color littlebox blue) 14 | 15 | fact: (inst pyramid1 pyramid) 16 | fact: (size pyramid1 small) 17 | fact: (color pyramid1 blue) 18 | 19 | fact: (inst pyramid2 pyramid) 20 | fact: (size pyramid2 small) 21 | fact: (color pyramid2 green) 22 | 23 | fact: (inst pyramid3 pyramid) 24 | fact: (size pyramid3 big) 25 | fact: (color pyramid3 red) 26 | 27 | fact: (inst pyramid4 pyramid) 28 | fact: (size pyramid4 big) 29 | fact: (color pyramid4 red) 30 | 31 | fact: (inst cube1 cube) 32 | fact: (inst cube2 cube) 33 | fact: (inst cube3 cube) 34 | fact: (inst cube4 cube) 35 | 36 | fact: (inst sphere1 sphere) 37 | 38 | rule: ((inst ?x ?y) (isa ?y ?z)) -> (inst ?x ?z) 39 | rule: ((inst ?x cube)) -> (flat ?x) 40 | rule: ((on ?x ?y) (bigger ?x ?y)) -> (covered ?y) 41 | rule: ((married ?x ?y) (love ?x ?y)) -> (happy ?x) 42 | rule: ((married ?x ?y) (love ?x ?y)) -> (happy ?y) -------------------------------------------------------------------------------- /main_v2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import read, copy 3 | from logical_classes import * 4 | from function import KnowledgeBase 5 | 6 | class KBTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | # Assert starter facts 10 | file = 'statements_kb4.txt' 11 | self.data = read.read_tokenize(file) 12 | data = read.read_tokenize(file) 13 | self.KB = KnowledgeBase([], []) 14 | for item in data: 15 | if isinstance(item, Fact) or isinstance(item, Rule): 16 | self.KB.kb_assert(item) 17 | 18 | def test1(self): 19 | ask1 = read.parse_input("fact: (motherof ada ?X)") 20 | print(' Asking if', ask1) 21 | answer = self.KB.kb_ask(ask1) 22 | self.assertEqual(str(answer[0]), "?X : bing") 23 | 24 | def test2(self): 25 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 26 | print(' Asking if', ask1) 27 | answer = self.KB.kb_ask(ask1) 28 | self.assertEqual(str(answer[0]), "?X : felix") 29 | self.assertEqual(str(answer[1]), "?X : chen") 30 | 31 | def test3(self): 32 | r1 = read.parse_input("fact: (motherof ada bing)") 33 | print(' Retracting', r1) 34 | self.KB.kb_retract(r1) 35 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 36 | print(' Asking if', ask1) 37 | answer = self.KB.kb_ask(ask1) 38 | self.assertEqual(str(answer[0]), "?X : felix") 39 | 40 | def test4(self): 41 | r1 = read.parse_input("fact: (grandmotherof ada chen)") 42 | print(' Retracting', r1) 43 | self.KB.kb_retract(r1) 44 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 45 | print(' Asking if', ask1) 46 | answer = self.KB.kb_ask(ask1) 47 | self.assertEqual(str(answer[0]), "?X : felix") 48 | self.assertEqual(str(answer[1]), "?X : chen") 49 | 50 | def test5(self): 51 | r1 = read.parse_input("rule: ((motherof ?x ?y)) -> (parentof ?x ?y)") 52 | print(' Retracting', r1) 53 | self.KB.kb_retract(r1) 54 | ask1 = read.parse_input("fact: (parentof ada ?X)") 55 | print(' Asking if', ask1) 56 | answer = self.KB.kb_ask(ask1) 57 | self.assertEqual(str(answer[0]), "?X : bing") 58 | 59 | 60 | def pprint_justification(answer): 61 | """Pretty prints (hence pprint) justifications for the answer. 62 | """ 63 | if not answer: print('Answer is False, no justification') 64 | else: 65 | print('\nJustification:') 66 | for i in range(0,len(answer.list_of_bindings)): 67 | # print bindings 68 | print(answer.list_of_bindings[i][0]) 69 | # print justifications 70 | for fact_rule in answer.list_of_bindings[i][1]: 71 | pprint_support(fact_rule,0) 72 | print 73 | 74 | def pprint_support(fact_rule, indent): 75 | """Recursive pretty printer helper to nicely indent 76 | """ 77 | if fact_rule: 78 | print(' '*indent, "Support for") 79 | 80 | if isinstance(fact_rule, Fact): 81 | print(fact_rule.statement) 82 | else: 83 | print(fact_rule.lhs, "->", fact_rule.rhs) 84 | 85 | if fact_rule.supported_by: 86 | for pair in fact_rule.supported_by: 87 | print(' '*(indent+1), "support option") 88 | for next in pair: 89 | pprint_support(next, indent+2) 90 | 91 | 92 | 93 | if __name__ == '__main__': 94 | unittest.main() 95 | -------------------------------------------------------------------------------- /KnowledgeBase/main_v2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import read, copy 3 | from logical_classes import * 4 | from function import KnowledgeBase 5 | 6 | class KBTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | # Assert starter facts 10 | file = 'statements_kb4.txt' 11 | self.data = read.read_tokenize(file) 12 | data = read.read_tokenize(file) 13 | self.KB = KnowledgeBase([], []) 14 | for item in data: 15 | if isinstance(item, Fact) or isinstance(item, Rule): 16 | self.KB.kb_assert(item) 17 | 18 | def test1(self): 19 | ask1 = read.parse_input("fact: (motherof ada ?X)") 20 | print(' Asking if', ask1) 21 | answer = self.KB.kb_ask(ask1) 22 | self.assertEqual(str(answer[0]), "?X : bing") 23 | 24 | def test2(self): 25 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 26 | print(' Asking if', ask1) 27 | answer = self.KB.kb_ask(ask1) 28 | self.assertEqual(str(answer[0]), "?X : felix") 29 | self.assertEqual(str(answer[1]), "?X : chen") 30 | 31 | def test3(self): 32 | r1 = read.parse_input("fact: (motherof ada bing)") 33 | print(' Retracting', r1) 34 | self.KB.kb_retract(r1) 35 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 36 | print(' Asking if', ask1) 37 | answer = self.KB.kb_ask(ask1) 38 | self.assertEqual(str(answer[0]), "?X : felix") 39 | 40 | def test4(self): 41 | r1 = read.parse_input("fact: (grandmotherof ada chen)") 42 | print(' Retracting', r1) 43 | self.KB.kb_retract(r1) 44 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 45 | print(' Asking if', ask1) 46 | answer = self.KB.kb_ask(ask1) 47 | self.assertEqual(str(answer[0]), "?X : felix") 48 | self.assertEqual(str(answer[1]), "?X : chen") 49 | 50 | def test5(self): 51 | r1 = read.parse_input("rule: ((motherof ?x ?y)) -> (parentof ?x ?y)") 52 | print(' Retracting', r1) 53 | self.KB.kb_retract(r1) 54 | ask1 = read.parse_input("fact: (parentof ada ?X)") 55 | print(' Asking if', ask1) 56 | answer = self.KB.kb_ask(ask1) 57 | self.assertEqual(str(answer[0]), "?X : bing") 58 | 59 | 60 | def pprint_justification(answer): 61 | """Pretty prints (hence pprint) justifications for the answer. 62 | """ 63 | if not answer: print('Answer is False, no justification') 64 | else: 65 | print('\nJustification:') 66 | for i in range(0,len(answer.list_of_bindings)): 67 | # print bindings 68 | print(answer.list_of_bindings[i][0]) 69 | # print justifications 70 | for fact_rule in answer.list_of_bindings[i][1]: 71 | pprint_support(fact_rule,0) 72 | print 73 | 74 | def pprint_support(fact_rule, indent): 75 | """Recursive pretty printer helper to nicely indent 76 | """ 77 | if fact_rule: 78 | print(' '*indent, "Support for") 79 | 80 | if isinstance(fact_rule, Fact): 81 | print(fact_rule.statement) 82 | else: 83 | print(fact_rule.lhs, "->", fact_rule.rhs) 84 | 85 | if fact_rule.supported_by: 86 | for pair in fact_rule.supported_by: 87 | print(' '*(indent+1), "support option") 88 | for next in pair: 89 | pprint_support(next, indent+2) 90 | 91 | 92 | 93 | if __name__ == '__main__': 94 | unittest.main() 95 | -------------------------------------------------------------------------------- /read.py: -------------------------------------------------------------------------------- 1 | from logical_classes import * 2 | 3 | # read_tokenize takes the name of a file, reads it in and tokenizes the 4 | # statements and rules in that file. 5 | def read_tokenize(file): 6 | """Reads in a file and processes contents into lists of facts and rules. 7 | 8 | Args: 9 | file (file): A txt file with facts of the form (predicate subject 10 | object) such as "fact: (isa cube block)". As well, there are rules with 11 | a right and left hand side that are essentially (fact1 and fact2) -> 12 | (fact3) such as "rule: ((inst ?x ?y) (isa ?y ?z)) -> (inst ?x ?z)". 13 | These facts and rules each go on a new line in the file and are looped 14 | over to build the two seperate lists of facts and rules. 15 | 16 | Returns: 17 | A list of Facts and Rules. 18 | """ 19 | file = open(file, "r") 20 | elements = [] 21 | current = "" 22 | for line in file: 23 | if line[0:5] in ("fact:", "rule:"): 24 | elements.append(current) 25 | current = line.rstrip() 26 | else: 27 | current = current + " " + line.rstrip().strip() 28 | elements.append(current) 29 | output = [] 30 | for e in elements: 31 | parsed = parse_input(e) 32 | if isinstance(parsed, Fact) or isinstance(parsed, Rule): 33 | output.append(parsed) 34 | file.close() 35 | return output 36 | 37 | 38 | def parse_input(e): 39 | """Parses input, assigning labels and splitting rules into LHS & RHS 40 | 41 | Args: 42 | e (string): Input string to parse 43 | 44 | Returns: 45 | (number, string | listof string): label, then parsed input 46 | """ 47 | if len(e) == 0: 48 | #return (BLANK, None) 49 | return None 50 | elif e[0] == '#': 51 | #return (COMMENT, e) 52 | return e[1:] 53 | elif e[0:5] == "fact:": 54 | e = e[5:].replace(")", "").replace("(", "").rstrip().strip().split() 55 | #return (FACT, e) 56 | return Fact(e) 57 | elif e[0:5] == "rule:": 58 | e = e[5:].split("->") 59 | rhs = e[1].replace(")", "").replace("(", "").rstrip().strip().split() 60 | lhs = e[0].rstrip(") ").strip("( ").replace("(", "").split(")") 61 | lhs = map(lambda x: x.rstrip().strip().split(), lhs) 62 | #return (RULE, [lhs, rhs]) 63 | return Rule([lhs, rhs]) 64 | else: 65 | print("PARSE ERROR: input header", e[0:5], "not recognized.") 66 | 67 | def get_new_fact_or_rule(): 68 | """Creates a new fact or rule. (instead of args, we use command line input 69 | via the read_from_input() function) 70 | 71 | Returns: 72 | list: fact list or list of the left and right hand sides of a rule 73 | """ 74 | msg = "Please type in a new fact or rule you want to add to the KB:\n" 75 | e = read_from_input(msg) 76 | return parse_input(e) 77 | 78 | def get_new_statements(): 79 | """Gets statements from user via the command line. Statments are expected 80 | in the form predicate followed by terms with spaces between (e.g. "isa 81 | cube block"). Changed from - 82 | map(lambda x: filter(str.isalnum, x), e.split(" ")) 83 | because I wanted to allow for statements to have non-alphanumeric 84 | characters, namely "?". This way, the method can be used any time that the 85 | user is creating a statement, not just when the statement is all constants. 86 | 87 | Returns: 88 | list: statement filtered for strings of the form `pred x1 x2 ... :` 89 | """ 90 | e = read_from_input("Please type in a statement of the form " + 91 | "\"pred x1 x2 ...\":\n") 92 | return e.split() 93 | -------------------------------------------------------------------------------- /KnowledgeBase/read.py: -------------------------------------------------------------------------------- 1 | from logical_classes import * 2 | 3 | # read_tokenize takes the name of a file, reads it in and tokenizes the 4 | # statements and rules in that file. 5 | def read_tokenize(file): 6 | """Reads in a file and processes contents into lists of facts and rules. 7 | 8 | Args: 9 | file (file): A txt file with facts of the form (predicate subject 10 | object) such as "fact: (isa cube block)". As well, there are rules with 11 | a right and left hand side that are essentially (fact1 and fact2) -> 12 | (fact3) such as "rule: ((inst ?x ?y) (isa ?y ?z)) -> (inst ?x ?z)". 13 | These facts and rules each go on a new line in the file and are looped 14 | over to build the two seperate lists of facts and rules. 15 | 16 | Returns: 17 | A list of Facts and Rules. 18 | """ 19 | file = open(file, "r") 20 | elements = [] 21 | current = "" 22 | for line in file: 23 | if line[0:5] in ("fact:", "rule:"): 24 | elements.append(current) 25 | current = line.rstrip() 26 | else: 27 | current = current + " " + line.rstrip().strip() 28 | elements.append(current) 29 | output = [] 30 | for e in elements: 31 | parsed = parse_input(e) 32 | if isinstance(parsed, Fact) or isinstance(parsed, Rule): 33 | output.append(parsed) 34 | file.close() 35 | return output 36 | 37 | 38 | def parse_input(e): 39 | """Parses input, assigning labels and splitting rules into LHS & RHS 40 | 41 | Args: 42 | e (string): Input string to parse 43 | 44 | Returns: 45 | (number, string | listof string): label, then parsed input 46 | """ 47 | if len(e) == 0: 48 | #return (BLANK, None) 49 | return None 50 | elif e[0] == '#': 51 | #return (COMMENT, e) 52 | return e[1:] 53 | elif e[0:5] == "fact:": 54 | e = e[5:].replace(")", "").replace("(", "").rstrip().strip().split() 55 | #return (FACT, e) 56 | return Fact(e) 57 | elif e[0:5] == "rule:": 58 | e = e[5:].split("->") 59 | rhs = e[1].replace(")", "").replace("(", "").rstrip().strip().split() 60 | lhs = e[0].rstrip(") ").strip("( ").replace("(", "").split(")") 61 | lhs = map(lambda x: x.rstrip().strip().split(), lhs) 62 | #return (RULE, [lhs, rhs]) 63 | return Rule([lhs, rhs]) 64 | else: 65 | print("PARSE ERROR: input header", e[0:5], "not recognized.") 66 | 67 | def get_new_fact_or_rule(): 68 | """Creates a new fact or rule. (instead of args, we use command line input 69 | via the read_from_input() function) 70 | 71 | Returns: 72 | list: fact list or list of the left and right hand sides of a rule 73 | """ 74 | msg = "Please type in a new fact or rule you want to add to the KB:\n" 75 | e = read_from_input(msg) 76 | return parse_input(e) 77 | 78 | def get_new_statements(): 79 | """Gets statements from user via the command line. Statments are expected 80 | in the form predicate followed by terms with spaces between (e.g. "isa 81 | cube block"). Changed from - 82 | map(lambda x: filter(str.isalnum, x), e.split(" ")) 83 | because I wanted to allow for statements to have non-alphanumeric 84 | characters, namely "?". This way, the method can be used any time that the 85 | user is creating a statement, not just when the statement is all constants. 86 | 87 | Returns: 88 | list: statement filtered for strings of the form `pred x1 x2 ... :` 89 | """ 90 | e = read_from_input("Please type in a statement of the form " + 91 | "\"pred x1 x2 ...\":\n") 92 | return e.split() 93 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | import logical_classes as lc 2 | 3 | def is_var(var): 4 | """Check whether an element is a variable (either instance of Variable, 5 | instance of Term (where .term is a Variable) or a string starting with 6 | `'?'`, e.g. `'?d'`) 7 | 8 | Args: 9 | var (any): value to check 10 | 11 | Returns: 12 | bool 13 | """ 14 | if type(var) == str: 15 | return var[0] == "?" 16 | if isinstance(var, lc.Term): 17 | return isinstance(var.term, lc.Variable) 18 | 19 | return isinstance(var, lc.Variable) 20 | 21 | def match(state1, state2, bindings=None): 22 | """Match two statements and return the associated bindings or False if there 23 | is no binding 24 | 25 | Args: 26 | state1 (Statement): statement to match with state2 27 | state2 (Statement): statement to match with state1 28 | bindings (Bindings|None): already associated bindings 29 | 30 | Returns: 31 | Bindings|False: either associated bindings or no match found 32 | """ 33 | if len(state1.terms) != len(state2.terms) or state1.predicate != state2.predicate: 34 | return False 35 | if not bindings: 36 | bindings = lc.Bindings() 37 | return match_recursive(state1.terms, state2.terms, bindings) 38 | 39 | def match_recursive(terms1, terms2, bindings): # recursive... 40 | """Recursive helper for match 41 | 42 | Args: 43 | terms1 (listof Term): terms to match with terms2 44 | terms2 (listof Term): terms to match with terms1 45 | bindings (Bindings): already associated bindings 46 | 47 | Returns: 48 | Bindings|False: either associated bindings or no match found 49 | """ 50 | if len(terms1) == 0: 51 | return bindings 52 | if is_var(terms1[0]): 53 | if not bindings.test_and_bind(terms1[0], terms2[0]): 54 | return False 55 | elif is_var(terms2[0]): 56 | if not bindings.test_and_bind(terms2[0], terms1[0]): 57 | return False 58 | elif terms1[0] != terms2[0]: 59 | return False 60 | return match_recursive(terms1[1:], terms2[1:], bindings) 61 | 62 | def instantiate(statement, bindings): 63 | """Generate Statement from given statement and bindings. Constructed statement 64 | has bound values for variables if they exist in bindings. 65 | 66 | Args: 67 | statement (Statement): statement to generate new statement from 68 | bindings (Bindings): bindings to substitute into statement 69 | """ 70 | def handle_term(term): 71 | if is_var(term): 72 | bound_value = bindings.bound_to(term.term) 73 | return lc.Term(bound_value) if bound_value else term 74 | else: 75 | return term 76 | 77 | new_terms = [handle_term(t) for t in statement.terms] 78 | return lc.Statement([statement.predicate] + new_terms) 79 | 80 | def factq(element): 81 | """Check if element is a fact 82 | 83 | Args: 84 | element (any): element to check 85 | 86 | Returns: 87 | bool 88 | """ 89 | return isinstance(element, lc.Fact) 90 | 91 | def printv(message, level, verbose, data=[]): 92 | """Prints given message formatted with data if passed in verbose flag is greater than level 93 | 94 | Args: 95 | message (str): message to print, if format string data should have values 96 | to format with 97 | level (int): value of verbose required to print 98 | verbose (int): value of verbose flag 99 | data (listof any): optional data to format message with 100 | """ 101 | if verbose > level: 102 | print(message.format(*data) if data else message) -------------------------------------------------------------------------------- /KnowledgeBase/util.py: -------------------------------------------------------------------------------- 1 | import logical_classes as lc 2 | 3 | def is_var(var): 4 | """Check whether an element is a variable (either instance of Variable, 5 | instance of Term (where .term is a Variable) or a string starting with 6 | `'?'`, e.g. `'?d'`) 7 | 8 | Args: 9 | var (any): value to check 10 | 11 | Returns: 12 | bool 13 | """ 14 | if type(var) == str: 15 | return var[0] == "?" 16 | if isinstance(var, lc.Term): 17 | return isinstance(var.term, lc.Variable) 18 | 19 | return isinstance(var, lc.Variable) 20 | 21 | def match(state1, state2, bindings=None): 22 | """Match two statements and return the associated bindings or False if there 23 | is no binding 24 | 25 | Args: 26 | state1 (Statement): statement to match with state2 27 | state2 (Statement): statement to match with state1 28 | bindings (Bindings|None): already associated bindings 29 | 30 | Returns: 31 | Bindings|False: either associated bindings or no match found 32 | """ 33 | if len(state1.terms) != len(state2.terms) or state1.predicate != state2.predicate: 34 | return False 35 | if not bindings: 36 | bindings = lc.Bindings() 37 | return match_recursive(state1.terms, state2.terms, bindings) 38 | 39 | def match_recursive(terms1, terms2, bindings): # recursive... 40 | """Recursive helper for match 41 | 42 | Args: 43 | terms1 (listof Term): terms to match with terms2 44 | terms2 (listof Term): terms to match with terms1 45 | bindings (Bindings): already associated bindings 46 | 47 | Returns: 48 | Bindings|False: either associated bindings or no match found 49 | """ 50 | if len(terms1) == 0: 51 | return bindings 52 | if is_var(terms1[0]): 53 | if not bindings.test_and_bind(terms1[0], terms2[0]): 54 | return False 55 | elif is_var(terms2[0]): 56 | if not bindings.test_and_bind(terms2[0], terms1[0]): 57 | return False 58 | elif terms1[0] != terms2[0]: 59 | return False 60 | return match_recursive(terms1[1:], terms2[1:], bindings) 61 | 62 | def instantiate(statement, bindings): 63 | """Generate Statement from given statement and bindings. Constructed statement 64 | has bound values for variables if they exist in bindings. 65 | 66 | Args: 67 | statement (Statement): statement to generate new statement from 68 | bindings (Bindings): bindings to substitute into statement 69 | """ 70 | def handle_term(term): 71 | if is_var(term): 72 | bound_value = bindings.bound_to(term.term) 73 | return lc.Term(bound_value) if bound_value else term 74 | else: 75 | return term 76 | 77 | new_terms = [handle_term(t) for t in statement.terms] 78 | return lc.Statement([statement.predicate] + new_terms) 79 | 80 | def factq(element): 81 | """Check if element is a fact 82 | 83 | Args: 84 | element (any): element to check 85 | 86 | Returns: 87 | bool 88 | """ 89 | return isinstance(element, lc.Fact) 90 | 91 | def printv(message, level, verbose, data=[]): 92 | """Prints given message formatted with data if passed in verbose flag is greater than level 93 | 94 | Args: 95 | message (str): message to print, if format string data should have values 96 | to format with 97 | level (int): value of verbose required to print 98 | verbose (int): value of verbose flag 99 | data (listof any): optional data to format message with 100 | """ 101 | if verbose > level: 102 | print(message.format(*data) if data else message) -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import read, copy 3 | from logical_classes import * 4 | from function import KnowledgeBase 5 | 6 | class KBTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | # Assert starter facts 10 | file = 'statements_kb4.txt' 11 | self.data = read.read_tokenize(file) 12 | data = read.read_tokenize(file) 13 | self.KB = KnowledgeBase([], []) 14 | for item in data: 15 | if isinstance(item, Fact) or isinstance(item, Rule): 16 | self.KB.kb_assert(item) 17 | 18 | def test1(self): 19 | ask1 = read.parse_input("fact: (motherof ada ?X)") 20 | print(' Asking if', ask1) 21 | answer = self.KB.kb_ask(ask1) 22 | self.assertEqual(str(answer[0]), "?X : bing") 23 | 24 | def test2(self): 25 | # Can fc_infer actually infer 26 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 27 | print(' Asking if', ask1) 28 | answer = self.KB.kb_ask(ask1) 29 | self.assertEqual(str(answer[0]), "?X : felix") 30 | self.assertEqual(str(answer[1]), "?X : chen") 31 | 32 | def test3(self): 33 | # Does retract actually retract things 34 | r1 = read.parse_input("fact: (motherof ada bing)") 35 | print(' Retracting', r1) 36 | self.KB.kb_retract(r1) 37 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 38 | print(' Asking if', ask1) 39 | answer = self.KB.kb_ask(ask1) 40 | self.assertEqual(len(answer), 1) 41 | self.assertEqual(str(answer[0]), "?X : felix") 42 | 43 | def test4(self): 44 | # makes sure retract does not retract supported fact 45 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 46 | print(' Asking if', ask1) 47 | answer = self.KB.kb_ask(ask1) 48 | self.assertEqual(str(answer[0]), "?X : felix") 49 | self.assertEqual(str(answer[1]), "?X : chen") 50 | 51 | r1 = read.parse_input("fact: (grandmotherof ada chen)") 52 | print(' Retracting', r1) 53 | self.KB.kb_retract(r1) 54 | 55 | print(' Asking if', ask1) 56 | answer = self.KB.kb_ask(ask1) 57 | self.assertEqual(str(answer[0]), "?X : felix") 58 | self.assertEqual(str(answer[1]), "?X : chen") 59 | 60 | def test5(self): 61 | # makes sure retract does not deal with rules 62 | ask1 = read.parse_input("fact: (parentof ada ?X)") 63 | print(' Asking if', ask1) 64 | answer = self.KB.kb_ask(ask1) 65 | self.assertEqual(str(answer[0]), "?X : bing") 66 | r1 = read.parse_input("rule: ((motherof ?x ?y)) -> (parentof ?x ?y)") 67 | print(' Retracting', r1) 68 | self.KB.kb_retract(r1) 69 | print(' Asking if', ask1) 70 | answer = self.KB.kb_ask(ask1) 71 | self.assertEqual(str(answer[0]), "?X : bing") 72 | 73 | 74 | def pprint_justification(answer): 75 | """Pretty prints (hence pprint) justifications for the answer. 76 | """ 77 | if not answer: print('Answer is False, no justification') 78 | else: 79 | print('\nJustification:') 80 | for i in range(0,len(answer.list_of_bindings)): 81 | # print bindings 82 | print(answer.list_of_bindings[i][0]) 83 | # print justifications 84 | for fact_rule in answer.list_of_bindings[i][1]: 85 | pprint_support(fact_rule,0) 86 | print 87 | 88 | def pprint_support(fact_rule, indent): 89 | """Recursive pretty printer helper to nicely indent 90 | """ 91 | if fact_rule: 92 | print(' '*indent, "Support for") 93 | 94 | if isinstance(fact_rule, Fact): 95 | print(fact_rule.statement) 96 | else: 97 | print(fact_rule.lhs, "->", fact_rule.rhs) 98 | 99 | if fact_rule.supported_by: 100 | for pair in fact_rule.supported_by: 101 | print(' '*(indent+1), "support option") 102 | for next in pair: 103 | pprint_support(next, indent+2) 104 | 105 | 106 | 107 | if __name__ == '__main__': 108 | unittest.main() -------------------------------------------------------------------------------- /KnowledgeBase/main.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import read, copy 3 | from logical_classes import * 4 | from function import KnowledgeBase 5 | 6 | class KBTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | # Assert starter facts 10 | file = 'statements_kb4.txt' 11 | self.data = read.read_tokenize(file) 12 | data = read.read_tokenize(file) 13 | self.KB = KnowledgeBase([], []) 14 | for item in data: 15 | if isinstance(item, Fact) or isinstance(item, Rule): 16 | self.KB.kb_assert(item) 17 | 18 | def test1(self): 19 | ask1 = read.parse_input("fact: (motherof ada ?X)") 20 | print(' Asking if', ask1) 21 | answer = self.KB.kb_ask(ask1) 22 | self.assertEqual(str(answer[0]), "?X : bing") 23 | 24 | def test2(self): 25 | # Can fc_infer actually infer 26 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 27 | print(' Asking if', ask1) 28 | answer = self.KB.kb_ask(ask1) 29 | self.assertEqual(str(answer[0]), "?X : felix") 30 | self.assertEqual(str(answer[1]), "?X : chen") 31 | 32 | def test3(self): 33 | # Does retract actually retract things 34 | r1 = read.parse_input("fact: (motherof ada bing)") 35 | print(' Retracting', r1) 36 | self.KB.kb_retract(r1) 37 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 38 | print(' Asking if', ask1) 39 | answer = self.KB.kb_ask(ask1) 40 | self.assertEqual(len(answer), 1) 41 | self.assertEqual(str(answer[0]), "?X : felix") 42 | 43 | def test4(self): 44 | # makes sure retract does not retract supported fact 45 | ask1 = read.parse_input("fact: (grandmotherof ada ?X)") 46 | print(' Asking if', ask1) 47 | answer = self.KB.kb_ask(ask1) 48 | self.assertEqual(str(answer[0]), "?X : felix") 49 | self.assertEqual(str(answer[1]), "?X : chen") 50 | 51 | r1 = read.parse_input("fact: (grandmotherof ada chen)") 52 | print(' Retracting', r1) 53 | self.KB.kb_retract(r1) 54 | 55 | print(' Asking if', ask1) 56 | answer = self.KB.kb_ask(ask1) 57 | self.assertEqual(str(answer[0]), "?X : felix") 58 | self.assertEqual(str(answer[1]), "?X : chen") 59 | 60 | def test5(self): 61 | # makes sure retract does not deal with rules 62 | ask1 = read.parse_input("fact: (parentof ada ?X)") 63 | print(' Asking if', ask1) 64 | answer = self.KB.kb_ask(ask1) 65 | self.assertEqual(str(answer[0]), "?X : bing") 66 | r1 = read.parse_input("rule: ((motherof ?x ?y)) -> (parentof ?x ?y)") 67 | print(' Retracting', r1) 68 | self.KB.kb_retract(r1) 69 | print(' Asking if', ask1) 70 | answer = self.KB.kb_ask(ask1) 71 | self.assertEqual(str(answer[0]), "?X : bing") 72 | 73 | 74 | def pprint_justification(answer): 75 | """Pretty prints (hence pprint) justifications for the answer. 76 | """ 77 | if not answer: print('Answer is False, no justification') 78 | else: 79 | print('\nJustification:') 80 | for i in range(0,len(answer.list_of_bindings)): 81 | # print bindings 82 | print(answer.list_of_bindings[i][0]) 83 | # print justifications 84 | for fact_rule in answer.list_of_bindings[i][1]: 85 | pprint_support(fact_rule,0) 86 | print 87 | 88 | def pprint_support(fact_rule, indent): 89 | """Recursive pretty printer helper to nicely indent 90 | """ 91 | if fact_rule: 92 | print(' '*indent, "Support for") 93 | 94 | if isinstance(fact_rule, Fact): 95 | print(fact_rule.statement) 96 | else: 97 | print(fact_rule.lhs, "->", fact_rule.rhs) 98 | 99 | if fact_rule.supported_by: 100 | for pair in fact_rule.supported_by: 101 | print(' '*(indent+1), "support option") 102 | for next in pair: 103 | pprint_support(next, indent+2) 104 | 105 | 106 | 107 | if __name__ == '__main__': 108 | unittest.main() -------------------------------------------------------------------------------- /function.py: -------------------------------------------------------------------------------- 1 | import read, copy 2 | from util import * 3 | from logical_classes import * 4 | 5 | verbose = 0 6 | 7 | class KnowledgeBase(object): 8 | def __init__(self, facts=[], rules=[]): 9 | self.facts = facts 10 | self.rules = rules 11 | self.ie = InferenceEngine() 12 | 13 | def __repr__(self): 14 | return 'KnowledgeBase({!r}, {!r})'.format(self.facts, self.rules) 15 | 16 | def __str__(self): 17 | string = "Knowledge Base: \n" 18 | string += "\n".join((str(fact) for fact in self.facts)) + "\n" 19 | string += "\n".join((str(rule) for rule in self.rules)) 20 | return string 21 | 22 | def _get_fact(self, fact): 23 | """INTERNAL USE ONLY 24 | Get the fact in the KB that is the same as the fact argument 25 | 26 | Args: 27 | fact (Fact): Fact we're searching for 28 | 29 | Returns: 30 | Fact: matching fact 31 | """ 32 | for kbfact in self.facts: 33 | if fact == kbfact: 34 | return kbfact 35 | 36 | def _get_rule(self, rule): 37 | """INTERNAL USE ONLY 38 | Get the rule in the KB that is the same as the rule argument 39 | 40 | Args: 41 | rule (Rule): Rule we're searching for 42 | 43 | Returns: 44 | Rule: matching rule 45 | """ 46 | for kbrule in self.rules: 47 | if rule == kbrule: 48 | return kbrule 49 | 50 | def kb_add(self, fact_rule): 51 | """Add a fact or rule to the KB 52 | Args: 53 | fact_rule (Fact|Rule) - the fact or rule to be added 54 | Returns: 55 | None 56 | """ 57 | printv("Adding {!r}", 1, verbose, [fact_rule]) 58 | if isinstance(fact_rule, Fact): 59 | if fact_rule not in self.facts: 60 | self.facts.append(fact_rule) 61 | for rule in self.rules: 62 | self.ie.fc_infer(fact_rule, rule, self) 63 | else: 64 | if fact_rule.supported_by: 65 | ind = self.facts.index(fact_rule) 66 | for f in fact_rule.supported_by: 67 | self.facts[ind].supported_by.append(f) 68 | else: 69 | ind = self.facts.index(fact_rule) 70 | self.facts[ind].asserted = True 71 | elif isinstance(fact_rule, Rule): 72 | if fact_rule not in self.rules: 73 | self.rules.append(fact_rule) 74 | for fact in self.facts: 75 | self.ie.fc_infer(fact, fact_rule, self) 76 | else: 77 | if fact_rule.supported_by: 78 | ind = self.rules.index(fact_rule) 79 | for f in fact_rule.supported_by: 80 | self.rules[ind].supported_by.append(f) 81 | else: 82 | ind = self.rules.index(fact_rule) 83 | self.rules[ind].asserted = True 84 | 85 | def kb_assert(self, fact_rule): 86 | """Assert a fact or rule into the KB 87 | 88 | Args: 89 | fact_rule (Fact or Rule): Fact or Rule we're asserting 90 | """ 91 | printv("Asserting {!r}", 0, verbose, [fact_rule]) 92 | self.kb_add(fact_rule) 93 | 94 | def kb_ask(self, fact): 95 | """Ask if a fact is in the KB 96 | 97 | Args: 98 | fact (Fact) - Statement to be asked (will be converted into a Fact) 99 | 100 | Returns: 101 | listof Bindings|False - list of Bindings if result found, False otherwise 102 | """ 103 | print("Asking {!r}".format(fact)) 104 | if factq(fact): 105 | f = Fact(fact.statement) 106 | bindings_lst = ListOfBindings() 107 | # ask matched facts 108 | for fact in self.facts: 109 | binding = match(f.statement, fact.statement) 110 | if binding: 111 | bindings_lst.add_bindings(binding, [fact]) 112 | 113 | return bindings_lst if bindings_lst.list_of_bindings else [] 114 | 115 | else: 116 | print("Invalid ask:", fact.statement) 117 | return [] 118 | 119 | def kb_retract(self, fact_or_rule): 120 | """Retract a fact from the KB 121 | 122 | Args: 123 | fact (Fact) - Fact to be retracted 124 | 125 | Returns: 126 | None 127 | """ 128 | printv("Retracting {!r}", 0, verbose, [fact_or_rule]) 129 | if len(fact_or_rule.supported_by) != 0: 130 | return None 131 | 132 | #if rule 133 | if isinstance(fact_or_rule, Rule): 134 | if fact_or_rule in self.rules and len(fact_or_rule.supported_by) == 0: 135 | self.rules.remove(fact_or_rule) 136 | 137 | #if fact 138 | if isinstance(fact_or_rule, Fact): 139 | flag = False 140 | for x in self.facts: 141 | if fact_or_rule.statement == x.statement: 142 | fact_or_rule = x 143 | flag = True 144 | break 145 | if flag == False: 146 | return None 147 | if len(fact_or_rule.supported_by) == 0: 148 | self.facts.remove(fact_or_rule) 149 | 150 | 151 | #search all the supports_facts 152 | for temp in fact_or_rule.supports_facts: 153 | #if temp.asserted == True: 154 | # continue 155 | templen = 0 156 | standard = len(temp.supported_by) 157 | for x in temp.supported_by: 158 | if fact_or_rule in x: 159 | temp.supported_by.remove(x) 160 | templen += 1 161 | if standard == templen: 162 | #temp.supported_by = [] 163 | self.kb_retract(temp) 164 | ''' 165 | if len(temp.supported_by) == 1: 166 | self.kb_retract(temp) 167 | else: 168 | for pair in temp.supported_by: 169 | if pair[0] == fact_or_rule or pair[1] == fact_or_rule: 170 | temp.supported_by.remove(pair) 171 | print('remove supported_by', temp, pair) 172 | ''' 173 | #search all the supports_rules 174 | for temp in fact_or_rule.supports_rules: 175 | templen = 0 176 | standard = len(temp.supported_by) 177 | for y in temp.supported_by: 178 | if fact_or_rule in y: 179 | temp.supported_by.remove(y) 180 | templen += 1 181 | if standard == templen: 182 | #temp.supported_by = [] 183 | self.kb_retract(temp) 184 | ''' 185 | if len(temp.supported_by) == 1: 186 | self.rules.remove(temp) 187 | print('remove rule', temp) 188 | else: 189 | for pair in temp.supported_by: 190 | if pair[0] == fact_or_rule or pair[1] == fact_or_rule: 191 | temp.supported_by.remove(pair) 192 | print('remove supported_by', temp, pair) 193 | ''' 194 | 195 | 196 | class InferenceEngine(object): 197 | def fc_infer(self, fact, rule, kb): 198 | """Forward-chaining to infer new facts and rules 199 | 200 | Args: 201 | fact (Fact) - A fact from the KnowledgeBase 202 | rule (Rule) - A rule from the KnowledgeBase 203 | kb (KnowledgeBase) - A KnowledgeBase 204 | 205 | Returns: 206 | Nothing 207 | """ 208 | printv('Attempting to infer from {!r} and {!r} => {!r}', 1, verbose, 209 | [fact.statement, rule.lhs, rule.rhs]) 210 | #get bingdings 211 | bindings = match(rule.lhs[0], fact.statement) 212 | if bindings == False: 213 | return None 214 | #only one lhs 215 | if len(rule.lhs) == 1: 216 | newfact = Fact(instantiate(rule.rhs, bindings), [[rule, fact]]) 217 | rule.supports_facts.append(newfact) 218 | fact.supports_facts.append(newfact) 219 | kb.kb_add(newfact) 220 | #more than one lhs 221 | else: 222 | locallhs = [] 223 | localrule = [] 224 | for i in range(1, len(rule.lhs)): 225 | locallhs.append(instantiate(rule.lhs[i], bindings)) 226 | localrule.append(locallhs) 227 | localrule.append(instantiate(rule.rhs, bindings)) 228 | newrule = Rule(localrule,[[rule, fact]]) 229 | rule.supports_rules.append(newrule) 230 | fact.supports_rules.append(newrule) 231 | kb.kb_add(newrule) -------------------------------------------------------------------------------- /KnowledgeBase/function.py: -------------------------------------------------------------------------------- 1 | import read, copy 2 | from util import * 3 | from logical_classes import * 4 | 5 | verbose = 0 6 | 7 | class KnowledgeBase(object): 8 | def __init__(self, facts=[], rules=[]): 9 | self.facts = facts 10 | self.rules = rules 11 | self.ie = InferenceEngine() 12 | 13 | def __repr__(self): 14 | return 'KnowledgeBase({!r}, {!r})'.format(self.facts, self.rules) 15 | 16 | def __str__(self): 17 | string = "Knowledge Base: \n" 18 | string += "\n".join((str(fact) for fact in self.facts)) + "\n" 19 | string += "\n".join((str(rule) for rule in self.rules)) 20 | return string 21 | 22 | def _get_fact(self, fact): 23 | """INTERNAL USE ONLY 24 | Get the fact in the KB that is the same as the fact argument 25 | 26 | Args: 27 | fact (Fact): Fact we're searching for 28 | 29 | Returns: 30 | Fact: matching fact 31 | """ 32 | for kbfact in self.facts: 33 | if fact == kbfact: 34 | return kbfact 35 | 36 | def _get_rule(self, rule): 37 | """INTERNAL USE ONLY 38 | Get the rule in the KB that is the same as the rule argument 39 | 40 | Args: 41 | rule (Rule): Rule we're searching for 42 | 43 | Returns: 44 | Rule: matching rule 45 | """ 46 | for kbrule in self.rules: 47 | if rule == kbrule: 48 | return kbrule 49 | 50 | def kb_add(self, fact_rule): 51 | """Add a fact or rule to the KB 52 | Args: 53 | fact_rule (Fact|Rule) - the fact or rule to be added 54 | Returns: 55 | None 56 | """ 57 | printv("Adding {!r}", 1, verbose, [fact_rule]) 58 | if isinstance(fact_rule, Fact): 59 | if fact_rule not in self.facts: 60 | self.facts.append(fact_rule) 61 | for rule in self.rules: 62 | self.ie.fc_infer(fact_rule, rule, self) 63 | else: 64 | if fact_rule.supported_by: 65 | ind = self.facts.index(fact_rule) 66 | for f in fact_rule.supported_by: 67 | self.facts[ind].supported_by.append(f) 68 | else: 69 | ind = self.facts.index(fact_rule) 70 | self.facts[ind].asserted = True 71 | elif isinstance(fact_rule, Rule): 72 | if fact_rule not in self.rules: 73 | self.rules.append(fact_rule) 74 | for fact in self.facts: 75 | self.ie.fc_infer(fact, fact_rule, self) 76 | else: 77 | if fact_rule.supported_by: 78 | ind = self.rules.index(fact_rule) 79 | for f in fact_rule.supported_by: 80 | self.rules[ind].supported_by.append(f) 81 | else: 82 | ind = self.rules.index(fact_rule) 83 | self.rules[ind].asserted = True 84 | 85 | def kb_assert(self, fact_rule): 86 | """Assert a fact or rule into the KB 87 | 88 | Args: 89 | fact_rule (Fact or Rule): Fact or Rule we're asserting 90 | """ 91 | printv("Asserting {!r}", 0, verbose, [fact_rule]) 92 | self.kb_add(fact_rule) 93 | 94 | def kb_ask(self, fact): 95 | """Ask if a fact is in the KB 96 | 97 | Args: 98 | fact (Fact) - Statement to be asked (will be converted into a Fact) 99 | 100 | Returns: 101 | listof Bindings|False - list of Bindings if result found, False otherwise 102 | """ 103 | print("Asking {!r}".format(fact)) 104 | if factq(fact): 105 | f = Fact(fact.statement) 106 | bindings_lst = ListOfBindings() 107 | # ask matched facts 108 | for fact in self.facts: 109 | binding = match(f.statement, fact.statement) 110 | if binding: 111 | bindings_lst.add_bindings(binding, [fact]) 112 | 113 | return bindings_lst if bindings_lst.list_of_bindings else [] 114 | 115 | else: 116 | print("Invalid ask:", fact.statement) 117 | return [] 118 | 119 | def kb_retract(self, fact_or_rule): 120 | """Retract a fact from the KB 121 | 122 | Args: 123 | fact (Fact) - Fact to be retracted 124 | 125 | Returns: 126 | None 127 | """ 128 | printv("Retracting {!r}", 0, verbose, [fact_or_rule]) 129 | if len(fact_or_rule.supported_by) != 0: 130 | return None 131 | 132 | #if rule 133 | if isinstance(fact_or_rule, Rule): 134 | if fact_or_rule in self.rules and len(fact_or_rule.supported_by) == 0: 135 | self.rules.remove(fact_or_rule) 136 | 137 | #if fact 138 | if isinstance(fact_or_rule, Fact): 139 | flag = False 140 | for x in self.facts: 141 | if fact_or_rule.statement == x.statement: 142 | fact_or_rule = x 143 | flag = True 144 | break 145 | if flag == False: 146 | return None 147 | if len(fact_or_rule.supported_by) == 0: 148 | self.facts.remove(fact_or_rule) 149 | 150 | 151 | #search all the supports_facts 152 | for temp in fact_or_rule.supports_facts: 153 | #if temp.asserted == True: 154 | # continue 155 | templen = 0 156 | standard = len(temp.supported_by) 157 | for x in temp.supported_by: 158 | if fact_or_rule in x: 159 | temp.supported_by.remove(x) 160 | templen += 1 161 | if standard == templen: 162 | #temp.supported_by = [] 163 | self.kb_retract(temp) 164 | ''' 165 | if len(temp.supported_by) == 1: 166 | self.kb_retract(temp) 167 | else: 168 | for pair in temp.supported_by: 169 | if pair[0] == fact_or_rule or pair[1] == fact_or_rule: 170 | temp.supported_by.remove(pair) 171 | print('remove supported_by', temp, pair) 172 | ''' 173 | #search all the supports_rules 174 | for temp in fact_or_rule.supports_rules: 175 | templen = 0 176 | standard = len(temp.supported_by) 177 | for y in temp.supported_by: 178 | if fact_or_rule in y: 179 | temp.supported_by.remove(y) 180 | templen += 1 181 | if standard == templen: 182 | #temp.supported_by = [] 183 | self.kb_retract(temp) 184 | ''' 185 | if len(temp.supported_by) == 1: 186 | self.rules.remove(temp) 187 | print('remove rule', temp) 188 | else: 189 | for pair in temp.supported_by: 190 | if pair[0] == fact_or_rule or pair[1] == fact_or_rule: 191 | temp.supported_by.remove(pair) 192 | print('remove supported_by', temp, pair) 193 | ''' 194 | 195 | 196 | class InferenceEngine(object): 197 | def fc_infer(self, fact, rule, kb): 198 | """Forward-chaining to infer new facts and rules 199 | 200 | Args: 201 | fact (Fact) - A fact from the KnowledgeBase 202 | rule (Rule) - A rule from the KnowledgeBase 203 | kb (KnowledgeBase) - A KnowledgeBase 204 | 205 | Returns: 206 | Nothing 207 | """ 208 | printv('Attempting to infer from {!r} and {!r} => {!r}', 1, verbose, 209 | [fact.statement, rule.lhs, rule.rhs]) 210 | #get bingdings 211 | bindings = match(rule.lhs[0], fact.statement) 212 | if bindings == False: 213 | return None 214 | #only one lhs 215 | if len(rule.lhs) == 1: 216 | newfact = Fact(instantiate(rule.rhs, bindings), [[rule, fact]]) 217 | rule.supports_facts.append(newfact) 218 | fact.supports_facts.append(newfact) 219 | kb.kb_add(newfact) 220 | #more than one lhs 221 | else: 222 | locallhs = [] 223 | localrule = [] 224 | for i in range(1, len(rule.lhs)): 225 | locallhs.append(instantiate(rule.lhs[i], bindings)) 226 | localrule.append(locallhs) 227 | localrule.append(instantiate(rule.rhs, bindings)) 228 | newrule = Rule(localrule,[[rule, fact]]) 229 | rule.supports_rules.append(newrule) 230 | fact.supports_rules.append(newrule) 231 | kb.kb_add(newrule) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Knowledge Base 2 | 3 | I write and extend a knowledge base (KB) and an inference engine. The knowledge base supports five main interfaces: `Assert`, `Retract`, and `Ask`. 4 | 5 | - `Assert`: Add facts or rules into the knowledge base. After you add facts or rules into the KB, the forward-chaining algorithm is used to infer other facts or rules. 6 | - `Ask`: ask queries and return a list of bindings for facts. 7 | - `Retract`: remove facts from the knowledge base. Also, remove all other facts or rules that are dependent on the removed fact or rule. 8 | 9 | ## Code 10 | 11 | Ther're five files: `main.py`, `logical_classes.py`, `read.py`, `util.py` and `function.py`. 12 | 13 | - `main.py` contains code for testing the KnowledgeBase 14 | - `function.py` contains the `KnowledgeBase` and `InferenceEngine` classes. 15 | - `logical_classes.py` contains classes for each type of logical component, e.g. `Fact`, `Rule`, etc. 16 | - `util.py` contains several useful helper functions 17 | - `read.py` contains functions that read statements from files or terminal. 18 | 19 | There are also two data files: `statements_kb.txt` and `statements_kb2.txt`. These files contain the facts and rules to be inserted into the KB.(some test cases.) 20 | 21 | some mian functions: 22 | 23 | 1. the `KnowledgeBase.kb_assert` method: 24 | Implement storing facts in the KB 25 | 2. the `KnowledgeBase.kb_ask` method: 26 | Implement retrieving facts from the KB 27 | 3. the `InferenceEnginer.fc_infer` method: 28 | Implement the forward-chaining inferences that occurs upon asserting facts and rules into the KB. 29 | 4. the `KnowledgeBase.kb_retract` method: 30 | Implement the `Retract` interface to remove facts from the KB. 31 | 32 | ## Storing facts 33 | 34 | Storing facts simply puts any facts received into a list of facts. Be careful to only put a Fact in the list and not just anything (i.e., check that the argument is a Fact). Note that we expect the facts to be stored in a list (and not a set) to ensure that they are retrieved in a deterministic order. This is not criticial to the function of the KB but is essential for the Auto Grader. 35 | 36 | ## Retrieving facts 37 | 38 | The key idea is to find any facts in the KB that match the "asked" for fact. Since the queried fact may contain a variable, matching facts might not be exact matches. To help in finding matching facts, we provide a `match` method in `util.py`. If a pair of facts match, then this method will return the `Bindings` that make the statements unify. 39 | 40 | `Bindings` is a list of pairs (binding), where each pair is a variable (e.g., '?X') and a value (e.g., 'red'). Since it is a list, there may be multiple pairs. Actually, there needs to be exactly one binding for each variable. For example, in asking for '(color ?X red)', there will be only one binding, the one for '?X'. But the query for '(color ?X ?Y)'' will result in bindings for '?X' and '?Y'. See test 5 for an example of bindings containing more than one variable. 41 | 42 | Since there may be many facts that match a queried fact, `kb_ask` needs to return a `ListOfBindings` (or False if no matching facts). The ListOfBindings is exactly as the name implies, a list of Bindings, packaged up in a class with convenient accessors and such. See tests 3 and 5 for examples of multiple bindings being returned from `kb_ask`. 43 | 44 | ### Rule currying in `fc_infer` 45 | 46 | The key idea is that we don't just infer new facts - we can infer new rules. 47 | 48 | When we add a new fact to the KB, we check to see if it triggers any rule(s). When we add a new rule, we check to see if it's triggered by existing facts. 49 | 50 | However, a rule might have multiple statements on its left-hand side (LHS), and we don't want to iterate each of these statements every time we add a new fact to the KB. Instead, we'll employ a cool trick. Whenever we add a new rule, we'll only check the first element of the LHS of that rule against the facts in our KB. (If we add a new fact, we'll reverse this - we'll examine each rule in our KB, and check the first element of its LHS against this new fact.) If there's a match with this first element, we'll add a new rule paired with *bindings* for that match. 51 | 52 | For example, imagine a box-world. Consider a rule stating that if a box `?x` is larger than another box `?y`, and box `?x` is on box `?y`, then box `?y` is covered. Formally, that looks like: 53 | 54 | ``` 55 | ((sizeIsLess(?y, ?x), on(?x, ?y)) => covered(?y)) 56 | ``` 57 | 58 | Now imagine that we know that box `A` is bigger than box `B`; i.e. that we have the fact `sizeIsLess(B, A)` in the KB. The above rule then matches, with the bindings `((?x: A, ?y: B))`. With that binding in place, we can now infer a new rule that uses it: 59 | 60 | ``` 61 | (on(A, B)) => covered(B) 62 | ``` 63 | 64 | If we find the fact `on(A, B)` in the KB, then we could use this rule to infer the fact `covered(B)`. If we don't have that fact, however, we now have a simple rule that will let us make the inference easily if we see that fact in the future. 65 | 66 | ### Removing rules and facts inferred from a removed fact 67 | 68 | When you remove a fact, you also need to remove all facts and rules that were inferred using this fact. However, a given fact/rule might be supported by multiple facts - so, you'll need to check whether the facts/rules inferred from this fact are also supported by other facts (or if they were directly asserted). 69 | 70 | As a simplification, you can assume that **no rules will create circular dependencies**. E.g., imagine a situation like `A => B`, `B => C`, and `C => B`. Removing `A` would mean removing `B` and `C`, since they depend on `A` via those rules. However, implementing that would get messy, since `B` and `C` depend on each other. You will **NOT** be given scenarios like this. 71 | 72 | ### Testing 73 | 74 | To test it, I create several testing files that contain a bunch of facts and rules (similar to the ones provided). Each fact/rule will be asserted one-by-one into the KB. Other files containing more facts/rules will be used to test the `Retract` operation, and make sure it worked correctly. 75 | 76 | 77 | #### Implementing `fc_infer` 78 | 79 | - Use the `util.match` function to do unification and create possible bindings 80 | - Use the `util.instantiate` function to bind a variable in the rest of a rule 81 | - `Rule`s and `Fact`s have fields for `supported_by`, `supports_facts`, and `supports_rules`. Use them to track inferences! For example, imagine that a fact `F` and rule `R` matched to infer a new fact/rule `fr`. 82 | - `fr` is *supported* by `F` and `R`. Add them to `fr`'s `supported_by` list - you can do this by passing them as a constructor argument when creating `fr`. 83 | - `F` and `R` now *support* `fr`. Add `fr` to the `supports_rules` and `supports_facts` lists (as appropriate) in `F` and `R`. 84 | 85 | #### Implementing `kb_retract` 86 | 87 | - A fact should only be removed if it is unsupported. 88 | - Use the `supports_rules` and `supports_facts` fields to find and adjust facts and rules that are supported by a retracted fact. 89 | - The `supported_by` lists in each fact/rule that it supports needs to be adjusted accordingly. 90 | - If a supported fact/rule is no longer supported as a result of retracting this fact (and is not asserted), it should also be removed. 91 | 92 | \pagebreak 93 | 94 | ## Appendix: File Breakdown 95 | 96 | Below is a description of each included file and the classes contained within each including a listing of their attributes. Each file has documentation in the code reflecting the information below (in most cases they are exactly the same). 97 | 98 | Attributes of each class are listed in the following format (_Note:_ if you see a type like `Fact|Rule` the `|` type is `or` and mean the type can be either Fact or Rule): 99 | 100 | - `field_name` (`type`) - text description 101 | 102 | ### logical_classes.py 103 | 104 | This file defines all basic structure classes. 105 | 106 | #### Fact 107 | 108 | Represents a fact in our knowledge base. Has a statement containing the content of the fact, e.g. (isa Sorceress Wizard) and fields tracking which facts/rules in the KB it supports and is supported by. 109 | 110 | **Attributes** 111 | 112 | - `name` (`str`): 'fact', the name of this class 113 | - `statement` (`Statement`): statement of this fact, basically what the fact actually says 114 | - `asserted` (`bool`): flag indicating if fact was asserted instead of inferred from other rules in the KB 115 | - `supported_by` (`listof Fact|Rule`): Facts/Rules that allow inference of the statement 116 | - `supports_facts` (`listof Fact`): Facts that this fact supports 117 | - `supports_rules` (`listof Rule`): Rules that this fact supports 118 | 119 | #### Rule 120 | 121 | Represents a rule in our knowledge base. Has a list of statements (the LHS) containing the statements that need to be in our KB for us to infer the RHS statement. Also has fields tracking which facts/rules in the KB it supports and is supported by. 122 | 123 | **Attributes** 124 | 125 | - `name` (`str`): 'rule', the name of this class 126 | - `lhs` (`listof Statement`): LHS statements of this rule 127 | - `rhs` (`Statement`): RHS statment of this rule 128 | - `asserted` (`bool`): flag indicating if rule was asserted instead of inferred from other rules/facts in the KB 129 | - `supported_by` (`listof Fact|Rule`): Facts/Rules that allow inference of the statement 130 | - `supports_facts` (`listof Fact`): Facts that this rule supports 131 | - `supports_rules` (`listof Rule`): Rules that this rule supports 132 | 133 | #### Statement 134 | 135 | Represents a statement in our knowledge base, e.g. (attacked Ai Nosliw), (diamonds Loot), (isa Sorceress Wizard), etc. These statements show up in Facts or on the LHS and RHS of Rules. 136 | 137 | **Attributes** 138 | 139 | - `predicate` (`str`) - the predicate of the statement, e.g. isa, hero, needs 140 | - `terms` (`listof Term`) - list of terms (Variable or Constant) in the statement, e.g. `'Nosliw'` or `'?d'` 141 | 142 | #### Term 143 | 144 | Represents a term (a Variable or Constant) in our knowledge base. Can sorta be thought of as a super class of Variable and Constant, though there is no actual inheritance implemented in the code. 145 | 146 | **Attributes** 147 | 148 | - `term` (`Variable|Constant`) - The Variable or Constant that this term holds (represents) 149 | 150 | #### Variable 151 | 152 | Represents a variable used in statements, e.g. `?x`. 153 | 154 | **Attributes** 155 | 156 | - `element` (`str`): The name of the variable, e.g. `'?x'` 157 | 158 | #### Constant 159 | 160 | Represents a constant used in statements 161 | 162 | **Attributes** 163 | 164 | - `element` (`str`): The value of the constant, e.g. `'Nosliw'` 165 | 166 | #### Binding 167 | 168 | Represents a binding of a constant to a variable, e.g. `'Nosliw'` might be bound to `'?d'` 169 | 170 | **Attributes** 171 | 172 | - `variable` (`str`): The name of the variable associated with this binding, e.g. `'?d'` 173 | - `constant` (`str`): The value of the variable, e.g. `'Nosliw'` 174 | 175 | #### Bindings 176 | 177 | Represents Binding(s) used while matching two statements 178 | 179 | **Attributes** 180 | 181 | - `bindings` (`listof Bindings`) - bindings involved in match 182 | - `bindings_dict` (`dictof Bindings`) - bindings involved in match where key is bound variable and value is bound value, e.g. some_bindings.bindings_dict['?d'] => 'Nosliw' 183 | 184 | **Methods** 185 | 186 | - `add_binding(variable, value)` (`(Variable, Constant) => void`) - add a binding from a variable to a value 187 | - `bound_to(variable)` (`(Variable) => Variable|Constant|False`) - check if variable is bound. If so return value bound to it, else False 188 | - `test_and_bind(variable_verm,value_term)` (`(Term, Term) => bool`) - Check if variable_term already bound. If so return whether or not passed in value_term matches bound value. If not, add binding between variable_terma and value_term and return True. 189 | 190 | #### ListOfBindings 191 | 192 | Container for multiple Bindings 193 | 194 | **Methods** 195 | 196 | - `add_bindings(bindings, facts_rules)` - (`(Bindings, listof Fact|Rule) => void`) - add given bindings to list of Bindings along with associated rules or facts 197 | 198 | ### read.py 199 | 200 | This file has no classes but defines useful helper functions for reading input from the user or a file. 201 | 202 | **Functions** 203 | 204 | - `read_tokenize(file)` - (`(str) => (listof Fact, listof Rule)`) - takes a filename, reads the file and returns a fact list and rule list. 205 | - `read_from_input(message)` - (`(str) => str`) - collects user input from the command line. 206 | - `parse_input(e)` - (`(str) => (int, str | listof str)`) - parses input, cleaning it as it does and assigning labels 207 | - `get_new_fact_or_rule()` - (`() => Fact | Rule`) - get a new fact or rule by typing, nothing passed in, data comes from user input 208 | - `get_new_statements()` - (`() => listof Statement`) - read statements from input, nothing passed in, data comes from user input 209 | 210 | ### util.py 211 | 212 | This file has no classes but defines useful helper functions. 213 | 214 | **Functions** 215 | 216 | - `is_var(var)` (`(str|Variable|Constant|Term) => bool`) - check whether an element is a variable (either instance of Variable or string starting with `'?'`, e.g. `'?d'`) 217 | - `match(state1, state2, bindings=None)` (`(Statement, Statement, Bindings) => Bindings|False`) - match two statements and return the associated bindings or False if there is no binding 218 | - `match_recursive(terms1, terms2, bindings)` (`(listof Term, listof Term, Bindings) => Bindings|False`) - recursive helper for match 219 | - `instantiate(statement, bindings)` (`(Statement, Bindings) => Statement|Term`) - generate Statement from given statement and bindings. Constructed statement has bound values for variables if they exist in bindings. 220 | - `vprint(message, level, verbose, data=[])` (`(str, int, int, listof any) => void`) - prints message if verbose > level, if data provided then formats message with given data 221 | 222 | #### KnowledgeBase 223 | 224 | Represents a knowledge base and implements the three actions described in the writeup (`Assert`, `Retract` and `Ask`) 225 | 226 | #### InferenceEngine 227 | 228 | Represents an inference engine. -------------------------------------------------------------------------------- /KnowledgeBase/README.md: -------------------------------------------------------------------------------- 1 | # Knowledge Base 2 | 3 | I write and extend a knowledge base (KB) and an inference engine. The knowledge base supports five main interfaces: `Assert`, `Retract`, and `Ask`. 4 | 5 | - `Assert`: Add facts or rules into the knowledge base. After you add facts or rules into the KB, the forward-chaining algorithm is used to infer other facts or rules. 6 | - `Ask`: ask queries and return a list of bindings for facts. 7 | - `Retract`: remove facts from the knowledge base. Also, remove all other facts or rules that are dependent on the removed fact or rule. 8 | 9 | ## Code 10 | 11 | Ther're five files: `main.py`, `logical_classes.py`, `read.py`, `util.py` and `function.py`. 12 | 13 | - `main.py` contains code for testing the KnowledgeBase 14 | - `function.py` contains the `KnowledgeBase` and `InferenceEngine` classes. 15 | - `logical_classes.py` contains classes for each type of logical component, e.g. `Fact`, `Rule`, etc. 16 | - `util.py` contains several useful helper functions 17 | - `read.py` contains functions that read statements from files or terminal. 18 | 19 | There are also two data files: `statements_kb.txt` and `statements_kb2.txt`. These files contain the facts and rules to be inserted into the KB.(some test cases.) 20 | 21 | some mian functions: 22 | 23 | 1. the `KnowledgeBase.kb_assert` method: 24 | Implement storing facts in the KB 25 | 2. the `KnowledgeBase.kb_ask` method: 26 | Implement retrieving facts from the KB 27 | 3. the `InferenceEnginer.fc_infer` method: 28 | Implement the forward-chaining inferences that occurs upon asserting facts and rules into the KB. 29 | 4. the `KnowledgeBase.kb_retract` method: 30 | Implement the `Retract` interface to remove facts from the KB. 31 | 32 | ## Storing facts 33 | 34 | Storing facts simply puts any facts received into a list of facts. Be careful to only put a Fact in the list and not just anything (i.e., check that the argument is a Fact). Note that we expect the facts to be stored in a list (and not a set) to ensure that they are retrieved in a deterministic order. This is not criticial to the function of the KB but is essential for the Auto Grader. 35 | 36 | ## Retrieving facts 37 | 38 | The key idea is to find any facts in the KB that match the "asked" for fact. Since the queried fact may contain a variable, matching facts might not be exact matches. To help in finding matching facts, we provide a `match` method in `util.py`. If a pair of facts match, then this method will return the `Bindings` that make the statements unify. 39 | 40 | `Bindings` is a list of pairs (binding), where each pair is a variable (e.g., '?X') and a value (e.g., 'red'). Since it is a list, there may be multiple pairs. Actually, there needs to be exactly one binding for each variable. For example, in asking for '(color ?X red)', there will be only one binding, the one for '?X'. But the query for '(color ?X ?Y)'' will result in bindings for '?X' and '?Y'. See test 5 for an example of bindings containing more than one variable. 41 | 42 | Since there may be many facts that match a queried fact, `kb_ask` needs to return a `ListOfBindings` (or False if no matching facts). The ListOfBindings is exactly as the name implies, a list of Bindings, packaged up in a class with convenient accessors and such. See tests 3 and 5 for examples of multiple bindings being returned from `kb_ask`. 43 | 44 | ### Rule currying in `fc_infer` 45 | 46 | The key idea is that we don't just infer new facts - we can infer new rules. 47 | 48 | When we add a new fact to the KB, we check to see if it triggers any rule(s). When we add a new rule, we check to see if it's triggered by existing facts. 49 | 50 | However, a rule might have multiple statements on its left-hand side (LHS), and we don't want to iterate each of these statements every time we add a new fact to the KB. Instead, we'll employ a cool trick. Whenever we add a new rule, we'll only check the first element of the LHS of that rule against the facts in our KB. (If we add a new fact, we'll reverse this - we'll examine each rule in our KB, and check the first element of its LHS against this new fact.) If there's a match with this first element, we'll add a new rule paired with *bindings* for that match. 51 | 52 | For example, imagine a box-world. Consider a rule stating that if a box `?x` is larger than another box `?y`, and box `?x` is on box `?y`, then box `?y` is covered. Formally, that looks like: 53 | 54 | ``` 55 | ((sizeIsLess(?y, ?x), on(?x, ?y)) => covered(?y)) 56 | ``` 57 | 58 | Now imagine that we know that box `A` is bigger than box `B`; i.e. that we have the fact `sizeIsLess(B, A)` in the KB. The above rule then matches, with the bindings `((?x: A, ?y: B))`. With that binding in place, we can now infer a new rule that uses it: 59 | 60 | ``` 61 | (on(A, B)) => covered(B) 62 | ``` 63 | 64 | If we find the fact `on(A, B)` in the KB, then we could use this rule to infer the fact `covered(B)`. If we don't have that fact, however, we now have a simple rule that will let us make the inference easily if we see that fact in the future. 65 | 66 | ### Removing rules and facts inferred from a removed fact 67 | 68 | When you remove a fact, you also need to remove all facts and rules that were inferred using this fact. However, a given fact/rule might be supported by multiple facts - so, you'll need to check whether the facts/rules inferred from this fact are also supported by other facts (or if they were directly asserted). 69 | 70 | As a simplification, you can assume that **no rules will create circular dependencies**. E.g., imagine a situation like `A => B`, `B => C`, and `C => B`. Removing `A` would mean removing `B` and `C`, since they depend on `A` via those rules. However, implementing that would get messy, since `B` and `C` depend on each other. You will **NOT** be given scenarios like this. 71 | 72 | ### Testing 73 | 74 | To test it, I create several testing files that contain a bunch of facts and rules (similar to the ones provided). Each fact/rule will be asserted one-by-one into the KB. Other files containing more facts/rules will be used to test the `Retract` operation, and make sure it worked correctly. 75 | 76 | 77 | #### Implementing `fc_infer` 78 | 79 | - Use the `util.match` function to do unification and create possible bindings 80 | - Use the `util.instantiate` function to bind a variable in the rest of a rule 81 | - `Rule`s and `Fact`s have fields for `supported_by`, `supports_facts`, and `supports_rules`. Use them to track inferences! For example, imagine that a fact `F` and rule `R` matched to infer a new fact/rule `fr`. 82 | - `fr` is *supported* by `F` and `R`. Add them to `fr`'s `supported_by` list - you can do this by passing them as a constructor argument when creating `fr`. 83 | - `F` and `R` now *support* `fr`. Add `fr` to the `supports_rules` and `supports_facts` lists (as appropriate) in `F` and `R`. 84 | 85 | #### Implementing `kb_retract` 86 | 87 | - A fact should only be removed if it is unsupported. 88 | - Use the `supports_rules` and `supports_facts` fields to find and adjust facts and rules that are supported by a retracted fact. 89 | - The `supported_by` lists in each fact/rule that it supports needs to be adjusted accordingly. 90 | - If a supported fact/rule is no longer supported as a result of retracting this fact (and is not asserted), it should also be removed. 91 | 92 | \pagebreak 93 | 94 | ## Appendix: File Breakdown 95 | 96 | Below is a description of each included file and the classes contained within each including a listing of their attributes. Each file has documentation in the code reflecting the information below (in most cases they are exactly the same). 97 | 98 | Attributes of each class are listed in the following format (_Note:_ if you see a type like `Fact|Rule` the `|` type is `or` and mean the type can be either Fact or Rule): 99 | 100 | - `field_name` (`type`) - text description 101 | 102 | ### logical_classes.py 103 | 104 | This file defines all basic structure classes. 105 | 106 | #### Fact 107 | 108 | Represents a fact in our knowledge base. Has a statement containing the content of the fact, e.g. (isa Sorceress Wizard) and fields tracking which facts/rules in the KB it supports and is supported by. 109 | 110 | **Attributes** 111 | 112 | - `name` (`str`): 'fact', the name of this class 113 | - `statement` (`Statement`): statement of this fact, basically what the fact actually says 114 | - `asserted` (`bool`): flag indicating if fact was asserted instead of inferred from other rules in the KB 115 | - `supported_by` (`listof Fact|Rule`): Facts/Rules that allow inference of the statement 116 | - `supports_facts` (`listof Fact`): Facts that this fact supports 117 | - `supports_rules` (`listof Rule`): Rules that this fact supports 118 | 119 | #### Rule 120 | 121 | Represents a rule in our knowledge base. Has a list of statements (the LHS) containing the statements that need to be in our KB for us to infer the RHS statement. Also has fields tracking which facts/rules in the KB it supports and is supported by. 122 | 123 | **Attributes** 124 | 125 | - `name` (`str`): 'rule', the name of this class 126 | - `lhs` (`listof Statement`): LHS statements of this rule 127 | - `rhs` (`Statement`): RHS statment of this rule 128 | - `asserted` (`bool`): flag indicating if rule was asserted instead of inferred from other rules/facts in the KB 129 | - `supported_by` (`listof Fact|Rule`): Facts/Rules that allow inference of the statement 130 | - `supports_facts` (`listof Fact`): Facts that this rule supports 131 | - `supports_rules` (`listof Rule`): Rules that this rule supports 132 | 133 | #### Statement 134 | 135 | Represents a statement in our knowledge base, e.g. (attacked Ai Nosliw), (diamonds Loot), (isa Sorceress Wizard), etc. These statements show up in Facts or on the LHS and RHS of Rules. 136 | 137 | **Attributes** 138 | 139 | - `predicate` (`str`) - the predicate of the statement, e.g. isa, hero, needs 140 | - `terms` (`listof Term`) - list of terms (Variable or Constant) in the statement, e.g. `'Nosliw'` or `'?d'` 141 | 142 | #### Term 143 | 144 | Represents a term (a Variable or Constant) in our knowledge base. Can sorta be thought of as a super class of Variable and Constant, though there is no actual inheritance implemented in the code. 145 | 146 | **Attributes** 147 | 148 | - `term` (`Variable|Constant`) - The Variable or Constant that this term holds (represents) 149 | 150 | #### Variable 151 | 152 | Represents a variable used in statements, e.g. `?x`. 153 | 154 | **Attributes** 155 | 156 | - `element` (`str`): The name of the variable, e.g. `'?x'` 157 | 158 | #### Constant 159 | 160 | Represents a constant used in statements 161 | 162 | **Attributes** 163 | 164 | - `element` (`str`): The value of the constant, e.g. `'Nosliw'` 165 | 166 | #### Binding 167 | 168 | Represents a binding of a constant to a variable, e.g. `'Nosliw'` might be bound to `'?d'` 169 | 170 | **Attributes** 171 | 172 | - `variable` (`str`): The name of the variable associated with this binding, e.g. `'?d'` 173 | - `constant` (`str`): The value of the variable, e.g. `'Nosliw'` 174 | 175 | #### Bindings 176 | 177 | Represents Binding(s) used while matching two statements 178 | 179 | **Attributes** 180 | 181 | - `bindings` (`listof Bindings`) - bindings involved in match 182 | - `bindings_dict` (`dictof Bindings`) - bindings involved in match where key is bound variable and value is bound value, e.g. some_bindings.bindings_dict['?d'] => 'Nosliw' 183 | 184 | **Methods** 185 | 186 | - `add_binding(variable, value)` (`(Variable, Constant) => void`) - add a binding from a variable to a value 187 | - `bound_to(variable)` (`(Variable) => Variable|Constant|False`) - check if variable is bound. If so return value bound to it, else False 188 | - `test_and_bind(variable_verm,value_term)` (`(Term, Term) => bool`) - Check if variable_term already bound. If so return whether or not passed in value_term matches bound value. If not, add binding between variable_terma and value_term and return True. 189 | 190 | #### ListOfBindings 191 | 192 | Container for multiple Bindings 193 | 194 | **Methods** 195 | 196 | - `add_bindings(bindings, facts_rules)` - (`(Bindings, listof Fact|Rule) => void`) - add given bindings to list of Bindings along with associated rules or facts 197 | 198 | ### read.py 199 | 200 | This file has no classes but defines useful helper functions for reading input from the user or a file. 201 | 202 | **Functions** 203 | 204 | - `read_tokenize(file)` - (`(str) => (listof Fact, listof Rule)`) - takes a filename, reads the file and returns a fact list and rule list. 205 | - `read_from_input(message)` - (`(str) => str`) - collects user input from the command line. 206 | - `parse_input(e)` - (`(str) => (int, str | listof str)`) - parses input, cleaning it as it does and assigning labels 207 | - `get_new_fact_or_rule()` - (`() => Fact | Rule`) - get a new fact or rule by typing, nothing passed in, data comes from user input 208 | - `get_new_statements()` - (`() => listof Statement`) - read statements from input, nothing passed in, data comes from user input 209 | 210 | ### util.py 211 | 212 | This file has no classes but defines useful helper functions. 213 | 214 | **Functions** 215 | 216 | - `is_var(var)` (`(str|Variable|Constant|Term) => bool`) - check whether an element is a variable (either instance of Variable or string starting with `'?'`, e.g. `'?d'`) 217 | - `match(state1, state2, bindings=None)` (`(Statement, Statement, Bindings) => Bindings|False`) - match two statements and return the associated bindings or False if there is no binding 218 | - `match_recursive(terms1, terms2, bindings)` (`(listof Term, listof Term, Bindings) => Bindings|False`) - recursive helper for match 219 | - `instantiate(statement, bindings)` (`(Statement, Bindings) => Statement|Term`) - generate Statement from given statement and bindings. Constructed statement has bound values for variables if they exist in bindings. 220 | - `vprint(message, level, verbose, data=[])` (`(str, int, int, listof any) => void`) - prints message if verbose > level, if data provided then formats message with given data 221 | 222 | #### KnowledgeBase 223 | 224 | Represents a knowledge base and implements the three actions described in the writeup (`Assert`, `Retract` and `Ask`) 225 | 226 | #### InferenceEngine 227 | 228 | Represents an inference engine. -------------------------------------------------------------------------------- /logical_classes.py: -------------------------------------------------------------------------------- 1 | from util import is_var 2 | 3 | class Fact(object): 4 | """Represents a fact in our knowledge base. Has a statement containing the 5 | content of the fact, e.g. (isa Sorceress Wizard) and fields tracking 6 | which facts/rules in the KB it supports and is supported by. 7 | 8 | Attributes: 9 | name (str): 'fact', the name of this class 10 | statement (Statement): statement of this fact, basically what the fact actually says 11 | asserted (bool): boolean flag indicating if fact was asserted instead of 12 | inferred from other rules/facts in the KB 13 | supported_by (listof Fact|Rule): Facts/Rules that allow inference of 14 | the statement 15 | supports_facts (listof Fact): Facts that this fact supports 16 | supports_rules (listof Rule): Rules that this fact supports 17 | """ 18 | def __init__(self, statement, supported_by=[]): 19 | """Constructor for Fact setting up useful flags and generating appropriate statement 20 | 21 | Args: 22 | statement (str|Statement): The statement of this fact, basically what the 23 | fact actually says 24 | supported_by (listof Fact|Rule): Facts/Rules that allow inference of 25 | the statement 26 | """ 27 | super(Fact, self).__init__() 28 | self.name = "fact" 29 | self.statement = statement if isinstance(statement, Statement) else Statement(statement) 30 | self.asserted = not supported_by 31 | #self.supported_by = supported_by 32 | self.supported_by = [] 33 | self.supports_facts = [] 34 | self.supports_rules = [] 35 | for pair in supported_by: 36 | self.supported_by.append(pair) 37 | 38 | def __repr__(self): 39 | """Define internal string representation 40 | """ 41 | return 'Fact({!r}, {!r}, {!r}, {!r}, {!r}, {!r})'.format( 42 | self.name, self.statement, 43 | self.asserted, self.supported_by, 44 | self.supports_facts, self.supports_rules) 45 | 46 | def __str__(self): 47 | """Define external representation when printed 48 | """ 49 | string = self.name + ":\n" 50 | string += "\t" + str(self.statement) + "\n" 51 | string += "\t Asserted: " + str(self.asserted) + "\n" 52 | if self.supported_by != []: 53 | name_strings = [str(x.name) for y in self.supported_by for x in y] 54 | supported_by_str = ", ".join(name_strings) 55 | string += "\t Supported by: [" + supported_by_str + "]\n" 56 | if self.supports_facts != []: 57 | name_strings = [str(x.name) for x in self.supports_facts] 58 | supports_f_str = ", ".join(name_strings) 59 | string += "\t Supports facts: [" + supports_f_str + "]\n" 60 | if self.supports_rules != []: 61 | name_strings = [str(x.name) for x in self.supports_rules] 62 | supports_r_str = ", ".join(name_strings) 63 | string += "\t Supports rules: [" + supports_r_str + "]\n" 64 | return string 65 | 66 | def __eq__(self, other): 67 | """Define behavior of == when applied to this object 68 | """ 69 | return isinstance(other, Fact) and self.statement == other.statement 70 | 71 | def __ne__(self, other): 72 | """Define behavior of != when applied to this object 73 | """ 74 | return not self == other 75 | 76 | class Rule(object): 77 | """Represents a rule in our knowledge base. Has a list of statements (the LHS) 78 | containing the statements that need to be in our KB for us to infer the 79 | RHS statement. Also has fields tracking which facts/rules in the KB it 80 | supports and is supported by. 81 | 82 | Attributes: 83 | name (str): 'rule', the name of this class 84 | lhs (listof Statement): LHS statements of this rule 85 | rhs (Statement): RHS statment of this rule 86 | asserted (bool): boolean flag indicating if rule was asserted instead of 87 | inferred from other rules/facts in the KB 88 | supported_by (listof Fact|Rule): Facts/Rules that allow inference of 89 | the statement 90 | supports_facts (listof Fact): Facts that this rule supports 91 | supports_rules (listof Rule): Rules that this rule supports 92 | """ 93 | def __init__(self, rule, supported_by=[]): 94 | """Constructor for Rule setting up useful flags and generating appropriate LHS & RHS 95 | 96 | Args: 97 | rule (listof list): Raw representation of statements making up LHS and 98 | RHS of this rule 99 | supported_by (listof Fact|Rule): Facts/Rules that allow inference of 100 | the statement 101 | """ 102 | super(Rule, self).__init__() 103 | self.name = "rule" 104 | self.lhs = [statement if isinstance(statement, Statement) else Statement(statement) for statement in rule[0]] 105 | self.rhs = rule[1] if isinstance(rule[1], Statement) else Statement(rule[1]) 106 | self.asserted = not supported_by 107 | self.supported_by = [] 108 | self.supports_facts = [] 109 | self.supports_rules = [] 110 | for pair in supported_by: 111 | self.supported_by.append(pair) 112 | 113 | def __repr__(self): 114 | """Define internal string representation 115 | """ 116 | return 'Rule({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})'.format( 117 | self.name, self.lhs, self.rhs, 118 | self.asserted, self.supported_by, 119 | self.supports_facts, self.supports_rules) 120 | 121 | def __str__(self): 122 | """Define external representation when printed 123 | """ 124 | string = self.name + ":\n" 125 | string += "\t Left hand:\n" 126 | for statement in self.lhs: 127 | string += "\t\t" + str(statement) + "\n" 128 | string += "\t Right hand:\n\t\t" + str(self.rhs) + "\n" 129 | string += "\t Asserted: " + str(self.asserted) + "\n" 130 | if self.supported_by != []: 131 | name_strings = [str(x.name) for y in self.supported_by for x in y ] 132 | supported_by_str = ", ".join(name_strings) 133 | string += "\t Supported by: [" + supported_by_str + "]\n" 134 | if self.supports_facts != []: 135 | name_strings = [str(x.name) for x in self.supports_facts] 136 | supports_f_str = ", ".join(name_strings) 137 | string += "\t Supports facts: [" + supports_f_str + "]\n" 138 | if self.supports_rules != []: 139 | name_strings = [str(x.name) for x in self.supports_rules] 140 | supports_r_str = ", ".join(name_strings) 141 | string += "\t Supports rules: [" + supports_r_str + "]\n" 142 | return string 143 | 144 | def __eq__(self, other): 145 | """Define behavior of == when applied to this object 146 | """ 147 | is_rule = isinstance(other, Rule) 148 | return is_rule and self.lhs == other.lhs and self.rhs == other.rhs 149 | 150 | def __ne__(self, other): 151 | """Define behavior of != when applied to this object 152 | """ 153 | return not self == other 154 | 155 | class Statement(object): 156 | """Represents a statement in our knowledge base, e.g. (attacked Ai Nosliw), 157 | (diamonds Loot), (isa Sorceress Wizard), etc. These statements show up 158 | in Facts or on the LHS and RHS of Rules 159 | 160 | Attributes: 161 | terms (listof Term): List of terms (Variable or Constant) in the 162 | statement, e.g. 'Nosliw' or '?d' 163 | predicate (str): The predicate of the statement, e.g. isa, hero, needs 164 | """ 165 | def __init__(self, statement_list=[]): 166 | """Constructor for Statements with optional list of Statements that are 167 | converted to appropriate terms (and one predicate) 168 | 169 | Args: 170 | statement_list (mostly listof str|Term, first element is str): The element at 171 | index 0 is the predicate of the statement (a str) while the rest of 172 | the list is either instantiated Terms or strings to be passed to the 173 | Term constructor 174 | """ 175 | super(Statement, self).__init__() 176 | self.terms = [] 177 | self.predicate = "" 178 | 179 | if statement_list: 180 | self.predicate = statement_list[0] 181 | self.terms = [t if isinstance(t, Term) else Term(t) for t in statement_list[1:]] 182 | 183 | def __repr__(self): 184 | """Define internal string representation 185 | """ 186 | return 'Statement({!r}, {!r})'.format(self.predicate, self.terms) 187 | 188 | def __str__(self): 189 | """Define external representation when printed 190 | """ 191 | return "(" + self.predicate + " " + ' '.join((str(t) for t in self.terms)) + ")" 192 | 193 | def __eq__(self, other): 194 | """Define behavior of == when applied to this object 195 | """ 196 | if self.predicate != other.predicate: 197 | return False 198 | 199 | for self_term, other_term in zip(self.terms, other.terms): 200 | if self_term != other_term: 201 | return False 202 | 203 | return True 204 | 205 | def __ne__(self, other): 206 | """Define behavior of != when applied to this object 207 | """ 208 | return not self == other 209 | 210 | class Term(object): 211 | """Represents a term (a Variable or Constant) in our knowledge base. Can 212 | sorta be thought of as a super class of Variable and Constant, though 213 | there is no inheritance implemented in the code. 214 | 215 | Attributes: 216 | term (Variable|Constant): The Variable or Constant that this term holds (represents) 217 | """ 218 | def __init__(self, term): 219 | """Constructor for Term which converts term to appropriate form 220 | 221 | Args: 222 | term (Variable|Constant|string): Either an instantiated Variable or 223 | Constant, or a string to be passed to the appropriate constructor 224 | """ 225 | super(Term, self).__init__() 226 | is_var_or_const = isinstance(term, Variable) or isinstance(term, Constant) 227 | self.term = term if is_var_or_const else (Variable(term) if is_var(term) else Constant(term)) 228 | 229 | def __repr__(self): 230 | """Define internal string representation 231 | """ 232 | return 'Term({!r})'.format(self.term) 233 | 234 | def __str__(self): 235 | """Define external representation when printed 236 | """ 237 | return str(self.term) 238 | 239 | def __eq__(self, other): 240 | """Define behavior of == when applied to this object 241 | """ 242 | return (self is other 243 | or isinstance(other, Term) and self.term.element == other.term.element 244 | or ((isinstance(other, Variable) or isinstance(other, Constant)) 245 | and self.term.element == other.element)) 246 | 247 | def __ne__(self, other): 248 | """Define behavior of != when applied to this object 249 | """ 250 | return not self == other 251 | 252 | class Variable(object): 253 | """Represents a variable used in statements 254 | 255 | Attributes: 256 | element (str): The name of the variable, e.g. '?x' 257 | """ 258 | def __init__(self, element): 259 | """Constructor for Variable 260 | 261 | Args: 262 | element (str): The name of the variable, e.g. '?x' 263 | """ 264 | super(Variable, self).__init__() 265 | self.element = element 266 | 267 | def __repr__(self): 268 | """Define internal string representation 269 | """ 270 | return 'Variable({!r})'.format(self.element) 271 | 272 | def __str__(self): 273 | """Define external representation when printed 274 | """ 275 | return str(self.element) 276 | 277 | def __eq__(self, other): 278 | """Define behavior of == when applied to this object 279 | """ 280 | return (self is other 281 | or isinstance(other, Term) and self.term.element == other.term.element 282 | or ((isinstance(other, Variable) or isinstance(other, Constant)) 283 | and self.term.element == other.element)) 284 | 285 | def __ne__(self, other): 286 | """Define behavior of != when applied to this object 287 | """ 288 | return not self == other 289 | 290 | class Constant(object): 291 | """Represents a constant used in statements 292 | 293 | Attributes: 294 | element (str): The value of the constant, e.g. 'Nosliw' 295 | """ 296 | def __init__(self, element): 297 | """Constructor for Constant 298 | 299 | Args: 300 | element (str): The value of the constant, e.g. 'Nosliw' 301 | """ 302 | super(Constant, self).__init__() 303 | self.element = element 304 | 305 | def __repr__(self): 306 | """Define internal string representation 307 | """ 308 | return 'Constant({!r})'.format(self.element) 309 | 310 | def __str__(self): 311 | """Define external representation when printed 312 | """ 313 | return str(self.element) 314 | 315 | def __eq__(self, other): 316 | """Define behavior of == when applied to this object 317 | """ 318 | return (self is other 319 | or isinstance(other, Term) and self.term.element == other.term.element 320 | or ((isinstance(other, Variable) or isinstance(other, Constant)) 321 | and self.term.element == other.element)) 322 | 323 | def __ne__(self, other): 324 | """Define behavior of != when applied to this object 325 | """ 326 | return not self == other 327 | 328 | class Binding(object): 329 | """Represents a binding of a constant to a variable, e.g. 'Nosliw' might be 330 | bound to'?d' 331 | 332 | Attributes: 333 | variable (Variable): The name of the variable associated with this binding 334 | constant (Constant): The value of the variable 335 | """ 336 | def __init__(self, variable, constant): 337 | """Constructor for Binding 338 | 339 | Args: 340 | variable (Variable): The name of the variable associated with this binding 341 | constant (Constant): The value of the variable 342 | """ 343 | super(Binding, self).__init__() 344 | self.variable = variable 345 | self.constant = constant 346 | 347 | def __repr__(self): 348 | """Define internal string representation 349 | """ 350 | return 'Binding({!r}, {!r})'.format(self.variable, self.constant) 351 | 352 | def __str__(self): 353 | """Define external representation when printed 354 | """ 355 | return self.variable.element.upper() + " : " + self.constant.element 356 | 357 | class Bindings(object): 358 | """Represents Binding(s) used while matching two statements 359 | 360 | Attributes: 361 | bindings (listof Bindings): bindings involved in match 362 | bindings_dict (dictof Bindings): bindings involved in match where key is 363 | bound variable and value is bound value, 364 | e.g. some_bindings.bindings_dict['?d'] => 'Nosliw' 365 | """ 366 | def __init__(self): 367 | """Constructor for Bindings creating initially empty instance 368 | """ 369 | self.bindings = [] 370 | self.bindings_dict = {} 371 | 372 | def __repr__(self): 373 | """Define internal string representation 374 | """ 375 | return 'Bindings({!r}, {!r})'.format(self.bindings_dict, self.bindings) 376 | 377 | def __str__(self): 378 | """Define external representation when printed 379 | """ 380 | if self.bindings == []: 381 | return "No bindings" 382 | return ", ".join((str(binding) for binding in self.bindings)) 383 | 384 | def __getitem__(self,key): 385 | """Define behavior for indexing, e.g. random_bindings[key] returns 386 | random_bindings.bindings_dict[key] when the dictionary is not empty 387 | and the key exists, otherwise None 388 | """ 389 | return (self.bindings_dict[key] 390 | if (self.bindings_dict and key in self.bindings_dict) 391 | else None) 392 | 393 | def add_binding(self, variable, value): 394 | """Add a binding from a variable to a value 395 | 396 | Args: 397 | variable (Variable): the variable to bind to 398 | value (Constant): the value to bind to the variable 399 | """ 400 | self.bindings_dict[variable.element] = value.element 401 | self.bindings.append(Binding(variable, value)) 402 | 403 | def bound_to(self, variable): 404 | """Check if variable is bound. If so return value bound to it, else False. 405 | 406 | Args: 407 | variable (Variable): variable to check for binding 408 | 409 | Returns: 410 | Variable|Constant|False: returns bound term if variable is bound else False 411 | """ 412 | if variable.element in self.bindings_dict.keys(): 413 | value = self.bindings_dict[variable.element] 414 | if value: 415 | return Variable(value) if is_var(value) else Constant(value) 416 | 417 | return False 418 | 419 | def test_and_bind(self, variable_term, value_term): 420 | """Check if variable_term already bound. If so return whether or not passed 421 | in value_term matches bound value. If not, add binding between 422 | variable_terma and value_term and return True. 423 | 424 | Args: 425 | value_term (Term): value to maybe bind 426 | variable_term (Term): variable to maybe bind to 427 | 428 | Returns: 429 | bool: if variable bound returns whether or not bound value matches value_term, 430 | else True 431 | """ 432 | bound = self.bound_to(variable_term.term) 433 | if bound: 434 | return value_term.term == bound 435 | 436 | self.add_binding(variable_term.term, value_term.term) 437 | return True 438 | 439 | 440 | class ListOfBindings(object): 441 | """Container for multiple Bindings 442 | 443 | Attributes: 444 | list_of_bindings (listof Bindings): collects Bindings 445 | """ 446 | def __init__(self): 447 | """Constructor for ListOfBindings 448 | """ 449 | super(ListOfBindings, self).__init__() 450 | self.list_of_bindings = [] 451 | 452 | def __repr__(self): 453 | """Define internal string representation 454 | """ 455 | return 'ListOfBindings({!r})'.format(self.list_of_bindings) 456 | 457 | def __str__(self): 458 | """Define external representation when printed 459 | """ 460 | string = "" 461 | for binding, associated_fact_rules in self.list_of_bindings: 462 | string += "Bindings for Facts and Rules: " + str(binding) + "\n" 463 | string += "Associated Facts and Rules: [" 464 | string += ", ".join((str(f) for f in associated_fact_rules)) + "]\n" 465 | return string 466 | 467 | def __len__(self): 468 | """Define behavior of len, when called on this class, 469 | e.g. len(ListOfBindings([])) == 0 470 | """ 471 | return len(self.list_of_bindings) 472 | 473 | def __getitem__(self,key): 474 | """Define behavior for indexing, e.g. random_list_of_bindings[i] returns 475 | random_list_of_bindings[i][0] 476 | """ 477 | return self.list_of_bindings[key][0] 478 | 479 | def add_bindings(self, bindings, facts_rules=[]): 480 | """Add given bindings to list of Bindings along with associated rules or facts 481 | 482 | Args: 483 | bindings (Bindings): bindings to add 484 | facts_rules (listof Fact|Rule): rules or facts associated with bindings 485 | 486 | Returns: 487 | Nothing 488 | """ 489 | self.list_of_bindings.append((bindings, facts_rules)) 490 | -------------------------------------------------------------------------------- /KnowledgeBase/logical_classes.py: -------------------------------------------------------------------------------- 1 | from util import is_var 2 | 3 | class Fact(object): 4 | """Represents a fact in our knowledge base. Has a statement containing the 5 | content of the fact, e.g. (isa Sorceress Wizard) and fields tracking 6 | which facts/rules in the KB it supports and is supported by. 7 | 8 | Attributes: 9 | name (str): 'fact', the name of this class 10 | statement (Statement): statement of this fact, basically what the fact actually says 11 | asserted (bool): boolean flag indicating if fact was asserted instead of 12 | inferred from other rules/facts in the KB 13 | supported_by (listof Fact|Rule): Facts/Rules that allow inference of 14 | the statement 15 | supports_facts (listof Fact): Facts that this fact supports 16 | supports_rules (listof Rule): Rules that this fact supports 17 | """ 18 | def __init__(self, statement, supported_by=[]): 19 | """Constructor for Fact setting up useful flags and generating appropriate statement 20 | 21 | Args: 22 | statement (str|Statement): The statement of this fact, basically what the 23 | fact actually says 24 | supported_by (listof Fact|Rule): Facts/Rules that allow inference of 25 | the statement 26 | """ 27 | super(Fact, self).__init__() 28 | self.name = "fact" 29 | self.statement = statement if isinstance(statement, Statement) else Statement(statement) 30 | self.asserted = not supported_by 31 | #self.supported_by = supported_by 32 | self.supported_by = [] 33 | self.supports_facts = [] 34 | self.supports_rules = [] 35 | for pair in supported_by: 36 | self.supported_by.append(pair) 37 | 38 | def __repr__(self): 39 | """Define internal string representation 40 | """ 41 | return 'Fact({!r}, {!r}, {!r}, {!r}, {!r}, {!r})'.format( 42 | self.name, self.statement, 43 | self.asserted, self.supported_by, 44 | self.supports_facts, self.supports_rules) 45 | 46 | def __str__(self): 47 | """Define external representation when printed 48 | """ 49 | string = self.name + ":\n" 50 | string += "\t" + str(self.statement) + "\n" 51 | string += "\t Asserted: " + str(self.asserted) + "\n" 52 | if self.supported_by != []: 53 | name_strings = [str(x.name) for y in self.supported_by for x in y] 54 | supported_by_str = ", ".join(name_strings) 55 | string += "\t Supported by: [" + supported_by_str + "]\n" 56 | if self.supports_facts != []: 57 | name_strings = [str(x.name) for x in self.supports_facts] 58 | supports_f_str = ", ".join(name_strings) 59 | string += "\t Supports facts: [" + supports_f_str + "]\n" 60 | if self.supports_rules != []: 61 | name_strings = [str(x.name) for x in self.supports_rules] 62 | supports_r_str = ", ".join(name_strings) 63 | string += "\t Supports rules: [" + supports_r_str + "]\n" 64 | return string 65 | 66 | def __eq__(self, other): 67 | """Define behavior of == when applied to this object 68 | """ 69 | return isinstance(other, Fact) and self.statement == other.statement 70 | 71 | def __ne__(self, other): 72 | """Define behavior of != when applied to this object 73 | """ 74 | return not self == other 75 | 76 | class Rule(object): 77 | """Represents a rule in our knowledge base. Has a list of statements (the LHS) 78 | containing the statements that need to be in our KB for us to infer the 79 | RHS statement. Also has fields tracking which facts/rules in the KB it 80 | supports and is supported by. 81 | 82 | Attributes: 83 | name (str): 'rule', the name of this class 84 | lhs (listof Statement): LHS statements of this rule 85 | rhs (Statement): RHS statment of this rule 86 | asserted (bool): boolean flag indicating if rule was asserted instead of 87 | inferred from other rules/facts in the KB 88 | supported_by (listof Fact|Rule): Facts/Rules that allow inference of 89 | the statement 90 | supports_facts (listof Fact): Facts that this rule supports 91 | supports_rules (listof Rule): Rules that this rule supports 92 | """ 93 | def __init__(self, rule, supported_by=[]): 94 | """Constructor for Rule setting up useful flags and generating appropriate LHS & RHS 95 | 96 | Args: 97 | rule (listof list): Raw representation of statements making up LHS and 98 | RHS of this rule 99 | supported_by (listof Fact|Rule): Facts/Rules that allow inference of 100 | the statement 101 | """ 102 | super(Rule, self).__init__() 103 | self.name = "rule" 104 | self.lhs = [statement if isinstance(statement, Statement) else Statement(statement) for statement in rule[0]] 105 | self.rhs = rule[1] if isinstance(rule[1], Statement) else Statement(rule[1]) 106 | self.asserted = not supported_by 107 | self.supported_by = [] 108 | self.supports_facts = [] 109 | self.supports_rules = [] 110 | for pair in supported_by: 111 | self.supported_by.append(pair) 112 | 113 | def __repr__(self): 114 | """Define internal string representation 115 | """ 116 | return 'Rule({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})'.format( 117 | self.name, self.lhs, self.rhs, 118 | self.asserted, self.supported_by, 119 | self.supports_facts, self.supports_rules) 120 | 121 | def __str__(self): 122 | """Define external representation when printed 123 | """ 124 | string = self.name + ":\n" 125 | string += "\t Left hand:\n" 126 | for statement in self.lhs: 127 | string += "\t\t" + str(statement) + "\n" 128 | string += "\t Right hand:\n\t\t" + str(self.rhs) + "\n" 129 | string += "\t Asserted: " + str(self.asserted) + "\n" 130 | if self.supported_by != []: 131 | name_strings = [str(x.name) for y in self.supported_by for x in y ] 132 | supported_by_str = ", ".join(name_strings) 133 | string += "\t Supported by: [" + supported_by_str + "]\n" 134 | if self.supports_facts != []: 135 | name_strings = [str(x.name) for x in self.supports_facts] 136 | supports_f_str = ", ".join(name_strings) 137 | string += "\t Supports facts: [" + supports_f_str + "]\n" 138 | if self.supports_rules != []: 139 | name_strings = [str(x.name) for x in self.supports_rules] 140 | supports_r_str = ", ".join(name_strings) 141 | string += "\t Supports rules: [" + supports_r_str + "]\n" 142 | return string 143 | 144 | def __eq__(self, other): 145 | """Define behavior of == when applied to this object 146 | """ 147 | is_rule = isinstance(other, Rule) 148 | return is_rule and self.lhs == other.lhs and self.rhs == other.rhs 149 | 150 | def __ne__(self, other): 151 | """Define behavior of != when applied to this object 152 | """ 153 | return not self == other 154 | 155 | class Statement(object): 156 | """Represents a statement in our knowledge base, e.g. (attacked Ai Nosliw), 157 | (diamonds Loot), (isa Sorceress Wizard), etc. These statements show up 158 | in Facts or on the LHS and RHS of Rules 159 | 160 | Attributes: 161 | terms (listof Term): List of terms (Variable or Constant) in the 162 | statement, e.g. 'Nosliw' or '?d' 163 | predicate (str): The predicate of the statement, e.g. isa, hero, needs 164 | """ 165 | def __init__(self, statement_list=[]): 166 | """Constructor for Statements with optional list of Statements that are 167 | converted to appropriate terms (and one predicate) 168 | 169 | Args: 170 | statement_list (mostly listof str|Term, first element is str): The element at 171 | index 0 is the predicate of the statement (a str) while the rest of 172 | the list is either instantiated Terms or strings to be passed to the 173 | Term constructor 174 | """ 175 | super(Statement, self).__init__() 176 | self.terms = [] 177 | self.predicate = "" 178 | 179 | if statement_list: 180 | self.predicate = statement_list[0] 181 | self.terms = [t if isinstance(t, Term) else Term(t) for t in statement_list[1:]] 182 | 183 | def __repr__(self): 184 | """Define internal string representation 185 | """ 186 | return 'Statement({!r}, {!r})'.format(self.predicate, self.terms) 187 | 188 | def __str__(self): 189 | """Define external representation when printed 190 | """ 191 | return "(" + self.predicate + " " + ' '.join((str(t) for t in self.terms)) + ")" 192 | 193 | def __eq__(self, other): 194 | """Define behavior of == when applied to this object 195 | """ 196 | if self.predicate != other.predicate: 197 | return False 198 | 199 | for self_term, other_term in zip(self.terms, other.terms): 200 | if self_term != other_term: 201 | return False 202 | 203 | return True 204 | 205 | def __ne__(self, other): 206 | """Define behavior of != when applied to this object 207 | """ 208 | return not self == other 209 | 210 | class Term(object): 211 | """Represents a term (a Variable or Constant) in our knowledge base. Can 212 | sorta be thought of as a super class of Variable and Constant, though 213 | there is no inheritance implemented in the code. 214 | 215 | Attributes: 216 | term (Variable|Constant): The Variable or Constant that this term holds (represents) 217 | """ 218 | def __init__(self, term): 219 | """Constructor for Term which converts term to appropriate form 220 | 221 | Args: 222 | term (Variable|Constant|string): Either an instantiated Variable or 223 | Constant, or a string to be passed to the appropriate constructor 224 | """ 225 | super(Term, self).__init__() 226 | is_var_or_const = isinstance(term, Variable) or isinstance(term, Constant) 227 | self.term = term if is_var_or_const else (Variable(term) if is_var(term) else Constant(term)) 228 | 229 | def __repr__(self): 230 | """Define internal string representation 231 | """ 232 | return 'Term({!r})'.format(self.term) 233 | 234 | def __str__(self): 235 | """Define external representation when printed 236 | """ 237 | return str(self.term) 238 | 239 | def __eq__(self, other): 240 | """Define behavior of == when applied to this object 241 | """ 242 | return (self is other 243 | or isinstance(other, Term) and self.term.element == other.term.element 244 | or ((isinstance(other, Variable) or isinstance(other, Constant)) 245 | and self.term.element == other.element)) 246 | 247 | def __ne__(self, other): 248 | """Define behavior of != when applied to this object 249 | """ 250 | return not self == other 251 | 252 | class Variable(object): 253 | """Represents a variable used in statements 254 | 255 | Attributes: 256 | element (str): The name of the variable, e.g. '?x' 257 | """ 258 | def __init__(self, element): 259 | """Constructor for Variable 260 | 261 | Args: 262 | element (str): The name of the variable, e.g. '?x' 263 | """ 264 | super(Variable, self).__init__() 265 | self.element = element 266 | 267 | def __repr__(self): 268 | """Define internal string representation 269 | """ 270 | return 'Variable({!r})'.format(self.element) 271 | 272 | def __str__(self): 273 | """Define external representation when printed 274 | """ 275 | return str(self.element) 276 | 277 | def __eq__(self, other): 278 | """Define behavior of == when applied to this object 279 | """ 280 | return (self is other 281 | or isinstance(other, Term) and self.term.element == other.term.element 282 | or ((isinstance(other, Variable) or isinstance(other, Constant)) 283 | and self.term.element == other.element)) 284 | 285 | def __ne__(self, other): 286 | """Define behavior of != when applied to this object 287 | """ 288 | return not self == other 289 | 290 | class Constant(object): 291 | """Represents a constant used in statements 292 | 293 | Attributes: 294 | element (str): The value of the constant, e.g. 'Nosliw' 295 | """ 296 | def __init__(self, element): 297 | """Constructor for Constant 298 | 299 | Args: 300 | element (str): The value of the constant, e.g. 'Nosliw' 301 | """ 302 | super(Constant, self).__init__() 303 | self.element = element 304 | 305 | def __repr__(self): 306 | """Define internal string representation 307 | """ 308 | return 'Constant({!r})'.format(self.element) 309 | 310 | def __str__(self): 311 | """Define external representation when printed 312 | """ 313 | return str(self.element) 314 | 315 | def __eq__(self, other): 316 | """Define behavior of == when applied to this object 317 | """ 318 | return (self is other 319 | or isinstance(other, Term) and self.term.element == other.term.element 320 | or ((isinstance(other, Variable) or isinstance(other, Constant)) 321 | and self.term.element == other.element)) 322 | 323 | def __ne__(self, other): 324 | """Define behavior of != when applied to this object 325 | """ 326 | return not self == other 327 | 328 | class Binding(object): 329 | """Represents a binding of a constant to a variable, e.g. 'Nosliw' might be 330 | bound to'?d' 331 | 332 | Attributes: 333 | variable (Variable): The name of the variable associated with this binding 334 | constant (Constant): The value of the variable 335 | """ 336 | def __init__(self, variable, constant): 337 | """Constructor for Binding 338 | 339 | Args: 340 | variable (Variable): The name of the variable associated with this binding 341 | constant (Constant): The value of the variable 342 | """ 343 | super(Binding, self).__init__() 344 | self.variable = variable 345 | self.constant = constant 346 | 347 | def __repr__(self): 348 | """Define internal string representation 349 | """ 350 | return 'Binding({!r}, {!r})'.format(self.variable, self.constant) 351 | 352 | def __str__(self): 353 | """Define external representation when printed 354 | """ 355 | return self.variable.element.upper() + " : " + self.constant.element 356 | 357 | class Bindings(object): 358 | """Represents Binding(s) used while matching two statements 359 | 360 | Attributes: 361 | bindings (listof Bindings): bindings involved in match 362 | bindings_dict (dictof Bindings): bindings involved in match where key is 363 | bound variable and value is bound value, 364 | e.g. some_bindings.bindings_dict['?d'] => 'Nosliw' 365 | """ 366 | def __init__(self): 367 | """Constructor for Bindings creating initially empty instance 368 | """ 369 | self.bindings = [] 370 | self.bindings_dict = {} 371 | 372 | def __repr__(self): 373 | """Define internal string representation 374 | """ 375 | return 'Bindings({!r}, {!r})'.format(self.bindings_dict, self.bindings) 376 | 377 | def __str__(self): 378 | """Define external representation when printed 379 | """ 380 | if self.bindings == []: 381 | return "No bindings" 382 | return ", ".join((str(binding) for binding in self.bindings)) 383 | 384 | def __getitem__(self,key): 385 | """Define behavior for indexing, e.g. random_bindings[key] returns 386 | random_bindings.bindings_dict[key] when the dictionary is not empty 387 | and the key exists, otherwise None 388 | """ 389 | return (self.bindings_dict[key] 390 | if (self.bindings_dict and key in self.bindings_dict) 391 | else None) 392 | 393 | def add_binding(self, variable, value): 394 | """Add a binding from a variable to a value 395 | 396 | Args: 397 | variable (Variable): the variable to bind to 398 | value (Constant): the value to bind to the variable 399 | """ 400 | self.bindings_dict[variable.element] = value.element 401 | self.bindings.append(Binding(variable, value)) 402 | 403 | def bound_to(self, variable): 404 | """Check if variable is bound. If so return value bound to it, else False. 405 | 406 | Args: 407 | variable (Variable): variable to check for binding 408 | 409 | Returns: 410 | Variable|Constant|False: returns bound term if variable is bound else False 411 | """ 412 | if variable.element in self.bindings_dict.keys(): 413 | value = self.bindings_dict[variable.element] 414 | if value: 415 | return Variable(value) if is_var(value) else Constant(value) 416 | 417 | return False 418 | 419 | def test_and_bind(self, variable_term, value_term): 420 | """Check if variable_term already bound. If so return whether or not passed 421 | in value_term matches bound value. If not, add binding between 422 | variable_terma and value_term and return True. 423 | 424 | Args: 425 | value_term (Term): value to maybe bind 426 | variable_term (Term): variable to maybe bind to 427 | 428 | Returns: 429 | bool: if variable bound returns whether or not bound value matches value_term, 430 | else True 431 | """ 432 | bound = self.bound_to(variable_term.term) 433 | if bound: 434 | return value_term.term == bound 435 | 436 | self.add_binding(variable_term.term, value_term.term) 437 | return True 438 | 439 | 440 | class ListOfBindings(object): 441 | """Container for multiple Bindings 442 | 443 | Attributes: 444 | list_of_bindings (listof Bindings): collects Bindings 445 | """ 446 | def __init__(self): 447 | """Constructor for ListOfBindings 448 | """ 449 | super(ListOfBindings, self).__init__() 450 | self.list_of_bindings = [] 451 | 452 | def __repr__(self): 453 | """Define internal string representation 454 | """ 455 | return 'ListOfBindings({!r})'.format(self.list_of_bindings) 456 | 457 | def __str__(self): 458 | """Define external representation when printed 459 | """ 460 | string = "" 461 | for binding, associated_fact_rules in self.list_of_bindings: 462 | string += "Bindings for Facts and Rules: " + str(binding) + "\n" 463 | string += "Associated Facts and Rules: [" 464 | string += ", ".join((str(f) for f in associated_fact_rules)) + "]\n" 465 | return string 466 | 467 | def __len__(self): 468 | """Define behavior of len, when called on this class, 469 | e.g. len(ListOfBindings([])) == 0 470 | """ 471 | return len(self.list_of_bindings) 472 | 473 | def __getitem__(self,key): 474 | """Define behavior for indexing, e.g. random_list_of_bindings[i] returns 475 | random_list_of_bindings[i][0] 476 | """ 477 | return self.list_of_bindings[key][0] 478 | 479 | def add_bindings(self, bindings, facts_rules=[]): 480 | """Add given bindings to list of Bindings along with associated rules or facts 481 | 482 | Args: 483 | bindings (Bindings): bindings to add 484 | facts_rules (listof Fact|Rule): rules or facts associated with bindings 485 | 486 | Returns: 487 | Nothing 488 | """ 489 | self.list_of_bindings.append((bindings, facts_rules)) 490 | --------------------------------------------------------------------------------