In [3]:
ex = np.array([[ 0.38042795,  0.97428829,  0.65831415,  0.9433624 ],
             [ 0.04150308 , 1.45256406 , 1.15764738 , 1.47280185],
             [-0.10862114 , 1.36035349 , 1.17000118 , 1.89851263],
             [-0.72437471 , 0.83167919 , 1.1901931  , 1.97947402]])

In [4]:
ex[0, 0]+ex[0, 1]>ex[1, 0]+ex[1, 1]
#If "False", (1, 1) PRD (0, 0).


Out[4]:
False

In [5]:
ex123 = ex[np.ix_((1,2,3),(1,2,3))]
print ex123


[[ 1.45256406  1.15764738  1.47280185]
 [ 1.36035349  1.17000118  1.89851263]
 [ 0.83167919  1.1901931   1.97947402]]

In [6]:
g123 = Game(ex123)
g123.is_mpmaximizer(0)
#Check (1, 1) is an MP-maximizer.


This strategy is a monotone potential maximizer.
[[  1.00000000e+00  -5.09636444e+02  -2.70958646e+03]
 [ -5.09636444e+02  -4.34338610e+02  -3.43936914e+02]
 [ -2.70958646e+03  -3.43936914e+02  -6.57060236e+00]]

In [7]:
g = Game(ex)

In [8]:
g.is_supermodular()


Out[8]:
True

In [9]:
for maximizer in range(4):
    print "Now checking ({0}, {0}).".format(maximizer)
    g.is_mpmaximizer(maximizer)
#about 5 min to calculate.


Now checking (0, 0).
This strategy is not a monotone potential maximizer.
Now checking (1, 1).
This strategy is not a monotone potential maximizer.
Now checking (2, 2).
This strategy is not a monotone potential maximizer.
Now checking (3, 3).
This strategy is not a monotone potential maximizer.

In [1]:
import FuncDesigner as fd
from openopt import LP
import numpy as np
from __future__ import division
import itertools as it
import matplotlib.pyplot as plt


    Seems like you are using OpenOpt from 
    commercial Enthought Python Distribution;
    consider using free GPL-licensed alternatives
    PythonXY (http://www.pythonxy.com) or
    Sage (http://sagemath.org) instead.
    

    Seems like you are using OpenOpt from 
    commercial Enthought Python Distribution;
    consider using free GPL-licensed alternatives
    PythonXY (http://www.pythonxy.com) or
    Sage (http://sagemath.org) instead.
    

In [2]:
def cut_matrix(matrix, raws, columns):
    """
    equilibriaを求めるのに必要.
    引数の「行」「列」を取り除く.
    """
    cutmatrix = np.delete(matrix, raws, 0)
    return np.delete(cutmatrix, columns, 1)

def cal_equilibrium(matrix):
    """
    与えられた利得行列に対し、BRが全ての戦略になるような相手の混合戦略(負の値を認める)を求める.
    条件は、利得行列が逆行列を持つこと.
    式で書くと、
    M = 与えられた行列(dim*dim次元の行列)
    x = 求める混合戦略(dim次元ベクトル)
    1 = 全ての成分が1のdim次元ベクトル
    c = 実数定数
    としたときに、
    Mx = c1 and x'1 = 1
    を満たすcとxを求め、xを返す.
    """
    ones = np.ones(matrix.shape[0])
    if np.linalg.matrix_rank(matrix) == matrix.shape[0]:
        x = np.linalg.solve(matrix, ones)
        c = np.dot(x.T, ones)
        return x / c

def test_area(vec, epsilon=1E-7):
    """
    与えられた混合戦略が確率分布ならばTrue、そうでなければFalseを返す.
    すなわち、全ての戦略の割合が非負ならばTrue.
    """
    boolvec = vec > -epsilon
    return boolvec.size - np.count_nonzero(boolvec) == 0

def gen_constraints(dims, potential_function_variances, maximizer, equilibria, br_candidate):
    """
    constraintsを加えて行く関数.生成はapp_constraintsで行う.
    """
    constraints = []
    for equilibrium, br in zip(equilibria, br_candidate):
        constraints.extend(app_constraints(dims, potential_function_variances, maximizer, equilibrium, br))
    return constraints


def app_constraints(dims, potential_function_variances, maximizer, equilibrium, br):
    """
    constraintsを生成する関数.
    """        
    constraints = []
    if br != maximizer:
        for dim in range(dims):
            if br != dim:
                nppfv_br = np.array((potential_function_variances[br]))
                nppfv_di = np.array((potential_function_variances[dim]))
                constraint = fd.dot(nppfv_br - nppfv_di, equilibrium)
                constraint.name = "func(" + str(br) + ">" + str(dim) + "," + str(equilibrium[0]) + ")"
                constraints.append(constraint >= 0.0)
                    
    return constraints


def set_potential_function_variances(dims, maximizer, epsilon=1E-3):
    """
    potential_functionの変数と初期値、目的関数を設定する.
    ここでは対称行列になるように設定するようにしてある.
    """
    potential_function = []
    combi = it.combinations(range(dims), 2)
    for dim in range(dims):
        potential_function.append([])
    for number, pair in enumerate(combi):
        x = fd.oovar('-x'+str(number), size=1, ub=0.0)
        potential_function[pair[0]].append(x)            
        potential_function[pair[1]].append(x)
    for dim in range(dims):
        if dim == maximizer:
            d = 1.0
        elif (dim-maximizer)%dims == 1: 
            #d = fd.oovar('obj', size=1)
            d = fd.oovar('-d'+str(dim), size=1, ub=0.0)
            #obj = d
            obj = 0 * d
        else:
            d = fd.oovar('-d'+str(dim), size=1, ub=0.0)
        potential_function[dim].insert(dim, d)
        
    startPoint = {}
    for i in range(dims):
        for j in range(i+1):
            if not (i == maximizer and j == maximizer):
                startPoint[potential_function[j][i]] = 1.0
        
    return potential_function, startPoint, obj

def gen_brs_candidate(dims, brs, first, maximizer):
    """
    potentialの方が満たすべきbrの候補を全て挙げる関数.
    """
    br_candidate = []
    for br in brs:
        if br == maximizer:
            br_candidate.append([br])
        else:
            if first:
                br_candidate.append(range(br+1))
            else:
                br_candidate.append(range(br, dims))
    return list(it.product(*br_candidate))

def cal_br_and_equilibria(dims, equilibrium_candidate, pure_strategies, first, deg_mat, epsilon=7):
    """
    それぞれの混合戦略に対応した順番にBestResponseを返す関数.
    BestResponseが複数ある場合はfirstがTrueなら最小値を、そうでなければ最大値を返す.
    BestResponseが完全に1つの場合は、内点とみなしてBRを返さず、equilibriaには入れない.
    最後に、pure_strategiesのBRを追加し、equilibriaにもpure_strategiesを追加する.
    """
    best_responses = []
    equilibria = []
        
    for equilibrium in equilibrium_candidate:
        br1st = np.argmax(np.around(np.dot(deg_mat, equilibrium), epsilon))
        br2nd = np.argmax(np.around(np.dot(deg_mat[::-1, :], equilibrium), epsilon))
        br2nd = dims - 1 - br2nd
            
        if br1st != br2nd:
            if first:
                br = br1st
            else:
                br = br2nd
            best_responses.append(br)
            equilibria.append(equilibrium)

    best_responses.extend(np.argmax(deg_mat, 0))
    equilibria.extend(pure_strategies)
        
    return best_responses, equilibria

def indefference_lines(matrix, mode):
    """
    3戦略限定で、そのうち2戦略が無差別な直線についての情報を返す.具体的には、
    x:2x4行列で、第1列は3戦略が無差別な点の座標、第2〜第4列はそれぞれ(1,2),(0,2),(0,1)が無差別になる方向ベクトル.
    a:3要素を持つベクトルで、それぞれ(1,2),(0,2),(0,1)が無差別になる直線の傾き.
    b:3要素を持つベクトルで、それぞれ(1,2),(0,2),(0,1)が無差別になる直線のy切片.
    startvalue:3要素を持つベクトルで、それぞれのmodeに対して3戦略が無差別な点の真上の点について利得を計算している.カラーリングに必要.
    """
    Y = np.array(
    [[ 1, 0, 1, 1],
     [ 1, 1, 0, 1],
     [ 1, 1, 1, 0]]
    )
    TRIANGLE = np.array(
    [[2*np.sqrt(3)/3, np.sqrt(3)/3],
     [0             , 1           ]]
    )
    BUILDUP = np.array(
    [[1, 1],
     [0 ,1]]
    )                   
    ROTATE = np.array(
    ((0, 1, 0),
     (0, 0, 1),
     (1, 0, 0))
    )
    
    x = np.linalg.solve(matrix, Y)
    x = x / x.sum(axis=0)
    x = x - np.c_[np.zeros(3), x[:, 0], x[:, 0], x[:, 0]]
    
    if mode == 'triangle':
        x = np.dot(ROTATE, x)
        convertmat = TRIANGLE
    elif mode == 'buildup':
        x = np.dot(ROTATE, x)
        convertmat = BUILDUP
    else:
        x = np.dot(ROTATE, x)        
        convertmat = np.eye(2)
    
    x = np.dot(convertmat, x[0:2, :])
    a = x[1, 1:4] / x[0, 1:4]
    b = x[1, 0] - a * x[0, 0]
    
    startvalue = x[:, 0] + np.array((0, 1))
    startvalue = np.dot(np.linalg.inv(convertmat), startvalue)
    startvalue = np.insert(startvalue, obj=2, values=1-np.sum(startvalue))
    if mode == 'triangle':
        startvalue = np.dot(np.linalg.inv(ROTATE), startvalue)
    elif mode == 'buildup':
        startvalue = np.dot(np.linalg.inv(ROTATE), startvalue)
    else:
        startvalue = np.dot(np.linalg.inv(ROTATE), startvalue)
    startvalue = np.dot(matrix, startvalue) 
    
    return x, a, b, startvalue


def line(a, b, t, j):
    return a[j]*t+b[j]


def paint(left, a, t, line_order, color, value, alpha, ax):
    
    CONVERT = np.array(
    (
    ((1, 0, 0),
     (0, 0, 1),
     (0, 1, 0)),
    ((0, 0, 1),
     (0, 1, 0),
     (1, 0, 0)),
    ((0, 1, 0),
     (1, 0, 0),
     (0, 0, 1))
    )
    )
    
    if left == 'left':
        j = a.argsort()
    else:
        j = a.argsort()[::-1]
    
    for i in range(3):
        v = value.argmax()
        ax.fill_between(t, line_order[i+1], line_order[i], color=color[v], alpha=alpha, lw=0)
        value = np.dot(CONVERT[j[i]], value)
    v = value.argmax()
    ax.fill_between(t, line_order[4], line_order[3], color=color[v], alpha=alpha, lw=0)

def paint_all(x, a, b, startvalue, mode, vmin, vmax, alpha):

    T0 = np.linspace(vmin,    x[0, 0], 2)
    T1 = np.linspace(x[0, 0], vmax,    2)
    
    Ta = np.linspace(vmin,         np.sqrt(3)/3, 2)
    Tb = np.linspace(np.sqrt(3)/3, vmax        , 2)
    
    T = np.linspace(vmin, vmax, 2)

    color = ['blue', 'green', 'red']
    fig, ax = plt.subplots()

    for i in range(3):
        ax.plot(T, a[i]*T + b[i], color=color[i], label=str(i))

    if mode == 'triangle':
        ax.plot(T,  0*T,             color='black')
        ax.plot(Ta, np.sqrt(3)*Ta,   color='black')
        ax.plot(Tb, 2-np.sqrt(3)*Tb, color='black')
        plt.legend(framealpha=0.5)
        plt.text(0, 0, '$x_0$')
        plt.text(2*np.sqrt(3)/3, 0, '$x_1$')
        plt.text(np.sqrt(3)/3, 1, '$x_2$')
    elif mode == 'buildup':
        ax.plot(T, 0*T, color='black')
        ax.plot(T, T,   color='black')
        ax.axvline(x=1, ymin=0, ymax=1, color='black')
        plt.legend(framealpha=0.5, loc='upper left')
        plt.text(1, 0, '1+2')
        plt.text(1, 1, '2')
    else:
        ax.plot(T, 0*T, color='black')
        ax.plot(T, 1-T, color='black')
        ax.axvline(x=0, ymin=0, ymax=1, color='black')
        plt.legend(framealpha=0.5)

    line_order_left  = [vmax, line(a, b, T0, a.argsort()[0]), line(a, b, T0, a.argsort()[1]), line(a, b, T0, a.argsort()[2]), vmin]
    line_order_right = [vmax, line(a, b, T1, a.argsort()[2]), line(a, b, T1, a.argsort()[1]), line(a, b, T1, a.argsort()[0]), vmin]

    paint(left='left',  a=a, t=T0, line_order=line_order_left,  color=color, value=startvalue, alpha=alpha, ax=ax)
    paint(left='right', a=a, t=T1, line_order=line_order_right, color=color, value=startvalue, alpha=alpha, ax=ax)
    
    plt.xlim(vmin, vmax)
    plt.ylim(vmin, vmax)    

    ax.set_aspect('equal')
    plt.show()    





class Game:
    def __init__(self, payoff_matrix):
        self.payoff_matrix = payoff_matrix
        self.dims = payoff_matrix.shape[0]
        self.equilibrium_candidate = self.gen_equilibrium_candidate()
        self.pure_strategies = np.split(np.eye(self.dims).flat, self.dims*np.arange(1, self.dims))


    def show_br_region(self, mode, VMIN=-0.1, VMAX=1.2, ALPHA=0.25):
        """
        3x3の対称ゲーム限定で使えるメソッドで、best response regionを図で返すメソッド.
        モードは、
        ・'triangle':正三角形で返す.
        ・'buildup':横軸にx_1+x_2、縦軸にx_2を取る形で返す.supermodularだと無差別直線が右下がり.
        ・それ以外:通常の座標で返す.
        を選択できる.
        """
        if self.dims != 3:
            print "This method can work only 3-dims payoff-matrix."
        else:
            x, a, b, startvalue = indefference_lines(self.payoff_matrix, mode)
            paint_all(x, a, b, startvalue, mode=mode, vmin=VMIN, vmax=VMAX, alpha=ALPHA)

    def is_supermodular(self):
        boo = []
        for i in range(self.dims-1):
            dif = self.payoff_matrix[i+1, :] - self.payoff_matrix[i, :]
            boo.append(np.array_equal(dif, np.sort(dif)))
        return all(boo)

    def is_mpmaximizer(self, maximizer, IPRINT=-1, show=True):
        """
        このGameの対角成分maximizerに対して、monotone potential maximizerが存在するかどうか調べる関数.
        存在すればそのpotential functionを返し、存在しなければ存在しないと返す.
        """
        deg1st_payoff_matrix = self.degen(True, maximizer)
        deg2nd_payoff_matrix = self.degen(False, maximizer)
        brs_1st, equilibria_1st = cal_br_and_equilibria(self.dims, self.equilibrium_candidate, self.pure_strategies, first=True,  deg_mat=deg1st_payoff_matrix)
        brs_2nd, equilibria_2nd = cal_br_and_equilibria(self.dims, self.equilibrium_candidate, self.pure_strategies, first=False, deg_mat=deg2nd_payoff_matrix)
        brs_candidate_1st = gen_brs_candidate(self.dims, brs=brs_1st, first=True,  maximizer=maximizer)
        brs_candidate_2nd = gen_brs_candidate(self.dims, brs=brs_2nd, first=False, maximizer=maximizer)
        potential_function_variances, startPoint, obj = set_potential_function_variances(self.dims, maximizer=maximizer)
        """
        mainの部分で、線形計画法を解く部分.
        """
        flag = False
        for br_candidate_1st in brs_candidate_1st:                
            if flag:
                break
            else:
                for br_candidate_2nd in brs_candidate_2nd:                
                    constraints = gen_constraints(self.dims, potential_function_variances, maximizer, equilibria_1st, brs_1st)
                    constraints.extend(gen_constraints(self.dims, potential_function_variances, maximizer, equilibria_2nd, brs_2nd))
                    p = LP(obj, startPoint, constraints=constraints)
                    sol = p.minimize('cvxopt_lp', iprint=IPRINT)
                    pfm = np.ones((self.dims, self.dims))
                    if sol.ff < 1:
                        flag = True
                        break
        if flag:
            for raw, ls in enumerate(potential_function_variances):
                for column, let in enumerate(ls):
                    if not isinstance(let, float):
                        pfm[raw][column] = sol(let)
            if show:
                print 'This strategy is a monotone potential maximizer.'
                print pfm           
        else:
            if show:
                print 'This strategy is not a monotone potential maximizer.'



    def gen_equilibrium_candidate(self):
        """
        2つ以上の戦略が無差別になる空間と、dim次元の(0,1)空間の境界部分との共通点を全て挙げる.
        (例)dim = 3ならば、まず3つの戦略が無差別になる点を探す.
            次に、2つの戦略が無差別になる直線が3本((0,1),(0,2),(1,2)それぞれ)あり、
            これとの3次元空間の(0,1)との境界部分は多くて直線1本につき2ヶ所だから6点が加わり、
            高々7点がequilibriaに加えられる.
        """
        equilibria = []
        
        for dim in range(self.dims - 1):
            for rawcolumn1 in it.combinations(range(self.dims), dim):
                for rawcolumn2 in it.combinations(range(self.dims), dim):
                    cutmatrix = cut_matrix(self.payoff_matrix, rawcolumn1, rawcolumn2)
                    equilibrium = np.array(cal_equilibrium(cutmatrix))
                  
                    if test_area(equilibrium):
                        for k in rawcolumn2:
                            equilibrium = np.insert(equilibrium, k, 0)
                        equilibria.append(equilibrium)
        
        return equilibria


    def degen(self, first, maximizer, HUGE=1E+7):
        """
        行列を退化させる関数.BestResponseを求めるが、MPMaximizerの戦略a-starによって、
        条件式で取れる戦略を限る必要があるので、この関数が必要.
        命名はupperがTrueなら下の例での1つ目、Falseなら2つ目になるよう返す.
        (例)A_i = {0, 1, 2, 3}でa-star_i = 2とすると、2つある条件のうちの1つ目
        br{pi_i|[0,2]}は、0から2までの戦略の中からbrを選ぶということなので、
        利得行列を、
        [ ## ## ## ##
        ## ## ## ##
        ## ## ## ##
        -- -- -- -- ]
        (ただし##は元の値、--は##に比べて明らかに小さい値(-- << ##))
        とすることで3という戦略が他に支配されるようにできる.同様に、2つ目の条件
        br{pi_i|[2,3]}は、利得行列を
        [ -- -- -- --
        -- -- -- --
        ## ## ## ##
        ## ## ## ## ]
        とする.
        """
        degen = HUGE * np.ones((self.dims, self.dims))
        if first:
            for i in range(maximizer+1):
                degen[i, :] = 0
        else:
            for i in range(maximizer, self.dims):
                degen[i, :] = 0
        return self.payoff_matrix - degen

In [ ]: