def test_CAS_sums_and_prods(a, b, c, d): mul_1 = Mul(a, d) mul_2 = Mul(b, c) sum_1 = Sum(mul_1, mul_2) assert do_eval(sum_1, a=1, b=2, c=2, d=4) == 8 r = a*d + b*c assert do_eval(r, a=1, b=2, c=2, d=4) == 8
def test_CAS_sum_between_strings_asymmetric(a): r1 = "hello" + a r2 = a + "hello" v1 = do_eval(r1, a='world') v2 = do_eval(r2, a='world') assert v1!=v2 assert v1 == 'helloworld' assert v2 == 'worldhello'
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_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_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_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_resect_part_of_the_DAG(a, b, c, d): r = a*d + b*c r2 = do_eval(r, a=1, c=3, d=4) r3 = 4+b*3 assert are_equal(r2, r3) def _replace_idx(obj, idx, value): obj = list(obj) obj[idx] = value return tuple(obj) assert do_eval(r3, b=1) == 7 clear_cache(r3, force=True) r3._args[1] = b*4 assert do_eval(r3, b=1) == 8
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_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_programmatically_create_model(): def make_pipeline(name_a='a', name_b='b'): a = Step(name_a) b = Step(name_b) return a+b R = make_pipeline('c', 'd') assert do_eval(R, c=1, d=2) == 3
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_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_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_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_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_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_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_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_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_CAS_rfloordiv(a): assert do_eval(5//a, a=2) == 2
def test_CAS_rmod(a): assert do_eval(5%a, a=2) == 1
def test_CAS_xor(a): assert do_eval(a ^ True, a=False) == True
def test_CAS_rlshift(a): assert do_eval(2<<a, a=3) == (2<<3)
def test_CAS_mod(a): assert do_eval(a %2, a=5) == 1
def test_CAS_lshift(a): assert do_eval(a <<2, a=5) == (5<<2)
def test_CAS_rrshift(a): assert do_eval(2>>a, a=3) == (2>>3)
def test_CAS_rshift(a): assert do_eval(a >>2, a=5) == (5>>2)
def test_CAS_rxor(a): assert do_eval(True ^ a, a=False) == True
def test_CAS_floordiv(a): assert do_eval(a //2, a=5) == 2