def power_iteration(matrix_a, variables=None, initial_vector=None, iterations=100, delta=10**-3): """ Computes the pagerank of a matrix. By convention, the variables in the matrix are prefixed with r_ if they are used as row variables and c_ if they are used as column variables. :param Matrix matrix_a: The square, stochastic adjacency matrix :param variables: The variables of the matrix (if None it is inferred from the matrix) :param damping_factor: The damping factor used to prevent problems due to dangling or disconnected nodes :param Matrix initial_vector: The initial column vector (if None it is initialized with 1/N, where N is the size of the matrix) :param iterations: The maximal number of iterations before the algorithm stops :param delta: The threshold when change is considered negligible :return: The page rank vector """ build = Builder(matrix_a.diagram.pool) if variables is None: raise RuntimeError("Variables computation not yet implemented.") names = [t[0] for t in variables] if initial_vector is None: initial_diagram = build.exp(1.0 / matrix_a.height) for name, lb, ub in variables: initial_diagram = initial_diagram * build.limit( "r_" + name, lb, ub) row_vars = [("r_" + name, lb, ub) for name, lb, ub in variables] initial_vector = Matrix(initial_diagram, row_vars, []) initial_vector = initial_vector.rename({"r_" + var: var for var in names}) previous_vector = None new_vector = initial_vector matrix_a = matrix_a.rename({"c_" + var: var for var in names}) for i in range(iterations): # Check for convergence if previous_vector is not None: # Calculate difference vector difference_vector = new_vector - previous_vector # Compare norm of difference with given delta if difference_vector.norm() < delta: return new_vector, i # Save previous vector previous_vector = new_vector # Compute next iteration new_vector = matrix_a * new_vector # Rename column variables to row variables new_vector = new_vector.rename({"r_" + var: var for var in names}).reduce() return new_vector, iterations
def dampen(matrix_a, damping_factor, reduce_result=True): """ :param Matrix matrix_a: The matrix to dampen :param float damping_factor: The dampening factor :param reduce_result: True if the dampened matrix should be reduced (default: True) :return Matrix: The dampened matrix """ build = Builder(matrix_a.diagram.pool) if damping_factor < 0 or damping_factor > 1: raise RuntimeError("Damping factor has to be in the interval [0, 1].") if damping_factor < 1: ones = build.exp(1.0 / matrix_a.height) for name, lb, ub in matrix_a.row_variables: ones = ones * build.limit(name, lb, ub) for name, lb, ub in matrix_a.column_variables: ones = ones * build.limit(name, lb, ub) matrix_ones = Matrix(ones, matrix_a.row_variables, matrix_a.column_variables) updated_a = damping_factor * matrix_a + (1 - damping_factor) * matrix_ones if reduce_result: assert isinstance(updated_a, Matrix) updated_a = updated_a.reduce() return updated_a else: return matrix_a
def build_diagram_2(): b = Builder(Pool()) b.ints("r", "c") lb, ub = 1, 10 bounds = b.limit("r", lb, ub) & b.limit("c", lb, ub) t1 = b.ite(b.test("r", "<=", 0), 2, 4) t2 = b.ite(b.test("c", ">=", 11), 3, 5) return bounds * (t1 + t2)
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))
class TestTransform(unittest.TestCase): def setUp(self): pool = Pool() self.builder = Builder(pool) self.builder.ints("x", "y") b = self.builder limits = b.limit("x", 0, 20) & b.limit("y", 0, 20) rect1 = b.limit("x", 0, 10) & b.limit("y", 0, 10) rect2 = b.limit("x", 5, 20) & b.limit("y", 10, 20) self.diagram = rect1 * b.terminal("5") \ + rect2 * b.terminal("x + 2") \ + ~(rect1 | rect2) & limits * b.terminal("1") self.exporter = Exporter( os.path.join(os.path.dirname(os.path.realpath(__file__)), "visual"), "transform") def test_constant(self): b = self.builder pool = b.pool test_d = b.limit("x", 1, 1) * b.exp(5) + b.limit("x", 2, 2) * b.exp(5) test_d = pool.diagram( LinearReduction(pool).reduce(test_d.root_node.node_id)) self.exporter.export(test_d, "test_d") # Test with profile add_profile_cache(pool) get_profile(self.diagram) self.exporter.export(self.diagram, "to_constant") constant = to_constant(self.diagram) get_profile(constant) self.exporter.export(constant, "constant") def t(_, node): self.assertTrue(is_constant(node), msg="{} still contains variables".format( node.expression)) walk_leaves(t, constant) def test_ground_tensor(self): bounds = [("x", 0, 20), ("y", 0, 20)] tensor = to_ground_tensor(self.diagram, bounds) size = 1 for s in [ub - lb + 1 for _, lb, ub in bounds]: size *= s self.assertEqual(len(tensor.flatten()), size) for x in range(0, 21): for y in range(0, 21): self.assertEqual(self.diagram.evaluate({ "x": x, "y": y }), tensor[(x, y)])
def construct_diagram(): pool = Pool() pool.int_var("x", "y") b = Builder(pool) bounds = b.test("x", ">=", 0) & b.test("x", "<=", 8) & b.test("y", ">=", 1) & b.test("y", "<=", 10) return bounds * b.ite(b.test("x", ">=", "y"), b.terminal("2*x + 3*y"), b.terminal("3*x + 2*y"))
def setUp(self): pool = Pool() pool.int_var("x") pool.int_var("y") pool.int_var("z") b = Builder(pool) self.diagrams = [] d = b.ite(b.test("x", "<=", "y"), b.terminal(1), b.test("x", "<=", 2)) self.diagrams.append(({"x", "y"}, d)) d = b.terminal("x * 2 * y + 5 * z") self.diagrams.append(({"x", "y", "z"}, d)) d = b.ite(b.test("x", "<", "y"), b.terminal("z"), b.terminal("z * y")) self.diagrams.append(({"x", "y", "z"}, d))
def test_summation_two_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) bounds &= b.test("y", ">=", 0) & b.test("y", "<=", 1) d = b.ite(bounds, b.terminal("x"), b.terminal(0)) d_const = Diagram(pool, SummationWalker(d, "x").walk()) for y in range(2): self.assertEqual(55, d_const.evaluate({"y": y}))
class TestTransform(unittest.TestCase): def setUp(self): pool = Pool() self.builder = Builder(pool) self.builder.ints("x", "y") b = self.builder limits = b.limit("x", 0, 20) & b.limit("y", 0, 20) rect1 = b.limit("x", 0, 10) & b.limit("y", 0, 10) rect2 = b.limit("x", 5, 20) & b.limit("y", 10, 20) self.diagram = rect1 * b.terminal("5") \ + rect2 * b.terminal("x + 2") \ + ~(rect1 | rect2) & limits * b.terminal("1") self.exporter = Exporter(os.path.join(os.path.dirname(os.path.realpath(__file__)), "visual"), "transform") def test_constant(self): b = self.builder pool = b.pool test_d = b.limit("x", 1, 1) * b.exp(5) + b.limit("x", 2, 2) * b.exp(5) test_d = pool.diagram(LinearReduction(pool).reduce(test_d.root_node.node_id)) self.exporter.export(test_d, "test_d") # Test with profile add_profile_cache(pool) get_profile(self.diagram) self.exporter.export(self.diagram, "to_constant") constant = to_constant(self.diagram) get_profile(constant) self.exporter.export(constant, "constant") def t(_, node): self.assertTrue(is_constant(node), msg="{} still contains variables".format(node.expression)) walk_leaves(t, constant) def test_ground_tensor(self): bounds = [("x", 0, 20), ("y", 0, 20)] tensor = to_ground_tensor(self.diagram, bounds) size = 1 for s in [ub - lb + 1 for _, lb, ub in bounds]: size *= s self.assertEqual(len(tensor.flatten()), size) for x in range(0, 21): for y in range(0, 21): self.assertEqual(self.diagram.evaluate({"x": x, "y": y}), tensor[(x, y)])
def setUp(self): pool = Pool() self.builder = Builder(pool) self.builder.ints("x", "y") b = self.builder limits = b.limit("x", 0, 20) & b.limit("y", 0, 20) rect1 = b.limit("x", 0, 10) & b.limit("y", 0, 10) rect2 = b.limit("x", 5, 20) & b.limit("y", 10, 20) self.diagram = rect1 * b.terminal("5") \ + rect2 * b.terminal("x + 2") \ + ~(rect1 | rect2) & limits * b.terminal("1") self.exporter = Exporter( os.path.join(os.path.dirname(os.path.realpath(__file__)), "visual"), "transform")
def build_diagram2(): build = Builder() build.vars("int", "x", "y") test1 = build.test("x", "<=", "y") test2 = build.test("y", "<=", 5) test3 = build.test("x", ">=", 5) exp1 = build.exp(5) exp2 = build.exp("2 * x") node3 = build.ite(test3, exp1, exp2) node2 = build.ite(test2, node3, exp1) node1 = build.ite(test1, node2, node3) if not is_ordered(node1): raise RuntimeError("Diagram not ordered") return node1
def setUp(self): pool = Pool() self.builder = Builder(pool) self.builder.ints("x", "y") b = self.builder limits = b.limit("x", 0, 20) & b.limit("y", 0, 20) rect1 = b.limit("x", 0, 10) & b.limit("y", 0, 10) rect2 = b.limit("x", 5, 20) & b.limit("y", 10, 20) self.diagram = rect1 * b.terminal("5") \ + rect2 * b.terminal("x + 2") \ + ~(rect1 | rect2) & limits * b.terminal("1") self.exporter = Exporter(os.path.join(os.path.dirname(os.path.realpath(__file__)), "visual"), "transform")
def to_constant(diagram): b = Builder(diagram.pool) from pyxadd.variables import variables all_variables = variables(diagram) print("Variables: {}".format(all_variables)) bounds = [(v, ) + get_bounds(v, diagram) for v in all_variables] # Approach 1: # Per path, find all included points, merge based on expression (constant all, one var => test only on that, ...) # Approach 2: # Ground to matrix, learn structure return ConstantWalker(diagram, all_variables).walk()
def build_example1(): # http://www.math.cornell.edu/~mec/Winter2009/RalucaRemus/Lecture3/lecture3.html pool = Pool() build = Builder(pool) variables = [("i", 1, 4)] for var in variables: name = var[0] build.ints("r_{}".format(name), "c_{}".format(name), name) limits = build.limit("r_i", 1, 4) & build.limit("c_i", 1, 4) column1 = (build.limit("c_i", 1, 1) & build.limit("r_i", 2, 4)) * build.exp("1/3") column2 = (build.limit("c_i", 2, 2) & build.limit("r_i", 3, 4)) * build.exp("1/2") column3 = (build.limit("c_i", 3, 3) & build.limit("r_i", 1, 1)) * build.exp("1") column4 = (build.limit("c_i", 4, 4) & (build.limit("r_i", 1, 1) | build.limit("r_i", 3, 3))) * build.exp("1/2") diagram = limits * (column1 + column2 + column3 + column4) return diagram, variables
def build_diagram1(): pool = Pool() b = Builder(pool) b.ints("x") return b.ite(b.test("x", "<=", 3), b.ite(b.test("x", "<=", 2), b.exp(1), b.exp("2*x")), b.exp(1))
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))
import itertools from png import Writer from pyxadd.build import Builder from pyxadd.diagram import Pool, Diagram from pyxadd.matrix.matrix import assignments from pyxadd.reduce import SmtReduce, LinearReduction from pyxadd.view import export pool = Pool() pool.add_var("x", "int") pool.add_var("xs", "int") pool.add_var("y", "int") pool.add_var("ys", "int") b = Builder(pool) # TODO needs control over interleaving class RedGreenBlueDiagrams(object): def __init__(self, red, green, blue): self.diagrams = (red, green, blue) @staticmethod def all(diagram): return RedGreenBlueDiagrams(diagram, diagram, diagram) def _binary(self, op, other): if isinstance(other, RedGreenBlueDiagrams): return RedGreenBlueDiagrams(*(op(self.diagrams[i], other.diagrams[i]) for i in range(3)))
from pyxadd.build import Builder from pyxadd.diagram import Pool from pyxadd.view import export pool = Pool() b = Builder(pool) b.ints("r", "c") xadd_1 = b.terminal("5 + r * c") export(xadd_1, "visual/examples/xadd_1.dot") xadd_2 = b.limit("r", 1, 2) & b.limit("c", 1, 2) * xadd_1 export(xadd_2, "visual/examples/xadd_2.dot")
def setUp(self): pool = Pool() pool.int_var("x") pool.int_var("y") pool.int_var("z") b = Builder(pool) self.diagrams = [] d = b.ite(b.test("x", "<=", "y"), b.terminal(1), b.test("x", "<=", 2)) self.diagrams.append(({"x", "y"}, d)) d = b.terminal("x * 2 * y + 5 * z") self.diagrams.append(({"x", "y", "z"}, d)) d = b.ite(b.test("x", "<", "y"), b.terminal("z"), b.terminal("z * y")) self.diagrams.append(({"x", "y", "z"}, d))
def build_example1(): # http://www.math.cornell.edu/~mec/Winter2009/RalucaRemus/Lecture3/lecture3.html pool = Pool() build = Builder(pool) variables = [("i", 1, 4)] for var in variables: name = var[0] build.ints("r_{}".format(name), "c_{}".format(name), name) limits = build.limit("r_i", 1, 4) & build.limit("c_i", 1, 4) column1 = (build.limit("c_i", 1, 1) & build.limit("r_i", 2, 4)) * build.exp("1/3") column2 = (build.limit("c_i", 2, 2) & build.limit("r_i", 3, 4)) * build.exp("1/2") column3 = (build.limit("c_i", 3, 3) & build.limit("r_i", 1, 1)) * build.exp("1") column4 = (build.limit("c_i", 4, 4) & (build.limit("r_i", 1, 1) | build.limit("r_i", 3, 3))) * build.exp("1/2") diagram = limits * (column1 + column2 + column3 + column4) return diagram, variables
diff_to_b = b - A_times_x norm_difference = diff_to_b.norm() residual = float(norm_difference) / norm_b print("Residual is {} / {} = {}".format(norm_difference, norm_b, residual)) if residual < delta: print("Found solution: ", [x.evaluate({"r": i}) for i in range(lb, ub + 1)]) return x x = x + w * diff_to_b x.export("visual/richardson/x_full{}.dot".format(i)) x = x.reduce() x.export("visual/richardson/x{}.dot".format(i)) print("X: ", [x.evaluate({"r": i}) for i in range(lb, ub + 1)]) pool = Pool() build = Builder(pool) build.ints("r", "c") bounds = build.limit("r", lb, ub) & build.limit("c", lb, ub) A_d = bounds \ & build.test("r", "<=", "c") \ & build.test("r", ">=", "c") * build.exp(2) A_db = bounds & (build.test("r", ">", lb + (ub - lb) / 2) & build.test("c", "<=", lb + (ub - lb) / 2) | (build.test("r", "<=", lb + (ub - lb) / 2) & build.test("c", ">", lb + (ub - lb) / 2))) A = Matrix(A_d * build.terminal(3) + A_db, [("r", lb, ub)], [("c", lb, ub)], auto_reduce=False) # TODO WHAT IS THAT 7 A.print_ground() A = A.reduce()
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}))
import itertools from png import Writer from pyxadd.build import Builder from pyxadd.diagram import Pool, Diagram from pyxadd.matrix.matrix import assignments from pyxadd.reduce import SmtReduce, LinearReduction from pyxadd.view import export pool = Pool() pool.add_var("x", "int") pool.add_var("xs", "int") pool.add_var("y", "int") pool.add_var("ys", "int") b = Builder(pool) # TODO needs control over interleaving class RedGreenBlueDiagrams(object): def __init__(self, red, green, blue): self.diagrams = (red, green, blue) @staticmethod def all(diagram): return RedGreenBlueDiagrams(diagram, diagram, diagram) def _binary(self, op, other): if isinstance(other, RedGreenBlueDiagrams): return RedGreenBlueDiagrams(
def build_diagram_1(): b = Builder(Pool()) b.ints("r", "c") lb = 1 ub = 10 bounds = b.limit("r", lb, ub) & b.limit("c", lb, ub) diagonal = b.test("r", "<=", "c") & b.test("r", ">=", "c") block_1 = b.test("r", ">", lb + (ub - lb) / 2) & b.test("c", "<=", lb + (ub - lb) / 2) block_2 = b.test("r", "<=", lb + (ub - lb) / 2) & b.test("c", ">", lb + (ub - lb) / 2) return bounds * (diagonal * b.exp(6) + (block_1 | block_2))
def build_diagram2(): build = Builder() build.vars("int", "x", "y") test1 = build.test("x", "<=", "y") test2 = build.test("y", "<=", 5) test3 = build.test("x", ">=", 5) exp1 = build.exp(5) exp2 = build.exp("2 * x") node3 = build.ite(test3, exp1, exp2) node2 = build.ite(test2, node3, exp1) node1 = build.ite(test1, node2, node3) if not is_ordered(node1): raise RuntimeError("Diagram not ordered") return node1