Skip to content

Commit

Permalink
Merge 95ad143 into 6d35538
Browse files Browse the repository at this point in the history
  • Loading branch information
oyamad committed Mar 4, 2019
2 parents 6d35538 + 95ad143 commit 28db1c2
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 18 deletions.
1 change: 1 addition & 0 deletions REQUIRE
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ QuantEcon
Polyhedra
LightGraphs
Combinatorics
Parameters
4 changes: 4 additions & 0 deletions src/Games.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ using Clp
using MathProgBase
using QuantEcon
using Combinatorics
using Parameters

# Geometry packages
using Polyhedra
Expand Down Expand Up @@ -79,6 +80,9 @@ export
# General functions
num_players, num_actions, num_opponents,

# Utilities
BROptions,

# Nash Equilibrium
pure_nash,

Expand Down
69 changes: 53 additions & 16 deletions src/normal_form_game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ Return true if `own_action` is a best response to `opponents_actions`.
- `player::Player` : Player instance.
- `own_action::PureAction` : Own pure action (integer).
- $(opponents_actions_docstring)
- `tol::Float64` : Tolerance to be used to determine best response actions.
- `tol::Real` : Tolerance to be used to determine best response actions.
# Returns
Expand All @@ -275,7 +275,7 @@ Return true if `own_action` is a best response to `opponents_actions`.
function is_best_response(player::Player,
own_action::PureAction,
opponents_actions::Union{Action,ActionProfile,Nothing};
tol::Float64=1e-8)
tol::Real=1e-8)
payoffs = payoff_vector(player, opponents_actions)
payoff_max = maximum(payoffs)
return payoffs[own_action] >= payoff_max - tol
Expand All @@ -291,7 +291,7 @@ Return true if `own_action` is a best response to `opponents_actions`.
- `player::Player` : Player instance.
- `own_action::MixedAction` : Own mixed action (vector of reals).
- $(opponents_actions_docstring)
- `tol::Float64` : Tolerance to be used to determine best response actions.
- `tol::Real` : Tolerance to be used to determine best response actions.
# Returns
Expand All @@ -301,7 +301,7 @@ Return true if `own_action` is a best response to `opponents_actions`.
function is_best_response(player::Player,
own_action::MixedAction,
opponents_actions::Union{Action,ActionProfile,Nothing};
tol::Float64=1e-8)
tol::Real=1e-8)
payoffs = payoff_vector(player, opponents_actions)
payoff_max = maximum(payoffs)
return dot(own_action, payoffs) >= payoff_max - tol
Expand All @@ -318,7 +318,7 @@ Return all the best response actions to `opponents_actions`.
- `player::Player` : Player instance.
- $(opponents_actions_docstring)
- `tol::Float64` : Tolerance to be used to determine best response actions.
- `tol::Real` : Tolerance to be used to determine best response actions.
# Returns
Expand All @@ -327,48 +327,85 @@ Return all the best response actions to `opponents_actions`.
"""
function best_responses(player::Player,
opponents_actions::Union{Action,ActionProfile,Nothing};
tol::Float64=1e-8)
tol::Real=1e-8)
payoffs = payoff_vector(player, opponents_actions)
payoff_max = maximum(payoffs)
best_responses = findall(x -> x >= payoff_max - tol, payoffs)
return best_responses
end

"""
best_response(player, opponents_actions; tie_breaking=:smallest, tol=1e-8)
best_response([rng=GLOBAL_RNG], player, opponents_actions;
tie_breaking=:smallest, tol=1e-8)
Return a best response action to `opponents_actions`.
# Arguments
- `rng::AbstractRNG=GLOBAL_RNG` : Random number generator; relevant only with
`tie_breaking=:random`.
- `player::Player` : Player instance.
- $(opponents_actions_docstring)
- `tie_breaking::Symbol` : Control how to break a tie (see Returns for details).
- `tol::Float64` : Tolerance to be used to determine best response actions.
- `tol::Real` : Tolerance to be used to determine best response actions.
# Returns
- `::Int` : If `tie_breaking=:smallest`, returns the best response action with
the smallest index; if `tie_breaking=:random`, returns an action randomly
chosen from the best response actions.
"""
function best_response(player::Player,
function best_response(rng::AbstractRNG, player::Player,
opponents_actions::Union{Action,ActionProfile,Nothing};
tie_breaking::Symbol=:smallest,
tol::Float64=1e-8)
tol::Real=1e-8)
if tie_breaking == :smallest
payoffs = payoff_vector(player, opponents_actions)
return argmax(payoffs)
elseif tie_breaking == :random
brs = best_responses(player, opponents_actions; tol=tol)
return rand(brs)
return rand(rng, brs)
else
throw(ArgumentError(
"tie_breaking must be one of `:smallest` or `:random`"
))
end
end

best_response(player::Player,
opponents_actions::Union{Action,ActionProfile,Nothing};
tie_breaking::Symbol=:smallest, tol::Real=1e-8) =
best_response(Random.GLOBAL_RNG, player, opponents_actions,
tie_breaking=tie_breaking, tol=tol)

"""
BROptions
Struct to contain options for `best_response`.
# Fieslds
- `tol::Real=1e-8` : Tolerance level.
- `tie_breaking::Symbol=:smallest` : `:smallest` or `:random`.
- `rng::AbstractRNG=GLOBAL_RNG` : Random number generator.
"""
@with_kw struct BROptions{T<:Real,TR<:AbstractRNG}
tol::T = 1e-8
tie_breaking::Symbol = :smallest
rng::TR = Random.GLOBAL_RNG
end

"""
best_response(player, opponents_actions, options)
Return a best response action to `opponents_actions` with options as specified
by a `BROptions` instance `options`.
"""
best_response(player::Player,
opponents_actions::Union{Action,ActionProfile,Nothing},
options::BROptions) =
best_response(options.rng, player, opponents_actions;
tie_breaking=options.tie_breaking, tol=options.tol)

# Perturbed best response
"""
best_response(player, opponents_actions, payoff_perturbation)
Expand Down Expand Up @@ -660,7 +697,7 @@ Base.setindex!(g::NormalFormGame{N}, v, ci::CartesianIndex{N}) where {N} =
# is_nash

function is_nash(g::NormalFormGame, action_profile::ActionProfile;
tol::Float64=1e-8)
tol::Real=1e-8)
for (i, player) in enumerate(g.players)
own_action = action_profile[i]
opponents_actions =
Expand All @@ -673,7 +710,7 @@ function is_nash(g::NormalFormGame, action_profile::ActionProfile;
end

function is_nash(g::NormalFormGame{2}, action_profile::ActionProfile;
tol::Float64=1e-8)
tol::Real=1e-8)
for (i, player) in enumerate(g.players)
own_action, opponent_action =
action_profile[i], action_profile[3-i]
Expand All @@ -695,7 +732,7 @@ Return true if `action_profile` is a Nash equilibrium.
- `g::NormalFormGame` : Instance of N-player NormalFormGame.
- `action_profile::ActionProfile` : Tuple of N integers (pure actions) or N
vectors of reals (mixed actions).
- `tol::Float64` : Tolerance to be used to determine best response actions.
- `tol::Real` : Tolerance to be used to determine best response actions.
# Returns
Expand All @@ -718,11 +755,11 @@ Return true if `action` is a Nash equilibrium of a trivial game with 1 player.
- `::Bool`
"""
is_nash(g::NormalFormGame{1}, action::Action; tol::Float64=1e-8) =
is_nash(g::NormalFormGame{1}, action::Action; tol::Real=1e-8) =
is_best_response(g.players[1], action, nothing, tol=tol)

is_nash(g::NormalFormGame{1}, action_profile::ActionProfile;
tol::Float64=1e-8) = is_nash(g, action_profile..., tol=tol)
tol::Real=1e-8) = is_nash(g, action_profile..., tol=tol)

# Utility functions

Expand Down
4 changes: 2 additions & 2 deletions src/pure_nash.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ will change in the future.
- `nfg::NormalFormGame`: Instance of N-player NormalFormGame.
- `ntofind::Inf`: Maximal number of pure action Nash equilibria to be
found; default is `prod(nfg.nums_actions)`.
- `tol::Float64` : Tolerance to be used to determine best response actions.
- `tol::Real` : Tolerance to be used to determine best response actions.
# Returns
- `ne::Vector{NTuple{N,Int}}`: Vector of pure action Nash equilibria.
"""
function pure_nash(nfg::NormalFormGame; ntofind=prod(nfg.nums_actions),
tol::Float64=1e-8)
tol::Real=1e-8)
# Get number of players and their actions
np = num_players(nfg)
na = nfg.nums_actions
Expand Down
4 changes: 4 additions & 0 deletions test/test_normal_form_game.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ using CDDLib

@test @inferred(best_response(player, 2)) == 2
@test @inferred(best_response(player, [1/2, 1/2])) == 2
@test @inferred(best_response(player, [1/2, 1/2], BROptions())) == 2
@test sort(@inferred(best_responses(player, [2/3, 1/3]))) ==
sort([1, 2])
@test best_response(
player, [2/3, 1/3], tie_breaking=:random
) in [1, 2]
@test best_response(
player, [2/3, 1/3], BROptions(tol=1e-5, tie_breaking=:random)
) in [1, 2]
@test_throws ArgumentError best_response(
player, [2/3, 1/3], tie_breaking=:invalid_symbol
)
Expand Down

0 comments on commit 28db1c2

Please sign in to comment.