Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
dc810b7
Added `Game.get_histories`
tturocy Sep 4, 2026
d296f60
Remove prior_sibling/next_sibling
tturocy Sep 4, 2026
4b8b610
Simplify tests so as not to need Node
tturocy Sep 4, 2026
e8041ef
Reorganise test suite around function
tturocy Sep 4, 2026
58f43fa
Remove obsolete Node tests
tturocy Sep 4, 2026
12aa697
Implement `Game.get_player`
tturocy Sep 4, 2026
6b1ab8a
Remove redundant `Node.is_terminal`
tturocy Sep 4, 2026
b52baaa
Remove plays from public node interface
tturocy Sep 4, 2026
81fcd47
Remove plays from node
tturocy Sep 4, 2026
3cccb79
Implement .get_action_probs() and .get_members()
tturocy Sep 4, 2026
6f3298e
Implement `Game.get_outcome` on selector
tturocy Sep 4, 2026
ac13604
`get_infosets` and `get_events` returns histories not nodes
tturocy Sep 4, 2026
b648a47
Simplify subgames API
tturocy Sep 5, 2026
22d75cf
Migrate `layout_tree` to use histories
tturocy Sep 5, 2026
5823ae8
Remove `Game.root` from public API
tturocy Sep 5, 2026
1d3e2dc
Remove unneeded members from Node
tturocy Sep 5, 2026
3ec960d
Remove tests that unnecessarily manipulate Node
tturocy Sep 5, 2026
8b736cc
Add `Game.get_strategy_unreachable`
tturocy Sep 5, 2026
4f28e95
Remove obsolete `Node` accessors
tturocy Sep 5, 2026
df82d0a
Remove GameOutcome and Node.outcome, standardise get_outcome return
tturocy Sep 5, 2026
210cc4f
Remove obsolete node tests
tturocy Sep 5, 2026
509ffee
Remove Node from behaviour support profile
tturocy Sep 5, 2026
381a792
Final closure of Node public interface
tturocy Sep 5, 2026
801ac79
Final closure of Node public interface
tturocy Sep 5, 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
40 changes: 13 additions & 27 deletions doc/pygambit.api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ Representation of games
Game
Outcome
Node
Branch
Subgame
TreeLayout
TreeLayoutCoordinates


Creating, reading, and writing games
Expand All @@ -43,6 +43,15 @@ Creating, reading, and writing games
Game.to_latex


Computing a tree layout for graphical display
..............................................

.. autosummary::
:toctree: api/

layout_tree


Transforming game trees
.......................

Expand Down Expand Up @@ -106,17 +115,16 @@ Information about the game
Game.max_payoff
Game.get_min_payoff
Game.get_max_payoff
Game.root
Game.get_infosets
Game.get_events
Game.get_strategies
Game.get_sequences
Game.nodes
Game.contingencies
Game.get_outcome
Game.get_payoffs
Game.subgames
Game.get_subgame_roots
Game.get_minimal_subgame
Game.get_strategy_unreachable

.. autosummary::
:toctree: api/
Expand All @@ -128,32 +136,10 @@ Information about the game
.. autosummary::
:toctree: api/

Node.label
Node.game
Node.outcome
Node.children
Node.parent
Node.is_subgame_root
Node.is_terminal
Node.is_strategy_reachable
Node.prior_action
Node.prior_sibling
Node.next_sibling
Node.members
Node.actions
Node.action_probs
Node.player
Node.is_successor_of
Node.plays
Node.own_prior_action

.. autosummary::
:toctree: api/

Subgame.game
Subgame.root
Subgame.parent
Subgame.children

.. autosummary::

Expand Down
80 changes: 40 additions & 40 deletions doc/tutorials/03_stripped_down_poker.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -371,26 +371,7 @@
"id": "9c1f6a2e",
"metadata": {},
"source": [
"Indexing the profile directly by a `Node` requires identifying it with a `Selector`, since `MixedBehaviorProfile.__getitem__` accepts only a player label or a `Selector`, not a bare `Node`.\n",
"\n",
"A `Node`'s own path of action labels back to the root builds exactly the `Selector` that matches it:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7c1e8a4",
"metadata": {},
"outputs": [],
"source": [
"def selector_for_node(node):\n",
" labels = []\n",
" current = node\n",
" while current.parent is not None:\n",
" labels.append(current.prior_action.label)\n",
" current = current.parent\n",
" labels.reverse()\n",
" return gbt.H.path(*labels)"
"`Game.get_infosets` returns each information set's canonical member as a `History` -- a plain tuple of action labels from the root. Indexing the profile directly by that tuple requires turning it into a `Selector` first, since `MixedBehaviorProfile.__getitem__` accepts only a player label or a `Selector`:"
]
},
{
Expand All @@ -401,10 +382,10 @@
"outputs": [],
"source": [
"for number, node in enumerate(g.get_infosets(\"Alice\"), start=1):\n",
" for action in node.actions:\n",
" for action in g.get_actions(gbt.H.path(*node)):\n",
" print(\n",
" f\"At information set {number}, \"\n",
" f\"Alice plays {action} with probability: {eqm[selector_for_node(node)][action]}\"\n",
" f\"Alice plays {action} with probability: {eqm[gbt.H.path(*node)][action]}\"\n",
" )"
]
},
Expand Down Expand Up @@ -443,7 +424,10 @@
"id": "2966e700",
"metadata": {},
"outputs": [],
"source": "(bob_node,) = g.get_infosets(\"Bob\")\neqm[selector_for_node(bob_node)][\"Call\"]"
"source": [
"(bob_node,) = g.get_infosets(\"Bob\")\n",
"eqm[gbt.H.path(*bob_node)][\"Call\"]"
]
},
{
"cell_type": "markdown",
Expand All @@ -462,8 +446,8 @@
"metadata": {},
"outputs": [],
"source": [
"bob_action_values = eqm.action_values[bob_node]\n",
"for action in bob_node.actions:\n",
"bob_action_values = eqm.action_values[gbt.H.path(*bob_node)]\n",
"for action in g.get_actions(gbt.H.path(*bob_node)):\n",
" print(\n",
" f\"When Bob plays {action} his expected payoff is {bob_action_values[action]}\"\n",
" )"
Expand All @@ -478,7 +462,7 @@
"\n",
"`MixedBehaviorProfile.beliefs` returns the probability of reaching each node, conditional on its information set being reached.\n",
"\n",
"Recall that the two nodes in Bob's only information set are `g.root.children[\"King\"].children[\"Bet\"]` and `g.root.children[\"Queen\"].children[\"Bet\"]`):"
"Recall that the two nodes in Bob's only information set are `(\"King\", \"Bet\")` and `(\"Queen\", \"Bet\")` (as Histories):"
]
},
{
Expand All @@ -488,10 +472,10 @@
"metadata": {},
"outputs": [],
"source": [
"for node in bob_node.members:\n",
"for node in g.get_members(gbt.H.path(*bob_node)):\n",
" print(\n",
" f\"Bob's belief in reaching the {node.parent.prior_action.label} -> \"\n",
" f\"{node.prior_action.label} node is: {eqm.beliefs[node]}\"\n",
" f\"Bob's belief in reaching the {node[-2]} -> \"\n",
" f\"{node[-1]} node is: {eqm.beliefs[node]}\"\n",
" )"
]
},
Expand All @@ -512,7 +496,7 @@
"metadata": {},
"outputs": [],
"source": [
"eqm.infoset_probs[bob_node]"
"eqm.infoset_probs[gbt.H.path(*bob_node)]"
]
},
{
Expand All @@ -531,10 +515,10 @@
"outputs": [],
"source": [
"bob_node_values = eqm.node_values[\"Bob\"]\n",
"for node in bob_node.members:\n",
"for node in g.get_members(gbt.H.path(*bob_node)):\n",
" print(\n",
" f\"The probability that the node {node.parent.prior_action.label} -> \"\n",
" f\"{node.prior_action.label} is reached is: {eqm.realiz_probs[node]}. \",\n",
" f\"The probability that the node {node[-2]} -> \"\n",
" f\"{node[-1]} is reached is: {eqm.realiz_probs[node]}. \",\n",
" f\"Bob's expected payoff conditional on reaching this node is {bob_node_values[node]}\"\n",
" )"
]
Expand Down Expand Up @@ -669,7 +653,23 @@
"id": "d18a91f0",
"metadata": {},
"outputs": [],
"source": "for player in g.players:\n print(\n f\"{player}'s expected payoffs:\"\n )\n gnm_action_values = gnm_eqm.as_behavior().action_values\n lcp_action_values = eqm.action_values\n for number, node in enumerate(g.get_infosets(player), start=1):\n for action in node.actions:\n print(\n f\"At information set {number}, \"\n f\"when playing {action} - \"\n f\"gnm: {gnm_action_values[node][action]:.4f}\"\n f\", lcp: {str(lcp_action_values[node][action])}\"\n )\n print()"
"source": [
"for player in g.players:\n",
" print(\n",
" f\"{player}'s expected payoffs:\"\n",
" )\n",
" gnm_action_values = gnm_eqm.as_behavior().action_values\n",
" lcp_action_values = eqm.action_values\n",
" for number, node in enumerate(g.get_infosets(player), start=1):\n",
" for action in g.get_actions(gbt.H.path(*node)):\n",
" print(\n",
" f\"At information set {number}, \"\n",
" f\"when playing {action} - \"\n",
" f\"gnm: {gnm_action_values[gbt.H.path(*node)][action]:.4f}\"\n",
" f\", lcp: {str(lcp_action_values[gbt.H.path(*node)][action])}\"\n",
" )\n",
" print()"
]
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -877,7 +877,7 @@
"source": [
"small_game = gbt.Game.new_tree()\n",
"small_game.append_event(gbt.H.path(), dict.fromkeys([\"a\", \"b\", \"c\"], gbt.Rational(1, 3)))\n",
"list(small_game.root.action_probs.values())"
"list(small_game.get_action_probs(gbt.H.path()).values())"
]
},
{
Expand All @@ -897,7 +897,7 @@
" gbt.H.path(),\n",
" {\"a\": gbt.Rational(1, 4), \"b\": gbt.Rational(1, 2), \"c\": gbt.Rational(1, 4)}\n",
")\n",
"list(small_game.root.action_probs.values())"
"list(small_game.get_action_probs(gbt.H.path()).values())"
]
},
{
Expand All @@ -919,7 +919,7 @@
" gbt.H.path(),\n",
" {\"a\": gbt.Decimal(\".25\"), \"b\": gbt.Decimal(\".50\"), \"c\": gbt.Decimal(\".25\")}\n",
")\n",
"list(small_game.root.action_probs.values())"
"list(small_game.get_action_probs(gbt.H.path()).values())"
]
},
{
Expand All @@ -942,7 +942,7 @@
"outputs": [],
"source": [
"small_game.make_event(gbt.H.path(), {\"a\": \"1/4\", \"b\": \"1/2\", \"c\": \"1/4\"})\n",
"list(small_game.root.action_probs.values())"
"list(small_game.get_action_probs(gbt.H.path()).values())"
]
},
{
Expand All @@ -953,7 +953,7 @@
"outputs": [],
"source": [
"small_game.make_event(gbt.H.path(), {\"a\": \".25\", \"b\": \".50\", \"c\": \".25\"})\n",
"list(small_game.root.action_probs.values())"
"list(small_game.get_action_probs(gbt.H.path()).values())"
]
},
{
Expand All @@ -977,7 +977,7 @@
"outputs": [],
"source": [
"small_game.make_event(gbt.H.path(), {\"a\": .25, \"b\": .50, \"c\": .25})\n",
"list(small_game.root.action_probs.values())"
"list(small_game.get_action_probs(gbt.H.path()).values())"
]
},
{
Expand Down
Loading
Loading