예제 #1
0
def build_safety_game(g, j, lam):
    """
    Build the game on which we will solve a safety objective.
    :param g: the game to solve.
    :param j: the player for which we want to create the new game.
    :param lam: the maximum size of a window.
    :return: g' the new game created. Note that we don't return beta like we do in the theory because we define a unique id for beta that is -1.
    """
    g_new = Graph()
    for s in g.get_nodes():  # O(n) * O(d) * O(lambda)
        for c in range(0, ops.max_priority(g) + 1):  #O(d) * O(lambda)
            for l in range(0, lam):  #O(lambda)
                s_player = g.get_node_player(s)
                #node id is the concatenation of s, c and l plus letters to make the separation.
                # Ex : if we have a node s = 5, c = 10, l = 2 we would have the node id = s5c10l2.
                #this way we have unique id and we can easily find a specific node if we want to
                node_id = compute_id(s, c, l)
                #node informations are in the following format : (player, base node id, maximum color on the current window, step on current window) aka (s_player, s, c, l)
                g_new.add_node(node_id, (s_player, s, c, l))
    #add the state beta that detects the lambda-bad windows. We use a special identification and descriptor for this state
    g_new.add_node(-1, (-1, -1, -1))
    g_new.add_successor(-1, -1)
    g_new.add_predecessor(-1, -1)

    #iterate on all the node of g_new
    for node_id in g_new.get_nodes(
    ):  # (O(m) * O(d) * O(lambda)) (the two loops make a complexity O(m))
        if node_id != -1:
            (s, c, l) = g_new.nodes[node_id][1:4]
            trans_list = g.get_successors(s)
            #iterate on all successors (in g) of the node s
            for s2 in trans_list:
                #when we encounter a c of parity j we know we can reset the window and go to the next state. So we add the corresponding transition in the new game
                if ((c % 2) == j):
                    #we want to add a transition from (s, c, l) towards (s2, c2, l2)
                    #compute c2 to be the color of s2
                    c2 = g.get_node_priority(s2)
                    #compute the id of the node we want to add a transition toward
                    node_id_trans = compute_id(s2, c2, 0)

                #when we are just continuing on the current window
                elif ((c % 2) != j and l < (lam - 1)):
                    #we want to add a transition from (s, c, l) towards (s2, c2, l2)
                    #compute c2 to be the maximum between c and the color of s2
                    c2 = max(c, g.get_node_priority(s2))
                    #compute the id of the node we want to add a transition toward
                    node_id_trans = compute_id(s2, c2, l + 1)

                #when we just detected a lambda-bad window
                else:
                    #we want to add a transition from (s, c, l) towards beta
                    node_id_trans = -1

                #we add the transition we just compute to g_new
                g_new.add_successor(node_id, node_id_trans)
                g_new.add_predecessor(node_id_trans, node_id)

    return (g_new)
예제 #2
0
def get_j_colors(g, j):
    """
    Give all the colors of parity j on the game g
    :param g: the game to solve.
    :param j: the player for who we want to get the colors.
    :return: all the colors of parity j on the game g.
    """
    d = ops.max_priority(g)
    j_colors = []
    for i in range(1, d + 1):
        if i % 2 == j:
            j_colors.append(i)
    return j_colors
예제 #3
0
def compute_gdagger(g, j):
    """
    Compute and return the product game g_dagger from g for the player j.
    Total complexity : O(n) + O(n*d) + O(n*d) * O(n) = O(n^2*d)
    :param g: the game on which g_dagger is based.
    :param j: the player for who g_dagger is computed.
    :return: the product game g_dagger from g for the player j.
    """
    #creating a list containing all colors in the game
    d = ops.max_priority(g)  #O(n)
    colors = range(0, d + 1)

    g_dagger = Graph()
    #adding all states in g_dagger. The stated are created doing a cartesian product between g states and colors
    for s in g.get_nodes():  #O(n*d)
        for v in colors:  #O(d)
            #node_id is a concatenation of the node id in g and the color v that will be added in the node_info with letter to identify each part.
            #Ex: s = 10, v = 8 => node_id = s10v8
            node_id = compute_id(s, v)
            #computing the color of the state
            s_color = g.get_node_priority(s)
            node_color = -1
            if v % 2 == j:
                node_color = s_color
            else:
                node_color = v
            #player of s is the same as the player of the node we are creating
            s_player = g.get_node_player(s)
            #node info take this form : (player, base node id, color computed, v : supposed to be the maximal color encoutered on the path)
            node_info = (s_player, s, node_color, v)
            #add the node in g_dagger
            g_dagger.add_node(node_id, node_info)

    #now adding the trasitions between the states.
    #we add a trasition ((s1, v1), (s2, v2)) iff (s1, s2) is a transition in g and if v2 = max(v1, c(s2))
    for s1 in g_dagger.get_nodes():  #O(n*d) * O(n) = O(n^2 * d)
        #getting the id of the corresponding node in g
        s1_id_g = g_dagger.nodes[s1][1]
        for s2 in g.get_successors(s1_id_g):  #O(n)
            #getting the color of s2 in g
            s2_color = g.get_node_priority(s2)
            #getting v1  from the state s1
            v1 = g_dagger.nodes[s1][3]
            v2 = max(v1, s2_color)
            #with that we've got (s2, v2) towards which we want to add a transition from s1 (s1 is already a state of g_dagger we can considerate it as (s1_id_g, v1))
            #getting the node id of (s2, v2) in g_dagger
            node_id_trans = compute_id(s2, v2)
            g_dagger.add_successor(s1, node_id_trans)  #amortized O(1)
            g_dagger.add_predecessor(node_id_trans, s1)  #amortized O(1)

    return g_dagger
예제 #4
0
def weak_parity_solver(g):
    """
    Weak parity games solver. This is an implementation of the algorithm presented in chapter 4 of the report.
    :param g: the game to solve.
    :return: the solution in the following format : (W_0, sigma_0), (W_1, sigma_1).
    """

    h = g  # the game we work on
    i = ops.max_priority(h)  # Maximum priority occurring in g

    W0 = []  # winning region for player 0
    W1 = []  # winning region for player 1
    sigma0 = defaultdict(lambda: -1)  # winning strategy for player 0
    sigma1 = defaultdict(lambda: -1)  # winning strategy for player 1

    # considering priorities from i to 0 in decreasing order
    for k in range(i, -1, -1):
        current_player = k % 2  # get current player

        # calling the reachability solver on the game h with target set "nodes of priority k" and for the current player
        (Ak, eta), (Bk, nu) = rs.reachability_solver(h,
                                                     ops.i_priority_node(h, k),
                                                     current_player)

        # depending on the current player, we add the nodes of Ak in a winning region and update strategies
        if current_player == 0:
            W0.extend(Ak)
            sigma0.update(eta)
            sigma1.update(nu)

        else:
            W1.extend(Ak)
            sigma1.update(eta)
            sigma0.update(nu)

        h = h.subgame(
            Bk)  # updates the current game (only keeping nodes in Bk)

    return (W0, sigma0), (W1, sigma1)
예제 #5
0
def disj_parity_win2(g, maxValues, k, u):
    """
    Recursive solver for generalized parity games. Uses the algorithm presented in
    http://www2.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-144.html
    This is used for testing purposes.
    :param g: the game to solve
    :param maxValues: the maximum value for each priority function
    :param k: the number of priority functions
    :param u: integer for testing purposes
    :return: W1, W2 the winning regions in the game for player 1 and player 2 (for the base game)
    """

    # Base case : all maxValues are 1 or the game is empty
    if all(value == 1 for value in maxValues) or len(g.nodes) == 0:
        #print(str(u*2*" ")+"it-"+str(u)+" return on base case")
        return g.get_nodes(), []

    for i in range(k):
        max = ops.max_priority(g)
        if max % 2 == 0:
            even = max
            attMaxOdd, compl_attMaxOdd = [], []
            #print(str(u*2*" ")+"it-"+str(u)+" maxOdd-"+str(maxValues[i])+" attMaxOdd-"+str(attMaxOdd)+" "+"complAttMaxOdd-"+str(compl_attMaxOdd))
            G1 = g
            attMaxEven, compl_attMaxEven = reachability.attractor(
                G1, ops.i_priority_node_function_j(G1, max, i + 1), 1)
            #print(str(u*2*" ")+"it-"+str(u)+" maxEven-"+str(maxValues[i]-1)+" attMaxEven-"+str(attMaxEven)+" "+"complAttMaxEven-"+str(compl_attMaxEven))
            H1 = G1.subgame(compl_attMaxEven)
            j = 0
            #print(str(u*2*" ")+"it-"+str(u)+" G\n"+str(G1))
            #print(str(u*2*" ")+"it-"+str(u)+" H\n"+str(H1))
        else:
            even = max - 1
            attMaxOdd, compl_attMaxOdd = reachability.attractor(
                g, ops.i_priority_node_function_j(g, max, i + 1), 0)
            # print(str(u*2*" ")+"it-"+str(u)+" maxOdd-"+str(maxValues[i])+" attMaxOdd-"+str(attMaxOdd)+" "+"complAttMaxOdd-"+str(compl_attMaxOdd))
            G1 = g.subgame(compl_attMaxOdd)
            attMaxEven, compl_attMaxEven = reachability.attractor(
                G1, ops.i_priority_node_function_j(G1, max - 1, i + 1), 1)
            # print(str(u*2*" ")+"it-"+str(u)+" maxEven-"+str(maxValues[i]-1)+" attMaxEven-"+str(attMaxEven)+" "+"complAttMaxEven-"+str(compl_attMaxEven))
            H1 = G1.subgame(compl_attMaxEven)
            j = 0
            # print(str(u*2*" ")+"it-"+str(u)+" G\n"+str(G1))
            # print(str(u*2*" ")+"it-"+str(u)+" H\n"+str(H1))
        while True:

            j += 1
            copy_maxValues = copy.copy(maxValues)
            copy_maxValues[i] -= even - 1
            W1, W2 = disj_parity_win2(H1, copy_maxValues, k, u + 1)
            #print(str(u * 2 * " ") + "it-" + str(u)+"-"+str(j) + " W1-" + str(W1) + " W2-" + str(W2))

            #print("W1 "+str(W1))
            #print("W2 "+str(W2))
            #print("game " + str(g) + "att " + str(attMaxOdd) + "compl " + str(compl_attMaxOdd))
            #print("stop "+str(set(W2))+" "+str(set(H1.get_nodes()))+" val "+ str(set(W2) == set(H1.get_nodes())))
            #break

            # cette cond etait en dessous de lautre et lautre prennait precedence quand on avait les 2
            #print(len(G1.nodes))
            if len(G1.nodes) == 0:
                #print("G empty")
                break

            if set(W2) == set(H1.get_nodes()):
                #print("hello")
                B, compl_B = reachability.attractor(g, G1.get_nodes(), 1)
                W1, W2 = disj_parity_win2(g.subgame(compl_B), maxValues, k,
                                          u + 1)
                #print("re "+str(B)+" "+str(W1)+" "+str(W2))
                B.extend(W2)
                return W1, B
            #break
            T, compl_T = reachability.attractor(G1, W1, 0)
            G1 = G1.subgame(compl_T)
            E, compl_E = reachability.attractor(
                G1, ops.i_priority_node_function_j(g, even, i + 1), 0)
            H1 = G1.subgame(compl_E)
            #break
        #break
    return g.get_nodes(), []
예제 #6
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
예제 #7
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)