def disj_parity_win(g, maxValues, k, u): """ Recursive solver for generalized parity games. Implements the classical algorithm which solves generalized parity games. :param g: the game to solve :param maxValues: the maximum value according to each priority function :param k: the number of priority functions :param u: integer for testing purposes :return: W1, W2 the winning regions in the game for player 1 and player 2 (for the original game, without complement) """ # Base case : all maxValues are 1 or the game is empty if all(value == 1 for value in maxValues) or len(g.nodes) == 0: return g.get_nodes(), [] for i in range(k): attMaxOdd, compl_attMaxOdd = reachability.attractor( g, ops.i_priority_node_function_j(g, maxValues[i], i + 1), 0) G1 = g.subgame(compl_attMaxOdd) attMaxEven, compl_attMaxEven = reachability.attractor( G1, ops.i_priority_node_function_j(G1, maxValues[i] - 1, i + 1), 1) H1 = G1.subgame(compl_attMaxEven) j = 0 while True: j += 1 copy_maxValues = copy.copy(maxValues) copy_maxValues[i] -= 2 W1, W2 = disj_parity_win(H1, copy_maxValues, k, u + 1) if len(G1.nodes) == 0: break if set(W2) == set(H1.get_nodes()): B, compl_B = reachability.attractor(g, G1.get_nodes(), 1) W1, W2 = disj_parity_win(g.subgame(compl_B), maxValues, k, u + 1) B.extend(W2) return W1, B T, compl_T = reachability.attractor(G1, W1, 0) G1 = G1.subgame(compl_T) E, compl_E = reachability.attractor( G1, ops.i_priority_node_function_j(g, maxValues[i] - 1, i + 1), 0) H1 = G1.subgame(compl_E) return g.get_nodes(), []
def reduction_to_safety_parity_solver(graph): """ Main function which solves a parity game by reduction to a safety game :param graph: the arena of the parity game :return: the winning regions in the parity game """ # First we find out the number of counters necessary maximum = -1 for node in graph.get_nodes(): if (graph.get_node_priority(node) > maximum): maximum = graph.get_node_priority(node) if maximum % 2 == 0: maxOdd = maximum - 1 else: maxOdd = maximum nbr_counters = (maxOdd // 2) + 1 max_counter = [0] * nbr_counters # counts every odd priority for node in graph.get_nodes(): if (graph.get_node_priority(node) % 2 != 0): position = graph.get_node_priority(node) // 2 max_counter[position] = max_counter[position] + 1 # Then we build the start counter for each node, we only build the part reachable from (v, 0, ..., 0) for all v # For convenience, we label the nodes using the str representation of their list : [v, c_1, c_2, ..., c_k] # This is a unique identifier for each node. # The overflow is simply written "-" # Start nodes for construction : (v, 0, ..., 0) start_nodes = [] for node in graph.get_nodes(): start_nodes.append([node] + [0] * nbr_counters) transformed_graph = createSafetyGame( graph, start_nodes, max_counter) # creates the safety game from the start nodes W1bis, W2bis = reachability.attractor( transformed_graph, ["-"], 1) # attractor for player 2 of the nodes in overflow W1 = [] W2 = [] empty_counters = [0] * nbr_counters # [0, ..., 0] # checks if the nodes [v, 0, ..., 0] belongs to the attractor or not and creates the winning regions for node in W1bis: if node != "-": node_list = ast.literal_eval(node) if node_list[-nbr_counters:] == empty_counters: W2.append(node_list[0]) for node in W2bis: if node != "-": node_list = ast.literal_eval(node) if node_list[-nbr_counters:] == empty_counters: W1.append(node_list[0]) return W1, W2
def disj_parity_win2(g, maxValues, k, u): """ Recursive solver for generalized parity games. Uses the algorithm presented in http://www2.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-144.html This is used for testing purposes. :param g: the game to solve :param maxValues: the maximum value for each priority function :param k: the number of priority functions :param u: integer for testing purposes :return: W1, W2 the winning regions in the game for player 1 and player 2 (for the base game) """ # Base case : all maxValues are 1 or the game is empty if all(value == 1 for value in maxValues) or len(g.nodes) == 0: #print(str(u*2*" ")+"it-"+str(u)+" return on base case") return g.get_nodes(), [] for i in range(k): max = ops.max_priority(g) if max % 2 == 0: even = max attMaxOdd, compl_attMaxOdd = [], [] #print(str(u*2*" ")+"it-"+str(u)+" maxOdd-"+str(maxValues[i])+" attMaxOdd-"+str(attMaxOdd)+" "+"complAttMaxOdd-"+str(compl_attMaxOdd)) G1 = g attMaxEven, compl_attMaxEven = reachability.attractor( G1, ops.i_priority_node_function_j(G1, max, i + 1), 1) #print(str(u*2*" ")+"it-"+str(u)+" maxEven-"+str(maxValues[i]-1)+" attMaxEven-"+str(attMaxEven)+" "+"complAttMaxEven-"+str(compl_attMaxEven)) H1 = G1.subgame(compl_attMaxEven) j = 0 #print(str(u*2*" ")+"it-"+str(u)+" G\n"+str(G1)) #print(str(u*2*" ")+"it-"+str(u)+" H\n"+str(H1)) else: even = max - 1 attMaxOdd, compl_attMaxOdd = reachability.attractor( g, ops.i_priority_node_function_j(g, max, i + 1), 0) # print(str(u*2*" ")+"it-"+str(u)+" maxOdd-"+str(maxValues[i])+" attMaxOdd-"+str(attMaxOdd)+" "+"complAttMaxOdd-"+str(compl_attMaxOdd)) G1 = g.subgame(compl_attMaxOdd) attMaxEven, compl_attMaxEven = reachability.attractor( G1, ops.i_priority_node_function_j(G1, max - 1, i + 1), 1) # print(str(u*2*" ")+"it-"+str(u)+" maxEven-"+str(maxValues[i]-1)+" attMaxEven-"+str(attMaxEven)+" "+"complAttMaxEven-"+str(compl_attMaxEven)) H1 = G1.subgame(compl_attMaxEven) j = 0 # print(str(u*2*" ")+"it-"+str(u)+" G\n"+str(G1)) # print(str(u*2*" ")+"it-"+str(u)+" H\n"+str(H1)) while True: j += 1 copy_maxValues = copy.copy(maxValues) copy_maxValues[i] -= even - 1 W1, W2 = disj_parity_win2(H1, copy_maxValues, k, u + 1) #print(str(u * 2 * " ") + "it-" + str(u)+"-"+str(j) + " W1-" + str(W1) + " W2-" + str(W2)) #print("W1 "+str(W1)) #print("W2 "+str(W2)) #print("game " + str(g) + "att " + str(attMaxOdd) + "compl " + str(compl_attMaxOdd)) #print("stop "+str(set(W2))+" "+str(set(H1.get_nodes()))+" val "+ str(set(W2) == set(H1.get_nodes()))) #break # cette cond etait en dessous de lautre et lautre prennait precedence quand on avait les 2 #print(len(G1.nodes)) if len(G1.nodes) == 0: #print("G empty") break if set(W2) == set(H1.get_nodes()): #print("hello") B, compl_B = reachability.attractor(g, G1.get_nodes(), 1) W1, W2 = disj_parity_win2(g.subgame(compl_B), maxValues, k, u + 1) #print("re "+str(B)+" "+str(W1)+" "+str(W2)) B.extend(W2) return W1, B #break T, compl_T = reachability.attractor(G1, W1, 0) G1 = G1.subgame(compl_T) E, compl_E = reachability.attractor( G1, ops.i_priority_node_function_j(g, even, i + 1), 0) H1 = G1.subgame(compl_E) #break #break return g.get_nodes(), []
def strong_parity_solver_no_strategies(g): """ Strong parity games solver. This is an implementation of the recursive algorithm used to solve parity games. This implementation does not compute the winning strategies (for comparison purpose with other algorithms which don't) :param g: the game to solve. :return: the solution in the following format : (W_0, sigma_0), (W_1, sigma_1). """ W1 = [] # Winning region of player 0 W2 = [] # Winning region of player 1 # if the game is empty, return the empty regions if len(g.nodes) == 0: return W1, W2 else: i = ops.max_priority(g) # get max priority occurring in g # determining which player we are considering, if i is even : player 0 and else player 1 if i % 2 == 0: j = 0 else: j = 1 opponent = ops.opponent(j) # getting the opponent of the player U = ops.i_priority_node(g, i) # target set for the attractor : nodes of priority i # getting the attractor A and discarding the region for the opponent A, discard1 = reachability.attractor(g,U,j) # The subgame G\A is composed of the nodes not in the attractor, thus the nodes of the opposite player's region G_A = g.subgame(discard1) # Recursively solving the subgame G\A, solution comes as (W_0, W_1) sol_player1, sol_player2 = strong_parity_solver_no_strategies(G_A) # depending on which player we are considering, assign regions to the proper variables # W'_j is noted W_j, sigma'_j is noted sig_j; the same aplies for jbar if j == 0: W_j = sol_player1 W_jbar = sol_player2 else: W_j = sol_player2 W_jbar = sol_player1 # if W'_jbar is empty we update the regions depending on the current player # the region for the whole game for one of the players is empty if not W_jbar: if j == 0: W1.extend(A) W1.extend(W_j) else: W2.extend(A) W2.extend(W_j) else: # compute attractor B B, discard1 = reachability.attractor(g, W_jbar, opponent) # The subgame G\B is composed of the nodes not in the attractor, so of the opposite player's winning region G_B = g.subgame(discard1) # recursively solve subgame G\B, solution comes as (W_0, W_1) sol_player1_, sol_player2_ = strong_parity_solver_no_strategies(G_B) # depending on which player we are considering, assign regions to the proper variables # W''_j is noted W__j, sigma''_j is noted sig__j; the same aplies for jbar if j == 0: W__j = sol_player1_ W__jbar = sol_player2_ else: W__j = sol_player2_ W__jbar = sol_player1_ # the last step is to update the winning regions depending on which player we consider if j == 0: W1 = W__j W2.extend(W__jbar) W2.extend(B) else: W2 = W__j W1.extend(W__jbar) W1.extend(B) return W1, W2