Ejemplo n.º 1
0
def norm(variables, diagram, l_norm=None):
    from pyxadd.matrix_vector import sum_out
    if l_norm is None:
        l_norm = 2
    elif l_norm != 2 and l_norm != 1:
        raise RuntimeError("Unsupported norm:{}, should be 1 or 2 [default: None => 2]".format(norm))

    if l_norm == 2:
        node_id = SquareWalker(diagram, diagram.profile).walk()
    else:
        node_id = AbsoluteValueWalker(diagram, diagram.profile).walk()
    normed = sum_out(diagram.pool, node_id, variables)
    diagram = Diagram(diagram.pool, normed)
    try:
        value = diagram.evaluate({})
        if value < 0:
            raise RuntimeError("The squared sum is negative ({})".format(value))
        if l_norm == 2:
            return sympy.sqrt(value)
        else:
            return value
    except RuntimeError as e:
        found = VariableFinder(diagram).walk()
        if len(found) > 0:
            raise RuntimeError("Variables {f} were not eliminated (given variables were {v})"
                               .format(f=list(found), v=variables))
        else:
            raise e
Ejemplo n.º 2
0
def norm(variables, diagram, l_norm=None):
    from pyxadd.matrix_vector import sum_out
    if l_norm is None:
        l_norm = 2
    elif l_norm != 2 and l_norm != 1:
        raise RuntimeError(
            "Unsupported norm:{}, should be 1 or 2 [default: None => 2]".
            format(norm))

    if l_norm == 2:
        node_id = SquareWalker(diagram, diagram.profile).walk()
    else:
        node_id = AbsoluteValueWalker(diagram, diagram.profile).walk()
    normed = sum_out(diagram.pool, node_id, variables)
    diagram = Diagram(diagram.pool, normed)
    try:
        value = diagram.evaluate({})
        if value < 0:
            raise RuntimeError(
                "The squared sum is negative ({})".format(value))
        if l_norm == 2:
            return sympy.sqrt(value)
        else:
            return value
    except RuntimeError as e:
        found = VariableFinder(diagram).walk()
        if len(found) > 0:
            raise RuntimeError(
                "Variables {f} were not eliminated (given variables were {v})".
                format(f=list(found), v=variables))
        else:
            raise e
Ejemplo n.º 3
0
    def test_mixed_symbolic(self):
        diagram_y = Diagram(self.diagram.pool, SummationWalker(self.diagram, "x").walk())
        diagram_y = Diagram(diagram_y.pool, LinearReduction(diagram_y.pool).reduce(diagram_y.root_node.node_id, ["y"]))

        for y in range(0, 12):
            row_result = 0
            for x in range(0, 12):
                row_result += self.diagram.evaluate({"x": x, "y": y})
            self.assertEqual(diagram_y.evaluate({"y": y}), row_result)
Ejemplo n.º 4
0
    def test_not(self):
        pool = Pool()
        pool.int_var("x")
        dd_true = Diagram(pool, pool.bool_test(LinearTest("x", ">=")))
        dd_false = Diagram(pool, pool.invert(dd_true.root_node.node_id))

        for i in range(-5, 6):
            assignment = {"x": i}
            self.assertEqual((dd_true.evaluate(assignment) + 1) % 2, dd_false.evaluate(assignment))
Ejemplo n.º 5
0
 def test_summation_one_var(self):
     pool = Pool()
     pool.add_var("x", "int")
     pool.add_var("y", "int")
     b = Builder(pool)
     bounds = b.test("x", ">=", 0) & b.test("x", "<=", 10)
     d = b.ite(bounds, b.terminal("x"), b.terminal(0))
     d_const = Diagram(pool, SummationWalker(d, "x").walk())
     self.assertEqual(55, d_const.evaluate({}))
Ejemplo n.º 6
0
    def test_square(self):
        diagram = Diagram(self.diagram.pool, SquareWalker(self.diagram, self.diagram.profile).walk())

        for x in range(0, 12):
            obtained = diagram.evaluate({"x": x})
            if x < 1:
                self.assertEqual(0, obtained)
            elif x <= 5:
                self.assertEqual((x + 2) ** 2, obtained)
            elif x <= 10:
                self.assertEqual((7 - 2 * (x - 5)) ** 2, obtained)
            else:
                self.assertEqual(0, obtained)
Ejemplo n.º 7
0
    def setUp(self):
        pool = Pool()
        pool.int_var("x")
        lb = Diagram(pool, pool.bool_test(LinearTest("x - 1", ">=")))
        ub = Diagram(pool, pool.bool_test(LinearTest("x - 10", "<=")))
        test = Diagram(pool, pool.bool_test(LinearTest("x - 5", "<=")))
        term_one = Diagram(pool, pool.terminal("x + 2"))
        term_two = Diagram(pool, pool.terminal("7 - 2 * (x - 5)"))

        b1 = lb & ub & test * term_one
        b2 = lb & ub & ~test * term_two

        self.diagram = b1 + b2
Ejemplo n.º 8
0
    def test_square(self):
        diagram = Diagram(
            self.diagram.pool,
            SquareWalker(self.diagram, self.diagram.profile).walk())

        for x in range(0, 12):
            obtained = diagram.evaluate({"x": x})
            if x < 1:
                self.assertEqual(0, obtained)
            elif x <= 5:
                self.assertEqual((x + 2)**2, obtained)
            elif x <= 10:
                self.assertEqual((7 - 2 * (x - 5))**2, obtained)
            else:
                self.assertEqual(0, obtained)
Ejemplo n.º 9
0
 def terminal(self, exp):
     if exp == "1" or exp == 1:
         node_id = self.pool.one_id
     elif exp == "0" or exp == 0:
         node_id = self.pool.zero_id
     else:
         node_id = self.pool.terminal(exp)
     return Diagram(self.pool, node_id)
Ejemplo n.º 10
0
    def test_summation_two_var_test(self):
        pool = Pool()
        pool.add_var("x", "int")
        pool.add_var("y", "int")
        b = Builder(pool)
        bounds = b.test("x", ">=", 0) & b.test("x", "<=", 1)
        bounds &= b.test("y", ">=", 1) & b.test("y", "<=", 3)
        two = b.test("x", ">=", "y")
        d = b.ite(bounds, b.ite(two, b.terminal("x"), b.terminal("10")), b.terminal(0))

        summed = Diagram(pool, SummationWalker(d, "x").walk())
        d_const = summed.reduce(["y"])
        for y in range(-20, 20):
            s = 0
            for x in range(-20, 20):
                s += d.evaluate({"x": x, "y": y})
            self.assertEqual(s, d_const.evaluate({"y": y}))
Ejemplo n.º 11
0
    def test_multiplication(self):
        pool = Pool()
        pool.int_var("x")
        two = pool.terminal("2")
        x = pool.terminal("x")

        test1 = pool.bool_test(LinearTest("x", ">="))
        test2 = pool.apply(Multiplication, pool.bool_test(LinearTest("x - 5", "<=")), x)

        product = pool.apply(Multiplication, test1, test2)
        result = Diagram(pool, pool.apply(Multiplication, product, two))

        for i in range(0, 10):
            evaluated = result.evaluate({"x": i})
            if 0 <= i <= 5:
                self.assertEqual(2 * i, evaluated)
            else:
                self.assertEqual(0, evaluated)
Ejemplo n.º 12
0
def reduce(diagram, linear=True):
    variables = [t[0] for t in column_variables + row_variables]
    if linear:
        reduction = LinearReduction(diagram.pool)
        result = reduction.reduce(diagram.root_node.node_id, variables)
    else:
        reduction = SmtReduce(diagram.pool)
        result = reduction.reduce(diagram.root_node.node_id, variables)
    return Diagram(diagram.pool, result)
Ejemplo n.º 13
0
    def test_multiplication(self):
        pool = Pool()
        pool.int_var("x")
        two = pool.terminal("2")
        x = pool.terminal("x")

        test1 = pool.bool_test(LinearTest("x", ">="))
        test2 = pool.apply(Multiplication,
                           pool.bool_test(LinearTest("x - 5", "<=")), x)

        product = pool.apply(Multiplication, test1, test2)
        result = Diagram(pool, pool.apply(Multiplication, product, two))

        for i in range(0, 10):
            evaluated = result.evaluate({"x": i})
            if 0 <= i <= 5:
                self.assertEqual(2 * i, evaluated)
            else:
                self.assertEqual(0, evaluated)
Ejemplo n.º 14
0
 def construct_diagram():
     pool = Pool(empty=True)
     pool.int_var("x")
     x = pool.terminal("x")
     zero = pool.terminal("0")
     test1 = pool.internal(LinearTest("x - 5", "<="), x, zero)
     test2 = pool.internal(LinearTest("x + 1", ">="), test1, zero)
     test3 = pool.internal(LinearTest("x + 2", "<="), x, test2)
     root = pool.internal(LinearTest("x", ">="), test1, test3)
     return Diagram(pool, root)
Ejemplo n.º 15
0
 def test(self, lhs, symbol=None, rhs=None):
     if symbol is None and rhs is None:
         if self.pool.get_var_type(lhs) != "bool":
             raise RuntimeError(
                 "'{}' is not a variable of type 'bool'".format(lhs))
         test = BinaryTest(lhs)
     else:
         test = LinearTest(lhs, symbol, rhs)
     return Diagram(
         self.pool,
         self.pool.internal(test, self.pool.one_id, self.pool.zero_id))
Ejemplo n.º 16
0
    def setUp(self):
        pool = Pool()
        pool.int_var("x")

        self.test1 = pool.bool_test(LinearTest("x", ">="))
        self.test2 = pool.bool_test(LinearTest("x + 2", ">"))
        self.test3 = pool.bool_test(LinearTest("x + 1", "<="))
        self.test4 = pool.bool_test(LinearTest("x - 5", "<="))
        self.x = pool.terminal("x")

        p1 = pool.apply(Multiplication, self.test1, self.test4)
        p2 = pool.apply(Multiplication, pool.invert(self.test1), self.test2)
        p3 = pool.apply(
            Multiplication,
            pool.apply(
                Multiplication,
                pool.apply(Multiplication, pool.invert(self.test1),
                           pool.invert(self.test2)), self.test3), self.test4)

        result = pool.apply(Summation, pool.apply(Summation, p1, p2), p3)
        result = pool.apply(Multiplication, result, self.x)
        self.diagram = Diagram(pool, result)
Ejemplo n.º 17
0
 def test(self, lhs, symbol=None, rhs=None):
     if symbol is None and rhs is None:
         if isinstance(lhs, str) and re.match(self.inequality_pattern, lhs):
             match = re.match(self.inequality_pattern, lhs)
             return self.test(match.group(1), match.group(2),
                              match.group(3))
         if self.pool.get_var_type(lhs) != "bool":
             raise RuntimeError(
                 "'{}' is not a variable of type 'bool'".format(lhs))
         test = BinaryTest(lhs)
     else:
         test = LinearTest(lhs, symbol, rhs)
     return Diagram(
         self.pool,
         self.pool.internal(test, self.pool.one_id, self.pool.zero_id))
Ejemplo n.º 18
0
    def test_not(self):
        pool = Pool()
        pool.int_var("x")
        dd_true = Diagram(pool, pool.bool_test(LinearTest("x", ">=")))
        dd_false = Diagram(pool, pool.invert(dd_true.root_node.node_id))

        for i in range(-5, 6):
            assignment = {"x": i}
            self.assertEqual((dd_true.evaluate(assignment) + 1) % 2,
                             dd_false.evaluate(assignment))
Ejemplo n.º 19
0
    def setUp(self):
        pool = Pool()
        pool.int_var("x")

        self.test1 = pool.bool_test(LinearTest("x", ">="))
        self.test2 = pool.bool_test(LinearTest("x + 2", ">"))
        self.test3 = pool.bool_test(LinearTest("x + 1", "<="))
        self.test4 = pool.bool_test(LinearTest("x - 5", "<="))
        self.x = pool.terminal("x")

        p1 = pool.apply(Multiplication, self.test1, self.test4)
        p2 = pool.apply(Multiplication, pool.invert(self.test1), self.test2)
        p3 = pool.apply(Multiplication, pool.apply(Multiplication, pool.apply(Multiplication,
                                                                              pool.invert(self.test1),
                                                                              pool.invert(self.test2)),
                                                   self.test3),
                        self.test4)

        result = pool.apply(Summation, pool.apply(Summation, p1, p2), p3)
        result = pool.apply(Multiplication, result, self.x)
        self.diagram = Diagram(pool, result)
Ejemplo n.º 20
0
    def setUp(self):
        pool = Pool()
        pool.int_var("x")
        lb = Diagram(pool, pool.bool_test(LinearTest("x - 1", ">=")))
        ub = Diagram(pool, pool.bool_test(LinearTest("x - 10", "<=")))
        test = Diagram(pool, pool.bool_test(LinearTest("x - 5", "<=")))
        redundant_test = Diagram(pool, pool.bool_test(LinearTest("x - 6", "<=")))

        term_one = Diagram(pool, pool.terminal("x + 2"))
        term_two = Diagram(pool, pool.terminal("7 - 2 * (x - 5)"))

        b1 = (lb & ub & test & redundant_test) * term_one
        b2 = (lb & ub & ~test & redundant_test) * term_two

        self.diagram = b1 + b2

        self.exporter = Exporter(os.path.join(os.path.dirname(os.path.realpath(__file__)), "visual"), "reduce")
Ejemplo n.º 21
0
    def test_multiplication(self):
        pool = Pool()
        pool.int_var("x1", "x2")
        x_two = Diagram(pool, pool.terminal("x2"))
        two = Diagram(pool, pool.terminal("2"))
        three = Diagram(pool, pool.terminal("3"))
        four = Diagram(pool, pool.terminal("4"))

        test11 = Diagram(pool, pool.bool_test(LinearTest("x1", ">=")))
        test12 = Diagram(pool, pool.bool_test(LinearTest("x1 - 1", "<=")))
        test13 = Diagram(pool, pool.bool_test(LinearTest("x1 - 3", ">")))

        test21 = Diagram(pool, pool.bool_test(LinearTest("x2", ">=")))
        test22 = Diagram(pool, pool.bool_test(LinearTest("x2", ">")))
        test23 = Diagram(pool, pool.bool_test(LinearTest("x2 - 1", ">")))
        test24 = Diagram(pool, pool.bool_test(LinearTest("x2 - 2", ">")))

        x_twos = test12 * ~test23 * x_two
        twos = test12 * test23 * two
        threes = ~test12 * ~test22 * three
        fours = ~test12 * test22 * four

        unlimited = x_twos + twos + threes + fours
        restricted = unlimited * test11 * ~test13 * test21 * ~test24

        vector = test21 * ~test24 * Diagram(pool, pool.terminal("x2 + 1"))

        result = Diagram(pool, matrix_multiply(pool, restricted.root_node.node_id, vector.root_node.node_id, ["x2"]))
        for x1 in range(0, 4):
            self.assertEqual(8 if x1 < 2 else 23, result.evaluate({"x1": x1}))
Ejemplo n.º 22
0
 def test_smt_reduce(self):
     self.exporter.export(self.diagram, "to_reduce")
     result = SmtReduce(self.diagram.pool).reduce(
         self.diagram.root_node.node_id, ["x"])
     self.exporter.export(Diagram(self.diagram.pool, result), "result")
Ejemplo n.º 23
0
class TestDiagram(unittest.TestCase):
    def setUp(self):
        pool = Pool()
        pool.int_var("x")

        self.test1 = pool.bool_test(LinearTest("x", ">="))
        self.test2 = pool.bool_test(LinearTest("x + 2", ">"))
        self.test3 = pool.bool_test(LinearTest("x + 1", "<="))
        self.test4 = pool.bool_test(LinearTest("x - 5", "<="))
        self.x = pool.terminal("x")

        p1 = pool.apply(Multiplication, self.test1, self.test4)
        p2 = pool.apply(Multiplication, pool.invert(self.test1), self.test2)
        p3 = pool.apply(
            Multiplication,
            pool.apply(
                Multiplication,
                pool.apply(Multiplication, pool.invert(self.test1),
                           pool.invert(self.test2)), self.test3), self.test4)

        result = pool.apply(Summation, pool.apply(Summation, p1, p2), p3)
        result = pool.apply(Multiplication, result, self.x)
        self.diagram = Diagram(pool, result)

    def test_evaluation(self):
        self.assertEqual(4, self.diagram.evaluate({"x": 4}))

    def test_multiplication(self):
        pool = Pool()
        pool.int_var("x")
        two = pool.terminal("2")
        x = pool.terminal("x")

        test1 = pool.bool_test(LinearTest("x", ">="))
        test2 = pool.apply(Multiplication,
                           pool.bool_test(LinearTest("x - 5", "<=")), x)

        product = pool.apply(Multiplication, test1, test2)
        result = Diagram(pool, pool.apply(Multiplication, product, two))

        for i in range(0, 10):
            evaluated = result.evaluate({"x": i})
            if 0 <= i <= 5:
                self.assertEqual(2 * i, evaluated)
            else:
                self.assertEqual(0, evaluated)

    def test_not(self):
        pool = Pool()
        pool.int_var("x")
        dd_true = Diagram(pool, pool.bool_test(LinearTest("x", ">=")))
        dd_false = Diagram(pool, pool.invert(dd_true.root_node.node_id))

        for i in range(-5, 6):
            assignment = {"x": i}
            self.assertEqual((dd_true.evaluate(assignment) + 1) % 2,
                             dd_false.evaluate(assignment))

    def test_construct(self):
        self.check_diagram(self.diagram, self.diagram.pool.zero_id, self.x)

    def test_summation(self):
        result = self.diagram + self.diagram
        self.check_diagram(result, result.pool.zero_id,
                           result.pool.terminal("2*x"))

    def check_diagram(self, diagram, zero_term, x_term):
        pool = diagram.pool
        layers = WalkingProfile.extract_layers(diagram,
                                               ParentsWalker(diagram).walk())
        self.assertEqual(5, len(layers))

        self.assertEqual(1, len(layers[0]), layers[0])
        self.assertEqual(
            pool.get_node(self.test1).test,
            pool.get_node(layers[0][0]).test, layers[0])

        self.assertEqual(1, len(layers[1]), layers[1])
        self.assertEqual(
            pool.get_node(self.test2).test,
            pool.get_node(layers[1][0]).test, layers[1])

        self.assertEqual(1, len(layers[2]), layers[2])
        self.assertEqual(
            pool.get_node(self.test3).test,
            pool.get_node(layers[2][0]).test, layers[2])

        self.assertEqual(1, len(layers[3]), layers[3])
        self.assertEqual(
            pool.get_node(self.test4).test,
            pool.get_node(layers[3][0]).test, layers[3])

        self.assertEqual(2, len(layers[4]))
        self.assertTrue(zero_term in layers[4], layers[4])
        self.assertTrue(x_term in layers[4], layers[4])

    def test_printing(self):
        import json
        encoded = Pool.to_json(self.diagram.pool)
        representation = json.loads(encoded)
        reconstructed = Pool.from_json(encoded)
        re_encoded = Pool.to_json(reconstructed)
        new_representation = json.loads(re_encoded)
        self.assertEquals(representation, new_representation)

    def test_inversion(self):
        pool = Pool()
        build = Builder(pool)
        build.vars("bool", "a", "b")
        build.vars("int", "x")

        test1 = build.test("a")
        test2 = build.test("b")
        test3 = build.test("x", "<=", 5)

        node3 = build.ite(test3, 1, 0)
        diagram = build.ite(test1, build.ite(test2, node3, 1), node3)

        self.assertTrue(is_ordered(diagram))

        def inversion1(root_id):
            minus_one = pool.terminal("-1")
            return pool.apply(Multiplication,
                              pool.apply(Summation, root_id, minus_one),
                              minus_one)

        def transform(terminal_node, d):
            if terminal_node.expression == 1:
                return d.pool.zero_id
            elif terminal_node.expression == 0:
                return d.pool.one_id
            else:
                raise RuntimeError("Could not invert value {}".format(
                    terminal_node.expression))

        def inversion2(root_id):
            to_invert = pool.diagram(root_id)
            profile = WalkingProfile(diagram)
            return leaf_transform.transform_leaves(transform, to_invert)

        iterations = 1000
        timer = Timer(precision=6)
        timer.start("Legacy inversion")
        for _ in range(iterations):
            inversion1(diagram.root_id)
        time_legacy = timer.stop()

        inverted1 = pool.diagram(inversion1(diagram.root_id))

        timer.start("New inversion")
        for _ in range(iterations):
            inversion2(diagram.root_id)
        time_new = timer.stop()

        inverted2 = pool.diagram(inversion2(diagram.root_id))

        for a in [True, False]:
            for b in [True, False]:
                for x in range(10):
                    assignment = {"a": a, "b": b, "x": x}
                    self.assertNotEqual(diagram.evaluate(assignment),
                                        inverted1.evaluate(assignment))
                    self.assertNotEqual(diagram.evaluate(assignment),
                                        inverted2.evaluate(assignment))

        self.assertTrue(
            time_legacy > time_new,
            "New inversion ({}) not faster than legacy implementation ({})".
            format(time_new, time_legacy))
Ejemplo n.º 24
0
 def walk(self):
     return Diagram(self._diagram.pool, BottomUpWalker.walk(self))
Ejemplo n.º 25
0
def variables(diagram_or_node, pool=None):
    if not isinstance(diagram_or_node, Diagram):
        diagram_or_node = Diagram(pool, diagram_or_node)
    return VariableFinder(diagram_or_node).walk()
Ejemplo n.º 26
0
 def _get_variables(self, node_id):
     return VariableFinder(Diagram(self.pool, node_id)).walk()
Ejemplo n.º 27
0
class TestDiagram(unittest.TestCase):
    def setUp(self):
        pool = Pool()
        pool.int_var("x")

        self.test1 = pool.bool_test(LinearTest("x", ">="))
        self.test2 = pool.bool_test(LinearTest("x + 2", ">"))
        self.test3 = pool.bool_test(LinearTest("x + 1", "<="))
        self.test4 = pool.bool_test(LinearTest("x - 5", "<="))
        self.x = pool.terminal("x")

        p1 = pool.apply(Multiplication, self.test1, self.test4)
        p2 = pool.apply(Multiplication, pool.invert(self.test1), self.test2)
        p3 = pool.apply(Multiplication, pool.apply(Multiplication, pool.apply(Multiplication,
                                                                              pool.invert(self.test1),
                                                                              pool.invert(self.test2)),
                                                   self.test3),
                        self.test4)

        result = pool.apply(Summation, pool.apply(Summation, p1, p2), p3)
        result = pool.apply(Multiplication, result, self.x)
        self.diagram = Diagram(pool, result)

    def test_evaluation(self):
        self.assertEqual(4, self.diagram.evaluate({"x": 4}))

    def test_multiplication(self):
        pool = Pool()
        pool.int_var("x")
        two = pool.terminal("2")
        x = pool.terminal("x")

        test1 = pool.bool_test(LinearTest("x", ">="))
        test2 = pool.apply(Multiplication, pool.bool_test(LinearTest("x - 5", "<=")), x)

        product = pool.apply(Multiplication, test1, test2)
        result = Diagram(pool, pool.apply(Multiplication, product, two))

        for i in range(0, 10):
            evaluated = result.evaluate({"x": i})
            if 0 <= i <= 5:
                self.assertEqual(2 * i, evaluated)
            else:
                self.assertEqual(0, evaluated)

    def test_not(self):
        pool = Pool()
        pool.int_var("x")
        dd_true = Diagram(pool, pool.bool_test(LinearTest("x", ">=")))
        dd_false = Diagram(pool, pool.invert(dd_true.root_node.node_id))

        for i in range(-5, 6):
            assignment = {"x": i}
            self.assertEqual((dd_true.evaluate(assignment) + 1) % 2, dd_false.evaluate(assignment))

    def test_construct(self):
        self.check_diagram(self.diagram, self.diagram.pool.zero_id, self.x)

    def test_summation(self):
        result = self.diagram + self.diagram
        self.check_diagram(result, result.pool.zero_id, result.pool.terminal("2*x"))

    def check_diagram(self, diagram, zero_term, x_term):
        pool = diagram.pool
        layers = WalkingProfile.extract_layers(diagram, ParentsWalker(diagram).walk())
        self.assertEqual(5, len(layers))

        self.assertEqual(1, len(layers[0]), layers[0])
        self.assertEqual(pool.get_node(self.test1).test, pool.get_node(layers[0][0]).test, layers[0])

        self.assertEqual(1, len(layers[1]), layers[1])
        self.assertEqual(pool.get_node(self.test2).test, pool.get_node(layers[1][0]).test, layers[1])

        self.assertEqual(1, len(layers[2]), layers[2])
        self.assertEqual(pool.get_node(self.test3).test, pool.get_node(layers[2][0]).test, layers[2])

        self.assertEqual(1, len(layers[3]), layers[3])
        self.assertEqual(pool.get_node(self.test4).test, pool.get_node(layers[3][0]).test, layers[3])

        self.assertEqual(2, len(layers[4]))
        self.assertTrue(zero_term in layers[4], layers[4])
        self.assertTrue(x_term in layers[4], layers[4])

    def test_printing(self):
        import json
        encoded = Pool.to_json(self.diagram.pool)
        representation = json.loads(encoded)
        reconstructed = Pool.from_json(encoded)
        re_encoded = Pool.to_json(reconstructed)
        new_representation = json.loads(re_encoded)
        self.assertEquals(representation, new_representation)

    def test_inversion(self):
        pool = Pool()
        build = Builder(pool)
        build.vars("bool", "a", "b")
        build.vars("int", "x")

        test1 = build.test("a")
        test2 = build.test("b")
        test3 = build.test("x", "<=", 5)

        node3 = build.ite(test3, 1, 0)
        diagram = build.ite(test1, build.ite(test2, node3, 1), node3)

        self.assertTrue(is_ordered(diagram))

        def inversion1(root_id):
            minus_one = pool.terminal("-1")
            return pool.apply(Multiplication, pool.apply(Summation, root_id, minus_one), minus_one)

        def transform(terminal_node, d):
            if terminal_node.expression == 1:
                return d.pool.zero_id
            elif terminal_node.expression == 0:
                return d.pool.one_id
            else:
                raise RuntimeError("Could not invert value {}".format(terminal_node.expression))

        def inversion2(root_id):
            to_invert = pool.diagram(root_id)
            profile = WalkingProfile(diagram)
            return leaf_transform.transform_leaves(transform, to_invert)

        iterations = 1000
        timer = Timer(precision=6)
        timer.start("Legacy inversion")
        for _ in range(iterations):
            inversion1(diagram.root_id)
        time_legacy = timer.stop()

        inverted1 = pool.diagram(inversion1(diagram.root_id))

        timer.start("New inversion")
        for _ in range(iterations):
            inversion2(diagram.root_id)
        time_new = timer.stop()

        inverted2 = pool.diagram(inversion2(diagram.root_id))

        for a in [True, False]:
            for b in [True, False]:
                for x in range(10):
                    assignment = {"a": a, "b": b, "x": x}
                    self.assertNotEqual(diagram.evaluate(assignment), inverted1.evaluate(assignment))
                    self.assertNotEqual(diagram.evaluate(assignment), inverted2.evaluate(assignment))

        self.assertTrue(time_legacy > time_new, "New inversion ({}) not faster than legacy implementation ({})"
                        .format(time_new, time_legacy))

    def test_invert_terminal(self):
        pool = Pool()
        self.assertEquals(pool.zero_id, pool.invert(pool.one_id))
        self.assertEquals(pool.one_id, pool.invert(pool.zero_id))
        try:
            pool.invert(pool.terminal(2))
            self.assertTrue(False)
        except RuntimeError:
            self.assertTrue(True)