sml.py
author Eugen Sawin <sawine@me73.com>
Mon, 30 May 2011 13:19:03 +0200
changeset 24 502d9d333417
parent 23 649753d09da6
child 25 3418ff66b9ed
permissions -rw-r--r--
Minor fix.
     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 
     7 def main():
     8     # edit your formulae here
     9     p = Variable("p")
    10     q = Variable("q")
    11     r = Variable("r") 
    12     s = Variable("s")
    13     formula1 = Imp(Eq(p, q), r)
    14     formula2 = Not(Or(p, q))
    15     formula3 = Not(Not(p))
    16     formula4 = Imp(Box(Imp(Diamond(p), p)), Box(Imp(p, Box(p))))
    17     formula5 = Imp(And(Or(p, q), r), Not(s))
    18     formula = formula4 # choose your formula here
    19 
    20     args = parse_arguments()
    21     if (args.formula):
    22         scanner = Scanner(TOKENS)
    23         parser = Parser(SYNTAX, scanner)
    24         (accepted, out, tree_nodes) = parser.parse(args.formula)
    25         if not accepted:
    26             print EXCUSE 
    27             return
    28         tree = SyntaxTree(out, tree_nodes) 
    29         formula = tree.root
    30    
    31     original = formula
    32     print "formula: \t\t\t%s [%i]" % (formula, formula.depth())
    33     formula = expand_imp_eq(formula)
    34     print "expanded -> and <->: \t\t%s [%i]" % (formula, formula.depth()) 
    35     formula = de_morgan(formula)
    36     print "reduced ~: \t\t\t%s [%i]" % (formula, formula.depth()) 
    37     formula = reduce_iter(formula)
    38     print "reduced iterated modalities: \t%s [%i]" % (formula, formula.depth()) 
    39     formula = reduce_iter(dist_mod((formula)))
    40     print "distributed modalities+: \t%s [%i]" % (formula, formula.depth()) 
    41     formula = mcnf(formula)
    42     print "alpha mcnf: \t\t\t%s [%i]" % (formula, formula.depth())
    43     cnf_formula = cnf(original)
    44     print "alpha cnf: \t\t\t%s [%i]" % (cnf_formula, cnf_formula.depth())
    45 
    46 def values(fun, f):  
    47     return [fun(v) for v in f.values] 
    48 
    49 def expand_imp_eq(formula):  
    50     expmap = {IMP: lambda (a, b): Or(Not(a), b),
    51               EQ: lambda (a, b): And(Or(Not(a), b), Or(a, Not(b)))}   
    52     if not isinstance(formula, Operator):   
    53         return formula
    54     if formula.id in expmap:
    55         return expmap[formula.id](values(expand_imp_eq, formula))   
    56     return formula.__class__(*values(expand_imp_eq, formula))
    57 
    58 def de_morgan(formula):    
    59     negmap = {AND: lambda (a, b): Or(negate(a), negate(b)),
    60               OR: lambda (a, b): And(negate(a), negate(b)),
    61               BOX: lambda (a,): Diamond(negate(a)),
    62               DIAMOND: lambda (a,): Box(negate(a)),
    63               VARIABLE: lambda (a,): Not(Variable(a)),
    64               NOT: lambda (a,): a}    
    65     def negate(f):
    66         return negmap[f.id](f.values)
    67     if not isinstance(formula, Operator):
    68         return formula  
    69     if formula.id == NOT:
    70         return negate(formula.values[0])
    71     return formula.__class__(*values(de_morgan, formula))
    72 
    73 def reduce_iter(formula):  
    74     redset = set(((BOX, BOX), (DIAMOND, DIAMOND), (DIAMOND, BOX), (BOX, DIAMOND)))
    75     if not isinstance(formula, Operator) or not isinstance(formula.values[0], Operator):
    76         return formula
    77     ids = (formula.id, formula.values[0].id)
    78     if ids in redset:
    79         return reduce_iter(formula.values[0])
    80     return formula.__class__(*values(reduce_iter, formula))
    81 
    82 def dist_mod(formula):    
    83     distmap = {(BOX, AND): lambda (a, b): And(Box(a), Box(b)),
    84                (DIAMOND, OR): lambda (a, b): Or(Diamond(a), Diamond(b)),
    85                (BOX, OR): lambda (a, b): (isinstance(a, Box) or isinstance(b, Box))               
    86                and Or(Box(a), Box(b)) or Box(Or(a, b)),
    87                (DIAMOND, AND): lambda (a, b): (isinstance(a, Diamond) or isinstance(b, Diamond))
    88                and And(Diamond(a), Diamond(b)) or Diamond(And(a, b))}   
    89     if not isinstance(formula, Operator) or not isinstance(formula.values[0], Operator):
    90         return formula
    91     ids = (formula.id, formula.values[0].id)
    92     if ids in distmap:
    93         return distmap[ids](values(dist_mod, formula.values[0]))
    94     return formula.__class__(*values(dist_mod, formula))    
    95 
    96 def mcnf(formula):
    97     if formula.id == OR:
    98         conj_id = int(formula.values[1].id == AND) 
    99         return And(Or(formula.values[conj_id].values[0], formula.values[1 - conj_id]), 
   100                     Or(formula.values[conj_id].values[1], formula.values[1 - conj_id]));
   101     return formula
   102 
   103 def cnf(formula):      
   104     def part(f):
   105         for v in f.values:
   106             if v.id != VARIABLE:
   107                 sub_formulae.append(v)
   108                 part(v)
   109     sub_formulae = [formula]
   110     part(formula)
   111     sub_formulae.reverse() 
   112     known = {}
   113     bicon = []
   114     for s in sub_formulae:       
   115         for i, v in enumerate(s.values):
   116             if v in known:
   117                 s.values[i] = known[v]
   118         bicon.append(Eq(Variable("x" + str(len(bicon))), s))
   119         known[s] = bicon[-1].values[0]    
   120     def conjunct(f):
   121         if len(bicon):
   122             return And(f, conjunct(bicon.pop()))
   123         return f
   124     return mcnf(dist_mod(reduce_iter(de_morgan(expand_imp_eq(conjunct(known[formula]))))))
   125 
   126 """ The Parser - Do NOT look below this line! """ 
   127 import re
   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 = list(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 Parser(object):
   273     def __init__(self, syntax, scanner):
   274         self.syntax = syntax
   275         self.scanner = scanner
   276     def parse(self, source):
   277         table = {(S, Variable): 0,
   278                  (S, Not): 1,
   279                  (S, Lb): 2,
   280                  (S, Box): 3,
   281                  (S, Diamond): 4,
   282                  (A, S, And): 5,
   283                  (A, S, Or): 6,
   284                  (A, S, Imp): 7,
   285                  (A, S, Eq): 8,
   286                  (A, Variable, And): 5,
   287                  (A, Variable, Or): 6,
   288                  (A, Variable, Imp): 7,
   289                  (A, Variable, Eq): 8,
   290                  (A, Not, Variable, And): 5,
   291                  (A, Not, Variable, Or): 6,
   292                  (A, Not, Variable, Imp): 7,
   293                  (A, Not, Variable, Eq): 8}
   294         stack = [S]
   295         tree_nodes = []
   296         tree = None
   297         out = []
   298         self.scanner.reset(source)
   299         (token, value) = self.scanner.next()
   300         (lookahead, la_value) = self.scanner.next()
   301         (lookahead2, la2_value) = self.scanner.next()
   302         accepted = True 
   303         while token and len(stack): 
   304             top = stack[-1]               
   305             if top == token:
   306                 tree_nodes.append((token, value))
   307                 stack.pop()
   308                 #tree_nodes.append((stack.pop(), value))
   309                 (token, value) = (lookahead, la_value)
   310                 (lookahead, la_value) = (lookahead2, la2_value)
   311                 #(lookahead, _) = self.scanner.next()
   312                 (lookahead2, la2_value) = self.scanner.next()
   313             else:
   314                 action = None
   315                 if (top, token) in table:
   316                     action = table[(top, token)]
   317                 elif (top, token, lookahead) in table:
   318                     action = table[(top, token, lookahead)]  
   319                 elif (top, token, lookahead, lookahead2) in table:
   320                     action = table[(top, token, lookahead, lookahead2)]    
   321                 if action is None:
   322                     accepted = False
   323                     break
   324                 
   325                 out.append(action)
   326                 stack.pop()
   327                 stack.extend(reversed(self.syntax[action]))
   328         accepted = accepted and not len(stack)
   329         return (accepted, out, tree_nodes) 
   330 
   331 class SyntaxTree(object):
   332     def __init__(self, seq, tree_nodes):
   333         seq.reverse()
   334         tree_nodes.reverse()      
   335         self.root = self.compile(seq, tree_nodes)[0]       
   336     def compile(self, seq, tree_nodes):
   337         stack = []     
   338         while len(seq):           
   339             s = seq.pop()
   340             if s == 0:
   341                 c, v = tree_nodes.pop()
   342                 while c != Variable:
   343                     c, v = tree_nodes.pop()
   344                 stack.append(Variable(v))
   345             elif s == 1:
   346                 stack.append(Not(self.compile(seq, tree_nodes)))
   347             elif s == 2:
   348                 stack.extend(self.compile(seq, tree_nodes))
   349             elif s == 3:
   350                 stack.append(Box(self.compile(seq, tree_nodes)))
   351             elif s == 4:
   352                 stack.append(Diamond(self.compile(seq, tree_nodes)))
   353             elif s == 5:
   354                 stack.append(And(self.compile(seq, tree_nodes)))
   355             elif s == 6:
   356                 stack.append(Or(self.compile(seq, tree_nodes)))
   357             elif s == 7:
   358                 stack.append(Imp(self.compile(seq, tree_nodes)))
   359             elif s == 8:
   360                 stack.append(Eq(self.compile(seq, tree_nodes)))       
   361         return stack   
   362 
   363 from argparse import ArgumentParser
   364 
   365 def parse_arguments():
   366     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.")
   367     parser.add_argument("-f", "--formula", help="provide your own formula here, or better don't") 
   368     return parser.parse_args()
   369  
   370 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"""
   371 
   372 if __name__ == "__main__":
   373     main()