diff --git a/.gitignore b/.gitignore index f8be35fcf..643f8c565 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index e42a18ad9..83464ab5f 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -1,13 +1,106 @@ 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 _write_efg_table(df: pd.DataFrame, f): + """Write the EFG games list-table to file handle f.""" + f.write(".. list-table::\n") + f.write(" :header-rows: 1\n") + f.write(" :widths: 100\n") + f.write(" :class: tight-table\n") + f.write("\n") + f.write(" * - **Extensive form games**\n") + + efg_df = df[df["Format"] == "efg"] + for _, row in efg_df.iterrows(): + slug = row["Game"] + title = str(row.get("Title", "")).strip() + description = str(row.get("Description", "")).strip() + # Skip any games which lack a description + if description: + # Main dropdown + f.write(f" * - .. dropdown:: {title}\n") + f.write(" :open:\n") + f.write(" \n") + for line in description.splitlines(): + f.write(f" {line}\n") + f.write(" \n") + f.write(" **Load in PyGambit:**\n") + f.write(" \n") + f.write(" .. code-block:: python\n") + f.write(" \n") + f.write(f' pygambit.catalog.load("{slug}")\n') + f.write(" \n") + + # Download links (inside the dropdown) + download_links = [row["Download"]] + f.write(" **Download:**\n") + f.write(" \n") + f.write(f" {' '.join(download_links)}\n") + f.write(" \n") + + +# def _write_nfg_table(df: pd.DataFrame, f): +# """Write the NFG games list-table to file handle f.""" +# f.write(".. list-table::\n") +# f.write(" :header-rows: 1\n") +# f.write(" :widths: 100\n") +# f.write(" :class: tight-table\n") +# f.write("\n") +# f.write(" * - **Strategic form games**\n") + +# nfg_df = df[df["Format"] == "nfg"] +# for _, row in nfg_df.iterrows(): +# slug = row["Game"] + +# # Title as plain text header +# f.write(" * - \n") +# f.write(" \n") + +# # Jupyter-execute block (no dropdown) +# f.write(" .. jupyter-execute::\n") +# f.write(" \n") +# f.write(" import pygambit\n") +# f.write(f' pygambit.catalog.load("{slug}")\n') +# f.write(" \n") + +# # Download link (plain, no dropdown) +# f.write(f" :download:`{slug}.nfg <../catalog/{slug}.nfg>`\n") +# f.write(" \n") + + +def generate_rst_table(df: pd.DataFrame, rst_path: Path): + """Generate RST output with two list-tables: one for EFG and one for NFG games.""" + + with open(rst_path, "w", encoding="utf-8") as f: + # TOC linking to both sections + # f.write(".. contents::\n") + # f.write(" :local:\n") + # f.write(" :depth: 1\n") + # f.write("\n") + + # EFG section + # f.write("Extensive form games\n") + # f.write("--------------------\n") + # f.write("\n") + _write_efg_table(df, f) + # f.write("\n") + + # # NFG section + # f.write("Strategic form games\n") + # f.write("--------------------\n") + # f.write("\n") + # _write_nfg_table(df, f) + + def update_makefile(): """Update the Makefile.am with all games from the catalog.""" @@ -60,15 +153,14 @@ def update_makefile(): if __name__ == "__main__": - parser = argparse.ArgumentParser() 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.") - - # Update the Makefile.am with the current list of catalog files + # 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.") if args.build: + # Update the Makefile.am with the current list of catalog files update_makefile() diff --git a/catalog/__init__.py b/catalog/__init__.py index 4a972d917..26104acdd 100644 --- a/catalog/__init__.py +++ b/catalog/__init__.py @@ -1,5 +1,6 @@ from importlib.resources import as_file, files from pathlib import Path +from typing import Any import pandas as pd @@ -17,32 +18,148 @@ def load(slug: str) -> gbt.Game: """ Load a game from the package catalog. + + Parameters + ---------- + slug : str + The slug of the game to load. + + Returns + ------- + gbt.Game + The loaded game. + + Raises + ------ + FileNotFoundError + If the game does not exist in the catalog. """ slug = str(Path(slug)).replace("\\", "/") + # Try to load from file for suffix, reader in READERS.items(): resource_path = _CATALOG_RESOURCE / f"{slug}{suffix}" - if resource_path.is_file(): - # as_file ensures we have a real filesystem path for the reader with as_file(resource_path) as path: return reader(str(path)) - raise FileNotFoundError(f"No catalog entry called {slug}.nfg or {slug}.efg") - - -def games() -> pd.DataFrame: + # Raise error if game does not exist + raise FileNotFoundError(f"No catalog entry called {slug}") + + +def games( + n_actions: int | None = None, + n_contingencies: int | None = None, + n_infosets: int | None = None, + is_const_sum: bool | None = None, + is_perfect_recall: bool | None = None, + is_tree: bool | None = None, + min_payoff: float | None = None, + max_payoff: float | None = None, + n_nodes: int | None = None, + 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, including subdirectories. + List games available in the package catalog. + + Most arguments are treated as filters on the + attributes of the Game objects. + + Parameters + ---------- + n_actions: int, optional + The number of actions in the game. Only extensive games are returned. + n_contingencies: int, optional + The number of contingencies in the game. + n_infosets: int, optional + The number of information sets in the game. Only extensive games are returned. + is_const_sum: bool, optional + Whether the game is constant-sum. + is_perfect_recall: bool, optional + Whether the game has perfect recall. + is_tree: bool, optional + Whether the game is an extensive game (a tree). + min_payoff: float, optional + The minimum payoff in the game. Games returned have `min_payoff >= value`. + max_payoff: float, optional + The maximum payoff in the game. Games returned have `max_payoff <= value`. + n_nodes: int, optional + The number of nodes in the game. Only extensive games are returned. + n_outcomes: int, optional + The number of outcomes in the game. + n_players: int, optional + 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, str]] = [] - - # Using rglob("*") to find files in all subdirectories + records: list[dict[str, Any]] = [] + + def check_filters(game: gbt.Game) -> bool: + if n_actions is not None: + if not game.is_tree: + return False + if len(game.actions) != n_actions: + return False + if n_contingencies is not None and len(game.contingencies) != n_contingencies: + return False + if n_infosets is not None: + if not game.is_tree: + return False + if len(game.infosets) != n_infosets: + return False + if is_const_sum is not None and game.is_const_sum != is_const_sum: + return False + if is_perfect_recall is not None and game.is_perfect_recall != is_perfect_recall: + return False + if is_tree is not None and game.is_tree != is_tree: + return False + if min_payoff is not None and game.min_payoff < min_payoff: + return False + if max_payoff is not None and game.max_payoff > max_payoff: + return False + if n_nodes is not None: + if not game.is_tree: + return False + if len(game.nodes) != n_nodes: + return False + if n_outcomes is not None and len(game.outcomes) != n_outcomes: + return False + if n_players is not None and len(game.players) != n_players: + 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}>`" + record["Format"] = 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) if reader is not None and resource_path.is_file(): - # Calculate the path relative to the root resource # and remove the suffix to get the "slug" rel_path = resource_path.relative_to(_CATALOG_RESOURCE) @@ -50,11 +167,11 @@ def games() -> pd.DataFrame: with as_file(resource_path) as path: game = reader(str(path)) - records.append( - { - "Game": slug, - "Title": game.title, - } - ) + if check_filters(game): + append_record(slug, game) + if include_descriptions: + return pd.DataFrame.from_records( + records, columns=["Game", "Title", "Description", "Download", "Format"] + ) return pd.DataFrame.from_records(records, columns=["Game", "Title"]) diff --git a/catalog/bagwell1995.efg b/catalog/bagwell1995.efg index 58889c664..3783d1649 100644 --- a/catalog/bagwell1995.efg +++ b/catalog/bagwell1995.efg @@ -1,15 +1,12 @@ EFG 2 R "Bagwell (GEB 1995) commitment and (un)observability" { "Player 1" "Player 2" } "This is a Stackelberg-type game with imperfectly observed commitment, following the -analysis of Bagwell [^Bag1995]. The outcomes and payoffs are the same as in Bagwell's +analysis of `Bag1995 `_. The outcomes and payoffs are the same as in Bagwell's model. This example sets the probability that the follower 'correctly' observes the leader's action as .99 (99/100). The key result is that the only pure-strategy equilibrium that survives if observability is imperfect is the one in which players choose the actions that would form an equilibrium if the game was a *simultaneous-move* game. There is an equilibrium in which the 'Stackelberg' action is played with high probability, but strictly less than one. - -[^Bag1995]: Bagwell, Kyle (1995) Commitment and observability in games. - _Games and Economic Behavior_ 8: 271-280. " p "" 1 1 "" { "S" "C" } 0 diff --git a/catalog/myerson1991/fig2_1.efg b/catalog/myerson1991/fig2_1.efg index a95745147..3c45dedee 100644 --- a/catalog/myerson1991/fig2_1.efg +++ b/catalog/myerson1991/fig2_1.efg @@ -1,20 +1,14 @@ EFG 2 R "A simple Poker game" { "Fred" "Alice" } -"This is a simple game of one-card poker from Myerson [^Mye91], used as the +"This is a simple game of one-card poker from `Mye91 `_, used as the introductory example for game models. Note that as specified in the text, the game has the slightly unusual feature that folding with the high (red) card results in the player winning rather than losing. -See also --------- -reiley2008/fig1 +See also `Rei2008 `_ Another one-card poker game where folding with the high card is a loss rather than a win. - - -[^Mye1991]: Myerson, Roger B. (1991) Game Theory: Analysis of Conflict. - Cambridge: Harvard University Press. " c "" 1 "" { "Red" 1/2 "Black" 1/2 } 0 diff --git a/catalog/myerson1991/fig4_2.efg b/catalog/myerson1991/fig4_2.efg index 9631090f0..d7b503bea 100644 --- a/catalog/myerson1991/fig4_2.efg +++ b/catalog/myerson1991/fig4_2.efg @@ -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 `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 @@ -7,9 +7,6 @@ representation. However, it is not a Nash equilibrium of the extensive 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. - Cambridge: Harvard University Press. " p "" 1 1 "" { "A1" "B1" } 0 diff --git a/catalog/reiley2008/fig1.efg b/catalog/reiley2008/fig1.efg index 9083ff0f6..df8c5a4d8 100644 --- a/catalog/reiley2008/fig1.efg +++ b/catalog/reiley2008/fig1.efg @@ -1,14 +1,8 @@ EFG 2 R "Stripped-down poker (Reiley et al 2008)" { "Professor" "Student" } -"This is a one-card poker game used in [^Rei2008] as a teaching exercise. +"This is a one-card poker game used in `Rei2008 `_ as a teaching exercise. -See also --------- -myerson1991/fig2_1 +See also `Mye91 `_ Another one-card poker game with slightly different rules. - -[^Rei2008]: Reiley, David H., Urbancic, Michael B, and Walker, Mark. (2008) - Stripped-Down Poker: A Classroom Game with Signaling and Bluffing. - _The Journal of Economic Education_ 4: 323-341. " c "" 1 "" { "King" 1/2 "Queen" 1/2 } 0 diff --git a/catalog/selten1975/fig1.efg b/catalog/selten1975/fig1.efg index c966809b6..039d50f8f 100644 --- a/catalog/selten1975/fig1.efg +++ b/catalog/selten1975/fig1.efg @@ -1,14 +1,10 @@ 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 `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 - for equilibrium points in extensive games. International Journal of Game - Theory 4(1): 25-55. " p "" 1 1 "" { "R" "L" } 0 diff --git a/catalog/selten1975/fig2.efg b/catalog/selten1975/fig2.efg index 1d36cb21f..116dadb5d 100644 --- a/catalog/selten1975/fig2.efg +++ b/catalog/selten1975/fig2.efg @@ -1,12 +1,8 @@ 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 - for equilibrium points in extensive games. International Journal of Game - Theory 4(1): 25-55. " p "" 1 1 "" { "R" "L" } 0 diff --git a/catalog/selten1975/fig3.efg b/catalog/selten1975/fig3.efg index 7df596906..f7364f91b 100644 --- a/catalog/selten1975/fig3.efg +++ b/catalog/selten1975/fig3.efg @@ -1,12 +1,8 @@ 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 - for equilibrium points in extensive games. International Journal of Game - Theory 4(1): 25-55. " p "" 1 1 "" { "R" "L" } 0 diff --git a/catalog/watson2013/exercise29_6.efg b/catalog/watson2013/exercise29_6.efg index 8ca0f7a3d..e1fbc7551 100644 --- a/catalog/watson2013/exercise29_6.efg +++ b/catalog/watson2013/exercise29_6.efg @@ -1,6 +1,6 @@ EFG 2 R "Princess Bride signaling game (from Watson)" { "Wesley" "Prince" } -"This game is Exercise 29.6 from Watson [^Wat13], based on a scene from -the Rob Reiner film, _The Princess Bride_: +"This game is Exercise 29.6 from Watson `Wat13 `_, based on a scene from +the Rob Reiner film, The Princess Bride: Wesley (the protagonist) confronts the evil prince Humperdinck. Wesley is one of two types: weak or strong. Wesley knows whether he is weak or @@ -15,9 +15,6 @@ swordsman. Also, the weak Wesley must pay a cost to get out of bed. In the game in this file, the cost the weak Wesley pays to get out of bed is set to 2. - -[^Wat13]: Watson, Joel. (2013) Strategy: An Introduction to Game Theory, - third edition. W. W. Norton & Company. " c "" 1 "" { "Strong" 1/2 "Weak" 1/2 } 0 diff --git a/catalog/watson2013/fig29_1.efg b/catalog/watson2013/fig29_1.efg index 1ec266fa2..6b08714b2 100644 --- a/catalog/watson2013/fig29_1.efg +++ b/catalog/watson2013/fig29_1.efg @@ -1,9 +1,6 @@ EFG 2 R "Job-market signaling game (version from Watson)" { "You" "Firm" } "This is a version of Spence's classic model of education being a job-market -signal, as presented in Figure 29.1 of Watson [^Wat13]. - -[^Wat13]: Watson, Joel. (2013) Strategy: An Introduction to Game Theory, - third edition. W. W. Norton & Company. +signal, as presented in Figure 29.1 of Watson `Wat13 `_. " c "" 1 "" { "High" 1/3 "Low" 2/3 } 0 diff --git a/doc/biblio.rst b/doc/biblio.rst index b84d341a8..ca292ffc4 100644 --- a/doc/biblio.rst +++ b/doc/biblio.rst @@ -1,122 +1,155 @@ +.. _bibliography: + 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 ------------------------------------------ -.. [BlaTur23] Bland, J. R. and Turocy, T. L., 2023. Quantal response equilibrium - as a structural model for estimation: The missing manual. - SSRN working paper 4425515. +.. [BlaTur23] Bland, J. R. and Turocy, T. L. 2023, + 'Quantal response equilibrium as a structural model for estimation: the + missing manual', *SSRN Working Paper*, no. 4425515. -.. [Eav71] B. C. Eaves, "The linear complementarity problem", 612-634, - Management Science , 17, 1971. +.. [Eav71] Eaves, B. C. 1971, 'The linear complementarity problem', + *Management Science*, vol. 17, pp. 612-634. -.. [GovWil03] Govindan, Srihari and Robert Wilson. (2003) - “A Global Newton Method to Compute Nash Equilibria.” - Journal of Economic Theory 110(1): 65-86. +.. [GovWil03] Govindan, S. and Wilson, R. 2003, + 'A global Newton method to compute Nash equilibria', + *Journal of Economic Theory*, vol. 110, no. 1, pp. 65-86. -.. [GovWil04] Govindan, Srihari and Robert Wilson. (2004) - “Computing Nash Equilibria by Iterated Polymatrix Approximation.” - Journal of Economic Dynamics and Control 28: 1229-1241. +.. [GovWil04] Govindan, S. and Wilson, R. 2004, + 'Computing Nash equilibria by iterated polymatrix approximation', + *Journal of Economic Dynamics and Control*, vol. 28, pp. 1229-1241. -.. [Jiang11] A. X. Jiang, K. Leyton-Brown, and N. Bhat. (2011) - "Action-Graph Games." Games and Economic Behavior 71(1): 141-173. +.. [Jiang11] Jiang, A. X., Leyton-Brown, K. and Bhat, N. 2011, + 'Action-graph games', *Games and Economic Behavior*, vol. 71, no. 1, + pp. 141-173. -.. [KolMegSte94] Daphne Koller, Nimrod Megiddo, and Bernhard von - Stengel (1996). - "Efficient computation of equilibria for extensive two-person games." - Games and Economic Behavior 14: 247-259. +.. [KolMegSte94] Koller, D., Megiddo, N. and von Stengel, B. 1996, + 'Efficient computation of equilibria for extensive two-person games', + *Games and Economic Behavior*, vol. 14, pp. 247-259. -.. [LemHow64] C. E. Lemke and J. T. Howson, "Equilibrium points of - bimatrix games", 413-423, Journal of the Society of Industrial and - Applied Mathematics , 12, 1964. +.. [LemHow64] Lemke, C. E. and Howson, J. T. 1964, + 'Equilibrium points of bimatrix games', + *Journal of the Society of Industrial and Applied Mathematics*, + vol. 12, pp. 413-423. -.. [Man64] O. Mangasarian, "Equilibrium points in bimatrix games", - 778-780, Journal of the Society for Industrial and Applied - Mathematics, 12, 1964. +.. [Man64] Mangasarian, O. 1964, 'Equilibrium points in bimatrix games', + *Journal of the Society for Industrial and Applied Mathematics*, + vol. 12, pp. 778-780. -.. [McK91] Richard McKelvey, A Liapunov function for Nash equilibria, - 1991, California Institute of Technology. +.. [McK91] McKelvey, R. 1991, 'A Liapunov function for Nash equilibria', + California Institute of Technology. -.. [McKMcL96] Richard McKelvey and Andrew McLennan, "Computation of - equilibria in finite games", 87-142, Handbook of Computational - Economics , Edited by H. Amman, D. Kendrick, J. Rust, Elsevier, 1996. +.. [McKMcL96] McKelvey, R. and McLennan, A. 1996, + 'Computation of equilibria in finite games', in Amman, H., Kendrick, + D. and Rust, J. (eds), *Handbook of Computational Economics*, Elsevier, + pp. 87-142. -.. [PNS04] Ryan Porter, Eugene Nudelman, and Yoav Shoham. - "Simple search methods for finding a Nash equilibrium." - Games and Economic Behavior 664-669, 2004. +.. [PNS04] Porter, R., Nudelman, E. and Shoham, Y. 2004, + 'Simple search methods for finding a Nash equilibrium', + *Games and Economic Behavior*, pp. 664-669. -.. [Ros71] J. Rosenmuller, "On a generalization of the Lemke-Howson - Algorithm to noncooperative n-person games", 73-79, SIAM Journal of - Applied Mathematics, 21, 1971. +.. [Ros71] Rosenmuller, J. 1971, + 'On a generalization of the Lemke-Howson algorithm to noncooperative + n-person games', *SIAM Journal of Applied Mathematics*, vol. 21, + pp. 73-79. -.. [Sha74] Lloyd Shapley, "A note on the Lemke-Howson algorithm", 175-189, - Mathematical Programming Study , 1, 1974. +.. [Sha74] Shapley, L. 1974, 'A note on the Lemke-Howson algorithm', + *Mathematical Programming Study*, vol. 1, pp. 175-189. -.. [Tur05] Theodore L. Turocy, "A dynamic homotopy interpretation of the - logistic quantal response equilibrium correspondence", 243-263, Games - and Economic Behavior, 51, 2005. +.. [Tur05] Turocy, T. L. 2005, + 'A dynamic homotopy interpretation of the logistic quantal response + equilibrium correspondence', *Games and Economic Behavior*, vol. 51, + pp. 243-263. -.. [Tur10] Theodore L. Turocy, "Using Quantal Response to Compute - Nash and Sequential Equilibria." Economic Theory 42(1): 255-269, 2010. +.. [Tur10] Turocy, T. L. 2010, + 'Using quantal response to compute Nash and sequential equilibria', + *Economic Theory*, vol. 42, no. 1, pp. 255-269. -.. [VTH87] G. van der Laan, A. J. J. Talman, and L. van Der Heyden, - "Simplicial variable dimension algorithms for solving the nonlinear +.. [VTH87] van der Laan, G., Talman, A. J. J. and van Der Heyden, L. 1987, + 'Simplicial variable dimension algorithms for solving the nonlinear complementarity problem on a product of unit simplices using a general - labelling", 377-397, Mathematics of Operations Research , 1987. + labelling', *Mathematics of Operations Research*, pp. 377-397. -.. [Wil71] Robert Wilson, "Computing equilibria of n-person games", 80-87, - SIAM Applied Math, 21, 1971. +.. [Wil71] Wilson, R. 1971, 'Computing equilibria of n-person games', + *SIAM Applied Math*, vol. 21, pp. 80-87. -.. [Yam93] Y. Yamamoto, 1993, "A Path-Following Procedure to Find a Proper - Equilibrium of Finite Games ", International Journal of Game Theory . +.. [Yam93] Yamamoto, Y. 1993, + 'A path-following procedure to find a proper equilibrium of finite + games', *International Journal of Game Theory*. General game theory articles and texts -------------------------------------- -.. [Harsanyi1967a] John Harsanyi, "Games of Incomplete Information Played - By Bayesian Players I", 159-182, Management Science , 14, 1967. +.. [Bag1995] Bagwell, K. 1995, 'Commitment and observability in games', + *Games and Economic Behavior*, vol. 8, pp. 271-280. -.. [Harsanyi1967b] John Harsanyi, "Games of Incomplete Information Played - By Bayesian Players II", 320-334, Management Science , 14, 1967. +.. [Harsanyi1967a] Harsanyi, J. 1967, + 'Games of incomplete information played by Bayesian players I', + *Management Science*, vol. 14, pp. 159-182. -.. [Harsanyi1968] John Harsanyi, "Games of Incomplete Information Played - By Bayesian Players III", 486-502, Management Science , 14, 1968. +.. [Harsanyi1967b] Harsanyi, J. 1967, + 'Games of incomplete information played by Bayesian players II', + *Management Science*, vol. 14, pp. 320-334. -.. [KreWil82] David Kreps and Robert Wilson, "Sequential Equilibria", - 863-894, Econometrica , 50, 1982. +.. [Harsanyi1968] Harsanyi, J. 1968, + 'Games of incomplete information played by Bayesian players III', + *Management Science*, vol. 14, pp. 486-502. -.. [McKPal95] Richard McKelvey and Tom Palfrey, "Quantal response - equilibria for normal form games", 6-38, Games and Economic Behavior , - 10, 1995. +.. [KreWil82] Kreps, D. and Wilson, R. 1982, 'Sequential equilibria', + *Econometrica*, vol. 50, pp. 863-894. -.. [McKPal98] Richard McKelvey and Tom Palfrey, "Quantal response - equilibria for extensive form games", 9-41, Experimental Economics , - 1, 1998. +.. [Kre90] Kreps, D. 1990, *A Course in Microeconomic Theory*, + Princeton University Press. -.. [Mye78] Roger Myerson, "Refinements of the Nash equilibrium concept", - 73-80, International Journal of Game Theory , 7, 1978. +.. [McKPal95] McKelvey, R. and Palfrey, T. 1995, + 'Quantal response equilibria for normal form games', + *Games and Economic Behavior*, vol. 10, pp. 6-38. -.. [Nas50] John Nash, "Equilibrium points in n-person games", 48-49, - Proceedings of the National Academy of Sciences , 36, 1950. +.. [McKPal98] McKelvey, R. and Palfrey, T. 1998, + 'Quantal response equilibria for extensive form games', + *Experimental Economics*, vol. 1, pp. 9-41. -.. [Och95] Jack Ochs, "Games with unique, mixed strategy equilibria: - An experimental study", Games and Economic Behavior 10: 202-217, 1995. +.. [Mye78] Myerson, R. 1978, + 'Refinements of the Nash equilibrium concept', + *International Journal of Game Theory*, vol. 7, pp. 73-80. -.. [Sel75] Reinhard Selten, Reexamination of the perfectness concept for - equilibrium points in extensive games , 25-55, International Journal - of Game Theory , 4, 1975. +.. [Nas50] Nash, J. 1950, 'Equilibrium points in n-person games', + *Proceedings of the National Academy of Sciences*, vol. 36, + pp. 48-49. -.. [vanD83] Eric van Damme, 1983, Stability and Perfection of Nash - Equilibria , Springer-Verlag, Berlin. +.. [Och95] Ochs, J. 1995, + 'Games with unique, mixed strategy equilibria: an experimental study', + *Games and Economic Behavior*, vol. 10, pp. 202-217. + +.. [Rei2008] Reiley, D. H., Urbancic, M. B. and Walker, M. 2008, + 'Stripped-down poker: a classroom game with signaling and bluffing', + *The Journal of Economic Education*, vol. 4, pp. 323-341. + +.. [Sel75] Selten, R. 1975, + 'Reexamination of the perfectness concept for equilibrium points in + extensive games', *International Journal of Game Theory*, vol. 4, + pp. 25-55. + +.. [vanD83] van Damme, E. 1983, *Stability and Perfection of Nash + Equilibria*, Springer-Verlag, Berlin. Textbooks and general reference ------------------------------- -.. [Mye91] Roger Myerson, 1991, Game Theory : Analysis of Conflict , +.. [Mye91] Myerson, R. 1991, *Game Theory: Analysis of Conflict*, Harvard University Press. + +.. [Wat13] Watson, J. 2013, *Strategy: An Introduction to Game Theory*, + 3rd edn, W. W. Norton & Company. diff --git a/doc/catalog.rst b/doc/catalog.rst index cf8d45e2c..2c90df4c8 100644 --- a/doc/catalog.rst +++ b/doc/catalog.rst @@ -1,8 +1,9 @@ +.. _catalog: + Catalog of games ================ -.. csv-table:: - :file: catalog.csv - :header-rows: 1 - :widths: 20, 80 - :class: tight-table +Below is a complete list of games included in Gambit's catalog. +Check out the :ref:`pygambit API reference ` for instructions on how to search and load these games in Python, and the :ref:`Updating the games catalog ` guide for instructions on how to contribute new games to the catalog. + +.. include:: catalog_table.rst diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst index 349d1792c..a967369ba 100644 --- a/doc/developer.catalog.rst +++ b/doc/developer.catalog.rst @@ -1,23 +1,39 @@ +.. _updating-catalog: + Updating the Games Catalog ========================== -This page covers the process for contributing to and updating Gambit's :ref:`Games Catalog `. +This page includes developer notes regarding the catalog module, and the process for contributing to and updating Gambit's :ref:`Games Catalog `. To do so, you will need to have the `gambit` GitHub repo cloned and be able to submit pull request via GitHub; you may wish to first review the :ref:`contributor guidelines `. You'll also need to have a developer install of `pygambit` available in your Python environment, see :ref:`build-python`. +The catalog module +------------------ + +Although the ``catalog`` directory is located at the project root outside of ``src/pygambit/``, it is installed and bundled as the ``pygambit.catalog`` subpackage. + +This is handled by the package build configuration in ``pyproject.toml`` under ``[tool.setuptools]``: + +- The ``package-dir`` mapping instructs ``setuptools`` to source the ``pygambit.catalog`` subpackage from the physical ``catalog`` directory. +- The ``package-data`` configuration ensures all non-Python data files (like ``.efg`` and ``.nfg`` files) inside the catalog are correctly bundled during installation. + +As a developer, this means you will need to reinstall the package (e.g., passing ``pip install .``) for any new game files or internal catalog changes to be reflected in the ``pygambit`` module. + +Add new game files +------------------ + You can add games to the catalog saved in a valid representation :ref:`format `. Currently supported representations are: - `.efg` for extensive form games - `.nfg` for normal form games -Add new games -------------- - 1. **Create the game file:** Use either :ref:`pygambit `, the Gambit :ref:`CLI ` or :ref:`GUI ` to create and save game in a valid representation :ref:`format `. + Make sure the game includes a description, with any citations referencing the :ref:`bibliography `. + Use a full link to the bibliography entry, so the link can be accessed from the file directly, as well as being rendered in the docs e.g. ```Rei2008 `_`` 2. **Add the game file:** diff --git a/doc/index.rst b/doc/index.rst index 101bba6d5..4d9cd59b0 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -10,7 +10,7 @@ construction and analysis of finite extensive and strategic games. .. grid:: .. grid-item-card:: ⬇️ Installing Gambit - :columns: 4 + :columns: 3 Quick installation with PyGambit: ``pip install pygambit`` @@ -23,7 +23,7 @@ construction and analysis of finite extensive and strategic games. .. grid-item-card:: 🐍 PyGambit - :columns: 4 + :columns: 3 Explore tutorial notebooks and API reference docs. @@ -35,7 +35,7 @@ construction and analysis of finite extensive and strategic games. .. grid-item-card:: 🧮 Analysing games - :columns: 4 + :columns: 3 Compute equilibria and run econometric estimations. @@ -45,8 +45,19 @@ construction and analysis of finite extensive and strategic games. :color: secondary :expand: + .. grid-item-card:: 📚 Catalog of games + :columns: 3 + + Browse a curated collection of game theory models. + + .. button-ref:: catalog + :ref-type: ref + :click-parent: + :color: secondary + :expand: + .. grid-item-card:: 🖱️ Graphical interface - :columns: 4 + :columns: 3 Interactively create, explore, and find equilibria of games. @@ -56,8 +67,19 @@ construction and analysis of finite extensive and strategic games. :color: secondary :expand: + .. grid-item-card:: 💻 Command-line interface + :columns: 3 + + Use Gambit's command-line tools for scripting. + + .. button-ref:: command-line + :ref-type: ref + :click-parent: + :color: secondary + :expand: + .. grid-item-card:: 🐛 Bugs and feature requests - :columns: 4 + :columns: 3 Report bugs and feature requests on GitHub. @@ -68,7 +90,7 @@ construction and analysis of finite extensive and strategic games. :expand: .. grid-item-card:: 📖 Developer docs - :columns: 4 + :columns: 3 Guides for developers & contributors to the package. diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 7568cfd5a..7f8eea604 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -325,3 +325,17 @@ Computation of quantal response equilibria logit_estimate LogitQREMixedStrategyFitResult LogitQREMixedBehaviorFitResult + + +.. _pygambit-catalog: + +Catalog of games +~~~~~~~~~~~~~~~~ + +.. currentmodule:: pygambit.catalog + +.. autosummary:: + :toctree: api/ + + load + games diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index f41068e1b..388891ae0 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -311,7 +311,7 @@ }, { "cell_type": "markdown", - "id": "3dfbf327", + "id": "24f36b0d", "metadata": {}, "source": [ "Saving and reading strategic form games to and from file\n", diff --git a/doc/tutorials/advanced_tutorials/starting_points.ipynb b/doc/tutorials/advanced_tutorials/starting_points.ipynb index 24d568c6c..491667abf 100644 --- a/doc/tutorials/advanced_tutorials/starting_points.ipynb +++ b/doc/tutorials/advanced_tutorials/starting_points.ipynb @@ -25,7 +25,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "493cafb8", "metadata": {}, "outputs": [], diff --git a/tests/test_catalog.py b/tests/test_catalog.py index c010c2a51..c034a3cbd 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -4,6 +4,11 @@ import pygambit as gbt +@pytest.fixture(scope="module") +def all_games(): + return gbt.catalog.games() + + @pytest.fixture def game_slugs(): """Fixture providing a set of all game slugs in the catalog.""" @@ -36,14 +41,134 @@ def test_catalog_load_invalid_slug(): gbt.catalog.load("invalid_slug") -def test_catalog_games(game_slugs): +def test_catalog_games(game_slugs, all_games): """Test games() function returns df of game slugs and titles.""" - all_games = gbt.catalog.games() assert isinstance(all_games, pd.DataFrame) - # The games() function should return exactly the set of slugs found above - assert set(all_games["Game"]) == game_slugs - # Test that standard columns are present assert "Game" in all_games.columns assert "Title" in all_games.columns + + +def test_catalog_games_filter_n_actions(all_games): + """Test games() function can filter on length of gbt.Game attribute 'actions'""" + filtered_games = gbt.catalog.games(n_actions=2) + assert isinstance(filtered_games, pd.DataFrame) + assert len(filtered_games) < len(all_games) + if len(filtered_games) > 0: + g = gbt.catalog.load(filtered_games.Game.iloc[0]) + assert len(g.actions) == 2 + + +def test_catalog_games_filter_n_contingencies(all_games): + """Test games() function can filter on length of gbt.Game attribute 'contingencies'""" + filtered_games = gbt.catalog.games(n_contingencies=2) + assert isinstance(filtered_games, pd.DataFrame) + assert len(filtered_games) < len(all_games) + if len(filtered_games) > 0: + g = gbt.catalog.load(filtered_games.Game.iloc[0]) + assert len(g.contingencies) == 2 + + +def test_catalog_games_filter_n_infosets(all_games): + """Test games() function can filter on length of gbt.Game attribute 'infosets'""" + filtered_games = gbt.catalog.games(n_infosets=2) + assert isinstance(filtered_games, pd.DataFrame) + assert len(filtered_games) < len(all_games) + if len(filtered_games) > 0: + g = gbt.catalog.load(filtered_games.Game.iloc[0]) + assert len(g.infosets) == 2 + + +def test_catalog_games_filter_is_const_sum(all_games): + """Test games() function can filter on boolean gbt.Game attribute 'is_const_sum'""" + filtered_games = gbt.catalog.games(is_const_sum=True) + assert isinstance(filtered_games, pd.DataFrame) + assert len(filtered_games) < len(all_games) + if len(filtered_games) > 0: + g = gbt.catalog.load(filtered_games.Game.iloc[0]) + assert g.is_const_sum + + +def test_catalog_games_filter_is_not_perfect_recall(all_games): + """Test games() function can filter on boolean gbt.Game attribute 'is_perfect_recall'""" + filtered_games = gbt.catalog.games(is_perfect_recall=False) + assert isinstance(filtered_games, pd.DataFrame) + assert len(filtered_games) < len(all_games) + if len(filtered_games) > 0: + g = gbt.catalog.load(filtered_games.Game.iloc[0]) + assert not g.is_perfect_recall + + +def test_catalog_games_filter_is_not_tree(all_games): + """Test games() function can filter on boolean gbt.Game attribute 'is_tree'""" + filtered_games = gbt.catalog.games(is_tree=False) + assert isinstance(filtered_games, pd.DataFrame) + assert len(filtered_games) < len(all_games) + if len(filtered_games) > 0: + g = gbt.catalog.load(filtered_games.Game.iloc[0]) + assert not g.is_tree + + +def test_catalog_games_filter_min_payoff_and_max_payoff(all_games): + """Test games() function can filter on min and max payoff values""" + filtered_games = gbt.catalog.games(min_payoff=0, max_payoff=10) + assert isinstance(filtered_games, pd.DataFrame) + assert len(filtered_games) < len(all_games) + if len(filtered_games) > 0: + g = gbt.catalog.load(filtered_games.Game.iloc[0]) + assert g.min_payoff >= 0 + assert g.max_payoff <= 10 + + +def test_catalog_games_filter_n_nodes(all_games): + """Test games() function can filter on length of gbt.Game attribute 'nodes'""" + filtered_games = gbt.catalog.games(n_nodes=5) + assert isinstance(filtered_games, pd.DataFrame) + assert len(filtered_games) < len(all_games) + if len(filtered_games) > 0: + g = gbt.catalog.load(filtered_games.Game.iloc[0]) + assert len(g.nodes) == 5 + + +def test_catalog_games_filter_n_outcomes(all_games): + """Test games() function can filter on length of gbt.Game attribute 'outcomes'""" + filtered_games = gbt.catalog.games(n_outcomes=3) + assert isinstance(filtered_games, pd.DataFrame) + assert len(filtered_games) < len(all_games) + if len(filtered_games) > 0: + g = gbt.catalog.load(filtered_games.Game.iloc[0]) + assert len(g.outcomes) == 3 + + +def test_catalog_games_filter_n_players(all_games): + """Test games() function can filter on length of gbt.Game attribute 'players'""" + filtered_games = gbt.catalog.games(n_players=2) + assert isinstance(filtered_games, pd.DataFrame) + assert len(filtered_games) < len(all_games) + if len(filtered_games) > 0: + g = gbt.catalog.load(filtered_games.Game.iloc[0]) + assert len(g.players) == 2 + + +def test_catalog_games_filter_n_strategies(all_games): + """Test games() function can filter on length of gbt.Game attribute 'strategies'""" + filtered_games = gbt.catalog.games(n_strategies=4) + assert isinstance(filtered_games, pd.DataFrame) + assert len(filtered_games) < len(all_games) + if len(filtered_games) > 0: + g = gbt.catalog.load(filtered_games.Game.iloc[0]) + assert len(g.strategies) == 4 + + +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