def max_cycle_ratio(g, estimate=None): maxratio, arg_cycle = None, None for scc in nx.strongly_connected_component_subgraphs(g): root = next(iter(scc.nodes())) scc_mcr, cycle = compute_mcr_component(scc, root, estimate) if scc_mcr is None: continue if maxratio is None or scc_mcr > maxratio: maxratio = scc_mcr arg_cycle = cycle forest = Forest() for scc in nx.strongly_connected_component_subgraphs(g, False): if scc.number_of_edges() == 0: continue for (v, w, scc_data) in scc.edges(data=True): data = g.get_edge_data(v, w) # negate weight so that we can construct a longest paths tree for the current solution scc_data['w'] = data.get('weight', 0) - data.get('tokens', 0) * maxratio root = w parents, _ = longest_distances(scc, root, 'w') for child in parents: in_edge = parents.get(child) if in_edge is not None: forest.add_edge(*in_edge) return maxratio, arg_cycle, forest
def test_multi_children(self): tree = Forest() tree.add_edge(1, 2) tree.add_edge(1, 3) self.assertEqual(tree.parent(2), (1, 2)) self.assertEqual(tree.parent(3), (1, 3)) self.assertListEqual(list(tree.pre_order(1)), [1, 2, 3]) tree.add_edge(1, 4) self.assertEqual(tree.parent(4), (1, 4)) self.assertListEqual(list(tree.pre_order(1)), [1, 2, 3, 4])
def compute_mcr_component(g, root, estimate=None): ''' Computes the maximum cycle ratio of g. NOTES: - The weight on each edge must be non-negative - The number of tokens on each edge must be non-negative. - The graph is assumed to be strongly connected. ''' # initialize: distances = {} queue = PriorityQueue() if estimate is None: # determine lower bound for mcr estimate = 1 for (v, w, data) in g.edges(data=True): tokens = data.get('tokens', 0) weight = data.get('weight', 0) estimate = estimate + max(0, weight) initial_graph = nx.MultiDiGraph() # construct graph with non-parametric path weights for v in g: initial_graph.add_node(v) for v, w, key, data in nx.MultiDiGraph(g).edges(keys=True, data=True): tokens = data.get('tokens', 0) weight = data.get('weight', 0) initial_graph.add_edge(v, w, key, weight=weight - tokens * estimate, dist=pdistance(weight, tokens)) try: parents, _ = longest_distances(initial_graph, root) # build tree from parents tree = Forest() for child in parents: in_edge = parents.get(child) if in_edge is not None: tree.add_edge(*in_edge) distances[root] = pdistance(0, 0) if root in tree: for v, w, key in tree.pre_order_edges(root): dv = distances[v] data = initial_graph.get_edge_data(v, w, key) distances[w] = dv + data.get('dist') except PositiveCycle as ex: raise InfeasibleException(ex.cycle) # fill priority queue: # go over all nodes and compute their key # print("Distances from root {}: {}".format(root, distances)) for v in distances: update_node_key(initial_graph, v, distances, queue) # pivot until cycle is found while len(queue) > 0: (node, (ratio, (v, w, vw_key))) = queue.pop() delta = distances[v] + initial_graph.get_edge_data( v, w, vw_key)['dist'] - distances[w] for j in tree.pre_order(w): # update parametric distance to j distances[j] += delta if v == j: # j is reachable from v -> there's a cycle! is_multi = g.is_multigraph() path = deque([(v, w, vw_key) if is_multi else (v, w)]) p = v while p != w: k, _, key = tree.parent(p) path.appendleft((k, p, key) if is_multi else (k, p)) p = k return -ratio, list(path) # update successors of j; the node key of a successor k can only increase! for _, k, jk_key, data in initial_graph.out_edges(j, keys=True, data=True): # update priority of (j, k) ratio_k = None if k in queue: ratio_k, _ = queue[k] delta_k = distances[j] + data['dist'] - distances[k] if delta_k[1] > 0: r = -Fraction(delta_k[0], delta_k[1]) if ratio_k is None or r < ratio_k: queue[k] = (r, (j, k, jk_key)) # recompute vertex key of j update_node_key(initial_graph, j, distances, queue) tree.add_edge(v, w, vw_key) else: # no cycle found, any period is admissible # Note that this implies that the graph is acyclic return None, None
def test_two_trees_merged(self): forest = Forest() forest.add_edge(1, 2) forest.add_edge(2, 3) forest.add_edge(2, 4) forest.add_edge(1, 5) forest.add_edge(5, 6) forest.add_edge(5, 7) forest.add_edge(7, 8) forest.add_edge(7, 9) forest.add_edge(10, 11) forest.add_edge(10, 12) forest.add_edge(12, 13) # merge the two trees together into a single tree forest.add_edge(9, 10) self.assertSetEqual(set(forest.roots()), {1}) self.assertListEqual(list(forest.pre_order()), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])
def test_two_trees(self): forest = Forest() forest.add_edge(1, 2) forest.add_edge(2, 3) forest.add_edge(2, 4) forest.add_edge(1, 5) forest.add_edge(5, 6) forest.add_edge(5, 7) forest.add_edge(7, 8) forest.add_edge(7, 9) self.assertListEqual(list(forest.pre_order(1)), [1, 2, 3, 4, 5, 6, 7, 8, 9]) forest.add_edge(10, 11) forest.add_edge(10, 12) forest.add_edge(12, 13) self.assertListEqual(list(forest.pre_order(10)), [10, 11, 12, 13]) self.assertSetEqual(set(forest.roots()), {1, 10}) po = sum([list(forest.pre_order(r)) for r in forest.roots()], []) self.assertListEqual(list(forest.pre_order()), po)
def test_single_edge(self): tree = Forest() tree.add_edge(1, 2, 3, dict(a=1)) self.assertListEqual(list(tree.pre_order(1)), [1, 2]) self.assertEqual(tree.parent(2), (1, 2, 3, dict(a=1)))
def test_swap_subtree(self): tree = Forest() tree.add_edge(1, 2) tree.add_edge(2, 3) tree.add_edge(2, 4) tree.add_edge(1, 5) tree.add_edge(5, 6) tree.add_edge(5, 7) tree.add_edge(7, 8) tree.add_edge(7, 9) self.assertListEqual(list(tree.pre_order(1)), [1, 2, 3, 4, 5, 6, 7, 8, 9]) tree.add_edge(2, 7) self.assertEqual(tree.parent(7), (2, 7)) self.assertListEqual(list(tree.pre_order(1)), [1, 2, 3, 4, 7, 8, 9, 5, 6])
def test_swap_edge(self): tree = Forest() tree.add_edge(1, 2) tree.add_edge(2, 3) tree.add_edge(2, 4) tree.add_edge(1, 5) tree.add_edge(5, 6) tree.add_edge(5, 7) self.assertListEqual(list(tree.pre_order(1)), [1, 2, 3, 4, 5, 6, 7]) tree.add_edge(6, 4) self.assertEqual(tree.parent(4), (6, 4)) self.assertListEqual(list(tree.pre_order(2)), [2, 3]) self.assertListEqual(list(tree.pre_order(5)), [5, 6, 4, 7]) self.assertListEqual(list(tree.pre_order(1)), [1, 2, 3, 5, 6, 4, 7])