Skip to content

Commit

Permalink
Add support enumeration.
Browse files Browse the repository at this point in the history
  • Loading branch information
shizejin committed Mar 16, 2017
1 parent fd1c11d commit 9d4bafb
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 1 deletion.
6 changes: 5 additions & 1 deletion src/Games.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ include("normal_form_game.jl")
include("pure_nash.jl")
include("repeated_game_util.jl")
include("repeated_game.jl")
include("support_enumeration.jl")


export Player, NormalFormGame, # Types
Expand All @@ -46,6 +47,9 @@ export Player, NormalFormGame, # Types
RepeatedGame, unpack, flow_u_1, flow_u_2, flow_u, best_dev_i,
best_dev_1, best_dev_2, best_dev_payoff_i, best_dev_payoff_1,
best_dev_payoff_2, worst_value_i, worst_value_1, worst_value_2,
worst_values, outerapproximation
worst_values, outerapproximation,

# Support Enumeration
support_enumeration, support_enumeration_gen

end # module
252 changes: 252 additions & 0 deletions src/support_enumeration.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
#=
Compute all mixed Nash equilibria of a 2-player (non-degenerate) normal
form game by support enumeration.
References
----------
B. von Stengel, "Equilibrium Computation for Two-Player Games in
Strategic and Extensive Form," Chapter 3, N. Nisan, T. Roughgarden, E.
Tardos, and V. Vazirani eds., Algorithmic Game Theory, 2007.
=#

"""
support_enumeration(g::NormalFormGame)
Compute mixed-action Nash equilibria with equal support size for a
2-player normal form game by support enumeration. For a
non-degenerate game input, these are all Nash equilibria.
The algorithm checks all the equal-size support pairs; if the
players have the same number n of actions, there are 2n choose n
minus 1 such pairs. This should thus be used only for small games.
# Arguments
* `g::NormalFormGame`: NormalFormGame instance.
# Returns
* `::Vector{Tuple{Vector{Float64},Vector{Float64}}}`: Mixed-action
Nash equilibria that are found.
"""
function support_enumeration(g::NormalFormGame)

task = support_enumeration_gen(g)

NEs = Tuple{Vector{Float64},Vector{Float64}}[NE for NE in task]

return NEs

end

"""
support_enumeration_gen(g::NormalFormGame)
Generator version of `support_enumeration`.
# Arguments
* `g::NormalFormGame`: NormalFormGame instance.
# Returns
* `::Task`: runnable task for generating Nash equilibria.
"""
function support_enumeration_gen(g::NormalFormGame)

N = length(g.nums_actions)
if N != 2
throw(ArgumentError("Implemented only for 2-player games"))
end

task = Task(() -> _support_enumeration_gen(g.players[1].payoff_array,
g.players[2].payoff_array))

return task
end

"""
_support_enumeration_gen{T<:Real}(payoff_matrix1::Matrix{T},
payoff_matrix2::Matrix{T})
Main body of `support_enumeration_gen`.
# Arguments
* `payoff_matrix1::Matrix{T}`: Payoff matrix of player 1.
* `payoff_matrix2::Matrix{T}`: Payoff matrix of player 2.
# Produces
* `Tuple{Vector{Float64},Vector{Float64}}`: Tuple of Nash equilibrium
mixed actions.
"""
function _support_enumeration_gen{T<:Real}(payoff_matrix1::Matrix{T},
payoff_matrix2::Matrix{T})

nums_actions = size(payoff_matrix1, 1), size(payoff_matrix2, 1)
n_min = min(nums_actions...)

for k = 1:n_min
supps = (collect(1:k), Vector{Int}(k))
actions = (Vector{Float64}(k), Vector{Float64}(k))
A = Matrix{T}(k+1, k+1)
A[1:end-1, end] = -one(T)
A[end, 1:end-1] = one(T)
A[end, end] = zero(T)
b = zeros(T, k+1)
b[end] = one(T)
while supps[1][end] < nums_actions[1]+1
supps[2][:] = collect(1:k)
while supps[2][end] < nums_actions[2]+1
if _indiff_mixed_action!(A, actions[2], b,
payoff_matrix1, supps[1], supps[2])
if _indiff_mixed_action!(A, actions[1], b, payoff_matrix2,
supps[2], supps[1])
out = (zeros(nums_actions[1]),
zeros(nums_actions[2]))
for (p, (supp, action)) in enumerate(zip(supps,
actions))
out[p][supp] = action
end
produce(out)
end
end
_next_k_array!(supps[2])
end
_next_k_array!(supps[1])
end
end

end

"""
_indiff_mixed_action!{T<:Real}(A::Matrix{T}, out::Vector{Float64},
b::Vector{T},
payoff_matrix::Matrix{T},
own_supp::Vector{Int},
opp_supp::Vector{Int})
Given a player's payoff matrix `payoff_matrix`, an array `own_supp`
of this player's actions, and an array `opp_supp` of the opponent's
actions, each of length k, compute the opponent's mixed action whose
support equals `opp_supp` and for which the player is indifferent
among the actions in `own_supp`, if any such exists. Return `true`
if such a mixed action exists and actions in `own_supp` are indeed
best responses to it, in which case the outcome is stored in `out`;
`false` otherwise. Arrays `A` and `b` are used in intermediate
steps.
# Arguments
* `A::Matrix{T}`: Matrix used in intermediate steps.
* `out::Vector{Float64}`: Vector to store the nonzero values of the
desired mixed action.
* `b::Vector{T}`: Vector used in intermediate steps.
* `payoff_matrix::Matrix{T}`: The player's payoff matrix.
* `own_supp::Vector{Int}`: Vector containing the player's action indices.
* `opp_supp::Vector{Int}`: Vector containing the opponent's action indices.
# Returns
* `::Bool`: `true` if a desired mixed action exists and `false` otherwise.
"""
function _indiff_mixed_action!{T<:Real}(A::Matrix{T}, out::Vector{Float64},
b::Vector{T},
payoff_matrix::Matrix{T},
own_supp::Vector{Int},
opp_supp::Vector{Int})

m = size(payoff_matrix, 1)
k = length(own_supp)

A[1:end-1, 1:end-1] = payoff_matrix[own_supp, :][:, opp_supp]
sol = Vector{Float64}(k+1)
try
sol[:] = A \ b
catch LinAlg.SingularException
return false
end

if any(sol[1:end-1] .<= 0)
return false
end
out[:] = sol[1:end-1]
val = sol[end]

if k == m
return true
end

own_supp_flags = falses(m)
own_supp_flags[own_supp] = true

for i = 1:m
if !own_supp_flags[i]
payoff = 0.
for j = 1:k
payoff += payoff_matrix[i, opp_supp[j]] * out[j]
end
if payoff > val
return false
end
end
end

return true
end

"""
_next_k_combination(x::Int)
Find the next k-combination, as described by an integer in binary
representation with the k set bits, by "Gosper's hack".
Copy-paste from en.wikipedia.org/wiki/Combinatorial_number_system
# Arguments
* `x::Int`: Integer with k set bits.
# Returns
* `::Int`: Smallest integer > x with k set bits.
"""
function _next_k_combination(x::Int)

u = x & -x
v = u + x
return v + (fld((v $ x), u) >> 2)

end

"""
_next_k_array!(a::Vector{Int})
Given an array `a` of k distinct nonnegative integers, return the
next k-array in lexicographic ordering of the descending sequences
of the elements. `a` is modified in place.
# Arguments
* `a::Vector{Int}`: Array of length k.
# Returns
* `:::Vector{Int}`: View of `a`.
"""
function _next_k_array!(a::Vector{Int})

k = length(a)
if k == 0
return a
end

x = 0
for i = 1:k
x += (1 << (a[i] - 1))
end

x = _next_k_combination(x)

pos = 0
for i = 1:k
while x & 1 == 0
x = x >> 1
pos += 1
end
a[i] = pos + 1
x = x >> 1
pos += 1
end

return a
end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ end
include("test_pure_nash.jl")
include("test_repeated_game.jl")
include("test_normal_form_game.jl")
include("test_support_enumeration.jl")

32 changes: 32 additions & 0 deletions test/test_support_enumeration.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@testset "Testing Support Enumeration" begin

@testset "test 3 by 2 non-degenerate normal form game" begin
g = NormalFormGame(Player([3 3; 2 5; 0 6]),
Player([3 2 3; 2 6 1]))
NEs = [([1.0, 0.0, 0.0], [1.0, 0.0]),
([0.8, 0.2, 0.0], [2/3, 1/3]),
([0.0, 1/3, 2/3], [1/3, 2/3])]

for (actions_computed, actions) in zip(NEs, support_enumeration(g))
for (action_computed, action) in zip(actions_computed, actions)
@test action_computed action
end
end

end

@testset "test 3 by 2 degenerate normal form game" begin
g = NormalFormGame(Player([1 -1; -1 1; 0 0]),
Player([1 0 0; 0 0 0]))
NEs = [([1.0, 0.0, 0.0], [1.0, 0.0]),
([0.0, 1.0, 0.0], [0.0, 1.0])]

for (actions_computed, actions) in zip(NEs, support_enumeration(g))
for (action_computed, action) in zip(actions_computed, actions)
@test action_computed action
end
end
end


end

0 comments on commit 9d4bafb

Please sign in to comment.