From a4dc1e26a60fe397f3bad8ab12b0baf51b8bc1cf Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 18 May 2026 12:08:40 +0100 Subject: [PATCH 01/20] Pin sphinx>=7.0 --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index aac243a76..5d519a31e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ dependencies = [ [project.optional-dependencies] test = ["pytest", "pytest-subtests", "nbformat", "nbclient", "ipykernel"] doc = [ + "sphinx>=7.0", "pydata-sphinx-theme", "sphinx_design", "sphinx-autobuild", From 260a04718fa9b916265fe5775c28f724f4f384e5 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 18 May 2026 12:09:01 +0100 Subject: [PATCH 02/20] Add nau2004 to catalog --- Makefile.am | 1 + {contrib/games => catalog/nau2004}/2x2x2-nau.nfg | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) rename {contrib/games => catalog/nau2004}/2x2x2-nau.nfg (54%) diff --git a/Makefile.am b/Makefile.am index 703049248..b7cc7b6fd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -213,6 +213,7 @@ EXTRA_DIST = \ catalog/jakobsen2016/fig3.efg \ catalog/myerson1991/fig2_1.efg \ catalog/myerson1991/fig4_2.efg \ + catalog/nau2004/2x2x2-nau.nfg \ catalog/reiley2008/fig1.efg \ catalog/selten1975/fig1.efg \ catalog/selten1975/fig2.efg \ diff --git a/contrib/games/2x2x2-nau.nfg b/catalog/nau2004/2x2x2-nau.nfg similarity index 54% rename from contrib/games/2x2x2-nau.nfg rename to catalog/nau2004/2x2x2-nau.nfg index 0e9ca9eec..13ab55e07 100644 --- a/contrib/games/2x2x2-nau.nfg +++ b/catalog/nau2004/2x2x2-nau.nfg @@ -4,12 +4,7 @@ NFG 1 R "2x2x2 example with 3 pure, 2 incompletely mixed, and a continuum of com { "1" "2" } { "1" "2" } } -"Example game from Nau et al [^Nau2004]. - -[^Nau2004]: Nau, Robert, Gomez Canovas, Sabrina, and Hansen, Pierre (2004). - On the geometry of Nash equilibria and correlated equilibria. - International Journal of Game Theory 32(4): 443-453 -" +"Example game from `Nau2004 `." { { "" 0, 0, 2 } From 96568d7fbd815943c7d5edf187b55b994082065d Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 18 May 2026 12:09:24 +0100 Subject: [PATCH 03/20] uncomment NFG catalog parts of update script --- build_support/catalog/update.py | 80 ++++++++++++++++----------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index 83464ab5f..b3a510e18 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -48,33 +48,33 @@ def _write_efg_table(df: pd.DataFrame, f): 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 _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): @@ -82,23 +82,23 @@ def generate_rst_table(df: pd.DataFrame, rst_path: Path): 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") + 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") + f.write("Extensive form games\n") + f.write("--------------------\n") + f.write("\n") _write_efg_table(df, f) - # f.write("\n") + f.write("\n") - # # NFG section - # f.write("Strategic form games\n") - # f.write("--------------------\n") - # f.write("\n") - # _write_nfg_table(df, f) + # NFG section + f.write("Strategic form games\n") + f.write("--------------------\n") + f.write("\n") + _write_nfg_table(df, f) def update_makefile(): From 8c94c4ebdb2ebc584dc4675099cac75b713bdc59 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 18 May 2026 13:44:15 +0100 Subject: [PATCH 04/20] rename --- Makefile.am | 2 +- catalog/nau2004/{2x2x2-nau.nfg => 2x2x2.nfg} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename catalog/nau2004/{2x2x2-nau.nfg => 2x2x2.nfg} (100%) diff --git a/Makefile.am b/Makefile.am index b7cc7b6fd..b37959722 100644 --- a/Makefile.am +++ b/Makefile.am @@ -213,7 +213,7 @@ EXTRA_DIST = \ catalog/jakobsen2016/fig3.efg \ catalog/myerson1991/fig2_1.efg \ catalog/myerson1991/fig4_2.efg \ - catalog/nau2004/2x2x2-nau.nfg \ + catalog/nau2004/2x2x2.nfg \ catalog/reiley2008/fig1.efg \ catalog/selten1975/fig1.efg \ catalog/selten1975/fig2.efg \ diff --git a/catalog/nau2004/2x2x2-nau.nfg b/catalog/nau2004/2x2x2.nfg similarity index 100% rename from catalog/nau2004/2x2x2-nau.nfg rename to catalog/nau2004/2x2x2.nfg From c5b8e0f5a8e083fd5ab9847f882419ff638ac868 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 18 May 2026 14:20:15 +0100 Subject: [PATCH 05/20] Add jupyter-sphinx to enable code execution in catalog page --- doc/conf.py | 1 + pyproject.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index 65760193b..d03473cbd 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -29,6 +29,7 @@ "IPython.sphinxext.ipython_directive", "sphinx_design", "nbsphinx", + "jupyter_sphinx", ] # IPython directive configuration diff --git a/pyproject.toml b/pyproject.toml index 5d519a31e..425ae3b0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ doc = [ "matplotlib", "pickleshare", "jupyter", + "jupyter-sphinx", "open_spiel; sys_platform != 'win32'", ] From ba2419dda82841c4468847b7bd797e59419d737e Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 18 May 2026 14:50:09 +0100 Subject: [PATCH 06/20] Combine EFG and NFG tables into a single RST table --- build_support/catalog/update.py | 121 +++++++++++--------------------- 1 file changed, 41 insertions(+), 80 deletions(-) diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index b3a510e18..c1d1a4b4f 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -10,75 +10,8 @@ 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.""" + """Generate RST output with a list-table for games.""" with open(rst_path, "w", encoding="utf-8") as f: # TOC linking to both sections @@ -86,19 +19,47 @@ def generate_rst_table(df: pd.DataFrame, rst_path: Path): 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(".. list-table::\n") + f.write(" :header-rows: 1\n") + f.write(" :widths: 100\n") + f.write(" :class: tight-table\n") f.write("\n") - _write_nfg_table(df, f) + f.write(" * - **Extensive form games**\n") + + for _, row in 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") + if row["Format"] == "efg": + f.write(" .. code-block:: python\n") + f.write(" \n") + f.write(f' pygambit.catalog.load("{slug}")\n') + f.write(" \n") + elif row["Format"] == "nfg": + 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 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 update_makefile(): From 13962d13400b629ea63e8f25ad0d167a4e1c9010 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 18 May 2026 15:21:54 +0100 Subject: [PATCH 07/20] Add Nau2004 biblio entry --- doc/biblio.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/biblio.rst b/doc/biblio.rst index b256f566b..28c4e7ce6 100644 --- a/doc/biblio.rst +++ b/doc/biblio.rst @@ -52,6 +52,10 @@ Articles on computation of Nash equilibria D. and Rust, J. (eds), *Handbook of Computational Economics*, Elsevier, pp. 87-142. +.. [Nau2004] Nau, Robert, Gomez Canovas, Sabrina, and Hansen, Pierre (2004). + On the geometry of Nash equilibria and correlated equilibria. + International Journal of Game Theory 32(4): 443-453 + .. [PNS04] Porter, R., Nudelman, E. and Shoham, Y. 2004, 'Simple search methods for finding a Nash equilibrium', *Games and Economic Behavior*, pp. 664-669. From 043629eaec02dbf7c224558ccfaf96afb8ccbf0f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 18 May 2026 15:23:17 +0100 Subject: [PATCH 08/20] Update the game file link --- catalog/nau2004/2x2x2.nfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/catalog/nau2004/2x2x2.nfg b/catalog/nau2004/2x2x2.nfg index 13ab55e07..48d7c4627 100644 --- a/catalog/nau2004/2x2x2.nfg +++ b/catalog/nau2004/2x2x2.nfg @@ -4,7 +4,7 @@ NFG 1 R "2x2x2 example with 3 pure, 2 incompletely mixed, and a continuum of com { "1" "2" } { "1" "2" } } -"Example game from `Nau2004 `." +"Example game from `Nau2004 `_." { { "" 0, 0, 2 } From 9833a404dda285e8515e9724d1a192d34fbc2591 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 19 May 2026 12:56:09 +0100 Subject: [PATCH 09/20] Add Nau2004 game catalog entries --- Makefile.am | 4 ++++ catalog/nau2004/sec3.nfg | 14 ++++++++++++++ catalog/nau2004/sec4.nfg | 19 +++++++++++++++++++ catalog/nau2004/sec5.nfg | 19 +++++++++++++++++++ catalog/nau2004/sec6.nfg | 27 +++++++++++++++++++++++++++ 5 files changed, 83 insertions(+) create mode 100644 catalog/nau2004/sec3.nfg create mode 100644 catalog/nau2004/sec4.nfg create mode 100644 catalog/nau2004/sec5.nfg create mode 100644 catalog/nau2004/sec6.nfg diff --git a/Makefile.am b/Makefile.am index b37959722..5ad9efb89 100644 --- a/Makefile.am +++ b/Makefile.am @@ -214,6 +214,10 @@ EXTRA_DIST = \ catalog/myerson1991/fig2_1.efg \ catalog/myerson1991/fig4_2.efg \ catalog/nau2004/2x2x2.nfg \ + catalog/nau2004/sec3.nfg \ + catalog/nau2004/sec4.nfg \ + catalog/nau2004/sec5.nfg \ + catalog/nau2004/sec6.nfg \ catalog/reiley2008/fig1.efg \ catalog/selten1975/fig1.efg \ catalog/selten1975/fig2.efg \ diff --git a/catalog/nau2004/sec3.nfg b/catalog/nau2004/sec3.nfg new file mode 100644 index 000000000..c7b2c8470 --- /dev/null +++ b/catalog/nau2004/sec3.nfg @@ -0,0 +1,14 @@ +NFG 1 R "Battle of the Sexes" { "Row" "Column" } + +{ { "Top" "Bottom" } +{ "Left" "Right" } +} +"The coordination game known as Battle of the Sexes (section 3 of `Nau2004 `_). Has three Nash equilibria: two pure-strategy (TL and BR) and one completely mixed." + +{ +{ "" 3, 2 } +{ "" 0, 0 } +{ "" 0, 0 } +{ "" 2, 3 } +} +1 2 3 4 diff --git a/catalog/nau2004/sec4.nfg b/catalog/nau2004/sec4.nfg new file mode 100644 index 000000000..f0bd9a918 --- /dev/null +++ b/catalog/nau2004/sec4.nfg @@ -0,0 +1,19 @@ +NFG 1 R "Three-player game with a unique Nash solution in irrational strategies" { "Row" "Column" "Matrix" } + +{ { "Top" "Bottom" } +{ "Left" "Right" } +{ "1" "2" } +} +"A three-player game with a unique Nash equilibrium in irrational mixed strategies (section 4 of `Nau2004 `_). The correlated equilibrium polytope is seven-dimensional with 33 vertices." + +{ +{ "" 3, 0, 2 } +{ "" 0, 1, 0 } +{ "" 0, 2, 0 } +{ "" 1, 0, 0 } +{ "" 1, 0, 0 } +{ "" 0, 3, 0 } +{ "" 0, 1, 0 } +{ "" 2, 0, 3 } +} +1 2 3 4 5 6 7 8 diff --git a/catalog/nau2004/sec5.nfg b/catalog/nau2004/sec5.nfg new file mode 100644 index 000000000..3fcdfbeb9 --- /dev/null +++ b/catalog/nau2004/sec5.nfg @@ -0,0 +1,19 @@ +NFG 1 R "Game with a continuum of completely mixed-strategy Nash equilibria" { "Row" "Column" "Matrix" } + +{ { "Top" "Bottom" } +{ "Left" "Right" } +{ "1" "2" } +} +"A three-player 2x2x2 game with 3 pure, 2 incompletely mixed, and a continuum of completely mixed Nash equilibria (section 5 of `Nau2004 `_). The correlated equilibrium polytope is seven-dimensional with 8 vertices." + +{ +{ "" 0, 0, 2 } +{ "" 3, 0, 0 } +{ "" 0, 3, 0 } +{ "" 0, 0, 0 } +{ "" 1, 1, 0 } +{ "" 0, 0, 0 } +{ "" 0, 0, 0 } +{ "" 0, 0, 3 } +} +1 2 3 4 5 6 7 8 diff --git a/catalog/nau2004/sec6.nfg b/catalog/nau2004/sec6.nfg new file mode 100644 index 000000000..2a107c1d5 --- /dev/null +++ b/catalog/nau2004/sec6.nfg @@ -0,0 +1,27 @@ +NFG 1 R "2x2x4 game with Nash equilibria in the relative interior of the correlated equilibrium polytope" { "Row" "Column" "Matrix" } + +{ { "Top" "Bottom" } +{ "Left" "Right" } +{ "1" "2" "3" "4" } +} +"A three-player 2x2x4 game (section 6 of `Nau2004 `_). The correlated equilibrium polytope is four-dimensional with six vertices. The set of Nash equilibria is a line segment in the relative interior of the polytope." + +{ +{ "" 2, 0, 0 } +{ "" 0, 2, 1 } +{ "" 0, 1, 1 } +{ "" 1, 0, 2 } +{ "" 1, 0, 2 } +{ "" 0, 1, 1 } +{ "" 0, 2, 1 } +{ "" 2, 0, 0 } +{ "" 1, 0, 1 } +{ "" 0, 2, 0 } +{ "" 0, 1, 2 } +{ "" 2, 0, 1 } +{ "" 2, 0, 1 } +{ "" 0, 1, 2 } +{ "" 0, 2, 0 } +{ "" 1, 0, 1 } +} +1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 From fecf40b6121edaa52788c39253dc0724d4a8be8c Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 19 May 2026 12:57:57 +0100 Subject: [PATCH 10/20] remove original nau file --- Makefile.am | 1 - catalog/nau2004/2x2x2.nfg | 19 ------------------- 2 files changed, 20 deletions(-) delete mode 100644 catalog/nau2004/2x2x2.nfg diff --git a/Makefile.am b/Makefile.am index 5ad9efb89..f07dd5034 100644 --- a/Makefile.am +++ b/Makefile.am @@ -213,7 +213,6 @@ EXTRA_DIST = \ catalog/jakobsen2016/fig3.efg \ catalog/myerson1991/fig2_1.efg \ catalog/myerson1991/fig4_2.efg \ - catalog/nau2004/2x2x2.nfg \ catalog/nau2004/sec3.nfg \ catalog/nau2004/sec4.nfg \ catalog/nau2004/sec5.nfg \ diff --git a/catalog/nau2004/2x2x2.nfg b/catalog/nau2004/2x2x2.nfg deleted file mode 100644 index 48d7c4627..000000000 --- a/catalog/nau2004/2x2x2.nfg +++ /dev/null @@ -1,19 +0,0 @@ -NFG 1 R "2x2x2 example with 3 pure, 2 incompletely mixed, and a continuum of completely mixed NE" { "" "" "" } - -{ { "1" "2" } -{ "1" "2" } -{ "1" "2" } -} -"Example game from `Nau2004 `_." - -{ -{ "" 0, 0, 2 } -{ "" 3, 0, 0 } -{ "" 0, 3, 0 } -{ "" 0, 0, 0 } -{ "" 1, 1, 0 } -{ "" 0, 0, 0 } -{ "" 0, 0, 0 } -{ "" 0, 0, 3 } -} -1 2 3 4 5 6 7 8 From d123df808f0a8641d6edf7cce1a3ce454cc3f882 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 20 May 2026 12:03:49 +0100 Subject: [PATCH 11/20] refactor: encapsulate tikz_re inside generate_rst_table and expose regenerate_images parameter --- build_support/catalog/update.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index d3666cded..86e17e120 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -1,4 +1,5 @@ import argparse +import re from pathlib import Path import pandas as pd @@ -29,8 +30,9 @@ def catalog_draw_tree_settings(slug: str) -> dict: return settings -def generate_rst_table(df: pd.DataFrame, rst_path: Path, tikz_re, regenerate_images: bool): +def generate_rst_table(df: pd.DataFrame, rst_path: Path, regenerate_images: bool = False): """Generate RST output with a list-table for games.""" + tikz_re = re.compile(r"\\begin\{document\}(.*?)\\end\{document\}", re.DOTALL) with open(rst_path, "w", encoding="utf-8") as f: # TOC linking to both sections @@ -175,7 +177,7 @@ def update_makefile(): # Create RST list-table used by doc/catalog.rst df = gbt.catalog.games(include_descriptions=True) - generate_rst_table(df, CATALOG_RST_TABLE) + generate_rst_table(df, CATALOG_RST_TABLE, regenerate_images=args.regenerate_images) 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 From 49d5c804ce638fce89273fcde1e6bbc3ae31bbf6 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 20 May 2026 13:04:53 +0100 Subject: [PATCH 12/20] Ignore generated SVGs --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f46c8bff9..e70893578 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,4 @@ catalog/img/**/*.pdf catalog/img/**/*.png catalog/img/**/*.tex catalog/img/**/*.ef +catalog/img/**/*.svg From 63956c0c4e4810af8cafef20d1d8c7ea467c34d1 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 20 May 2026 13:17:57 +0100 Subject: [PATCH 13/20] Remove EF download from NFG games --- build_support/catalog/update.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index 86e17e120..2ed5cabea 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -53,12 +53,19 @@ def generate_rst_table(df: pd.DataFrame, rst_path: Path, regenerate_images: bool description = str(row.get("Description", "")).strip() # Skip any games which lack a description if description: + all_exts = [] + all_paths = [] + if row["Format"] == "efg": + ef_path = CATALOG_DIR / "img" / f"{slug}.ef" + all_exts.append("ef") + all_paths.append(ef_path) + all_exts = all_exts + ["tex", "png", "pdf", "svg"] tex_path = CATALOG_DIR / "img" / f"{slug}.tex" - png_path = CATALOG_DIR / "img" / f"{slug}.png" - pdf_path = CATALOG_DIR / "img" / f"{slug}.pdf" - ef_path = CATALOG_DIR / "img" / f"{slug}.ef" - - missing_any = not all(p.exists() for p in [tex_path, png_path, pdf_path, ef_path]) + all_paths.append(tex_path) + all_paths.append(CATALOG_DIR / "img" / f"{slug}.png") + all_paths.append(CATALOG_DIR / "img" / f"{slug}.pdf") + all_paths.append(CATALOG_DIR / "img" / f"{slug}.svg") + missing_any = not all(p.exists() for p in all_paths) if regenerate_images or missing_any: g = gbt.catalog.load(slug) @@ -100,7 +107,7 @@ def generate_rst_table(df: pd.DataFrame, rst_path: Path, regenerate_images: bool # Download links download_links = [row["Download"]] - for ext in ["ef", "tex", "png", "pdf", "svg"]: + for ext in all_exts: download_links.append( f":download:`{slug}.{ext} <../catalog/img/{slug}.{ext}>`" ) From d82b87fead2aa6f01fc2e4a128242c3677357224 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 20 May 2026 13:19:33 +0100 Subject: [PATCH 14/20] build: upgrade draw-tree dependency to v0.9.0 across documentation and CI pipelines --- .github/workflows/python.yml | 8 ++++---- .readthedocs.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index df04d9957..f380bff25 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -30,7 +30,7 @@ jobs: cd dist sdist=$(ls pygambit-*.tar.gz) pip install -v "${sdist}[test,doc]" - pip install "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.6.1" + pip install "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.9.0" - name: Run tests run: pytest --run-tutorials @@ -53,7 +53,7 @@ jobs: - name: Build extension run: | python -m pip install -v .[test,doc] - pip install "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.6.1" + pip install "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.9.0" - name: Run tests run: pytest --run-tutorials @@ -76,7 +76,7 @@ jobs: - name: Build extension run: | python -m pip install -v .[test,doc] - pip install "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.6.1" + pip install "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.9.0" - name: Run tests run: pytest --run-tutorials @@ -99,6 +99,6 @@ jobs: - name: Build extension run: | python -m pip install -v .[test,doc] - pip install "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.6.1" + pip install "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.9.0" - name: Run tests run: pytest --run-tutorials diff --git a/.readthedocs.yml b/.readthedocs.yml index 4f24ce64f..3052576c6 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -16,7 +16,7 @@ build: - imagemagick jobs: post_install: - - pip install "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.6.1" + - pip install "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.9.0" # Create RST for catalog table in docs - $READTHEDOCS_VIRTUALENV_PATH/bin/python build_support/catalog/update.py From 756840aadcd300385949f0bfdee37d5e3458c5d5 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 20 May 2026 13:21:50 +0100 Subject: [PATCH 15/20] remove tikz graphic for nfgs --- build_support/catalog/update.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index 2ed5cabea..d2915e732 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -117,12 +117,13 @@ def generate_rst_table(df: pd.DataFrame, rst_path: Path, regenerate_images: bool f.write(" \n") # TiKZ image - f.write(" .. tikz::\n") - f.write(" :align: center\n") - f.write(" \n") - for line in tikz.splitlines(): - f.write(f" {line}\n") - f.write(" \n") + if row["Format"] == "efg": + f.write(" .. tikz::\n") + f.write(" :align: center\n") + f.write(" \n") + for line in tikz.splitlines(): + f.write(f" {line}\n") + f.write(" \n") def update_makefile(): From 8f32f1e573eeacd9131d7e8d0240e7073ffefe3c Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 20 May 2026 13:40:06 +0100 Subject: [PATCH 16/20] refactor: unify PyGambit loading code and move jupyter-execute visualization for nfg files to end --- build_support/catalog/update.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index d2915e732..707d4866e 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -93,17 +93,10 @@ def generate_rst_table(df: pd.DataFrame, rst_path: Path, regenerate_images: bool f.write(" \n") f.write(" **Load in PyGambit:**\n") f.write(" \n") - if row["Format"] == "efg": - f.write(" .. code-block:: python\n") - f.write(" \n") - f.write(f' pygambit.catalog.load("{slug}")\n') - f.write(" \n") - elif row["Format"] == "nfg": - f.write(" .. jupyter-execute::\n") - f.write(" \n") - f.write(" import pygambit\n") - f.write(f' pygambit.catalog.load("{slug}")\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 download_links = [row["Download"]] @@ -124,6 +117,14 @@ def generate_rst_table(df: pd.DataFrame, rst_path: Path, regenerate_images: bool for line in tikz.splitlines(): f.write(f" {line}\n") f.write(" \n") + elif row["Format"] == "nfg": + f.write(" .. jupyter-execute::\n") + f.write(" :hide-code:\n") + f.write(" \n") + f.write(" import pygambit\n") + f.write(" from draw_tree import draw_tree\n") + f.write(f' draw_tree(pygambit.catalog.load("{slug}"))\n') + f.write(" \n") def update_makefile(): From 0110d1b816405a8ca7d815822d1ccdbf18195de6 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 20 May 2026 14:00:53 +0100 Subject: [PATCH 17/20] Use jupyter to generate EFG as well as NFG images --- build_support/catalog/update.py | 39 ++++++++++++--------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index 707d4866e..b7d8c5d40 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -1,5 +1,4 @@ import argparse -import re from pathlib import Path import pandas as pd @@ -32,8 +31,6 @@ def catalog_draw_tree_settings(slug: str) -> dict: def generate_rst_table(df: pd.DataFrame, rst_path: Path, regenerate_images: bool = False): """Generate RST output with a list-table for games.""" - tikz_re = re.compile(r"\\begin\{document\}(.*?)\\end\{document\}", re.DOTALL) - with open(rst_path, "w", encoding="utf-8") as f: # TOC linking to both sections f.write(".. contents::\n") @@ -74,15 +71,6 @@ def generate_rst_table(df: pd.DataFrame, rst_path: Path, regenerate_images: bool for func in [generate_tex, generate_png, generate_pdf, generate_svg]: func(g, save_to=str(viz_path), **catalog_draw_tree_settings(slug)) - with open(tex_path, encoding="utf-8") as tex_f: - tex_content = tex_f.read() - match = tikz_re.search(tex_content) - tikz = ( - match.group(1).strip() - if match - else "% Could not extract tikzpicture from tex file" - ) - # Main dropdown f.write(f" * - .. dropdown:: {title}\n") f.write(" :open:\n") @@ -109,22 +97,23 @@ def generate_rst_table(df: pd.DataFrame, rst_path: Path, regenerate_images: bool f.write(f" {' '.join(download_links)}\n") f.write(" \n") - # TiKZ image + # Draw image + f.write(" .. jupyter-execute::\n") + f.write(" :hide-code:\n") + f.write(" \n") + f.write(" import pygambit\n") + f.write(" from draw_tree import draw_tree\n") if row["Format"] == "efg": - f.write(" .. tikz::\n") - f.write(" :align: center\n") - f.write(" \n") - for line in tikz.splitlines(): - f.write(f" {line}\n") - f.write(" \n") + settings = catalog_draw_tree_settings(slug) + settings_str = ", ".join(f"{k}={v!r}" for k, v in settings.items()) + f.write( + f" draw_tree(" + f'pygambit.catalog.load("{slug}"), ' + f"{settings_str})\n" + ) elif row["Format"] == "nfg": - f.write(" .. jupyter-execute::\n") - f.write(" :hide-code:\n") - f.write(" \n") - f.write(" import pygambit\n") - f.write(" from draw_tree import draw_tree\n") f.write(f' draw_tree(pygambit.catalog.load("{slug}"))\n') - f.write(" \n") + f.write(" \n") def update_makefile(): From 99f77ba1d58dd2e919936bb8be17b52025223aef Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 20 May 2026 14:02:47 +0100 Subject: [PATCH 18/20] Include pdf2svg dependency in Read the Docs configuration --- .readthedocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index 3052576c6..16e06bb4c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -14,6 +14,7 @@ build: - pandoc - texlive-full - imagemagick + - pdf2svg jobs: post_install: - pip install "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.9.0" From 124d0f4c38d4caec04fa150797387631692e2a52 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 20 May 2026 14:18:23 +0100 Subject: [PATCH 19/20] feat: update nfg tree generation to save output images to the catalog directory --- build_support/catalog/update.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index b7d8c5d40..0d44b3cc6 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -112,7 +112,11 @@ def generate_rst_table(df: pd.DataFrame, rst_path: Path, regenerate_images: bool f"{settings_str})\n" ) elif row["Format"] == "nfg": - f.write(f' draw_tree(pygambit.catalog.load("{slug}"))\n') + f.write( + f" draw_tree(" + f'pygambit.catalog.load("{slug}"), ' + f'save_to="../catalog/img/{slug}.png")\n' + ) f.write(" \n") From de85e1f495a2d9fe587f8568367318f635fa95a0 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 20 May 2026 14:54:51 +0100 Subject: [PATCH 20/20] Update nfg example games with player labels --- catalog/nau2004/sec3.nfg | 2 +- catalog/nau2004/sec4.nfg | 2 +- catalog/nau2004/sec5.nfg | 2 +- catalog/nau2004/sec6.nfg | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/catalog/nau2004/sec3.nfg b/catalog/nau2004/sec3.nfg index c7b2c8470..bc203d6b2 100644 --- a/catalog/nau2004/sec3.nfg +++ b/catalog/nau2004/sec3.nfg @@ -1,4 +1,4 @@ -NFG 1 R "Battle of the Sexes" { "Row" "Column" } +NFG 1 R "Battle of the Sexes" { "Player 1" "Player 2" } { { "Top" "Bottom" } { "Left" "Right" } diff --git a/catalog/nau2004/sec4.nfg b/catalog/nau2004/sec4.nfg index f0bd9a918..57b754b6f 100644 --- a/catalog/nau2004/sec4.nfg +++ b/catalog/nau2004/sec4.nfg @@ -1,4 +1,4 @@ -NFG 1 R "Three-player game with a unique Nash solution in irrational strategies" { "Row" "Column" "Matrix" } +NFG 1 R "Three-player game with a unique Nash solution in irrational strategies" { "Player 1" "Player 2" "Player 3" } { { "Top" "Bottom" } { "Left" "Right" } diff --git a/catalog/nau2004/sec5.nfg b/catalog/nau2004/sec5.nfg index 3fcdfbeb9..fa9f40d53 100644 --- a/catalog/nau2004/sec5.nfg +++ b/catalog/nau2004/sec5.nfg @@ -1,4 +1,4 @@ -NFG 1 R "Game with a continuum of completely mixed-strategy Nash equilibria" { "Row" "Column" "Matrix" } +NFG 1 R "Game with a continuum of completely mixed-strategy Nash equilibria" { "Player 1" "Player 2" "Player 3" } { { "Top" "Bottom" } { "Left" "Right" } diff --git a/catalog/nau2004/sec6.nfg b/catalog/nau2004/sec6.nfg index 2a107c1d5..bb378500a 100644 --- a/catalog/nau2004/sec6.nfg +++ b/catalog/nau2004/sec6.nfg @@ -1,4 +1,4 @@ -NFG 1 R "2x2x4 game with Nash equilibria in the relative interior of the correlated equilibrium polytope" { "Row" "Column" "Matrix" } +NFG 1 R "2x2x4 game with Nash equilibria in the relative interior of the correlated equilibrium polytope" { "Player 1" "Player 2" "Player 3" } { { "Top" "Bottom" } { "Left" "Right" }