Exemple #1
0
def psol_edge_removal(g, W1, W2):
    """
    PSOL partial solver using fatal attractors and edge removal.
    /!\ This modifies the arena by removing edges.
    :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 node in sort_nodes_by_priority(g):

        MA, rest = monotone_attractor(g, node)

        if node in MA:

            priority = g.get_node_priority(node)

            att, complement = attractor(g, MA, priority % 2)

            if priority % 2 == 0:
                W1.extend(att)
            else:
                W2.extend(att)

            return psol(g.subgame(complement), W1, W2)

        # removing edges which can't be used since they would create a fatal attractor
        for successor in g.get_successors(node):
            if successor in MA:
                # TODO check efficiency of edge removal
                g.remove_successor(node, successor)
                g.remove_predecessor(successor, node)

    return g, W1, W2
Exemple #2
0
def buchi_inter_safety(g, u, s):
    """
    Solves a Buchi inter safety game where player 0 has that objective. U is the set to be visited infinitely often
    and s is to be avoided.
    :param g: a game graph.
    :param u: the set od nodes to be visited infinitely often.
    :param s: the set of nodes to be avoid.
    :return: the winning regions w_0, w_1.
    """

    a, not_a = attractors.attractor(g, s, 1)
    g_reduced = g.subgame(not_a)
    return buchi.buchi_classical(g_reduced, u)
Exemple #3
0
def generalized_buchi_inter_safety(g, sets, s):
    """
    Solves a generalized Buchi inter safety game where player 0 has that objective.
    sets contains the sets to be visited infinitely often
    and s is to be avoided.
    :param g: a game graph.
    :param sets: the sets of nodes to be visited infinitely often.
    :param s: the set of nodes to be avoid.
    :return: the winning regions w_0, w_1.
    """

    a, not_a = attractors.attractor(g, s, 1)
    g_reduced = g.subgame(not_a)
    return generalized_buchi_classical(g_reduced, sets)
Exemple #4
0
def avoid_set_classical(g, b):
    """
    This procedure is part of buchi_classical and computes the set of states in G from which player 1
    can avoid reaching b. Assumes player 1 has the Buchi objective.
    :param g: a game graph.
    :param B: a set of nodes in g.
    :return: w the set of nodes from which player 1 can avoid reaching b.
    """
    if DEBUG_PRINT:
        print("----------------------------")
        print("     AVOID SET")

    r, t_r = attractors.attractor(g, b, 0)

    if DEBUG_PRINT: print("     R " + str(r) + " TR " + str(t_r))

    w, not_w = attractors.attractor(g, t_r, 1)

    if DEBUG_PRINT:
        print("     W " + str(w) + " NOT W " + str(not_w))
        print("----------------------------")

    return w, not_w
Exemple #5
0
def generalized_buchi_classical(g, sets):
    """
    Classical algorithm in O(k.n.m) to solve genralized Buchi games. Assumes player 1 has the generalized Buchi
    objective.
    :param g: a game graph.
    :param sets: a list of sets of nodes in g.
    :return: w_0, w_1 the solution of the Buchi game.
    """

    # We don't want g to be modified # TODO check if this is required as subgames don't modify the object
    g = copy.deepcopy(g)
    nbr_of_sets = len(sets)
    # This is a reapeat until loop which creates a fixpoint
    while True:

        for l in range(nbr_of_sets):

            y, s = attractors.attractor(g, sets[l], 0)

            if s != []:
                break

        d, not_d = attractors.attractor(g, s, 1)

        # TODO check if I really need to compute the intersection here or I can just ignore it and handle it in
        # the attractor directly.
        sets[l] = list(set(sets[l]).intersection(set(g.get_nodes())))
        # TODO check sets operations for removing nodes, check bi sets

        g = g.subgame(not_d)

        if len(d) == 0:

            break
    # TODO return the remaining game and the other winning region
    return g.get_nodes()
Exemple #6
0
def buchi_inter_safety_player(g, u, s, j):
    """
    Solves a Buchi inter safety game where player j has that objective. U is the set to be visited infinitely often
    and s is to be avoided.
    :param g: a game graph.
    :param u: the set od nodes to be visited infinitely often.
    :param s: the set of nodes to be avoid.
    :param j: the player with the Buchi inter safety objective.
    :return: the winning regions w_0, w_1.
    """

    a, not_a = attractors.attractor(g, s, (j + 1) % 2)
    g_reduced = g.subgame(not_a)
    # TODO check if it is required to return both winning regions
    return buchi.buchi_classical_player(g_reduced, u, j)
Exemple #7
0
def inlined_generalized_buchi_inter_safety(g, sets, U):
    nbr_of_sets = len(sets)
    # This is a reapeat until loop which creates a fixpoint
    while True:
        for l in range(nbr_of_sets):
            y, s = attractors.safe_attractor(g, sets[l], U, 0)
            if s != []:
                break  # we've found a dimension in which Pl 1 can win
        d, not_d = attractors.attractor(g, s, 1)
        sets[l] = list(set(sets[l]).intersection(set(g.get_nodes())))
        # TODO check sets operations for removing nodes, check bi sets
        g = g.subgame(not_d)
        if len(d) == 0:
            break
    # TODO return the remaining game and the other winning region
    return g.get_nodes()
Exemple #8
0
def psol(g, W1, W2):
    """
    PSOL partial solver 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.
    """

    for node in sort_nodes_by_priority(g):

        if DEBUG_PRINT: print("Computing for node " + str(node))

        # compute monotone attractor
        MA, rest = monotone_attractor(g, node)

        if DEBUG_PRINT:
            print(" MA " + str(MA) + " Player " +
                  str(g.get_node_player(node)) + "\n")

        # if attractor is fatal
        if node in MA:

            if DEBUG_PRINT: print("Node " + str(node) + " in MA ")

            priority = g.get_node_priority(node)

            # attractor to the fatal attractor, winning for player priority % 2
            att, complement = attractor(g, MA, priority % 2)

            if priority % 2 == 0:
                W1.extend(att)
            else:
                W2.extend(att)

            return psol(g.subgame(complement), W1, W2)

    return g, W1, W2
def psolQ_generalized(g, W1, W2):
    """
    Adaptation of partial solver psolQ for generalized parity games.
    :param g: a game graph.
    :return: a partial solution g', W1', W2' in which g is an unsolved sub-game of g and W1', W2' are included in the
    two respective winning regions of g.
    """

    # base case : game is empty
    if g.get_nodes() == []:
        if DEBUG_PRINT: print("Base case return")
        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])  # 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 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
    even_indexes = [
        0
    ] * nbr_func  # index for each function to go trough its priorities

    if DEBUG_PRINT:
        print("Priorities " + str(priorities))
        print("Sizes " + str(sizes))
        print("Even priorities " + str(even_priorities))
        print("Even sizes " + str(even_sizes))

    # 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 priority in the game i.e. not gone trough the list of all priorities
    while not all(indexes[w] == sizes[w] for w in range(nbr_func)):

        if DEBUG_PRINT:
            print("\nPriorities " + str(priorities) + " Indexes " +
                  str(indexes))

        # for each function, 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("     odd : 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("           odd : index " + str(indexes[i]) +
                          " element " + 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
                # TODO this is PSOLB for player 1, coudl use PSOLQ

                cache = set()

                while cache != target_set and target_set != empty_set:

                    cache = target_set

                    # uses monotone attractor from psolB generalized
                    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 psolQ_generalized(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). Also we know that the lists of even priorities are not empty

        if DEBUG_PRINT:
            print("\nEven indexes " + str(even_indexes) + " Even priorities " +
                  str(even_priorities))

        # if lists of priorities end with odd priorities we may reach this place and no even priorities remain
        # this means we have handled every priorities and can return. Without this, if every even priorities has
        # been handled already we are 'stuck' with the last even priority of each function and keep recomputing with
        # these even priorities even though we have already done it. Since even indexes can only go to even_size[m]-1
        # the element always exists but we should check whether this is reach to prevent computing the same thing
        # over and over. The previous condition stating all(even_sizes[m] == even_indexes[m] for m in range(nbr_func))
        # could not happen as indexes for even don't go up to sizes

        # TODO all below needs to be computed the first time we reach all(even_sizes[m]-1 == even_indexes[m]
        # but not the times after. A solution is to let go the even indexes to even_sizes[m] which is an index that does
        # not exist but put the statement below going to the next iteration every time its true (done with even, only
        # consider odd). Problem : they don't all reach the even_sizes[m] in a synchronised way so one could be too far
        # actually we are done when for all of them they are to even_sizes[m]-1 then we set them all to even_sizes[m]
        # and use the condition below (or even simply a flag).
        # if all(even_sizes[m] == even_indexes[m] for m in range(nbr_func)):
        #    continue

        # we now retrieve the priorities we want to consider in this iteration
        current_priorities = [
            even_priorities[j][even_indexes[j]] for j in range(nbr_func)
        ]

        if DEBUG_PRINT:
            print("Even priorities considered " + str(current_priorities) +
                  "\n")

        # now we create the layered fatal attractor for the given current priorities vector

        # set containing nodes of parity 0 or priority below the required one for all functions, used for potential node
        # this is used to create target sets and is refined until reaching a fixpoint in the layered attractor
        # nodes which don't respect this cannot be used as targets
        # TODO this seems weird compared to PSOLQ
        potential_nodes = set(
            filter(
                lambda z: all(g.nodes[z][u + 1] % 2 == 0 or g.nodes[z][
                    u + 1] <= current_priorities[u] for u in range(nbr_func)),
                g.nodes.iterkeys()))

        if DEBUG_PRINT:
            print("Potential nodes larger than" + str(current_priorities) +
                  " : " + str(potential_nodes))

        cache = set()

        while cache != potential_nodes and potential_nodes != empty_set:

            if DEBUG_PRINT: print("Potential nodes " + str(potential_nodes))

            cache = potential_nodes

            # create the layered attractor, with the even priorities and the even indexes (to compute every layer)
            # and the potential nodes to consider. Yields the layered attractor and the star nodes of the last layer
            MA, rest, star = layered_attractor(g, even_priorities,
                                               even_indexes, potential_nodes)

            # if the potential nodes v are subset of the star, then (v, M(v)) is in the attractor for each target v
            # making it fatal
            if potential_nodes.issubset(star):

                if DEBUG_PRINT:
                    print("Set " + str(potential_nodes) + " in MA ")

                # TODO does star have more nodes than potential_nodes ? I think it's logical that nodes in star are
                # winning, but are there more than in potential and would they have been caught in attractor anyways ?
                att, complement = attractor(g, star, 0)

                W1.extend(att)

                if DEBUG_PRINT:
                    print("TEMP W1 " + str(W1))
                    print("TEMP W2 " + str(W2))
                    print("TEMP REST " + str(complement))

                return psolQ_generalized(g.subgame(complement), W1, W2)

            else:
                potential_nodes = potential_nodes.intersection(star)

        # we can also advance in the list of even priorities
        for l in range(nbr_func):
            if even_indexes[l] < even_sizes[
                    l] - 1:  # TODO added -1, handle as described above
                even_indexes[l] += 1

    return g, W1, W2
Exemple #10
0
def disj_parity_win_with_partial(g, maxValues, k, u, partial):
    """
    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
    :param partial: partial solver.
    :return: W1, W2 the winning regions in the game for player 1 and player 2
             (for the original game, without complement)
    """

    rest, W0, W1 = partial(g, [], [])  # call to the partial solver

    if len(rest.nodes) == 0:
        return W0, W1

    # For the correctness argument to work, and for the base case too,
    # we need the max value of each priority to be odd!
    assert(all(m % 2 == 1 for m in maxValues))

    # Base case : all maxValues are 1 or the game is empty
    if all(value == 1 for value in maxValues) or len(rest.get_nodes()) == 0:
        return rest.get_nodes(), []

    # FIXME: the code below is a hacked base case, remove it when the bug is
    # fixed. Clement added this condition, which states that if there is only
    # one node left with only odd priorities, it is winning for player 1
    # (since we work with complemented priorities in this algorithm)
    # if len(g.nodes) == 1 and all(value % 2 == 1 for value in g.nodes[g.get_nodes()[0]][1:]):
    #     return g.get_nodes(), []
    for i in range(k):

        # We only consider priority functions according to which every value is not 1
        if maxValues[i] != 1:

            # if u <= 4:
            #     print("-" * u + str(i))
            attMaxOdd, compl_attMaxOdd = reachability.attractor(rest, ops.i_priority_node_function_j(rest, maxValues[i], i + 1),
                                                                0)
            G1 = rest.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)
            while True:
                h1_old_len = len(H1.get_nodes())
                copy_maxValues = copy.copy(maxValues)
                copy_maxValues[i] -= 2
                # sanity check: on recursive calls we have less priorities
                # It should not be the case that negative max priorities occur as we only consider functions
                # in which the max odd value is > 1. Negative values happened when we considered the following
                # instruction when maxValue is 1.
                assert(copy_maxValues[i] >= 0)
                assert(copy_maxValues[i] == maxValues[i] - 2)
                # end of sanity check
                W1, W2 = disj_parity_win_with_partial(H1, copy_maxValues, k, u + 1, partial)
                # sanity check: if all priorities were odd, then W1 union G1.V should be g.V
                assert(set(G1.get_nodes()).union(set(W1)) != rest.get_nodes()
                       or any(rest.get_node_priority_function_i(n, i) % 2 == 0
                              for n in rest.get_nodes()))
                # end of sanity check

                if len(G1.get_nodes()) == 0 or set(W2) == set(H1.get_nodes()):
                    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(G1, maxValues[i] - 1, i + 1), 1)
                H1 = G1.subgame(compl_E)
                # assert(len(H1.get_nodes()) < h1_old_len)

            # checks after the end of the loop (base cases, essentially)
            if set(W2) == set(H1.get_nodes()) and len(G1.get_nodes()) > 0:
                assert(len(G1.get_nodes()) > 0)  # otherwise this makes no sense!
                B, compl_B = reachability.attractor(rest, G1.get_nodes(), 1)
                # sanity check: we always do a recursive call on a smaller game
                # and so necessarily B is non-empty
                assert(len(B) > 0)
                # end of sanity check
                W1, W2 = disj_parity_win_with_partial(rest.subgame(compl_B), maxValues, k, u + 1, partial)
                B.extend(W2)
                return W1, B

    return rest.get_nodes(), []
Exemple #11
0
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
Exemple #12
0
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
Exemple #13
0
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
Exemple #14
0
def psol_generalized(g, W1, W2):
    """
    Adaptation of psol partial solver to generalized parity 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.
    """

    # TODO we can add ordering on the nodes, such as considering those with odd priorities first

    # we iterate over the node, node_info in the game
    for node, info in g.nodes.iteritems():

        found_odd = False  # have we found an odd priority for the node

        # iterate over priority functions
        for i in range(1, len(info)):

            # one of the priorities is odd
            if info[i] % 2 == 1:

                found_odd = True

                # note : we can reuse the monotone attractor of player 0 for player 1 but it is a stronger condition
                # than needed indeed, we ask that no bigger priority is visited according to every function, instead of
                # focusing on one

                # compute the monotone attractor for this node, for player 1 on the correct function
                MA, rest = monotone_attractor_player1(g, node, i)

                # if the attractor is fatal
                if node in MA:

                    if DEBUG_PRINT: print("Node " + str(node) + " in MA ")

                    att, complement = attractor(g, MA, 1)

                    W2.extend(att)

                    return psol_generalized(g.subgame(complement), W1, W2)

        # if we get here, then every priority is even or none of the computed attractors are fatal

        # if every priority is even
        if not found_odd:

            # compute the monotone attractor for this node, for player 0 on the correct function
            MA, rest = monotone_attractor_player0(g, node, info, 0)

            # if the attractor is fatal
            if node in MA:

                if DEBUG_PRINT: print("Node " + str(node) + " in MA ")

                att, complement = attractor(g, MA, 0)

                W1.extend(att)

                return psol_generalized(g.subgame(complement), W1, W2)

    return g, W1, W2