Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ Gambit.app/*
build_support/msw/gambit.wxs
build_support/osx/Info.plist
src/pygambit/catalog
doc/catalog.csv
doc/catalog_table.rst
45 changes: 41 additions & 4 deletions build_support/catalog/update.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,49 @@
import argparse
from pathlib import Path

import pandas as pd

import pygambit as gbt

CATALOG_CSV = Path(__file__).parent.parent.parent / "doc" / "catalog.csv"
CATALOG_RST_TABLE = Path(__file__).parent.parent.parent / "doc" / "catalog_table.rst"
CATALOG_DIR = Path(__file__).parent.parent.parent / "catalog"
MAKEFILE_AM = Path(__file__).parent.parent.parent / "Makefile.am"


def generate_rst_table(df: pd.DataFrame, rst_path: Path):
"""Generate a list-table RST file with dropdowns for long descriptions."""
with open(rst_path, "w", encoding="utf-8") as f:
f.write(".. list-table::\n")
f.write(" :header-rows: 1\n")
f.write(" :widths: 20 80 20\n")
f.write(" :class: tight-table\n")
f.write("\n")

f.write(" * - **Game**\n")
f.write(" - **Description**\n")
f.write(" - **Download**\n")

for _, row in df.iterrows():
f.write(f" * - {row['Game']}\n")

description_cell_lines = []
title = str(row.get("Title", "")).strip()
description = str(row.get("Description", "")).strip()
if description:
description_cell_lines.append(f".. dropdown:: {title}")
description_cell_lines.append(" ") # Indented blank line
for line in description.splitlines():
description_cell_lines.append(f" {line}")
else:
description_cell_lines.append(title)

f.write(f" - {description_cell_lines[0]}\n")
for line in description_cell_lines[1:]:
f.write(f" {line}\n")

f.write(f" - {row['Download']}\n")


def update_makefile():
"""Update the Makefile.am with all games from the catalog."""

Expand Down Expand Up @@ -65,9 +101,10 @@ def update_makefile():
parser.add_argument("--build", action="store_true")
args = parser.parse_args()

# Create CSV used by RST docs page
gbt.catalog.games().to_csv(CATALOG_CSV, index=False)
print(f"Generated {CATALOG_CSV} for use in local docs build. DO NOT COMMIT.")
# Create RST list-table used by doc/catalog.rst
df = gbt.catalog.games(include_descriptions=True)
generate_rst_table(df, CATALOG_RST_TABLE)
print(f"Generated {CATALOG_RST_TABLE} for use in local docs build. DO NOT COMMIT.")

# Update the Makefile.am with the current list of catalog files
if args.build:
Expand Down
41 changes: 27 additions & 14 deletions catalog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,12 @@ def games(
n_outcomes: int | None = None,
n_players: int | None = None,
n_strategies: int | None = None,
include_descriptions: bool = False,
) -> pd.DataFrame:
"""
List games available in the package catalog.

Arguments are treated as filters on the
Most arguments are treated as filters on the
attributes of the Game objects.

Parameters
Expand Down Expand Up @@ -98,11 +99,15 @@ def games(
The number of players in the game.
n_strategies: int, optional
The number of pure strategies in the game.
include_descriptions: bool, optional
Whether to include the description of each game in the returned DataFrame.
Defaults to False.

Returns
-------
pd.DataFrame
A DataFrame with columns "Game" and "Title", where "Game" is the slug to load the game.
If `include_descriptions=True`, the DataFrame will also include a "Description" column.
"""
records: list[dict[str, Any]] = []

Expand Down Expand Up @@ -140,6 +145,20 @@ def check_filters(game: gbt.Game) -> bool:
return False
return not (n_strategies is not None and len(game.strategies) != n_strategies)

def append_record(
slug: str,
game: gbt.Game,
) -> None:
record = {
"Game": slug,
"Title": game.title,
}
if include_descriptions:
record["Description"] = game.description
ext = "efg" if game.is_tree else "nfg"
record["Download"] = f":download:`{slug}.{ext} <../catalog/{slug}.{ext}>`"
records.append(record)

# Add all the games stored as EFG/NFG files
for resource_path in sorted(_CATALOG_RESOURCE.rglob("*")):
reader = READERS.get(resource_path.suffix)
Expand All @@ -153,12 +172,7 @@ def check_filters(game: gbt.Game) -> bool:
with as_file(resource_path) as path:
game = reader(str(path))
if check_filters(game):
records.append(
{
"Game": slug,
"Title": game.title,
}
)
append_record(slug, game)

# Add all the games from families
for slug, game in family_games().items():
Expand All @@ -168,13 +182,12 @@ def check_filters(game: gbt.Game) -> bool:
f"Slug collision: {slug} is present in both file-based and family games."
)
if check_filters(game):
records.append(
{
"Game": slug,
"Title": game.title,
}
)
append_record(slug, game)

if include_descriptions:
return pd.DataFrame.from_records(
records, columns=["Game", "Title", "Description", "Download"]
)
return pd.DataFrame.from_records(records, columns=["Game", "Title"])


Expand Down Expand Up @@ -218,7 +231,7 @@ def one_shot_trust(unique_NE_variant: bool = False) -> gbt.Game:
The constructed extensive-form game.
"""
g = gbt.Game.new_tree(players=["Buyer", "Seller"])
g.description = "One-shot trust game with binary actions, originally from Kreps (1990)."
g.description = "One-shot trust game with binary actions, originally from [Kre90]_."
g.append_move(g.root, "Buyer", ["Trust", "Not trust"])
g.append_move(g.root.children[0], "Seller", ["Honor", "Abuse"])
g.set_outcome(g.root.children[0].children[0], g.add_outcome([1, 1], label="Trustworthy"))
Expand Down
4 changes: 2 additions & 2 deletions catalog/myerson1991/fig4_2.efg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
EFG 2 R "Myerson (1991) Figure 4.2" { "Player 1" "Player 2" }
"An example from Myerson [^Mye1991] which illustrates the distinction between
"An example from Myerson [Mye91]_ which illustrates the distinction between
an equilibrium of an extensive form game and an equilibrium of its
(multi)agent representation. The actions B1, Z1, and W2 form a
behavior profile which is an equilibrium in the (multi)agent
Expand All @@ -8,7 +8,7 @@ game, because Player 1 would prefer to switch from (B1, Z1) to
(A1, Y1); the (multi)agent representation rules out such coordinated
deviations across information sets.

[^Mye1991]: Myerson, Roger B. (1991) Game Theory: Analysis of Conflict.
[Mye91]_: Myerson, Roger B. (1991) Game Theory: Analysis of Conflict.
Cambridge: Harvard University Press.
"

Expand Down
4 changes: 2 additions & 2 deletions catalog/selten1975/fig1.efg
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
EFG 2 R "Selten's horse (Selten IJGT 1975, Figure 1)" { "Player 1" "Player 2" "Player 3" }
"This is a three-player game presented in Selten [^Sel1975], commonly referred
"This is a three-player game presented in Selten [Sel75]_, commonly referred
to as \"Selten's horse\" owing to the layout in which it can be drawn.
It is the motivating example for his definition of (trembling-hand)
perfect equilibrium, by showing a game that has an equilibrium which
is \"unreasonable\", but which is not ruled out by subgame perfection because
this game has no proper subgames.

[^Sel1975]: Selten, Reinhard (1975). A reexamination of the perfectness concept
[Sel75]_: Selten, Reinhard (1975). A reexamination of the perfectness concept
for equilibrium points in extensive games. International Journal of Game
Theory 4(1): 25-55.
"
Expand Down
4 changes: 2 additions & 2 deletions catalog/selten1975/fig2.efg
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
EFG 2 R "Selten (IJGT 1975) Figure 2" { "Player 1" "Player 2" }
"This is a counterexample presented in [^Sel1975], to show that extensive and
"This is a counterexample presented in [Sel75]_, to show that extensive and
normal form concepts of perfectness do not coincide. This game has one
perfect equilibrium in the extensive from, but a distinct (pure) strategy
equilibrium is also perfect in the normal form.

[^Sel75]: Selten, Reinhard (1975). A reexamination of the perfectness concept
[Sel75]_: Selten, Reinhard (1975). A reexamination of the perfectness concept
for equilibrium points in extensive games. International Journal of Game
Theory 4(1): 25-55.
"
Expand Down
4 changes: 2 additions & 2 deletions catalog/selten1975/fig3.efg
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
EFG 2 R "Selten (IJGT 1975) Figure 3" { "Player 1" "Player 2" "Player 3" }
"This is a counterexample presented in [^Sel1975], to show that extensive and
"This is a counterexample presented in [Sel75]_, to show that extensive and
normal form concepts of perfectness do not coincide. Specifically, there
are two equilibria which are perfect in the normal form but not perfect
in the extensive form.

[^Sel75]: Selten, Reinhard (1975). A reexamination of the perfectness concept
[Sel75]_: Selten, Reinhard (1975). A reexamination of the perfectness concept
for equilibrium points in extensive games. International Journal of Game
Theory 4(1): 25-55.
"
Expand Down
8 changes: 8 additions & 0 deletions doc/biblio.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Bibliography
============

.. note::

To reference an entry in this bibliography, use the format ``[key]_``, for example, ``[Mye91]_`` will link to the Myerson (1991) textbook entry.



Articles on computation of Nash equilibria
------------------------------------------
Expand Down Expand Up @@ -89,6 +94,9 @@ General game theory articles and texts
.. [KreWil82] David Kreps and Robert Wilson, "Sequential Equilibria",
863-894, Econometrica , 50, 1982.

.. [Kre90] David Kreps, 1990, A Course in Microeconomic Theory,
Princeton University Press.

.. [McKPal95] Richard McKelvey and Tom Palfrey, "Quantal response
equilibria for normal form games", 6-38, Games and Economic Behavior ,
10, 1995.
Expand Down
6 changes: 1 addition & 5 deletions doc/catalog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,4 @@ Catalog of games
Below is a complete list of games included in Gambit's catalog.
Check out the :ref:`pygambit API reference <pygambit-catalog>` for instructions on how to search and load these games in Python, and the :ref:`Updating the games catalog <updating-catalog>` guide for instructions on how to contribute new games to the catalog.

.. csv-table::
:file: catalog.csv
:header-rows: 1
:widths: 20, 80
:class: tight-table
.. include:: catalog_table.rst
1 change: 0 additions & 1 deletion doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ We recommended most new users install the PyGambit Python package and read the a
tools
gui
catalog
samples
developer
formats
biblio
Expand Down
7 changes: 7 additions & 0 deletions tests/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,10 @@ def test_catalog_games_filter_bad_filter():
"""Test games() function raises error on invalid filter key"""
with pytest.raises(TypeError):
gbt.catalog.games(invalid_filter=123)


def test_catalog_games_include_descriptions():
"""Test games() function can include descriptions"""
games_with_desc = gbt.catalog.games(include_descriptions=True)
assert "Description" in games_with_desc.columns
assert "Download" in games_with_desc.columns