def psolC_generalized(g, W0, W1): # base case : game is empty if g.get_nodes() == []: if DEBUG_PRINT: print("Base case return") return g, W0, W1 # else retrieve useful information on the game nbr_func = g.get_nbr_priority_functions() # number of functions priorities = [[] for z in xrange(nbr_func)] # setup list containing list of priorities for each function even_priorities = [[] for z in xrange(nbr_func)] # setup list containing list of even priorities for each function # first, retrieve all priorities and put them in the lists of priorities for each function for node in g.nodes.iterkeys(): for func in range(nbr_func): priorities[func].append(g.get_node_priority_function_i(node, func + 1)) # function are numbered 1 to k # sort priorities and create the lists containing only the even priorities for func in range(nbr_func): priorities[func] = sorted(set(priorities[func]), reverse=False) # change into set to remove duplicates and sort even_priorities[func] = filter(lambda x: x % 2 == 0, priorities[func]) # keep the sorted even priorities # if there are no even priorities according to one of the functions, the game is completely won by player 1 # return empty game and all nodes added to W1 if len(even_priorities[func]) == 0: W1.extend(g.nodes.keys()) return Graph(), W0, W1 if DEBUG_PRINT: print("Priorities " + str(priorities)) print("Even priorities " + str(even_priorities)) # handle odd priorities by calling psolC with the correct function for i in range(1, nbr_func + 1): safe_episodes = jfs_algo_func(g, i, 1) if len(safe_episodes) > 0: A, complement = attractor(g, safe_episodes, 1) W1.extend(A) subgame = g.subgame(complement) return psolC_generalized(subgame, W0, W1) # handle even priorities w = truc(g, nbr_func, even_priorities, priorities) if len(w) > 0: A, complement = attractor(g, w, 0) W0.extend(A) subgame = g.subgame(complement) return psolC_generalized(subgame, W0, W1) return g, W0, W1
def psolC(g, W1, W2): safe_episodes = jfs_algo(g, 0) subgame = g if len(safe_episodes) > 0: A, complement = attractor(subgame, safe_episodes, 0) W1.extend(A) subgame = subgame.subgame(complement) subgame, W1, W2 = psolC(subgame, W1, W2) safe_episodes = jfs_algo(subgame, 1) if len(safe_episodes) > 0: A, complement = attractor(subgame, safe_episodes, 1) W2.extend(A) subgame = subgame.subgame(complement) subgame, W1, W2 = psolC(subgame, W1, W2) return subgame, W1, W2
def psolB_modified_att(g, W1, W2): """ Partial solver psolB for parity games using fatal attractors. Implementation using a variant of attractors. :param g: a game graph. :return: a partial solution g', W1', W2' in which g is an unsolved subgame of g and W1', W2' are included in the two respective winning regions of g. """ for color in sort_colors_ascending(g): if DEBUG_PRINT: print("Computing for color " + str(color)) target_set = ops.i_priority_node(g, color) # set of nodes of color 'color' cache = set() # TODO check list comparison efficiency while cache != set(target_set) and target_set != []: cache = target_set MA, in_att, rest = monotone_attractor_including_target(g, target_set, color) if DEBUG_PRINT: print(" MA " + str(MA) + " Player " + str(g.get_node_player(target_set[0])) + "\n") # if the sum of values is the length of the target set, every node in target is in the attractor if sum(in_att.itervalues()) == len(target_set): if DEBUG_PRINT: print("Set " + str(target_set) + " in MA ") att, complement = attractor(g, MA, color % 2) if color % 2 == 0: W1.extend(att) else: W2.extend(att) return psolB_modified_att(g.subgame(complement), W1, W2) else: # only keep in target the target nodes that are included in the attractor target_set = filter(lambda x: in_att[x] == 1, in_att.iterkeys()) return g, W1, W2
def psolB_set(g, W1, W2): """ Partial solver psolB for parity games using fatal attractors. Implementation using sets. :param g: a game graph. :return: a partial solution g', W1', W2' in which g is an unsolved subgame of g and W1', W2' are included in the two respective winning regions of g. """ empty_set = set() for color in sort_colors_ascending(g): if DEBUG_PRINT: print("Computing for color " + str(color)) target_set = set(ops.i_priority_node(g, color)) # set of nodes of color 'color' cache = set() while cache != target_set and target_set != empty_set: cache = target_set MA, rest = monotone_attractor(g, target_set, color) if DEBUG_PRINT: print(" MA " + str(MA) + " Player " + str(g.get_node_player(target_set[0])) + "\n") if target_set.issubset(MA): if DEBUG_PRINT: print("Set " + str(target_set) + " in MA ") att, complement = attractor(g, MA, color % 2) if color % 2 == 0: W1.extend(att) else: W2.extend(att) return psolB_set(g.subgame(complement), W1, W2) else: target_set = target_set.intersection(MA) return g, W1, W2
def psolB_buchi_safety(g, W1, W2): """ Partial solver psolB for parity games. This implementation uses buchi inter safety to find fatal attractors. :param g: a game graph. :return: a partial solution g', W1', W2' in which g is an unsolved subgame of g and W1', W2' are included in the two respective winning regions of g. """ for color in sort_colors_ascending(g): if DEBUG_PRINT: print("Computing for color " + str(color)) target_set = ops.i_priority_node(g, color) # set of nodes of color 'color' # TODO here replace previous call by a call which goes through each node and add it to in/out set of color i # we only go trough every node once and add it to greater than color or of priority color # record the nodes we don't want to see infinitely often i.e. the ones with greater priority than color not_target_set_bigger = [] for node in g.nodes.iterkeys(): if g.get_node_priority(node) > color: not_target_set_bigger.append(node) # winning region of buchi inter safety for player color % 2 w = safety.buchi_inter_safety_player(g, target_set, not_target_set_bigger, color % 2) if DEBUG_PRINT: print(" MA " + str(w) + " Player " + str(g.get_node_player(target_set[0])) + "\n") if w != []: if DEBUG_PRINT: print("Set " + str(target_set) + " in MA ") att, complement = attractor(g, w, color % 2) if color % 2 == 0: W1.extend(att) else: W2.extend(att) return psolB_buchi_safety(g.subgame(complement), W1, W2) return g, W1, W2
def psolB_generalized_inline(g, W1, W2): """ Adaptation of partial solver psolB for generalized parity games. This implementation uses inline version of generalized buchi inter safety games. :param g: a game graph. :return: a partial solution g', W1', W2' in which g is an unsolved subgame of g and W1', W2' are included in the two respective winning regions of g. """ # base case : game is empty if g.get_nodes() == []: return g, W1, W2 # else retrieve useful information on the game nbr_func = g.get_nbr_priority_functions() # number of functions priorities = [[] for z in xrange(nbr_func)] # setup list containing list of priorities for each function even_priorities = [[] for z in xrange(nbr_func)] # setup list containing list of even priorities for each function sizes = [0] * nbr_func # setup the sizes for the lists of priorities even_sizes = [0] * nbr_func # setup the sizes for the lists of even priorities empty_set = set() # useful when computing fatal attractor for player 1 # first, retrieve all priorities and put them in the lists of priorities for each function for node in g.nodes.iterkeys(): for func in range(nbr_func): priorities[func].append(g.get_node_priority_function_i(node, func + 1)) # function are numbered 1 to k # sort priorities and create the lists containing only the even priorities for func in range(nbr_func): # TODO we transform into set to remove duplicate, might check itertools, ordered dicts and heaps also priorities[func] = sorted(set(priorities[func]), reverse=True) # change into set to remove duplicates and sort even_priorities[func] = filter(lambda x: x % 2 == 0, priorities[func]) # if there are no even priorities according to one of the functions, the game is completely won by player 1 # return empty game and all nodes added to W2 if len(even_priorities[func]) == 0: W2.extend(g.nodes.keys()) return Graph(), W1, W2 sizes[func] = len(priorities[func]) even_sizes[func] = len(even_priorities[func]) # here we have sorted lists of priorities as well as their sizes indexes = [0] * nbr_func # index for each function to go trough its priorities depth = 0 # depth is needed for the level of the lattice max_size = max(even_sizes) # needed for the maximum level of the lattice if DEBUG_PRINT: print("priorities " + str(priorities)) print("even priorities " + str(even_priorities)) print("sizes " + str(sizes)) print("depth " + str(depth)) print("max_size " + str(max_size)) # TODO instead of doing as above, go through nodes in iterator order and for each node add priorities to the list # of priorities and even priorities. Then we work on those unsorted lists. The order is random but it might still # work while avoiding to sort the lists. # while we have not considered every couple of the lattice i.e. not reached the maximal depth for the levels in # the lattice for the even priorities or we have not considered every priority in the list of priorities while (not all(indexes[w] == sizes[w] for w in range(nbr_func))) or (depth != max_size + 2): if DEBUG_PRINT: print("iteration " + str(depth) + " indexes " + str(indexes)) # for each function, we treat odd priorities in order in the list until we have reached an even priority for i in range(nbr_func): if DEBUG_PRINT: print(" function " + str(i)) # while we can advance in the list and we encounter an odd priority, we consider it while indexes[i] < sizes[i] and priorities[i][indexes[i]] % 2 == 1: if DEBUG_PRINT: print(" index " + str(indexes[i]) + " element " + str(priorities[i][indexes[i]])) print(" fonction " + str(i + 1) + " " + str(priorities[i][indexes[i]])) # we have an odd priority to consider odd_priority = priorities[i][indexes[i]] # set of nodes of color 'odd_priority' according to function i+1 target_set = set(ops.i_priority_node_function_j(g, odd_priority, i + 1)) # perform fixpoint computation to find fatal attractor for player odd cache = set() while cache != target_set and target_set != empty_set: cache = target_set MA, rest = monotone_attractor(g, target_set, odd_priority, i + 1) if DEBUG_PRINT: print(" MA " + str(MA) + " Player " + str(1) + "\n") if target_set.issubset(MA): if DEBUG_PRINT: print("Set " + str(target_set) + " in MA ") att, complement = attractor(g, MA, 1) W2.extend(att) return psolB_generalized_inline(g.subgame(complement), W1, W2) else: target_set = target_set.intersection(MA) # if we have not found a fatal attractor, we go forward in the list and restart the same logic until # reaching an even priority or the end of the list indexes[i] += 1 # we have found an even priority at position indexes[i], at next iteration of the outer while, we restart # from the next index in the list if indexes[i] < sizes[i]: indexes[i] += 1 # when this is reached, we know we have handled every odd priorities until reaching an even priority for each # function i.e. if [5,3,4,1,0] and [2,1,0] are the priorities, after first iteration of outer while we have # handled 5, 3 and reached 4 in the first list and reached 2 in the second (assuming there was no recursive # call after handling 5 and 3). # TODO if we have gone trough the whole list for each function, player 2 wins ? i.e. index is list size for each # TODO bug : sometimes there are no even priorities according to some function : even_tuples_iterator bugs # go through every k-uple of even priorities in the current level for kuple in even_tuples_iterator(depth, even_priorities, even_sizes, [0] * nbr_func, nbr_func, 0): if DEBUG_PRINT: print(" " + str(kuple)) # we now will compute a generalized buchi inter safety game avoid = [] # nodes for the safety game, to be avoided sets_gen = [[] for z in xrange(nbr_func)] # sets of nodes to be visited infinitely often for nod in g.nodes.iterkeys(): flag = False # is the node to be avoided (greater priority than required for some function) # for each function for f in range(1, nbr_func + 1): # priority of the node according to that function prio = g.get_node_priority_function_i(nod, f) if DEBUG_PRINT: print(" " + str(prio)) print(" " + str(prio % 2 == 1) + " " + str(prio > kuple[f - 1])) # priority is odd and greater than the corresponding one in the k-uple, we want to avoid the node if prio % 2 == 1 and prio > kuple[f - 1]: flag = True # else if the priority is the one we want to see, add it to the set to be visited infinitely often elif prio == kuple[f - 1]: sets_gen[f - 1].append(nod) # if the flag is true, we don't want to see this node if flag: avoid.append(nod) if DEBUG_PRINT: print("sets " + str(sets_gen)) print("avoid " + str(avoid)) # solution of the generalized buchi inter safety game win = generalized_buchi_inter_safety.inlined_generalized_buchi_inter_safety(g, sets_gen, avoid) if DEBUG_PRINT: print(g) print("=========================== win " + str(win)) # if we have some winning region if len(win) != 0: att2, comp = attractor(g, win, 0) W1.extend(att2) return psolB_generalized_inline(g.subgame(comp), W1, W2) depth += 1 return g, W1, W2
def psolQ_classical_attractor(g, W1, W2): """ Partial solver psolQ for parity games using fatal attractors. This implementation uses classical attractors, where the nodes in the target set are in the attractor by default. :param g: a game graph. :return: a partial solution g', W1', W2' in which g is an unsolved subgame of g and W1', W2' are included in the two respective winning regions of g. """ # base case : game is empty if g.get_nodes() == []: return g, W1, W2 # else sort priorities and retrieve maximum even and odd priorities colors_descending = sort_colors_descending(g) empty_set = set() # useful later if DEBUG_PRINT: print("Colors descending" + str(colors_descending)) max_prio = colors_descending[0] max_prio_player = max_prio % 2 if max_prio_player == 0: max_even = max_prio max_odd = max_prio - 1 if max_prio_player == 1: max_even = max_prio - 1 max_odd = max_prio if DEBUG_PRINT: print("MAX EVEN " + str(max_even)) print("MAX ODD " + str(max_odd)) for color in colors_descending: if DEBUG_PRINT: print("Computing for color " + str(color)) color_player = color % 2 # set containing nodes of g with priority >= d and parity color_player x = set( filter( lambda z: g.get_node_priority(z) % 2 == color_player and g. get_node_priority(z) >= color, g.nodes.iterkeys())) if DEBUG_PRINT: print("NODES COLOR BIGGER THAN " + str(color) + " " + str(x)) cache = set() while cache != x and x != empty_set: if DEBUG_PRINT: print("SET X" + str(x)) cache = x if color_player == 0: MA, rest = layered_classical_attractor(g, max_even, x) else: MA, rest = layered_classical_attractor(g, max_odd, x) if DEBUG_PRINT: print(" MA " + str(MA) + " Player " + str(g.get_node_player(x[0])) + "\n") # Here instead of check for inclusion of the target set x in the attractor MA we consider the nodes in x and # check wether they can force for the correct player to go back in the attractor. We can't simply check for # inclusion of x in MA since in this version the target set x is added de facto in the attractor # we also can't put this step directly inside the attractor, by checking for inclusion of the target set in # the attractor because here we check the inclusion in the layered attractor and not in the attractor at a # specific step is_ok = True # create a new version of x which only contains the node in x from which player color_player can ensure to # reach MA x_new = set() # for each node in x for node in x: # if node belongs to color player, it needs to have one successor in MA if g.get_node_player(node) == color_player: for succ in g.get_successors(node): if succ in MA: x_new.add(node) break else: # if node belongs to other player, all its successors must be in MA found = True # does the node have all of its successors in MA for succ in g.get_successors(node): if not (succ in MA): # we have found a successor not in MA found = False break if found: x_new.add(node) # if the length of x is different than the length of x_new, we have removed nodes from x and need to # continue. Else, the attractor is fatal if len(x) != len(x_new): is_ok = False # replace x by x_new, no intersection with MA can be done per above reasons, but intersection with MA # is done implicitly i.e. x_new is actually the intersection x = x_new if is_ok: # attractor is fatal if DEBUG_PRINT: print("Set " + str(x) + " in MA ") att, complement = attractor(g, MA, color_player) if color_player == 0: W1.extend(att) else: W2.extend(att) if DEBUG_PRINT: print("TEMP W1 " + str(W1)) print("TEMP W2 " + str(W2)) print("TEMP REST " + str(complement)) return psolQ_classical_attractor(g.subgame(complement), W1, W2) return g, W1, W2
def psolQ(g, W1, W2): """ Partial solver psolQ for parity games using fatal attractors. :param g: a game graph. :return: a partial solution g', W1', W2' in which g is an unsolved subgame of g and W1', W2' are included in the two respective winning regions of g. """ # base case : game is empty if g.get_nodes() == []: return g, W1, W2 # else sort priorities and retrieve maximum even and odd priorities colors_descending = sort_colors_descending(g) empty_set = set() # useful later if DEBUG_PRINT: print("Colors descending" + str(colors_descending)) max_prio = colors_descending[0] max_prio_player = max_prio % 2 if max_prio_player == 0: max_even = max_prio max_odd = max_prio - 1 if max_prio_player == 1: max_even = max_prio - 1 max_odd = max_prio if DEBUG_PRINT: print("MAX EVEN " + str(max_even)) print("MAX ODD " + str(max_odd)) for color in colors_descending: if DEBUG_PRINT: print("Computing for color " + str(color)) color_player = color % 2 # set containing nodes of g with priority >= d and parity color_player x = set( filter( lambda z: g.get_node_priority(z) % 2 == color_player and g. get_node_priority(z) >= color, g.nodes.iterkeys())) if DEBUG_PRINT: print("NODES COLOR BIGGER THAN " + str(color) + " " + str(x)) cache = set() while cache != x and x != empty_set: if DEBUG_PRINT: print("SET X" + str(x)) cache = x if color_player == 0: MA, rest = layered_attractor(g, max_even, x) else: MA, rest = layered_attractor(g, max_odd, x) if DEBUG_PRINT: print(" MA " + str(MA) + " Player " + str(g.get_node_player(x[0])) + "\n") if x.issubset(MA): if DEBUG_PRINT: print("Set " + str(x) + " in MA ") att, complement = attractor(g, MA, color_player) if color % 2 == 0: W1.extend(att) else: W2.extend(att) if DEBUG_PRINT: print("TEMP W1 " + str(W1)) print("TEMP W2 " + str(W2)) print("TEMP REST " + str(complement)) return psolQ(g.subgame(complement), W1, W2) else: x = x.intersection(MA) return g, W1, W2