Ejemplo n.º 1
0
def solve_fixwp(g, j, lam):
    """
    Compute the winning state on the game g for the player j and the objective FixWP_j(lam). To do this, we will construct a new game g_new 
    with the function build_buchi_game and we will compute the winning regions of a co-buchi objective on it (actually we compute a buchi 
    objective in order to get the winning regions of the co-buchi objective). With that we will have the winning state of FixWP_j(lam).
    :param g: the game to solve.
    :param j: the player for who we want to retrieve the winning state of his objective.
    :param lam: the maximum size of a window.
    :return: a list containing the winning state of the player j.
    """
    #construction of the game on which we will solve the buchi/co_buchi objective.
    g_new = build_buchi_game(g, j, lam)
    #The map and the lambda function take all the node in g and return a list with each the opposite of each element, that corresponds to the beta states
    beta = map(lambda x: x * -1, g.get_nodes())
    #we compute the winning region of the player jbar for the objective Buchi(beta).
    w_buchi = buchi.basic_buchi(g_new, beta, ops.opponent(j))
    #now we will transform what we just got into the winning region of the play j for the objective Buchi(S \ beta).
    #instead of basically computing the winning region of the co-buchi objective, we directly keep the state s that we are interested in.
    #because the true winning region of the co_buchi objective contains state in the following format : (s, c, l).
    w_cobuchi = []
    for node_id in g_new.get_nodes():
        #to avoid taking beta states. str are considered as > 0
        if node_id > 0:
            s = g_new.nodes[node_id][1]
            if (node_id not in w_buchi) and (s not in w_cobuchi):
                w_cobuchi.append(s)

    #now we just have to return w_cobuchi because it is equal to the winning region of the player j for the objective FixWP_j(lam)
    return w_cobuchi
Ejemplo n.º 2
0
def solve_dirfixwp(g, j, lam):
    """
    Compute the winning state on the game g for the player j and the objective DirFixWP_j(lam). To do this, we will construct a new game g_new 
    with the function build_safety_game and we will compute the winning regions of a safety objective on it (actually we compute a reachability 
    objective in order to get the winning regions of the safety objective). With that we will have the winning state of DirFixWP_j(lam).
    :param g: the game to solve.
    :param j: the player for who we want to retrieve the winning state of his objective.
    :param lam: the maximum size of a window.
    :return: a list containing the winning state of the player j.
    """
    #construction of the game on which we will solve the safety/reachability objective.
    g_new = build_safety_game(g, j, lam)
    #we compute the winning region of the player jbar for the objective Reach(beta).
    w_reach = attr(g_new, [-1], ops.opponent(j))[0]
    #now we will transform what we just got into the winning region of the play j for the objective Reach(S \ beta).
    #instead of basically computing the winning region of the safety objective, we directly keep the state s that we are interested in.
    #because the true winning region of the safety objective contains state in the following format : (s, c, l).
    w_safety = []
    for node_id in g_new.get_nodes():
        if node_id != -1:
            s = g_new.nodes[node_id][1]
            if (node_id not in w_reach) and (s not in w_safety):
                w_safety.append(s)

    #now we just have to return w_safety because it is equal to the winning region of the player j for the objective DirFixWP_j(lam)
    return w_safety
Ejemplo n.º 3
0
def avoid_set_classical(g, t, j):
    """
    Compute a set of states that are winning for player j for the buchi objective.
    :param g: the game to solve.
    :param t: the target set such that t is included in g's states.
    :return: a set of states that are winning for player j for the buchi objective.
    """
    jbar = ops.opponent(j)
    a, abar = attr(g, t, j)  #O(n+m)
    w_current = attr(g, abar, jbar)[0]  #O(n+m)
    #the total is in O(2n+2m+n^2) = O(n^2)
    return w_current
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
def new_buchi(g, t, j):
    """
    Solve a Buchi objective on the game g for the target set t. This version of the algorithm works in O(n^2) where n is the number of states in g. 
    This algorithm returns the winning state for player j only. If we want to we can get the winning states for player jbar by taking S \ Wj.
    :param g: the game to solve.
    :param t: the target set.
    :return: the winning regions of player j for the objective Buchi(g, t).
    """
    #first loop index, we are iterating on k and i. Not directly but those two number change and so are the rest of the data
    k = 0
    #y is the attractor in g for player j towards the set t
    # x = s \ y so the states that are not in the attractor
    y, x = attr(g, t, j)  #O(m+n)
    #d the set of states that we removed from the current game g_k
    #s_k = s \ d, so the states that are not in the attractor
    d, s_k = attr(g, x, ops.opponent(j))  #O(n+m)
    #we have to sort the edge of g_k such that edge (u, v) are first with u such that u belongs to jbar and u is not in t
    #we do it only one time, once it's sorted it will stay sorted with the way subgames are created
    for v in g.get_nodes():  #O(m)
        first_succ = []
        last_succ = []
        for u in g.get_predecessors(v):
            if g.get_node_player(u) == ops.opponent(j) and u not in t:
                first_succ.append(u)
            else:
                last_succ.append(u)
        g.predecessors[v] = first_succ + last_succ
    #the current game g_k
    g_k = g.subgame(s_k)
    #target set in iteration k to correspond with the remaining state of g_k
    t_k = copy.deepcopy(t)
    #updating the iteration we are on
    k = k + 1
    #set of all the states we removed from g from that start
    U = d
    #second loop index
    i = 1
    #"repeat until" equivalent. We repeat the loop until we have i = log_2(n)
    while True:  #O(log(n))
        #construct a special graph with and ordering on inedges and color on states
        g_i_k = construct_g_i_k(g_k, i, k, j)  #O(n*m)
        #compute z_i_k to be a set containing states from g_i_k such that they are (i) red (color 1) without outedges in g_i_k or (ii) blue (color 0) in g_i_k
        z = []
        for s in g_i_k.get_nodes():  #O(n)
            s_color = g_i_k.get_node_priority(s)
            if (s_color == 1
                    and len(g_i_k.get_successors(s)) == 0) or s_color == 0:
                z.append(s)
        #actualing the target set to correspond with the current set of state
        t_k = [s for s in t_k if s in s_k]  #O(n^2)
        target = t_k + z
        #computing y with our new value
        #y is the attractor in g for player j towards the set t
        # x = s \ y so the states that are not in the attractor
        y, x = attr(g, target, j)  #O(m+n)
        #incrementing i
        i = i + 1
        #if x is not empty we compute a new d and remove it from the g_k
        if len(x) != 0:
            #d the set of states that we removed from the current game g_k
            #s_k = s \ d, so the states that are not in the attractor
            d, s_k = attr(g, x, ops.opponent(j))  #O(n+m)
            g_k = g_k.subgame(s_k)
            k = k + 1
            U = U + d
        #we stop when i = log_2(n)
        if len(g.get_nodes()) == 0 or i >= np.log2(len(g.get_nodes())):
            break
    #we will return all states but those who were removed ie we return the set of states S \ U
    Wj = [s for s in g.get_nodes() if s not in U]  #O(n^2)
    return Wj
Ejemplo n.º 6
0
def reachability_solver(g, U, j):
    """
    Reachability games solver. This function computes Att_j^g(U), the attractor for player j of target set U in the
    game g. That attractor is the winning region of player j who has the reachability objective in the game. The
    rest of the nodes are part of the winning region of player jbar (player j's opponent). Winning regions and
    strategies are computed and returned by the algorithm. The winning regions and strategies are return as two tuples
    to resemble pseudo-code and facilitate weak and strong parity solvers readability.
    :param g: the game graph.
    :param U: the target set.
    :param j: the player with the reachability objective.
    :return: two tuples : (w_j, strat_j), (w_jbar, strat_jbar) where w_j and w_jbar are lists containing nodes of their
    respective winning regions and where strat_j and strat_jbar are dictionaries containing winning strategies.
    """
    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)
    region_j = []  # winning region of j
    region_opponent = []  # winning region of j bar
    strat_j = defaultdict(lambda: -1)  # init strat for player j
    strat_opponent = defaultdict(lambda: -1)  # init strat for player jbar
    opponent = op.opponent(j)  # player j's opponent (jbar)

    # 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)
        region_j.append(node)  # add the node to the winning region list of j
        # if node belongs to j, set an arbitrary strategy for that node (we chose to select first successor)
        if g.get_node_player(node) == j:
            strat_j[node] = g.get_successors(node)[0]

    # 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
                    region_j.append(sbis)
                    strat_j[sbis] = s

                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
                        region_j.append(sbis)

    # for each node that is not marked we set its region to the opponent and find a successor for the strategy
    for node in g.get_nodes():
        if regions[node] != j:
            regions[node] = opponent
            region_opponent.append(node)
            if g.get_node_player(node) == opponent:
                for successor in g.get_successors(node):
                    if regions[successor] != j:
                        strat_opponent[node] = successor

    return (region_j, strat_j), (region_opponent, strat_opponent)
Ejemplo n.º 7
0
def strong_parity_solver_non_removed(g, removed):
    """
    Strong parity games solver. This algorithm is an implementation of the recursive algorithm used to solve parity
    games. It uses a list of non-removed nodes as a way to track sub-games. The attractor computation also uses this
    technique. The value at position i in the list is true if node i is removed from the original game arena.
    :param removed: the removed nodes.
    :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
    strat1 = defaultdict(lambda: -1)  # Winning strategy of player 0
    strat2 = defaultdict(lambda: -1)  # Winning strategy of player 1

    # if the game is empty, return the empty regions and strategies
    # removed is a bitarray, count(42) counts the occurrences of True
    # if every element in the list is true, every node is removed and the game is empty
    if removed.count(42) == len(g.nodes):
        return (W1, strat1), (W2, strat2)

    else:
        i = ops.max_priority_non_removed(g, removed)  # get max priority occurring in g, considering the removed nodes
        # 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

        # target set for the attractor : nodes of priority i, considering the removed nodes
        U = ops.i_priority_node_non_removed(g, i, removed)

        # getting the attractor A and the attractor strategy tau and discarding the region and strategy for the opponent
        # using the attractor function which considers the non removed nodes
        (A, tau1), (discard1, discard2) = reachability.reachability_solver_non_removed(g, U, j, removed)

        # The subgame G\A is composed of the nodes not in the attractor, thus the nodes of the opposite player's region
        # Copy the bitarray and remove the nodes of the attractor by setting their value to True in the list
        copy_removed1 = bitarray(removed)
        for nodes in A:
            copy_removed1[nodes] = True
        # Recursively solving the subgame G\A, solution comes as (W_0, sigma_0), (W_1, sigma_1)
        sol_player1, sol_player2 = strong_parity_solver_non_removed(g, copy_removed1)

        # depending on which player we are considering, assign regions and strategies 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, sig_j = sol_player1
            W_jbar, sig_jbar = sol_player2
        else:
            W_j, sig_j = sol_player2
            W_jbar, sig_jbar = sol_player1

        # if W'_jbar is empty we update the strategies and regions depending on the current player
        # the region and strategy 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)
                strat1.update(tau1)
                strat1.update(sig_j)
            else:
                W2.extend(A)
                W2.extend(W_j)
                strat2.update(tau1)
                strat2.update(sig_j)
        else:
            # compute attractor B and strategy nu
            (B, nu), (discard1, discard2) = reachability.reachability_solver_non_removed(g, W_jbar, opponent, removed)
            # The subgame G\B is composed of the nodes not in the attractor, so of the opposite player's winning region
            copy_removed2 = bitarray(removed)
            for nodes in B:
                copy_removed2[nodes] = True

            # recursively solve subgame G\B, solution comes as (W_0, sigma_0), (W_1, sigma_1)
            sol_player1_, sol_player2_ = strong_parity_solver_non_removed(g, copy_removed2)

            # depending on which player we are considering, assign regions and strategies 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, sig__j = sol_player1_
                W__jbar, sig__jbar = sol_player2_
            else:
                W__j, sig__j = sol_player2_
                W__jbar, sig__jbar = sol_player1_

            # the last step is to update the winning regions and strategies depending on which player we consider
            if j == 0:
                W1 = W__j
                strat1 = sig__j

                W2.extend(W__jbar)
                W2.extend(B)
                # nu is defined on W_jbar and must be replaced on W_jbar by the strategy sig_jbar
                # the replacement is implicit because updating sig_jbar last will overwrite already defined strategy
                strat2.update(nu)
                strat2.update(sig__jbar)
                strat2.update(sig_jbar)

            else:
                W2 = W__j
                strat2 = sig__j

                W1.extend(W__jbar)
                W1.extend(B)
                strat1.update(nu)
                strat1.update(sig__jbar)
                strat1.update(sig_jbar)

    return (W1, strat1), (W2, strat2)
Ejemplo n.º 8
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, 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
Ejemplo n.º 9
0
def strong_parity_solver(g):
    """
    Strong parity games solver. This is an implementation of the recursive algorithm used to solve parity games.
    :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
    strat1 = defaultdict(lambda: -1)  # Winning strategy of player 0
    strat2 = defaultdict(lambda: -1)  # Winning strategy of player 1

    # if the game is empty, return the empty regions and strategies
    if len(g.nodes) == 0:
        return (W1, strat1), (W2, strat2)

    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 the attractor strategy tau and discarding the region and strategy for the opponent
        (A, tau1), (discard1, discard2) = reachability.reachability_solver(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, sigma_0), (W_1, sigma_1)
        sol_player1, sol_player2 = strong_parity_solver(G_A)

        # depending on which player we are considering, assign regions and strategies 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, sig_j = sol_player1
            W_jbar, sig_jbar = sol_player2
        else:
            W_j, sig_j = sol_player2
            W_jbar, sig_jbar = sol_player1

        # if W'_jbar is empty we update the strategies and regions depending on the current player
        # the region and strategy 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)
                strat1.update(tau1)
                strat1.update(sig_j)
            else:
                W2.extend(A)
                W2.extend(W_j)
                strat2.update(tau1)
                strat2.update(sig_j)
        else:
            # compute attractor B and strategy nu
            (B, nu), (discard1, discard2) = reachability.reachability_solver(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, sigma_0), (W_1, sigma_1)
            sol_player1_, sol_player2_ = strong_parity_solver(G_B)

            # depending on which player we are considering, assign regions and strategies 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, sig__j = sol_player1_
                W__jbar, sig__jbar = sol_player2_
            else:
                W__j, sig__j = sol_player2_
                W__jbar, sig__jbar = sol_player1_

            # the last step is to update the winning regions and strategies depending on which player we consider
            if j == 0:
                W1 = W__j
                strat1 = sig__j

                W2.extend(W__jbar)
                W2.extend(B)
                # nu is defined on W_jbar and must be replaced on W_jbar by the strategy sig_jbar
                # the replacement is implicit because updating sig_jbar last will overwrite already defined strategy
                strat2.update(nu)
                strat2.update(sig__jbar)
                strat2.update(sig_jbar)

            else:
                W2 = W__j
                strat2 = sig__j

                W1.extend(W__jbar)
                W1.extend(B)
                strat1.update(nu)
                strat1.update(sig__jbar)
                strat1.update(sig_jbar)

    return (W1, strat1), (W2, strat2)