def test_Template_in_step(): v = Template('v') expr = Step(op.add, 1, 2) test = Step(op.add, 1, v) assert are_equal(test, expr) assert are_equal(expr, test) assert are_equal(v.meaning, 2)
def test_Template_in_step_repeated_true(): v = Template('v') expr = Step(op.add, 1, 1) test = Step(op.add, v, v) assert are_equal(test, expr) assert are_equal(expr, test) assert are_equal(v.meaning, 1)
def test_from_dict_with_dict_params(): a = Step('a') b = Step(dict.get, {'dog': 1, 'cat': 2}, a) assert do_eval(b, a='cat') == 2 p = to_dict(b) e = from_dict(p) assert are_equal(e, b) assert are_equal(b._last_result, e._last_result)
def test_replace_in_dag(): a = Step('a') b = Step('b') expr1 = 2**a + a*2 + 1 expr2 = replace_in_DAG(deepcopy(expr1), a, b) res1 = do_eval(expr1, a=1, b=2) res2 = do_eval(expr2, a=2, b=1) assert are_equal(res1, res2)
def test_static_map_filter_reduce(a): def add1(v): return v+1 def is_gt_3(v): return v>3 mapping = Step(map, add1, a) filtering = Step(filter, is_gt_3, mapping) s2 = Step(list, filtering) assert do_eval(s2, a=[1, 2, 3, 4, 5]) == [4, 5, 6]
def test_Template_in_step_with_input_False(): a = Step('a') b = Step('b') v = Template('v') expr = Step(op.add, 1, a+b) test = Step(op.add, 1, a*v) assert not are_equal(expr, test) assert not are_equal(test, expr) assert are_equal(v.meaning, Tokens.NO_PREVIOUS_RESULT)
def test_Template_in_step_with_input_partial_operations_invert(): a = Step('a') b = Step('b') v = Template('v') expr = Step(op.add, 1, a+b) test = Step(op.add, 1, v+b) assert are_equal(expr, test) assert are_equal(test, expr) assert are_equal(v.meaning, a)
def test_dynamic_map_filter_reduce(a): def add1(v): return v+1 def is_gt_3(v): return v>3 mapping = Step(map, add1, a) filtering = Step(filter, is_gt_3, mapping) s2 = Step(list, filtering) assert do_eval(s2, a=range(6)) == [4, 5, 6]
def test_Template_in_step_with_input(): a = Step('a') b = Step('b') v = Template('v') expr = Step(op.add, a, b) test = Step(op.add, a, v) assert are_equal(expr, test) assert are_equal(test, expr) assert are_equal(v.meaning, b)
def test_basic_equality(): a = Step('a') b = Step('b') c = Step('a') assert not are_equal(a, b) assert are_equal(a, c) assert are_equal(a+b, c+b) assert are_equal(2*(a+b), 2*(c+b))
def test_basic_copy_shallow_and_deep(): b = Step('b') a = Step('a', meta=b) a1 = copy(a) a2 = deepcopy(a) assert are_equal(a, a1) assert are_equal(a, a2) assert a._kwargs['meta'] is a1._kwargs['meta'] assert a._kwargs['meta'] is not a2._kwargs['meta'] assert are_equal(a._kwargs['meta'], a2._kwargs['meta'])
def test_multiple_templates(): a = Step('a') b = Step('b') v = Template('v') t = Template('t') expr = 1+a+b test = v+a+t assert are_equal(expr, test) assert are_equal(v.meaning, 1) assert are_equal(t.meaning, b)
def test_returning_function(): a = Step('base') b = Step('power') def make_power_function(pow): def make_power(base): return base**pow return make_power make_pow = Step(make_power_function, a) do_math = Step(make_pow, b) assert do_eval(do_math, base=2, power=3, non_used=4) == 9
def test_count_expressions(): a = Step('a') b = Step('b') c = Step('c') dag = a + b + c do_eval(dag, a=1, b=2) res = count_operations(dag) assert res.n_of_nodes == 5 assert res.n_of_operations == 5 assert res.n_cached == 2 assert res.n_variables == 3 assert res.n_free_variables == 3
def test_dynamic_variable_generation_surprising(): """this is a weird one but logically correct during evaluation, given that "a" is replaced by a string, b becomes a variable and thus the result of the evaluation is a new pipeline. once that pipeline is evluated, the result is just the function, as the parameters of the Steps are not used. one can also do everything in one step to male it weirder. """ a = Step('a') b= Step(a, 1, 2) res = do_eval(b, a="adios", adios=op.add) assert res(1, 2) == 3
def test_clear_cache_from_errors_with_kwargs(a, b, c): def partial_pow(p): if not isinstance(p, int): raise ValueError("should be int") def my_pow(i): return i**p return my_pow pow = Step(partial_pow, p=b) expr = Step(sorted, a, key=pow) * (c+1) do_eval(expr, a=[1, 3, -2], b='2', c=1) clear_cache(expr) to_dict(expr) assert do_eval(expr, a=[1, 3, -2], b=2, c=1) == [1, -2, 3, 1, -2, 3]
def test_deepcopy_cache_no_interaction(a): def is_variable(s): return isinstance(s._function, str) a = Step('a') b = 1/a +1 c = deepcopy(b) res = do_eval(c , a=0) d = deepcopy(c) assert is_variable(a) assert not is_variable(b) assert not is_variable(c) assert not is_variable(d) for step, parent, position in b: if not is_variable(step): assert step._last_result is Tokens.NO_PREVIOUS_RESULT for step, parent, position in c: if not is_variable(step): assert step._last_result is not Tokens.NO_PREVIOUS_RESULT assert c._last_result is res for step, parent, position in d: if not is_variable(step): assert step._last_result is not Tokens.NO_PREVIOUS_RESULT
def test_iteration_dag_with_template(): a = Step('a') v = Template('v') test = a+v unroll = list(test) assert len(unroll)==3 assert unroll[-1] == (v, test, 1)
def test_simple_unroll(a, b, c, d): expr_base = Step(c, d) expr = Step(expr_base, x=a, y=b) for subdag, basedag, position in expr: assert isinstance(subdag, Step) if basedag is None: assert position is None continue assert isinstance(basedag, Step) if position == Tokens.FUNCTION_IDX: assert subdag is basedag._function elif isinstance(position, str): assert subdag is basedag._kwargs[position] elif isinstance(position, int): assert subdag is basedag._args[position] else: assert False
def test_subclass_deferred_execution(): class TestStep(Step): def __radd__(self, other): return super().__add__(other*2) a = Step('a') b = TestStep('b') c = a + b assert do_eval(c, a=1, b=2) == 4 # a*2 + b, driven by b
def test_find_subtrees(a, b, c, d): r = (a*b)*d + c*(a*b) res = list(find_elements(a*b, r)) assert len(res)==2 r2 = Step((lambda x, y: x+y), x=(a*b)*d, y=c*(a*b)) res = list(find_elements(a*b, r2)) assert len(res)==2
def test_clear_cache_from_errors(): a = Step('a') b = Step('b') expr = 1/b + 1/a do_eval(expr, a=0, b=1) expected_1 = {Tokens.FUNCTION_IDX: op.add, Tokens.CACHE_IDX: TypeError("unsupported operand type(s) for +: 'int' and 'ZeroDivisionError'"), 0: {Tokens.FUNCTION_IDX: op.truediv, Tokens.CACHE_IDX: 1, 0: 1, 1: {Tokens.FUNCTION_IDX: 'b', Tokens.CACHE_IDX: Tokens.NO_PREVIOUS_RESULT, } }, 1: {Tokens.FUNCTION_IDX: op.truediv, Tokens.CACHE_IDX: ZeroDivisionError('division by zero'), 0: 1, 1: {Tokens.FUNCTION_IDX: 'a', Tokens.CACHE_IDX: Tokens.NO_PREVIOUS_RESULT, } } } assert are_equal(from_dict(expected_1), from_dict(expected_1)) assert are_equal(expr, from_dict(expected_1)) clean_expr = clear_cache(expr) expected_2 = {Tokens.FUNCTION_IDX: op.add, Tokens.CACHE_IDX: Tokens.NO_PREVIOUS_RESULT, 0: {Tokens.FUNCTION_IDX: op.truediv, Tokens.CACHE_IDX: 1, 0: 1, 1: {Tokens.FUNCTION_IDX: 'b', Tokens.CACHE_IDX: Tokens.NO_PREVIOUS_RESULT, } }, 1: {Tokens.FUNCTION_IDX: op.truediv, Tokens.CACHE_IDX: Tokens.NO_PREVIOUS_RESULT, 0: 1, 1: {Tokens.FUNCTION_IDX: 'a', Tokens.CACHE_IDX: Tokens.NO_PREVIOUS_RESULT, } } } assert are_equal(clean_expr, from_dict(expected_2))
def test_deferred_equality_to_subclass(): class Useless(Step): def __equal__(self, other): return True a = Step("a") b = Useless('b') assert are_equal(a, b) == True assert are_equal(b, a) == True
def test_matmul(): """given that numpy implements the matmul operation but does not conceive that they might not know how to handle it, it can't be the first object, need a trick to test the interface""" class Vector: def __init__(self, *args): self.args = args def __matmul__(self, other): if not isinstance(other, Vector): return NotImplemented return sum(i*j for i, j in zip(self.args, other.args)) v1 = Vector(1, 2) v2 = Vector(1, 2) assert v1@v2 == 5 a = Step('a') assert do_eval(a @ v1, a=v2) == v1@v2 a = Step('a') assert do_eval(v1 @ a, a=v2) == v1@v2
def test_pattern_matching(): a = Step('a') b = Step('b') v = Template('v') expr = 2*(a+b)+3*(a+2*b) test = a+v assert not are_equal(expr, test) results = list(match(test, expr)) assert len(results)==2 assert are_equal(results[0][Tokens.FUNCTION_IDX][0], a+b) assert are_equal(results[0][Tokens.FUNCTION_IDX][1], 2*(a+b)) assert are_equal(results[0][Tokens.FUNCTION_IDX][2], 1) assert are_equal(results[0]['v'], b) assert are_equal(results[1][Tokens.FUNCTION_IDX][0], a+2*b) assert are_equal(results[1][Tokens.FUNCTION_IDX][1], 3*(a+2*b)) assert are_equal(results[1][Tokens.FUNCTION_IDX][2], 1) assert are_equal(results[1]['v'], 2*b)
def test_eval_curry_hyp(name_a, name_b, value_a, value_b, op_math_function): try: expected = op_math_function(value_a, value_b) except Exception: assume(False) try: is_nan_value = isnan(expected) except OverflowError: is_nan_value = False assume(not is_nan_value) assume(name_a != name_b) a = Step(name_a) b = Step(name_b) expr = Step(op_math_function, a, b) a_dict = {name_a: value_a} b_dict = {name_b: value_b} curried = do_eval(expr, **a_dict) assert isinstance(curried, Step) observed = do_eval(curried, **b_dict) assert isinstance(observed, type(expected)) assert observed == expected
def test_dag_processing_into_dict(): a = Step('a') assert to_dict(a) == {Tokens.FUNCTION_IDX: 'a', Tokens.CACHE_IDX: Tokens.NO_PREVIOUS_RESULT} b = a+2 assert to_dict(b) == {Tokens.FUNCTION_IDX: op.add, Tokens.CACHE_IDX: Tokens.NO_PREVIOUS_RESULT, 0: {Tokens.FUNCTION_IDX: 'a', Tokens.CACHE_IDX: Tokens.NO_PREVIOUS_RESULT}, 1: 2} do_eval(b, a=2) assert to_dict(b) == {Tokens.FUNCTION_IDX: op.add, Tokens.CACHE_IDX: 4, 0: {Tokens.FUNCTION_IDX: 'a', Tokens.CACHE_IDX: Tokens.NO_PREVIOUS_RESULT}, 1: 2} c = Step("random", a=1, b=2) to_dict(c) == {Tokens.FUNCTION_IDX: 'random', Tokens.CACHE_IDX: Tokens.NO_PREVIOUS_RESULT, 'a': 1, 'b': 2}
def test_math_interface_hyp(name_a, name_b, value_a, value_b, op_math_function): try: expected = op_math_function(value_a, value_b) except Exception: assume(False) try: is_nan_value = isnan(expected) except OverflowError: is_nan_value = False assume(not is_nan_value) assume(name_a != name_b) a = Step(name_a) b = Step(name_b) value_dict = {name_a: value_a, name_b: value_b} expr_1 = Step(op_math_function, a, b) expr_2 = op_math_function(a, b) assert isinstance(expr_1, Step) assert isinstance(expr_2, Step) assert are_equal(expr_1, expr_2) observed_1 = do_eval(expr_1, **value_dict) observed_2 = do_eval(expr_2, **value_dict) assert isinstance(observed_1, type(expected)) assert isinstance(observed_2, type(expected)) assert observed_1 == expected assert observed_2 == expected value_a_dict = {name_a: value_a} expr_direct = op_math_function(a, value_b) assert isinstance(expr_direct, Step) observed = do_eval(expr_direct, **value_a_dict) assert isinstance(observed, type(expected)) assert observed == expected value_b_dict = {name_b: value_b} expr_inverse = op_math_function(value_a, b) assert isinstance(expr_inverse, Step) observed = do_eval(expr_inverse, **value_b_dict) assert isinstance(observed, type(expected)) assert observed == expected
def test_caching_results(a): num_of_executions = 0 def echo_increase(x): nonlocal num_of_executions num_of_executions +=1 return x+1 s = Step(echo_increase, a) assert do_eval(s, a=1) == 2 assert do_eval(s, a=2) == 2 clear_cache(s, force=True) assert do_eval(s, a=2) == 3 assert num_of_executions == 2
def test_multi_eval(a, b): def import_math(): import math as _math return _math math = Step(import_math) def operation(x, y): z = x**2 + y**2 sz = math.sqrt(z) return x/sz, y/sz xn, yn = operation(a, b) xn_v, yn_v = multi_eval(xn, yn, a=3, b=4) assert xn_v == 0.6 assert yn_v == 0.8