def test_assoccomm(): from symbolic_pymc.relations import buildo x, a, b, c = tt.dvectors('xabc') test_expr = x + 1 q = var('q') res = run(1, q, buildo(tt.add, test_expr.owner.inputs, test_expr)) assert q == res[0] res = run(1, q, buildo(q, test_expr.owner.inputs, test_expr)) assert tt.add == res[0].reify() res = run(1, q, buildo(tt.add, q, test_expr)) assert mt(tuple(test_expr.owner.inputs)) == res[0] res = run(0, var('x'), eq_comm(mt.mul(a, b), mt.mul(b, var('x')))) assert (mt(a), ) == res res = run(0, var('x'), eq_comm(mt.add(a, b), mt.add(b, var('x')))) assert (mt(a), ) == res res = run(0, var('x'), (eq_assoc, mt.add(a, b, c), mt.add(a, var('x')))) # TODO: `res[0]` should return `etuple`s. Since `eq_assoc` effectively # picks apart the results of `arguments(...)`, I don't know if we can # keep the `etuple`s around. We might be able to convert the results # to `etuple`s automatically by wrapping `eq_assoc`, though. res_obj = etuple(*res[0]).eval_obj assert res_obj == mt(b + c) res = run(0, var('x'), (eq_assoc, mt.mul(a, b, c), mt.mul(a, var('x')))) res_obj = etuple(*res[0]).eval_obj assert res_obj == mt(b * c)
def test_seq_apply_anyo_misc(): q_lv = var('q') assert len(run(0, q_lv, seq_apply_anyo(eq, [1, 2, 3], [1, 2, 3]))) == 1 assert len(run(0, q_lv, seq_apply_anyo(eq, [1, 2, 3], [1, 3, 3]))) == 0 def one_to_threeo(x, y): return conde([eq(x, 1), eq(y, 3)]) res = run(0, q_lv, seq_apply_anyo(one_to_threeo, [1, 2, 4, 1, 4, 1, 1], q_lv)) assert res[0] == [3, 2, 4, 3, 4, 3, 3] assert len( run(4, q_lv, seq_apply_anyo(math_reduceo, [etuple(mul, 2, var('x'))], q_lv))) == 0 test_res = run(4, q_lv, seq_apply_anyo(math_reduceo, [etuple(add, 2, 2), 1], q_lv)) assert test_res == ([etuple(mul, 2, 2), 1], ) test_res = run(4, q_lv, seq_apply_anyo(math_reduceo, [1, etuple(add, 2, 2)], q_lv)) assert test_res == ([1, etuple(mul, 2, 2)], ) test_res = run(4, q_lv, seq_apply_anyo(math_reduceo, q_lv, var('z'))) assert all(isinstance(r, list) for r in test_res) test_res = run(4, q_lv, seq_apply_anyo(math_reduceo, q_lv, var('z'), tuple())) assert all(isinstance(r, tuple) for r in test_res)
def test_pprint(): pretty_mod = pytest.importorskip("IPython.lib.pretty") et = etuple(1, etuple("a", *range(20)), etuple(3, "b"), blah=etuple('c', 0)) assert pretty_mod.pretty( et ) == "e(\n 1,\n e('a', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19),\n e(3, 'b'),\n blah=e(c, 0))"
def distributes(in_lv, out_lv): return ( lallgreedy, # lhs == A * (x + b) (eq, etuple(mt.dot, var('A'), etuple(mt.add, var('x'), var('b'))), etuplize(in_lv)), # rhs == A * x + A * b (eq, etuple(mt.add, etuple(mt.dot, var('A'), var('x')), etuple(mt.dot, var('A'), var('b'))), out_lv))
def test_str(): et = etuple(1, etuple("a", 2), etuple(3, "b")) assert repr( et ) == "ExpressionTuple((1, ExpressionTuple(('a', 2)), ExpressionTuple((3, 'b'))))" assert str(et) == 'e(1, e(a, 2), e(3, b))' kw = KwdPair('a', 1) assert repr(kw) == "KwdPair('a', 1)" assert str(kw) == 'a=1'
def test_graph_applyo_misc(): q_lv = var('q') expr = etuple(add, etuple(mul, 2, 1), etuple(add, 1, 1)) assert len( run(0, q_lv, graph_applyo(eq, expr, expr, preprocess_graph=None))) == 1 expr2 = etuple(add, etuple(mul, 2, 1), etuple(add, 2, 1)) assert len( run(0, q_lv, graph_applyo(eq, expr, expr2, preprocess_graph=None))) == 0 assert len( run(0, q_lv, graph_applyo(eq, etuple(), etuple(), preprocess_graph=None))) == 1 def one_to_threeo(x, y): return conde([eq(x, 1), eq(y, 3)]) res = run( 0, q_lv, graph_applyo(one_to_threeo, [1, [1, 2, 4], 2, [[4, 1, 1]], 1], q_lv, preprocess_graph=None)) assert res[0] == [3, [3, 2, 4], 2, [[4, 3, 3]], 3]
def test_etuple(): """Test basic `etuple` functionality.""" def test_op(*args): return tuple(object() for i in range(sum(args))) e1 = etuple(test_op, 1, 2) assert e1._eval_obj is ExpressionTuple.null with pytest.raises(ValueError): e1.eval_obj = 1 e1_obj = e1.eval_obj assert len(e1_obj) == 3 assert all(type(o) == object for o in e1_obj) # Make sure we don't re-create the cached `eval_obj` e1_obj_2 = e1.eval_obj assert e1_obj == e1_obj_2 # Confirm that evaluation is recursive e2 = etuple(add, (object(), ), e1) # Make sure we didn't convert this single tuple value to # an `etuple` assert type(e2[1]) == tuple # Slices should be `etuple`s, though. assert isinstance(e2[:1], ExpressionTuple) assert e2[1] == e2[1:2][0] e2_obj = e2.eval_obj assert type(e2_obj) == tuple assert len(e2_obj) == 4 assert all(type(o) == object for o in e2_obj) # Make sure that it used `e1`'s original `eval_obj` assert e2_obj[1:] == e1_obj # Confirm that any combination of `tuple`s/`etuple`s in # concatenation result in an `etuple` e_radd = (1, ) + etuple(2, 3) assert isinstance(e_radd, ExpressionTuple) assert e_radd == (1, 2, 3) e_ladd = etuple(1, 2) + (3, ) assert isinstance(e_ladd, ExpressionTuple) assert e_ladd == (1, 2, 3)
def math_reduceo(in_expr, out_expr): """Create a relation for a couple math-based identities.""" x_lv = var() x_lv.token = f'x{x_lv.token}' return (lall, conde([ eq(in_expr, etuple(add, x_lv, x_lv)), eq(out_expr, etuple(mul, 2, x_lv)) ], [ eq(in_expr, etuple(log, etuple(exp, x_lv))), eq(out_expr, x_lv) ]), conde([(isinstanceo, [in_expr, (float, int, ExpressionTuple)], True)], [(isinstanceo, [out_expr, (float, int, ExpressionTuple)], True)]))
def test_etuple(): et = etuple(var('a'), ) res = reify(et, {var('a'): 1}) assert isinstance(res, ExpressionTuple) et = etuple(var('a'), ) res = unify(et, (1, )) assert res == {var('a'): 1} from operator import add et = etuple(add, 1, 2) assert et.eval_obj == 3 res = unify(et, cons(var('a'), var('b'))) assert res == {var('a'): add, var('b'): et[1:]} assert ((res[var('a')], ) + res[var('b')])._eval_obj == 3
def test_seq_apply_anyo_types(): """Make sure that `applyo` preserves the types between its arguments.""" q_lv = var() res = run(1, q_lv, seq_apply_anyo(lambda x, y: eq(x, y), [1], q_lv)) assert res[0] == [1] res = run(1, q_lv, seq_apply_anyo(lambda x, y: eq(x, y), (1, ), q_lv)) assert res[0] == (1, ) res = run( 1, q_lv, seq_apply_anyo(lambda x, y: eq(x, y), etuple(1, ), q_lv, skip_op=False)) assert res[0] == etuple(1, ) res = run(1, q_lv, seq_apply_anyo(lambda x, y: eq(x, y), q_lv, (1, ))) assert res[0] == (1, ) res = run(1, q_lv, seq_apply_anyo(lambda x, y: eq(x, y), q_lv, [1])) assert res[0] == [1] res = run( 1, q_lv, seq_apply_anyo(lambda x, y: eq(x, y), q_lv, etuple(1), skip_op=False)) assert res[0] == etuple(1) res = run(1, q_lv, seq_apply_anyo(lambda x, y: eq(x, y), [1, 2], [1, 2])) assert len(res) == 1 res = run(1, q_lv, seq_apply_anyo(lambda x, y: eq(x, y), [1, 2], [1, 3])) assert len(res) == 0 res = run(1, q_lv, seq_apply_anyo(lambda x, y: eq(x, y), [1, 2], (1, 2))) assert len(res) == 0 res = run( 0, q_lv, seq_apply_anyo(lambda x, y: eq(y, etuple(mul, 2, x)), etuple(add, 1, 2), q_lv, skip_op=True)) assert len(res) == 3 assert all(r[0] == add for r in res)
def test_sexp_unify_reify(): """Make sure we can unify and reify etuples/S-exps.""" # Unify `A . (x + y)`, for `x`, `y` logic variables A = tf.compat.v1.placeholder(tf.float64, name='A', shape=tf.TensorShape([None, None])) x = tf.compat.v1.placeholder(tf.float64, name='x', shape=tf.TensorShape([None, 1])) y = tf.compat.v1.placeholder(tf.float64, name='y', shape=tf.TensorShape([None, 1])) z = tf.matmul(A, tf.add(x, y)) z_sexp = etuplize(z, shallow=False) # Let's just be sure that the original TF objects are preserved assert z_sexp[1].eval_obj.reify() == A assert z_sexp[2][1].eval_obj.reify() == x assert z_sexp[2][2].eval_obj.reify() == y dis_pat = etuple(etuple(TFlowMetaOperator, mt.matmul.op_def, var()), var('A'), etuple(etuple(TFlowMetaOperator, mt.add.op_def, var()), var('x'), var('y'))) s = unify(dis_pat, z_sexp, {}) assert s[var('A')].eval_obj == mt(A) assert s[var('x')].eval_obj == mt(x) assert s[var('y')].eval_obj == mt(y) # Now, we construct a graph that reflects the distributive property and # reify with the substitutions from the un-distributed form out_pat = etuple(mt.add, etuple(mt.matmul, var('A'), var('x')), etuple(mt.matmul, var('A'), var('y'))) z_dist = reify(out_pat, s) # Evaluate the tuple-expression and get a meta object/graph z_dist_mt = z_dist.eval_obj # If all the logic variables were reified, we should be able to # further reify the meta graph and get a concrete TF graph z_dist_tf = z_dist_mt.reify() assert isinstance(z_dist_tf, tf.Tensor) # Check the first part of `A . x + A . y` (i.e. `A . x`) assert z_dist_tf.op.inputs[0].op.inputs[0] == A assert z_dist_tf.op.inputs[0].op.inputs[1] == x # Now, the second, `A . y` assert z_dist_tf.op.inputs[1].op.inputs[0] == A assert z_dist_tf.op.inputs[1].op.inputs[1] == y
def test_seq_apply_anyo_reverse(): """Test `seq_apply_anyo` in "reverse" (i.e. specify the reduced form and generate the un-reduced form).""" # Unbounded reverse q_lv = var() rev_input = [etuple(mul, 2, 1)] test_res = run(4, q_lv, (seq_apply_anyo, math_reduceo, q_lv, rev_input)) assert test_res == ([etuple(add, 1, 1)], [etuple(log, etuple(exp, etuple(mul, 2, 1)))]) # Guided reverse test_res = run(4, q_lv, (seq_apply_anyo, math_reduceo, [etuple(add, q_lv, 1) ], [etuple(mul, 2, 1)])) assert test_res == (1, )
def test_etuple_term(): """Test `etuplize` and `etuple` interaction with `term` """ # Make sure that we don't lose underlying `eval_obj`s # when taking apart and re-creating expression tuples # using `kanren`'s `operator`, `arguments` and `term` # functions. e1 = etuple(add, (object(), ), (object(), )) e1_obj = e1.eval_obj e1_dup = (operator(e1), ) + arguments(e1) assert isinstance(e1_dup, ExpressionTuple) assert e1_dup.eval_obj == e1_obj e1_dup_2 = term(operator(e1), arguments(e1)) assert e1_dup_2 == e1_obj
def test_etuple_term(): """Test `etuplize` and `etuple` interaction with `term` """ # Take apart an already constructed/evaluated meta # object. e2 = mt.add(mt.vector(), mt.vector()) e2_et = etuplize(e2) assert isinstance(e2_et, ExpressionTuple) # e2_et_expect = etuple( # mt.add, # etuple(mt.TensorVariable, # etuple(mt.TensorType, # 'float64', (False,), None), # None, None, None), # etuple(mt.TensorVariable, # etuple(mt.TensorType, # 'float64', (False,), None), # None, None, None), # ) e2_et_expect = etuple(mt.add, e2.base_arguments[0], e2.base_arguments[1]) assert e2_et == e2_et_expect assert e2_et.eval_obj is e2 # Make sure expression expansion works from Theano objects, too. # First, do it manually. tt_expr = tt.vector() + tt.vector() mt_expr = mt(tt_expr) assert mt_expr.obj is tt_expr assert mt_expr.reify() is tt_expr e3 = etuplize(mt_expr) assert e3 == e2_et assert e3.eval_obj is mt_expr assert e3.eval_obj.reify() is tt_expr # Now, through `etuplize` e2_et_2 = etuplize(tt_expr) assert e2_et_2 == e3 == e2_et assert isinstance(e2_et_2, ExpressionTuple) assert e2_et_2.eval_obj == tt_expr
def test_graph_applyo_reverse(): """Test `graph_applyo` in "reverse" (i.e. specify the reduced form and generate the un-reduced form).""" q_lv = var('q') test_res = run(2, q_lv, fixedp_graph_applyo(math_reduceo, q_lv, 5)) assert test_res == (etuple(log, etuple(exp, 5)), etuple(log, etuple(exp, etuple(log, etuple(exp, 5))))) assert all(e.eval_obj == 5.0 for e in test_res) # Make sure we get some variety in the results test_res = run(2, q_lv, fixedp_graph_applyo(math_reduceo, q_lv, etuple(mul, 2, 5))) assert test_res == ( # Expansion of the term's root etuple(add, 5, 5), # Two step expansion at the root etuple(log, etuple(exp, etuple(add, 5, 5))), # Expansion into a sub-term # etuple(mul, 2, etuple(log, etuple(exp, 5))) ) assert all(e.eval_obj == 10.0 for e in test_res) r_lv = var('r') test_res = run(4, [q_lv, r_lv], fixedp_graph_applyo(math_reduceo, q_lv, r_lv)) expect_res = ([etuple(add, 1, 1), etuple(mul, 2, 1)], [ etuple(log, etuple(exp, etuple(add, 1, 1))), etuple(mul, 2, 1) ], [etuple(), etuple()], [ etuple(add, etuple(mul, 2, 1), etuple(add, 1, 1)), etuple(mul, 2, etuple(mul, 2, 1)) ]) assert list( unify(a1, a2) and unify(b1, b2) for [a1, b1], [a2, b2] in zip(test_res, expect_res))
test_res = run(4, q_lv, seq_apply_anyo(math_reduceo, [1, etuple(add, 2, 2)], q_lv)) assert test_res == ([1, etuple(mul, 2, 2)], ) test_res = run(4, q_lv, seq_apply_anyo(math_reduceo, q_lv, var('z'))) assert all(isinstance(r, list) for r in test_res) test_res = run(4, q_lv, seq_apply_anyo(math_reduceo, q_lv, var('z'), tuple())) assert all(isinstance(r, tuple) for r in test_res) @pytest.mark.parametrize( 'test_input, test_output', [([], ()), ([1], ()), ([ etuple(add, 1, 1), ], ([etuple(mul, 2, 1)], )), ([1, etuple(add, 1, 1)], ([1, etuple(mul, 2, 1)], )), ([etuple(add, 1, 1), 1], ([etuple(mul, 2, 1), 1], )), ([etuple(mul, 2, 1), etuple(add, 1, 1), 1], ([etuple(mul, 2, 1), etuple(mul, 2, 1), 1], )), ([ etuple(add, 1, 1), etuple(log, etuple(exp, 5)), ], ([etuple(mul, 2, 1), 5], [etuple(add, 1, 1), 5], [etuple(mul, 2, 1), etuple(log, etuple(exp, 5))]))]) def test_seq_apply_anyo(test_input, test_output): """Test `seq_apply_anyo` with fully ground terms (i.e. no logic variables).""" q_lv = var() test_res = run(0, q_lv, (seq_apply_anyo, full_math_reduceo, test_input, q_lv))
def test_etuple_kwargs(): """Test keyword arguments and default argument values.""" def test_func(a, b, c=None, d='d-arg', **kwargs): assert isinstance(c, (type(None), int)) return [a, b, c, d] e1 = etuple(test_func, 1, 2) assert e1.eval_obj == [1, 2, None, 'd-arg'] # Make sure we handle variadic args properly def test_func2(*args, c=None, d='d-arg', **kwargs): assert isinstance(c, (type(None), int)) return list(args) + [c, d] e11 = etuple(test_func2, 1, 2) assert e11.eval_obj == [1, 2, None, 'd-arg'] e2 = etuple(test_func, 1, 2, 3) assert e2.eval_obj == [1, 2, 3, 'd-arg'] e3 = etuple(test_func, 1, 2, 3, 4) assert e3.eval_obj == [1, 2, 3, 4] e4 = etuple(test_func, 1, 2, c=3) assert e4.eval_obj == [1, 2, 3, 'd-arg'] e5 = etuple(test_func, 1, 2, d=3) assert e5.eval_obj == [1, 2, None, 3] e6 = etuple(test_func, 1, 2, 3, d=4) assert e6.eval_obj == [1, 2, 3, 4] # Try evaluating nested etuples e7 = etuple(test_func, etuple(add, 1, 0), 2, c=etuple(add, 1, etuple(add, 1, 1))) assert e7.eval_obj == [1, 2, 3, 'd-arg'] # Try a function without an obtainable signature object e8 = etuple(enumerate, etuple(list, ['a', 'b', 'c', 'd']), start=etuple(add, 1, etuple(add, 1, 1))) assert list(e8.eval_obj) == [(3, 'a'), (4, 'b'), (5, 'c'), (6, 'd')] # Use "eval_obj" kwarg and make sure it doesn't end up in the `_tuple` object e9 = etuple(add, 1, 2, eval_obj=3) assert e9._tuple == (add, 1, 2) assert e9._eval_obj == 3
def test_reduceo(): q_lv = var() # Reduce/forward res = run( 0, q_lv, full_math_reduceo(etuple(log, etuple(exp, etuple(log, 1))), q_lv)) assert len(res) == 1 assert res[0] == etuple(log, 1) res = run( 0, q_lv, full_math_reduceo( etuple(log, etuple(exp, etuple(log, etuple(exp, 1)))), q_lv)) assert res[0] == 1 assert res[1] == etuple(log, etuple(exp, 1)) # Expand/backward res = run(2, q_lv, full_math_reduceo(q_lv, 1)) assert res[0] == etuple(log, etuple(exp, 1)) assert res[1] == etuple(log, etuple(exp, etuple(log, etuple(exp, 1))))