def test_feasible( csdfg, bindings, target_mcr ): # compute pessimistic approximation pess = transform.single_rate_apx( csdfg, bindings = bindings ) # set path weights for u, v, data in pess.edges_iter( data = True ): tokens = data.get( __PARAM_TOKENS__, 0 ) weight = pess.node[ v ].get( __PARAM_WCET__ ) data['weight'] = tokens * target_mcr - weight # compute longest paths try: bfct.find_shortest_paths( pess, None ) return None except NegativeCycleException as ex: return ex.cycle
def compute_vertex_keys(graph, variable, q, parameter): root = next(graph.nodes_iter()) # construct shortest paths tree using token = parameter for v, w, data in graph.edges_iter(data=True): tokens = data.get(__param__tokens, 0) weight = data.get(__param__weight, 0) data['dist'] = pdistance(weight, tokens) data['w'] = tokens * parameter - weight distances = dict() try: tree, _, _ = bfct.find_shortest_paths(graph, root, 'w') distances[root] = pdistance(0, 0) for v, i in tree.dfs(root): if i == 0: dv = distances[v] for w in tree.children(v): distances[w] = dv + graph.get_edge_data(v, w).get('dist') except NegativeCycleException as ex: raise InfeasibleException(ex.cycle) # compute vertex keys for v in graph: ratio, edge = compute_vertex_key(graph, v, distances) q[(variable, v)] = (-ratio, edge) # clean up 'dist' and 'w' parameters for v, w, data in graph.edges_iter(data=True): del data['dist'] del data['w'] # return shortest paths tree that is valid for the parameter return tree
def compute_mcm(g, estimate=None, pweight='weight'): maxmean, arg_cycle = None, None for scc in nx.strongly_connected_component_subgraphs(g): root = next(scc.nodes_iter()) scc_mcm, cycle = compute_mcm_component(scc, root, estimate, pweight) if scc_mcm is None: continue if maxmean is None or scc_mcm > maxmean: maxmean = scc_mcm 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_iter(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'] = maxmean - data.get('weight', 0) root = w lpp_tree, _ = bfct.find_shortest_paths(scc, root, arg='w') forest.add_forest(lpp_tree) return maxmean, arg_cycle, forest
def test_feasible(csdfg, bindings, target_mcr): # compute pessimistic approximation pess = transform.single_rate_apx(csdfg, bindings=bindings) # set path weights for u, v, data in pess.edges_iter(data=True): tokens = data.get(__PARAM_TOKENS__, 0) weight = pess.node[v].get(__PARAM_WCET__) data['weight'] = tokens * target_mcr - weight # compute longest paths try: bfct.find_shortest_paths(pess, None) return None except NegativeCycleException as ex: return ex.cycle
def compute_mcm( g, estimate = None, pweight = 'weight' ): maxmean, arg_cycle = None, None for scc in nx.strongly_connected_component_subgraphs( g ): root = next( scc.nodes_iter() ) scc_mcm, cycle = compute_mcm_component( scc, root, estimate, pweight ) if scc_mcm is None: continue if maxmean is None or scc_mcm > maxmean: maxmean = scc_mcm 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_iter( 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'] = maxmean - data.get( 'weight', 0 ) root = w lpp_tree, _ = bfct.find_shortest_paths( scc, root, arg = 'w' ) forest.add_forest( lpp_tree ) return maxmean, arg_cycle, forest
def compute_vertex_keys( graph, variable, q, parameter ): root = next( graph.nodes_iter() ) # construct shortest paths tree using token = parameter for v, w, data in graph.edges_iter(data=True): tokens = data.get(__param__tokens, 0) weight = data.get(__param__weight, 0) data['dist'] = pdistance( weight, tokens ) data['w'] = tokens * parameter - weight distances = dict() try: tree, _, _ = bfct.find_shortest_paths(graph, root, 'w') distances[root] = pdistance(0, 0) for v, i in tree.dfs( root ): if i == 0: dv = distances[ v ] for w in tree.children( v ): distances[ w ] = dv + graph.get_edge_data(v, w).get('dist') except NegativeCycleException as ex: raise InfeasibleException( ex.cycle ) # compute vertex keys for v in graph: ratio, edge = compute_vertex_key( graph, v, distances ) q[(variable, v)] = ( -ratio, edge ) # clean up 'dist' and 'w' parameters for v, w, data in graph.edges_iter( data = True ): del data['dist'] del data['w'] # return shortest paths tree that is valid for the parameter return tree
def path_weight( graph, start, end ): # compute shortest paths _, dists, _ = bfct.find_shortest_paths( graph, start ) return dists.get( end, None )
def ensure_liveness(g, factor = 100): """ Assigns tokens to channels of an SDF graph such that a minimum throughput is attained. Works by finding cycles of positive weight in the graph's pessimistic approximation and resolving them by adding sufficient tokens. Parameters: ---------- g: the SDF graph factor: indicates the maximum number of time units a single graph iteration may take, multiplied by the length of a fully sequential iteration. Lower numbers result in higher throughputs """ assert g.is_consistent() # assert nx.is_strongly_connected( g ) seqperiod = 0 for v, data in g.nodes_iter( data = True ): wcet = data.get('wcet').sum() # periods = g.q[v] // data.get('phases') periods = 1 seqperiod += ( wcet * periods ) target_mcr = Fraction( seqperiod, g.tpi ) / factor added_tokens = 0 m = 4 * g.number_of_edges() while m > 0: m = m - 1 # transform the graph to its single-rate approximation pess = transform.single_rate_apx( g ) # create graph where each token represents a weight of -factor lpg = nx.DiGraph() for u, v, data in pess.edges( data = True ): wcet = pess.node[v].get('wcet') tokens = data.get('tokens', 0) lpg.add_edge( u, v, weight = tokens * target_mcr - wcet ) # find positive cycle if m == 0: import pdb; pdb.set_trace() try: bfct.find_shortest_paths( lpg, u ) break except NegativeCycleException as ex: # find edge with heaviest weight weight, tokens = 0, 0 max_weight = 0 for u, v in ex.cycle: weight += pess.node[ v ].get('wcet') tokens += pess.get_edge_data(u, v).get('tokens', 0) suv = g.s[ (u, v) ] if suv > max_weight: max_weight = suv arg_max = (u, v) delta = Fraction( Fraction(weight, target_mcr) - tokens, max_weight ).__ceil__() edge_data = g.get_edge_data( *arg_max ) edge_data['tokens'] = edge_data.get('tokens', 0) + delta #math.ceil(extra_factor * delta) added_tokens += delta # print("Added {} tokens".format( delta )) else: assert False, "too many iterations" return added_tokens
def test_random(n=50, p=0.1, runs=1000, debug=None): for run in range(runs) if debug is None else [debug]: g = fast_gnp_random_graph(n, p, seed=run + 1, directed=True) # add source connected to all nodes source = 100 * (n // 100) + 200 for v in list(g.nodes()): g.add_edge(source, v, weight=0, tokens=0) # add random weights and tokens wsum = 1 for _, _, data in g.edges_iter(data=True): data['weight'] = randrange(1, 10) data['tokens'] = randrange(-1, 8) wsum += data['weight'] # create shortest path formulation for initial tree for _, _, data in g.edges_iter(data=True): data['sp'] = data['tokens'] * wsum - data['weight'] # ensure that the graph admits a feasible solution its = 0 while True: try: its += 1 tree, distances = bfct.find_shortest_paths(g, source, arg='sp') break except NegativeCycleException as ex: toks = sum( map(lambda vw: g.get_edge_data(*vw).get('tokens'), ex.cycle)) edge_data = g.get_edge_data(*choice(ex.cycle)) edge_data['tokens'] += (1 - toks) edge_data[ 'sp'] = edge_data['tokens'] * wsum - edge_data['weight'] if debug is not None: import pdb pdb.set_trace() negative_toks = False for _, _, data in g.edges_iter(data=True): if data['tokens'] < 0: negative_toks = True break print("Run {}: negative tokens: {}, iterations: {}".format( run, negative_toks, its)) ratio, cycle = compute_mcr(g, source) assert ratio is not None, "Deadlocked cycle found" if not cycle: # verify that the graph is acyclic assert is_directed_acyclic_graph( g), "[run = {}] Graph is not acyclic".format(run) else: wsum, tsum = 0, 0 for v, w in cycle: data = g.get_edge_data(v, w) wsum += data['weight'] tsum += data['tokens'] assert Fraction( wsum, tsum ) == ratio, "[run = {}] computed MCR {} does not match ratio of critical cycle {}".format( run, ratio, Fraction(wsum, tsum)) for v, w, data in g.edges_iter(data=True): data['weight'] = data['tokens'] * ratio - data['weight'] try: bellman_ford(g, source) except Exception: print("Exception during run {}".format(run))
def compute_mcm_component(g, root, estimate=None, pweight='weight'): """ Computes the maximum cycle mean 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 = pq.priority_queue() # determine lower bound for mcr init_mcr = 1 for (v, w, data) in g.edges_iter(data=True): weight = data.get(pweight, 0) init_mcr = init_mcr + max(0, weight) data['dist'] = pdistance(weight, 1) if estimate is not None: # compute initial tree by computing shortest paths tree # print("Computing initial tree from estimate {}".format(estimate)) for (v, w, data) in g.edges_iter(data=True): dist = data['dist'] data['w'] = estimate - dist[0] try: tree, _ = bfct.find_shortest_paths(g, root, arg='w') distances[root] = pdistance(0, 0) for v, i in tree.dfs(root): if i == 0: dv = distances[v] for w in tree.children(v): distances[w] = dv + g.get_edge_data(v, w).get('dist') except NegativeCycleException as ex: # print("Infeasible solution for estimate {}, due to cycle {}".format(estimate, [(u, v, data) for u, v, data in g.edges_iter(data = True) if (u, v) in ex.cycle])) raise InfeasibleException(ex.cycle) else: # create tree rooted at root tree = DFSTree(root) # run Dijkstra on tokens distances[root] = 0 queue[root] = 0 while len(queue) > 0: v, _ = queue.pop() dv = distances[v] for _, w, data in g.out_edges_iter(v, True): dw = distances.get(w, None) if (dw is None) or dv + 1 < dw: distances[w] = dv + 1 queue[w] = distances[w] tree.append_child(v, w) # DFS on shortest paths DAG # (this is the DAG induced by the union of all shortest path trees) pre, post, index = dict(), dict(), 0 post_order = list() for v, i in tree.dfs(root): if i == 0: pre[v] = index index += 1 dv = distances[v] for _, w, data in g.out_edges_iter(v, True): dw = distances[w] if dv + 1 == dw and w not in pre: tree.append_child(v, w) else: post[v] = index index += 1 post_order.append(v) # scan in topological order distances.clear() distances[root] = pdistance(0, 0) while post_order: v = post_order.pop() dv = distances[v] for _, w, data in g.out_edges_iter(v, True): dist = data['dist'] dw = distances.get(w, None) newdist = dv + data['dist'] if dw is None or newdist[1] < dw[1]: distances[w] = newdist tree.append_child(v, w) elif (newdist[1] == dw[1] and newdist[0] - dw[0] > 0): # can't have back edges assert post[w] < post[v] distances[w] = newdist tree.append_child(v, w) def compute_node_key(node): maxratio, argmax = None, None # go over all incoming edges of the node for u, v, data in g.in_edges_iter(node, data=True): if u in distances: delta = distances[u] + data['dist'] - distances[v] # print("Delta for {} = {}".format((u, v), delta)) if delta[1] > 0: ratio = Fraction(delta[0], delta[1]) if argmax is None or ratio > maxratio: maxratio = ratio argmax = (u, v) # store the node key for v if argmax is not None: queue[node] = (-maxratio, argmax) elif node in queue: del queue[node] return maxratio # fill priority queue: # go over all nodes and compute their key # print("Distances from root {}: {}".format(root, distances)) for v in distances: compute_node_key(v) # pivot until cycle is found path_changes = 0 while len(queue) > 0: (node, (ratio, (v, w))) = queue.pop() delta = distances[v] + g.get_edge_data(v, w)['dist'] - distances[w] for j in tree.pre_order(w): distances[j] += delta if v == j: cycle = list() if v != w: child = j for p in tree.ancestors(child): cycle.append((p, j)) if p == w: break j = p cycle.reverse() cycle.append((v, w)) assert __is_cycle( cycle ), "Found wrong cycle after pivoting with back edge ({}, {})".format( v, w) return -ratio, cycle for (_, k, data) in g.out_edges_iter(j, True): # update priority of (j, k) ratio_k = None if k in queue: (ratio_k, edge) = 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)) # recompute vertex key of j compute_node_key(j) tree.append_child(v, w) else: # no cycle found, any period is admissible # Note that this implies that the graph is acyclic return None, None
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 = pq.priority_queue() # print("Computing MCR for graph {}".format( g.edges( data = True ))) # determine lower bound for mcr # while doing so, determine vertices with outgoing tokenless init_mcr = 1 negative_tokens_or_weights = False for (v, w, data) in g.edges_iter(data=True): tokens = data.get('tokens', 0) weight = data.get('weight', 0) init_mcr = init_mcr + max(0, weight) data['dist'] = pdistance(weight, tokens) if tokens < 0 or weight < 0: negative_tokens_or_weights = True if estimate is not None: # compute initial tree by computing shortest paths tree # print("Computing initial tree from estimate {}".format(estimate)) for (v, w, data) in g.edges_iter(data=True): dist = data['dist'] data['w'] = dist[1] * estimate - dist[0] try: tree, _ = bfct.find_shortest_paths(g, root, arg='w') distances[root] = pdistance(0, 0) for v, i in tree.dfs(root): if i == 0: dv = distances[v] for w in tree.children(v): distances[w] = dv + g.get_edge_data(v, w).get('dist') except NegativeCycleException as ex: # print("Infeasible solution for estimate {}, due to cycle {}".format(estimate, [(u, v, data) for u, v, data in g.edges_iter(data = True) if (u, v) in ex.cycle])) raise InfeasibleException(ex.cycle) elif True: # print("Computing initial tree from estimate {}".format(init_mcr)) # compute initial tree by computing shortest paths tree for (v, w, data) in g.edges_iter(data=True): dist = data['dist'] data['w'] = dist[1] * init_mcr - dist[0] try: tree, _ = bfct.find_shortest_paths(g, root, arg='w') distances[root] = pdistance(0, 0) for v, i in tree.dfs(root): if i == 0: dv = distances[v] for w in tree.children(v): distances[w] = dv + g.get_edge_data(v, w).get('dist') if False: # verify distances for v in distances: distv = distances[v] distv = distv[0] - distv[1] * init_mcr for _, w, data in g.out_edges_iter(v, True): distw = distances[w] distw = distw[0] - distw[1] * init_mcr edge_dist = data['dist'] edge_dist = edge_dist[0] - edge_dist[1] * init_mcr assert distv + edge_dist <= distw except NegativeCycleException as ex: # print("Infeasible solution for estimate {}, due to cycle {}".format(init_mcr, [(u, v, data) for u, v, data in g.edges_iter(data = True) if (u, v) in ex.cycle])) raise InfeasibleException(ex.cycle) else: # create tree rooted at root tree = DFSTree(root) # run Dijkstra on tokens distances[root] = 0 queue[root] = 0 while len(queue) > 0: v, _ = queue.pop() dv = distances[v] for _, w, data in g.out_edges_iter(v, True): dist = data['dist'] dw = distances.get(w, dv + dist[1] + 1) if dv + dist[1] - dw < 0: distances[w] = dv + dist[1] queue[w] = distances[w] tree.append_child(v, w) # DFS on shortest paths DAG # (this is the DAG induced by the union of all shortest path trees) pre, post, index = dict(), dict(), 0 post_order = list() for v, i in tree.dfs(root): if i == 0: pre[v] = index index += 1 dv = distances[v] for _, w, data in g.out_edges_iter(v, True): tokens = data['dist'][1] dw = distances[w] if dv + tokens == dw and w not in pre: tree.append_child(v, w) else: post[v] = index index += 1 post_order.append(v) # scan in topological order distances.clear() distances[root] = pdistance(0, 0) while post_order: v = post_order.pop() dv = distances[v] for _, w, data in g.out_edges_iter(v, True): dist = data['dist'] dw = distances.get(w, None) newdist = dv + data['dist'] if dw is None or newdist[1] < dw[1]: distances[w] = newdist tree.append_child(v, w) elif (newdist[1] == dw[1] and newdist[0] - dw[0] > 0): # check that (v, w) is not a back edge! if post[w] >= post[v]: # back edge, MCR not defined prev = v cycle = list() if v != w: for p in tree.ancestors(v): cycle.append((p, prev)) if p == w: break prev = p cycle.reverse() cycle.append((v, w)) assert __is_cycle(cycle), "Not a cycle: {}".format( cycle) # print(cycle) # print("Positive cycle found: {}".format( cycle )) raise InfeasibleException(cycle) distances[w] = newdist tree.append_child(v, w) def compute_node_key(node): maxratio, argmax = None, None # go over all incoming edges of the node for (u, v, data) in g.in_edges_iter(nbunch=[node], data=True): if u in distances: delta = distances[u] + data['dist'] - distances[v] # print("Delta for {} = {}".format((u, v), delta)) if delta[1] > 0: ratio = Fraction(delta[0], delta[1]) if argmax is None or ratio > maxratio: maxratio = ratio argmax = (u, v) # store the node key for v if argmax is not None: queue[node] = (-maxratio, argmax) # print("Vertex key for {} = {}, attained by edge {}".format(node, maxratio, argmax)) elif node in queue: del queue[node] return maxratio # fill priority queue: # go over all nodes and compute their key # print("Distances from root {}: {}".format(root, distances)) for v in distances: compute_node_key(v) # pivot until cycle is found path_changes = 0 pivots = 0 while len(queue) > 0: (node, (ratio, (v, w))) = queue.pop() pivots += 1 delta = distances[v] + g.get_edge_data(v, w)['dist'] - distances[w] # print("Pivoting with edge ({}, {}), delta = {}".format(v, w, delta)) for j in tree.pre_order(w): # update parametric distance to j distances[j] += delta path_changes += 1 if v == j: cycle = list() if v != w: child = j for p in tree.ancestors(child): cycle.append((p, j)) if p == w: break j = p cycle.reverse() cycle.append((v, w)) assert __is_cycle( cycle ), "Found wrong cycle after pivoting with back edge ({}, {})".format( v, w) return -ratio, cycle for (_, k, data) in g.out_edges_iter(j, True): # update priority of (j, k) ratio_k = None if k in queue: (ratio_k, edge) = queue[k] delta_k = distances[j] + data['dist'] - distances[k] # commented out line below to allow a negative MCR 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)) # print("New arc key for ({}, {}) = {}, delta = {} + {} - {} = {}".format(j, k, -r, distances[j], data['dist'], distances[k], delta_k)) # recompute vertex key of j compute_node_key(j) tree.append_child(v, w) else: # no cycle found, any period is admissible # Note that this implies that the graph is acyclic return None, None
def path_weight(graph, start, end): # compute shortest paths _, dists, _ = bfct.find_shortest_paths(graph, start) return dists.get(end, None)
def test_random(n = 50, p = 0.1, runs = 1000, debug = None): for run in range(runs) if debug is None else [debug]: g = fast_gnp_random_graph(n, p, seed = run + 1, directed = True) # add source connected to all nodes source = 100 * (n // 100) + 200 for v in list(g.nodes()): g.add_edge(source, v, weight = 0, tokens = 0) # add random weights and tokens wsum = 1 for _, _, data in g.edges_iter( data = True ): data['weight'] = randrange(1, 10) data['tokens'] = randrange(-1, 8) wsum += data['weight'] # create shortest path formulation for initial tree for _, _, data in g.edges_iter( data = True ): data['sp'] = data['tokens'] * wsum - data['weight'] # ensure that the graph admits a feasible solution its = 0 while True: try: its += 1 tree, distances = bfct.find_shortest_paths(g, source, arg = 'sp') break except NegativeCycleException as ex: toks = sum(map(lambda vw : g.get_edge_data(*vw).get('tokens'), ex.cycle)) edge_data = g.get_edge_data(*choice(ex.cycle)) edge_data['tokens'] += (1 - toks) edge_data['sp'] = edge_data['tokens'] * wsum - edge_data['weight'] if debug is not None: import pdb; pdb.set_trace() negative_toks = False for _, _, data in g.edges_iter( data = True ): if data['tokens'] < 0: negative_toks = True; break print("Run {}: negative tokens: {}, iterations: {}".format(run, negative_toks, its)) ratio, cycle = compute_mcr(g, source) assert ratio is not None, "Deadlocked cycle found" if not cycle: # verify that the graph is acyclic assert is_directed_acyclic_graph(g), "[run = {}] Graph is not acyclic".format(run) else: wsum, tsum = 0, 0 for v, w in cycle: data = g.get_edge_data(v, w) wsum += data['weight'] tsum += data['tokens'] assert Fraction( wsum, tsum ) == ratio, "[run = {}] computed MCR {} does not match ratio of critical cycle {}".format(run, ratio, Fraction(wsum, tsum)) for v, w, data in g.edges_iter( data = True ): data['weight'] = data['tokens'] * ratio - data['weight'] try: bellman_ford(g, source) except Exception: print("Exception during run {}".format(run))
def compute_mcm_component( g, root, estimate = None, pweight = 'weight' ): """ Computes the maximum cycle mean 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 = pq.priority_queue() # determine lower bound for mcr init_mcr = 1 for (v, w, data) in g.edges_iter(data=True): weight = data.get(pweight, 0) init_mcr = init_mcr + max(0, weight) data['dist'] = pdistance(weight, 1) if estimate is not None: # compute initial tree by computing shortest paths tree # print("Computing initial tree from estimate {}".format(estimate)) for (v, w, data) in g.edges_iter(data=True): dist = data['dist'] data['w'] = estimate - dist[0] try: tree, _ = bfct.find_shortest_paths(g, root, arg = 'w') distances[root] = pdistance(0, 0) for v, i in tree.dfs( root ): if i == 0: dv = distances[ v ] for w in tree.children( v ): distances[ w ] = dv + g.get_edge_data(v, w).get('dist') except NegativeCycleException as ex: # print("Infeasible solution for estimate {}, due to cycle {}".format(estimate, [(u, v, data) for u, v, data in g.edges_iter(data = True) if (u, v) in ex.cycle])) raise InfeasibleException( ex.cycle ) else: # create tree rooted at root tree = DFSTree( root ) # run Dijkstra on tokens distances[root] = 0 queue[ root ] = 0 while len(queue) > 0: v, _ = queue.pop() dv = distances[ v ] for _, w, data in g.out_edges_iter(v, True): dw = distances.get(w, None) if (dw is None) or dv + 1 < dw: distances[w] = dv + 1 queue[ w ] = distances[w] tree.append_child( v, w ) # DFS on shortest paths DAG # (this is the DAG induced by the union of all shortest path trees) pre, post, index = dict(), dict(), 0 post_order = list() for v, i in tree.dfs( root ): if i == 0: pre[v] = index index += 1 dv = distances[v] for _, w, data in g.out_edges_iter(v, True): dw = distances[w] if dv + 1 == dw and w not in pre: tree.append_child( v, w ) else: post[v] = index index += 1 post_order.append(v) # scan in topological order distances.clear() distances[root] = pdistance(0, 0) while post_order: v = post_order.pop() dv = distances[v] for _, w, data in g.out_edges_iter(v, True): dist = data['dist'] dw = distances.get(w, None) newdist = dv + data['dist'] if dw is None or newdist[1] < dw[1]: distances[w] = newdist tree.append_child( v, w ) elif (newdist[1] == dw[1] and newdist[0] - dw[0] > 0): # can't have back edges assert post[w] < post[v] distances[w] = newdist tree.append_child( v, w ) def compute_node_key(node): maxratio, argmax = None, None # go over all incoming edges of the node for u, v, data in g.in_edges_iter( node, data = True ): if u in distances: delta = distances[u] + data['dist'] - distances[v] # print("Delta for {} = {}".format((u, v), delta)) if delta[1] > 0: ratio = Fraction(delta[0], delta[1]) if argmax is None or ratio > maxratio: maxratio = ratio argmax = (u, v) # store the node key for v if argmax is not None: queue[node] = (-maxratio, argmax) elif node in queue: del queue[node] return maxratio # fill priority queue: # go over all nodes and compute their key # print("Distances from root {}: {}".format(root, distances)) for v in distances: compute_node_key(v) # pivot until cycle is found path_changes = 0 while len(queue) > 0: (node, (ratio, (v, w))) = queue.pop() delta = distances[v] + g.get_edge_data(v, w)['dist'] - distances[w] for j in tree.pre_order(w): distances[j] += delta if v == j: cycle = list() if v != w: child = j for p in tree.ancestors(child): cycle.append( (p, j) ) if p == w: break j = p cycle.reverse() cycle.append( (v, w) ) assert __is_cycle( cycle ), "Found wrong cycle after pivoting with back edge ({}, {})".format( v, w ) return -ratio, cycle for (_, k, data) in g.out_edges_iter(j, True): # update priority of (j, k) ratio_k = None if k in queue: (ratio_k, edge) = 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)) # recompute vertex key of j compute_node_key(j) tree.append_child( v, w ) else: # no cycle found, any period is admissible # Note that this implies that the graph is acyclic return None, None
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 = pq.priority_queue() # print("Computing MCR for graph {}".format( g.edges( data = True ))) # determine lower bound for mcr # while doing so, determine vertices with outgoing tokenless init_mcr = 1 negative_tokens_or_weights = False for (v, w, data) in g.edges_iter(data=True): tokens = data.get('tokens', 0) weight = data.get('weight', 0) init_mcr = init_mcr + max(0, weight) data['dist'] = pdistance(weight, tokens) if tokens < 0 or weight < 0: negative_tokens_or_weights = True if estimate is not None: # compute initial tree by computing shortest paths tree # print("Computing initial tree from estimate {}".format(estimate)) for (v, w, data) in g.edges_iter(data=True): dist = data['dist'] data['w'] = dist[1] * estimate - dist[0] try: tree, _ = bfct.find_shortest_paths(g, root, arg = 'w') distances[root] = pdistance(0, 0) for v, i in tree.dfs( root ): if i == 0: dv = distances[ v ] for w in tree.children( v ): distances[ w ] = dv + g.get_edge_data(v, w).get('dist') except NegativeCycleException as ex: # print("Infeasible solution for estimate {}, due to cycle {}".format(estimate, [(u, v, data) for u, v, data in g.edges_iter(data = True) if (u, v) in ex.cycle])) raise InfeasibleException( ex.cycle ) elif True: # print("Computing initial tree from estimate {}".format(init_mcr)) # compute initial tree by computing shortest paths tree for (v, w, data) in g.edges_iter(data=True): dist = data['dist'] data['w'] = dist[1] * init_mcr - dist[0] try: tree, _ = bfct.find_shortest_paths(g, root, arg = 'w') distances[root] = pdistance(0, 0) for v, i in tree.dfs( root ): if i == 0: dv = distances[ v ] for w in tree.children( v ): distances[ w ] = dv + g.get_edge_data(v, w).get('dist') if False: # verify distances for v in distances: distv = distances[v] distv = distv[0] - distv[1] * init_mcr for _, w, data in g.out_edges_iter( v, True ): distw = distances[w] distw = distw[0] - distw[1] * init_mcr edge_dist = data['dist'] edge_dist = edge_dist[0] - edge_dist[1] * init_mcr assert distv + edge_dist <= distw except NegativeCycleException as ex: # print("Infeasible solution for estimate {}, due to cycle {}".format(init_mcr, [(u, v, data) for u, v, data in g.edges_iter(data = True) if (u, v) in ex.cycle])) raise InfeasibleException( ex.cycle ) else: # create tree rooted at root tree = DFSTree( root ) # run Dijkstra on tokens distances[root] = 0 queue[ root ] = 0 while len(queue) > 0: v, _ = queue.pop() dv = distances[ v ] for _, w, data in g.out_edges_iter(v, True): dist = data['dist'] dw = distances.get(w, dv + dist[1] + 1) if dv + dist[1] - dw < 0: distances[w] = dv + dist[1] queue[ w ] = distances[w] tree.append_child( v, w ) # DFS on shortest paths DAG # (this is the DAG induced by the union of all shortest path trees) pre, post, index = dict(), dict(), 0 post_order = list() for v, i in tree.dfs( root ): if i == 0: pre[v] = index index += 1 dv = distances[v] for _, w, data in g.out_edges_iter(v, True): tokens = data['dist'][1] dw = distances[w] if dv + tokens == dw and w not in pre: tree.append_child( v, w ) else: post[v] = index index += 1 post_order.append(v) # scan in topological order distances.clear() distances[root] = pdistance(0, 0) while post_order: v = post_order.pop() dv = distances[v] for _, w, data in g.out_edges_iter(v, True): dist = data['dist'] dw = distances.get(w, None) newdist = dv + data['dist'] if dw is None or newdist[1] < dw[1]: distances[w] = newdist tree.append_child( v, w ) elif (newdist[1] == dw[1] and newdist[0] - dw[0] > 0): # check that (v, w) is not a back edge! if post[w] >= post[v]: # back edge, MCR not defined prev = v cycle = list() if v != w: for p in tree.ancestors(v): cycle.append( (p, prev) ) if p == w: break prev = p cycle.reverse() cycle.append( (v, w) ) assert __is_cycle( cycle ), "Not a cycle: {}".format( cycle ) # print(cycle) # print("Positive cycle found: {}".format( cycle )) raise InfeasibleException(cycle) distances[w] = newdist tree.append_child( v, w ) def compute_node_key(node): maxratio, argmax = None, None # go over all incoming edges of the node for (u, v, data) in g.in_edges_iter(nbunch=[node], data=True): if u in distances: delta = distances[u] + data['dist'] - distances[v] # print("Delta for {} = {}".format((u, v), delta)) if delta[1] > 0: ratio = Fraction(delta[0], delta[1]) if argmax is None or ratio > maxratio: maxratio = ratio argmax = (u, v) # store the node key for v if argmax is not None: queue[node] = (-maxratio, argmax) # print("Vertex key for {} = {}, attained by edge {}".format(node, maxratio, argmax)) elif node in queue: del queue[node] return maxratio # fill priority queue: # go over all nodes and compute their key # print("Distances from root {}: {}".format(root, distances)) for v in distances: compute_node_key(v) # pivot until cycle is found path_changes = 0 pivots = 0 while len(queue) > 0: (node, (ratio, (v, w))) = queue.pop() pivots += 1 delta = distances[v] + g.get_edge_data(v, w)['dist'] - distances[w] # print("Pivoting with edge ({}, {}), delta = {}".format(v, w, delta)) for j in tree.pre_order(w): # update parametric distance to j distances[j] += delta path_changes += 1 if v == j: cycle = list() if v != w: child = j for p in tree.ancestors(child): cycle.append( (p, j) ) if p == w: break j = p cycle.reverse() cycle.append( (v, w) ) assert __is_cycle( cycle ), "Found wrong cycle after pivoting with back edge ({}, {})".format( v, w ) return -ratio, cycle for (_, k, data) in g.out_edges_iter(j, True): # update priority of (j, k) ratio_k = None if k in queue: (ratio_k, edge) = queue[k] delta_k = distances[j] + data['dist'] - distances[k] # commented out line below to allow a negative MCR 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)) # print("New arc key for ({}, {}) = {}, delta = {} + {} - {} = {}".format(j, k, -r, distances[j], data['dist'], distances[k], delta_k)) # recompute vertex key of j compute_node_key(j) tree.append_child( v, w ) else: # no cycle found, any period is admissible # Note that this implies that the graph is acyclic return None, None