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
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
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)
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_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({}))
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)
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
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)
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)
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}))
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 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)
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)
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))
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(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))
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 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")
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}))
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")
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 walk(self): return Diagram(self._diagram.pool, BottomUpWalker.walk(self))
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()
def _get_variables(self, node_id): return VariableFinder(Diagram(self.pool, node_id)).walk()
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)