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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
## [17.0.0-beta.1] - unreleased

### Added
- Added `Game.get_infosets(player)`, returning a materialized list of one representative
`Node` per information set belonging to `player`, replacing `Player.infosets`/`Game.infosets`.
`player` must be a personal player; use `Game.get_events()` for the chance player's events.
- Added `Game.get_events()`, returning a materialized list of one representative `Node` per
chance event in the game.
- Added `Event`, the chance-event counterpart to `Infoset`: a lazy, node-anchored view of the
chance player's information partition. Added `Node.event` alongside the existing
`Node.infoset`, which is now specific to personal players' information sets; each is falsy
when it does not apply to a given node.
- Added `Node.members`, the set of nodes belonging to the same information set or event as `Node`,
equivalent to `Node.infoset.members`/`Node.event.members`, whichever applies.
- Added `Node.actions`, the labels of the actions available at the node's current information
set or event, whichever applies (equivalent to `Node.infoset.actions`/`Node.event.actions`).
- Added `Node.action_probs`, the probability of each action at the node's current chance event,
keyed by label.
- Added `Branch`, a `(node, label)` pair returned by `Node.prior_action`/`Node.own_prior_action`,
replacing `Action`.
- Added `BehaviorSupportProfile` to `pygambit`.
- Added callback functions to Nash solvers, allowing calling code to take actions when an equilibrium
is found or another solver event is emitted.
Expand All @@ -17,6 +34,22 @@
contingency, for a game in any representation.

### Changed
- `Infoset` is now a lazy, node-anchored view (like `Node.player`/`Node.outcome`), constructed
by anchoring on a representative member node rather than carrying independent identity, and is
now specific to personal players; see `Event` for the chance player. `Infoset.is_chance` has
been removed, as `Infoset` is never chance; test truthiness of `Node.infoset`/`Node.event`
instead.
`Game.append_infoset`, `Game.insert_infoset`, `Game.set_move_actions`, `Game.set_event_actions`,
`Game.relabel_actions`, `Game.reveal`, and `Game.minimal_subgame` now identify an information
set or event via a member `Node` (or its label) rather than an `Infoset` object.
- `Infoset.actions`/`Event.actions`/`Node.actions` now return the plain labels (`list[str]`) of
the actions at the information set or event, rather than `Action` objects.
- `Sequence.actions` now returns a `tuple[str, ...]` of labels rather than `Action` objects.
- `Node.prior_action`/`Node.own_prior_action` now return a `Branch` (a `(node, label)` pair)
rather than an `Action`.
- `Game.behavior_support_profile`'s `actions` filter callable is now called as
`actions(node, action)` (two positional arguments, where `node` is a representative node of
the information set and `action` is the action's label) rather than with a single `Action`.
- Command-line tools are now implemented in `pygambit` rather than C++-based compiled programs.
The `gambit-convert` utility has been removed.
- Refreshed GUI icons and standardised dialog layout and formatting
Expand Down Expand Up @@ -47,8 +80,6 @@
- `Game.get_behavior`'s `player` and `strategy` parameters are now labels (`str`) rather than
accepting a `Player`/`Strategy` object; `StrategyBehavior.player`/`.strategy` now return
labels rather than `Player`/`Strategy` objects.

### Removed
- Removed `Game.__getitem__`, replaced by `Game.get_outcome`/`Game.get_payoffs`.
- Removed `Game.strategies`; iterate `Game.players` and each player's `strategies` instead.
- Removed `Strategy.action`; use `Game.get_behavior(player, strategy)[infoset]` (or `.get`),
Expand All @@ -57,6 +88,17 @@
`Player.strategies`; a `Strategy` object carried no information a label didn't already
carry, now that strategy labels are unique within a player. `GameStrategyRep` in the C++
core is unaffected by this change (a separate, later piece of work).
- Removed `Player.infosets` and `Game.infosets`; use `Game.get_infosets(player)` instead.
- Removed `Game.actions`/`Player.actions`; the total count/collection of actions across a
game or player is now most directly available by iterating `Game.get_infosets(player)` and
each node's `Node.actions`.
- Removed `Action`. Actions are now identified purely by label (`str`), as returned by
`Node.actions`/`Infoset.actions`/`Event.actions`; use `Node.action_probs` for chance-event
action probabilities, previously `Action.prob`, and `Node.prior_action`/`Node.own_prior_action`'s
`Branch` in place of an `Action` reached via a node. `StrategyBehavior`'s prescribed actions
are likewise now labels rather than `Action` objects. Also removed
`Infoset.own_prior_actions`/`Infoset.plays` (and their `Event` mirrors), subsumed by the
existing `Node.own_prior_action`/`Node.plays`.

## [17.0.0-alpha.2] - 2026-08-21

Expand Down
28 changes: 15 additions & 13 deletions doc/pygambit.api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ Representation of games
Outcome
Node
Infoset
Action
Event
Branch
Subgame


Expand Down Expand Up @@ -109,8 +110,8 @@ Information about the game
Game.min_payoff
Game.max_payoff
Game.root
Game.actions
Game.infosets
Game.get_infosets
Game.get_events
Game.nodes
Game.contingencies
Game.get_outcome
Expand All @@ -125,8 +126,6 @@ Information about the game
Player.number
Player.game
Player.strategies
Player.infosets
Player.actions
Player.is_chance
Player.min_payoff
Player.max_payoff
Expand Down Expand Up @@ -154,6 +153,10 @@ Information about the game
Node.prior_sibling
Node.next_sibling
Node.infoset
Node.event
Node.members
Node.actions
Node.action_probs
Node.player
Node.is_successor_of
Node.plays
Expand All @@ -173,24 +176,23 @@ Information about the game

Infoset.label
Infoset.game
Infoset.is_chance
Infoset.is_absent_minded
Infoset.player
Infoset.actions
Infoset.members
Infoset.precedes
Infoset.plays
Infoset.own_prior_actions

.. autosummary::

:toctree: api/

Action.label
Action.infoset
Action.precedes
Action.prob
Action.plays
Event.label
Event.game
Event.is_absent_minded
Event.player
Event.actions
Event.members
Event.precedes

.. autosummary::

Expand Down
79 changes: 50 additions & 29 deletions doc/tutorials/03_stripped_down_poker.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@
"source": [
"The loop above causes each of the newly-appended moves to be in new information sets, reflecting the fact that Alice's decision depends on the knowledge of which card she holds.\n",
"\n",
"In contrast, Bob does not know Alice’s card, and therefore cannot distinguish between the two nodes at which he has to make his decision:\n",
"In contrast, Bob does not know Alice\u2019s card, and therefore cannot distinguish between the two nodes at which he has to make his decision:\n",
"\n",
" - Chance player chooses King, then Alice Bets: `g.root.children[\"King\"].children[\"Bet\"]`\n",
" - Chance player chooses Queen, then Alice Bets: `g.root.children[\"Queen\"].children[\"Bet\"]`\n",
Expand Down Expand Up @@ -391,20 +391,20 @@
"metadata": {},
"outputs": [],
"source": [
"for action in g.players[\"Alice\"].actions:\n",
" node = next(iter(action.infoset.members))\n",
" print(\n",
" f\"At information set {action.infoset.number}, \"\n",
" f\"Alice plays {action.label} with probability: {eqm[node][action.label]}\"\n",
" )"
"for node in g.get_infosets(\"Alice\"):\n",
" for action in node.actions:\n",
" print(\n",
" f\"At information set {node.infoset.number}, \"\n",
" f\"Alice plays {action} with probability: {eqm[node][action]}\"\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "1f121d48",
"metadata": {},
"source": [
"Now let's look at Bob’s strategy:"
"Now let's look at Bob\u2019s strategy:"
]
},
{
Expand All @@ -422,7 +422,7 @@
"id": "e906c4c4",
"metadata": {},
"source": [
"Bob Calls Alice’s Bet two-thirds of the time.\n",
"Bob Calls Alice\u2019s Bet two-thirds of the time.\n",
"\n",
"Since Bob has just one information set, we can get its representative node and index\n",
"the profile directly by it to read off a single action's probability:"
Expand All @@ -434,11 +434,7 @@
"id": "2966e700",
"metadata": {},
"outputs": [],
"source": [
"(bob_infoset,) = g.players[\"Bob\"].infosets\n",
"bob_node = next(iter(bob_infoset.members))\n",
"eqm[bob_node][\"Call\"]"
]
"source": "(bob_node,) = g.get_infosets(\"Bob\")\nbob_infoset = bob_node.infoset\neqm[bob_node][\"Call\"]"
},
{
"cell_type": "markdown",
Expand All @@ -460,7 +456,7 @@
"bob_action_values = eqm.action_values[bob_node]\n",
"for action in bob_infoset.actions:\n",
" print(\n",
" f\"When Bob plays {action.label} his expected payoff is {bob_action_values[action.label]}\"\n",
" f\"When Bob plays {action} his expected payoff is {bob_action_values[action]}\"\n",
" )"
]
},
Expand Down Expand Up @@ -671,14 +667,14 @@
" )\n",
" gnm_action_values = gnm_eqm.as_behavior().action_values\n",
" lcp_action_values = eqm.action_values\n",
" for action in player.actions:\n",
" node = next(iter(action.infoset.members))\n",
" print(\n",
" f\"At information set {action.infoset.number}, \"\n",
" f\"when playing {action.label} - \"\n",
" f\"gnm: {gnm_action_values[node][action.label]:.4f}\"\n",
" f\", lcp: {str(lcp_action_values[node][action.label])}\"\n",
" )\n",
" for node in g.get_infosets(player.label):\n",
" for action in node.actions:\n",
" print(\n",
" f\"At information set {node.infoset.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()"
]
},
Expand Down Expand Up @@ -885,7 +881,11 @@
"id": "2f79695a",
"metadata": {},
"outputs": [],
"source": "small_game = gbt.Game.new_tree()\nsmall_game.append_event(small_game.root, [\"a\", \"b\", \"c\"], [gbt.Rational(1, 3)] * 3)\n[act.prob for act in small_game.root.infoset.actions]"
"source": [
"small_game = gbt.Game.new_tree()\n",
"small_game.append_event(small_game.root, [\"a\", \"b\", \"c\"], [gbt.Rational(1, 3)] * 3)\n",
"list(small_game.root.action_probs.values())"
]
},
{
"cell_type": "markdown",
Expand All @@ -899,7 +899,13 @@
"id": "5de6acb2",
"metadata": {},
"outputs": [],
"source": "small_game.make_event(\n [small_game.root],\n [gbt.Rational(1, 4), gbt.Rational(1, 2), gbt.Rational(1, 4)]\n)\n[act.prob for act in small_game.root.infoset.actions]"
"source": [
"small_game.make_event(\n",
" [small_game.root],\n",
" [gbt.Rational(1, 4), gbt.Rational(1, 2), gbt.Rational(1, 4)]\n",
")\n",
"list(small_game.root.action_probs.values())"
]
},
{
"cell_type": "markdown",
Expand All @@ -915,7 +921,13 @@
"id": "c47d2ab6",
"metadata": {},
"outputs": [],
"source": "small_game.make_event(\n [small_game.root],\n [gbt.Decimal(\".25\"), gbt.Decimal(\".50\"), gbt.Decimal(\".25\")]\n)\n[act.prob for act in small_game.root.infoset.actions]"
"source": [
"small_game.make_event(\n",
" [small_game.root],\n",
" [gbt.Decimal(\".25\"), gbt.Decimal(\".50\"), gbt.Decimal(\".25\")]\n",
")\n",
"list(small_game.root.action_probs.values())"
]
},
{
"cell_type": "markdown",
Expand All @@ -935,15 +947,21 @@
"id": "04329084",
"metadata": {},
"outputs": [],
"source": "small_game.make_event([small_game.root], [\"1/4\", \"1/2\", \"1/4\"])\n[act.prob for act in small_game.root.infoset.actions]"
"source": [
"small_game.make_event([small_game.root], [\"1/4\", \"1/2\", \"1/4\"])\n",
"list(small_game.root.action_probs.values())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9015e129",
"metadata": {},
"outputs": [],
"source": "small_game.make_event([small_game.root], [\".25\", \".50\", \".25\"])\n[act.prob for act in small_game.root.infoset.actions]"
"source": [
"small_game.make_event([small_game.root], [\".25\", \".50\", \".25\"])\n",
"list(small_game.root.action_probs.values())"
]
},
{
"cell_type": "markdown",
Expand All @@ -964,7 +982,10 @@
"id": "0a019aa5",
"metadata": {},
"outputs": [],
"source": "small_game.make_event([small_game.root], [.25, .50, .25])\n[act.prob for act in small_game.root.infoset.actions]"
"source": [
"small_game.make_event([small_game.root], [.25, .50, .25])\n",
"list(small_game.root.action_probs.values())"
]
},
{
"cell_type": "markdown",
Expand Down
Loading
Loading