Skip to content

Commit c297a5a

Browse files
Merge pull request #2 from brunompacheco/examples
Examples, removing notebooks, adding docstrings
2 parents 8f41020 + 6006f1b commit c297a5a

File tree

17 files changed

+521
-2742
lines changed

17 files changed

+521
-2742
lines changed

Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
99
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
1010
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1111
NormalGames = "4dc104ef-1e0c-4a09-93ea-14ddc8e86204"
12+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1213

1314
[sources]
1415
NormalGames = {rev = "optimizer-agnosticism", url = "https://github.com/mxmmargarida/Normal-form-games"}
@@ -18,15 +19,16 @@ IterTools = "1.10.0"
1819
JSON3 = "1.14.1"
1920
JuMP = "1.23.6"
2021
LinearAlgebra = "1.11.0"
22+
Random = "1.11.0"
2123
Test = "1.11.0"
2224
TestItemRunner = "1.1.0"
2325
TestItems = "1.0.0"
2426

2527
[extras]
2628
SCIP = "82193955-e24f-5292-bf16-6f2c5261a85f"
2729
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
28-
TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe"
2930
TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a"
31+
TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe"
3032

3133
[targets]
3234
test = ["Test", "SCIP", "TestItems", "TestItemRunner"]

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,17 @@ using IPG
1919

2020
p1 = Player(); ... ; pn = Player()
2121

22-
# variable examples
22+
# variable definition
2323
@variable(p1.X, x1[1:5], Int)
2424
@variable(p1.X, y1 >= 0)
2525

2626
@constraint(p1.X, ...) # add your constraints
2727

28-
# do so for players 2..n
28+
... # the same for players 2..n
2929

3030
# set payoff function
3131
set_payoff!(p1, c' * x1 + 0.5 * x1' * x2)
32+
...
3233
```
3334

3435
## Payoff Functions
@@ -57,15 +58,15 @@ julia> set_payoff!(P1, -x1*x1 + x1*x2)
5758

5859
julia> set_payoff!(P2, -x2*x2 + x1*x2);
5960

60-
julia> Σ, payoff_improvements = IPG.SGM([P1, P2], SCIP.Optimizer, max_iter=5);
61+
julia> Σ, payoff_improvements = SGM([P1, P2], SCIP.Optimizer, max_iter=5);
6162

6263
julia> Σ[end]
6364
2-element Vector{DiscreteMixedStrategy}:
6465
DiscreteMixedStrategy([1.0], [[0.625]])
6566
DiscreteMixedStrategy([1.0], [[1.25]])
6667

6768
```
68-
Further details in [`example-5.3.ipynb`](notebooks/example-5.3.ipynb).
69+
Further details in [`example_5_3.jl`](examples/example_5_3.jl). In fact, check out [`examples/`](examples/).
6970

7071
<!-- ## Two-player games
7172
@@ -91,7 +92,7 @@ julia> @variable(players[1].X, x1, start=10); @constraint(players[1].X, x1 >= 0)
9192
9293
julia> @variable(players[2].X, x2, start=10); @constraint(players[2].X, x2 >= 0);
9394
94-
julia> Σ, payoff_improvements = IPG.SGM(players, SCIP.Optimizer, max_iter=5);
95+
julia> Σ, payoff_improvements = SGM(players, SCIP.Optimizer, max_iter=5);
9596
9697
julia> Σ[end]
9798
2-element Vector{DiscreteMixedStrategy}:
@@ -104,7 +105,7 @@ julia> Σ[end]
104105

105106
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.
106107

107-
A practical example is shown in notebook [`example-5.3.ipynb`](./example-5.3.ipynb), at section _Customization_. Below, we detail the customizable parts and the available options.
108+
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.
108109

109110
### Initialization
110111

@@ -128,6 +129,6 @@ A deviation from the candidate equilibrium is found by computing a player's best
128129

129130
### MIP Solver
130131

131-
MIP solvers are used (in the default methods) for the initialization of strategies and for computing a deviation (best response, by default) from the candidate equilibrium. Any JuMP-supported MIP solver that can handle the players' problem can be used in SGM. For example, it may be that your players have quadratic constraints, so you will need a MIQCP solver. Your call will look like `IGP.SGM(my_players, MySolver.Optimizer)`.
132+
MIP solvers are used (in the default methods) for the initialization of strategies and for computing a deviation (best response, by default) from the candidate equilibrium. Any JuMP-supported MIP solver that can handle the players' problem can be used in SGM. For example, it may be that your players have quadratic constraints, so you will need a MIQCP solver. Your call will look like `SGM(my_players, MySolver.Optimizer)`.
132133

133134
The algorithm also supports using a different solver for each player. You just need to initialize each player's strategy space with that optimizer factory or use JuMP's method, e.g., `set_optimizer(player, MySolver.Optimizer)`. It is important to note that the `optimizer_factory` that is provided to SGM will _not_ overwrite any player's optimizer. It will be set as the player's optimizer only if no optimizer has been set.

examples/Project.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[deps]
2+
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
3+
ExcelFiles = "89b67f3b-d1aa-5f6f-9ca4-282e8d98620d"
4+
IPG = "2f50017d-522a-4a0a-b317-915d5df6b243"
5+
NormalGames = "4dc104ef-1e0c-4a09-93ea-14ddc8e86204"
6+
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
7+
SCIP = "82193955-e24f-5292-bf16-6f2c5261a85f"
8+
StringEncodings = "69024149-9ee7-55f6-a4c4-859efe599b68"
9+
10+
[sources]
11+
IPG = {path = ".."}
12+
NormalGames = {rev = "optimizer-agnosticism", url = "https://github.com/mxmmargarida/Normal-form-games"}

examples/cfld.jl

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
"""
2+
Two-player Competitive Facility Location and Design (CFLD) problem.
3+
4+
Based on Crönert and Minner (2024). For futher details, see Section 3.2 of:
5+
[*T. Crönert, S. Minner, "Equilibrium Identification and Selection in Finite Games". 2024. Operations Research*](https://doi.org/10.1287/opre.2022.2413)
6+
7+
Note that the players have *nonlinear* payoffs, which SGM can handle because this is a
8+
two-player game. In other words, the polymatrix can still be computed for the sampled games.
9+
10+
# Notes
11+
- SCIP can be replaced with other solvers, such as Gurobi or CPLEX.
12+
"""
13+
14+
using IPG, IPG.JuMP, SCIP
15+
16+
# ==== Problem Parameters ====
17+
d_max = 20
18+
β = 0.5
19+
B = 40 # B = B^A = B^B
20+
21+
# ==== Problem Data ====
22+
a = [
23+
3.00 7.31 14.35;
24+
3.00 7.14 10.41;
25+
3.00 4.43 6.25;
26+
3.00 7.21 12.57;
27+
3.00 4.16 11.50;
28+
3.00 4.36 6.77;
29+
3.00 6.63 13.63;
30+
3.00 4.89 6.16;
31+
3.00 6.93 12.90;
32+
3.00 5.95 9.82;
33+
3.00 5.22 8.19;
34+
3.00 4.65 13.12;
35+
3.00 5.90 13.17;
36+
3.00 5.50 7.50;
37+
3.00 7.74 9.30;
38+
3.00 5.01 6.05;
39+
3.00 5.35 10.35;
40+
3.00 4.76 7.15;
41+
3.00 5.65 9.19;
42+
3.00 5.61 14.32;
43+
3.00 8.97 12.93;
44+
3.00 5.86 13.04;
45+
3.00 5.98 14.29;
46+
3.00 4.60 11.48;
47+
3.00 4.28 9.89;
48+
3.00 4.26 5.31;
49+
3.00 6.01 11.17;
50+
3.00 7.14 9.95;
51+
3.00 5.98 9.19;
52+
3.00 4.76 13.25;
53+
3.00 6.11 8.19;
54+
3.00 5.93 9.93;
55+
3.00 5.34 13.93;
56+
3.00 4.94 8.32;
57+
3.00 4.37 8.58;
58+
3.00 4.51 8.58;
59+
3.00 5.62 8.16;
60+
3.00 4.56 10.33;
61+
3.00 6.56 7.92;
62+
3.00 7.92 16.48;
63+
3.00 6.98 12.84;
64+
3.00 5.81 11.57;
65+
3.00 7.40 12.45;
66+
3.00 4.80 11.05;
67+
3.00 6.62 8.61;
68+
3.00 8.93 11.76;
69+
3.00 7.38 14.59;
70+
3.00 8.66 11.27;
71+
3.00 5.49 8.06;
72+
3.00 5.23 11.40
73+
]
74+
f = [
75+
12.70 25.75 60.05;
76+
13.67 33.94 45.15;
77+
13.20 11.91 17.13;
78+
13.03 37.58 46.85;
79+
14.58 27.63 24.94;
80+
10.26 9.96 32.24;
81+
10.49 25.28 35.73;
82+
13.07 19.89 28.53;
83+
16.65 26.25 66.88;
84+
14.86 34.10 54.43;
85+
12.90 20.61 47.22;
86+
12.50 24.02 62.47;
87+
18.76 24.12 59.14;
88+
19.63 28.00 42.23;
89+
23.37 36.27 43.87;
90+
19.28 20.00 28.22;
91+
11.69 35.63 48.83;
92+
11.17 29.47 22.53;
93+
10.74 22.22 20.72;
94+
17.06 21.77 72.05;
95+
13.30 32.47 54.42;
96+
10.90 20.49 46.20;
97+
12.94 22.73 62.78;
98+
12.30 17.13 21.80;
99+
22.82 29.16 34.72;
100+
20.50 13.67 25.78;
101+
13.32 20.25 35.01;
102+
17.04 31.14 45.76;
103+
18.18 18.25 41.56;
104+
13.33 26.81 28.67;
105+
8.18 39.76 46.08;
106+
9.30 18.06 52.03;
107+
10.58 19.97 52.17;
108+
15.18 18.75 32.50;
109+
15.08 26.37 16.99;
110+
10.03 17.78 26.01;
111+
18.82 16.54 24.26;
112+
14.08 22.27 32.44;
113+
13.92 32.62 33.39;
114+
16.08 17.31 47.45;
115+
15.29 32.82 51.17;
116+
18.97 34.00 26.90;
117+
17.51 43.23 67.03;
118+
15.12 29.63 44.91;
119+
18.69 33.23 29.94;
120+
16.75 36.22 65.61;
121+
18.76 45.20 39.84;
122+
11.16 26.68 47.90;
123+
18.34 29.54 26.95;
124+
18.25 23.82 50.04
125+
]
126+
locs = [
127+
(81.423, 63.358),
128+
(23.986, 24.907),
129+
(58.360, 94.707),
130+
(70.994, 95.909),
131+
(16.525, 18.016),
132+
(33.446, 55.179),
133+
(35.339, 61.412),
134+
(82.131, 82.424),
135+
(17.120, 30.418),
136+
(89.266, 36.745),
137+
(22.486, 28.671),
138+
(50.849, 62.173),
139+
(66.774, 83.822),
140+
(34.897, 22.596),
141+
(63.452, 8.297),
142+
(3.999, 8.276),
143+
(25.426, 49.606),
144+
(85.620, 81.129),
145+
(60.199, 9.892),
146+
(88.603, 50.964),
147+
(19.818, 39.411),
148+
(89.291, 99.867),
149+
(91.544, 33.614),
150+
(35.724, 96.915),
151+
(40.176, 88.541),
152+
(43.333, 70.414),
153+
(37.977, 94.200),
154+
(80.580, 29.840),
155+
(43.137, 70.615),
156+
(67.976, 54.672),
157+
(12.704, 76.245),
158+
(38.914, 92.317),
159+
(60.930, 43.457),
160+
(35.754, 32.293),
161+
(98.959, 20.722),
162+
(39.608, 48.879),
163+
(16.806, 26.254),
164+
(57.243, 29.399),
165+
(93.555, 42.752),
166+
(13.555, 30.350),
167+
(46.719, 7.948),
168+
(23.052, 28.537),
169+
(56.692, 80.636),
170+
(38.770, 59.428),
171+
(27.251, 86.887),
172+
(2.071, 59.893),
173+
(68.009, 76.120),
174+
(41.762, 57.527),
175+
(9.797, 41.724),
176+
(95.959, 73.750)
177+
]
178+
w = [3, 9, 6, 4, 4, 3, 4, 9, 2, 6, 10, 6, 10, 8, 2, 7, 2, 3, 7, 5, 4, 4, 2, 2, 6, 8, 3, 7, 8, 4, 2, 6, 2, 9, 3, 4, 6, 8, 7, 5, 5, 2, 4, 1, 4, 3, 7, 6, 8, 4]
179+
180+
I = J = 1:size(a,1)
181+
R = 1:size(a,2)
182+
183+
# It is not clear how the distance matrix is computed. In the source, they say that "The
184+
# matrix d_ij is the Euclidean distance matrix", but that does not give me the info on the
185+
# location of the facilities, as the .txt only has the location of the customers.
186+
187+
# I assume that the facilities' locations are the same as the customers
188+
euclidean_distance(x,y) = sqrt((x[1]-y[1])^2 + (x[2]-y[2])^2)
189+
d = [euclidean_distance(locs[i], locs[j]) for i in I, j in J]
190+
191+
# utility computation
192+
CM_utility(i,j,r) = (d[i,j] <= d_max) ? a[i,r]/((d[i,j]+1)^β) : 0
193+
u = [CM_utility(i,j,r) for i in I, j in J, r in R]
194+
195+
196+
# ==== Player Definition ====
197+
player_A = Player(; name="Player A")
198+
@variable(player_A.X, x1[I,R], Bin)
199+
@constraint(player_A.X, sum(f[i,r] .* x1[i,r] for i in I for r in R) <= B)
200+
@constraint(player_A.X, [i in I], sum(x1[i,r] for r in R) <= 1)
201+
202+
player_B = Player(; name="Player B")
203+
@variable(player_B.X, x2[I,R], Bin)
204+
@constraint(player_B.X, sum(f[i,r] .* x2[i,r] for i in I for r in R) <= B)
205+
@constraint(player_B.X, [i in I], sum(x2[i,r] for r in R) <= 1)
206+
207+
const ε = 1e-3 # small value to prevent division by zero
208+
function cfld_payoff(x_self, x_other)
209+
self_costs = [sum(u[i,j,r] * x_self[i,r] for i in I for r in R) for j in J]
210+
others_costs = [sum(u[i,j,r] * x_other[i,r] for i in I for r in R) for j in J]
211+
return sum(
212+
w[j] * self_costs[j] / (self_costs[j] + others_costs[j] + ε)
213+
for j in J
214+
)
215+
end
216+
217+
set_payoff!(player_A, cfld_payoff(x1, x2))
218+
set_payoff!(player_B, cfld_payoff(x2, x1))
219+
220+
# ==== SGM ====
221+
IPG.initialize_strategies = IPG.initialize_strategies_player_alone
222+
223+
Σ, payoff_improvements = SGM([player_A, player_B], SCIP.Optimizer, max_iter=5, verbose=true)

examples/example_5_3.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
Computing an equilibrium with SGM (Example 5.3, CLP, 2022)
3+
4+
Implementation of Example 5.3 from Carvalho, Lodi, and Pedroso (2022). For futher details,
5+
see:
6+
[*M. Carvalho, A. Lodi, J. P. Pedroso, "Computing equilibria for integer programming games". 2022. European Journal of Operational Research*](https://www.sciencedirect.com/science/article/pii/S0377221722002727)
7+
[*M. Carvalho, A. Lodi, J. P. Pedroso, "Computing Nash equilibria for integer programming games". 2020. arXiv:2012.07082*](https://arxiv.org/abs/2012.07082)
8+
9+
Notes
10+
- SCIP can be replaced with other solvers, such as Gurobi or CPLEX.
11+
"""
12+
13+
using IPG, SCIP
14+
15+
# ==== Player Definition ====
16+
# Strategy spaces are defined through JuMP models
17+
player1 = Player()
18+
@variable(player1.X, x1, start=10.0)
19+
@constraint(player1.X, x1 >= 0)
20+
21+
player2 = Player()
22+
@variable(player2.X, x2, start=10.0)
23+
@constraint(player2.X, x2 >= 0)
24+
25+
# Payoffs are defined using JuMP expressions
26+
set_payoff!(player1, -x1^2 + x1*x2)
27+
set_payoff!(player2, -x2^2 + x1*x2)
28+
29+
# ==== SGM Algorithm Subroutines ====
30+
# The following are the default options for the SGM algorithm, so they can be omitted.
31+
# See docstrings of each parameter for further details
32+
IPG.initialize_strategies = IPG.initialize_strategies_feasibility
33+
IPG.solve = IPG.solve_PNS
34+
IPG.find_deviation = IPG.find_deviation_best_response
35+
36+
# It is also possible to pass custom subroutines. The following is a re-implementation of
37+
# `IPG.get_player_order_random` for demonstration.
38+
using Random
39+
function get_player_random_order(players::Vector{Player}, iter::Integer, Σ_S::Vector{Profile{DiscreteMixedStrategy}}, payoff_improvements::Vector{Tuple{Player,Float64}})
40+
return shuffle(keys(players))
41+
end
42+
IPG.get_player_order = get_player_random_order
43+
44+
# ==== SGM ====
45+
players = [player1, player2]
46+
Σ, payoff_improvements = SGM(players, SCIP.Optimizer, max_iter=100, verbose=true);

0 commit comments

Comments
 (0)