def attractor(g, U, j): """ Computes the attractor for player j of the set U in g. Does not create any strategy and only returns the set that corresponds to the attractor. :param g: the game graph. :param U: the target set. :param j: the player for which we compute the attractor. :return: W the set of nodes corresponding to the attractor. """ out = init_out(g) # init out queue = deque( ) # init queue (deque is part of standard library and allows O(1) append() and pop() at either end) # this dictionary is used to know if a node belongs to a winning region without # iterating over both winning regions lists (we can check in O(1) in average) regions = defaultdict(lambda: -1) W = [] # the attractor opponent = op.opponent(j) # player j's opponent # for each node in the target set U for node in U: queue.append(node) # add node to the end of the queue regions[ node] = j # set its regions to j (node is winning for j because reachability objective is satisfied) W.append(node) # add the node to the winning region list of j # while queue is not empty while queue: s = queue.popleft( ) # remove and return node on the left side of the queue (first in, first out) # iterating over the predecessors of node s for sbis in g.get_predecessors(s): if regions[ sbis] == -1: # if sbis is not yet visited, its region is -1 by default if g.get_node_player(sbis) == j: # belongs to j, set regions and strategy accordingly queue.append(sbis) regions[sbis] = j W.append(sbis) elif g.get_node_player(sbis) == opponent: # belongs to j bar, decrement out. If out is 0, set the region accordingly out[sbis] -= 1 if out[sbis] == 0: queue.append(sbis) regions[sbis] = j W.append(sbis) Wbis = [] for node in g.get_nodes(): if regions[node] != j: Wbis.append(node) return W, Wbis
def R_set(g, target_set, j): """ We compute the attractor of a set of node-priority pairs where the priority represents the maximal priority seen so far. """ ascending_priorities = g.get_sorted_priorities() v_out = init_out(g) # a counter for visited edges from each vertex out = {(v, p): v_out[v] for v in g.get_nodes() for p in ascending_priorities} regions = defaultdict(lambda: -1) adversary = operations.opponent(j) # we keep a queue of newly found winning vertex-priority pairs queue = deque(target_set) while queue: (node, priority) = queue.popleft() for pred in g.get_predecessors(node): pred_player = g.get_node_player(pred) pred_priority = g.get_node_priority(pred) if pred_priority > priority: continue # cannot be a predecessor if priority > g.get_node_priority(node): options = [priority] else: assert (priority == g.get_node_priority(node)) options = filter( lambda x: x >= pred_priority and x <= priority, ascending_priorities) assert (len(options) > 0) for p in options: if regions[(pred, p)] == -1: # vertex-priority is undecided if pred_player == j: regions[(pred, p)] = j if (pred, p) not in target_set: queue.append((pred, p)) elif pred_player == adversary: out[(pred, p)] -= 1 if out[(pred, p)] == 0: regions[(pred, p)] = j if (pred, p) not in target_set: queue.append((pred, p)) # prepare output W = set() for n in g.get_nodes(): if regions[(n, g.get_node_priority(n))] == j: W.add(n) return W
def safe_attractor(g, U, Ubis, j): out = init_out(g) queue = deque() regions = defaultdict(lambda: -1) opponent = op.opponent(j) for node in set(U) - set(Ubis): queue.append(node) # add node to the end of the queue regions[ node] = j # set its regions to j (node is winning for j b/c reachability is satisfied) while queue: s = queue.popleft( ) # remove and return node on the left side of the queue (first in, first out) # iterating over the predecessors of node s for sbis in set(g.get_predecessors(s)) - set(Ubis): if regions[ sbis] == -1: # if sbis is not yet visited, its region is -1 by default if g.get_node_player(sbis) == j: # belongs to j, set regions and strategy accordingly queue.append(sbis) regions[sbis] = j elif g.get_node_player(sbis) == opponent: # belongs to j bar, decrement out. If out is 0, set the region accordingly out[sbis] -= 1 if out[sbis] == 0: queue.append(sbis) regions[sbis] = j W = [] Wbis = [] for node in g.get_nodes(): if regions[node] == j: W.append(node) else: Wbis.append(node) return W, Wbis
def permissive_monotone_attractor(g, v_star, target_set, current_priorities): """ Attractor for one layer, handling previously computed v_star :param g: a game graph. :param v_star: the previously computed v_star. :param target_set: the target set (nodes with memory). :param current_priorities: the current priorities for this layer. :return: """ out_base = init_out(g) # init out in base game nbr_func = g.get_nbr_priority_functions() out = defaultdict(lambda: -1) # this out is used for nodes with memory queue = deque( ) # init queue (deque is part of standard library and allows O(1) append() and pop() at either end) # this dictionary is used to know if a node belongs to a winning region without # iterating over both winning regions lists (we can check in O(1) in average) regions = defaultdict(lambda: -1) W = [] # the attractor j = 0 # the player for which we compute the attractor opponent = ops.opponent(j) # player j's opponent # in the attractor we won't consider (v, M) such that v is in star, either in predecessors or in target star_attractor, not_star_attractor = attractor_color_vector( g, v_star, 0, current_priorities) # for each predecessor of nodes in star of the original game, decrement their counter as they have a successor # already winning for star in star_attractor: for pr in g.get_predecessors(star): out_base[pr] -= 1 for node in target_set: # todo added 3 june morning because since this was not removed, the out[base] was -1 for node v in star att but # then some (v, M) was seen and a further decrement was done # only consider targets which are not in star if not node[0] in star_attractor: queue.append(node) # add node to the end of the queue if DEBUG_PRINT: print("--- Monotone attractor ---") print(g) print("star attractor " + str(star_attractor)) print("out base " + str(out_base)) print("Set " + str(target_set) + " Player " + str(j) + " Opponent " + str(opponent) + " Prio " + str(current_priorities)) print("Marked before start " + str(regions) + " Queue before start " + str(queue)) # while queue is not empty while queue: if DEBUG_PRINT: print(" Queue " + str(queue)) s = queue.popleft( ) # remove and return node on the left side of the queue (first in, first out) if DEBUG_PRINT: print(" Considering node " + str(s)) # here we need to create the predecessors preds = create_predecessors(g, s, current_priorities, star_attractor) # iterating over the predecessors of node s for sbis in preds: sbis_node = sbis[0] sbis_memory = sbis[1] # get sbis info sbis = node, memory sbis_player = g.get_node_player(sbis_node) priority_ok = all( g.nodes[sbis_node][z + 1] % 2 == 0 or g.nodes[sbis_node][z + 1] <= current_priorities[z] for z in range(nbr_func)) # TODO verifying that the priorities are correct is done when predecessors are created if DEBUG_PRINT: print(" Considering predecessor " + str(sbis) + " Is marked ? " + str(regions[sbis]) + "Player " + str(sbis_player) + " Priority " + str(current_priorities)) if regions[ sbis] == -1: # if sbis is not yet visited, its region is -1 by default # if node is the correct player and its priority is lower or equal, add it. If it is in the target set, # also consider it even if it breaks the condition on priority. # and (sbis_priority <= priority or sbis in target_set) # condition on priorities is true if sbis_player == j: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") # if node has not been considered yet (not already been in the queue) add it # this is to avoid considering the same node twice, which can happen only for the target node and # can mess up the decrementation of the counters for nodes of the opponent if sbis not in target_set: queue.append(sbis) # mark accordingly and add to winning region regions[sbis] = j W.append(sbis) # if node is the opposite player and its priority is lower or equal, check its counter of successors # if it is in the target set, also consider it even if it breaks the condition on priority. # and (sbis_priority <= priority or sbis in target_set) elif sbis_player == opponent: if out[sbis] == -1: # first ime, init out out[sbis] = out_base[sbis_node] if DEBUG_PRINT: print("init out node " + str(sbis) + " out " + str(out[sbis]) + " out base " + str(out_base[sbis_node])) # belongs to j bar, decrement out. If out is 0, set the region accordingly out[sbis] -= 1 if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Decrement, new count = " + str(out[sbis])) if out[sbis] == 0: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") # if node has not been considered yet (not already been in the queue) add it if sbis not in target_set: queue.append(sbis) # mark accordingly and add to winning region regions[sbis] = j W.append(sbis) # every node that is not marked is not in the attractor, we filter them for speed Wbis = filter(lambda x: regions[x] != j, g.nodes.iterkeys()) if DEBUG_PRINT: print("Attractor " + str(W) + " Complement " + str(Wbis)) print("-------------------------\n") return W, Wbis
def monotone_attractor_including_target(g, target_set, color): """ Computes the monotone attractor of the target set, meaning the attractor without visiting bigger priorities than the one of the target set. This implementation adds the target set in the attractor at the start and checks during computation if the nodes of that target set would have been added to the attractor had they not been added at the start. This differs from the usual implementation which does not add the target set to the attractor at the start. In theory, this should allow us a quicker check of inclusion of the target set in the attractor. :param g: a game graph. :param target_set: a target set of nodes. :param color: the color of the nodes in target set. :return: W, in_att, Wbis the attractor, a dictionary of which nodes in the target set are in the attractor and the nodes which are not in the attractor. """ p = color # priority of the node gives us the player for which we compute the attractor out = init_out(g) # init out queue = deque() # init queue (deque is part of standard library and allows O(1) append() and pop() at either end) # this dictionary is used to know if a node belongs to a winning region without # iterating over both winning regions lists (we can check in O(1) in average) regions = defaultdict(lambda: -1) W = [] # the attractor j = p % 2 # the player for which we compute the attractor opponent = ops.opponent(j) # player j's opponent in_att = defaultdict(lambda: 0) # for each node in the target set U for node in target_set: queue.append(node) # add node to the end of the queue regions[node] = j # set its regions to j (node is winning for j because reachability objective is satisfied) W.append(node) # add the node to the winning region list of j if DEBUG_PRINT: print(g) print("Set " + str(target_set) + " Player " + str(j) + " Opponent " + str(opponent) + " Prio " + str(p)) print("Marked before start " + str(regions) + " Queue before start " + str(queue)) # while queue is not empty while queue: if DEBUG_PRINT: print(" Queue " + str(queue)) s = queue.popleft() # remove and return node on the left side of the queue (first in, first out) if DEBUG_PRINT: print(" Considering node " + str(s)) # iterating over the predecessors of node s for sbis in g.get_predecessors(s): # get sbis info sbis_player = g.get_node_player(sbis) sbis_priority = g.get_node_priority(sbis) if DEBUG_PRINT: print(" Considering predecessor " + str(sbis) + " Is marked ? " + str(regions[sbis]) + "Player " + str(sbis_player) + " Priority " + str(sbis_priority)) # if the predecessor is in the target set # TODO here instead of in, we can use a special value in the regions dictionnary for nodes in target # set for quicker check, if so we also need to modify Wbis computation to take this into account if sbis in target_set: # if it belongs to player 1, sbis is in the attractor so we mark it if sbis_player == j: in_att[sbis] = 1 # else we decrement its out value, and when it is 0 we mark it else: out[sbis] -= 1 if out[sbis] == 0: in_att[sbis] = 1 if regions[sbis] == -1: # if sbis is not yet visited, its region is -1 by default if sbis_player == j and sbis_priority <= p: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") # belongs to j, set regions and strategy accordingly queue.append(sbis) regions[sbis] = j W.append(sbis) elif sbis_player == opponent and sbis_priority <= p: # belongs to j bar, decrement out. If out is 0, set the region accordingly out[sbis] -= 1 if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Decrement, new count = " + str(out[sbis])) if out[sbis] == 0: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") queue.append(sbis) regions[sbis] = j W.append(sbis) # every node that is not marked is not in the attractor, we filter them for speed Wbis = filter(lambda x: regions[x] != j, g.nodes.iterkeys()) if DEBUG_PRINT: print("Attractor " + str(W) + " Complement " + str(Wbis)) print("-------------------------\n") return W, in_att, Wbis
def monotone_attractor(g, target_set, color): """ Computes the monotone attractor of the target set, meaning the attractor without visiting bigger priorities than the one of the target set. :param g: a game graph. :param target_set: a target set of nodes. :param color: the color of the nodes in target set. :return: W, Wbis the attractor and the nodes which are not in the attractor. """ priority = color # priority of the node gives us the player for which we compute the attractor out = init_out(g) # init out queue = deque() # init queue (deque is part of standard library and allows O(1) append() and pop() at either end) # this dictionary is used to know if a node belongs to a winning region without # iterating over both winning regions lists (we can check in O(1) in average) regions = defaultdict(lambda: -1) W = [] # the attractor j = priority % 2 # the player for which we compute the attractor opponent = ops.opponent(j) # player j's opponent for node in target_set: queue.append(node) # add node to the end of the queue if DEBUG_PRINT: print("--- Monotone attractor ---") print(g) print("Set " + str(target_set) + " Player " + str(j) + " Opponent " + str(opponent) + " Prio " + str(priority)) print("Marked before start " + str(regions) + " Queue before start " + str(queue)) # while queue is not empty while queue: if DEBUG_PRINT: print(" Queue " + str(queue)) s = queue.popleft() # remove and return node on the left side of the queue (first in, first out) if DEBUG_PRINT: print(" Considering node " + str(s)) # iterating over the predecessors of node s for sbis in g.get_predecessors(s): # get sbis info sbis_player = g.get_node_player(sbis) sbis_priority = g.get_node_priority(sbis) if DEBUG_PRINT: print(" Considering predecessor " + str(sbis) + " Is marked ? " + str(regions[sbis]) + "Player " + str(sbis_player) + " Priority " + str(sbis_priority)) if regions[sbis] == -1: # if sbis is not yet visited, its region is -1 by default # if node is the correct player and its priority is lower or equal, add it if sbis_player == j and sbis_priority <= priority: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") # if node has not been considered yet (not already been in the queue) add it # this is to avoid considering the same node twice, which can happen only for the target node and # can mess up the decrementation of the counters for nodes of the opponent if sbis not in target_set: queue.append(sbis) # mark accordingly and add to winning region regions[sbis] = j W.append(sbis) # if node is the opposite player and its priority is lower or equal, check its counter of successors elif sbis_player == opponent and sbis_priority <= priority: # belongs to j bar, decrement out. If out is 0, set the region accordingly out[sbis] -= 1 if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Decrement, new count = " + str(out[sbis])) if out[sbis] == 0: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") # if node has not been considered yet (not already been in the queue) add it if sbis not in target_set: queue.append(sbis) # mark accordingly and add to winning region regions[sbis] = j W.append(sbis) # every node that is not marked is not in the attractor, we filter them for speed Wbis = filter(lambda x: regions[x] != j, g.nodes.iterkeys()) if DEBUG_PRINT: print("Attractor " + str(W) + " Complement " + str(Wbis)) print("-------------------------\n") return W, Wbis
def attractor_color_vector(g, U, j, priorities): """ Computes the attractor for player j of the set U in g. Does not create any strategy and only returns the set that corresponds to the attractor. Nodes used in the attractor cannot have a priority function p_i such that the priority p_i(node) of that node is larger than priorities[i]. :param g: the game graph. :param U: the target set. :param j: the player for which we compute the attractor. :param priorities: vector of priorities. :return: W the set of nodes corresponding to the attractor. """ out = init_out(g) # init out queue = deque( ) # init queue (deque is part of standard library and allows O(1) append() and pop() at either end) # this dictionary is used to know if a node belongs to a winning region without # iterating over both winning regions lists (we can check in O(1) in average) regions = defaultdict(lambda: -1) W = [] # the attractor opponent = op.opponent(j) # player j's opponent nbr_func = len(priorities) # for each node in the target set U for node in U: queue.append(node) # add node to the end of the queue regions[ node] = j # set its regions to j (node is winning for j because reachability objective is satisfied) W.append(node) # add the node to the winning region list of j if DEBUG_PRINT: print(g) print("Set " + str(U) + " Player " + str(j) + " Opponent " + str(opponent) + " Prio " + str(priorities)) print("Marked before start " + str(regions) + " Queue before start " + str(queue)) # while queue is not empty while queue: if DEBUG_PRINT: print(" Queue " + str(queue)) s = queue.popleft( ) # remove and return node on the left side of the queue (first in, first out) if DEBUG_PRINT: print(" Considering node " + str(s)) # iterating over the predecessors of node s for sbis in g.get_predecessors(s): sbis_player = g.get_node_player(sbis) # whether sbis has smaller priorities than the provided priority vector. # either the priorities are smaller or they are even TODO priority_ok = all(g.nodes[sbis][z + 1] % 2 == 0 or g.nodes[sbis][z + 1] <= priorities[z] for z in range(nbr_func)) if DEBUG_PRINT: print(" Considering predecessor " + str(sbis) + " Is marked ? " + str(regions[sbis]) + "Player " + str(g.get_node_player(sbis)) + " Priority " + str(g.get_node_priority(sbis))) if regions[ sbis] == -1: # if sbis is not yet visited, its region is -1 by default if sbis_player == j and priority_ok: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") # belongs to j, set regions and strategy accordingly queue.append(sbis) regions[sbis] = j W.append(sbis) elif sbis_player == opponent and priority_ok: # belongs to j bar, decrement out. If out is 0, set the region accordingly out[sbis] -= 1 if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Decrement, new count = " + str(out[sbis])) if out[sbis] == 0: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") queue.append(sbis) regions[sbis] = j W.append(sbis) # every node that is not marked is not in the attractor, we filter them for speed Wbis = filter(lambda x: regions[x] != j, g.nodes.iterkeys()) if DEBUG_PRINT: print("Attractor " + str(W) + " Complement " + str(Wbis)) print("-------------------------\n") return W, Wbis
def attractor_color(g, U, j, p): """ Computes the attractor for player j of the set U in g. Does not create any strategy and only returns the set that corresponds to the attractor. Nodes used in the attractor cannot have a larger priority than p. :param g: the game graph. :param U: the target set. :param j: the player for which we compute the attractor. :param p: nodes used in the computation cannot have a larger priority than p. :return: W the set of nodes corresponding to the attractor. """ out = init_out(g) # init out queue = deque( ) # init queue (deque is part of standard library and allows O(1) append() and pop() at either end) # this dictionary is used to know if a node belongs to a winning region without # iterating over both winning regions lists (we can check in O(1) in average) regions = defaultdict(lambda: -1) W = [] # the attractor opponent = op.opponent(j) # player j's opponent # for each node in the target set U for node in U: queue.append(node) # add node to the end of the queue regions[ node] = j # set its regions to j (node is winning for j because reachability objective is satisfied) W.append(node) # add the node to the winning region list of j if DEBUG_PRINT: print(g) print("Set " + str(U) + " Player " + str(j) + " Opponent " + str(opponent) + " Prio " + str(p)) print("Marked before start " + str(regions) + " Queue before start " + str(queue)) # while queue is not empty while queue: if DEBUG_PRINT: print(" Queue " + str(queue)) s = queue.popleft( ) # remove and return node on the left side of the queue (first in, first out) if DEBUG_PRINT: print(" Considering node " + str(s)) # iterating over the predecessors of node s for sbis in g.get_predecessors(s): if DEBUG_PRINT: print(" Considering predecessor " + str(sbis) + " Is marked ? " + str(regions[sbis]) + "Player " + str(g.get_node_player(sbis)) + " Priority " + str(g.get_node_priority(sbis))) if regions[ sbis] == -1: # if sbis is not yet visited, its region is -1 by default if g.get_node_player( sbis) == j and g.get_node_priority(sbis) <= p: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") # belongs to j, set regions and strategy accordingly queue.append(sbis) regions[sbis] = j W.append(sbis) elif g.get_node_player( sbis) == opponent and g.get_node_priority(sbis) <= p: # belongs to j bar, decrement out. If out is 0, set the region accordingly out[sbis] -= 1 if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Decrement, new count = " + str(out[sbis])) if out[sbis] == 0: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") queue.append(sbis) regions[sbis] = j W.append(sbis) Wbis = [] for node in g.get_nodes(): if regions[node] != j: Wbis.append(node) if DEBUG_PRINT: print("Attractor " + str(W) + " Complement " + str(Wbis)) print("-------------------------\n") return W, Wbis
def zielonka_with_partial(g, partial_solver): """ This is an implementation of Zielonka's recursive algorithm used to solve parity games. This implementation uses a provided partial solver to speed up the solving process. This implementation does not compute the winning strategies (for comparison purpose with other algorithms which don't). :param g: the game to solve. :param partial_solver: a partial solver. :return: the solution in the following format : (W_0, W_1). """ rest, partial_1, partial_2 = partial_solver( g, [], []) # TODO this is the default input for the algorithms # a more general way is to use just g as input and add the [] [] in a wrapper function in PSOLB W1 = [] # Winning region of player 0 W2 = [] # Winning region of player 1 # add the partial solutions to the winning regions W1.extend(partial_1) W2.extend(partial_2) # if the game is empty, return the empty regions if len(rest.nodes) == 0: return W1, W2 else: i = ops.max_priority(rest) # 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( rest, i) # target set for the attractor : nodes of priority i # getting the attractor A and discarding the region for the opponent A, discard1 = attractor(rest, 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 = rest.subgame(discard1) # Recursively solving the subgame G\A, solution comes as (W_0, W_1) sol_player1, sol_player2 = zielonka_with_partial(G_A, partial_solver) # 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 = attractor(rest, 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 = rest.subgame(discard1) # recursively solve subgame G\B, solution comes as (W_0, W_1) sol_player1_, sol_player2_ = zielonka_with_partial( G_B, partial_solver) # 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
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, W_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 = 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 = 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
def zielonka_with_single_psolB_iteration(g): """ This is an implementation of Zielonka's recursive algorithm used to solve parity games. This implementation uses one iteration of psolB, for the set of nodes of maximum priority retrieved by the algorithm. 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, W_1). """ W1 = [] # Winning region of player 0 W2 = [] # Winning region of player 1 W1_partial = [] W2_partial = [] empty_set = set() # 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 rest = g # from now on we work on the game called rest which is g by default, but can be a sub-game of g target_set = set(U) # set of nodes of color 'color' cache = set() flag = True while flag and cache != target_set and target_set != empty_set: cache = target_set MA, discard = psolB.monotone_attractor(rest, target_set, i) if target_set.issubset(MA): att, complement = attractor(rest, MA, i % 2) if i % 2 == 0: W1_partial.extend(att) else: W2_partial.extend(att) # we have updated winning regions, we also remove attractor to obtain a sub-game rest = g.subgame(complement) if rest.get_nodes() == []: return W1_partial, W2_partial i = ops.max_priority(rest) # 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( rest, i) # target set for the attractor : nodes of priority i flag = False else: target_set = target_set.intersection(MA) # getting the attractor A and discarding the region for the opponent A, discard1 = attractor(rest, 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 = rest.subgame(discard1) # Recursively solving the subgame G\A, solution comes as (W_0, W_1) sol_player1, sol_player2 = zielonka_with_single_psolB_iteration(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 = attractor(rest, 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 = rest.subgame(discard1) # recursively solve subgame G\B, solution comes as (W_0, W_1) sol_player1_, sol_player2_ = zielonka_with_single_psolB_iteration( 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) if not flag: W1.extend(W1_partial) W2.extend(W2_partial) return W1, W2
def monotone_attractor_player0(g, node, priorities, player): """ Computes an attractor to a single node while making sure that no priority greater than that of the target node is visited (for every priority functions). :param g: a game graph. :param node: a target node. :param priorities: the informations regarding that node : (player, priority_1, ..., priority_k). :param player: the player for which we compute the attractor. :return: the monotone attractor of node in g. """ nbr_func = len(priorities) - 1 # number of priority functions out = init_out(g) # init out queue = deque( ) # init queue (deque is part of standard library and allows O(1) append() and pop() at either end) # this dictionary is used to know if a node belongs to a winning region without # iterating over both winning regions lists (we can check in O(1) in average) regions = defaultdict(lambda: -1) W = [] # the attractor j = player # the player for which we compute the attractor opponent = op.opponent(j) # player j's opponent queue.append(node) # add node to the end of the queue. # Note: node is not de facto in the attractor, it will be added during computations if it is if DEBUG_PRINT: print("--- Monotone attractor ---") print(g) print("Node " + str(node) + " Player " + str(j) + " Opponent " + str(opponent) + " Priority " + str(priorities)) print("Marked before start " + str(regions) + " Queue before start " + str(queue)) # while queue is not empty while queue: if DEBUG_PRINT: print(" Queue " + str(queue)) s = queue.popleft( ) # remove and return node on the left side of the queue (first in, first out) if DEBUG_PRINT: print(" Considering node " + str(s)) # iterating over the predecessors of node s for sbis in g.get_predecessors(s): sbis_priority = g.get_node_priority(sbis) sbis_player = g.get_node_player(sbis) if DEBUG_PRINT: print(" Considering predecessor " + str(sbis) + " Is marked ? " + str(regions[sbis]) + " Player " + str(sbis_player) + " Priority " + str(sbis_priority)) if regions[ sbis] == -1: # if sbis is not yet visited, its region is -1 by default # in any case, we only consider predecessors which have smaller priorities according to every function flag = False # records wether a priority function i has been found such that alpha_i(sbis) > alpha_i(s) # functions are from 1 to k for index in range(1, nbr_func + 1): # priority of sbis according to function index prio = g.get_node_priority_function_i(sbis, index) # TODO add prio % 2 != player and here if we are to consider the extended condition on priorities # if the priority of sbis is greater, we break because we have found a function which falsifies if prio > priorities[index]: flag = True break # if the node is not to be considered, continue to the next iteration of the loop, else check the player if flag: continue # if node is the correct player (its priority is lower or equal per the above check) if sbis_player == j: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") # if node has not been considered yet (not already been in the queue) add it # this is to avoid considering the same node twice, which can happen only for the target node and # can mess up the decrementation of the counters for nodes of the opponent if sbis != node: queue.append(sbis) # mark accordingly and add to winning region regions[sbis] = j W.append(sbis) # if node is the opposite player, check its counter of successors elif sbis_player == opponent: # belongs to opponent, decrement out. If out is 0, set the region accordingly out[sbis] -= 1 if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Decrement, new count = " + str(out[sbis])) if out[sbis] == 0: if DEBUG_PRINT: print(" Predecessor " + str(sbis) + " Added ") # if node has not been considered yet (not already been in the queue) add it if sbis != node: queue.append(sbis) # mark accordingly and add to winning region regions[sbis] = j W.append(sbis) Wbis = filter(lambda x: regions[x] != j, g.nodes.iterkeys()) if DEBUG_PRINT: print("Attractor " + str(W) + " Complement " + str(Wbis)) print("-------------------------\n") return W, Wbis