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_example_2( self ): g = nx.MultiDiGraph() g.add_edge(1, 5, weight = -5) g.add_edge(1, 4, weight = -4) g.add_edge(1, 3, weight = -3) g.add_edge(1, 2, weight = -1) g.add_edge(2, 5, weight = -4) g.add_edge(2, 4, weight = -3) g.add_edge(2, 3, weight = -1) g.add_edge(3, 2, weight = 0) g.add_edge(3, 4, weight = -1) g.add_edge(4, 2, weight = 1) g.add_edge(4, 3, weight = 0) g.add_edge(4, 5, weight = -1) try: parents, distances = longest_distances( g, 1 ) expected_distances = { 1: 0, 2: -1, 3: -2, 4: -3, 5: -4 } expected_parents = { 1: None, 2: (1, 2, 0), 3: (2, 3, 0), 4: (3, 4, 0), 5: (4, 5, 0)} self.assertDictEqual( distances, expected_distances ) self.assertDictEqual( parents, expected_parents ) except PositiveCycle as c: self.fail( "False positive cycle detected" )
def test_example_1( self ): g = nx.MultiDiGraph() g.add_edge(1, 2, weight = 2) g.add_edge(1, 3, weight = -3) g.add_edge(2, 4, weight = -3) g.add_edge(2, 6, weight = -6) g.add_edge(3, 2, weight = 3) g.add_edge(3, 5, weight = -1) g.add_edge(4, 2, weight = 2) g.add_edge(4, 3, weight = -1) g.add_edge(4, 5, weight = -1) g.add_edge(4, 6, weight = -2) g.add_edge(4, 7, weight = -2) g.add_edge(5, 3, weight = -1) g.add_edge(5, 7, weight = -1) g.add_edge(6, 7, weight = 1) g.add_edge(7, 6, weight = -2) try: parents, distances = longest_distances( g, 1 ) expected_distances = { 1: 0, 2: 2, 3: -2, 4: -1, 5: -2, 6: -3, 7: -2 } expected_parents = { 1: None, 2: (1, 2, 0), 3: (4, 3, 0), 4: (2, 4, 0), 5: (4, 5, 0), 6: (4, 6, 0), 7: (6, 7, 0)} self.assertDictEqual( distances, expected_distances ) self.assertDictEqual( parents, expected_parents ) except PositiveCycle as c: self.fail( "False positive cycle detected" )
def test_positive_self_loop(self): # create a small graph g = nx.MultiDiGraph() g.add_edge(1, 2, weight = -1) g.add_edge(2, 3, weight = 1) g.add_edge(2, 3, weight = -1) g.add_edge(3, 3, weight = 1) try: parents, distances = longest_distances( g, 1 ) except PositiveCycle as c: self.assertListEqual( c.cycle, [(3, 3, 0)] ) else: self.fail( "Positive self-loop not detected" )
def strictly_periodic_schedule(graph, admissible=True): # transform the graph to its (pessimistic) single-rate aproximation apx = transform.single_rate_apx(graph, admissible) # transform the single-rate apx to a marked graph mg = transform.single_rate_as_marked_graph(apx, True) # compute max. cycle ratio for the approximation cycle_time, cycle, *_ = mcr.max_cycle_ratio(mg) # create a weighted graph representation from mg wg = nx.MultiDiGraph() for u, v, data in mg.edges(data=True): wg.add_edge(u, v, weight=data.get('weight', 0) - data.get('tokens', 0) * cycle_time) # choose a critical node (a, *_), *_ = cycle # compute the longest distances from a critical node parents, eigen_vector = graphs.longest_distances(wg, a) # compute the period for each actor periods = { v: (graph.modulus() // graph.repetition_vector()[v]) * cycle_time for v in graph } # transform the eigenvector to a periodic schedule for the graph # first element in tuple = time of first firing result = { v: (eigen_vector[v] + (graph.modulus() // graph.repetition_vector()[v] - 1) * cycle_time, periods[v]) for v in periods } # ensure that time of first firing is non-negative min_start_time, _ = min(result.values()) return { v: (eigen_vector[v] - min_start_time + (graph.modulus() // graph.repetition_vector()[v] - 1) * cycle_time, periods[v]) for v in periods }
def test_positive_small_loop(self): # create a small graph g = nx.MultiDiGraph() g.add_edge(1, 2, weight = -1) g.add_edge(2, 3, weight = 1) g.add_edge(2, 3, weight = -1) g.add_edge(3, 2, weight = 0) try: parents, distances = longest_distances( g, 1 ) except PositiveCycle as c: # put cycle into "canonical" form min_edge = min( c.cycle ) while c.cycle[ 0 ] > min_edge: c.cycle = c.cycle[1:] + [ c.cycle[ 0 ] ] self.assertListEqual( c.cycle, [(2, 3, 0),(3, 2, 0)] ) else: self.fail( "Positive cycle not detected" )
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