diff --git a/README.md b/README.md index 18cd142..14b7f4d 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/SGM/Initialization.jl b/src/SGM/Initialization.jl index c405dec..5b46507 100644 --- a/src/SGM/Initialization.jl +++ b/src/SGM/Initialization.jl @@ -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)) @@ -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) @@ -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 diff --git a/test/sgm.jl b/test/sgm.jl index 32ce908..b5722b8 100644 --- a/test/sgm.jl +++ b/test/sgm.jl @@ -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 @@ -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 @@ -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