Skip to content

Commit b3b049c

Browse files
authored
Merge pull request #65 from shizejin/add_AS_algorithm
Add AS algorithm.
2 parents 4be039c + fffb4e6 commit b3b049c

File tree

3 files changed

+225
-2
lines changed

3 files changed

+225
-2
lines changed

src/GameTheory.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export
109109
RepeatedGame, unpack, flow_u_1, flow_u_2, flow_u, best_dev_i,
110110
best_dev_1, best_dev_2, best_dev_payoff_i, best_dev_payoff_1,
111111
best_dev_payoff_2, worst_value_i, worst_value_1, worst_value_2,
112-
worst_values, outerapproximation,
112+
worst_values, outerapproximation, AS,
113113

114114
# Random Games
115115
random_game, covariance_game,

src/repeated_game.jl

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ It currently only has tools for solving two player repeated
77
games, but could be extended to do more with some effort.
88
=#
99

10+
using Polyhedra
11+
1012
"""
1113
RepeatedGame{N,T}
1214
@@ -525,3 +527,185 @@ function outerapproximation(
525527

526528
return vertices
527529
end
530+
531+
"""
532+
AS(rpd; maxiter=1000, plib=default_library(2, Float64), tol=1e-5, u=nothing)
533+
534+
Using AS algorithm to compute the set of payoff pairs of all pure-strategy
535+
subgame-perfect equilibria with public randomization for any repeated
536+
two-player games with perfect monitoring and discounting, following
537+
Abreu and Sannikov (2014).
538+
539+
# Arguments
540+
541+
- `rpd::RepeatedGame{2, T}` : Two player repeated game with T<:Real.
542+
- `maxiter::Integer` : Maximum number of iterations.
543+
- `plib`: Allows users to choose a particular package for the geometry
544+
computations.
545+
(See [Polyhedra.jl](https://github.com/JuliaPolyhedra/Polyhedra.jl)
546+
docs for more info). By default, it chooses to use SimplePolyhedraLibrary.
547+
- `tol::Float64` : Tolerance in differences of set.
548+
- `u` : The punishment payoff pair if any player deviates. In default,
549+
we use minimax payoff pair. If there is better guess, you can specify it
550+
by passing a `Vector` with length 2.
551+
552+
# Returns
553+
554+
- `::Matrix{T}` : Vertices of the set of payoff pairs.
555+
"""
556+
function AS(rpd::RepeatedGame{2}; maxiter::Integer=1000,
557+
plib=default_library(2, Float64), tol::Float64=1e-5,
558+
u::Union{AbstractVector{TU}, Nothing}=nothing) where {TU}
559+
560+
lib = similar_library(plib, 2, Float64)
561+
562+
# Initialize W0 with each entries of payoff bimatrix
563+
v_old = _payoff_points(Float64, rpd.sg)
564+
565+
if isnothing(u)
566+
u = Float64[minimum(rpd.sg.players[1].payoff_array),
567+
minimum(rpd.sg.players[2].payoff_array)]
568+
else
569+
u = convert(Vector{Float64}, u)
570+
end
571+
572+
# create VRepresentation and Polyhedron and get rid of redundant vertices
573+
p = polyhedron(vrep(v_old), lib)
574+
removevredundancy!(p)
575+
H = hrep(p)
576+
577+
# calculate the best deviation gains
578+
# normalize with (1-delta)/delta
579+
best_dev_gains1, best_dev_gains2 = (1-rpd.delta)/rpd.delta .* _best_dev_gains(rpd.sg)
580+
581+
for iter = 1:maxiter
582+
583+
v_new = Float64[] # to store new vertices
584+
# step 1
585+
for a2 in 1:rpd.sg.nums_actions[2]
586+
for a1 in 1:rpd.sg.nums_actions[1]
587+
payoff1 = rpd.sg.players[1].payoff_array[a1, a2]
588+
payoff2 = rpd.sg.players[2].payoff_array[a2, a1]
589+
IC1 = u[1] + best_dev_gains1[a1, a2]
590+
IC2 = u[2] + best_dev_gains2[a2, a1]
591+
592+
# check if the payoff point is interior
593+
# first check if it satisifies IC
594+
if all([payoff1, payoff2] .> [IC1, IC2])
595+
# then check if it is in the polyhedron
596+
if [payoff1, payoff2] in H
597+
push!(v_new, payoff1, payoff2)
598+
end
599+
end
600+
601+
# find out the intersections of polyhedron and IC boundaries
602+
p_IC = polyhedron(hrep(-Matrix{Float64}(I, 2, 2), -[IC1, IC2]), lib)
603+
p_inter = intersect(p_IC, p)
604+
Vmat = MixedMatVRep(vrep(p_inter)).V
605+
for i in 1:size(Vmat, 1)
606+
if Vmat[i, 1] IC1 || Vmat[i, 2] IC2
607+
push!(v_new, (rpd.delta * Vmat[i, :] +
608+
(1 - rpd.delta) * [payoff1, payoff2])...)
609+
end
610+
end
611+
end
612+
end
613+
614+
v_new = reshape(v_new, 2, :)'
615+
616+
# get rid of redundant points
617+
p = polyhedron(vrep(v_new), lib)
618+
removevredundancy!(p)
619+
620+
# check if it's converged
621+
# Use deduplicated vertices for convergence check
622+
v_dedup = MixedMatVRep(vrep(p)).V
623+
# first check if the numbers of vertices are the same
624+
if size(v_dedup) == size(v_old)
625+
# then check the euclidean distance
626+
if norm(v_dedup-v_old) < tol
627+
println("converged in $(iter) iterations")
628+
break
629+
end
630+
end
631+
632+
# check if maxiter is reached
633+
if iter == maxiter
634+
@warn "Maximum Iteration Reached"
635+
end
636+
637+
v_old = v_dedup
638+
H = hrep(p)
639+
640+
# step 2
641+
# update u
642+
u_ = [minimum(v_new[:, 1]),
643+
minimum(v_new[:, 2])]
644+
if any(u_ .> u)
645+
u = u_
646+
end
647+
648+
end
649+
650+
# To ensure the return is Matrix{Float64}
651+
vr = vrep(p)
652+
pts = points(vr)
653+
vertices = Matrix{Float64}(undef, (length(pts), 2))
654+
for (i, pt) in enumerate(pts)
655+
vertices[i, :] = pt
656+
end
657+
658+
return vertices
659+
end
660+
661+
"""
662+
_payoff_points(::Type{T}, g)
663+
664+
Return a matrix with each row being a payoff pair point in the two dimensional
665+
space.
666+
667+
# Arguments
668+
669+
- `g::NormalFormGame{2}` : Two-player NormalFormGame.
670+
671+
# Returns
672+
673+
- `v::Matrix{T}` : Matrix with size n by 2, where n is the number of
674+
action profiles. Each row corresponds to one payoff pair.
675+
"""
676+
function _payoff_points(::Type{T}, g::NormalFormGame{2}) where T
677+
678+
nums_action_profiles = prod(g.nums_actions)
679+
v = Matrix{T}(undef, nums_action_profiles, 2)
680+
v[:, 1] = reshape(g.players[1].payoff_array, nums_action_profiles)
681+
v[:, 2] = reshape(g.players[2].payoff_array', nums_action_profiles)
682+
683+
return v
684+
end
685+
686+
"""
687+
_best_dev_gains(g)
688+
689+
Calculate the payoff gains from deviating from the current action to
690+
the best response for each player.
691+
692+
# Arguments
693+
694+
- `g::NormalFormGame{2, T}` : Two-player NormalFormGame.
695+
696+
# Returns
697+
698+
- `::Tuple{Matrix{T}, Matrix{T}}` : Tuple of best deviating gain matrices
699+
for two players. For example, for the first matrix `best_dev_gains1`,
700+
`best_dev_gains1[i, j]` is the payoff gain for player 1 for deviating
701+
to the best response from ith action given player 2 choosing jth action.
702+
"""
703+
function _best_dev_gains(g::NormalFormGame{2, T}) where T
704+
705+
best_dev_gains1 = (maximum(g.players[1].payoff_array; dims=1)
706+
.- g.players[1].payoff_array)
707+
best_dev_gains2 = (maximum(g.players[2].payoff_array; dims=1)
708+
.- g.players[2].payoff_array)
709+
710+
return best_dev_gains1, best_dev_gains2
711+
end

test/test_repeated_game.jl

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,44 @@
5757
@test any(mybools)
5858
end
5959

60-
end
60+
#
61+
# Test AS algorithm
62+
#
63+
@testset "Testing AS algorithm" begin
64+
vertices = @inferred(AS(rpd; tol=1e-9))
65+
66+
pts_sorted = [3.0 3.0;
67+
3.0 9.75;
68+
9.0 9.0;
69+
9.75 3.0]
70+
@test size(vertices) == size(pts_sorted)
71+
@test all(sortslices(round.(vertices, digits=5), dims=1) .≈ pts_sorted)
72+
73+
@testset "AS with Int payoffs" begin
74+
nfg_int = NormalFormGame(Int, nfg)
75+
rpd_int = RepeatedGame(nfg_int, 0.75)
76+
vertices = @inferred(AS(rpd_int; tol=1e-9))
77+
@test size(vertices) == size(pts_sorted)
78+
@test all(
79+
sortslices(round.(vertices, digits=5), dims=1) .≈ pts_sorted
80+
)
6181

82+
vertices_u = @inferred(AS(rpd_int; tol=1e-9, u=[0, 0]))
83+
@test size(vertices_u) == size(pts_sorted)
84+
@test all(
85+
sortslices(round.(vertices_u, digits=5), dims=1) .≈ pts_sorted
86+
)
87+
end
88+
89+
@testset "AS with Rational payoffs" begin
90+
nfg_rat = NormalFormGame(Rational{Int}, nfg)
91+
rpd_rat = RepeatedGame(nfg_rat, 0.75)
92+
vertices = @inferred(AS(rpd_rat; tol=1e-9))
93+
@test size(vertices) == size(pts_sorted)
94+
@test all(
95+
sortslices(round.(vertices, digits=5), dims=1) .≈ pts_sorted
96+
)
97+
end
98+
end
99+
100+
end

0 commit comments

Comments
 (0)