1- using JuMP
1+ using JuMP, JSON3
22
33abstract type AbstractPlayer end
44
55" A player in an IPG."
6- struct Player <: AbstractPlayer
6+ struct Player{Payoff <: AbstractPayoff } <: AbstractPlayer
77 " Strategy space."
88 Xp:: Model
99 " Payoff function."
10- Πp:: AbstractPayoff
10+ Πp:: Payoff
1111 " Player's index."
1212 p:: Integer # TODO : this could be Any, to allow for more general collections, e.g, string names
1313 # TODO : maybe I could just index everything relevant as a Dict{Player, T}?
1414end
1515" Initialize player with empty strategy space."
16- function Player (Πp:: AbstractPayoff , p:: Integer )
17- return Player (Model (), Πp, p)
16+ function Player (Πp:: Payoff , p:: Integer ) where Payoff <: AbstractPayoff
17+ return Player {Payoff} (Model (), Πp, p)
1818end
1919
2020" Check whether an optimizer has already been set for player."
21- function has_optimizer (player:: Player )
21+ function has_optimizer (player:: AbstractPlayer )
2222 return ~ (backend (player. Xp). state == JuMP. MOIU. NO_OPTIMIZER)
2323end
2424
2525" Define the optimizer for player."
26- function set_optimizer (player:: Player , optimizer_factory)
26+ function set_optimizer (player:: AbstractPlayer , optimizer_factory)
2727 JuMP. set_optimizer (player. Xp, optimizer_factory)
2828end
2929
3030" Compute the utility that `player_p` receives from `player_k` when they play, resp., `xp` and `xk`."
31- function bilateral_payoff (player_p:: Player , xp:: Vector{<:Union{Real,VariableRef}} , player_k:: Player , xk:: Vector{<:Real} )
31+ function bilateral_payoff (player_p:: Player{QuadraticPayoff} , xp:: Vector{<:Union{Real,VariableRef}} , player_k:: Player{QuadraticPayoff} , xk:: Vector{<:Real} )
3232 return bilateral_payoff (player_p. Πp, player_p. p, xp, player_k. p, xk)
3333end
3434" Compute the utility that `player_p` receives from `player_k` when they play, resp., `xp` and `σk`."
35- function bilateral_payoff (player_p:: Player , xp:: Vector{<:Union{Real,VariableRef}} , player_k:: Player , σk:: DiscreteMixedStrategy )
35+ function bilateral_payoff (player_p:: Player{QuadraticPayoff} , xp:: Vector{<:Union{Real,VariableRef}} , player_k:: Player{QuadraticPayoff} , σk:: DiscreteMixedStrategy )
3636 return expected_value (xk -> bilateral_payoff (player_p. Πp, player_p. p, xp, player_k. p, xk), σk)
3737end
3838
3939" Compute the payoff of player `player` given pure strategy profile `x`."
40- function payoff (player:: Player , x:: Vector{<:Vector{<:Real}} )
40+ function payoff (player:: AbstractPlayer , x:: Vector{<:Vector{<:Real}} )
4141 return payoff (player. Πp, x, player. p)
4242end
4343" Compute the payoff of player `player` given mixed strategy profile `σ`."
44- function payoff (player:: Player , σ:: Vector{DiscreteMixedStrategy} )
44+ function payoff (player:: AbstractPlayer , σ:: Vector{DiscreteMixedStrategy} )
4545 _payoff = x -> payoff (player, x)
4646 return expected_value (_payoff, σ)
4747end
4848
4949" Compute `player`'s best response to the mixed strategy profile `σp`."
50- function best_response (player:: Player , σ:: Vector{DiscreteMixedStrategy} )
50+ function best_response (player:: Player{QuadraticPayoff} , σ:: Vector{DiscreteMixedStrategy} )
5151 xp = all_variables (player. Xp)
5252
5353 # TODO : No idea why this doesn't work
@@ -68,7 +68,7 @@ function best_response(player::Player, σ::Vector{DiscreteMixedStrategy})
6868end
6969
7070" Solve the feasibility problem for a player, returning a feasible strategy."
71- function find_feasible_pure_strategy (player:: Player )
71+ function find_feasible_pure_strategy (player:: AbstractPlayer )
7272 @objective (player. Xp, JuMP. MOI. FEASIBILITY_SENSE, 0 )
7373
7474 set_silent (player. Xp)
@@ -78,6 +78,47 @@ function find_feasible_pure_strategy(player::Player)
7878end
7979
8080" Solve the feasibility problem of all players, returning a feasible profile."
81- function find_feasible_pure_profile (players:: Vector{Player } )
81+ function find_feasible_pure_profile (players:: Vector{<:AbstractPlayer } )
8282 return [find_feasible_pure_strategy (player) for player in players]
83- end
83+ end
84+
85+ function save (player:: Player{QuadraticPayoff} , filename:: String )
86+ # we need to ensure that the file is stored as a json, so we can add the payoff information
87+ JuMP. write_to_file (player. Xp, filename; format = JuMP. MOI. FileFormats. FORMAT_MOF)
88+
89+ # TODO : this could be refactored as a payoff JSON-serialization method
90+ # see https://quinnj.github.io/JSON3.jl/stable/#Struct-API
91+ mof_json = JSON3. read (read (filename, String))
92+ mof_json = copy (mof_json) # JSON type is immutable
93+
94+ mof_json[:IPG__player_index ] = player. p
95+ mof_json[:IPG__payoff ] = Dict (
96+ :cp => player. Πp. cp,
97+ :Qp => player. Πp. Qp,
98+ # JSON3 cannot store matrices, it stores them as a flat vector
99+ :Qp_shapes => [size (Qpk) for Qpk in player. Πp. Qp],
100+ )
101+
102+ open (filename, " w" ) do file
103+ JSON3. write (file, mof_json)
104+ end
105+ end
106+
107+ function load (filename:: String ):: Player{QuadraticPayoff}
108+ Xp = JuMP. read_from_file (filename; format = JuMP. MOI. FileFormats. FORMAT_MOF)
109+
110+ # see https://github.com/jump-dev/JuMP.jl/issues/3946
111+ set_start_value .(all_variables (Xp), start_value .(all_variables (Xp)))
112+
113+ mof_json = JSON3. read (read (filename, String))
114+
115+ player_index = mof_json[:IPG__player_index ]
116+ payoff_data = mof_json[:IPG__payoff ]
117+ cp = copy (payoff_data[:cp ])
118+ flat_Qp = copy (payoff_data[:Qp ])
119+ Qp_shapes = copy (payoff_data[:Qp_shapes ])
120+
121+ Qp = [reshape (flat_Qpk, Tuple (Qpk_shape)) for (flat_Qpk, Qpk_shape) in zip (flat_Qp, Qp_shapes)]
122+
123+ return Player {QuadraticPayoff} (Xp, QuadraticPayoff (cp, Qp), player_index)
124+ end
0 commit comments