Skip to content

Commit ed06f43

Browse files
Added tests for enumpoly, LP and LCP (#705)
--------- Co-authored-by: Rahul Savani <rahul.savani@gmail.com>
1 parent 1cb6a02 commit ed06f43

3 files changed

Lines changed: 638 additions & 181 deletions

File tree

tests/games.py

Lines changed: 260 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def read_from_file(fn: str) -> gbt.Game:
1919

2020

2121
def create_efg_corresponding_to_bimatrix_game(
22-
A: np.ndarray, B: np.ndarray, title: str
22+
A: np.ndarray, B: np.ndarray, title: str
2323
) -> gbt.Game:
2424
"""
2525
There is no direct pygambit method to create an EFG from a stategic-form game.
@@ -123,6 +123,238 @@ def create_2x2_zero_sum_efg(missing_term_outcome: bool = False) -> gbt.Game:
123123
return g
124124

125125

126+
def create_perfect_info_with_chance_efg() -> gbt.Game:
127+
# Tests case in which sequence profile probabilities don't sum to 1
128+
g = gbt.Game.new_tree(players=["1", "2"], title="2 player perfect info with chance")
129+
g.append_move(g.root, "1", ["a", "b"])
130+
g.append_move(g.root.children[0], g.players.chance, ["L", "R"])
131+
g.append_move(g.root.children[0].children[0], "2", ["A", "B"])
132+
g.append_move(g.root.children[0].children[1], "2", ["C", "D"])
133+
g.set_outcome(
134+
g.root.children[0].children[0].children[0], g.add_outcome([-2, 2], label="aLA")
135+
)
136+
g.set_outcome(
137+
g.root.children[0].children[0].children[1], g.add_outcome([-2, 2], label="aLB")
138+
)
139+
g.set_outcome(
140+
g.root.children[0].children[1].children[0], g.add_outcome([-2, 2], label="aRC")
141+
)
142+
g.set_outcome(
143+
g.root.children[0].children[1].children[1], g.add_outcome([-2, 2], label="aRD")
144+
)
145+
g.set_outcome(g.root.children[1], g.add_outcome([-1, 1], label="b"))
146+
return g
147+
148+
149+
def create_three_action_internal_outcomes_efg(nonterm_outcomes: bool = False) -> gbt.Game:
150+
"""
151+
with nonterm_outcomes there are nonterminal outcomes, and missing outcomes at some leaves
152+
"""
153+
g = gbt.Game.new_tree(players=["1", "2"], title="")
154+
g.append_move(g.root, g.players.chance, ["H", "L"])
155+
for i in range(2):
156+
g.append_move(g.root.children[i], "1", ["A", "B", "C"])
157+
for i in range(3):
158+
g.append_move(g.root.children[0].children[i], "2", ["X", "Y"])
159+
g.append_infoset(g.root.children[1].children[i], g.root.children[0].children[i].infoset)
160+
o_1 = g.add_outcome([1, -1], label="1")
161+
o_m1 = g.add_outcome([-1, 1], label="-1")
162+
o_2 = g.add_outcome([2, -2], label="2")
163+
o_m2 = g.add_outcome([-2, 2], label="-2")
164+
o_z = g.add_outcome([0, 0], label="0")
165+
if nonterm_outcomes:
166+
g.set_outcome(g.root.children[0].children[0], o_1)
167+
g.set_outcome(g.root.children[1].children[2], o_m1)
168+
g.set_outcome(g.root.children[0].children[0].children[1], o_m2)
169+
g.set_outcome(g.root.children[0].children[1].children[0], o_m1)
170+
g.set_outcome(g.root.children[0].children[1].children[1], o_1)
171+
g.set_outcome(g.root.children[0].children[2].children[0], o_1)
172+
g.set_outcome(g.root.children[1].children[0].children[1], o_1)
173+
g.set_outcome(g.root.children[1].children[1].children[0], o_1)
174+
g.set_outcome(g.root.children[1].children[1].children[1], o_m1)
175+
g.set_outcome(g.root.children[1].children[2].children[1], o_2)
176+
else:
177+
g.set_outcome(g.root.children[0].children[0].children[0], o_1)
178+
g.set_outcome(g.root.children[0].children[0].children[1], o_m1)
179+
g.set_outcome(g.root.children[0].children[1].children[0], o_m1)
180+
g.set_outcome(g.root.children[0].children[1].children[1], o_1)
181+
g.set_outcome(g.root.children[0].children[2].children[0], o_1)
182+
g.set_outcome(g.root.children[0].children[2].children[1], o_z)
183+
184+
g.set_outcome(g.root.children[1].children[0].children[0], o_z)
185+
g.set_outcome(g.root.children[1].children[0].children[1], o_1)
186+
g.set_outcome(g.root.children[1].children[1].children[0], o_1)
187+
g.set_outcome(g.root.children[1].children[1].children[1], o_m1)
188+
g.set_outcome(g.root.children[1].children[2].children[0], o_m1)
189+
g.set_outcome(g.root.children[1].children[2].children[1], o_1)
190+
return g
191+
192+
193+
def create_entry_accomodation_efg(nonterm_outcomes: bool = False) -> gbt.Game:
194+
g = gbt.Game.new_tree(players=["1", "2"],
195+
title="Entry-accomodation game")
196+
g.append_move(g.root, "1", ["S", "T"])
197+
g.append_move(g.root.children[0], "2", ["E", "O"])
198+
g.append_infoset(g.root.children[1], g.root.children[0].infoset)
199+
g.append_move(g.root.children[0].children[0], "1", ["A", "F"])
200+
g.append_move(g.root.children[1].children[0], "1", ["A", "F"])
201+
if nonterm_outcomes:
202+
g.set_outcome(g.root.children[0], g.add_outcome([3, 2]))
203+
g.set_outcome(g.root.children[0].children[0].children[1], g.add_outcome([-3, -1]))
204+
g.set_outcome(g.root.children[0].children[1], g.add_outcome([-2, 1]))
205+
else:
206+
g.set_outcome(g.root.children[0].children[0].children[0], g.add_outcome([3, 2]))
207+
g.set_outcome(g.root.children[0].children[0].children[1], g.add_outcome([0, 1]))
208+
g.set_outcome(g.root.children[0].children[1], g.add_outcome([1, 3]))
209+
g.set_outcome(g.root.children[1].children[0].children[0], g.add_outcome([2, 3]))
210+
g.set_outcome(g.root.children[1].children[0].children[1], g.add_outcome([1, 0]))
211+
g.set_outcome(g.root.children[1].children[1], g.add_outcome([3, 1]))
212+
return g
213+
214+
215+
def create_non_zero_sum_lacking_outcome_efg(missing_term_outcome: bool = False) -> gbt.Game:
216+
g = gbt.Game.new_tree(players=["1", "2"], title="Non constant-sum game lacking outcome")
217+
g.append_move(g.root, g.players.chance, ["H", "T"])
218+
g.set_chance_probs(g.root.infoset, ["1/2", "1/2"])
219+
g.append_move(g.root.children[0], "1", ["A", "B"])
220+
g.append_infoset(g.root.children[1], g.root.children[0].infoset)
221+
g.append_move(g.root.children[0].children[0], "2", ["X", "Y"])
222+
g.append_infoset(g.root.children[0].children[1], g.root.children[0].children[0].infoset)
223+
g.append_infoset(g.root.children[1].children[0], g.root.children[0].children[0].infoset)
224+
g.append_infoset(g.root.children[1].children[1], g.root.children[0].children[0].infoset)
225+
g.set_outcome(g.root.children[0].children[0].children[0], g.add_outcome([2, 1]))
226+
g.set_outcome(g.root.children[0].children[0].children[1], g.add_outcome([-1, 2]))
227+
g.set_outcome(g.root.children[0].children[1].children[0], g.add_outcome([1, -1]))
228+
if not missing_term_outcome:
229+
g.set_outcome(g.root.children[0].children[1].children[1], g.add_outcome([0, 0]))
230+
g.set_outcome(g.root.children[1].children[0].children[0], g.add_outcome([1, 0]))
231+
g.set_outcome(g.root.children[1].children[0].children[1], g.add_outcome([0, 1]))
232+
g.set_outcome(g.root.children[1].children[1].children[0], g.add_outcome([-1, 1]))
233+
g.set_outcome(g.root.children[1].children[1].children[1], g.add_outcome([2, -1]))
234+
return g
235+
236+
237+
def create_chance_in_middle_efg(nonterm_outcomes: bool = False) -> gbt.Game:
238+
g = gbt.Game.new_tree(players=["1", "2"],
239+
title="Chance in middle game")
240+
g.append_move(g.root, "1", ["A", "B"])
241+
g.append_move(g.root.children[0], g.players.chance, ["H", "L"])
242+
g.set_chance_probs(g.root.children[0].infoset, ["1/5", "4/5"])
243+
g.append_move(g.root.children[1], g.players.chance, ["H", "L"])
244+
g.set_chance_probs(g.root.children[1].infoset, ["7/10", "3/10"])
245+
for i in range(2):
246+
g.append_move(g.root.children[0].children[i], "2", ["X", "Y"])
247+
ist = g.root.children[0].children[i].infoset
248+
g.append_infoset(g.root.children[1].children[i], ist)
249+
for i in range(2):
250+
for j in range(2):
251+
g.append_move(g.root.children[i].children[0].children[j], "1", ["C", "D"])
252+
ist = g.root.children[i].children[0].children[j].infoset
253+
g.append_infoset(g.root.children[i].children[1].children[j], ist)
254+
o_1 = g.add_outcome([1, -1], label="1")
255+
o_m1 = g.add_outcome([-1, 1], label="-1")
256+
o_m2 = g.add_outcome([-2, 2], label="-2")
257+
o_h = g.add_outcome(["1/2", "-1/2"], label="0.5")
258+
o_mh = g.add_outcome(["-1/2", "1/2"], label="-0.5")
259+
o_z = g.add_outcome([0, 0], label="0")
260+
o_m3o2 = g.add_outcome(["-3/2", "3/2"], label="-1.5")
261+
if nonterm_outcomes:
262+
g.set_outcome(g.root.children[0].children[0], g.add_outcome([-1, 1], label="a"))
263+
g.set_outcome(g.root.children[0].children[0].children[0].children[0], o_1)
264+
g.set_outcome(g.root.children[0].children[0].children[0].children[1], o_m1)
265+
g.set_outcome(g.root.children[0].children[0].children[1].children[0], o_h)
266+
g.set_outcome(g.root.children[0].children[0].children[1].children[1], o_mh)
267+
else:
268+
g.set_outcome(g.root.children[0].children[0].children[0].children[0], o_z)
269+
g.set_outcome(g.root.children[0].children[0].children[0].children[1], o_m2)
270+
g.set_outcome(g.root.children[0].children[0].children[1].children[0], o_mh)
271+
g.set_outcome(g.root.children[0].children[0].children[1].children[1], o_m3o2)
272+
g.set_outcome(g.root.children[0].children[1].children[0].children[0], o_h)
273+
g.set_outcome(g.root.children[0].children[1].children[0].children[1], o_mh)
274+
g.set_outcome(g.root.children[0].children[1].children[1].children[0], o_1)
275+
g.set_outcome(g.root.children[0].children[1].children[1].children[1], o_m1)
276+
g.set_outcome(g.root.children[1].children[0].children[0].children[0], o_h)
277+
g.set_outcome(g.root.children[1].children[0].children[0].children[1], o_mh)
278+
g.set_outcome(g.root.children[1].children[0].children[1].children[0], o_1)
279+
g.set_outcome(g.root.children[1].children[0].children[1].children[1], o_m1)
280+
g.set_outcome(g.root.children[1].children[1].children[0].children[0], o_1)
281+
g.set_outcome(g.root.children[1].children[1].children[0].children[1], o_m1)
282+
g.set_outcome(g.root.children[1].children[1].children[1].children[0], o_h)
283+
g.set_outcome(g.root.children[1].children[1].children[1].children[1], o_mh)
284+
return g
285+
286+
287+
def create_large_payoff_game_efg() -> gbt.Game:
288+
g = gbt.Game.new_tree(players=["1", "2"], title="Large payoff game")
289+
g.append_move(g.root, g.players.chance, ["L", "R"])
290+
for i in range(2):
291+
g.append_move(g.root.children[i], "1", ["A", "B"])
292+
for i in range(2):
293+
g.append_move(g.root.children[0].children[i], "2", ["X", "Y"])
294+
g.append_infoset(g.root.children[1].children[i], g.root.children[0].children[i].infoset)
295+
o_large = g.add_outcome([10000000000000000000, -10000000000000000000], label="large payoff")
296+
o_1 = g.add_outcome([1, -1], label="1")
297+
o_m1 = g.add_outcome([-1, 1], label="-1")
298+
o_zero = g.add_outcome([0, 0], label="0")
299+
g.set_outcome(g.root.children[0].children[0].children[0], o_large)
300+
g.set_outcome(g.root.children[0].children[0].children[1], o_1)
301+
g.set_outcome(g.root.children[0].children[1].children[0], o_m1)
302+
g.set_outcome(g.root.children[0].children[1].children[1], o_zero)
303+
g.set_outcome(g.root.children[1].children[0].children[0], o_m1)
304+
g.set_outcome(g.root.children[1].children[0].children[1], o_1)
305+
g.set_outcome(g.root.children[1].children[1].children[0], o_zero)
306+
g.set_outcome(g.root.children[1].children[1].children[1], o_large)
307+
return g
308+
309+
310+
def create_3_player_with_internal_outcomes_efg(nonterm_outcomes: bool = False) -> gbt.Game:
311+
g = gbt.Game.new_tree(players=["1", "2", "3"], title="3 player game")
312+
g.append_move(g.root, g.players.chance, ["H", "T"])
313+
g.set_chance_probs(g.root.infoset, ["1/2", "1/2"])
314+
g.append_move(g.root.children[0], "1", ["a", "b"])
315+
g.append_move(g.root.children[1], "1", ["c", "d"])
316+
g.append_move(g.root.children[0].children[0], "2", ["A", "B"])
317+
g.append_infoset(g.root.children[1].children[0], g.root.children[0].children[0].infoset)
318+
g.append_move(g.root.children[0].children[1], "3", ["W", "X"])
319+
g.append_infoset(g.root.children[1].children[1], g.root.children[0].children[1].infoset)
320+
g.append_move(g.root.children[0].children[0].children[0], "3", ["Y", "Z"])
321+
iset = g.root.children[0].children[0].children[0].infoset
322+
g.append_infoset(g.root.children[0].children[0].children[1], iset)
323+
g.append_move(g.root.children[0].children[1].children[1], "2", ["C", "D"])
324+
o = g.add_outcome([3, 1, 4])
325+
g.set_outcome(g.root.children[0].children[0].children[0].children[0], o)
326+
o = g.add_outcome([4, 0, 1])
327+
g.set_outcome(g.root.children[0].children[0].children[0].children[1], o)
328+
o = g.add_outcome([1, 3, 2])
329+
g.set_outcome(g.root.children[0].children[1].children[0], o)
330+
o = g.add_outcome([2, 4, 1])
331+
g.set_outcome(g.root.children[0].children[1].children[1].children[0], o)
332+
o = g.add_outcome([4, 1, 3])
333+
g.set_outcome(g.root.children[0].children[1].children[1].children[1], o)
334+
if nonterm_outcomes:
335+
o = g.add_outcome([1, 2, 3])
336+
g.set_outcome(g.root.children[1], o)
337+
o = g.add_outcome([1, 0, 1])
338+
g.set_outcome(g.root.children[1].children[0].children[0], o)
339+
o = g.add_outcome([2, -1, -2])
340+
g.set_outcome(g.root.children[1].children[0].children[1], o)
341+
o = g.add_outcome([-1, 2, -1])
342+
g.set_outcome(g.root.children[1].children[1].children[0], o)
343+
else:
344+
o = g.add_outcome([2, 2, 4])
345+
g.set_outcome(g.root.children[1].children[0].children[0], o)
346+
o = g.add_outcome([3, 1, 1])
347+
g.set_outcome(g.root.children[1].children[0].children[1], o)
348+
o = g.add_outcome([0, 4, 2])
349+
g.set_outcome(g.root.children[1].children[1].children[0], o)
350+
o = g.add_outcome([1, 2, 3])
351+
g.set_outcome(g.root.children[1].children[1].children[1], o)
352+
o = g.add_outcome([0, 0, 0])
353+
g.set_outcome(g.root.children[0].children[0].children[1].children[0], o)
354+
g.set_outcome(g.root.children[0].children[0].children[1].children[1], o)
355+
return g
356+
357+
126358
def create_matching_pennies_efg(with_neutral_outcome: bool = False) -> gbt.Game:
127359
"""
128360
The version with_neutral_outcome adds a (0,0) payoff outcomes at a non-terminal node.
@@ -211,8 +443,6 @@ def create_stripped_down_poker_efg(nonterm_outcomes: bool = False) -> gbt.Game:
211443
g.set_outcome(bob_calls_and_loses_node, bob_calls_and_loses_outcome)
212444
bob_calls_and_wins_node = g.root.children["Queen"].children["Bet"].children["Call"]
213445
g.set_outcome(bob_calls_and_wins_node, bob_calls_and_wins_outcome)
214-
# g.to_efg("stripped_down_poker_nonterminal_outcomes.efg")
215-
216446
return g
217447

218448

@@ -701,21 +931,33 @@ def create_seq_form_STOC_paper_zero_sum_2_player_efg() -> gbt.Game:
701931
return g
702932

703933

704-
def create_two_player_perfect_info_win_lose_efg() -> gbt.Game:
934+
def create_two_player_perfect_info_win_lose_efg(nonterm_outcomes: bool = False) -> gbt.Game:
705935
g = gbt.Game.new_tree(players=["1", "2"], title="2 player perfect info win lose")
706936
g.append_move(g.root, "2", ["a", "b"])
707937
g.append_move(g.root.children[0], "1", ["L", "R"])
708938
g.append_move(g.root.children[1], "1", ["L", "R"])
709939
g.append_move(g.root.children[0].children[0], "2", ["l", "r"])
710-
g.set_outcome(
711-
g.root.children[0].children[0].children[0], g.add_outcome([1, -1], label="aLl")
712-
)
713-
g.set_outcome(
714-
g.root.children[0].children[0].children[1], g.add_outcome([-1, 1], label="aLr")
715-
)
716-
g.set_outcome(g.root.children[0].children[1], g.add_outcome([1, -1], label="aR"))
717-
g.set_outcome(g.root.children[1].children[0], g.add_outcome([1, -1], label="bL"))
718-
g.set_outcome(g.root.children[1].children[1], g.add_outcome([-1, 1], label="bR"))
940+
if not nonterm_outcomes:
941+
g.set_outcome(
942+
g.root.children[0].children[0].children[0], g.add_outcome([1, -1], label="aLl")
943+
)
944+
g.set_outcome(
945+
g.root.children[0].children[0].children[1], g.add_outcome([-1, 1], label="aLr")
946+
)
947+
g.set_outcome(g.root.children[0].children[1], g.add_outcome([1, -1], label="aR"))
948+
g.set_outcome(g.root.children[1].children[0], g.add_outcome([1, -1], label="bL"))
949+
g.set_outcome(g.root.children[1].children[1], g.add_outcome([-1, 1], label="bR"))
950+
else:
951+
g.set_outcome(g.root.children[0], g.add_outcome([-100, 50], label="a"))
952+
g.set_outcome(
953+
g.root.children[0].children[0].children[0], g.add_outcome([101, -51], label="aLl")
954+
)
955+
g.set_outcome(
956+
g.root.children[0].children[0].children[1], g.add_outcome([99, -49], label="aLr")
957+
)
958+
g.set_outcome(g.root.children[0].children[1], g.add_outcome([101, -51], label="aR"))
959+
g.set_outcome(g.root.children[1].children[0], g.add_outcome([1, -1], label="bL"))
960+
g.set_outcome(g.root.children[1].children[1], g.add_outcome([-1, 1], label="bR"))
719961
return g
720962

721963

@@ -990,14 +1232,14 @@ def _redu_strats(self, player, level):
9901232
first_half = tmp[:n_half]
9911233
second_half = tmp[n_half:]
9921234
n_stars = (
993-
self.get_n_infosets(level)[1] - self.get_n_infosets(level - 1)[1] - 1
1235+
self.get_n_infosets(level)[1] - self.get_n_infosets(level - 1)[1] - 1
9941236
)
9951237
stars = "*" * n_stars
9961238
return (
997-
["11" + t + stars for t in first_half]
998-
+ ["12" + t + stars for t in second_half]
999-
+ ["21" + stars + t for t in first_half]
1000-
+ ["22" + stars + t for t in second_half]
1239+
["11" + t + stars for t in first_half]
1240+
+ ["12" + t + stars for t in second_half]
1241+
+ ["21" + stars + t for t in first_half]
1242+
+ ["22" + stars + t for t in second_half]
10011243
)
10021244

10031245

tests/test_extensive.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,63 @@ def test_reduced_strategic_form(
395395
# convert strings to rationals
396396
exp_array = games.vectorized_make_rational(np_arrays_of_rsf[i])
397397
assert (arrays[i] == exp_array).all()
398+
399+
400+
@pytest.mark.parametrize(
401+
"standard,modified",
402+
[
403+
(
404+
games.create_two_player_perfect_info_win_lose_efg(),
405+
games.create_two_player_perfect_info_win_lose_efg(nonterm_outcomes=True)
406+
),
407+
(
408+
games.create_3_player_with_internal_outcomes_efg(),
409+
games.create_3_player_with_internal_outcomes_efg(nonterm_outcomes=True)
410+
),
411+
(
412+
games.create_chance_in_middle_efg(),
413+
games.create_chance_in_middle_efg(nonterm_outcomes=True)
414+
),
415+
(
416+
games.create_non_zero_sum_lacking_outcome_efg(),
417+
games.create_non_zero_sum_lacking_outcome_efg(missing_term_outcome=True)
418+
),
419+
(
420+
games.create_entry_accomodation_efg(),
421+
games.create_entry_accomodation_efg(nonterm_outcomes=True)
422+
),
423+
(
424+
games.create_three_action_internal_outcomes_efg(),
425+
games.create_three_action_internal_outcomes_efg(nonterm_outcomes=True)
426+
),
427+
(
428+
games.create_kuhn_poker_efg(),
429+
games.create_kuhn_poker_efg(nonterm_outcomes=True)
430+
),
431+
(
432+
games.create_stripped_down_poker_efg(),
433+
games.create_stripped_down_poker_efg(nonterm_outcomes=True)
434+
),
435+
(
436+
games.create_2x2_zero_sum_efg(),
437+
games.create_2x2_zero_sum_efg(missing_term_outcome=True)
438+
),
439+
(
440+
games.create_matching_pennies_efg(),
441+
games.create_matching_pennies_efg(with_neutral_outcome=True)
442+
),
443+
],
444+
)
445+
def test_reduced_strategy_form_nonterminal_outcomes_consistency(standard: gbt.Game,
446+
modified: gbt.Game):
447+
"""
448+
standard: game uses only non-terminal outcomes, with all non-terminal nodes having outcomes
449+
modified: is payoff equivalent, but with non-terminal outcomes or missing terminal outcomes
450+
451+
The test checks that the corresponding reduced strategic forms match.
452+
"""
453+
arrays_s = standard.to_arrays()
454+
arrays_m = modified.to_arrays()
455+
assert (len(arrays_s) == len(arrays_m))
456+
for array_s, array_m in zip(arrays_s, arrays_m, strict=True):
457+
assert (array_s == array_m).all()

0 commit comments

Comments
 (0)