expert-system
This commit is contained in:
parent
e3ded9ec84
commit
fdf374dcd7
|
@ -1,4 +1,3 @@
|
|||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
@ -48,7 +47,6 @@ htmlcov/
|
|||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
|
@ -60,7 +58,6 @@ coverage.xml
|
|||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
|
@ -85,19 +82,8 @@ ipython_config.py
|
|||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
@ -129,3 +115,27 @@ dmypy.json
|
|||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
wap
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
*~
|
||||
# Auto-generated tag files
|
||||
tags
|
||||
# Persistent undo
|
||||
[._]*.un~
|
||||
|
||||
#VSCODE
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
class Ft_op:
|
||||
|
||||
# def __init__ (self):
|
||||
def ft_and(a, b):
|
||||
return a & b
|
||||
|
||||
def ft_or(a, b):
|
||||
return a | b
|
||||
|
||||
def ft_xor(a, b):
|
||||
return a ^ b
|
||||
|
||||
def ft_not(a, b):
|
||||
return not a
|
|
@ -0,0 +1,60 @@
|
|||
from fact import Fact, check_fact
|
||||
from user import *
|
||||
from Ft_op import Ft_op
|
||||
from config import *
|
||||
import config
|
||||
|
||||
@v_args(inline=True) # Affects the signatures of the methods
|
||||
class InferenceEngine(Transformer):
|
||||
|
||||
def iter_subtree(self, fact_tree):
|
||||
for fact in fact_tree.children:
|
||||
self.set_fact(str(fact))
|
||||
|
||||
def set_fact(self, *string):
|
||||
for letter in string:
|
||||
self.assign_var(letter, True)
|
||||
|
||||
def print_state(self, string):
|
||||
for letter in string:
|
||||
print(letter, '=', self.var(letter))
|
||||
|
||||
def apply_func(self, tree):
|
||||
if (isinstance(tree, Tree) == False):
|
||||
return self.var(str(tree))
|
||||
else:
|
||||
return getattr(Ft_op, tree.data)(self.apply_func(tree.children[0]),
|
||||
self.apply_func(tree.children[1] if len(tree.children) > 1 else 0))
|
||||
|
||||
def assign_var(self, name, state):
|
||||
config.fact_dict[name] = Fact(state=state, key=name, isset=True)
|
||||
return state
|
||||
|
||||
def var(self, name):
|
||||
if (config.glob != True):
|
||||
return name
|
||||
if (config.fact_dict.get(name) == None):
|
||||
config.fact_dict[name] = Fact(key=name)
|
||||
return config.fact_dict[name].get_state(self)
|
||||
|
||||
|
||||
def set_state(self, tree, op_tree, is_not=False, is_iff=False):
|
||||
if (isinstance(tree, Tree) == False):
|
||||
check_fact(str(tree))
|
||||
config.fact_dict[str(tree)].trees.append((op_tree, is_not, is_iff))
|
||||
elif (tree.data == 'ft_and'):
|
||||
self.set_state(tree.children[0], op_tree, is_not, is_iff)
|
||||
self.set_state(tree.children[1], op_tree, is_not, is_iff)
|
||||
elif (tree.data == 'ft_not'):
|
||||
self.set_state(tree.children[0], op_tree, not is_not)
|
||||
elif (tree.data == 'ft_or'):
|
||||
choice = ask_or(tree)
|
||||
if (choice is 2):
|
||||
self.set_state(tree.children[0], op_tree, is_not, is_iff)
|
||||
self.set_state(tree.children[1], op_tree, is_not, is_iff)
|
||||
else:
|
||||
self.set_state(tree.children[choice], op_tree, is_not, is_iff)
|
||||
elif (tree.data == 'ft_xor'):
|
||||
choice = ask_xor(tree)
|
||||
self.set_state(tree.children[choice], op_tree, False, is_iff)
|
||||
self.set_state(tree.children[not choice], op_tree, True, is_iff)
|
|
@ -0,0 +1,8 @@
|
|||
from lark import Lark, Transformer, v_args, Tree
|
||||
from InferenceEngine import InferenceEngine
|
||||
computer = InferenceEngine()
|
||||
pstr ="\033[1;32m--->\033[0m"
|
||||
skip = False
|
||||
glob = False
|
||||
fact_dict = {}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import sys
|
||||
from user import *
|
||||
from lark import Tree
|
||||
import config
|
||||
|
||||
def check_fact(key, state=False):
|
||||
|
||||
if (config.fact_dict.get(key) == None):
|
||||
config.fact_dict[key] = Fact(state, key)
|
||||
|
||||
class Fact:
|
||||
|
||||
def __init__(self, state = False, key = 0, isset=False):
|
||||
self.trees = list()
|
||||
self.isset = isset
|
||||
self.mutex_lock = 0
|
||||
self.key = key
|
||||
self.state = state
|
||||
|
||||
def check_fact(self,fact):
|
||||
if (self.key == str(fact)):
|
||||
return 1
|
||||
|
||||
def remove_tree(self, tree, idx, computer):
|
||||
if (config.skip):
|
||||
sys.exit('Conflict between rules')
|
||||
choice = 'w'
|
||||
st = ("<=>" if tree[2] else "=>") + ('!' if tree[1] else '') + self.key
|
||||
while choice not in ['y', 'n']:
|
||||
choice = input(str("There is an error in operation, would you like to remove this one ? {"
|
||||
+ set_choices(tree[0]) + st+ "} y/n?\n"))
|
||||
if (choice == 'n'):
|
||||
sys.exit('Conflict between rules')
|
||||
self.trees.pop(idx)
|
||||
return self.get_state(computer)
|
||||
|
||||
def check_imply(self,tree):
|
||||
ret = 0
|
||||
if (isinstance(tree, Tree)):
|
||||
fact = tree.find_data("query")
|
||||
for truc in fact:
|
||||
ret = self.check_fact(fact)
|
||||
else:
|
||||
ret = self.check_fact(tree)
|
||||
return ret
|
||||
|
||||
def get_state(self, computer):
|
||||
if (self.mutex_lock >= 2):
|
||||
return self.state
|
||||
self.mutex_lock += 1
|
||||
config.glob = True
|
||||
idx =0
|
||||
for tree in self.trees:
|
||||
is_set = self.isset is True or idx > 0
|
||||
if (self.check_imply(tree[0]) and tree[1] is True):
|
||||
return self.remove_tree(tree, idx, computer)
|
||||
new_state = computer.apply_func(tree[0])
|
||||
if new_state is False :
|
||||
continue
|
||||
new_state = False if tree[1] and new_state is True else True
|
||||
if (tree[2] is False and is_set and new_state != self.state):
|
||||
return self.remove_tree(tree, idx, computer)
|
||||
self.state |= new_state
|
||||
idx+=1
|
||||
self.mutex_lock -= 1
|
||||
return self.state
|
|
@ -0,0 +1,71 @@
|
|||
import config
|
||||
from config import *
|
||||
from parse import *
|
||||
|
||||
interactive_grammar = r"""
|
||||
?start: imply
|
||||
| initial_fact | query
|
||||
|
||||
?imply: xor "=>" xor -> imply
|
||||
| xor "<=>" xor -> iff
|
||||
?xor: or
|
||||
| xor "^" or -> ft_xor
|
||||
?or: and
|
||||
| or "|" and -> ft_or
|
||||
?and: atom
|
||||
| and "+" atom -> ft_and
|
||||
?atom: UCASE_LETTER -> var
|
||||
| "!" atom -> ft_not
|
||||
| "(" xor ")"
|
||||
|
||||
?initial_fact: "=" UCASE_LETTER* -> set_fact
|
||||
?query: "?" UCASE_LETTER+ ->query
|
||||
|
||||
_LI: (_COMMENT | LF+)
|
||||
_COMMENT: /#[^\n].*\n/
|
||||
|
||||
%import common.UCASE_LETTER
|
||||
%import common.NUMBER
|
||||
%import common.WS_INLINE
|
||||
%import common.LF
|
||||
%ignore WS_INLINE
|
||||
%ignore _COMMENT
|
||||
"""
|
||||
|
||||
|
||||
def reset_states():
|
||||
for idx in fact_dict:
|
||||
fact_dict[idx].state = False
|
||||
fact_dict[idx].is_set = False
|
||||
|
||||
def interactive():
|
||||
print('Welcome to the best expert system in the world wide web')
|
||||
calc_parser = Lark(interactive_grammar, parser='lalr',
|
||||
debug=True, transformer=computer)
|
||||
while True:
|
||||
try:
|
||||
s = input('> ')
|
||||
except EOFError:
|
||||
break
|
||||
if (s == 'exit'):
|
||||
exit('Good bye')
|
||||
continue
|
||||
elif (s == 'reset'):
|
||||
reset_states()
|
||||
continue
|
||||
elif (s == 'reset --hard'):
|
||||
config.fact_dict = {}
|
||||
continue
|
||||
|
||||
try:
|
||||
tree = calc_parser.parse(s)
|
||||
except UnexpectedInput as e:
|
||||
print(e)
|
||||
continue
|
||||
if (tree == None):
|
||||
continue
|
||||
if (tree.data == "imply" or tree.data == "iff"):
|
||||
set_trees(tree)
|
||||
else:
|
||||
query(tree)
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import argparse
|
||||
from lark import Lark, Transformer, v_args, Tree, UnexpectedInput
|
||||
import traceback
|
||||
import logging
|
||||
from config import *
|
||||
import config
|
||||
import interactive_m
|
||||
|
||||
try:
|
||||
input = raw_input # For Python2 compatibility
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
calc_grammar = r"""
|
||||
?start: (imply _LI)+ initial_fact _LI query _LI
|
||||
|
||||
?imply: xor "=>" xor -> imply
|
||||
| xor "<=>" xor -> iff
|
||||
?xor: or
|
||||
| xor "^" or -> ft_xor
|
||||
?or: and
|
||||
| or "|" and -> ft_or
|
||||
?and: atom
|
||||
| and "+" atom -> ft_and
|
||||
?atom: UCASE_LETTER -> var
|
||||
| "!" atom -> ft_not
|
||||
| "(" xor ")"
|
||||
|
||||
?initial_fact: "=" UCASE_LETTER* -> set_fact
|
||||
?query: "?" UCASE_LETTER+ ->query
|
||||
|
||||
_LI: (_COMMENT | LF+)
|
||||
_COMMENT: /#[^\n].*\n/
|
||||
|
||||
%import common.UCASE_LETTER
|
||||
%import common.NUMBER
|
||||
%import common.WS_INLINE
|
||||
%import common.LF
|
||||
%ignore WS_INLINE
|
||||
%ignore _COMMENT
|
||||
"""
|
||||
|
||||
def set_fact(tree):
|
||||
ifact = tree.find_data("initial_fact")
|
||||
for fact in ifact:
|
||||
computer.iter_subtree(fact)
|
||||
|
||||
def set_trees(tree):
|
||||
implies = tree.find_data("imply")
|
||||
for imply in list(implies):
|
||||
new_tree = imply.children[0]
|
||||
computer.set_state(imply.children[1], new_tree)
|
||||
iffs = tree.find_data("iff")
|
||||
for iff in list(iffs):
|
||||
new_tree1 = iff.children[0]
|
||||
new_tree2 = iff.children[1]
|
||||
computer.set_state(iff.children[1], new_tree1, is_iff=True)
|
||||
computer.set_state(iff.children[0], new_tree2, is_iff=True)
|
||||
|
||||
def query(tree):
|
||||
config.glob = True
|
||||
queries = list(tree.find_data("query"))
|
||||
if (len(queries) <= 0):
|
||||
return
|
||||
st = str()
|
||||
for token in queries[0].children:
|
||||
st += str(token)
|
||||
computer.print_state(st)
|
||||
|
||||
def test(args):
|
||||
if (args.interactive == True):
|
||||
interactive_m.interactive()
|
||||
return
|
||||
calc_parser = Lark(calc_grammar, parser='lalr',
|
||||
debug=True, transformer=computer)
|
||||
with open(args.path, 'r') as myfile:
|
||||
string=myfile.read()
|
||||
print(string)
|
||||
try:
|
||||
tree = calc_parser.parse(string)
|
||||
except UnexpectedInput as e:
|
||||
print(e)
|
||||
return
|
||||
if (args.pretty):
|
||||
print(tree.pretty(pstr))
|
||||
set_trees(tree)
|
||||
query(tree)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Smarter expert system you have ever seen')
|
||||
parser.add_argument("-i", "--interactive", default=False, action="store_true",
|
||||
help="interactive expert system")
|
||||
parser.add_argument("-b", "--batch", default=False, action="store_true",
|
||||
help="run install in batch mode (without manual intervention)")
|
||||
parser.add_argument("-p", "--pretty", default=False, action="store_true",
|
||||
help="print pretty trees")
|
||||
parser.add_argument("path", type=str, default="none", help="input file name")
|
||||
args = parser.parse_args()
|
||||
config.skip = args.batch
|
||||
test(args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,13 @@
|
|||
B => A
|
||||
D + E => B
|
||||
G + H => F
|
||||
I + J => G
|
||||
G => H
|
||||
L + M => K
|
||||
O + P => L + N
|
||||
N => M
|
||||
|
||||
=DEIJOP
|
||||
?AFKP
|
||||
|
||||
#AFKP is true
|
|
@ -0,0 +1,13 @@
|
|||
B => A
|
||||
D + E => B
|
||||
G + H => F
|
||||
I + J => G
|
||||
G => H
|
||||
L + M => K
|
||||
O + P => L + N
|
||||
N => M
|
||||
|
||||
=DEIJP
|
||||
?AFKP
|
||||
|
||||
#AFP is true, K is false
|
|
@ -0,0 +1,8 @@
|
|||
A <=> B
|
||||
C <=> D
|
||||
C => A
|
||||
=D
|
||||
|
||||
?ABCD
|
||||
|
||||
# ABCD should be true
|
|
@ -0,0 +1,5 @@
|
|||
A => C
|
||||
B => !C
|
||||
|
||||
=
|
||||
?ABC
|
|
@ -0,0 +1,5 @@
|
|||
A => C
|
||||
B => !C
|
||||
|
||||
=A
|
||||
?ABC
|
|
@ -0,0 +1,5 @@
|
|||
A => C
|
||||
B => !C
|
||||
|
||||
=B
|
||||
?ABC
|
|
@ -0,0 +1,5 @@
|
|||
A => C
|
||||
B => !C
|
||||
|
||||
=AB
|
||||
?ABC
|
|
@ -0,0 +1,5 @@
|
|||
B => !C
|
||||
A => C
|
||||
|
||||
=ABC
|
||||
?ABC
|
|
@ -0,0 +1,8 @@
|
|||
B + C => A
|
||||
D | E => B
|
||||
B => C
|
||||
|
||||
=
|
||||
?A
|
||||
|
||||
# A should be false
|
|
@ -0,0 +1,8 @@
|
|||
B + C => A
|
||||
D | E => B
|
||||
B => C
|
||||
|
||||
=D
|
||||
?A
|
||||
|
||||
# A should be true
|
|
@ -0,0 +1,8 @@
|
|||
B + C => A
|
||||
D | E => B
|
||||
B => C
|
||||
|
||||
=E
|
||||
?A
|
||||
|
||||
# A should be true
|
|
@ -0,0 +1,8 @@
|
|||
B + C => A
|
||||
D | E => B
|
||||
B => C
|
||||
|
||||
=DE
|
||||
?A
|
||||
|
||||
# A should be true
|
|
@ -0,0 +1,6 @@
|
|||
A => B | C
|
||||
B => D
|
||||
C => E | B
|
||||
|
||||
=A
|
||||
?ABCDE
|
|
@ -0,0 +1,7 @@
|
|||
A | B + C => E
|
||||
(F | G) + H => E
|
||||
|
||||
=
|
||||
?E
|
||||
|
||||
# E should be false
|
|
@ -0,0 +1,7 @@
|
|||
A | B + C => E
|
||||
(F | G) + H => E
|
||||
|
||||
=FH
|
||||
?E
|
||||
|
||||
# E should be true
|
|
@ -0,0 +1,7 @@
|
|||
A | B + C => E
|
||||
(F | G) + H => E
|
||||
|
||||
=GH
|
||||
?E
|
||||
|
||||
# E should be true
|
|
@ -0,0 +1,7 @@
|
|||
A | B + C => E
|
||||
(F | G) + H => E
|
||||
|
||||
=A
|
||||
?E
|
||||
|
||||
# E should be true
|
|
@ -0,0 +1,7 @@
|
|||
A | B + C => E
|
||||
(F | G) + H => E
|
||||
|
||||
=B
|
||||
?E
|
||||
|
||||
# E should be false
|
|
@ -0,0 +1,7 @@
|
|||
A | B + C => E
|
||||
(F | G) + H => E
|
||||
|
||||
=C
|
||||
?E
|
||||
|
||||
# E should be false
|
|
@ -0,0 +1,7 @@
|
|||
A | B + C => E
|
||||
(F | G) + H => E
|
||||
|
||||
=AC
|
||||
?E
|
||||
|
||||
# E should be true
|
|
@ -0,0 +1,7 @@
|
|||
A | B + C => E
|
||||
(F | G) + H => E
|
||||
|
||||
=BC
|
||||
?E
|
||||
|
||||
# E should be true
|
|
@ -0,0 +1,7 @@
|
|||
A | B + C => E
|
||||
(F | G) + H => E
|
||||
|
||||
=F
|
||||
?E
|
||||
|
||||
# E should be false
|
|
@ -0,0 +1,7 @@
|
|||
A | B + C => E
|
||||
(F | G) + H => E
|
||||
|
||||
=G
|
||||
?E
|
||||
|
||||
# E should be false
|
|
@ -0,0 +1,7 @@
|
|||
A | B + C => E
|
||||
(F | G) + H => E
|
||||
|
||||
=H
|
||||
?E
|
||||
|
||||
# E should be false
|
|
@ -0,0 +1,7 @@
|
|||
B => A
|
||||
C => A
|
||||
|
||||
=
|
||||
?A
|
||||
|
||||
# A should be false
|
|
@ -0,0 +1,7 @@
|
|||
B => A
|
||||
C => A
|
||||
|
||||
=B
|
||||
?A
|
||||
|
||||
# A should be true
|
|
@ -0,0 +1,7 @@
|
|||
B => A
|
||||
C => A
|
||||
|
||||
=C
|
||||
?A
|
||||
|
||||
# A should be true
|
|
@ -0,0 +1,7 @@
|
|||
B => A
|
||||
C => A
|
||||
|
||||
=BC
|
||||
?A
|
||||
|
||||
# A should be true
|
|
@ -0,0 +1,8 @@
|
|||
B + C => A
|
||||
D ^ E => B
|
||||
B => C
|
||||
|
||||
=
|
||||
?A
|
||||
|
||||
# A should be false
|
|
@ -0,0 +1,8 @@
|
|||
B + C => A
|
||||
D ^ E => B
|
||||
B => C
|
||||
|
||||
=D
|
||||
?A
|
||||
|
||||
# A should be true
|
|
@ -0,0 +1,8 @@
|
|||
B + C => A
|
||||
D ^ E => B
|
||||
B => C
|
||||
|
||||
=E
|
||||
?A
|
||||
|
||||
# A should be true
|
|
@ -0,0 +1,8 @@
|
|||
B + C => A
|
||||
D ^ E => B
|
||||
B => C
|
||||
|
||||
=DE
|
||||
?A
|
||||
|
||||
# A should be false
|
|
@ -0,0 +1,6 @@
|
|||
A => B ^ C
|
||||
B => D
|
||||
C => E ^ B
|
||||
|
||||
=A
|
||||
?ABCDE
|
|
@ -0,0 +1,6 @@
|
|||
for file in test/*
|
||||
do
|
||||
python3 parse.py $file
|
||||
echo "\n\n\033[32mPress ENTER\033[0m\n"
|
||||
read
|
||||
done
|
|
@ -0,0 +1,25 @@
|
|||
import config
|
||||
from config import *
|
||||
def set_choices(tree):
|
||||
if (isinstance(tree, Tree) == False):
|
||||
return str(tree)
|
||||
else:
|
||||
return tree.pretty().replace('\n', '')
|
||||
|
||||
def ask_xor(tree):
|
||||
if (config.skip is True):
|
||||
return 0
|
||||
choices = [set_choices(tree.children[0]), set_choices(tree.children[1])]
|
||||
choice = 0
|
||||
while choice not in [ '0', '1']:
|
||||
choice = input(str('Would you like to choose 0:{'+ choices[0]+ '} or 1:{'+ choices[1]+ '}\n'))
|
||||
return int(choice)
|
||||
|
||||
def ask_or(tree):
|
||||
if (config.skip is True):
|
||||
return 0
|
||||
choices = [set_choices(tree.children[0]), set_choices(tree.children[1])]
|
||||
choice = 0
|
||||
while choice not in [ '0', '1', '2']:
|
||||
choice = input(str('Would you like to choose 0:{'+ choices[0]+ '} or 1:{'+ choices[1]+ '} or 2: for both\n'))
|
||||
return int(choice)
|
Loading…
Reference in New Issue