Skip to content

Commit 49c56bb

Browse files
committed
New tests for creation of reduced strategic form for centipede and binary games with an exponentially-large reduced strategic form
1 parent 89cbddc commit 49c56bb

2 files changed

Lines changed: 208 additions & 0 deletions

File tree

tests/games.py

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""A utility module to create/load games for the test suite."""
22

33
import pathlib
4+
from itertools import product
45

56
import numpy as np
67

@@ -234,6 +235,200 @@ def create_reduction_both_players_payoff_ties_efg() -> gbt.Game:
234235
return g
235236

236237

238+
class Centipede:
239+
"""
240+
Helper class for creating EFG and corresponding reduced strategy sets and payoff arrays
241+
for the two-player centipede game (with the number of rounds as a parameter, and two payoff
242+
parameters, one for each player)
243+
"""
244+
245+
def create_game(N, m0, m1):
246+
# Create two-player centipede game with N rounds and payoff params m0 and m1
247+
g = gbt.Game.new_tree(
248+
players=["1", "2"], title=f"Centipede Game with {N} rounds"
249+
)
250+
current_node = g.root
251+
current_player = "1"
252+
for t in range(N):
253+
g.append_move(current_node, current_player, ["Take", "Push"])
254+
payoffs = [2**t * m0, 2**t * m1] # take payoffs
255+
if current_player == "2":
256+
payoffs.reverse()
257+
g.set_outcome(current_node.children[0], g.add_outcome(payoffs))
258+
if t == N - 1: # for last round, push payoffs
259+
payoffs = [2 ** (t + 1) * m1, 2 ** (t + 1) * m0]
260+
if current_player == "2":
261+
payoffs.reverse()
262+
g.set_outcome(current_node.children[1], g.add_outcome(payoffs))
263+
current_node = current_node.children[1]
264+
current_player = "2" if current_player == "1" else "1"
265+
return g
266+
267+
def redu_strats(N):
268+
269+
if N % 2 == 0:
270+
p1_n_moves = p2_n_moves = int(N / 2)
271+
else:
272+
p1_n_moves = int((N + 1) / 2)
273+
p2_n_moves = int((N - 1) / 2)
274+
275+
def get_rss(n):
276+
# Given n (which should be I and J for players 1 and 2 respectively)
277+
# creates the set of strategies that:
278+
# - have n + 1 positions
279+
# - have all *s after any 1
280+
# - have prefixes 1, 21, 221, 2221, etc. ending with all 2s
281+
ret = ["2" * (i) + "1" * 1 + "*" * (n - i - 1) for i in range(n)]
282+
ret.append("2" * n)
283+
return ret
284+
285+
return [get_rss(n) for n in [p1_n_moves, p2_n_moves]]
286+
287+
def get_size_of_RSF(N):
288+
return [len(x) for x in Centipede.redu_strats(N)]
289+
290+
def create_rsf(N, m0, m1):
291+
m, n = Centipede.get_size_of_RSF(N)
292+
p1_payoffs = np.zeros((m, n), dtype=int)
293+
p2_payoffs = np.zeros((m, n), dtype=int)
294+
row1_1 = [m0] * n
295+
row1_2 = [m1] * n
296+
p1_payoffs[0, :] = row1_1
297+
p2_payoffs[0, :] = row1_2
298+
299+
for j in range(n - 1 if N % 2 == 0 else n):
300+
max_in_col_p1 = 2 ** (2 * j + 1) * m1
301+
max_in_col_p2 = 2 ** (2 * j + 1) * m0
302+
base1 = [max_in_col_p1] * (m - 1)
303+
base2 = [max_in_col_p2] * (m - 1)
304+
for i in range(1, (j + 1)):
305+
base1[i - 1] = 2 ** (2 * i) * m0
306+
base2[i - 1] = 2 ** (2 * i) * m1
307+
p1_payoffs[1:, j] = base1
308+
p2_payoffs[1:, j] = base2
309+
if N % 2 == 0:
310+
# final col
311+
p1_payoffs[:, n - 1] = p1_payoffs[:, n - 2]
312+
p2_payoffs[:, n - 1] = p2_payoffs[:, n - 2]
313+
p1_extra_pay = 2 ** (2 * (n - 1)) * m0
314+
p2_extra_pay = 2 ** (2 * (n - 1)) * m1
315+
else:
316+
# final row
317+
p1_payoffs[m - 1, :] = p1_payoffs[m - 2, :]
318+
p2_payoffs[m - 1, :] = p2_payoffs[m - 2, :]
319+
p1_extra_pay = 2 ** (2 * (n) - 1) * m1
320+
p2_extra_pay = 2 ** (2 * (n) - 1) * m0
321+
p1_payoffs[m - 1, n - 1] = p1_extra_pay
322+
p2_payoffs[m - 1, n - 1] = p2_extra_pay
323+
return p1_payoffs, p2_payoffs
324+
325+
def test_parametrization(N, m0, m1):
326+
return (
327+
Centipede.create_game(N, m0, m1),
328+
Centipede.redu_strats(N),
329+
Centipede.create_rsf(N, m0, m1),
330+
)
331+
332+
333+
class BinEFGExpRSF:
334+
"""
335+
Helper class for creating EFG and corresponding reduced strategy sets and payoff arrays
336+
for two-player games on a binary tree with exponentially-many (~ 2^root(level))
337+
reduced strategies
338+
339+
Games taken from:
340+
341+
B. von Stengel, A. van den Elzen, and A. J. J. Talman (2002)
342+
Computing normal form perfect equilibria for extensive two-person games
343+
Econometrica 70(2), 693-715
344+
345+
The games are parametrized by a single positive integer, namely the number of "level"s
346+
"""
347+
348+
def get_n_infosets(level):
349+
if level % 2 == 0:
350+
p1_n_moves = p2_n_moves = int(level / 2)
351+
else:
352+
p1_n_moves = int((level + 1) / 2)
353+
p2_n_moves = int((level - 1) / 2)
354+
p1_n_isets = (4**p1_n_moves + 2) / 6
355+
p2_n_isets = (4**p2_n_moves - 1) / 3
356+
return int(p1_n_isets), int(p2_n_isets)
357+
358+
def redu_strats(player, level):
359+
assert player in [1, 2] and level >= 2
360+
if level == 2:
361+
return ["1", "2"]
362+
elif (level % 2 == 0 and player == 1) or (level % 2 != 0 and player == 2):
363+
return BinEFGExpRSF.redu_strats(player, level - 1)
364+
elif player == 2:
365+
tmp = BinEFGExpRSF.redu_strats(player=1, level=level - 1)
366+
tmp = [
367+
t[1:] for t in tmp
368+
] # remove first action (1 from 1st half; 2 from 2nd half)
369+
# split into two halves
370+
n_half = int(len(tmp) / 2)
371+
first_half = tmp[:n_half]
372+
second_half = tmp[n_half:]
373+
# create first half suffix
374+
first_half = product(first_half, first_half)
375+
first_half = ["".join(t) for t in first_half]
376+
first_half = ["1" + t for t in first_half] # add 1 to front
377+
# create second half suffix
378+
second_half = product(second_half, second_half)
379+
second_half = ["".join(t) for t in second_half]
380+
second_half = ["2" + t for t in second_half] # add 2 to front
381+
return first_half + second_half # glue halves together
382+
else:
383+
p1_n_isets, p2_n_isets = BinEFGExpRSF.get_n_infosets(level)
384+
p1_n_isets_level_minus1, p2_n_isets_level_minus1 = (
385+
BinEFGExpRSF.get_n_infosets(level - 1)
386+
)
387+
stars = "*" * (p1_n_isets - p2_n_isets_level_minus1 - 1)
388+
return [
389+
"1" + t + stars
390+
for t in BinEFGExpRSF.redu_strats(player=2, level=level - 1)
391+
] + [
392+
"2" + stars + t
393+
for t in BinEFGExpRSF.redu_strats(player=2, level=level - 1)
394+
]
395+
396+
def create_binary_tree(g, node, player1_turn, depth, max_depth):
397+
if depth == max_depth:
398+
payoff1 = payoff2 = 0
399+
g.set_outcome(node, g.add_outcome([payoff1, payoff2]))
400+
else:
401+
current_player = "1" if player1_turn else "2"
402+
g.append_move(node, current_player, ["L", "R"])
403+
for child in node.children:
404+
BinEFGExpRSF.create_binary_tree(
405+
g, child, not player1_turn, depth + 1, max_depth
406+
)
407+
408+
def create_game(L):
409+
g = gbt.Game.new_tree(players=["1", "2"], title=f"Binary Tree Game (L={L})")
410+
BinEFGExpRSF.create_binary_tree(g, g.root, True, 0, L)
411+
for n in g.nodes:
412+
if not n.is_terminal and not n.children[0].is_terminal:
413+
g.set_infoset(n.children[1], n.children[0].infoset)
414+
return g
415+
416+
def create_rsf(reduced_strategies):
417+
m = len(reduced_strategies[0])
418+
n = len(reduced_strategies[1])
419+
zeros = np.zeros((m, n), dtype=int)
420+
return [zeros, zeros]
421+
422+
def test_parametrization(L):
423+
redu_strats = [BinEFGExpRSF.redu_strats(1, L), BinEFGExpRSF.redu_strats(2, L)]
424+
425+
return (
426+
BinEFGExpRSF.create_game(L),
427+
redu_strats,
428+
BinEFGExpRSF.create_rsf(redu_strats),
429+
)
430+
431+
237432
def make_rational(input: str):
238433
return gbt.Rational(input)
239434

tests/test_extensive.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,19 @@ def test_outcome_index_exception_label():
293293
),
294294
],
295295
),
296+
(games.Centipede.test_parametrization(N=3, m0=2, m1=7)),
297+
(games.Centipede.test_parametrization(N=4, m0=2, m1=7)),
298+
(games.Centipede.test_parametrization(N=5, m0=2, m1=7)),
299+
(games.Centipede.test_parametrization(N=3, m0=1, m1=3)),
300+
(games.Centipede.test_parametrization(N=4, m0=1, m1=3)),
301+
(games.Centipede.test_parametrization(N=5, m0=1, m1=3)),
302+
(games.Centipede.test_parametrization(N=9, m0=3, m1=11)),
303+
(games.BinEFGExpRSF.test_parametrization(L=3)),
304+
(games.BinEFGExpRSF.test_parametrization(L=4)),
305+
(games.BinEFGExpRSF.test_parametrization(L=5)),
306+
(games.BinEFGExpRSF.test_parametrization(L=6)),
307+
(games.BinEFGExpRSF.test_parametrization(L=7)),
308+
#
296309
# I M P E R F E C T R E C A L L --- commented out in the test suite
297310
# Wichardt (2008): binary tree of height 3; 2 players; the root player forgets the action
298311
# (

0 commit comments

Comments
 (0)