sml.py
author Eugen Sawin <sawine@me73.com>
Wed, 18 May 2011 05:09:05 +0200
changeset 22 53f84d131998
parent 21 b7480b2e8fd2
child 23 649753d09da6
permissions -rw-r--r--
Basic CNF.
     1 """
     2 Name: sml - Simple Modal Logic Lib.
     3 Description: sml provides classes for simple modal logic formulae construction and reduction. It also features a disfunctional parser for the SML syntax.
     4 Author: Eugen Sawin <sawine@informatik.uni-freiburg.de>
     5 """
     6 import re
     7 
     8 def main():
     9     # edit your formulae here
    10     p = Variable(["p"])
    11     q = Variable(["q"])
    12     r = Variable(["r"]) 
    13     s = Variable(["s"])
    14     formula1 = Imp([Eq([p, q]), r])
    15     formula2 = Not([Or([p, q])])
    16     formula3 = Not([Not([p])])
    17     formula4 = Imp([Box([Imp([Diamond([p]), p])]), Box([Imp([p, Box([p])])])])
    18     formula5 = Imp([And([Or([p, q]), r]), Not([s])])
    19     formula = formula5 # choose your formula here
    20 
    21     args = parse_arguments()
    22     if (args.formula):
    23         scanner = Scanner(TOKENS)
    24         parser = Parser(SYNTAX, scanner)
    25         (accepted, out, tree_nodes) = parser.parse(args.formula)
    26         if not accepted:
    27             print EXCUSE 
    28             return
    29         tree = SyntaxTree(out, tree_nodes) 
    30         formula = tree.root
    31    
    32     original = formula
    33     print "formula: \t\t\t%s [%i]" % (formula, formula.depth())
    34     formula = expand_imp_eq(formula)
    35     print "expanded -> and <->: \t\t%s [%i]" % (formula, formula.depth()) 
    36     formula = de_morgan(formula)
    37     print "reduced ~: \t\t\t%s [%i]" % (formula, formula.depth()) 
    38     formula = reduce_iter(formula)
    39     print "reduced iterated modalities: \t%s [%i]" % (formula, formula.depth()) 
    40     formula = reduce_iter(dist_mod((formula)))
    41     print "distributed modalities+: \t%s [%i]" % (formula, formula.depth()) 
    42     formula = mcnf(formula)
    43     print "alpha mcnf: \t\t\t%s [%i]" % (formula, formula.depth())
    44     cnf_formula = cnf(original)
    45     print "alpha cnf: \t\t\t%s [%i]" % (cnf_formula, cnf_formula.depth())
    46 
    47 def values(fun, f):  
    48     return [fun(v) for v in f.values] 
    49 
    50 def expand_imp_eq(formula):  
    51     expmap = {IMP: lambda (a, b): Or([Not([a]), b]),
    52               EQ: lambda (a, b): And([Or([Not([a]), b]), Or([a, Not([b])])])}   
    53     if not isinstance(formula, Operator):   
    54         return formula
    55     if formula.id in expmap:
    56         return expmap[formula.id](values(expand_imp_eq, formula))   
    57     return formula.__class__(values(expand_imp_eq, formula))
    58 
    59 def de_morgan(formula):    
    60     negmap = {AND: lambda (a, b): Or([negate(a), negate(b)]),
    61               OR: lambda (a, b): And([negate(a), negate(b)]),
    62               BOX: lambda (a,): Diamond([negate(a)]),
    63               DIAMOND: lambda (a,): Box([negate(a)]),
    64               VARIABLE: lambda (a,): Not([Variable([a])]),
    65               NOT: lambda (a,): a}    
    66     def negate(f):
    67         return negmap[f.id](f.values)
    68     if not isinstance(formula, Operator):
    69         return formula  
    70     if formula.id == NOT:
    71         return negate(formula.values[0])
    72     return formula.__class__(values(de_morgan, formula))
    73 
    74 def reduce_iter(formula):  
    75     redset = set(((BOX, BOX), (DIAMOND, DIAMOND), (DIAMOND, BOX), (BOX, DIAMOND)))
    76     if not isinstance(formula, Operator) or not isinstance(formula.values[0], Operator):
    77         return formula
    78     ids = (formula.id, formula.values[0].id)
    79     if ids in redset:
    80         return reduce_iter(formula.values[0])
    81     return formula.__class__(values(reduce_iter, formula))
    82 
    83 def dist_mod(formula):    
    84     distmap = {(BOX, AND): lambda (a, b): And([Box([a]), Box([b])]),
    85                (DIAMOND, OR): lambda (a, b): Or([Diamond([a]), Diamond([b])]),
    86                (BOX, OR): lambda (a, b): (isinstance(a, Box) or isinstance(b, Box))               
    87                and Or([Box([a]), Box([b])]) or Box([Or([a, b])]),
    88                (DIAMOND, AND): lambda (a, b): (isinstance(a, Diamond) or isinstance(b, Diamond))
    89                and And([Diamond([a]), Diamond([b])]) or Diamond([And([a, b])])}   
    90     if not isinstance(formula, Operator) or not isinstance(formula.values[0], Operator):
    91         return formula
    92     ids = (formula.id, formula.values[0].id)
    93     if ids in distmap:
    94         return distmap[ids](values(dist_mod, formula.values[0]))
    95     return formula.__class__(values(dist_mod, formula))    
    96 
    97 def mcnf(formula):
    98     if formula.id == OR:
    99         conj_id = int(formula.values[1].id == AND) 
   100         return And([Or([formula.values[conj_id].values[0], formula.values[1 - conj_id]]), 
   101                     Or([formula.values[conj_id].values[1], formula.values[1 - conj_id]])]);
   102     return formula
   103 
   104 def cnf(formula):      
   105     def part(f):
   106         for v in f.values:
   107             if v.id != VARIABLE:
   108                 sub_formulae.append(v)
   109                 part(v)
   110     sub_formulae = [formula]
   111     part(formula)
   112     sub_formulae.reverse() 
   113     known = {}
   114     bicon = []
   115     for s in sub_formulae:       
   116         for i, v in enumerate(s.values):
   117             if v in known:
   118                 s.values[i] = known[v]
   119         bicon.append(Eq([Variable(["x" + str(len(bicon))]), s]))
   120         known[s] = bicon[-1].values[0]    
   121     def conjunct(f):
   122         if len(bicon):
   123             return And([f, conjunct(bicon.pop())])
   124         return f
   125     return mcnf(dist_mod(reduce_iter(de_morgan(expand_imp_eq(conjunct(known[formula]))))))
   126 
   127 """ The Parser - Do NOT look below this line! """ 
   128 
   129 NOT = "~"
   130 AND = "&"
   131 OR = "|"
   132 IMP = "->"
   133 EQ = "<->"
   134 BOX = "[]"
   135 DIAMOND = "<>"
   136 VARIABLE = "VAR"
   137 LB = "("
   138 RB = ")"
   139 
   140 class Operator(object):
   141     def __init__(self, id, arity, values):
   142         self.id = id         
   143         self.arity = arity
   144         self.values = values
   145     def depth(self):       
   146         if self.arity == 0:
   147             return 0
   148         if self.id in (BOX, DIAMOND):
   149             return 1 + self.values[0].depth()
   150         if self.id == NOT:           
   151             return self.values[0].depth()
   152         return max(self.values[0].depth(), self.values[1].depth())
   153     def __eq__(self, other):
   154         return self.id == other.id and self.values == other.values # commutative types not considered yet
   155     def __str__(self):
   156         out = self.id
   157         if self.arity == 0:
   158             out = str(self.values[0])           
   159         if self.arity == 1:
   160             out = self.id + str(self.values[0])
   161         elif self.arity  == 2:          
   162             out = "(" + str(self.values[0]) + " " + self.id + " " + str(self.values[1]) + ")"
   163         return out
   164 
   165 class Not(Operator):
   166     arity = 1
   167     def __init__(self, values):
   168         Operator.__init__(self, NOT, Not.arity, values)       
   169     def __call__(self):
   170         pass
   171 
   172 class And(Operator):
   173     arity = 2
   174     def __init__(self, values):
   175         Operator.__init__(self, AND, And.arity, values)
   176        
   177 class Or(Operator):
   178     arity = 2
   179     def __init__(self, values):
   180         Operator.__init__(self, OR, Or.arity, values)       
   181 
   182 class Imp(Operator):
   183     arity = 2
   184     def __init__(self, values):
   185         Operator.__init__(self, IMP, Imp.arity, values)
   186     
   187 class Eq(Operator):
   188     arity = 2
   189     def __init__(self, values):
   190         Operator.__init__(self, EQ, Eq.arity, values)
   191     
   192 class Box(Operator):
   193     arity = 1
   194     def __init__(self, values):
   195         Operator.__init__(self, BOX, Box.arity, values)
   196 
   197 class Diamond(Operator):
   198     arity = 1
   199     def __init__(self, values):
   200         Operator.__init__(self, DIAMOND, Diamond.arity, values)
   201     
   202 class Variable(Operator):
   203     arity = 0
   204     def __init__(self, values):
   205         Operator.__init__(self, VARIABLE, Variable.arity, values)   
   206 
   207 class Lb(Operator):
   208     arity = -1
   209     def __init__(self, values):
   210         Operator.__init__(self, LB, Lb.arity, values)
   211 
   212 class Rb(Operator):
   213     arity = -1
   214     def __init__(self, values):
   215         Operator.__init__(self, RB, Rb.arity, values)
   216 
   217 class Formula(object):
   218     def __init__(self):
   219         pass 
   220 
   221 TOKENS = {re.compile("\("): Lb,
   222           re.compile("\)"): Rb,
   223           re.compile("~"): Not,
   224           re.compile("&"): And,
   225           re.compile("\|"): Or,
   226           re.compile("->"): Imp,
   227           re.compile("<->"): Eq,
   228           re.compile("\[\]"): Box,
   229           re.compile("<>"): Diamond,
   230           re.compile("[a-z]+"): Variable}
   231          
   232 class Scanner(object):
   233     def __init__(self, tokens, source=None):
   234         self.tokens = tokens
   235         self.reset(source)
   236     def next(self):         
   237         while self.i < len(self.source):
   238             re.purge()
   239             for p, token in self.tokens.iteritems():
   240                 m = p.match(self.source[self.i:])               
   241                 if m:                  
   242                     self.i += m.end(0)
   243                     value = m.group(0)  
   244                     return (token, value)
   245             self.i += 1
   246         return (None, None)
   247     def reset(self, source):
   248         self.i = 0
   249         self.source = source
   250 
   251 S = Formula
   252 A = "A"
   253 #0 S: (Variable,),
   254 #1 S: (Not, S),
   255 #2 S: (Lb, A, Rb),
   256 #3 S: (Box, S),
   257 #4 S: (Diamond, S),
   258 #5 A: (S, And, S),
   259 #6 A: (S, Or, S),
   260 #7 A: (S, Imp, S),
   261 #8 A: (S, Eq, S)
   262 SYNTAX = ((Variable,),
   263           (Not, S),
   264           (Lb, A, Rb),
   265           (Box, S),
   266           (Diamond, S),
   267           (S, And, S),
   268           (S, Or, S),
   269           (S, Imp, S),
   270           (S, Eq, S))
   271 
   272 class Node(object):
   273     def __init__(self, parent, value):
   274         self.parent = parent
   275         self.value = value
   276         self.children = []
   277     def addChild(self, value):
   278         self.children.append(self, node)
   279 
   280 class Parser(object):
   281     def __init__(self, syntax, scanner):
   282         self.syntax = syntax
   283         self.scanner = scanner
   284     def parse(self, source):
   285         table = {(S, Variable): 0,
   286                  (S, Not): 1,
   287                  (S, Lb): 2,
   288                  (S, Box): 3,
   289                  (S, Diamond): 4,
   290                  (A, S, And): 5,
   291                  (A, S, Or): 6,
   292                  (A, S, Imp): 7,
   293                  (A, S, Eq): 8,
   294                  (A, Variable, And): 5,
   295                  (A, Variable, Or): 6,
   296                  (A, Variable, Imp): 7,
   297                  (A, Variable, Eq): 8,
   298                  (A, Not, Variable, And): 5,
   299                  (A, Not, Variable, Or): 6,
   300                  (A, Not, Variable, Imp): 7,
   301                  (A, Not, Variable, Eq): 8}
   302         stack = [S]
   303         tree_nodes = []
   304         tree = None
   305         out = []
   306         self.scanner.reset(source)
   307         (token, value) = self.scanner.next()
   308         (lookahead, la_value) = self.scanner.next()
   309         (lookahead2, la2_value) = self.scanner.next()
   310         accepted = True 
   311         while token and len(stack): 
   312             top = stack[-1]               
   313             if top == token:
   314                 tree_nodes.append((token, value))
   315                 stack.pop()
   316                 #tree_nodes.append((stack.pop(), value))
   317                 (token, value) = (lookahead, la_value)
   318                 (lookahead, la_value) = (lookahead2, la2_value)
   319                 #(lookahead, _) = self.scanner.next()
   320                 (lookahead2, la2_value) = self.scanner.next()
   321             else:
   322                 action = None
   323                 if (top, token) in table:
   324                     action = table[(top, token)]
   325                 elif (top, token, lookahead) in table:
   326                     action = table[(top, token, lookahead)]  
   327                 elif (top, token, lookahead, lookahead2) in table:
   328                     action = table[(top, token, lookahead, lookahead2)]    
   329                 if action is None:
   330                     accepted = False
   331                     break
   332                 
   333                 out.append(action)
   334                 stack.pop()
   335                 stack.extend(reversed(self.syntax[action]))
   336         accepted = accepted and not len(stack)
   337         return (accepted, out, tree_nodes) 
   338 
   339 class SyntaxTree(object):
   340     def __init__(self, seq, tree_nodes):
   341         seq.reverse()
   342         tree_nodes.reverse()      
   343         self.root = self.compile(seq, tree_nodes)[0]       
   344     def compile(self, seq, tree_nodes):
   345         stack = []     
   346         while len(seq):           
   347             s = seq.pop()
   348             if s == 0:
   349                 c, v = tree_nodes.pop()
   350                 while c != Variable:
   351                     c, v = tree_nodes.pop()
   352                 stack.append(Variable(v))
   353             elif s == 1:
   354                 stack.append(Not(self.compile(seq, tree_nodes)))
   355             elif s == 2:
   356                 stack.extend(self.compile(seq, tree_nodes))
   357             elif s == 3:
   358                 stack.append(Box(self.compile(seq, tree_nodes)))
   359             elif s == 4:
   360                 stack.append(Diamond(self.compile(seq, tree_nodes)))
   361             elif s == 5:
   362                 stack.append(And(self.compile(seq, tree_nodes)))
   363             elif s == 6:
   364                 stack.append(Or(self.compile(seq, tree_nodes)))
   365             elif s == 7:
   366                 stack.append(Imp(self.compile(seq, tree_nodes)))
   367             elif s == 8:
   368                 stack.append(Eq(self.compile(seq, tree_nodes)))       
   369         return stack   
   370 
   371 from argparse import ArgumentParser
   372 
   373 def parse_arguments():
   374     parser = ArgumentParser(description="Tries to parse simple modal logic syntax and reduces the formula. Edit your formula directly in the code file for best results.")
   375     parser.add_argument("-f", "--formula", help="provide your own formula here, or better don't") 
   376     return parser.parse_args()
   377  
   378 EXCUSE = """The formula was not accepted by me, because I do not like you. It is not personal. I seem to have some issues with formulae with inner nesting and I generally suffer from my hasty implementation. I would like to thank you for your coorporation by providing the formula within the code file itself. Thank you. ~The Parser"""
   379 
   380 if __name__ == "__main__":
   381     main()