def test_abstraction(): x = Expression.make('x') y = Expression.make('y') z = Expression.make('z') assert lam(x, x) == I assert lam(x, y) == APP(K, y) assert lam(y, APP(APP(x, y), y)) == APP(W, x) assert lam(z, APP(APP(x, z), APP(y, z))) == APP(APP(S, x), y) assert lam(z, APP(APP(x, z), y)) == APP(APP(C, x), y) assert lam(z, APP(x, APP(y, z))) == COMP(x, y) assert lam(z, COMP(APP(x, z), APP(y, z))) == APP(APP(S, COMP(B, x)), y) assert lam(z, COMP(APP(x, z), y)) == COMP(APP(CB, y), x) assert lam(z, COMP(x, APP(y, z))) == COMP(APP(B, x), y) assert lam(y, COMP(x, y)) == APP(B, x) assert lam(x, COMP(x, y)) == APP(CB, y) assert lam(z, JOIN(APP(x, z), APP(y, z))) == JOIN(x, y) assert lam(z, JOIN(x, APP(y, z))) == COMP(APP(J, x), y) assert lam(z, JOIN(APP(x, z), y)) == COMP(APP(J, y), x) assert lam(y, JOIN(x, y)) == APP(J, x) assert lam(x, JOIN(x, y)) == APP(J, y) assert lam(z, RAND(APP(x, z), APP(y, z))) == RAND(x, y) assert lam(z, RAND(x, APP(y, z))) == COMP(APP(R, x), y) assert lam(z, RAND(APP(x, z), y)) == COMP(APP(R, y), x) assert lam(y, RAND(x, y)) == APP(R, x) assert lam(x, RAND(x, y)) == APP(R, y)
def validate(expr): assert expr.is_rel(), expr assert expr.name in ['LESS', 'EQUAL'] if expr.name != 'EQUAL': print 'WARNING: not validating {0}'.format(expr) return while True: try: lhs, rhs = expr.args lhs = head_normalize(lhs) rhs = head_normalize(rhs) assert len(lhs) == len(rhs),\ 'Failed to validate\n {0}\nbecause\n {1} != {2}'.format( expr, lhs, rhs) assert lhs[0] == rhs[0],\ 'Failed to validate\n {0}\nbecause \n{1} != {2}'.format( expr, lhs[0], rhs[0]) for args in zip(lhs[1:], rhs[1:]): validate(Expression.make(expr.name, *args)) break except RequireVariable: lhs, rhs = expr.args fresh = get_fresh(expr.vars) lhs = APP(lhs, fresh) rhs = APP(rhs, fresh) expr = Expression.make(expr.name, lhs, rhs) except SkipValidation: print 'WARNING: not validating {0}'.format(expr) return
def __call__(self, term): nargs = len(term.args) if nargs == 0: return self._fillings if term is HOLE else () elif nargs == 1: name = term.name key, = term.args return tuple(Expression.make(name, f) for f in self(key)) elif nargs == 2: name = term.name lhs, rhs = term.args return tuple(itertools.chain( (Expression.make(name, f, rhs) for f in self(lhs)), (Expression.make(name, lhs, f) for f in self(rhs)), ))
def define_a( max_solutions=15, max_memory=pomagma.analyst.synthesize.MAX_MEMORY, verbose=1, address=pomagma.analyst.ADDRESS): ''' Search for definition of A = Join {<r, s> | r o s [= I}. Tip: use pyprofile and snakeviz to profile this function: $ pyprofile -o define_a.pstats -s time src/examples/synthesize.py define_a $ snakeviz define_a.pstats ''' assert max_solutions > 0, max_solutions assert 0 < max_memory and max_memory < 1, max_memory facts = parse_facts(A_THEORY) hole = Expression.make('hole') initial_sketch = parse_expr('HOLE') language = { 'APP': 1.0, # 'COMP': 1.6, 'JOIN': 3.0, 'B': 1.0, 'C': 1.3, 'A': 2.0, 'BOT': 2.0, 'TOP': 2.0, 'I': 2.2, # 'Y': 2.3, 'K': 2.6, 'S': 2.7, 'J': 2.8, 'DIV': 3.0, } with pomagma.analyst.connect(address) as db: results = synthesize_from_facts( db=db, facts=facts, var=hole, initial_sketch=initial_sketch, language=language, max_solutions=max_solutions, max_memory=max_memory, verbose=verbose) print 'Possible Fillings:' APP = Expression_2('APP') f = Expression.make('f') for complexity, term, filling in results: print simplify_expr(APP(filling, f)) return results
def unguard_vars(expr): if expr.name == 'VAR': return expr.args[0] elif expr.is_var(): return expr else: args = map(unguard_vars, expr.args) return Expression.make(expr.name, *args)
def guard_vars(expr): """Whereas pomagma.compiler follows the variable naming convention defined in pomagma.compiler.signature.is_var(), pomagma.analyst.validate and the puddle editor require VAR guarded variables.""" if expr.name == 'VAR': return expr elif expr.is_var(): return VAR(expr) else: args = map(guard_vars, expr.args) return Expression.make(expr.name, *args)
def weaken_sequent(fact): if fact.arity == 'UnaryMeta': return Expression.make(fact.name, weaken_sequent(fact.args[0])) else: assert fact.is_rel(), fact if fact.name == 'EQUAL': lhs, rhs = fact.args if lhs == TOP or rhs == BOT: return LESS(lhs, rhs) elif rhs == TOP or lhs == BOT: return LESS(rhs, lhs) return fact
def test_compile_S(): # Compiling incremental search given: APP APP_x_y APP_y_z # cost = 2.70067540518 # if S # for x let APP_S_x # for y if APP x y let APP_APP_S_x_y # for z if APP y z let APP_APP_APP_S_x_y_z # ensure EQUAL APP_APP_APP_S_x_y_z APP_APP_x_z_APP_y_z S = Expression.make('S') _test_sequent( [], [EQUAL(APP(APP(APP(S, x), y), z), APP(APP(x, z), APP(y, z)))])
def desugar_expr(self): expr = Expression.make(self.name, *map(desugar_expr, self.args)) if expr.name == 'FUN': var, body = expr.args assert var.is_var(), var expr = body.abstract(var) elif expr.name == 'FIX': var, body = expr.args assert var.is_var(), var expr = APP(Y, body.abstract(var)) elif expr.name == 'PAIR': x, y = expr.args expr = COMP(APP(CI, y), APP(CI, x)) elif expr.name == 'ABIND': r, s, body = expr.args assert r.is_var(), r assert s.is_var(), s expr = APP(A, body.abstract(s).abstract(r)) elif expr.name == 'FIXES': typ, inhab = expr.args expr = EQUAL(APP(typ, inhab), inhab) return expr
def as_succedent(expr, bound): while expr.name in ['OPTIONALLY', 'NONEGATE']: expr = expr.args[0] antecedents = set() if expr.arity == 'Equation': args = [] for arg in expr.args: if arg.is_var() or arg.var in bound: args.append(arg.var) elif arg.args: args.append(as_atom(arg)) for argarg in arg.args: antecedents |= as_antecedents(argarg, bound) else: assert arg.arity == 'NullaryFunction', arg args.append(arg.var) antecedents.add(arg) succedent = Expression.make(expr.name, *args) else: assert expr.args, expr.args succedent = as_atom(expr) for arg in expr.args: antecedents |= as_antecedents(arg, bound) return antecedents, succedent
def write_event_programs(programs, sequents): event_tasks = {} for sequent in sequents: for event in compiler.get_events(sequent): name = 'Variable' if event.is_var() else event.name plans = sorted(compiler.compile_given(sequent, event)) cost = add_costs(c for (c, _, _) in plans) tasks = event_tasks.setdefault(name, []) tasks.append((cost, event, sequent, plans)) group_tasks = {} for name, tasks in event_tasks.iteritems(): groupname = signature.get_arity(name) group_tasks.setdefault(groupname, {})[name] = sorted(tasks) group_tasks = sorted(group_tasks.iteritems()) group_id = 0 for groupname, group in group_tasks: group = sorted(group.iteritems()) arity = signature.get_arity(group[0][0]) for eventname, tasks in group: total_cost = add_costs(c for (c, _, _, _) in tasks) programs += [ '', '# ' + '-' * 76, '# plans {}.*: total cost = {:0.1f}'.format( group_id, total_cost), '# given {}'.format(eventname), ] plan_id = 0 for _, event, sequent, plans in tasks: diagonal = ( len(event.args) == 2 and event.args[0] == event.args[1]) if diagonal: lhs = event.args[0] assert lhs.arity == 'Variable' rhs = Expression.make(lhs.name + '_') event = Expression.make(event.name, lhs, rhs) if arity == 'Variable': given = 'GIVEN_EXISTS {var}'.format(var=event.name) elif arity == 'UnaryRelation': given = 'GIVEN_UNARY_RELATION {rel} {key}'.format( rel=event.name, key=event.args[0]) elif arity == 'BinaryRelation': given = 'GIVEN_BINARY_RELATION {rel} {lhs} {rhs}'.format( rel=event.name, lhs=event.args[0], rhs=event.args[1]) elif arity == 'NullaryFunction': given = 'GIVEN_NULLARY_FUNCTION {fun} {val}'\ .format( fun=event.name, val=event.var.name) elif arity == 'InjectiveFunction': given = 'GIVEN_INJECTIVE_FUNCTION {fun} {key} {val}'\ .format( fun=event.name, key=event.args[0], val=event.var.name) elif arity == 'BinaryFunction': given = 'GIVEN_BINARY_FUNCTION {fun} {lhs} {rhs} {val}'\ .format( fun=event.name, lhs=event.args[0], rhs=event.args[1], val=event.var.name) elif arity == 'SymmetricFunction': given = 'GIVEN_SYMMETRIC_FUNCTION {fun} {lhs} {rhs} {val}'\ .format( fun=event.name, lhs=event.args[0], rhs=event.args[1], val=event.var.name) else: raise ValueError('invalid arity: {}'.format(arity)) header = [given] if diagonal: header.append('IF_EQUAL {lhs} {rhs}'.format( lhs=event.args[0], rhs=event.args[1])) for cost, seq, plan in plans: programs += [ '', '# plan {}.{}: cost = {:0.1f}'.format( group_id, plan_id, cost), '# using {}'.format(sequent), '# infer {}'.format(seq), ] programs += header plan.program(programs) plan_id += 1 group_id += 1
from parsable import parsable import pomagma.analyst from pomagma.analyst.synthesize import synthesize_from_facts from pomagma.compiler.expressions import Expression, Expression_2 from pomagma.compiler.parser import parse_string_to_expr, parse_theory_string from pomagma.compiler.simplify import simplify_expr from pomagma.compiler.sugar import desugar_expr APP = Expression_2('APP') K = Expression.make('K') F = Expression.make('F') def parse_expr(string): return desugar_expr(parse_string_to_expr(string)) def parse_facts(string): facts = parse_theory_string(string)['facts'] facts = map(desugar_expr, facts) for fact in facts: for var in fact.vars: assert len(var.name) > 2, 'unbound variable: {}'.format(var) return facts A_THEORY = ( ''' EQUAL conj FUN s FUN r FUN f COMP r COMP f s EQUAL conj FUN s FUN r FUN f COMP COMP r f s
def parse_tokens_to_expr(tokens): head = tokens.pop() arity = get_arity(head) nargs = get_nargs(arity) args = [parse_tokens_to_expr(tokens) for _ in xrange(nargs)] return Expression.make(head, *args)
def test_compile_I(): I = Expression.make('I') _test_sequent( [], [EQUAL(APP(I, x), x)])
def test_compile_ap_quote(): AP = Expression.make('AP') _test_sequent( [], [EQUAL(APP(APP(AP, QUOTE(x)), QUOTE(y)), QUOTE(APP(x, y)))])
def test_compile_qt_quote(): QT = Expression.make('QT') _test_sequent( [], [EQUAL(APP(QT, QUOTE(x)), QUOTE(QUOTE(x)))])
def test_compile_eval(): EVAL = Expression.make('EVAL') _test_sequent( [], [EQUAL(APP(EVAL, QUOTE(x)), x)])
def test_compile_comp_x_x_x(): U = Expression.make('U') _test_sequent( [EQUAL(COMP(x, x), x)], [EQUAL(x, APP(U, x))])
def test_compile_bot(): BOT = Expression.make('BOT') _test_sequent( [], [LESS(BOT, x)])
def test_compile_Y(): Y = Expression.make('Y') _test_sequent( [], [EQUAL(APP(Y, f), APP(f, APP(Y, f)))])
def as_atom(expr): args = [arg.var for arg in expr.args] return Expression.make(expr.name, *args)
def test_compile_K(): K = Expression.make('K') _test_sequent( [], [EQUAL(APP(APP(K, x), y), x)])
def get_fresh(bound): for name in 'abcdefghijklmnopqrstuvwxyz': fresh = Expression.make(name) if fresh not in bound: return fresh raise NotImplementedError('Exceeded fresh variable limit')
from pomagma.compiler.expressions import Expression from pomagma.compiler.parser import parse_corpus from pomagma.compiler.util import inputs from pomagma.util import TODO HOLE = Expression.make('HOLE') class Corpus(object): ''' Corpus is a general-recursive set of definitions with holes. ''' def __init__(self): self._defs = {} def load(self, filename): with open(filename) as f: self._defs = parse_corpus(f, filename=filename) def dump(self, filename): with open(filename, 'w') as f: f.write('# Corpus written by {}'.format(__file__)) for var, expr in sorted(self._defs.iteritems()): assert isinstance(var, basestring), var assert isinstance(expr, Expression), expr f.write('\nEQUAL {} {}'.format(var, expr)) def __getitem__(self, var): assert isinstance(var, Expression), var assert var.is_var(), var
def abstract(self, var): assert isinstance(var, Expression) assert var.is_var() if self.name == 'VAR': self = self.args[0] if self.is_var(): if self == var: return I else: return APP(K, self) elif self.is_fun(): name = self.name if var not in self.vars: return APP(K, self) elif name == 'APP': # I,K,C,SW,COMP,eta abstraction lhs, rhs = self.args if var in lhs.vars: lhs_abs = lhs.abstract(var) if var in rhs.vars: if rhs == var: return APP(W, lhs_abs) else: return APP(APP(S, lhs_abs), rhs.abstract(var)) else: return APP(APP(C, lhs_abs), rhs) else: assert var in rhs.vars if rhs == var: return lhs else: return COMP(lhs, rhs.abstract(var)) elif name == 'COMP': # K,B,CB,C,S,COMP,eta abstraction lhs, rhs = self.args if var in lhs.vars: lhs_abs = lhs.abstract(var) if var in rhs.vars: return APP(APP(S, COMP(B, lhs_abs)), rhs.abstract(var)) else: if lhs == var: return APP(CB, rhs) else: return COMP(APP(CB, rhs), lhs_abs) else: assert var in rhs.vars if rhs == var: return APP(B, lhs) else: return COMP(APP(B, lhs), rhs.abstract(var)) elif name == 'JOIN': return abstract_symmetric(self, var, J, JOIN) elif name == 'RAND': return abstract_symmetric(self, var, R, RAND) else: raise AbstractionFailed elif self.is_rel(): args = [arg.abstract(var) for arg in self.args] return Expression.make(self.name, *args) else: raise ValueError('bad expression: %s' % self.name)
def test_compile_B_comp(): B = Expression.make('B') _test_sequent( [], [EQUAL(APP(APP(B, x), y), COMP(x, y))])
def test_compile_W(): W = Expression.make('W') _test_sequent( [], [EQUAL(APP(APP(W, x), y), APP(APP(x, y), y))])
def test_compile_C(): C = Expression.make('C') _test_sequent( [], [EQUAL(APP(APP(APP(C, x), y), z), APP(APP(x, z), y))])
def insert(self, key, value='HOLE'): var = Expression.make(key) expr = Expression.make(value) self[var] = expr
def test_compile_B_app(): B = Expression.make('B') _test_sequent( [], [EQUAL(APP(APP(APP(B, x), y), z), APP(x, APP(y, z)))])