Updated with goodies.
authorEugen Sawin <sawine@me73.com>
Mon, 18 Jul 2011 01:51:06 +0200
changeset 56d95a6fb42886
parent 55 a0a90e9ae77a
child 57 b27fbffacec9
Updated with goodies.
downloads/cau.zip
downloads/sml.py
downloads/tim.zip
factory/v2011/linksend.html
factory/v2011/personalwork.html
     1.1 Binary file downloads/cau.zip has changed
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/downloads/sml.py	Mon Jul 18 01:51:06 2011 +0200
     2.3 @@ -0,0 +1,447 @@
     2.4 +"""
     2.5 +Name: sml - Simple Modal Logic Lib.
     2.6 +Description: sml provides classes for simple modal logic formulae construction and reduction. It also features a disfunctional parser for the SML syntax.
     2.7 +Author: Eugen Sawin <sawine@informatik.uni-freiburg.de>
     2.8 +"""
     2.9 +
    2.10 +TMP_DIMACS = "dimacs~"
    2.11 +TMP_SOLUTION = "solution~"
    2.12 +
    2.13 +def main():
    2.14 +    # edit your formulae here
    2.15 +    p = Variable("p")
    2.16 +    q = Variable("q")
    2.17 +    r = Variable("r") 
    2.18 +    s = Variable("s")
    2.19 +    formula1 = Imp(Eq(p, q), r)
    2.20 +    formula2 = Not(Or(p, q))
    2.21 +    formula3 = Not(Not(p))
    2.22 +    formula4 = Imp(Box(Imp(Diamond(p), p)), Box(Imp(p, Box(p))))
    2.23 +    formula5 = Imp(And(Or(p, q), r), Not(s))
    2.24 +    formula6 = And(Or(p, Or(q, Not(s))), And(Or(r, Not(s)), Or(Not(p), s)))
    2.25 +    formula = formula6 # choose your formula here
    2.26 +
    2.27 +    args = parse_arguments()   
    2.28 +    if (args.formula):
    2.29 +        scanner = Scanner(TOKENS)
    2.30 +        parser = Parser(SYNTAX, scanner)
    2.31 +        (accepted, out, tree_nodes) = parser.parse(args.formula)
    2.32 +        if not accepted:
    2.33 +            print EXCUSE 
    2.34 +            return
    2.35 +        tree = SyntaxTree(out, tree_nodes) 
    2.36 +        formula = tree.root
    2.37 +   
    2.38 +    original = formula
    2.39 +    print "formula: \t\t\t%s [%i]" % (formula, formula.depth())
    2.40 +    formula = expand_imp_eq(formula)
    2.41 +    print "expanded -> and <->: \t\t%s [%i]" % (formula, formula.depth()) 
    2.42 +    formula = de_morgan(formula)
    2.43 +    print "reduced ~: \t\t\t%s [%i]" % (formula, formula.depth()) 
    2.44 +    formula = reduce_iter(formula)
    2.45 +    print "reduced iterated modalities: \t%s [%i]" % (formula, formula.depth()) 
    2.46 +    formula = reduce_iter(dist_mod((formula)))
    2.47 +    print "distributed modalities+: \t%s [%i]" % (formula, formula.depth()) 
    2.48 +    formula = mcnf(formula)
    2.49 +    print "alpha mcnf: \t\t\t%s [%i]" % (formula, formula.depth())
    2.50 +    #cnf_formula = cnf(original)
    2.51 +    #print "alpha cnf: \t\t\t%s [%i]" % (cnf_formula, cnf_formula.depth())
    2.52 +    
    2.53 +    if not args.dimacs:
    2.54 +        dimacs_file = TMP_DIMACS
    2.55 +    else:
    2.56 +        dimacs_file = args.dimacs
    2.57 +    solution = sat_test(original, dimacs_file)
    2.58 +    if solution:
    2.59 +        print "satisfiable: \t\t\t%s " % (", ".join([str(s) for s in solution]))
    2.60 +    else:
    2.61 +        print "not satisfiable"
    2.62 +    
    2.63 +from subprocess import call, PIPE
    2.64 +
    2.65 +def sat_test(formula, dimacs_file):
    2.66 +    out, variables = dimacs(formula)
    2.67 +    with open(dimacs_file, "w") as f:
    2.68 +        f.write(out) 
    2.69 +    call(["minisat2", "-verb=0", dimacs_file, TMP_SOLUTION], stdout=PIPE)
    2.70 +    with open(TMP_SOLUTION) as f:
    2.71 +        solved, assignment = f.readlines()  
    2.72 +    solved = solved[0] == "S"      
    2.73 +    if solved:
    2.74 +        positive = set() 
    2.75 +        for a in assignment.split():           
    2.76 +            neg = a[0] == "-"
    2.77 +            if not neg:
    2.78 +                positive.add(int(a))       
    2.79 +        solution = []
    2.80 +        for v, n in variables.iteritems():           
    2.81 +            if n in positive:              
    2.82 +                solution.append(Variable(v))
    2.83 +            else:
    2.84 +                solution.append(Not(Variable(v)))
    2.85 +        return solution
    2.86 +
    2.87 +def dimacs(formula):
    2.88 +    def clausify(f):        
    2.89 +        if f.id == AND:    
    2.90 +            c1 = clausify(f.values[0])   
    2.91 +            c2 = clausify(f.values[1])   
    2.92 +            if c1:
    2.93 +                clauses.append(clausify(f.values[0]))  
    2.94 +            if c2:
    2.95 +                clauses.append(clausify(f.values[1]))              
    2.96 +        if f.id == OR:
    2.97 +            clause = []         
    2.98 +            clause.extend(clausify(f.values[0]))
    2.99 +            clause.extend(clausify(f.values[1]))            
   2.100 +            return clause
   2.101 +        if f.id == NOT:
   2.102 +            return ["-" + clausify(f.values[0])]
   2.103 +        if f.id == VARIABLE:
   2.104 +            if f.values[0] in variables:
   2.105 +                n = variables[f.values[0]]
   2.106 +            else:
   2.107 +                n = len(variables) + 1
   2.108 +                variables[f.values[0]] = n
   2.109 +            return str(n)
   2.110 +      
   2.111 +    variables = {}
   2.112 +    clauses = []
   2.113 +    clausify(formula)
   2.114 +    out = "c DIMACS CNF input file generated by sml\n"
   2.115 +    out += "c Author: Eugen Sawin <sawine@informatik.uni-freiburg.de>\n"
   2.116 +    out += "p cnf %i %i\n" % (len(variables), len(clauses))    
   2.117 +    out += " 0\n".join([" ".join(c) for c in clauses]) + " 0\n"
   2.118 +    return out, variables
   2.119 +
   2.120 +def values(fun, f):  
   2.121 +    return [fun(v) for v in f.values] 
   2.122 +
   2.123 +def expand_imp_eq(formula):  
   2.124 +    expmap = {IMP: lambda (a, b): Or(Not(a), b),
   2.125 +              EQ: lambda (a, b): And(Or(Not(a), b), Or(a, Not(b)))}   
   2.126 +    if not isinstance(formula, Operator):   
   2.127 +        return formula
   2.128 +    if formula.id in expmap:
   2.129 +        return expmap[formula.id](values(expand_imp_eq, formula))   
   2.130 +    return formula.__class__(*values(expand_imp_eq, formula))
   2.131 +
   2.132 +def de_morgan(formula):    
   2.133 +    negmap = {AND: lambda (a, b): Or(negate(a), negate(b)),
   2.134 +              OR: lambda (a, b): And(negate(a), negate(b)),
   2.135 +              BOX: lambda (a,): Diamond(negate(a)),
   2.136 +              DIAMOND: lambda (a,): Box(negate(a)),
   2.137 +              VARIABLE: lambda (a,): Not(Variable(a)),
   2.138 +              NOT: lambda (a,): a}    
   2.139 +    def negate(f):
   2.140 +        return negmap[f.id](f.values)
   2.141 +    if not isinstance(formula, Operator):
   2.142 +        return formula  
   2.143 +    if formula.id == NOT:
   2.144 +        return negate(formula.values[0])
   2.145 +    return formula.__class__(*values(de_morgan, formula))
   2.146 +
   2.147 +def reduce_iter(formula):  
   2.148 +    redset = set(((BOX, BOX), (DIAMOND, DIAMOND), (DIAMOND, BOX), (BOX, DIAMOND)))
   2.149 +    if not isinstance(formula, Operator) or not isinstance(formula.values[0], Operator):
   2.150 +        return formula
   2.151 +    ids = (formula.id, formula.values[0].id)
   2.152 +    if ids in redset:
   2.153 +        return reduce_iter(formula.values[0])
   2.154 +    return formula.__class__(*values(reduce_iter, formula))
   2.155 +
   2.156 +def dist_mod(formula):    
   2.157 +    distmap = {(BOX, AND): lambda (a, b): And(Box(a), Box(b)),
   2.158 +               (DIAMOND, OR): lambda (a, b): Or(Diamond(a), Diamond(b)),
   2.159 +               (BOX, OR): lambda (a, b): (isinstance(a, Box) or isinstance(b, Box))               
   2.160 +               and Or(Box(a), Box(b)) or Box(Or(a, b)),
   2.161 +               (DIAMOND, AND): lambda (a, b): (isinstance(a, Diamond) or isinstance(b, Diamond))
   2.162 +               and And(Diamond(a), Diamond(b)) or Diamond(And(a, b))}   
   2.163 +    if not isinstance(formula, Operator) or not isinstance(formula.values[0], Operator):
   2.164 +        return formula
   2.165 +    ids = (formula.id, formula.values[0].id)
   2.166 +    if ids in distmap:
   2.167 +        return distmap[ids](values(dist_mod, formula.values[0]))
   2.168 +    return formula.__class__(*values(dist_mod, formula))    
   2.169 +
   2.170 +def mcnf(formula):
   2.171 +    if formula.id == OR:
   2.172 +        conj_id = int(formula.values[1].id == AND) 
   2.173 +        return And(Or(formula.values[conj_id].values[0], formula.values[1 - conj_id]), 
   2.174 +                    Or(formula.values[conj_id].values[1], formula.values[1 - conj_id]));
   2.175 +    return formula
   2.176 +
   2.177 +def cnf(formula):      
   2.178 +    def part(f):
   2.179 +        for v in f.values:
   2.180 +            if v.id != VARIABLE:
   2.181 +                sub_formulae.append(v)
   2.182 +                part(v) 
   2.183 +
   2.184 +    def conjunct(f):
   2.185 +        if len(bicon):
   2.186 +            return And(f, conjunct(bicon.pop()))
   2.187 +        return f
   2.188 +
   2.189 +    sub_formulae = [formula]
   2.190 +    part(formula)
   2.191 +    sub_formulae.reverse() 
   2.192 +    known = {}
   2.193 +    bicon = []
   2.194 +    for s in sub_formulae:       
   2.195 +        for i, v in enumerate(s.values):
   2.196 +            if v in known:
   2.197 +                s.values[i] = known[v]
   2.198 +        bicon.append(Eq(Variable("x" + str(len(bicon))), s))
   2.199 +        known[s] = bicon[-1].values[0]     
   2.200 +    return mcnf(dist_mod(reduce_iter(de_morgan(expand_imp_eq(conjunct(known[formula]))))))
   2.201 +
   2.202 +""" The Parser - Do NOT look below this line! """ 
   2.203 +import re
   2.204 +
   2.205 +NOT = "~"
   2.206 +AND = "&"
   2.207 +OR = "|"
   2.208 +IMP = "->"
   2.209 +EQ = "<->"
   2.210 +BOX = "[]"
   2.211 +DIAMOND = "<>"
   2.212 +VARIABLE = "VAR"
   2.213 +LB = "("
   2.214 +RB = ")"
   2.215 +
   2.216 +class Operator(object):
   2.217 +    def __init__(self, id, arity, *values):
   2.218 +        self.id = id         
   2.219 +        self.arity = arity
   2.220 +        self.values = list(values)
   2.221 +    def depth(self):       
   2.222 +        if self.arity == 0:
   2.223 +            return 0
   2.224 +        if self.id in (BOX, DIAMOND):
   2.225 +            return 1 + self.values[0].depth()
   2.226 +        if self.id == NOT:           
   2.227 +            return self.values[0].depth()       
   2.228 +        return max(self.values[0].depth(), self.values[1].depth())
   2.229 +    def __eq__(self, other):
   2.230 +        return self.id == other.id and self.values == other.values # commutative types not considered yet
   2.231 +    def __str__(self):
   2.232 +        out = self.id
   2.233 +        if self.arity == 0:
   2.234 +            out = str(self.values[0])           
   2.235 +        if self.arity == 1:
   2.236 +            out = self.id + str(self.values[0])
   2.237 +        elif self.arity  == 2:          
   2.238 +            out = "(" + str(self.values[0]) + " " + self.id + " " + str(self.values[1]) + ")"
   2.239 +        return out
   2.240 +
   2.241 +class Not(Operator):
   2.242 +    arity = 1
   2.243 +    def __init__(self, *values):
   2.244 +        Operator.__init__(self, NOT, Not.arity, *values)       
   2.245 +    def __call__(self):
   2.246 +        pass
   2.247 +
   2.248 +class And(Operator):
   2.249 +    arity = 2
   2.250 +    def __init__(self, *values):
   2.251 +        Operator.__init__(self, AND, And.arity, *values)
   2.252 +       
   2.253 +class Or(Operator):
   2.254 +    arity = 2
   2.255 +    def __init__(self, *values):
   2.256 +        Operator.__init__(self, OR, Or.arity, *values)       
   2.257 +
   2.258 +class Imp(Operator):
   2.259 +    arity = 2
   2.260 +    def __init__(self, *values):
   2.261 +        Operator.__init__(self, IMP, Imp.arity, *values)
   2.262 +    
   2.263 +class Eq(Operator):
   2.264 +    arity = 2
   2.265 +    def __init__(self, *values):
   2.266 +        Operator.__init__(self, EQ, Eq.arity, *values)
   2.267 +    
   2.268 +class Box(Operator):
   2.269 +    arity = 1
   2.270 +    def __init__(self, *values):
   2.271 +        Operator.__init__(self, BOX, Box.arity, *values)
   2.272 +
   2.273 +class Diamond(Operator):
   2.274 +    arity = 1
   2.275 +    def __init__(self, *values):
   2.276 +        Operator.__init__(self, DIAMOND, Diamond.arity, *values)
   2.277 +    
   2.278 +class Variable(Operator):
   2.279 +    arity = 0
   2.280 +    def __init__(self, *values):
   2.281 +        Operator.__init__(self, VARIABLE, Variable.arity, *values)   
   2.282 +
   2.283 +class Lb(Operator):
   2.284 +    arity = -1
   2.285 +    def __init__(self, *values):
   2.286 +        Operator.__init__(self, LB, Lb.arity, *values)
   2.287 +
   2.288 +class Rb(Operator):
   2.289 +    arity = -1
   2.290 +    def __init__(self, *values):
   2.291 +        Operator.__init__(self, RB, Rb.arity, *values)
   2.292 +
   2.293 +class Formula(object):
   2.294 +    def __init__(self):
   2.295 +        pass 
   2.296 +
   2.297 +TOKENS = {re.compile("\("): Lb,
   2.298 +          re.compile("\)"): Rb,
   2.299 +          re.compile("~"): Not,
   2.300 +          re.compile("&"): And,
   2.301 +          re.compile("\|"): Or,
   2.302 +          re.compile("->"): Imp,
   2.303 +          re.compile("<->"): Eq,
   2.304 +          re.compile("\[\]"): Box,
   2.305 +          re.compile("<>"): Diamond,
   2.306 +          re.compile("[a-z]+"): Variable}
   2.307 +         
   2.308 +class Scanner(object):
   2.309 +    def __init__(self, tokens, source=None):
   2.310 +        self.tokens = tokens
   2.311 +        self.reset(source)
   2.312 +    def next(self):         
   2.313 +        while self.i < len(self.source):
   2.314 +            re.purge()
   2.315 +            for p, token in self.tokens.iteritems():
   2.316 +                m = p.match(self.source[self.i:])               
   2.317 +                if m:                  
   2.318 +                    self.i += m.end(0)
   2.319 +                    value = m.group(0)  
   2.320 +                    return (token, value)
   2.321 +            self.i += 1
   2.322 +        return (None, None)
   2.323 +    def reset(self, source):
   2.324 +        self.i = 0
   2.325 +        self.source = source
   2.326 +
   2.327 +S = Formula
   2.328 +A = "A"
   2.329 +#0 S: (Variable,),
   2.330 +#1 S: (Not, S),
   2.331 +#2 S: (Lb, A, Rb),
   2.332 +#3 S: (Box, S),
   2.333 +#4 S: (Diamond, S),
   2.334 +#5 A: (S, And, S),
   2.335 +#6 A: (S, Or, S),
   2.336 +#7 A: (S, Imp, S),
   2.337 +#8 A: (S, Eq, S)
   2.338 +SYNTAX = ((Variable,),
   2.339 +          (Not, S),
   2.340 +          (Lb, A, Rb),
   2.341 +          (Box, S),
   2.342 +          (Diamond, S),
   2.343 +          (S, And, S),
   2.344 +          (S, Or, S),
   2.345 +          (S, Imp, S),
   2.346 +          (S, Eq, S))
   2.347 +
   2.348 +class Parser(object):
   2.349 +    def __init__(self, syntax, scanner):
   2.350 +        self.syntax = syntax
   2.351 +        self.scanner = scanner
   2.352 +    def parse(self, source):
   2.353 +        table = {(S, Variable): 0,
   2.354 +                 (S, Not): 1,
   2.355 +                 (S, Lb): 2,
   2.356 +                 (S, Box): 3,
   2.357 +                 (S, Diamond): 4,
   2.358 +                 (A, S, And): 5,
   2.359 +                 (A, S, Or): 6,
   2.360 +                 (A, S, Imp): 7,
   2.361 +                 (A, S, Eq): 8,
   2.362 +                 (A, Variable, And): 5,
   2.363 +                 (A, Variable, Or): 6,
   2.364 +                 (A, Variable, Imp): 7,
   2.365 +                 (A, Variable, Eq): 8,
   2.366 +                 (A, Not, Variable, And): 5,
   2.367 +                 (A, Not, Variable, Or): 6,
   2.368 +                 (A, Not, Variable, Imp): 7,
   2.369 +                 (A, Not, Variable, Eq): 8}
   2.370 +        stack = [S]
   2.371 +        tree_nodes = []
   2.372 +        tree = None
   2.373 +        out = []
   2.374 +        self.scanner.reset(source)
   2.375 +        (token, value) = self.scanner.next()
   2.376 +        (lookahead, la_value) = self.scanner.next()
   2.377 +        (lookahead2, la2_value) = self.scanner.next()
   2.378 +        accepted = True 
   2.379 +        while token and len(stack): 
   2.380 +            top = stack[-1]               
   2.381 +            if top == token:
   2.382 +                tree_nodes.append((token, value))
   2.383 +                stack.pop()
   2.384 +                #tree_nodes.append((stack.pop(), value))
   2.385 +                (token, value) = (lookahead, la_value)
   2.386 +                (lookahead, la_value) = (lookahead2, la2_value)
   2.387 +                #(lookahead, _) = self.scanner.next()
   2.388 +                (lookahead2, la2_value) = self.scanner.next()
   2.389 +            else:
   2.390 +                action = None
   2.391 +                if (top, token) in table:
   2.392 +                    action = table[(top, token)]
   2.393 +                elif (top, token, lookahead) in table:
   2.394 +                    action = table[(top, token, lookahead)]  
   2.395 +                elif (top, token, lookahead, lookahead2) in table:
   2.396 +                    action = table[(top, token, lookahead, lookahead2)]    
   2.397 +                if action is None:
   2.398 +                    accepted = False
   2.399 +                    break
   2.400 +                
   2.401 +                out.append(action)
   2.402 +                stack.pop()
   2.403 +                stack.extend(reversed(self.syntax[action]))
   2.404 +        accepted = accepted and not len(stack)
   2.405 +        return (accepted, out, tree_nodes) 
   2.406 +
   2.407 +class SyntaxTree(object):
   2.408 +    def __init__(self, seq, tree_nodes):
   2.409 +        seq.reverse()
   2.410 +        tree_nodes.reverse()      
   2.411 +        self.root = self.compile(seq, tree_nodes)[0]       
   2.412 +    def compile(self, seq, tree_nodes):
   2.413 +        stack = []     
   2.414 +        while len(seq):           
   2.415 +            s = seq.pop()
   2.416 +            if s == 0:
   2.417 +                c, v = tree_nodes.pop()
   2.418 +                while c != Variable:
   2.419 +                    c, v = tree_nodes.pop()
   2.420 +                stack.append(Variable(v))
   2.421 +            elif s == 1:
   2.422 +                stack.append(Not(self.compile(seq, tree_nodes)))
   2.423 +            elif s == 2:
   2.424 +                stack.extend(self.compile(seq, tree_nodes))
   2.425 +            elif s == 3:
   2.426 +                stack.append(Box(self.compile(seq, tree_nodes)))
   2.427 +            elif s == 4:
   2.428 +                stack.append(Diamond(self.compile(seq, tree_nodes)))
   2.429 +            elif s == 5:
   2.430 +                stack.append(And(self.compile(seq, tree_nodes)))
   2.431 +            elif s == 6:
   2.432 +                stack.append(Or(self.compile(seq, tree_nodes)))
   2.433 +            elif s == 7:
   2.434 +                stack.append(Imp(self.compile(seq, tree_nodes)))
   2.435 +            elif s == 8:
   2.436 +                stack.append(Eq(self.compile(seq, tree_nodes)))       
   2.437 +        return stack   
   2.438 +
   2.439 +from argparse import ArgumentParser
   2.440 +
   2.441 +def parse_arguments():
   2.442 +    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.")
   2.443 +    parser.add_argument("-f", "--formula", help="provide your own formula here, or better don't") 
   2.444 +    parser.add_argument("-d", "--dimacs", help="output file for the CNF DIMACS output")
   2.445 +    return parser.parse_args()
   2.446 + 
   2.447 +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"""
   2.448 +
   2.449 +if __name__ == "__main__":
   2.450 +    main()
     3.1 Binary file downloads/tim.zip has changed
     4.1 --- a/factory/v2011/linksend.html	Sat Jul 16 12:52:33 2011 +0200
     4.2 +++ b/factory/v2011/linksend.html	Mon Jul 18 01:51:06 2011 +0200
     4.3 @@ -44,6 +44,7 @@
     4.4  <h2>Recursive</h2>
     4.5  <ul>
     4.6  <li>
     4.7 -This End.
     4.8 +This End.<br />
     4.9 +Recursive humour recycled from <a href="http://www4.informatik.tu-muenchen.de/~schulz/">Stephan Schulz</a>, to preserve the environment.
    4.10  </li>
    4.11  </ul>
     5.1 --- a/factory/v2011/personalwork.html	Sat Jul 16 12:52:33 2011 +0200
     5.2 +++ b/factory/v2011/personalwork.html	Mon Jul 18 01:51:06 2011 +0200
     5.3 @@ -1,4 +1,42 @@
     5.4 -<p>Here an unfiltered, random stack of small hacks, that I've produced for recreational reasons.</p>
     5.5 +<p>Here is an unfiltered, undocumented, user-unfriendly random stack of little hacks, that I've produced for recreational reasons. I put them online in spirit of the liberation of useless machines.</p>
     5.6 +
     5.7 +<h2><a name="tim">Tim</a></h2>
     5.8 +<ul>
     5.9 +<li>
    5.10 +<h4>Description</h4>
    5.11 +A console-based head-on time recording tool, because time is money.</li>
    5.12 +<li>
    5.13 +<h4>Version</h4>
    5.14 +A stable prototype, no output generation yet.
    5.15 +<div class="download"><a href="downloads/tim.zip">
    5.16 +                    Download Tim (Python and SQLite3 required)</a></div></li>
    5.17 +</ul>
    5.18 +
    5.19 +<h2><a name="cau">Cau</a></h2>
    5.20 +<img src="images/cau.png" alt="cau screenshot" class="float-right" height="150" width="200" />
    5.21 +<ul>
    5.22 +<li>
    5.23 +<h4>Description</h4>
    5.24 +Is bad PI-approximation good enough? A cellular automaton-based PI-approximation machine with Tkinter-based visualisation.</li>
    5.25 +<li>
    5.26 +<h4>Version</h4>
    5.27 +Working, approximating, slowly visualising.
    5.28 +<div class="download"><a href="downloads/cau.zip">
    5.29 +                    Download Cau (Python and Tkinter required)</a></div></li>
    5.30 +</ul>
    5.31 +
    5.32 +<h2><a name="sml">SML</a></h2>
    5.33 +<ul>
    5.34 +<li>
    5.35 +<h4>Description</h4>
    5.36 +A simple Simple Modal Logic library doing all kinds of reductions including Modal Conjunctive Normal Form. It also features DIMACS output and a satisfiability test via MiniSat2.</li>
    5.37 +<li>
    5.38 +<h4>Version</h4>
    5.39 +Reliable version except for features noted as alpha. Parser has too much personality.
    5.40 +<div class="download"><a href="downloads/sml.py">
    5.41 +                    Download SML (Python and MiniSat2 required)</a></div></li>
    5.42 +</ul>
    5.43 +
    5.44  <h2><a name="netchannel">NetChannel</a></h2>
    5.45  <ul>
    5.46  <li>
    5.47 @@ -6,9 +44,9 @@
    5.48  NetChannel is a simple Python object for message-based network communication
    5.49                      on the TCP/IP stack. NetChannel is based on stateful sessions for improved performance.</li>
    5.50  <li>
    5.51 -<h4>Version 0.7</h4>
    5.52 +<h4>Version</h4>
    5.53  A stable prototype.
    5.54 -<div class="download"><a href="http://me73.com/downloads/netchannel.zip">
    5.55 +<div class="download"><a href="downloads/netchannel.zip">
    5.56                      Download NetChannel (Python required)</a></div></li>
    5.57  </ul>
    5.58  
    5.59 @@ -20,9 +58,9 @@
    5.60  Eden Plotter or Eden One is a quick prototype for my genetic programming routines.
    5.61  It approximates a given function by the methods of GP.</li>
    5.62  <li>
    5.63 -<h4>Version 0.9</h4>
    5.64 +<h4>Version</h4>
    5.65  An unendurable slow prototype.
    5.66 -<div class="download"><a href="http://me73.com/downloads/eden.zip">
    5.67 +<div class="download"><a href="downloads/eden.zip">
    5.68                      Download Eden Plotter (Python required)</a></div></li>
    5.69  </ul>
    5.70  
    5.71 @@ -47,11 +85,11 @@
    5.72  <li>
    5.73  <h4>Version Antquarium Prototype</h4>
    5.74  Antquarium is the predecessor of ANQ. It was an extended course assignment and serves as a prototype for ANQ.
    5.75 -<div class="download"><a href="http://me73.com/downloads/antquarium.zip">
    5.76 +<div class="download"><a href="downloads/antquarium.zip">
    5.77                      Download Antquarium for Windows (Python and .Net 2.0 required)</a></div>
    5.78  </li>
    5.79  <li>
    5.80 -<h4>Version 0.4.1</h4>
    5.81 +<h4>Version</h4>
    5.82  Stable version missing features like flow and runtime topology control.
    5.83  </li>
    5.84  </ul>
    5.85 @@ -70,7 +108,8 @@
    5.86  </li>
    5.87  <li>
    5.88  <h4>Requirements</h4>		        
    5.89 -<ul><li>Microsoft Windows XP or Vista</li><li>Graphics card supporting OpenGL 1.3 or higher</li><li>One keyboard with at least the arrow keys working</li></ul>	                		    <div class="download"><a href="http://me73.com/downloads/ThemeBlocksSetup.exe">Download Theme Blocks (1.4MB)</a></div>     	        
    5.90 +<ul><li>Microsoft Windows XP or Vista</li><li>Graphics card supporting OpenGL 1.3 or higher</li><li>One keyboard with at least the arrow keys working</li></ul>	                		    
    5.91 +<div class="download"><a href="downloads/ThemeBlocksSetup.exe">Download Theme Blocks (1.4MB)</a></div>           
    5.92  </li>
    5.93  </ul>
    5.94  
    5.95 @@ -84,9 +123,9 @@
    5.96  <li>
    5.97  <h4>Features</h4>
    5.98  <ul><li>Three AI difficulty levels</li><li>Endless gameplay, play till you're bored!</li></ul>
    5.99 -<div class="download"><a href="http://me73.com/downloads/PyngPongSetup.exe">
   5.100 +<div class="download"><a href="downloads/PyngPongSetup.exe">
   5.101                      Download Pyng Pong for Windows (2.4MB)</a></div>
   5.102 -<div class="download"><a href="http://me73.com/downloads/pyngpong.zip">
   5.103 +<div class="download"><a href="downloads/pyngpong.zip">
   5.104                      Download Pyng Pong Source (Python + PyGame needed) (0.4MB)</a></div>	
   5.105  </li>
   5.106  </ul>
   5.107 @@ -101,7 +140,7 @@
   5.108  <li>
   5.109  <h4>Features</h4>
   5.110  <ul><li>Drag, create and remove sound sources</li><li>Height of placement sets the sound pitch</li></ul>
   5.111 -<div class="download"><a href="http://me73.com/downloads/klangbild.zip">
   5.112 +<div class="download"><a href="downloads/klangbild.zip">
   5.113                      Download Klangbild (Python and PyGlet required)</a></div>		            
   5.114  </li></ul>            
   5.115