diff --git a/catalog/books/shohamleytonbrown2008/fig5_1.efg b/catalog/books/shohamleytonbrown2008/fig5_1.efg new file mode 100644 index 000000000..49f598d28 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_1.efg @@ -0,0 +1,21 @@ +EFG 2 R "Fig 5.1 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.1 from :cite:p:`ShoLeyB08`. +This is a sharing game. Imagine a brother and sister sharing two indivisible and identical presents +from their parents. First the brother suggests a split, which can be one of three: he +keeps both, she keeps both, or they each keep one. Then the sister chooses whether +to accept or reject the split. If she accepts they each get their allocated present(s), +and otherwise neither gets any gift. +" + +p "" 1 1 "" { "2-0" "1-1" "0-2" } 0 +p "" 2 2 "" { "no" "yes" } 0 +t "" 1 "" { 0 0 } +t "" 2 "" { 2 0 } +p "" 2 3 "" { "no" "yes" } 0 +t "" 3 "" { 0 0 } +t "" 4 "" { 1 1 } +p "" 2 4 "" { "no" "yes" } 0 +t "" 5 "" { 0 0 } +t "" 6 "" { 0 2 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_10.efg b/catalog/books/shohamleytonbrown2008/fig5_10.efg new file mode 100644 index 000000000..94cd759ff --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_10.efg @@ -0,0 +1,16 @@ +EFG 2 R "Fig 5.10 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.10 from :cite:p:`ShoLeyB08`. +This is an example of an imperfect-information game. +" + +p "" 1 2 "" { "L" "R" } 0 +p "" 2 3 "" { "A" "B" } 0 +p "" 1 1 "" { "l" "r" } 0 +t "" 1 "" { 0 0 } +t "" 2 "" { 2 4 } +p "" 1 1 0 +t "" 3 "" { 4 2 } +t "" 4 "" { 0 0 } +t "" 5 "" { 0 0 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_10__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_10__original_layout.ef new file mode 100644 index 000000000..55e983222 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_10__original_layout.ef @@ -0,0 +1,12 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 player 2 xshift -3.58 from 0,1 move L +level 2 node 2 xshift 3.58 from 0,1 move R payoffs 0 0 +level 4 node 1 xshift -2.86 from 2,1 move A +level 4 node 2 xshift 2.86 from 2,1 move B +level 6 node 1 xshift -1.43 from 4,1 move l payoffs 0 0 +level 6 node 2 xshift 1.43 from 4,1 move r payoffs 2 4 +level 6 node 3 xshift -1.43 from 4,2 move l payoffs 4 2 +level 6 node 4 xshift 1.43 from 4,2 move r payoffs 0 0 +iset 4,1 4,2 player 1 diff --git a/catalog/books/shohamleytonbrown2008/fig5_11.efg b/catalog/books/shohamleytonbrown2008/fig5_11.efg new file mode 100644 index 000000000..a972fb90b --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_11.efg @@ -0,0 +1,14 @@ +EFG 2 R "Fig 5.11 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.11 from :cite:p:`ShoLeyB08`. +The Prisoner's Dilemma game in extensive form. +" + +p "" 1 2 "" { "C" "D" } 0 +p "" 2 1 "" { "c" "d" } 0 +t "" 1 "" { -1 -1 } +t "" 2 "" { -4 0 } +p "" 2 1 0 +t "" 3 "" { 0 -4 } +t "" 4 "" { -3 -3 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_11__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_11__original_layout.ef new file mode 100644 index 000000000..8aa60582e --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_11__original_layout.ef @@ -0,0 +1,10 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 xshift -3.58 from 0,1 move C +level 2 node 2 xshift 3.58 from 0,1 move D +level 4 node 1 xshift -1.79 from 2,1 move c payoffs -1 -1 +level 4 node 2 xshift 1.79 from 2,1 move d payoffs -4 0 +level 4 node 3 xshift -1.79 from 2,2 move c payoffs 0 -4 +level 4 node 4 xshift 1.79 from 2,2 move d payoffs -3 -3 +iset 2,1 2,2 player 2 diff --git a/catalog/books/shohamleytonbrown2008/fig5_12.efg b/catalog/books/shohamleytonbrown2008/fig5_12.efg new file mode 100644 index 000000000..27c70afe2 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_12.efg @@ -0,0 +1,15 @@ +EFG 2 R "Fig 5.12 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.12 from :cite:p:`ShoLeyB08`. +A game with imperfect recall, in particular absent-mindedness. +" + + +p "" 1 1 "" { "L" "R" } 0 +p "" 1 1 0 +t "" 1 "" { 1 0 } +t "" 2 "" { 100 100 } +p "" 2 2 "" { "U" "D" } 0 +t "" 3 "" { 5 1 } +t "" 4 "" { 2 2 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_12__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_12__original_layout.ef new file mode 100644 index 000000000..98b720cdc --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_12__original_layout.ef @@ -0,0 +1,15 @@ +% Extensive-form game transcribed from the uploaded image +player 1 name 1 +player 2 name 2 + +% Player 1's two decision nodes are in the same information set. +level 0 node 1 +level 2 node 1 xshift -3 from 0,1 move L +level 2 node 2 xshift 3 from 0,1 move R player 2 + +level 4 node 1 xshift -1 from 2,1 move L payoffs 1 0 +level 4 node 2 xshift 1 from 2,1 move R payoffs 100 100 +level 4 node 3 xshift -1 from 2,2 move U payoffs 5 1 +level 4 node 4 xshift 1 from 2,2 move D payoffs 2 2 + +iset 0,1 2,1 player 1 diff --git a/catalog/books/shohamleytonbrown2008/fig5_15.efg b/catalog/books/shohamleytonbrown2008/fig5_15.efg new file mode 100644 index 000000000..ceb402d94 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_15.efg @@ -0,0 +1,17 @@ +EFG 2 R "Fig 5.15 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.15 from :cite:p:`ShoLeyB08`. +A game with imperfect information. +This example shows how a requirement that a substrategy be a best response in +all subgames is too simplistic for defining SPE in games with imperfect information. +" + +p "" 1 2 "" { "L" "C" "R" } 0 +t "" 1 "" { 1 1 } +p "" 2 1 "" { "U" "D" } 0 +t "" 2 "" { 0 1000 } +t "" 3 "" { 0 0 } +p "" 2 1 0 +t "" 4 "" { 1 0 } +t "" 5 "" { 3 1 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_15__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_15__original_layout.ef new file mode 100644 index 000000000..50d5d3446 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_15__original_layout.ef @@ -0,0 +1,11 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 xshift -5.01 from 0,1 move L payoffs 1 1 +level 2 node 2 xshift -0.72 from 0,1 move C +level 2 node 3 xshift 5.01 from 0,1 move R +level 4 node 1 xshift -1.43 from 2,2 move U payoffs 0 1000 +level 4 node 2 xshift 1.43 from 2,2 move D payoffs 0 0 +level 4 node 3 xshift -1.43 from 2,3 move U payoffs 1 0 +level 4 node 4 xshift 1.43 from 2,3 move D payoffs 3 1 +iset 2,2 2,3 player 2 diff --git a/catalog/books/shohamleytonbrown2008/fig5_1__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_1__original_layout.ef new file mode 100644 index 000000000..492f04754 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_1__original_layout.ef @@ -0,0 +1,12 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 player 2 xshift -4.77 from 0,1 move 2-0 +level 2 node 2 player 2 xshift 0 from 0,1 move 1-1 +level 4 node 1 xshift -1.19 from 2,1 move no payoffs 0 0 +level 4 node 2 xshift 1.19 from 2,1 move yes payoffs 2 0 +level 2 node 3 player 2 xshift 4.77 from 0,1 move 0-2 +level 4 node 3 xshift -1.19 from 2,2 move no payoffs 0 0 +level 4 node 4 xshift 1.19 from 2,2 move yes payoffs 1 1 +level 4 node 5 xshift -1.19 from 2,3 move no payoffs 0 0 +level 4 node 6 xshift 1.19 from 2,3 move yes payoffs 0 2 diff --git a/catalog/books/shohamleytonbrown2008/fig5_2.efg b/catalog/books/shohamleytonbrown2008/fig5_2.efg new file mode 100644 index 000000000..5ed1bd189 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_2.efg @@ -0,0 +1,16 @@ +EFG 2 R "Fig 5.2 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.2 from :cite:p:`ShoLeyB08`. +This is an example of a perfect-information game in extensive form. +" + +p "" 1 1 "" { "A" "B" } 0 +p "" 2 2 "" { "C" "D" } 0 +t "" 1 "" { 3 8 } +t "" 2 "" { 8 3 } +p "" 2 3 "" { "E" "F" } 0 +t "" 3 "" { 5 5 } +p "" 1 4 "" { "G" "H" } 0 +t "" 4 "" { 2 10 } +t "" 5 "" { 1 0 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_2__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_2__original_layout.ef new file mode 100644 index 000000000..1d55ae617 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_2__original_layout.ef @@ -0,0 +1,11 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 player 2 xshift -3.22 from 0,1 move A +level 2 node 2 player 2 xshift 3.22 from 0,1 move B +level 4 node 1 xshift -1.43 from 2,1 move C payoffs 3 8 +level 4 node 2 xshift 1.43 from 2,1 move D payoffs 8 3 +level 4 node 3 xshift -2.15 from 2,2 move E payoffs 5 5 +level 4 node 4 player 1 xshift 2.15 from 2,2 move F +level 6 node 1 xshift -1.43 from 4,4 move G payoffs 2 10 +level 6 node 2 xshift 1.43 from 4,4 move H payoffs 1 0 diff --git a/catalog/books/shohamleytonbrown2008/fig5_9.efg b/catalog/books/shohamleytonbrown2008/fig5_9.efg new file mode 100644 index 000000000..861c7c0b7 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_9.efg @@ -0,0 +1,22 @@ +EFG 2 R "Fig 5.9 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.9 from :cite:p:`ShoLeyB08`. +This is the centipede game. In this game two players alternate in making decisions, at each +turn choosing between going down and ending the game or going across and +continuing it. +This example is used to explain the criticisms of backward induction for finding subgame-perfect equilibrium. +Note that centipede is also a parametrized game, with the parameter being the number of rounds. +" + +p "" 1 1 "" { "A" "D" } 0 +t "" 1 "" { 1 0 } +p "" 2 2 "" { "A" "D" } 0 +t "" 2 "" { 0 2 } +p "" 1 3 "" { "A" "D" } 0 +t "" 3 "" { 3 1 } +p "" 2 4 "" { "A" "D" } 0 +t "" 4 "" { 2 4 } +p "" 1 5 "" { "A" "D" } 0 +t "" 5 "" { 4 3 } +t "" 6 "" { 3 5 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_9__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_9__original_layout.ef new file mode 100644 index 000000000..9f477bc52 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_9__original_layout.ef @@ -0,0 +1,13 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 xshift -2.31 from 0,1 move A payoffs 1 0 +level 2 node 2 player 2 xshift 2.31 from 0,1 move D +level 4 node 1 xshift -2.24 from 2,2 move A payoffs 0 2 +level 4 node 2 player 1 xshift 2.24 from 2,2 move D +level 6 node 1 xshift -2.09 from 4,2 move A payoffs 3 1 +level 6 node 2 player 2 xshift 2.09 from 4,2 move D +level 8 node 1 xshift -1.79 from 6,2 move A payoffs 2 4 +level 8 node 2 player 1 xshift 1.79 from 6,2 move D +level 10 node 1 xshift -1.19 from 8,2 move A payoffs 4 3 +level 10 node 2 xshift 1.19 from 8,2 move D payoffs 3 5 diff --git a/catalog/books/shohamleytonbrown2008/fig6_2.efg b/catalog/books/shohamleytonbrown2008/fig6_2.efg new file mode 100644 index 000000000..6a543e361 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig6_2.efg @@ -0,0 +1,38 @@ +EFG 2 R "Fig 6.2 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 6.2 from :cite:p:`ShoLeyB08`. +This is a repeated game, where Prisoner's Dilemma is played twice. +" + +p "" 1 6 "" { "C" "D" } 0 +p "" 2 1 "" { "c" "d" } 0 +p "" 1 7 "" { "C" "D" } 0 +p "" 2 4 "" { "c" "d" } 0 +t "" 1 "" { -2 -2 } +t "" 2 "" { -5 -1 } +p "" 2 4 0 +t "" 3 "" { -1 -5 } +t "" 4 "" { -4 -4 } +p "" 1 8 "" { "C" "D" } 0 +p "" 2 5 "" { "c" "d" } 0 +t "" 5 "" { -5 -1 } +t "" 6 "" { -8 0 } +p "" 2 5 0 +t "" 7 "" { -4 -4 } +t "" 8 "" { -7 -3 } +p "" 2 1 0 +p "" 1 9 "" { "C" "D" } 0 +p "" 2 2 "" { "c" "d" } 0 +t "" 9 "" { -1 -5 } +t "" 10 "" { -4 -4 } +p "" 2 2 0 +t "" 11 "" { 0 -8 } +t "" 12 "" { -3 -7 } +p "" 1 10 "" { "C" "D" } 0 +p "" 2 3 "" { "c" "d" } 0 +t "" 13 "" { -4 -4 } +t "" 14 "" { -7 -3 } +p "" 2 3 0 +t "" 15 "" { -3 -7 } +t "" 16 "" { -6 -6 } diff --git a/catalog/books/shohamleytonbrown2008/fig6_2__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig6_2__original_layout.ef new file mode 100644 index 000000000..563e3b0f0 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig6_2__original_layout.ef @@ -0,0 +1,38 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 xshift -6.37 from 0,1 move C +level 2 node 2 xshift 6.37 from 0,1 move D +level 4 node 1 player 1 xshift -3.19 from 2,1 move c +level 4 node 2 player 1 xshift 3.19 from 2,1 move d +level 4 node 3 player 1 xshift -3.19 from 2,2 move c +level 4 node 4 player 1 xshift 3.19 from 2,2 move d +level 6 node 1 xshift -1.59 from 4,1 move C +level 6 node 2 xshift 1.59 from 4,1 move D +level 6 node 3 xshift -1.59 from 4,2 move C +level 6 node 4 xshift 1.59 from 4,2 move D +level 6 node 5 xshift -1.59 from 4,3 move C +level 6 node 6 xshift 1.59 from 4,3 move D +level 6 node 7 xshift -1.59 from 4,4 move C +level 6 node 8 xshift 1.59 from 4,4 move D +level 8 node 1 xshift -0.8 from 6,1 move c payoffs -2 -2 +level 8 node 2 xshift 0.8 from 6,1 move d payoffs -5 -1 +level 8 node 3 xshift -0.8 from 6,2 move c payoffs -1 -5 +level 8 node 4 xshift 0.8 from 6,2 move d payoffs -4 -4 +level 8 node 5 xshift -0.8 from 6,3 move c payoffs -5 -1 +level 8 node 6 xshift 0.8 from 6,3 move d payoffs -8 0 +level 8 node 7 xshift -0.8 from 6,4 move c payoffs -4 -4 +level 8 node 8 xshift 0.8 from 6,4 move d payoffs -7 -3 +level 8 node 9 xshift -0.8 from 6,5 move c payoffs -1 -5 +level 8 node 10 xshift 0.8 from 6,5 move d payoffs -4 -4 +level 8 node 11 xshift -0.8 from 6,6 move c payoffs 0 -8 +level 8 node 12 xshift 0.8 from 6,6 move d payoffs -3 -7 +level 8 node 13 xshift -0.8 from 6,7 move c payoffs -4 -4 +level 8 node 14 xshift 0.8 from 6,7 move d payoffs -7 -3 +level 8 node 15 xshift -0.8 from 6,8 move c payoffs -3 -7 +level 8 node 16 xshift 0.8 from 6,8 move d payoffs -6 -6 +iset 2,1 2,2 player 2 +iset 6,5 6,6 player 2 +iset 6,7 6,8 player 2 +iset 6,1 6,2 player 2 +iset 6,3 6,4 player 2 diff --git a/catalog/books/shohamleytonbrown2008/fig6_8.efg b/catalog/books/shohamleytonbrown2008/fig6_8.efg new file mode 100644 index 000000000..08e0e3cba --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig6_8.efg @@ -0,0 +1,36 @@ +EFG 2 R "Fig 6.8 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 6.8 from :cite:p:`ShoLeyB08`. +This is a Bayesian game, represented in EFG with Nature deciding the types. +" + +c "" 5 "" { "MP" 1/4 "PD" 1/4 "Coord" 1/4 "BoS" 1/4 } 0 +p "" 1 1 "" { "U" "D" } 0 +p "" 2 3 "" { "L" "R" } 0 +t "" 1 "" { 2 0 } +t "" 2 "" { 0 2 } +p "" 2 3 0 +t "" 3 "" { 0 2 } +t "" 4 "" { 2 0 } +p "" 1 1 0 +p "" 2 4 "" { "L" "R" } 0 +t "" 5 "" { 2 2 } +t "" 6 "" { 0 3 } +p "" 2 4 0 +t "" 7 "" { 3 0 } +t "" 8 "" { 1 1 } +p "" 1 2 "" { "U" "D" } 0 +p "" 2 3 0 +t "" 9 "" { 2 2 } +t "" 10 "" { 0 0 } +p "" 2 3 0 +t "" 11 "" { 0 0 } +t "" 12 "" { 1 1 } +p "" 1 2 0 +p "" 2 4 0 +t "" 13 "" { 2 1 } +t "" 14 "" { 0 0 } +p "" 2 4 0 +t "" 15 "" { 0 0 } +t "" 16 "" { 1 2 } diff --git a/catalog/books/shohamleytonbrown2008/fig6_8__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig6_8__original_layout.ef new file mode 100644 index 000000000..a01dfbf0f --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig6_8__original_layout.ef @@ -0,0 +1,92 @@ +% Four games with uniform chance and non-overlapping information sets. +% Nature chooses among MP, PD, Coord, and BoS with probability 1/4 each. +% +% The Player 2 information sets are separated vertically: +% - MP and Coord nodes are at level 5. +% - PD and BoS nodes are at level 11. +% +% The additional spacing prevents the U/D labels and the terminal +% payoffs of the upper subtrees from overlapping either information set. + +player 0 name Nature +player 1 name 1 +player 2 name 2 + +% Nature root +level 0 node 1 player 0 + +% Nature moves +% MP and PD belong to Player 1's first information set. +level 2 node 1 xshift -7.5 from 0,1 move MP~(1/4) +level 2 node 2 xshift -2.5 from 0,1 move PD~(1/4) + +% Coord and BoS belong to Player 1's second information set. +level 3 node 3 xshift 2.5 from 0,1 move Coord~(1/4) +level 3 node 4 xshift 7.5 from 0,1 move BoS~(1/4) + +% Player 1 moves U/D + +% Matching Pennies: +% These nodes belong to Player 2 information set 1. +level 5 node 1 xshift -1 from 2,1 move U +level 5 node 2 xshift 1 from 2,1 move D + +% Prisoner's Dilemma: +% These nodes belong to Player 2 information set 2. +level 11 node 3 xshift -1 from 2,2 move U +level 11 node 4 xshift 1 from 2,2 move D + +% Coordination: +% These nodes belong to Player 2 information set 1. +level 5 node 5 xshift -1 from 3,3 move U +level 5 node 6 xshift 1 from 3,3 move D + +% Battle of the Sexes: +% These nodes belong to Player 2 information set 2. +level 11 node 7 xshift -1 from 3,4 move U +level 11 node 8 xshift 1 from 3,4 move D + +% Player 2 moves L/R and terminal payoffs + +% Matching Pennies +% The terminal level is sufficiently above the second information set. +level 8 node 1 xshift -0.55 from 5,1 move L payoffs 2 0 +level 8 node 2 xshift 0.55 from 5,1 move R payoffs 0 2 +level 8 node 3 xshift -0.55 from 5,2 move L payoffs 0 2 +level 8 node 4 xshift 0.55 from 5,2 move R payoffs 2 0 + +% Prisoner's Dilemma +level 14 node 5 xshift -0.55 from 11,3 move L payoffs 2 2 +level 14 node 6 xshift 0.55 from 11,3 move R payoffs 0 3 +level 14 node 7 xshift -0.55 from 11,4 move L payoffs 3 0 +level 14 node 8 xshift 0.55 from 11,4 move R payoffs 1 1 + +% Coordination +% These payoffs are placed at level 8, leaving three levels before +% the lower Player 2 information set at level 11. +level 8 node 9 xshift -0.55 from 5,5 move L payoffs 2 2 +level 8 node 10 xshift 0.55 from 5,5 move R payoffs 0 0 +level 8 node 11 xshift -0.55 from 5,6 move L payoffs 0 0 +level 8 node 12 xshift 0.55 from 5,6 move R payoffs 1 1 + +% Battle of the Sexes +level 14 node 13 xshift -0.55 from 11,7 move L payoffs 2 1 +level 14 node 14 xshift 0.55 from 11,7 move R payoffs 0 0 +level 14 node 15 xshift -0.55 from 11,8 move L payoffs 0 0 +level 14 node 16 xshift 0.55 from 11,8 move R payoffs 1 2 + +% Player 1 information sets + +% Player 1 cannot distinguish MP from PD. +iset 2,1 2,2 player 1 + +% Player 1 cannot distinguish Coordination from Battle of the Sexes. +iset 3,3 3,4 player 1 + +% Player 2 information sets + +% Player 2 cannot distinguish MP from Coordination. +iset 5,1 5,2 5,5 5,6 player 2 + +% Player 2 cannot distinguish PD from Battle of the Sexes. +iset 11,3 11,4 11,7 11,8 player 2 diff --git a/src/solvers/hp/hp.cc b/src/solvers/hp/hp.cc index 1e75d8ddd..bd2aed987 100644 --- a/src/solvers/hp/hp.cc +++ b/src/solvers/hp/hp.cc @@ -20,7 +20,6 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include #include "gambit.h" #include "solvers/hp/hp.h" #include "solvers/hp/hpsystem.h" @@ -39,34 +38,70 @@ HPStrategySolve(const MixedStrategyProfile &p_prior) const PathTracer tracer; const PathTracer::TraceDirection direction = PathTracer::TraceDirection::Positive; const size_t tracking_index = 1; // Track the first variable (t) for orientation - auto termination_condition = [](const Vector &point) { return point[1] >= 1.5; }; - auto criterion_function = [](const Vector &point, - const Vector &tangent) -> double { return point[1] - 1.0; }; + + const double t_target = 1.0; + const double tol = 1e-8; + double last_t = 0.0; + bool has_crossed = false; + + auto termination_condition = [t_target, &last_t, &has_crossed, tol, + &system](const Vector &point) { + const double t = point[1]; + + // Path tracer reaches tol + if (system.ExtractEquilibrium(point).GetMaxRegret() <= tol && t >= t_target - tol) { + return true; + } + + if (t > t_target) { + if (!has_crossed) { + has_crossed = true; + } + else if (t > last_t + tol) { // Criterion function is not working; polish will do the job + return true; + } + } + else { + // Criterion function might take t back to being less than t_target + has_crossed = false; + } + + last_t = t; + return false; + }; + + auto criterion_function = [t_target](const Vector &point, + const Vector &tangent) -> double { + return point[1] - t_target; + }; + + auto polishing_termination_condition = [tol, &system](const Vector &point) -> bool { + return system.ExtractEquilibrium(point).GetMaxRegret() <= tol; + }; const TracePathResult result = tracer.TracePath( [&system](const Vector &point, Vector &lhs) { system.GetValue(point, lhs); }, [&system](const Vector &point, Matrix &jac) { system.GetJacobian(point, jac); }, - x, direction, tracking_index, termination_condition, - [&system](const Vector &point) { - std::cout << "[Path Tracer Step] t = " << point[1]; - std::cout << " | Alfas: "; - for (size_t i = 2; i <= 5; ++i) { - std::cout << point[i] << " "; - } - std::cout << "| Mu: " << point[6] << " " << point[7] << std::endl; - - std::cout << "Full point vector in probabilities: "; - Vector prob_vector = system.ExtractEquilibrium(point).GetProbVector(); - for (size_t i = 1; i <= prob_vector.size(); ++i) { - std::cout << prob_vector[i] << " "; - } - std::cout << std::endl; - }, + x, direction, tracking_index, termination_condition, NullCallbackFunction, criterion_function); + if (!result.status && system.ExtractEquilibrium(x).GetMaxRegret() > tol) { + return {}; + } + const PolishResult polishing_result = PolishPoint( + [&system](const Vector &point, Vector &lhs) { system.GetValue(point, lhs); }, + [&system](const Vector &point, Matrix &jac) { + system.GetJacobian(point, jac); + }, + x, t_target, 1, polishing_termination_condition, 100, NullCallbackFunction); + + if (!polishing_result.status) { + return {}; + } equilibria.push_back(system.ExtractEquilibrium(x)); + return equilibria; } } // namespace Gambit diff --git a/src/solvers/logit/path.cc b/src/solvers/logit/path.cc index d88156166..25291b525 100644 --- a/src/solvers/logit/path.cc +++ b/src/solvers/logit/path.cc @@ -133,14 +133,15 @@ PathTracer::TracePath(std::function &, Vector CriterionFunctionType p_criterion, CriterionBracketFunctionType p_criterionBracket) const { - const double c_tol = 1.0e-4; // tolerance for corrector iteration - const double c_maxDist = 0.4; // maximal distance to curve - const double c_maxContr = 0.6; // maximal contraction rate in corrector - const double c_eta = 0.1; // perturbation to avoid cancellation - // in calculating contraction rate - double h = m_hStart; // initial stepsize - const double c_hmin = 1.0e-8; // minimal stepsize - const int c_maxIter = 100; // maximum iterations in corrector + const double c_tol = 1.0e-4; // tolerance for corrector iteration + const double c_maxDist = 0.4; // maximal distance to curve + const double c_maxContr = 0.6; // maximal contraction rate in corrector + const double c_eta = 0.1; // perturbation to avoid cancellation + // in calculating contraction rate + double h = m_hStart; // initial stepsize + const double c_hmin = 1.0e-8; // minimal stepsize + const int c_maxIter = 100; // maximum iterations in corrector + const double c_newtonTol = 1.0e-8; // tolerance for Newton convergence bool newton = false; // using Newton steplength (for zero-finding) const double c_pert = 0.0000001; // The size of perturbation to apply to avoid bifurcation traps @@ -148,6 +149,9 @@ PathTracer::TracePath(std::function &, Vector double pert_countdown = 0.0; // How much longer (in arclength) to apply perturbation const double c_orientTol = 1.0e-8; // tolerance for detecting change in orientation + const double b_tol = 1.0e-10; // Tolerance for perturbing the b matrix in case of singularity + const double b_pert = 1.0e-8; // Perturbation of the b matrix in case of singularity + Vector u(x.size()); // t is current tangent at x; newT is tangent at u, which is the next point. Vector t(x.size()), newT(x.size()); @@ -159,27 +163,39 @@ PathTracer::TracePath(std::function &, Vector QRDecomp(b, q); q.GetRow(q.NumRows(), t); p_callback(x); + int steps = 0; + + auto stepsizeBelowMinimum = [&]() -> TracePathResult { + if (newton && std::abs(p_criterion(x, t)) < c_newtonTol) { + return {x, true, + "Path following terminated successfully at point satisfying criterion function.", + steps}; + } + return {x, false, "Stepsize fell below minimum threshold.", steps}; + }; + bool first_step = true; double omega = (p_direction == TraceDirection::Positive) ? 1.0 : -1.0; if (p_trackingIndex > x.size() || p_trackingIndex < 1) { - return {x, false, "Tracking index exceeds dimension of point vector."}; + return {x, false, "Tracking index exceeds dimension of point vector.", steps}; } while (!p_terminate(x)) { bool accept = true; if (std::abs(h) <= c_hmin) { - return {x, false, "Stepsize fell below minimum threshold."}; + return stepsizeBelowMinimum(); } if (first_step) { if (std::abs(t[p_trackingIndex]) <= c_orientTol) { - return {x, false, "Initial tangent vector is orthogonal to path-following direction."}; + return {x, false, "Initial tangent vector is orthogonal to path-following direction.", + steps}; } // Ensure that the tangent is oriented in the same direction as // the path-following direction. - else if (t[p_trackingIndex] < -c_orientTol) { + if (t[p_trackingIndex] < -c_orientTol) { omega *= -1.0; } first_step = false; @@ -194,6 +210,17 @@ PathTracer::TracePath(std::function &, Vector p_jacobian(u, b); QRDecomp(b, q); + for (size_t i = 1; i < b.NumRows(); i++) { + if (std::abs(b(i, i)) < b_tol) { + if (b(i, i) < 0) { + b(i, i) -= b_pert; + } + else { + b(i, i) += b_pert; + } + } + } + int iter = 1; double disto = 0.0; while (true) { @@ -225,7 +252,7 @@ PathTracer::TracePath(std::function &, Vector disto = dist; iter++; if (iter > c_maxIter) { - return {x, false, "Maximum iterations exceeded."}; + return {x, false, "Maximum iterations exceeded.", steps}; } } @@ -239,7 +266,7 @@ PathTracer::TracePath(std::function &, Vector // is oriented in the same direction as we were originally following if (pert_countdown == 0.0) { pert = c_pert; - pert_countdown = abs(2 * h); + pert_countdown = std::abs(2 * h); } accept = false; } @@ -247,7 +274,7 @@ PathTracer::TracePath(std::function &, Vector if (!accept) { h /= m_maxDecel; // PC not accepted; change stepsize and retry if (std::abs(h) <= c_hmin) { - return {x, false, "Stepsize fell below minimum threshold."}; + return stepsizeBelowMinimum(); } continue; } @@ -278,6 +305,7 @@ PathTracer::TracePath(std::function &, Vector x = u; t = newT; p_callback(x); + steps++; if (pert_countdown > 0.0) { // If we are currently perturbing in the neighborhood of a bifurcation, check to see @@ -289,7 +317,85 @@ PathTracer::TracePath(std::function &, Vector } } } - return {x, true, "Path tracing terminated successfully."}; + return {x, true, "Path tracing terminated successfully.", steps}; } +PolishResult PolishPoint(std::function &, Vector &)> p_function, + std::function &, Matrix &)> p_jacobian, + Vector &x, double fixed_value, size_t fixed_index, + TerminationFunctionType p_terminate, int max_iter, + CallbackFunctionType p_callback) +{ + x[fixed_index] = fixed_value; + + const Vector original_x = x; + + const size_t N = x.size() - 1; + Vector y(N); // Equations results + Matrix jac_full(N + 1, N); // Full Jacobian matrix (N+1 unknowns, N equations) + Matrix jac_square(N, N); // Jacobian matrix with fixed_index row removed + Matrix Q(N, N); // Orthogonal matrix from QR decomposition + Vector x_reduced(N); // Reduced x vector with fixed_index removed + + double const eq_tol = 1e-2; + + int steps = 0; + double dist = 0.0; + + while (!p_terminate(x)) { + if (steps >= max_iter) { + return {x, false, "Polishing exceeded maximum iterations.", steps}; + } + + p_function(x, y); + p_jacobian(x, jac_full); + + size_t row_index = 1; + for (size_t i = 1; i <= N + 1; ++i) { // Newton step expects the transposed Jacobian + if (i != fixed_index) { + for (size_t j = 1; j <= N; ++j) { + jac_square(row_index, j) = jac_full(i, j); + } + row_index++; + } + } + + // Reduced x vector removing fixed_index + size_t temp_idx = 1; + for (size_t i = 1; i <= N + 1; ++i) { + if (i != fixed_index) { + x_reduced[temp_idx++] = x[i]; + } + } + + QRDecomp(jac_square, Q); + + // Solve jac_square * x_reduced = -y + NewtonStep(Q, jac_square, x_reduced, y, dist); + + // Update x, keeping fixed_index constant + temp_idx = 1; + for (size_t i = 1; i <= N + 1; ++i) { + if (i != fixed_index) { + x[i] = x_reduced[temp_idx++]; + } + } + + steps++; + + if (p_callback) { + p_callback(x); + } + } + + // Checking that the profile satisfies the system of equations + for (size_t i = 1; i <= N; ++i) { + if (std::abs(y[i]) > eq_tol) { + x = original_x; + return {x, false, "Polishing converged to an invalid mathematical state. Reverted.", steps}; + } + } + + return {x, true, "Polishing terminated successfully.", steps}; +} } // end namespace Gambit diff --git a/src/solvers/logit/path.h b/src/solvers/logit/path.h index 8193c5764..dd6b2375f 100644 --- a/src/solvers/logit/path.h +++ b/src/solvers/logit/path.h @@ -62,6 +62,14 @@ struct TracePathResult { Vector final_point; bool status; // true if path tracing terminated successfully, false if it terminated due to error std::string message; // error message if status is false + int steps; // Step at which the tracing terminated +}; + +struct PolishResult { + Vector final_point; + bool status; // true if polishing terminated successfully, false if it terminated due to error + std::string message; // error message if status is false + int steps; // Step at which the polishing terminated }; // @@ -94,6 +102,15 @@ class PathTracer { double m_maxDecel{1.1}, m_hStart{0.03}; }; +// This function reduces the regret of a point that is close to an equilibrium that has been found +// by the path-following algorithm. Fixing the value of a component of the point, it uses a +// Newton-type method to find a nearby point with lower regret. +PolishResult PolishPoint(std::function &, Vector &)> p_function, + std::function &, Matrix &)> p_jacobian, + Vector &p_x, double fixed_value, size_t fixed_index, + TerminationFunctionType p_terminate, int max_iter = 100, + CallbackFunctionType p_callback = NullCallbackFunction); + } // end namespace Gambit #endif // PATH_H diff --git a/tests/test_hp.py b/tests/test_hp.py index 4b6af3cca..3aca424c5 100644 --- a/tests/test_hp.py +++ b/tests/test_hp.py @@ -1,6 +1,7 @@ """Test of calls to the Herings & Peeters (2001) homotopy solver.""" import dataclasses +import functools import typing import numpy as np @@ -8,7 +9,9 @@ import pygambit as gbt -TOL = 1e-6 +from . import games + +TOL = 1e-8 def d(*probs) -> tuple: @@ -26,6 +29,26 @@ class HPSolverTestCase: prob_tol: float = TOL +def create_prior_from_catalog(game_path: str) -> gbt.MixedStrategyProfile: + """Loads a game from the catalog and returns a prior that is uniform over all strategies.""" + game = gbt.catalog.load(game_path) + return game.mixed_strategy_profile() + + +def load_game_from_file(file_name: str) -> gbt.MixedStrategyProfile: + """Loads a game from a file in tests/test_games""" + return games.read_from_file(file_name) + + +def check_equilibrium(result, subtests) -> None: + """Checks that the result of the HP solver is a valid Nash equilibrium.""" + with subtests.test("number of equilibria found"): + assert len(result.equilibria) == 1 + + with subtests.test("valid Nash equilibrium (max_regret <= TOL)"): + assert result.equilibria[0].max_regret() <= TOL + + def create_hs_base_game() -> gbt.Game: """Creates the base 2x2 game used in all examples from Harsanyi & Selten (1988) Section 4.11 and also featured in Herings & Peeters (2001). @@ -78,6 +101,72 @@ def create_t0_degenerate_example() -> gbt.MixedStrategyProfileDouble: return prior +def create_test_hp_2x2x2_nfg_eq1() -> gbt.MixedStrategyProfileDouble: + """Creates a prior for the 2x2x2.nfg game that is exactly the first equilibrium.""" + game = load_game_from_file("../../contrib/games/2x2x2.nfg") + return game.mixed_strategy_profile(data=[(1.0, 0.0), (1.0, 0.0), (1.0, 0.0)]) + + +def create_test_hp_2x2x2_nfg_eq1_perturbed() -> gbt.MixedStrategyProfileDouble: + """Creates a prior for the 2x2x2.nfg game that is the first equilibrium perturbed.""" + game = load_game_from_file("../../contrib/games/2x2x2.nfg") + return game.mixed_strategy_profile(data=[(0.9, 0.1), (0.9, 0.1), (0.9, 0.1)]) + + +def create_test_hp_2x2x2_nfg_eq2() -> gbt.MixedStrategyProfileDouble: + """Creates a prior for the 2x2x2.nfg game that is exactly the second pure equilibrium.""" + game = load_game_from_file("../../contrib/games/2x2x2.nfg") + return game.mixed_strategy_profile(data=[(1.0, 0.0), (0.0, 1.0), (0.0, 1.0)]) + + +def create_test_hp_2x2x2_nfg_eq2_perturbed() -> gbt.MixedStrategyProfileDouble: + """Creates a prior for the 2x2x2.nfg game that is the second equilibrium perturbed.""" + game = load_game_from_file("../../contrib/games/2x2x2.nfg") + return game.mixed_strategy_profile(data=[(0.9, 0.1), (0.1, 0.9), (0.1, 0.9)]) + + +def create_test_hp_2x2x2_nfg_eq3() -> gbt.MixedStrategyProfileDouble: + """Creates a prior for the 2x2x2.nfg game that is exactly the third pure equilibrium.""" + game = load_game_from_file("../../contrib/games/2x2x2.nfg") + return game.mixed_strategy_profile(data=[(0.0, 1.0), (1.0, 0.0), (0.0, 1.0)]) + + +def create_test_hp_2x2x2_nfg_eq3_perturbed() -> gbt.MixedStrategyProfileDouble: + """Creates a prior for the 2x2x2.nfg game that is the third equilibrium perturbed.""" + game = load_game_from_file("../../contrib/games/2x2x2.nfg") + return game.mixed_strategy_profile(data=[(0.1, 0.9), (0.9, 0.1), (0.1, 0.9)]) + + +def create_test_hp_2x2x2_nfg_eq4() -> gbt.MixedStrategyProfileDouble: + """Creates a prior for the 2x2x2.nfg game that is exactly the fourth pure equilibrium.""" + game = load_game_from_file("../../contrib/games/2x2x2.nfg") + return game.mixed_strategy_profile(data=[(0.0, 1.0), (0.0, 1.0), (1.0, 0.0)]) + + +def create_test_hp_2x2x2_nfg_eq4_perturbed() -> gbt.MixedStrategyProfileDouble: + """Creates a prior for the 2x2x2.nfg game that is the fourth equilibrium perturbed.""" + game = load_game_from_file("../../contrib/games/2x2x2.nfg") + return game.mixed_strategy_profile(data=[(0.1, 0.9), (0.1, 0.9), (0.9, 0.1)]) + + +def create_test_hp_2x2x2_nfg_eq5_perturbed() -> gbt.MixedStrategyProfileDouble: + """Creates a perturbed prior near the fifth equilibrium (partially mixed).""" + game = load_game_from_file("../../contrib/games/2x2x2.nfg") + return game.mixed_strategy_profile(data=[(0.51, 0.49), (0.49, 0.51), (0.9, 0.1)]) + + +def create_test_hp_2x2x2_nfg_eq8_perturbed() -> gbt.MixedStrategyProfileDouble: + """Creates a perturbed prior near the eighth equilibrium (totally mixed).""" + game = load_game_from_file("../../contrib/games/2x2x2.nfg") + return game.mixed_strategy_profile(data=[(0.51, 0.49), (0.41, 0.59), (0.26, 0.74)]) + + +def create_test_hp_2x2x2_nfg_eq9_perturbed() -> gbt.MixedStrategyProfileDouble: + """Creates a perturbed prior near the ninth equilibrium (totally mixed).""" + game = load_game_from_file("../../contrib/games/2x2x2.nfg") + return game.mixed_strategy_profile(data=[(0.41, 0.59), (0.51, 0.49), (0.34, 0.66)]) + + HP_CASES = [ pytest.param( HPSolverTestCase( @@ -93,6 +182,94 @@ def create_t0_degenerate_example() -> gbt.MixedStrategyProfileDouble: ), id="test_hp_hs_example_1", ), + pytest.param( + HPSolverTestCase( + factory=functools.partial(create_prior_from_catalog, "books/myerson1991/fig2_1"), + expected=[d(1.0 / 3.0, 2.0 / 3.0, 0.0, 0.0), d(2.0 / 3.0, 1.0 / 3.0)], + ), + id="test_hp_myerson1991_fig2_1", + ), + pytest.param( + HPSolverTestCase( + factory=functools.partial(create_prior_from_catalog, "books/vonstengel2022/fig10.1"), + expected=[d(0.0, 0.5, 0, 0.5), d(1.0 / 4.0, 3.0 / 4.0)], + ), + id="test_hp_vonstengel2022_fig10.1", + ), + pytest.param( + HPSolverTestCase( + factory=functools.partial(create_prior_from_catalog, "journals/ijgt/nau2004/sec4"), + expected=[ + d(0.6192325794725538, 0.38076742052744617), + d(0.4798042226776052, 0.5201957773223949), + d(0.37882533606563146, 0.6211746639343685) + ], + ), + id="test_hp_nau2004_sec4", + ), + pytest.param( + HPSolverTestCase( + factory=functools.partial(create_prior_from_catalog, "journals/other/reiley2008/fig1"), + expected=[d(1.0 / 3.0, 2.0 / 3.0, 0.0, 0.0), d(2.0 / 3.0, 1.0 / 3.0)], + ), + id="test_hp_reiley2008_fig1", + ), + pytest.param( + HPSolverTestCase( + factory=create_test_hp_2x2x2_nfg_eq1, + expected=[d(1.0, 0.0), d(1.0, 0.0), d(1.0, 0.0)], + ), + id="test_hp_2x2x2_nfg_eq1", + ), + pytest.param( + HPSolverTestCase( + factory=create_test_hp_2x2x2_nfg_eq1_perturbed, + expected=[d(1.0, 0.0), d(1.0, 0.0), d(1.0, 0.0)], + ), + id="test_hp_2x2x2_nfg_eq1_perturbed", + ), + pytest.param( + HPSolverTestCase( + factory=create_test_hp_2x2x2_nfg_eq2, + expected=[d(1.0, 0.0), d(0.0, 1.0), d(0.0, 1.0)], + ), + id="test_hp_2x2x2_nfg_eq2", + ), + pytest.param( + HPSolverTestCase( + factory=create_test_hp_2x2x2_nfg_eq2_perturbed, + expected=[d(1.0, 0.0), d(0.0, 1.0), d(0.0, 1.0)], + ), + id="test_hp_2x2x2_nfg_eq2_perturbed", + ), + pytest.param( + HPSolverTestCase( + factory=create_test_hp_2x2x2_nfg_eq3, + expected=[d(0.0, 1.0), d(1.0, 0.0), d(0.0, 1.0)], + ), + id="test_hp_2x2x2_nfg_eq3", + ), + pytest.param( + HPSolverTestCase( + factory=create_test_hp_2x2x2_nfg_eq3_perturbed, + expected=[d(0.0, 1.0), d(1.0, 0.0), d(0.0, 1.0)], + ), + id="test_hp_2x2x2_nfg_eq3_perturbed", + ), + pytest.param( + HPSolverTestCase( + factory=create_test_hp_2x2x2_nfg_eq4, + expected=[d(0.0, 1.0), d(0.0, 1.0), d(1.0, 0.0)], + ), + id="test_hp_2x2x2_nfg_eq4", + ), + pytest.param( + HPSolverTestCase( + factory=create_test_hp_2x2x2_nfg_eq4_perturbed, + expected=[d(0.0, 1.0), d(0.0, 1.0), d(1.0, 0.0)], + ), + id="test_hp_2x2x2_nfg_eq4_perturbed", + ), ] @@ -130,3 +307,169 @@ def test_hp_degenerate_t0_prior_raises_error() -> None: with pytest.raises(RuntimeError, match="Multiple best responses found for player 1. " "Only one best response is allowed."): gbt.nash.hp_solve(prior=prior) + + +@pytest.mark.nash +def test_hp_degenerate_t0_prior_selten1975_fig2() -> None: + game = gbt.catalog.load("journals/ijgt/selten1975/fig2") + prior = game.mixed_strategy_profile() + with pytest.raises(RuntimeError, match="Multiple best responses found for player 1. " + "Only one best response is allowed."): + gbt.nash.hp_solve(prior=prior) + + +@pytest.mark.nash +def test_hp_2x2x2_nfg_eq5_perturbed(subtests) -> None: + """Test convergence from a perturbed prior near eq 5 using max regret.""" + prior = create_test_hp_2x2x2_nfg_eq5_perturbed() + result = gbt.nash.hp_solve(prior=prior) + check_equilibrium(result, subtests) + + +@pytest.mark.nash +def test_hp_2x2x2_nfg_eq8_perturbed(subtests) -> None: + """Test convergence from a perturbed prior near eq 8 using max regret.""" + prior = create_test_hp_2x2x2_nfg_eq8_perturbed() + result = gbt.nash.hp_solve(prior=prior) + check_equilibrium(result, subtests) + + +@pytest.mark.nash +def test_hp_2x2x2_nfg_eq9_perturbed(subtests) -> None: + """Test convergence from a perturbed prior near eq 9 using max regret.""" + prior = create_test_hp_2x2x2_nfg_eq9_perturbed() + result = gbt.nash.hp_solve(prior=prior) + check_equilibrium(result, subtests) + + +CATALOG_GAMES_TO_TEST = [ + pytest.param( + "books/myerson1991/fig2_1", + id="catalog games that converge to a NE - books/myerson1991/fig2_1" + ), + pytest.param( + "books/shohamleytonbrown2008/fig5_10", + id="catalog games that converge to a NE - books/shohamleytonbrown2008/fig5_10" + ), + pytest.param( + "books/shohamleytonbrown2008/fig5_11", + id="catalog games that converge to a NE - books/shohamleytonbrown2008/fig5_11" + ), + pytest.param( + "books/shohamleytonbrown2008/fig5_15", + id="catalog games that converge to a NE - books/shohamleytonbrown2008/fig5_15" + ), + pytest.param( + "books/shohamleytonbrown2008/fig6_2", + id="catalog games that converge to a NE - books/shohamleytonbrown2008/fig6_2" + ), + pytest.param( + "books/shohamleytonbrown2008/fig6_8", + id="catalog games that converge to a NE - books/shohamleytonbrown2008/fig6_8" + ), + pytest.param( + "books/vonstengel2022/fig10.1", + id="catalog games that converge to a NE - books/vonstengel2022/fig10.1" + ), + pytest.param( + "books/watson2013/fig29_1", + id="catalog games that converge to a NE - books/watson2013/fig29_1" + ), + pytest.param( + "conf/itcs/jakobsen2016/fig1c", + id="catalog games that converge to a NE - conf/itcs/jakobsen2016/fig1c" + ), + pytest.param( + "conf/itcs/jakobsen2016/fig3", + id="catalog games that converge to a NE - conf/itcs/jakobsen2016/fig3" + ), + pytest.param( + "journals/geb/bagwell1995", + id="catalog games that converge to a NE - journals/geb/bagwell1995" + ), + pytest.param( + "journals/ijgt/nau2004/sec3", + id="catalog games that converge to a NE - journals/ijgt/nau2004/sec3" + ), + pytest.param( + "journals/ijgt/nau2004/sec4", + id="catalog games that converge to a NE - journals/ijgt/nau2004/sec4" + ), + pytest.param( + "journals/ijgt/nau2004/sec5", + id="catalog games that converge to a NE - journals/ijgt/nau2004/sec5" + ), + pytest.param( + "journals/other/reiley2008/fig1", + id="catalog games that converge to a NE - journals/other/reiley2008/fig1" + ), +] + + +@pytest.mark.nash +@pytest.mark.parametrize("game_id", CATALOG_GAMES_TO_TEST) +def test_hp_catalog_games_max_regret(game_id, subtests) -> None: + """Some catalog games that converge to a Nash equilibrium with a 1e-8 tolerance.""" + game = gbt.catalog.load(game_id) + prior = game.mixed_strategy_profile() + result = gbt.nash.hp_solve(prior=prior) + check_equilibrium(result, subtests) + + +LOCAL_GAMES_TO_TEST = [ + pytest.param( + "8x8.nfg", + id="contrib games that converge to a NE - 8x8.nfg" + ), + pytest.param( + "5x4x3.nfg", + id="contrib games that converge to a NE - 5x4x3.nfg" + ), + pytest.param( + "3x3x3.nfg", + id="contrib games that converge to a NE - 3x3x3.nfg" + ), + pytest.param( + "2x2x2x2x2.nfg", + id="contrib games that converge to a NE - 2x2x2x2x2.nfg" + ), + pytest.param( + "8x2x2.nfg", + id="contrib games that converge to a NE - 8x2x2.nfg" + ), + pytest.param( + "pd.nfg", + id="contrib games that converge to a NE - pd.nfg" + ), + pytest.param( + "2x2x2x2.nfg", + id="contrib games that converge to a NE - 2x2x2x2.nfg" + ), + pytest.param( + "vd.nfg", + id="contrib games that converge to a NE (convex set of equilibria) - vd.nfg" + ), + pytest.param( + "wink3.nfg", + id="contrib games that converge to a NE - wink3.nfg" + ), + pytest.param( + "g1.nfg", + id="contrib games that converge to a NE - g1.nfg" + ), + pytest.param( + "g2.nfg", + id="contrib games that converge to a NE - g2.nfg (initial tangent goes negative)" + ), + +] + + +@pytest.mark.nash +@pytest.mark.parametrize("filename", LOCAL_GAMES_TO_TEST) +def test_hp_large_local_games_max_regret(filename, subtests) -> None: + """Some contrib/games that converge to a Nash equilibrium with a 1e-8 tolerance.""" + game = load_game_from_file(f"../../contrib/games/{filename}") + prior = game.mixed_strategy_profile() + result = gbt.nash.hp_solve(prior=prior) + check_equilibrium(result, subtests)