def policy_buchi_pa(pa, weight_label='weight'): '''Computes the policy.''' if not pa.final: return float('Inf'), None vinit = generate_unique_node() pa.g.add_node(vinit) pa.g.add_edges_from([(vinit, init, {weight_label: 0}) for init in pa.init]) prefix_costs, prefix_paths = nx.single_source_dijkstra(pa.g, source=vinit, weight=weight_label) pa.g.remove_node(vinit) opt_cost, opt_suffix_path = float('Inf'), None for final in pa.final: if final in prefix_costs: suffix_cost, suffix_path = source_to_target_dijkstra( pa.g, source=final, target=final, degen_paths=False, weight_key=weight_label) if prefix_costs[final] + suffix_cost < opt_cost: opt_cost = prefix_costs[final] + suffix_cost opt_suffix_path = suffix_path if opt_suffix_path is None: return float('Inf'), None opt_final = opt_suffix_path[0] return (opt_cost, [u[0] for u in prefix_paths[opt_final][1:]], [u[0] for u in opt_suffix_path])
def clean_final_states(self): # By construction all final states are reachable from # the initial states of global_pa # Find and remove the final states that cannot reach themselves unreaching_finals = set() unreaching_finals.update(self.global_pa.final) for final in self.global_pa.final: dist, _ = source_to_target_dijkstra(self.global_pa.g, final, final, degen_paths=False, weight_key='weight') if dist == float('inf'): # final cannot reach itself continue # final can reach itself unreaching_finals -= set([final]) self.global_pa.final -= unreaching_finals # Remove unreaching finals from the set of initial states for state in unreaching_finals: if state in self.global_pa.init: del self.global_pa.init[state] # Finally, actually remove these states from the graph # (Can also remove states w/ fd(q) = \infty) self.global_pa.g.remove_nodes_from(unreaching_finals)
def empty_language(p): # Not checking reachability from initial states # as such finals are not included by construction for final in p.final: dist, _ = source_to_target_dijkstra(p.g, final, final, degen_paths=False, weight_key='weight') if dist != float('inf'): # final can reach itself return False return True
def optimal_run(t, formula, opt_prop): try: logger.info('T has %d states', len(t.g)) # Convert formula to Buchi automaton b = Buchi() b.from_formula(formula) logger.info('B has %d states', len(b.g)) # Compute the product automaton p = ts_times_buchi(t, b) logger.info('P has %d states', len(p.g)) logger.info('Set F has %d states', len(p.final)) # Find the set S of states w/ opt_prop s = p.nodes_w_prop(opt_prop) logger.info('Set S has %d states', len(s)) # Compute the suffix_cycle* and suffix_cycle_cost* suffix_cycle_cost, suffix_cycle_on_p = min_bottleneck_cycle( p.g, s, p.final) # Compute the prefix: a shortest path from p.init to suffix_cycle prefix_length = float('inf') prefix_on_p = [''] i_star = 0 for init_state in p.init.keys(): for i in range(0, len(suffix_cycle_on_p)): length, prefix = source_to_target_dijkstra( p.g, init_state, suffix_cycle_on_p[i], degen_paths=True) if (length < prefix_length): prefix_length = length prefix_on_p = prefix i_star = i if (prefix_length == float('inf')): raise Exception(__name__, 'Could not compute the prefix.') # Wrap suffix_cycle_on_p as required if i_star != 0: # Cut and paste suffix_cycle_on_p = suffix_cycle_on_p[i_star:] + suffix_cycle_on_p[ 1:i_star + 1] # Compute projection of prefix and suffix-cycle to T and return suffix_cycle = [x[0] for x in suffix_cycle_on_p] prefix = [x[0] for x in prefix_on_p] return (prefix_length, prefix, suffix_cycle_cost, suffix_cycle) except Exception as ex: if (len(ex.args) == 2): print("{}: {}".format(*ex.args)) else: print("{}: Unknown exception {}: {}".format( __name__, type(ex), ex)) exc_type, exc_value, exc_traceback = sys.exc_info() traceback.print_tb(exc_traceback) exit(1)
def globalPolicy(self, ts=None): '''Computes the global policy.''' paths = nx.shortest_path(self.g) policy = None suffix_cost = None for init in self.init: if self.g.has_node(init): for final in self.final: if self.g.has_node(final): prefix = paths[init][final] cost, suffix = source_to_target_dijkstra( self.g, final, final) if prefix and len(suffix) > 2: _, prefix = source_to_target_dijkstra( self.g, init, final) comp_policy = (list(prefix), list(suffix)) if not policy or cost < suffix_cost: policy = comp_policy suffix_cost = cost prefix, suffix = policy return prefix, suffix, suffix_cost
def optimal_run(t, formula, opt_prop): try: logger.info('T has %d states', len(t.g)) # Convert formula to Buchi automaton b = Buchi() b.from_formula(formula) logger.info('B has %d states', len(b.g)) # Compute the product automaton p = ts_times_buchi(t, b) logger.info('P has %d states', len(p.g)) logger.info('Set F has %d states', len(p.final)) # Find the set S of states w/ opt_prop s = p.nodes_w_prop(opt_prop) logger.info('Set S has %d states', len(s)) # Compute the suffix_cycle* and suffix_cycle_cost* suffix_cycle_cost, suffix_cycle_on_p = min_bottleneck_cycle(p.g, s, p.final) # Compute the prefix: a shortest path from p.init to suffix_cycle prefix_length = float('inf') prefix_on_p = [''] i_star = 0 for init_state in p.init.keys(): for i in range(0,len(suffix_cycle_on_p)): length, prefix = source_to_target_dijkstra(p.g, init_state, suffix_cycle_on_p[i], degen_paths = True) if(length < prefix_length): prefix_length = length prefix_on_p = prefix i_star = i if(prefix_length == float('inf')): raise Exception(__name__, 'Could not compute the prefix.') # Wrap suffix_cycle_on_p as required if i_star != 0: # Cut and paste suffix_cycle_on_p = suffix_cycle_on_p[i_star:] + suffix_cycle_on_p[1:i_star+1] # Compute projection of prefix and suffix-cycle to T and return suffix_cycle = map(lambda x: x[0], suffix_cycle_on_p) prefix = map(lambda x: x[0], prefix_on_p) return (prefix_length, prefix, suffix_cycle_cost, suffix_cycle) except Exception as ex: if(len(ex.args) == 2): print "%s: %s" % ex.args else: print "%s: Unknown exception %s: %s" % (__name__, type(ex), ex) exc_type, exc_value, exc_traceback = sys.exc_info() traceback.print_tb(exc_traceback) exit(1)
def globalPolicy(self, ts=None): '''Computes the global policy.''' paths = nx.shortest_path(self.g) policy = None policy_len = None for init in self.init: for final in self.final: prefix = paths[init][final] _, suffix = source_to_target_dijkstra(self.g, final, final) if prefix and len(suffix) > 2: comp_policy = (list(prefix), list(suffix)) comp_len = len(comp_policy[0]) + len(comp_policy[1]) if not policy or policy_len > comp_len: policy = comp_policy policy_len = comp_len prefix, suffix = policy return [v[0] for v in prefix], [v[0] for v in suffix]
def min_bottleneck_cycle(g, s, f): """ Returns the minimum bottleneck cycle from s to f in graph g. An implementation of the Min-Bottleneck-Cycle Algortihm in S.L. Smith, J. Tumova, C. Belta, D. Rus " Optimal Path Planning for Surveillance with Temporal Logic Constraints", in IJRR. Parameters ---------- g : NetworkX graph s : A set of nodes These nodes satisfy the optimizing proposition. f : A set of nodes These nodes are the final states of B x T product. Returns ------- cycle : List of node labels. The minimum bottleneck cycle S->F->S Examples -------- Notes ----- """ global pp_installed if not pp_installed: raise Exception('This functionality is not enables because, ' 'Parallel Python not installed!') # Start job server job_server = pp.Server(ppservers=pp_servers, secret='trivial') # Compute shortest S->S and S->F paths logger.info('S->S+F') #d = subset_to_subset_dijkstra_path_value(g, s, s|f, degen_paths = False) jobs = job_dispatcher(job_server, subset_to_subset_dijkstra_path_value, list(s), 1, '0', (g, s|f, 'sum', False, 'weight'), data_source) d = dict() for i in range(0,len(jobs)): d.update(jobs[i]()) jobs[i]='' del jobs logger.info('Collected results for S->S+F') # Create S->S, S->F dict of dicts g_s_edges = [] d_s_to_f = dict() for src in d.keys(): for dest in d[src].keys(): if dest in s: w = d[src][dest] g_s_edges.append((src,dest,w)) if dest in f: # We allow degenerate S->F paths w = 0 if src == dest else d[src][dest] if src not in d_s_to_f: d_s_to_f[src] = dict() d_s_to_f[src][dest] = w # Create the G_s graph g_s = nx.MultiDiGraph() g_s.add_weighted_edges_from(g_s_edges) # Remove d and g_s_edges to save memory del d del g_s_edges # Compute shortest F->S paths logger.info('F->S') #d_f_to_s = subset_to_subset_dijkstra_path_value(g, f, s, degen_paths = True) jobs = job_dispatcher(job_server, subset_to_subset_dijkstra_path_value, list(f), 1, '1', (g, s, 'sum', True, 'weight'), data_source) d_f_to_s = dict() for i in range(0,len(jobs)): d_f_to_s.update(jobs[i]()) jobs[i]='' del jobs logger.info('Collected results for F->S') # Compute shortest S-bottleneck paths between verices in s logger.info('S-bottleneck') #d_bot = subset_to_subset_dijkstra_path_value(g_s, s, s, combine_fn = (lambda a,b: max(a,b)), degen_paths = False) jobs = job_dispatcher(job_server, subset_to_subset_dijkstra_path_value, list(s), 1, '2', (g_s, s, 'max', False, 'weight'), data_source) d_bot = dict() for i in range(0,len(jobs)): d_bot.update(jobs[i]()) jobs[i]='' del jobs logger.info('Collected results for S-bottleneck') # Find the triple \in F x S x S that minimizes C(f,s1,s2) logger.info('Path*') jobs = job_dispatcher(job_server, find_best_cycle, list(f), 1, '3', (s, d_f_to_s, d_s_to_f, d_bot), data_source) cost_star = float('inf') len_star = float('inf') cycle_star = None for i in range(0,len(jobs)): this_cost, this_len, this_cycle = jobs[i]() jobs[i]='' if (this_cost < cost_star or (this_cost == cost_star and this_len < len_star)): cost_star = this_cost len_star = this_len cycle_star = this_cycle del jobs logger.info('Collected results for Path*') logger.info('Cost*: %d, Len*: %d, Cycle*: %s', cost_star, len_star, cycle_star) if cost_star == float('inf'): raise Exception(__name__, 'Failed to find a satisfying cycle, spec cannot be satisfied.') else: logger.info('Extracting Path*') (ff, s1, s2) = cycle_star # This is the F->S1 path (cost_ff_to_s1, path_ff_to_s1) = source_to_target_dijkstra(g, ff, s1, degen_paths = True, cutoff = d_f_to_s[ff][s1]) # This is the S2->F path (cost_s2_to_ff, path_s2_to_ff) = source_to_target_dijkstra(g, s2, ff, degen_paths = True, cutoff = d_s_to_f[s2][ff]) if s1 == s2 and ff != s1: # The path will be F->S1==S2->F path_star = path_ff_to_s1[0:-1] + path_s2_to_ff assert(cost_star == (cost_ff_to_s1 + cost_s2_to_ff)) assert(len_star == (cost_ff_to_s1 + cost_s2_to_ff)) else: # The path will be F->S1->S2->F # Extract the path from s_1 to s_2 (bot_cost_s1_to_s2, bot_path_s1_to_s2) = source_to_target_dijkstra(g_s, s1, s2, combine_fn = 'max', degen_paths = False, cutoff = d_bot[s1][s2][0]) assert(cost_star == max((cost_ff_to_s1 + cost_s2_to_ff),bot_cost_s1_to_s2)) path_s1_to_s2 = [] cost_s1_to_s2 = 0 for i in range(1,len(bot_path_s1_to_s2)): source = bot_path_s1_to_s2[i-1] target = bot_path_s1_to_s2[i] cost_segment, path_segment = source_to_target_dijkstra(g, source, target, degen_paths = False) path_s1_to_s2 = path_s1_to_s2[0:-1] + path_segment cost_s1_to_s2 += cost_segment assert(len_star == cost_ff_to_s1 + cost_s1_to_s2 + cost_s2_to_ff) # path_ff_to_s1 and path_s2_to_ff can be degenerate paths, # but path_s1_to_s2 cannot, thus path_star is defined as this: # last ff is kept to make it clear that this is a suffix-cycle path_star = path_ff_to_s1[0:-1] + path_s1_to_s2[0:-1] + path_s2_to_ff return (cost_star, path_star)
def min_bottleneck_cycle(g, s, f): """ Returns the minimum bottleneck cycle from s to f in graph g. An implementation of the Min-Bottleneck-Cycle Algortihm in S.L. Smith, J. Tumova, C. Belta, D. Rus " Optimal Path Planning for Surveillance with Temporal Logic Constraints", in IJRR. Parameters ---------- g : NetworkX graph s : A set of nodes These nodes satisfy the optimizing proposition. f : A set of nodes These nodes are the final states of B x T product. Returns ------- cycle : List of node labels. The minimum bottleneck cycle S->F->S Examples -------- Notes ----- """ global pp_installed if not pp_installed: raise Exception('This functionality is not enables because, ' 'Parallel Python not installed!') # Start job server job_server = pp.Server(ppservers=pp_servers, secret='trivial') # Compute shortest S->S and S->F paths logger.info('S->S+F') #d = subset_to_subset_dijkstra_path_value(g, s, s|f, degen_paths = False) jobs = job_dispatcher(job_server, subset_to_subset_dijkstra_path_value, list(s), 1, '0', (g, s | f, 'sum', False, 'weight'), data_source) d = dict() for i in range(0, len(jobs)): d.update(jobs[i]()) jobs[i] = '' del jobs logger.info('Collected results for S->S+F') # Create S->S, S->F dict of dicts g_s_edges = [] d_s_to_f = dict() for src in d.keys(): for dest in d[src].keys(): if dest in s: w = d[src][dest] g_s_edges.append((src, dest, w)) if dest in f: # We allow degenerate S->F paths w = 0 if src == dest else d[src][dest] if src not in d_s_to_f: d_s_to_f[src] = dict() d_s_to_f[src][dest] = w # Create the G_s graph g_s = nx.MultiDiGraph() g_s.add_weighted_edges_from(g_s_edges) # Remove d and g_s_edges to save memory del d del g_s_edges # Compute shortest F->S paths logger.info('F->S') #d_f_to_s = subset_to_subset_dijkstra_path_value(g, f, s, degen_paths = True) jobs = job_dispatcher(job_server, subset_to_subset_dijkstra_path_value, list(f), 1, '1', (g, s, 'sum', True, 'weight'), data_source) d_f_to_s = dict() for i in range(0, len(jobs)): d_f_to_s.update(jobs[i]()) jobs[i] = '' del jobs logger.info('Collected results for F->S') # Compute shortest S-bottleneck paths between verices in s logger.info('S-bottleneck') #d_bot = subset_to_subset_dijkstra_path_value(g_s, s, s, combine_fn = (lambda a,b: max(a,b)), degen_paths = False) jobs = job_dispatcher(job_server, subset_to_subset_dijkstra_path_value, list(s), 1, '2', (g_s, s, 'max', False, 'weight'), data_source) d_bot = dict() for i in range(0, len(jobs)): d_bot.update(jobs[i]()) jobs[i] = '' del jobs logger.info('Collected results for S-bottleneck') # Find the triple \in F x S x S that minimizes C(f,s1,s2) logger.info('Path*') jobs = job_dispatcher(job_server, find_best_cycle, list(f), 1, '3', (s, d_f_to_s, d_s_to_f, d_bot), data_source) cost_star = float('inf') len_star = float('inf') cycle_star = None for i in range(0, len(jobs)): this_cost, this_len, this_cycle = jobs[i]() jobs[i] = '' if (this_cost < cost_star or (this_cost == cost_star and this_len < len_star)): cost_star = this_cost len_star = this_len cycle_star = this_cycle del jobs logger.info('Collected results for Path*') logger.info('Cost*: %d, Len*: %d, Cycle*: %s', cost_star, len_star, cycle_star) if cost_star == float('inf'): raise Exception( __name__, 'Failed to find a satisfying cycle, spec cannot be satisfied.') else: logger.info('Extracting Path*') (ff, s1, s2) = cycle_star # This is the F->S1 path (cost_ff_to_s1, path_ff_to_s1) = source_to_target_dijkstra(g, ff, s1, degen_paths=True, cutoff=d_f_to_s[ff][s1]) # This is the S2->F path (cost_s2_to_ff, path_s2_to_ff) = source_to_target_dijkstra(g, s2, ff, degen_paths=True, cutoff=d_s_to_f[s2][ff]) if s1 == s2 and ff != s1: # The path will be F->S1==S2->F path_star = path_ff_to_s1[0:-1] + path_s2_to_ff assert (cost_star == (cost_ff_to_s1 + cost_s2_to_ff)) assert (len_star == (cost_ff_to_s1 + cost_s2_to_ff)) else: # The path will be F->S1->S2->F # Extract the path from s_1 to s_2 (bot_cost_s1_to_s2, bot_path_s1_to_s2) = source_to_target_dijkstra( g_s, s1, s2, combine_fn='max', degen_paths=False, cutoff=d_bot[s1][s2][0]) assert (cost_star == max((cost_ff_to_s1 + cost_s2_to_ff), bot_cost_s1_to_s2)) path_s1_to_s2 = [] cost_s1_to_s2 = 0 for i in range(1, len(bot_path_s1_to_s2)): source = bot_path_s1_to_s2[i - 1] target = bot_path_s1_to_s2[i] cost_segment, path_segment = source_to_target_dijkstra( g, source, target, degen_paths=False) path_s1_to_s2 = path_s1_to_s2[0:-1] + path_segment cost_s1_to_s2 += cost_segment assert (len_star == cost_ff_to_s1 + cost_s1_to_s2 + cost_s2_to_ff) # path_ff_to_s1 and path_s2_to_ff can be degenerate paths, # but path_s1_to_s2 cannot, thus path_star is defined as this: # last ff is kept to make it clear that this is a suffix-cycle path_star = path_ff_to_s1[0:-1] + path_s1_to_s2[ 0:-1] + path_s2_to_ff return (cost_star, path_star)