expert-system

This commit is contained in:
gbrochar 2020-11-22 17:57:25 +01:00
parent e3ded9ec84
commit fdf374dcd7
48 changed files with 653 additions and 15 deletions

40
.gitignore vendored
View File

@ -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

14
Ft_op.py Normal file
View File

@ -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

60
InferenceEngine.py Normal file
View File

@ -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)

2
auteur Normal file
View File

@ -0,0 +1,2 @@
ygarrot
gbrochar

9
bonus Normal file
View File

@ -0,0 +1,9 @@
or
xor
iff
interactive
reset
reset --hard
pretty graph

8
config.py Normal file
View File

@ -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 = {}

66
fact.py Normal file
View File

@ -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

71
interactive_m.py Normal file
View File

@ -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)

103
parse.py Normal file
View File

@ -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()

13
test/basic1 Normal file
View File

@ -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

13
test/basic2 Normal file
View File

@ -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

8
test/iff1 Normal file
View File

@ -0,0 +1,8 @@
A <=> B
C <=> D
C => A
=D
?ABCD
# ABCD should be true

6
test/neg1 Normal file
View File

@ -0,0 +1,6 @@
B + !C => A
=
?A
# A should be false

6
test/neg2 Normal file
View File

@ -0,0 +1,6 @@
B + !C => A
=B
?A
# A should be true

6
test/neg3 Normal file
View File

@ -0,0 +1,6 @@
B + !C => A
=C
?A
# A should be false

6
test/neg4 Normal file
View File

@ -0,0 +1,6 @@
B + !C => A
=BC
?A
# A should be false

5
test/not_conclu1 Normal file
View File

@ -0,0 +1,5 @@
A => C
B => !C
=
?ABC

5
test/not_conclu2 Normal file
View File

@ -0,0 +1,5 @@
A => C
B => !C
=A
?ABC

5
test/not_conclu3 Normal file
View File

@ -0,0 +1,5 @@
A => C
B => !C
=B
?ABC

5
test/not_conclu4 Normal file
View File

@ -0,0 +1,5 @@
A => C
B => !C
=AB
?ABC

5
test/not_conclu5 Normal file
View File

@ -0,0 +1,5 @@
B => !C
A => C
=ABC
?ABC

8
test/or1 Normal file
View File

@ -0,0 +1,8 @@
B + C => A
D | E => B
B => C
=
?A
# A should be false

8
test/or2 Normal file
View File

@ -0,0 +1,8 @@
B + C => A
D | E => B
B => C
=D
?A
# A should be true

8
test/or3 Normal file
View File

@ -0,0 +1,8 @@
B + C => A
D | E => B
B => C
=E
?A
# A should be true

8
test/or4 Normal file
View File

@ -0,0 +1,8 @@
B + C => A
D | E => B
B => C
=DE
?A
# A should be true

6
test/or_conclu1 Normal file
View File

@ -0,0 +1,6 @@
A => B | C
B => D
C => E | B
=A
?ABCDE

7
test/par1 Normal file
View File

@ -0,0 +1,7 @@
A | B + C => E
(F | G) + H => E
=
?E
# E should be false

7
test/par10 Normal file
View File

@ -0,0 +1,7 @@
A | B + C => E
(F | G) + H => E
=FH
?E
# E should be true

7
test/par11 Normal file
View File

@ -0,0 +1,7 @@
A | B + C => E
(F | G) + H => E
=GH
?E
# E should be true

7
test/par2 Normal file
View File

@ -0,0 +1,7 @@
A | B + C => E
(F | G) + H => E
=A
?E
# E should be true

7
test/par3 Normal file
View File

@ -0,0 +1,7 @@
A | B + C => E
(F | G) + H => E
=B
?E
# E should be false

7
test/par4 Normal file
View File

@ -0,0 +1,7 @@
A | B + C => E
(F | G) + H => E
=C
?E
# E should be false

7
test/par5 Normal file
View File

@ -0,0 +1,7 @@
A | B + C => E
(F | G) + H => E
=AC
?E
# E should be true

7
test/par6 Normal file
View File

@ -0,0 +1,7 @@
A | B + C => E
(F | G) + H => E
=BC
?E
# E should be true

7
test/par7 Normal file
View File

@ -0,0 +1,7 @@
A | B + C => E
(F | G) + H => E
=F
?E
# E should be false

7
test/par8 Normal file
View File

@ -0,0 +1,7 @@
A | B + C => E
(F | G) + H => E
=G
?E
# E should be false

7
test/par9 Normal file
View File

@ -0,0 +1,7 @@
A | B + C => E
(F | G) + H => E
=H
?E
# E should be false

7
test/same_conclu1 Normal file
View File

@ -0,0 +1,7 @@
B => A
C => A
=
?A
# A should be false

7
test/same_conclu2 Normal file
View File

@ -0,0 +1,7 @@
B => A
C => A
=B
?A
# A should be true

7
test/same_conclu3 Normal file
View File

@ -0,0 +1,7 @@
B => A
C => A
=C
?A
# A should be true

7
test/same_conclu4 Normal file
View File

@ -0,0 +1,7 @@
B => A
C => A
=BC
?A
# A should be true

8
test/xor1 Normal file
View File

@ -0,0 +1,8 @@
B + C => A
D ^ E => B
B => C
=
?A
# A should be false

8
test/xor2 Normal file
View File

@ -0,0 +1,8 @@
B + C => A
D ^ E => B
B => C
=D
?A
# A should be true

8
test/xor3 Normal file
View File

@ -0,0 +1,8 @@
B + C => A
D ^ E => B
B => C
=E
?A
# A should be true

8
test/xor4 Normal file
View File

@ -0,0 +1,8 @@
B + C => A
D ^ E => B
B => C
=DE
?A
# A should be false

6
test/xor_conclu1 Normal file
View File

@ -0,0 +1,6 @@
A => B ^ C
B => D
C => E ^ B
=A
?ABCDE

6
unit.sh Normal file
View File

@ -0,0 +1,6 @@
for file in test/*
do
python3 parse.py $file
echo "\n\n\033[32mPress ENTER\033[0m\n"
read
done

25
user.py Normal file
View File

@ -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)