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