Esempio n. 1
0
def test_optional_per_function_with_same_output():
    # Test that the same need can be both optional and not on different operations.
    #
    ## ATTENTION, the selected function is NOT the one with more inputs
    # but the 1st satisfiable function added in the network.

    add_op = operation(name="add", needs=["a", "b"], provides="a+-b")(add)
    sub_op_optional = operation(name="sub_opt",
                                needs=["a", optional("b")],
                                provides="a+-b")(lambda a, b=10: a - b)

    # Normal order
    #
    pipeline = compose(name="partial_optionals")(add_op, sub_op_optional)
    #
    named_inputs = {"a": 1, "b": 2}
    assert pipeline(named_inputs) == {"a": 1, "a+-b": 3, "b": 2}
    assert pipeline(named_inputs, ["a+-b"]) == {"a+-b": 3}
    #
    named_inputs = {"a": 1}
    assert pipeline(named_inputs) == {"a": 1, "a+-b": -9}
    assert pipeline(named_inputs, ["a+-b"]) == {"a+-b": -9}

    # Inverse op order
    #
    pipeline = compose(name="partial_optionals")(sub_op_optional, add_op)
    #
    named_inputs = {"a": 1, "b": 2}
    assert pipeline(named_inputs) == {"a": 1, "a+-b": -1, "b": 2}
    assert pipeline(named_inputs, ["a+-b"]) == {"a+-b": -1}
    #
    named_inputs = {"a": 1}
    assert pipeline(named_inputs) == {"a": 1, "a+-b": -9}
    assert pipeline(named_inputs, ["a+-b"]) == {"a+-b": -9}

    # PARALLEL + Normal order
    #
    pipeline = compose(name="partial_optionals")(add_op, sub_op_optional)
    pipeline.set_execution_method("parallel")
    #
    named_inputs = {"a": 1, "b": 2}
    assert pipeline(named_inputs) == {"a": 1, "a+-b": 3, "b": 2}
    assert pipeline(named_inputs, ["a+-b"]) == {"a+-b": 3}
    #
    named_inputs = {"a": 1}
    assert pipeline(named_inputs) == {"a": 1, "a+-b": -9}
    assert pipeline(named_inputs, ["a+-b"]) == {"a+-b": -9}

    # PARALLEL + Inverse op order
    #
    pipeline = compose(name="partial_optionals")(sub_op_optional, add_op)
    pipeline.set_execution_method("parallel")
    #
    named_inputs = {"a": 1, "b": 2}
    assert pipeline(named_inputs) == {"a": 1, "a+-b": -1, "b": 2}
    assert pipeline(named_inputs, ["a+-b"]) == {"a+-b": -1}
    #
    named_inputs = {"a": 1}
    assert pipeline(named_inputs) == {"a": 1, "a+-b": -9}
    assert pipeline(named_inputs, ["a+-b"]) == {"a+-b": -9}
Esempio n. 2
0
def test_type_checking():
    def abspow(a, p=3):
        c = abs(a)**p
        return c

    try:
        graph = compose(name="graph")(
            operation(name="mul1",
                      needs=[Var("a", int), Var("b", int)],
                      provides=[Var("ab", int)])(mul),
            operation(name="sub1",
                      needs=[Var("a", float),
                             Var("ab", float)],
                      provides=[Var("a_minus_ab", float)])(sub),
            operation(name="abspow1",
                      needs=[Var("a_minus_ab", float)],
                      provides=[Var("abs_a_minus_ab_cubed", float)],
                      params={"p": 3})(abspow))
    except TypeError as e:
        pass

    graph = compose(name="graph")(
        operation(name="mul1",
                  needs=[Var("a", int), Var("b", int)],
                  provides=[Var("ab", int)])(mul),
        operation(name="sub1",
                  needs=[Var("a", int), Var("ab", int)],
                  provides=[Var("a_minus_ab", int)])(sub),
        operation(name="abspow1",
                  needs=[Var("a_minus_ab", int),
                         Var("p", int, optional=True)],
                  provides=[Var("abs_a_minus_ab_cubed", int)])(abspow))

    out = graph({'a': 2, 'b': 5})
    assert out == {'abs_a_minus_ab_cubed': 512, 'a_minus_ab': -8, 'ab': 10}
Esempio n. 3
0
def test_network_simple_merge():

    sum_op1 = operation(name="sum_op1", needs=["a", "b"], provides="sum1")(add)
    sum_op2 = operation(name="sum_op2", needs=["a", "b"], provides="sum2")(add)
    sum_op3 = operation(name="sum_op3", needs=["sum1", "c"],
                        provides="sum3")(add)
    net1 = compose(name="my network 1")(sum_op1, sum_op2, sum_op3)

    exp = {"a": 1, "b": 2, "c": 4, "sum1": 3, "sum2": 3, "sum3": 7}
    sol = net1({"a": 1, "b": 2, "c": 4})
    assert sol == exp

    sum_op4 = operation(name="sum_op1", needs=["d", "e"], provides="a")(add)
    sum_op5 = operation(name="sum_op2", needs=["a", "f"], provides="b")(add)

    net2 = compose(name="my network 2")(sum_op4, sum_op5)
    exp = {"a": 3, "b": 7, "d": 1, "e": 2, "f": 4}
    sol = net2({"d": 1, "e": 2, "f": 4})
    assert sol == exp

    net3 = compose(name="merged")(net1, net2)
    exp = {
        "a": 3,
        "b": 7,
        "c": 5,
        "d": 1,
        "e": 2,
        "f": 4,
        "sum1": 10,
        "sum2": 10,
        "sum3": 15,
    }
    sol = net3({"c": 5, "d": 1, "e": 2, "f": 4})
    assert sol == exp
Esempio n. 4
0
def test_network_deep_merge():

    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op2 = operation(name='sum_op2', needs=['a', 'b'], provides='sum2')(add)
    sum_op3 = operation(name='sum_op3', needs=['sum1', 'c'], provides='sum3')(add)
    net1 = compose(name='my network 1')(sum_op1, sum_op2, sum_op3)
    pprint(net1({'a': 1, 'b': 2, 'c': 4}))

    sum_op4 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op5 = operation(name='sum_op4', needs=['sum1', 'b'], provides='sum2')(add)
    net2 = compose(name='my network 2')(sum_op4, sum_op5)
    pprint(net2({'a': 1, 'b': 2}))

    net3 = compose(name='merged', merge=True)(net1, net2)
    pprint(net3({'a': 1, 'b': 2, 'c': 4}))
Esempio n. 5
0
def test_network_simple_merge():

    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op2 = operation(name='sum_op2', needs=['a', 'b'], provides='sum2')(add)
    sum_op3 = operation(name='sum_op3', needs=['sum1', 'c'], provides='sum3')(add)
    net1 = compose(name='my network 1')(sum_op1, sum_op2, sum_op3)
    pprint(net1({'a': 1, 'b': 2, 'c': 4}))

    sum_op4 = operation(name='sum_op1', needs=['d', 'e'], provides='a')(add)
    sum_op5 = operation(name='sum_op2', needs=['a', 'f'], provides='b')(add)
    net2 = compose(name='my network 2')(sum_op4, sum_op5)
    pprint(net2({'d': 1, 'e': 2, 'f': 4}))

    net3 = compose(name='merged')(net1, net2)
    pprint(net3({'c': 5, 'd': 1, 'e': 2, 'f': 4}))
Esempio n. 6
0
def test_multithreading_plan_execution():
    # From Huygn's test-code given in yahoo/graphkit#31
    from multiprocessing.dummy import Pool
    from graphkit import compose, operation

    # Compose the mul, sub, and abspow operations into a computation graph.
    graph = compose(name="graph")(
        operation(name="mul1", needs=["a", "b"], provides=["ab"])(mul),
        operation(name="sub1", needs=["a", "ab"],
                  provides=["a_minus_ab"])(sub),
        operation(
            name="abspow1",
            needs=["a_minus_ab"],
            provides=["abs_a_minus_ab_cubed"],
            params={"p": 3},
        )(abspow),
    )

    pool = Pool(10)
    graph.set_execution_method("parallel")
    pool.map(
        lambda i: graph({
            "a": 2,
            "b": 5
        }, ["a_minus_ab", "abs_a_minus_ab_cubed"]),
        range(100),
    )
Esempio n. 7
0
def test_sideffect_real_input(bools):
    reverse = bools >> 0 & 1
    parallel = bools >> 1 & 1

    ops = [
        operation(name="extend", needs=["box", "a"],
                  provides=[sideffect("b")])(_box_extend),
        operation(name="increment",
                  needs=["box", sideffect("b")],
                  provides="c")(_box_increment),
    ]
    if reverse:
        ops = reversed(ops)
    # Designate `a`, `b` as sideffect inp/out arguments.
    graph = compose("mygraph")(*ops)
    if parallel:
        graph.set_execution_method("parallel")

    assert graph({
        "box": [0],
        "a": True
    }) == {
        "a": True,
        "box": [1, 2, 3],
        "c": None
    }
    assert graph({
        "box": [0],
        "a": True
    }, ["box", "c"]) == {
        "box": [1, 2, 3],
        "c": None
    }
Esempio n. 8
0
def test_multi_threading():
    import time
    import random
    from multiprocessing.dummy import Pool

    def op_a(a, b):
        time.sleep(random.random()*.02)
        return a+b

    def op_b(c, b):
        time.sleep(random.random()*.02)
        return c+b

    def op_c(a, b):
        time.sleep(random.random()*.02)
        return a*b

    pipeline = compose(name="pipeline", merge=True)(
        operation(name="op_a", needs=['a', 'b'], provides='c')(op_a),
        operation(name="op_b", needs=['c', 'b'], provides='d')(op_b),
        operation(name="op_c", needs=['a', 'b'], provides='e')(op_c),
    )

    def infer(i):
        # data = open("616039-bradpitt.jpg").read()
        outputs = ["c", "d", "e"]
        results = pipeline({"a": 1, "b":2}, outputs)
        assert tuple(sorted(results.keys())) == tuple(sorted(outputs)), (outputs, results)
        return results

    N = 100
    for i in range(20, 200):
        pool = Pool(i)
        pool.map(infer, range(N))
        pool.close()
Esempio n. 9
0
def test_network_simple_merge():

    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op2 = operation(name='sum_op2', needs=['a', 'b'], provides='sum2')(add)
    sum_op3 = operation(name='sum_op3', needs=['sum1', 'c'],
                        provides='sum3')(add)
    net1 = compose(name='my network 1')(sum_op1, sum_op2, sum_op3)
    pprint(net1({'a': 1, 'b': 2, 'c': 4}))

    sum_op4 = operation(name='sum_op1', needs=['d', 'e'], provides='a')(add)
    sum_op5 = operation(name='sum_op2', needs=['a', 'f'], provides='b')(add)
    net2 = compose(name='my network 2')(sum_op4, sum_op5)
    pprint(net2({'d': 1, 'e': 2, 'f': 4}))

    net3 = compose(name='merged')(net1, net2)
    pprint(net3({'c': 5, 'd': 1, 'e': 2, 'f': 4}))
Esempio n. 10
0
def pipeline():
    return compose(name="netop")(
        operation(name="add", needs=["a", "b1"], provides=["ab1"])(add),
        operation(name="sub", needs=["a", optional("b2")],
                  provides=["ab2"])(lambda a, b=1: a - b),
        operation(name="abb", needs=["ab1", "ab2"], provides=["asked"])(add),
    )
Esempio n. 11
0
def test_network_deep_merge():

    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op2 = operation(name='sum_op2', needs=['a', 'b'], provides='sum2')(add)
    sum_op3 = operation(name='sum_op3', needs=['sum1', 'c'],
                        provides='sum3')(add)
    net1 = compose(name='my network 1')(sum_op1, sum_op2, sum_op3)
    pprint(net1({'a': 1, 'b': 2, 'c': 4}))

    sum_op4 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op5 = operation(name='sum_op4', needs=['sum1', 'b'],
                        provides='sum2')(add)
    net2 = compose(name='my network 2')(sum_op4, sum_op5)
    pprint(net2({'a': 1, 'b': 2}))

    net3 = compose(name='merged', merge=True)(net1, net2)
    pprint(net3({'a': 1, 'b': 2, 'c': 4}))
Esempio n. 12
0
def get_project_facts(project_insights, project_id, start, end):
    graph = []
    for insight in project_insights:
        graph.append(insight.graph)
    project_graph = compose(name="project_graph", merge=True)(*graph)
    project = var_project(project_id)
    args = {"start": start, "end": end, "project": project}
    data = project_graph(args)
    for k in args:
        del data[k]
    return data
Esempio n. 13
0
 def __init__(
     self,
     name: str,
     template: Union[Callable, str],
     triggers: list,
     graph: list = None,
 ):
     self.name = name
     self.template = template
     self.triggers = triggers
     if graph is not None:
         self.graph = compose(name=name)(*graph)
Esempio n. 14
0
def test_network_deep_merge():

    sum_op1 = operation(name="sum_op1", needs=["a", "b"], provides="sum1")(add)
    sum_op2 = operation(name="sum_op2", needs=["a", "b"], provides="sum2")(add)
    sum_op3 = operation(name="sum_op3", needs=["sum1", "c"],
                        provides="sum3")(add)
    net1 = compose(name="my network 1")(sum_op1, sum_op2, sum_op3)

    exp = {"a": 1, "b": 2, "c": 4, "sum1": 3, "sum2": 3, "sum3": 7}
    assert net1({"a": 1, "b": 2, "c": 4}) == exp

    sum_op4 = operation(name="sum_op1", needs=["a", "b"], provides="sum1")(add)
    sum_op5 = operation(name="sum_op4", needs=["sum1", "b"],
                        provides="sum2")(add)
    net2 = compose(name="my network 2")(sum_op4, sum_op5)
    exp = {"a": 1, "b": 2, "sum1": 3, "sum2": 5}
    assert net2({"a": 1, "b": 2}) == exp

    net3 = compose(name="merged", merge=True)(net1, net2)
    exp = {"a": 1, "b": 2, "c": 4, "sum1": 3, "sum2": 3, "sum3": 7}
    assert net3({"a": 1, "b": 2, "c": 4}) == exp
Esempio n. 15
0
def test_network_merge_in_doctests():
    graphop = compose(name="graphop")(
        operation(name="mul1", needs=["a", "b"], provides=["ab"])(mul),
        operation(name="sub1", needs=["a", "ab"],
                  provides=["a_minus_ab"])(sub),
        operation(
            name="abspow1",
            needs=["a_minus_ab"],
            provides=["abs_a_minus_ab_cubed"],
            params={"p": 3},
        )(abspow),
    )

    another_graph = compose(name="another_graph")(
        operation(name="mul1", needs=["a", "b"], provides=["ab"])(mul),
        operation(name="mul2", needs=["c", "ab"], provides=["cab"])(mul),
    )
    merged_graph = compose(name="merged_graph", merge=True)(graphop,
                                                            another_graph)
    assert merged_graph.needs
    assert merged_graph.provides
Esempio n. 16
0
def test_network():

    # Sum operation, late-bind compute function
    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum_ab')(add)

    # sum_op1 is callable
    print(sum_op1(1, 2))

    # Multiply operation, decorate in-place
    @operation(name='mul_op1', needs=['sum_ab', 'b'], provides='sum_ab_times_b')
    def mul_op1(a, b):
        return a * b

    # mul_op1 is callable
    print(mul_op1(1, 2))

    # Pow operation
    @operation(name='pow_op1', needs='sum_ab', provides=['sum_ab_p1', 'sum_ab_p2', 'sum_ab_p3'], params={'exponent': 3})
    def pow_op1(a, exponent=2):
        return [math.pow(a, y) for y in range(1, exponent+1)]

    print(pow_op1._compute({'sum_ab':2}, ['sum_ab_p2']))

    # Partial operation that is bound at a later time
    partial_op = operation(name='sum_op2', needs=['sum_ab_p1', 'sum_ab_p2'], provides='p1_plus_p2')

    # Bind the partial operation
    sum_op2 = partial_op(add)

    # Sum operation, early-bind compute function
    sum_op_factory = operation(add)

    sum_op3 = sum_op_factory(name='sum_op3', needs=['a', 'b'], provides='sum_ab2')

    # sum_op3 is callable
    print(sum_op3(5, 6))

    # compose network
    net = compose(name='my network')(sum_op1, mul_op1, pow_op1, sum_op2, sum_op3)

    #
    # Running the network
    #

    # get all outputs
    pprint(net({'a': 1, 'b': 2}))

    # get specific outputs
    pprint(net({'a': 1, 'b': 2}, outputs=["sum_ab_times_b"]))

    # start with inputs already computed
    pprint(net({"sum_ab": 1, "b": 2}, outputs=["sum_ab_times_b"]))
Esempio n. 17
0
def test_parallel_execution():
    import time

    def fn(x):
        time.sleep(1)
        print("fn %s" % (time.time() - t0))
        return 1 + x

    def fn2(a,b):
        time.sleep(1)
        print("fn2 %s" % (time.time() - t0))
        return a+b

    def fn3(z, k=1):
        time.sleep(1)
        print("fn3 %s" % (time.time() - t0))
        return z + k

    pipeline = compose(name="l", merge=True)(

        # the following should execute in parallel under threaded execution mode
        operation(name="a", needs="x", provides="ao")(fn),
        operation(name="b", needs="x", provides="bo")(fn),

        # this should execute after a and b have finished
        operation(name="c", needs=["ao", "bo"], provides="co")(fn2),

        operation(name="d",
                  needs=["ao", modifiers.optional("k")],
                  provides="do")(fn3),

        operation(name="e", needs=["ao", "bo"], provides="eo")(fn2),
        operation(name="f", needs="eo", provides="fo")(fn),
        operation(name="g", needs="fo", provides="go")(fn)


    )

    t0 = time.time()
    pipeline.set_execution_method("parallel")
    result_threaded = pipeline({"x": 10}, ["co", "go", "do"])
    print("threaded result")
    print(result_threaded)

    t0 = time.time()
    pipeline.set_execution_method("sequential")
    result_sequential = pipeline({"x": 10}, ["co", "go", "do"])
    print("sequential result")
    print(result_sequential)

    # make sure results are the same using either method
    assert result_sequential == result_threaded
Esempio n. 18
0
def test_pruning_with_given_intermediate_and_asked_out():
    # Test #24: v1.2.4 does not prune before given intermediate data when
    # outputs not asked, but does so when output asked.
    pipeline = compose(name="pipeline")(
        operation(name="unjustly pruned", needs=["given-1"],
                  provides=["a"])(identity),
        operation(name="shortcuted", needs=["a", "b"],
                  provides=["given-2"])(add),
        operation(name="good_op", needs=["a", "given-2"],
                  provides=["asked"])(add),
    )

    exp = {"given-1": 5, "b": 2, "given-2": 2, "a": 5, "asked": 7}
    # v1.2.4 is ok
    assert pipeline({"given-1": 5, "b": 2, "given-2": 2}) == exp
    # FAILS
    # - on v1.2.4 with KeyError: 'a',
    # - on #18 (unsatisfied) with no result.
    # FIXED on #18+#26 (new dag solver).
    assert pipeline({
        "given-1": 5,
        "b": 2,
        "given-2": 2
    }, ["asked"]) == filtdict(exp, "asked")

    ## Test OVERWITES
    #
    overwrites = {}
    pipeline.set_overwrites_collector(overwrites)
    assert pipeline({"given-1": 5, "b": 2, "given-2": 2}) == exp
    assert overwrites == {}

    overwrites = {}
    pipeline.set_overwrites_collector(overwrites)
    assert pipeline({
        "given-1": 5,
        "b": 2,
        "given-2": 2
    }, ["asked"]) == filtdict(exp, "asked")
    assert overwrites == {}

    ## Test parallel
    #  FAIL! in #26!
    #
    pipeline.set_execution_method("parallel")
    assert pipeline({"given-1": 5, "b": 2, "given-2": 2}) == exp
    assert pipeline({
        "given-1": 5,
        "b": 2,
        "given-2": 2
    }, ["asked"]) == filtdict(exp, "asked")
Esempio n. 19
0
def test_sideffect_no_real_data(bools):
    reverse = bools >> 0 & 1
    parallel = bools >> 1 & 1

    ops = [
        operation(name="extend",
                  needs=["box", sideffect("a")],
                  provides=[sideffect("b")])(_box_extend),
        operation(name="increment",
                  needs=["box", sideffect("b")],
                  provides=sideffect("c"))(_box_increment),
    ]
    if reverse:
        ops = reversed(ops)
    # Designate `a`, `b` as sideffect inp/out arguments.
    graph = compose("mygraph")(*ops)
    if parallel:
        graph.set_execution_method("parallel")

    # Normal data must not match sideffects
    with pytest.raises(ValueError, match="Unknown output node"):
        graph({"box": [0], "a": True}, outputs=["a"])
    with pytest.raises(ValueError, match="Unknown output node"):
        graph({"box": [0], "a": True}, outputs=["b"])

    sol = graph({"box": [0], "a": True})
    # Nothing run if no sideffect inputs given.
    assert sol == {"box": [0], "a": True}

    # Nothing run if no sideffect inputs given.
    sol = graph({"box": [0], "a": True}, outputs=["box", sideffect("b")])
    assert sol == {"box": [0]}

    ## OK INPUT SIDEFFECTS
    #
    # ok, no asked out
    sol = graph({"box": [0], sideffect("a"): True})
    assert sol == {"box": [1, 2, 3], sideffect("a"): True}
    #
    # bad, not asked the out-sideffect
    sol = graph({"box": [0], sideffect("a"): True}, "box")
    assert sol == {"box": [0]}
    #
    # ok, asked the 1st out-sideffect
    sol = graph({"box": [0], sideffect("a"): True}, ["box", sideffect("b")])
    assert sol == {"box": [0, 1, 2]}
    #
    # ok, asked the 2nd out-sideffect
    sol = graph({"box": [0], sideffect("a"): True}, ["box", sideffect("c")])
    assert sol == {"box": [1, 2, 3]}
Esempio n. 20
0
def test_evict_instructions_vary_with_inputs():
    # Check #21: _EvictInstructions positions vary when inputs change.
    def count_evictions(steps):
        return sum(isinstance(n, _EvictInstruction) for n in steps)

    pipeline = compose(name="pipeline")(
        operation(name="a free without b", needs=["a"],
                  provides=["aa"])(identity),
        operation(name="satisfiable", needs=["a", "b"], provides=["ab"])(add),
        operation(name="optional ab",
                  needs=["aa", optional("ab")],
                  provides=["asked"])(lambda a, ab=10: a + ab),
    )

    inp = {"a": 2, "b": 3}
    exp = inp.copy()
    exp.update({"aa": 2, "ab": 5, "asked": 7})
    res = pipeline(inp)
    assert res == exp  # ok
    steps11 = pipeline.compile(inp).steps
    res = pipeline(inp, outputs=["asked"])
    assert res == filtdict(exp, "asked")  # ok
    steps12 = pipeline.compile(inp, ["asked"]).steps

    inp = {"a": 2}
    exp = inp.copy()
    exp.update({"aa": 2, "asked": 12})
    res = pipeline(inp)
    assert res == exp  # ok
    steps21 = pipeline.compile(inp).steps
    res = pipeline(inp, outputs=["asked"])
    assert res == filtdict(exp, "asked")  # ok
    steps22 = pipeline.compile(inp, ["asked"]).steps

    # When no outs, no evict-instructions.
    assert steps11 != steps12
    assert count_evictions(steps11) == 0
    assert steps21 != steps22
    assert count_evictions(steps21) == 0

    # Check steps vary with inputs
    #
    # FAILs in v1.2.4 + #18, PASS in #26
    assert steps11 != steps21

    # Check evicts vary with inputs
    #
    # FAILs in v1.2.4 + #18, PASS in #26
    assert count_evictions(steps12) != count_evictions(steps22)
Esempio n. 21
0
def test_pruning_raises_for_bad_output():
    # Make sure we get a ValueError during the pruning step if we request an
    # output that doesn't exist.

    # Set up a network that doesn't have the output sum4, which we'll request
    # later.
    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op2 = operation(name='sum_op2', needs=['c', 'd'], provides='sum2')(add)
    sum_op3 = operation(name='sum_op3', needs=['c', 'sum2'], provides='sum3')(add)
    net = compose(name='test_net')(sum_op1, sum_op2, sum_op3)

    # Request two outputs we can compute and one we can't compute.  Assert
    # that this raises a ValueError.
    assert_raises(ValueError, net, {'a': 1, 'b': 2, 'c': 3, 'd': 4},
        outputs=['sum1', 'sum3', 'sum4'])
Esempio n. 22
0
def test_color():
    graph = compose(name='graph')(operation(name='sum',
                                            needs=['a', 'b'],
                                            provides=['apb'],
                                            color='red')(add),
                                  operation(name='mul',
                                            needs=['a', 'b'],
                                            provides=['ab'],
                                            color='blue')(mul))

    res = graph({'a': 2, 'b': 3}, color='red')
    assert res == {'apb': 5}

    res = graph({'a': 2, 'b': 3}, color='blue')
    assert res == {'ab': 6}
Esempio n. 23
0
def test_pruning_multiouts_not_override_intermediates1():
    # Test #25: v.1.2.4 overwrites intermediate data when a previous operation
    # must run for its other outputs (outputs asked or not)
    pipeline = compose(name="graph")(
        operation(name="must run",
                  needs=["a"],
                  provides=["overriden", "calced"])(lambda x: (x, 2 * x)),
        operation(name="add",
                  needs=["overriden", "calced"],
                  provides=["asked"])(add),
    )

    inputs = {"a": 5, "overriden": 1, "c": 2}
    exp = {"a": 5, "overriden": 1, "calced": 10, "asked": 11}
    # FAILs
    # - on v1.2.4 with (overriden, asked) = (5, 15) instead of (1, 11)
    # - on #18(unsatisfied) + #23(ordered-sets) like v1.2.4.
    # FIXED on #26
    assert pipeline({"a": 5, "overriden": 1}) == exp
    # FAILs
    # - on v1.2.4 with KeyError: 'e',
    # - on #18(unsatisfied) + #23(ordered-sets) with empty result.
    # FIXED on #26
    assert pipeline(inputs, ["asked"]) == filtdict(exp, "asked")
    # Plan must contain "overriden" step twice, for pin & evict.
    # Plot it to see, or check https://github.com/huyng/graphkit/pull/1#discussion_r334226396.
    datasteps = [s for s in pipeline.net.last_plan.steps if s == "overriden"]
    assert len(datasteps) == 2
    assert isinstance(datasteps[0], network._PinInstruction)
    assert isinstance(datasteps[1], network._EvictInstruction)

    ## Test OVERWITES
    #
    overwrites = {}
    pipeline.set_overwrites_collector(overwrites)
    assert pipeline({"a": 5, "overriden": 1}) == exp
    assert overwrites == {"overriden": 5}

    overwrites = {}
    pipeline.set_overwrites_collector(overwrites)
    assert pipeline(inputs, ["asked"]) == filtdict(exp, "asked")
    assert overwrites == {"overriden": 5}

    ## Test parallel
    #
    pipeline.set_execution_method("parallel")
    assert pipeline({"a": 5, "overriden": 1}) == exp
    assert pipeline(inputs, ["asked"]) == filtdict(exp, "asked")
Esempio n. 24
0
def test_deleted_optional():
    # Test that DeleteInstructions included for optionals do not raise
    # exceptions when the corresponding input is not prodided.

    # Function to add two values plus an optional third value.
    def addplusplus(a, b, c=0):
        return a + b + c

    # Here, a DeleteInstruction will be inserted for the optional need 'c'.
    sum_op1 = operation(name='sum_op1', needs=['a', 'b', modifiers.optional('c')], provides='sum1')(addplusplus)
    sum_op2 = operation(name='sum_op2', needs=['sum1', 'sum1'], provides='sum2')(add)
    net = compose(name='test_net')(sum_op1, sum_op2)

    # DeleteInstructions are used only when a subset of outputs are requested.
    results = net({'a': 4, 'b': 3}, outputs=['sum2'])
    assert 'sum2' in results
Esempio n. 25
0
def test_parallel_execution():
    import time

    def fn(x):
        time.sleep(1)
        print("fn %s" % (time.time() - t0))
        return 1 + x

    def fn2(a, b):
        time.sleep(1)
        print("fn2 %s" % (time.time() - t0))
        return a + b

    def fn3(z, k=1):
        time.sleep(1)
        print("fn3 %s" % (time.time() - t0))
        return z + k

    pipeline = compose(name="l", merge=True)(

        # the following should execute in parallel under threaded execution mode
        operation(name="a", needs="x", provides="ao")(fn),
        operation(name="b", needs="x", provides="bo")(fn),

        # this should execute after a and b have finished
        operation(name="c", needs=["ao", "bo"], provides="co")(fn2),
        operation(name="d",
                  needs=["ao", modifiers.optional("k")],
                  provides="do")(fn3),
        operation(name="e", needs=["ao", "bo"], provides="eo")(fn2),
        operation(name="f", needs="eo", provides="fo")(fn),
        operation(name="g", needs="fo", provides="go")(fn))

    t0 = time.time()
    pipeline.set_execution_method("parallel")
    result_threaded = pipeline({"x": 10}, ["co", "go", "do"])
    print("threaded result")
    print(result_threaded)

    t0 = time.time()
    pipeline.set_execution_method("sequential")
    result_sequential = pipeline({"x": 10}, ["co", "go", "do"])
    print("sequential result")
    print(result_sequential)

    # make sure results are the same using either method
    assert result_sequential == result_threaded
Esempio n. 26
0
def test_pruning_raises_for_bad_output():
    # Make sure we get a ValueError during the pruning step if we request an
    # output that doesn't exist.

    # Set up a network that doesn't have the output sum4, which we'll request
    # later.
    sum_op1 = operation(name="sum_op1", needs=["a", "b"], provides="sum1")(add)
    sum_op2 = operation(name="sum_op2", needs=["c", "d"], provides="sum2")(add)
    sum_op3 = operation(name="sum_op3", needs=["c", "sum2"],
                        provides="sum3")(add)
    net = compose(name="test_net")(sum_op1, sum_op2, sum_op3)

    # Request two outputs we can compute and one we can't compute.  Assert
    # that this raises a ValueError.
    with pytest.raises(ValueError) as exinfo:
        net({"a": 1, "b": 2, "c": 3, "d": 4}, outputs=["sum1", "sum3", "sum4"])
    assert exinfo.match("sum4")
Esempio n. 27
0
def test_unsatisfied_operations_same_out():
    # Test unsatisfied pairs of operations providing the same output.
    pipeline = compose(name="pipeline")(
        operation(name="mul", needs=["a", "b1"], provides=["ab"])(mul),
        operation(name="div", needs=["a", "b2"], provides=["ab"])(floordiv),
        operation(name="add", needs=["ab", "c"], provides=["ab_plus_c"])(add),
    )

    exp = {"a": 10, "b1": 2, "c": 1, "ab": 20, "ab_plus_c": 21}
    assert pipeline({"a": 10, "b1": 2, "c": 1}) == exp
    assert pipeline({
        "a": 10,
        "b1": 2,
        "c": 1
    }, outputs=["ab_plus_c"]) == filtdict(exp, "ab_plus_c")

    exp = {"a": 10, "b2": 2, "c": 1, "ab": 5, "ab_plus_c": 6}
    assert pipeline({"a": 10, "b2": 2, "c": 1}) == exp
    assert pipeline({
        "a": 10,
        "b2": 2,
        "c": 1
    }, outputs=["ab_plus_c"]) == filtdict(exp, "ab_plus_c")

    ## Test parallel
    #
    #  FAIL! in #26
    pipeline.set_execution_method("parallel")
    exp = {"a": 10, "b1": 2, "c": 1, "ab": 20, "ab_plus_c": 21}
    assert pipeline({"a": 10, "b1": 2, "c": 1}) == exp
    assert pipeline({
        "a": 10,
        "b1": 2,
        "c": 1
    }, outputs=["ab_plus_c"]) == filtdict(exp, "ab_plus_c")
    #
    #  FAIL! in #26
    exp = {"a": 10, "b2": 2, "c": 1, "ab": 5, "ab_plus_c": 6}
    assert pipeline({"a": 10, "b2": 2, "c": 1}) == exp
    assert pipeline({
        "a": 10,
        "b2": 2,
        "c": 1
    }, outputs=["ab_plus_c"]) == filtdict(exp, "ab_plus_c")
Esempio n. 28
0
def test_output_based_pruning():
    # Tests to make sure we don't need to pass graph inputs if they're not
    # needed to compute the requested outputs.

    c = 2
    d = 3

    # Set up a network such that we don't need to provide a or b if we only
    # request sum3 as output.
    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op2 = operation(name='sum_op2', needs=['c', 'd'], provides='sum2')(add)
    sum_op3 = operation(name='sum_op3', needs=['c', 'sum2'], provides='sum3')(add)
    net = compose(name='test_net')(sum_op1, sum_op2, sum_op3)

    results = net({'c': c, 'd': d}, outputs=['sum3'])

    # Make sure we got expected result without having to pass a or b.
    assert 'sum3' in results
    assert results['sum3'] == add(c, add(c, d))
Esempio n. 29
0
def test_input_based_pruning():
    # Tests to make sure we don't need to pass graph inputs if we're provided
    # with data further downstream in the graph as an input.

    sum1 = 2
    sum2 = 5

    # Set up a net such that if sum1 and sum2 are provided directly, we don't
    # need to provide a and b.
    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op2 = operation(name='sum_op2', needs=['a', 'b'], provides='sum2')(add)
    sum_op3 = operation(name='sum_op3', needs=['sum1', 'sum2'], provides='sum3')(add)
    net = compose(name='test_net')(sum_op1, sum_op2, sum_op3)

    results = net({'sum1': sum1, 'sum2': sum2})

    # Make sure we got expected result without having to pass a or b.
    assert 'sum3' in results
    assert results['sum3'] == add(sum1, sum2)
Esempio n. 30
0
def test_pruning_multiouts_not_override_intermediates2():
    # Test #25: v.1.2.4 overrides intermediate data when a previous operation
    # must run for its other outputs (outputs asked or not)
    # SPURIOUS FAILS in < PY3.6 due to unordered dicts,
    # eg https://travis-ci.org/ankostis/graphkit/jobs/594813119
    pipeline = compose(name="pipeline")(
        operation(name="must run", needs=["a"],
                  provides=["overriden", "e"])(lambda x: (x, 2 * x)),
        operation(name="op1", needs=["overriden", "c"], provides=["d"])(add),
        operation(name="op2", needs=["d", "e"], provides=["asked"])(mul),
    )

    inputs = {"a": 5, "overriden": 1, "c": 2}
    exp = {"a": 5, "overriden": 1, "c": 2, "d": 3, "e": 10, "asked": 30}
    # FAILs
    # - on v1.2.4 with (overriden, asked) = (5, 70) instead of (1, 13)
    # - on #18(unsatisfied) + #23(ordered-sets) like v1.2.4.
    # FIXED on #26
    assert pipeline(inputs) == exp
    # FAILs
    # - on v1.2.4 with KeyError: 'e',
    # - on #18(unsatisfied) + #23(ordered-sets) with empty result.
    assert pipeline(inputs, ["asked"]) == filtdict(exp, "asked")
    # FIXED on #26

    ## Test OVERWITES
    #
    overwrites = {}
    pipeline.set_overwrites_collector(overwrites)
    assert pipeline(inputs) == exp
    assert overwrites == {"overriden": 5}

    overwrites = {}
    pipeline.set_overwrites_collector(overwrites)
    assert pipeline(inputs, ["asked"]) == filtdict(exp, "asked")
    assert overwrites == {"overriden": 5}

    ## Test parallel
    #
    pipeline.set_execution_method("parallel")
    assert pipeline(inputs) == exp
    assert pipeline(inputs, ["asked"]) == filtdict(exp, "asked")
Esempio n. 31
0
def test_pruning_not_overrides_given_intermediate():
    # Test #25: v1.2.4 overwrites intermediate data when no output asked
    pipeline = compose(name="pipeline")(
        operation(name="not run", needs=["a"], provides=["overriden"])(scream),
        operation(name="op", needs=["overriden", "c"],
                  provides=["asked"])(add),
    )

    inputs = {"a": 5, "overriden": 1, "c": 2}
    exp = {"a": 5, "overriden": 1, "c": 2, "asked": 3}
    # v1.2.4.ok
    assert pipeline(inputs, ["asked"]) == filtdict(exp, "asked")
    # FAILs
    # - on v1.2.4 with (overriden, asked): = (5, 7) instead of (1, 3)
    # - on #18(unsatisfied) + #23(ordered-sets) with (overriden, asked) = (5, 7) instead of (1, 3)
    # FIXED on #26
    assert pipeline(inputs) == exp

    ## Test OVERWITES
    #
    overwrites = {}
    pipeline.set_overwrites_collector(overwrites)
    assert pipeline(inputs, ["asked"]) == filtdict(exp, "asked")
    assert overwrites == {}  # unjust must have been pruned

    overwrites = {}
    pipeline.set_overwrites_collector(overwrites)
    assert pipeline(inputs) == exp
    assert overwrites == {}  # unjust must have been pruned

    ## Test Parallel
    #
    pipeline.set_execution_method("parallel")
    overwrites = {}
    pipeline.set_overwrites_collector(overwrites)
    assert pipeline(inputs, ["asked"]) == filtdict(exp, "asked")
    assert overwrites == {}  # unjust must have been pruned

    overwrites = {}
    pipeline.set_overwrites_collector(overwrites)
    assert pipeline(inputs) == exp
    assert overwrites == {}  # unjust must have been pruned
Esempio n. 32
0
def test_control_and_color():
    graph = compose(name='graph')(
        operation(name="mul1", needs=['a', 'b'], provides=['ab'],
                  color='red')(mul),
        If(name='if_less_than_2',
           needs=['ab'],
           provides=['d'],
           condition_needs=['i'],
           condition=lambda i: i < 2)(operation(
               name='add', needs=['ab'], provides=['c'],
               color='red')(lambda ab: ab + 2),
                                      operation(name='sub2',
                                                needs=['c'],
                                                provides=['d'],
                                                color='red')(lambda c: c - 2)),
        ElseIf(name='elseif',
               needs=['ab'],
               provides=['d'],
               condition_needs=['ab'],
               condition=lambda ab: ab > 2)(
                   operation(name='mul2',
                             needs=['ab'],
                             provides=['d'],
                             color='blue')(lambda ab: ab * 10)),
        Else(name='else_less_than_2', needs=['ab'],
             provides=['d'])(operation(name='sub',
                                       needs=['ab'],
                                       provides=['c'],
                                       color='red')(lambda ab: ab - 1),
                             operation(name='add2',
                                       needs=['c'],
                                       provides=['d'],
                                       color='red')(lambda c: c + 1)),
        operation(name='div', needs=['d'], provides=['e'],
                  color='blue')(lambda d: d / 2))

    res = graph({'a': 1, 'b': 3, 'i': 3}, color='red')
    assert res == {'ab': 3}

    res.update({'i': 3})
    res2 = graph(res, color='blue')
    assert res2 == {'d': 30, 'e': 15.0}
Esempio n. 33
0
def test_output_based_pruning():
    # Tests to make sure we don't need to pass graph inputs if they're not
    # needed to compute the requested outputs.

    c = 2
    d = 3

    # Set up a network such that we don't need to provide a or b if we only
    # request sum3 as output.
    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op2 = operation(name='sum_op2', needs=['c', 'd'], provides='sum2')(add)
    sum_op3 = operation(name='sum_op3', needs=['c', 'sum2'],
                        provides='sum3')(add)
    net = compose(name='test_net')(sum_op1, sum_op2, sum_op3)

    results = net({'c': c, 'd': d}, outputs=['sum3'])

    # Make sure we got expected result without having to pass a or b.
    assert 'sum3' in results
    assert results['sum3'] == add(c, add(c, d))
Esempio n. 34
0
def test_output_based_pruning():
    # Tests to make sure we don't need to pass graph inputs if they're not
    # needed to compute the requested outputs.

    c = 2
    d = 3

    # Set up a network such that we don't need to provide a or b if we only
    # request sum3 as output.
    sum_op1 = operation(name="sum_op1", needs=["a", "b"], provides="sum1")(add)
    sum_op2 = operation(name="sum_op2", needs=["c", "d"], provides="sum2")(add)
    sum_op3 = operation(name="sum_op3", needs=["c", "sum2"],
                        provides="sum3")(add)
    net = compose(name="test_net")(sum_op1, sum_op2, sum_op3)

    results = net({"a": 0, "b": 0, "c": c, "d": d}, outputs=["sum3"])

    # Make sure we got expected result without having to pass a or b.
    assert "sum3" in results
    assert results["sum3"] == add(c, add(c, d))
Esempio n. 35
0
def test_input_output_based_pruning():
    # Tests to make sure we don't need to pass graph inputs if they're not
    # needed to compute the requested outputs or of we're provided with
    # inputs that are further downstream in the graph.

    c = 2
    sum2 = 5

    # Set up a network such that we don't need to provide a or b d if we only
    # request sum3 as output and if we provide sum2.
    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op2 = operation(name='sum_op2', needs=['c', 'd'], provides='sum2')(add)
    sum_op3 = operation(name='sum_op3', needs=['c', 'sum2'], provides='sum3')(add)
    net = compose(name='test_net')(sum_op1, sum_op2, sum_op3)

    results = net({'c': c, 'sum2': sum2}, outputs=['sum3'])

    # Make sure we got expected result without having to pass a, b, or d.
    assert 'sum3' in results
    assert results['sum3'] == add(c, sum2)
Esempio n. 36
0
def test_evicted_optional():
    # Test that _EvictInstructions included for optionals do not raise
    # exceptions when the corresponding input is not prodided.

    # Function to add two values plus an optional third value.
    def addplusplus(a, b, c=0):
        return a + b + c

    # Here, a _EvictInstruction will be inserted for the optional need 'c'.
    sum_op1 = operation(name="sum_op1",
                        needs=["a", "b", optional("c")],
                        provides="sum1")(addplusplus)
    sum_op2 = operation(name="sum_op2",
                        needs=["sum1", "sum1"],
                        provides="sum2")(add)
    net = compose(name="test_net")(sum_op1, sum_op2)

    # _EvictInstructions are used only when a subset of outputs are requested.
    results = net({"a": 4, "b": 3}, outputs=["sum2"])
    assert "sum2" in results
Esempio n. 37
0
def test_deleted_optional():
    # Test that DeleteInstructions included for optionals do not raise
    # exceptions when the corresponding input is not prodided.

    # Function to add two values plus an optional third value.
    def addplusplus(a, b, c=0):
        return a + b + c

    # Here, a DeleteInstruction will be inserted for the optional need 'c'.
    sum_op1 = operation(name='sum_op1',
                        needs=['a', 'b', modifiers.optional('c')],
                        provides='sum1')(addplusplus)
    sum_op2 = operation(name='sum_op2',
                        needs=['sum1', 'sum1'],
                        provides='sum2')(add)
    net = compose(name='test_net')(sum_op1, sum_op2)

    # DeleteInstructions are used only when a subset of outputs are requested.
    results = net({'a': 4, 'b': 3}, outputs=['sum2'])
    assert 'sum2' in results
Esempio n. 38
0
def test_control():

    # create graph with control flow (if, elseif, else)
    graph = compose(name='graph')(
        operation(name="mul1", needs=['a', 'b'], provides=['ab'])(mul),
        If(name='if_less_than_2',
           needs=['ab'],
           provides=['d'],
           condition_needs=['i'],
           condition=lambda i: i < 2)(
               operation(name='add', needs=['ab'],
                         provides=['c'])(lambda ab: ab + 2),
               operation(name='sub2', needs=['c'],
                         provides=['d'])(lambda c: c - 2)),
        ElseIf(name='elseif',
               needs=['ab'],
               provides=['d'],
               condition_needs=['ab'],
               condition=lambda ab: ab > 2)(
                   operation(name='add', needs=['ab'],
                             provides=['d'])(lambda ab: ab * 10)),
        Else(name='else_less_than_2', needs=['ab'],
             provides=['d'])(operation(name='sub',
                                       needs=['ab'],
                                       provides=['c'])(lambda ab: ab - 1),
                             operation(name='add2',
                                       needs=['c'],
                                       provides=['d'])(lambda c: c + 1)),
        operation(name='div', needs=['d'], provides=['e'])(lambda d: d / 2))

    # check if branch
    results = graph({'a': 1, 'b': 3, 'i': 1})
    assert results == {'ab': 3, 'c': 5, 'd': 3, 'e': 1.5}

    # check else if branch
    results = graph({'a': 1, 'b': 3, 'i': 3})
    assert results == {'ab': 3, 'd': 30, 'e': 15.0}

    # check else branch
    results = graph({'a': 1, 'b': 1, 'i': 3})
    assert results == {'ab': 1, 'c': 0, 'd': 1, 'e': 0.5}
Esempio n. 39
0
def test_input_based_pruning():
    # Tests to make sure we don't need to pass graph inputs if we're provided
    # with data further downstream in the graph as an input.

    sum1 = 2
    sum2 = 5

    # Set up a net such that if sum1 and sum2 are provided directly, we don't
    # need to provide a and b.
    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op2 = operation(name='sum_op2', needs=['a', 'b'], provides='sum2')(add)
    sum_op3 = operation(name='sum_op3',
                        needs=['sum1', 'sum2'],
                        provides='sum3')(add)
    net = compose(name='test_net')(sum_op1, sum_op2, sum_op3)

    results = net({'sum1': sum1, 'sum2': sum2})

    # Make sure we got expected result without having to pass a or b.
    assert 'sum3' in results
    assert results['sum3'] == add(sum1, sum2)
Esempio n. 40
0
def test_input_output_based_pruning():
    # Tests to make sure we don't need to pass graph inputs if they're not
    # needed to compute the requested outputs or of we're provided with
    # inputs that are further downstream in the graph.

    c = 2
    sum2 = 5

    # Set up a network such that we don't need to provide a or b d if we only
    # request sum3 as output and if we provide sum2.
    sum_op1 = operation(name='sum_op1', needs=['a', 'b'], provides='sum1')(add)
    sum_op2 = operation(name='sum_op2', needs=['c', 'd'], provides='sum2')(add)
    sum_op3 = operation(name='sum_op3', needs=['c', 'sum2'],
                        provides='sum3')(add)
    net = compose(name='test_net')(sum_op1, sum_op2, sum_op3)

    results = net({'c': c, 'sum2': sum2}, outputs=['sum3'])

    # Make sure we got expected result without having to pass a, b, or d.
    assert 'sum3' in results
    assert results['sum3'] == add(c, sum2)
Esempio n. 41
0
def test_optional():
    # Test that optional() needs work as expected.

    # Function to add two values plus an optional third value.
    def addplusplus(a, b, c=0):
        return a + b + c

    sum_op = operation(name='sum_op1', needs=['a', 'b', modifiers.optional('c')], provides='sum')(addplusplus)

    net = compose(name='test_net')(sum_op)

    # Make sure output with optional arg is as expected.
    named_inputs = {'a': 4, 'b': 3, 'c': 2}
    results = net(named_inputs)
    assert 'sum' in results
    assert results['sum'] == sum(named_inputs.values())

    # Make sure output without optional arg is as expected.
    named_inputs = {'a': 4, 'b': 3}
    results = net(named_inputs)
    assert 'sum' in results
    assert results['sum'] == sum(named_inputs.values())