Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ julia> Σ[end]

## Customization

**WARNING: the following is outdated. The current approach for customization uses dispatch.**

Many components of the algorithm can be modified, as is already discussed in the original work (Table 1 and Section 6.2, Carvalho, Lodi, and Pedroso, 2020). To choose between different options, you have only to assign different implementations to the baseline pointer. Note that those different implementations can be custom, local functions as well.

A practical example is shown in [`example_5_3.jl`](./examples/example_5_3.jl), at section _Customization_. Below, we detail the customizable parts and the available options.
Expand Down
42 changes: 35 additions & 7 deletions src/SGM/Initialization.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@

abstract type AbstractStrategyInit end

empty_S_X(players::Vector{Player}) = Dict{Player, Vector{PureStrategy}}(p => Vector{PureStrategy}() for p in players)

# TODO: refactor strategies to apply to a single player at a time. leave the overwriting of start values outside?

"Solves a feasibility problem for each player individually."
function initialize_strategies_feasibility(players::Vector{Player})
struct FeasibilityStrategyInit <: AbstractStrategyInit end
export FeasibilityStrategyInit

function initialize_strategies(::FeasibilityStrategyInit, players::Vector{Player})
S_X = empty_S_X(players)

for player in players
xp_init = start_value.(all_variables(player))

Expand All @@ -22,7 +28,10 @@ function initialize_strategies_feasibility(players::Vector{Player})
end

"Computes the best response of each player when others play 0."
function initialize_strategies_player_alone(players::Vector{Player})
struct PlayerAloneStrategyInit <: AbstractStrategyInit end
export PlayerAloneStrategyInit

function initialize_strategies(::PlayerAloneStrategyInit, players::Vector{Player})
S_X = empty_S_X(players)

# profile that simulates players being alone (all others play 0)
Expand All @@ -41,20 +50,39 @@ function initialize_strategies_player_alone(players::Vector{Player})
return S_X
end

""" Default strategy initialization method.

Options:
- `FeasibilityStrategyInit()` (default)
- `PlayerAloneStrategyInit()`

"""
DEFAULT_STRATEGY_INITIALIZER = FeasibilityStrategyInit()
public DEFAULT_STRATEGY_INITIALIZER

"""
SGM subroutine that computes initial strategies for each player.

In all current options, initialization is only applied to players that do *not* have start
value for *all* variables, i.e., whenever `all(has_start_value.(all_variables(player))) == false`.


# Options
- `initialize_strategies_feasibility` (default)
- `initialize_strategies_player_alone`
- `FeasibilityStrategyInit()` (default)
- `PlayerAloneStrategyInit()`


# Examples
```julia
IPG.initialize_strategies = IPG.initialize_strategies_feasibility
# Use a specific initializer for one call
S_X = initialize_strategies(PlayerAloneStrategyInit(), players)

# Change the default initializer globally
IPG.DEFAULT_STRATEGY_INITIALIZER = PlayerAloneStrategyInit()
S_X = initialize_strategies(players) # now uses PlayerAloneStrategyInit by default
```
"""
initialize_strategies = initialize_strategies_feasibility
public initialize_strategies, initialize_strategies_player_alone, initialize_strategies_feasibility
initialize_strategies(players::Vector{Player}) = initialize_strategies(DEFAULT_STRATEGY_INITIALIZER, players)
initialize_strategies(init::AbstractStrategyInit, players::Vector{Player}) = initialize_strategies(init, players)

public initialize_strategies
43 changes: 41 additions & 2 deletions test/sgm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ include("utils.jl")
set_start_value(var, nothing)
end

S_X = IPG.initialize_strategies_feasibility(players)
S_X = IPG.initialize_strategies(FeasibilityStrategyInit(), players)

@test Set(keys(S_X)) == Set(players)
for player in players
Expand All @@ -29,7 +29,7 @@ include("utils.jl")
set_start_value(var, nothing)
end

S_X = IPG.initialize_strategies_player_alone(players)
S_X = IPG.initialize_strategies(PlayerAloneStrategyInit(), players)

@test Set(keys(S_X)) == Set(players)
for player in players
Expand All @@ -40,6 +40,45 @@ include("utils.jl")
end
end

@testitem "Default initializer change" setup=[Utilities] begin
players = get_example_two_player_game()
for player in players
IPG.set_optimizer(player, SCIP.Optimizer)
end

# remove start values from both players
for player in players
for var in all_variables(player.X)
set_start_value(var, nothing)
end
end

# Store original default initializer
original_default = IPG.DEFAULT_STRATEGY_INITIALIZER

try
# Test default behavior (should be FeasibilityStrategyInit)
@test IPG.DEFAULT_STRATEGY_INITIALIZER isa FeasibilityStrategyInit
S_X_default = IPG.initialize_strategies(players)

# Change default to PlayerAloneStrategyInit
IPG.DEFAULT_STRATEGY_INITIALIZER = PlayerAloneStrategyInit()
@test IPG.DEFAULT_STRATEGY_INITIALIZER isa PlayerAloneStrategyInit

S_X_changed = IPG.initialize_strategies(players)

# Results should be different - PlayerAloneStrategyInit gives all zeros
for player in players
@test all(S_X_changed[player][1] .== 0) # PlayerAlone should give zeros
@test !(all(S_X_default[player][1] .== 0)) # Feasibility should not give all zeros
end

finally
# Restore original default initializer
IPG.DEFAULT_STRATEGY_INITIALIZER = original_default
end
end

@testitem "Deviation reaction" setup=[Utilities] begin
players = get_example_two_player_game()
for player in players
Expand Down
Loading