def buchi_gen(bdd, g, f): g_copy = g.subarena(g.player0_vertices | g.player1_vertices, bdd) while True: for curr_f in range(g.nbr_functions): b0 = attractor(g_copy, f[curr_f], 0, bdd) not_b0 = (g_copy.player0_vertices | g_copy.player1_vertices) & ~b0 if not not_b0 == bdd.false: break b1 = attractor(g_copy, not_b0, 1, bdd) if b1 == bdd.false: break not_b1 = ~b1 g_copy = g_copy.subarena(not_b1, bdd) return g_copy.player0_vertices | g_copy.player1_vertices
def buchi_inter_safety(bdd, g, i, f, s): if i == 0: oppo = 1 else: oppo = 0 # changed here because for Charly -1 is player 1 and for me -1 evaluates to False (which is 0) attr_adv_f = attractor(g, s, oppo, bdd) g_bar = g.subarena(~attr_adv_f, bdd) return buchi(bdd, g_bar, i, f)
def buchi_partial_solver(arena, partial_winning_region_player0, partial_winning_region_player1, manager): """ Partial solver for parity games using fatal attractors. Implementation using sets. :param arena: the arena we consider :type arena: Arena :param partial_winning_region_player0: should be empty list when called :type partial_winning_region_player0: [] :param partial_winning_region_player1: should be empty list when called :type partial_winning_region_player1: [] :param manager: the BDD manager :type manager: dd.cudd.BDD :return: a partial solution sub-arena, partial_player0, partial_player1 in which sub-arena remains unsolved and partial_player0 (resp. partial_player1) is included in the winning region of player 0 (resp. player 1) in arena. :rtype: (Arena, dd.cudd.Function, dd.cudd.Function) """ empty_set = manager.false for priority in sort_priorities_ascending(arena): target_set = arena.priorities[0][priority] & ( arena.player0_vertices | arena.player1_vertices ) # set of vertices of priority cache = manager.false while cache != target_set and target_set != empty_set: cache = target_set monotone_att = monotone_attractor(arena, target_set, priority, manager) # if target set is a subset of the monotone attractor if (monotone_att | target_set) == monotone_att: regular_att = attractor(arena, monotone_att, priority % 2, manager) # if priority is odd if priority % 2: partial_winning_region_player1 = partial_winning_region_player1 | regular_att else: partial_winning_region_player0 = partial_winning_region_player0 | regular_att return buchi_partial_solver( arena.subarena(~regular_att, manager), partial_winning_region_player0, partial_winning_region_player1, manager) else: target_set = target_set & monotone_att return arena, partial_winning_region_player0, partial_winning_region_player1
def psolB(bdd, g): """ This is Charly's implementation of psolB. """ if g.player0_vertices == bdd.false and g.player1_vertices == bdd.false: return bdd.false, bdd.false d = max(g.priorities[0].keys()) for curr_p in range(0, d + 1): player = curr_p % 2 x = g.priorities[0][curr_p] & (g.player0_vertices | g.player1_vertices) f_old = bdd.false while not (x == bdd.false or x == f_old): f_old = x m_attr_x = monotone_attractor_cha(bdd, g, player, x, curr_p) if (m_attr_x | x) == m_attr_x: attr_ma = attractor(g, m_attr_x, player, bdd) ind_game = g.subarena(~attr_ma, bdd) (w_0, w_1) = psolB(bdd, ind_game) if player == 0: w_0 = w_0 | attr_ma else: w_1 = w_1 | attr_ma return w_0, w_1 else: x = x & m_attr_x return bdd.false, bdd.false
def ziel_with_psolver(g, bdd): """ Solve the parity game provided in arena using a combinations of the recursive algorithm and the partial solver implemented using bdds. This is Charly's implementation. :param g: a game arena :type g: Arena :param bdd: the BDD manager :type bdd: dd.cudd.BDD :return: the solution of the provided parity game, that is the set of vertices won by each player :rtype: (dd.cudd.Function, dd.cudd.Function) """ if g.player0_vertices == bdd.false and g.player1_vertices == bdd.false: return bdd.false, bdd.false z_0, z_1 = psolB(bdd, g) g_bar = g.subarena(~(z_0 | z_1), bdd) if (g_bar.player0_vertices | g_bar.player1_vertices) == bdd.false: return z_0, z_1 p_max = max(g_bar.priorities[0].keys()) # get max priority occurring in g i = p_max % 2 x = attractor(g_bar, g_bar.priorities[0][p_max], i, bdd) g_ind = g_bar.subarena(~x, bdd) (win_0, win_1) = ziel_with_psolver(g_ind, bdd) if i == 0: win_i = win_0 win_i_bis = win_1 else: win_i = win_1 win_i_bis = win_0 if win_i_bis == bdd.false: if i == 0: return z_0 | win_i | x, z_1 else: return z_0, z_1 | win_i | x else: x = attractor(g_bar, win_i_bis, 1 - i, bdd) g_ind = g_bar.subarena(~x, bdd) (win_0, win_1) = ziel_with_psolver(g_ind, bdd) if i == 0: return z_0 | win_0, z_1 | win_1 | x else: return z_0 | win_0 | x, z_1 | win_1
def recursive(arena, manager): """ Solve the parity game provided in arena using the recursive algorithm implemented with bdds. :param arena: a game arena :type arena: Arena :param manager: the BDD manager :type manager: dd.cudd.BDD :return: the solution of the provided parity game, that is the set of vertices won by each player :rtype: (dd.cudd.Function, dd.cudd.Function) """ winning_region_player0 = manager.false # winning region of player 0 winning_region_player1 = manager.false # winning region of player 1 # if the game is empty, return the empty regions if arena.player0_vertices == manager.false and arena.player1_vertices == manager.false: return winning_region_player0, winning_region_player1 else: max_occurring_priority = max(arena.priorities[0].keys()) # get max priority occurring in the arena # determining which player we are considering, if max_occurring_priority is odd : player 1 and else player 0 j = max_occurring_priority % 2 opponent = 0 if j else 1 # getting the opponent of the player # vertices with priority max_occurring_priority U = arena.priorities[0][max_occurring_priority] # getting the attractor A A = attractor(arena, U, j, manager) # The subgame G\A is composed of the vertices not in the attractor G_A = arena.subarena(~A, manager) # Recursively solving the subgame G\A winning_region_player0_G_A, winning_region_player1_G_A = recursive(G_A, manager) # depending on which player we are considering, assign regions to the proper variables # if we consider player1 if j: winning_region_player = winning_region_player1_G_A winning_region_opponent = winning_region_player0_G_A else: winning_region_player = winning_region_player0_G_A winning_region_opponent = winning_region_player1_G_A # if winning_region_opponent 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 winning_region_opponent == manager.false: # if we consider player1 if j: winning_region_player1 = winning_region_player1 | A winning_region_player1 = winning_region_player1 | winning_region_player else: winning_region_player0 = winning_region_player0 | A winning_region_player0 = winning_region_player0 | winning_region_player else: # compute attractor B B = attractor(arena, winning_region_opponent, opponent, manager) # The subgame G\B is composed of the vertices not in the attractor G_B = arena.subarena(~B, manager) # recursively solve subgame G\B winning_region_player0_G_B, winning_region_player1_G_B = recursive(G_B, manager) # depending on which player we are considering, assign regions to the proper variables # if we consider player1 if j: winning_region_player_bis = winning_region_player1_G_B winning_region_opponent_bis = winning_region_player0_G_B else: winning_region_player_bis = winning_region_player0_G_B winning_region_opponent_bis = winning_region_player1_G_B # the last step is to update the winning regions depending on which player we consider # if we consider player1 if j: winning_region_player1 = winning_region_player_bis winning_region_player0 = winning_region_player0 | winning_region_opponent_bis winning_region_player0 = winning_region_player0 | B else: winning_region_player0 = winning_region_player_bis winning_region_player1 = winning_region_player1 | winning_region_opponent_bis winning_region_player1 = winning_region_player1 | B return winning_region_player0, winning_region_player1
def buchi(bdd, g, i, f): return attractor(g, recur(bdd, g, i, f), i, bdd)
def buchi_solver_gen_inverted_players(arena, manager): """ k = nbr func @param arena: @type arena: @param manager: @type manager: @return: @rtype: """ max_priorities = [-1] * arena.nbr_functions # TODO check if this should be done in every recursive call for function_index in range(arena.nbr_functions): for priority, bdd in arena.priorities[function_index].items(): if (priority) > max_priorities[function_index]: max_priorities[function_index] = (priority) # Iterate over all 1-priority for prio_f_index in range(arena.nbr_functions): # arena.d[prio_f_index] max prio selon cette dimension ? TODO for curr_prio in range(max_priorities[prio_f_index] + 1): if curr_prio % 2 == 0 and not arena.priorities[prio_f_index][ curr_prio] == manager.false: u = arena.priorities[prio_f_index][curr_prio] u_bis = sup_prio_expr_odd(arena, manager, curr_prio, prio_f_index, max_priorities) w = attractor(arena, buchi_inter_safety(manager, arena, 1, u, u_bis), 1, manager) # ca change pas # pour buchi inter safety j'ai du changer l'ordre dans le monotone if not w == manager.false: ind_game = arena.subarena(~w, manager) (z0, z1) = buchi_solver_gen_inverted_players( ind_game, manager) return z0, z1 | w even_priorities = [[] for _ in range(arena.nbr_functions)] for prio_f_index in range(arena.nbr_functions): # changee en 1 => devrait etre toutes les imapir, le + 1 doit il rester +1 ou bien +2 pour aller au dessu alors que la prio existe pasTODO for curr_prio in range(1, max_priorities[prio_f_index] + 1, 2): if not arena.priorities[prio_f_index][curr_prio] == manager.false: even_priorities[prio_f_index].append(curr_prio) all_combinations = product(*even_priorities) # ici ca devrait etre des odd # Iterate over all 0-priority vectors for curr_comb in all_combinations: u = [ arena.priorities[l][curr_comb[l]] for l in range(arena.nbr_functions) ] u_bis = sup_one_prio_even(arena, manager, curr_comb, max_priorities) w = attractor(arena, buchi_inter_safety_gen(manager, arena, u, u_bis), 0, manager) if not w == manager.false: ind_game = arena.subarena(~w, manager) (z0, z1) = buchi_solver_gen_inverted_players(ind_game, manager) return z0 | w, z1 return manager.false, manager.false
def buchi_solver_gen(arena, manager): """ k = nbr func @param arena: @type arena: @param manager: @type manager: @return: @rtype: """ max_priorities = [-1] * arena.nbr_functions # TODO check if this should be done in every recursive call for function_index in range(arena.nbr_functions): for priority, bdd in arena.priorities[function_index].items(): if (priority) > max_priorities[function_index]: max_priorities[function_index] = (priority) # Iterate over all 1-priority for prio_f_index in range(arena.nbr_functions): # arena.d[prio_f_index] max prio selon cette dimension ? for curr_prio in range(max_priorities[prio_f_index] + 1): if curr_prio % 2 == 1 and not arena.priorities[prio_f_index][ curr_prio] == manager.false: u = arena.priorities[prio_f_index][curr_prio] u_bis = sup_prio_expr_even(arena, manager, curr_prio, prio_f_index, max_priorities) w = attractor(arena, buchi_inter_safety(manager, arena, 1, u, u_bis), 1, manager) if not w == manager.false: ind_game = arena.subarena(~w, manager) (z0, z1) = buchi_solver_gen(ind_game, manager) return z0, z1 | w even_priorities = [[] for _ in range(arena.nbr_functions)] for prio_f_index in range(arena.nbr_functions): for curr_prio in range(0, max_priorities[prio_f_index] + 1, 2): if not arena.priorities[prio_f_index][curr_prio] == manager.false: even_priorities[prio_f_index].append(curr_prio) all_combinations = product(*even_priorities) # Iterate over all 0-priority vectors for curr_comb in all_combinations: u = [ arena.priorities[l][curr_comb[l]] for l in range(arena.nbr_functions) ] u_bis = sup_one_prio_odd(arena, manager, curr_comb, max_priorities) w = attractor(arena, buchi_inter_safety_gen(manager, arena, u, u_bis), 0, manager) if not w == manager.false: ind_game = arena.subarena(~w, manager) (z0, z1) = buchi_solver_gen(ind_game, manager) return z0 | w, z1 return manager.false, manager.false
def buchi_inter_safety_gen(bdd, g, f, s): attr_adv_f = attractor(g, s, 1, bdd) g_bar = g.subarena(~attr_adv_f, bdd) return buchi_gen(bdd, g_bar, f)