Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a4dc1e2
Pin sphinx>=7.0
edwardchalstrey1 May 18, 2026
260a047
Add nau2004 to catalog
edwardchalstrey1 May 18, 2026
96568d7
uncomment NFG catalog parts of update script
edwardchalstrey1 May 18, 2026
8c94c4e
rename
edwardchalstrey1 May 18, 2026
c5b8e0f
Add jupyter-sphinx to enable code execution in catalog page
edwardchalstrey1 May 18, 2026
ba2419d
Combine EFG and NFG tables into a single RST table
edwardchalstrey1 May 18, 2026
13962d1
Add Nau2004 biblio entry
edwardchalstrey1 May 18, 2026
043629e
Update the game file link
edwardchalstrey1 May 18, 2026
9833a40
Add Nau2004 game catalog entries
edwardchalstrey1 May 19, 2026
fecf40b
remove original nau file
edwardchalstrey1 May 19, 2026
d97fa25
Merge branch 'master' into add-example-nfg-catalog
edwardchalstrey1 May 20, 2026
d123df8
refactor: encapsulate tikz_re inside generate_rst_table and expose re…
edwardchalstrey1 May 20, 2026
49d5c80
Ignore generated SVGs
edwardchalstrey1 May 20, 2026
63956c0
Remove EF download from NFG games
edwardchalstrey1 May 20, 2026
d82b87f
build: upgrade draw-tree dependency to v0.9.0 across documentation an…
edwardchalstrey1 May 20, 2026
756840a
remove tikz graphic for nfgs
edwardchalstrey1 May 20, 2026
8f32f1e
refactor: unify PyGambit loading code and move jupyter-execute visual…
edwardchalstrey1 May 20, 2026
0110d1b
Use jupyter to generate EFG as well as NFG images
edwardchalstrey1 May 20, 2026
99f77ba
Include pdf2svg dependency in Read the Docs configuration
edwardchalstrey1 May 20, 2026
124d0f4
feat: update nfg tree generation to save output images to the catalog…
edwardchalstrey1 May 20, 2026
de85e1f
Update nfg example games with player labels
edwardchalstrey1 May 20, 2026
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
8 changes: 4 additions & 4 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ catalog/img/**/*.pdf
catalog/img/**/*.png
catalog/img/**/*.tex
catalog/img/**/*.ef
catalog/img/**/*.svg
3 changes: 2 additions & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ build:
- pandoc
- texlive-full
- imagemagick
- pdf2svg
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

Expand Down
4 changes: 4 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ EXTRA_DIST = \
catalog/jakobsen2016/fig3.efg \
catalog/myerson1991/fig2_1.efg \
catalog/myerson1991/fig4_2.efg \
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 \
Expand Down
208 changes: 87 additions & 121 deletions build_support/catalog/update.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import argparse
import re
from pathlib import Path

import pandas as pd
from draw_tree import generate_pdf, generate_png, generate_tex
from draw_tree import generate_pdf, generate_png, generate_svg, generate_tex

import pygambit as gbt

Expand All @@ -30,128 +29,95 @@ def catalog_draw_tree_settings(slug: str) -> dict:
return settings


def _write_efg_table(df: pd.DataFrame, f, tikz_re, regenerate_images: bool):
"""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()
if description:
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])

if regenerate_images or missing_any:
g = gbt.catalog.load(slug)
viz_path = CATALOG_DIR / "img" / f"{slug}"
viz_path.parent.mkdir(parents=True, exist_ok=True)
for func in [generate_tex, generate_png, generate_pdf]:
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(" \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
download_links = [row["Download"]]
for ext in ["ef", "tex", "png", "pdf"]:
download_links.append(f":download:`{slug}.{ext} <../catalog/img/{slug}.{ext}>`")
f.write(" **Download game and image files:**\n")
f.write(" \n")
f.write(f" {' '.join(download_links)}\n")
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")


# 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, regenerate_images: bool = False):
"""Generate RST output with two list-tables: one for EFG and one for NFG games."""
tikz_re = re.compile(r"\\begin\{document\}(.*?)\\end\{document\}", re.DOTALL)

"""Generate RST output with a list-table for 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, tikz_re, regenerate_images)
# f.write("\n")

# # NFG section
# f.write("Strategic form games\n")
# f.write("--------------------\n")
# f.write("\n")
# _write_nfg_table(df, f)
f.write(".. contents::\n")
f.write(" :local:\n")
f.write(" :depth: 1\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")
f.write(" * - **Catalog of 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:
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"
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)
viz_path = CATALOG_DIR / "img" / f"{slug}"
viz_path.parent.mkdir(parents=True, exist_ok=True)
for func in [generate_tex, generate_png, generate_pdf, generate_svg]:
func(g, save_to=str(viz_path), **catalog_draw_tree_settings(slug))

# 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
download_links = [row["Download"]]
for ext in all_exts:
download_links.append(
f":download:`{slug}.{ext} <../catalog/img/{slug}.{ext}>`"
)
f.write(" **Download game and image files:**\n")
f.write(" \n")
f.write(f" {' '.join(download_links)}\n")
f.write(" \n")

# 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":
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(
f" draw_tree("
f'pygambit.catalog.load("{slug}"), '
f'save_to="../catalog/img/{slug}.png")\n'
)
f.write(" \n")


def update_makefile():
Expand Down
14 changes: 14 additions & 0 deletions catalog/nau2004/sec3.nfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
NFG 1 R "Battle of the Sexes" { "Player 1" "Player 2" }

{ { "Top" "Bottom" }
{ "Left" "Right" }
}
"The coordination game known as Battle of the Sexes (section 3 of `Nau2004 <https://gambitproject.readthedocs.io/en/latest/biblio.html#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
19 changes: 19 additions & 0 deletions catalog/nau2004/sec4.nfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
NFG 1 R "Three-player game with a unique Nash solution in irrational strategies" { "Player 1" "Player 2" "Player 3" }

{ { "Top" "Bottom" }
{ "Left" "Right" }
{ "1" "2" }
}
"A three-player game with a unique Nash equilibrium in irrational mixed strategies (section 4 of `Nau2004 <https://gambitproject.readthedocs.io/en/latest/biblio.html#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
19 changes: 19 additions & 0 deletions catalog/nau2004/sec5.nfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
NFG 1 R "Game with a continuum of completely mixed-strategy Nash equilibria" { "Player 1" "Player 2" "Player 3" }

{ { "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 <https://gambitproject.readthedocs.io/en/latest/biblio.html#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
27 changes: 27 additions & 0 deletions catalog/nau2004/sec6.nfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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" }
{ "1" "2" "3" "4" }
}
"A three-player 2x2x4 game (section 6 of `Nau2004 <https://gambitproject.readthedocs.io/en/latest/biblio.html#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
24 changes: 0 additions & 24 deletions contrib/games/2x2x2-nau.nfg

This file was deleted.

Loading
Loading