Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a66993a
Sketch H selector algebra: path/plays ops, Game.get_nodes/get_histories
tturocy Sep 2, 2026
03ff0bf
Add H.after(*labels), the suffix-anchored counterpart to H.path
tturocy Sep 2, 2026
a6e724a
Add .by(callable) grouping, HistoryView facade, Game.get_groups
tturocy Sep 2, 2026
f4dcbb4
Add .filter(callable), the general predicate escape hatch
tturocy Sep 2, 2026
9d82d61
Wire H selectors into append_move/append_event mutation
tturocy Sep 2, 2026
73b4537
Wire H selectors into append_infoset/make_outcome; accept bare Histories
tturocy Sep 2, 2026
f55e1a0
Add .with_recall(player) and GroupedSelector elementwise chaining
tturocy Sep 2, 2026
ab58c6f
Add H selector algebra tutorial notebook, six worked examples
tturocy Sep 2, 2026
47de947
Fix CI failure: notebook was missing kernelspec metadata
tturocy Sep 2, 2026
6e75493
Document and test Selector/History support in make_outcome/make_outco…
tturocy Sep 3, 2026
2a5c303
Add HistoryView.members, replacing the need for Infoset.members/Event…
tturocy Sep 3, 2026
966ae02
Make H-selector machinery private
tturocy Sep 3, 2026
ba89f62
`append_move` only takes a selector
tturocy Sep 3, 2026
367930a
Update `append_event` signature
tturocy Sep 3, 2026
6505dfd
Update `insert_move` signature
tturocy Sep 3, 2026
6ba06d9
Update `append_infoset` signature
tturocy Sep 3, 2026
2fa665d
Update `insert_infoset` signature
tturocy Sep 3, 2026
b1c8896
Update `insert_move` signature
tturocy Sep 3, 2026
3238b68
Update copy/mode/delete signatures, make_event/make_infoset
tturocy Sep 3, 2026
1051bb6
Remove reveal from pygambit
tturocy Sep 3, 2026
efdd0f7
Migrate `get_minimal_subgame` to use selector.
tturocy Sep 3, 2026
0d589f4
Migrate `set_move_actions`/`set_event_actions`
tturocy Sep 4, 2026
3540876
Migrate `relabel_actions`
tturocy Sep 4, 2026
34eef76
Migrate `make_outcome`/`make_outcome_null`
tturocy Sep 4, 2026
58808f4
Remove `is_defined_at` and `NodeReference`/`NodeReferenceSet`
tturocy Sep 4, 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
4 changes: 1 addition & 3 deletions doc/pygambit.api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ Transforming game information structure
Game.relabel_actions
Game.set_move_actions
Game.set_event_actions
Game.reveal


Transforming game components
Expand Down Expand Up @@ -119,7 +118,7 @@ Information about the game
Game.get_outcome
Game.get_payoffs
Game.subgames
Game.minimal_subgame
Game.get_minimal_subgame

.. autosummary::
:toctree: api/
Expand Down Expand Up @@ -291,7 +290,6 @@ Probability distributions over behavior
MixedBehaviorProfile.realiz_probs
MixedBehaviorProfile.infoset_probs
MixedBehaviorProfile.beliefs
MixedBehaviorProfile.is_defined_at
MixedBehaviorProfile.agent_max_regret
MixedBehaviorProfile.agent_liap_value
MixedBehaviorProfile.max_regret
Expand Down
30 changes: 24 additions & 6 deletions doc/tutorials/02_extensive_form.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"id": "962b4e52",
"metadata": {},
"source": [
"To extend a game from an existing terminal node, use `Game.append_move`. To begin with, the sole root node is the terminal node.\n",
"To extend a game from an existing terminal node, use `Game.append_move`. `append_move` takes an `H`-built selector identifying the node(s) to add the move at, rather than a `Node` object directly; `gbt.H.path()` (with no arguments) selects the root itself, which to begin with is the sole terminal node.\n",
"\n",
"Here we extend the game from the root node by adding the first move for the \"Buyer\" player, creating two child nodes (one for each possible action)."
]
Expand All @@ -106,7 +106,7 @@
"outputs": [],
"source": [
"g.append_move(\n",
" g.root, # This is the node to append the move to\n",
" gbt.H.path(), # Selects the root node\n",
" player=\"Buyer\",\n",
" actions=[\"Trust\", \"Not trust\"]\n",
")"
Expand Down Expand Up @@ -138,7 +138,7 @@
"outputs": [],
"source": [
"g.append_move(\n",
" g.root.children[\"Trust\"],\n",
" gbt.H.path(\"Trust\"),\n",
" player=\"Seller\",\n",
" actions=[\"Honor\", \"Abuse\"]\n",
")"
Expand Down Expand Up @@ -172,7 +172,13 @@
"id": "716e9b9a",
"metadata": {},
"outputs": [],
"source": "g.make_outcome(\n g.root.children[\"Trust\"].children[\"Honor\"],\n {\"Buyer\": 1, \"Seller\": 1},\n \"Trustworthy\"\n)"
"source": [
"g.make_outcome(\n",
" gbt.H.path(\"Trust\", \"Honor\"),\n",
" {\"Buyer\": 1, \"Seller\": 1},\n",
" \"Trustworthy\"\n",
")"
]
},
{
"cell_type": "code",
Expand All @@ -198,7 +204,13 @@
"id": "695b1aad",
"metadata": {},
"outputs": [],
"source": "g.make_outcome(\n g.root.children[\"Trust\"].children[\"Abuse\"],\n {\"Buyer\": -1, \"Seller\": 2},\n \"Untrustworthy\"\n)"
"source": [
"g.make_outcome(\n",
" gbt.H.path(\"Trust\", \"Abuse\"),\n",
" {\"Buyer\": -1, \"Seller\": 2},\n",
" \"Untrustworthy\"\n",
")"
]
},
{
"cell_type": "code",
Expand All @@ -224,7 +236,13 @@
"id": "0704ef86",
"metadata": {},
"outputs": [],
"source": "g.make_outcome(\n g.root.children[\"Not trust\"],\n {\"Buyer\": 0, \"Seller\": 0},\n \"Opt-out\"\n)"
"source": [
"g.make_outcome(\n",
" gbt.H.path(\"Not trust\"),\n",
" {\"Buyer\": 0, \"Seller\": 0},\n",
" \"Opt-out\"\n",
")"
]
},
{
"cell_type": "code",
Expand Down
83 changes: 59 additions & 24 deletions doc/tutorials/03_stripped_down_poker.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,21 @@
"cell_type": "markdown",
"id": "0d4c7f5b",
"metadata": {},
"source": "A move belonging to the chance player is called an **event**, and is created with `append_event` rather than `append_move`, since it requires the probability distribution over its actions to be specified explicitly.\n\nThe first step in this game is that Alice is dealt a card which could be a King or Queen, each with probability 1/2.\n\nTo simulate this in Gambit, we create a chance event at the root node of the game:"
"source": [
"A move belonging to the chance player is called an **event**, and is created with `append_event` rather than `append_move`, since it requires the probability distribution over its actions to be specified explicitly. Like `append_move`, `append_event` takes an `H`-built selector identifying the node(s) to add the event at.\n",
"\n",
"The first step in this game is that Alice is dealt a card which could be a King or Queen, each with probability 1/2.\n",
"\n",
"To simulate this in Gambit, we create a chance event at the root node of the game, using `gbt.H.path()` to select it:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe80c64c",
"metadata": {},
"outputs": [],
"source": "g.append_event(\n g.root,\n actions=[\"King\", \"Queen\"],\n probs=[gbt.Rational(1, 2), gbt.Rational(1, 2)]\n)"
"source": "g.append_event(\n gbt.H.path(),\n actions={\"King\": gbt.Rational(1, 2), \"Queen\": gbt.Rational(1, 2)}\n)"
},
{
"cell_type": "code",
Expand All @@ -126,8 +132,8 @@
"In this game, information structure is important.\n",
"Alice knows her card, so the two nodes at which she has the move are part of different **information sets**.\n",
"\n",
"We'll therefore need to append Alice's move separately for each of the root node's children, i.e. the scenarios where she has a King or a Queen.\n",
"Let's now add both of these possible moves:"
"We'll therefore need to append Alice's move separately for each possible card, i.e. the scenarios where she has a King or a Queen.\n",
"`append_move` takes an `H`-built selector describing which node(s) to add the move at; `gbt.H.path(label)` describes the node reached by taking the action labeled `label` from the root:"
]
},
{
Expand All @@ -137,12 +143,8 @@
"metadata": {},
"outputs": [],
"source": [
"for node in g.root.children:\n",
" g.append_move(\n",
" node,\n",
" player=\"Alice\",\n",
" actions=[\"Bet\", \"Fold\"]\n",
" )"
"for card in [\"King\", \"Queen\"]:\n",
" g.append_move(gbt.H.path(card), player=\"Alice\", actions=[\"Bet\", \"Fold\"])"
]
},
{
Expand All @@ -164,13 +166,13 @@
"\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",
"\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",
" - Chance player chooses King, then Alice Bets: `gbt.H.path(\"King\", \"Bet\")`\n",
" - Chance player chooses Queen, then Alice Bets: `gbt.H.path(\"Queen\", \"Bet\")`\n",
"\n",
"In other words, Bob's decision when Alice Bets with a Queen should be part of the same information set as Bob's decision when Alice Bets with a King.\n",
"\n",
"To set this scenario up in Gambit, we'll need to add both possible moves as part of the same information set (represented in Gambit as an `Infoset`).\n",
"This can be done by passing a list of nodes to the `append_move` method:"
"This can be done with a single selector: `gbt.H.path(..., \"Bet\")` describes the node reached by *any* single action from the root (either card), followed by \"Bet\" -- so it matches both of Bob's decision nodes at once, joining them into one information set:"
]
},
{
Expand All @@ -181,7 +183,7 @@
"outputs": [],
"source": [
"g.append_move(\n",
" [g.root.children[\"King\"].children[\"Bet\"], g.root.children[\"Queen\"].children[\"Bet\"]],\n",
" gbt.H.path(..., \"Bet\"),\n",
" player=\"Bob\",\n",
" actions=[\"Call\", \"Fold\"]\n",
")"
Expand Down Expand Up @@ -209,7 +211,35 @@
"id": "29aa60a0",
"metadata": {},
"outputs": [],
"source": "# Alice folds, Bob wins small\ng.make_outcome(\n [g.root.children[\"King\"].children[\"Fold\"], g.root.children[\"Queen\"].children[\"Fold\"]],\n {\"Alice\": -1, \"Bob\": 1},\n \"Lose\"\n)\n\n# Bob sees Alice Bet and calls, correctly believing she is bluffing, Bob wins big\ng.make_outcome(\n g.root.children[\"Queen\"].children[\"Bet\"].children[\"Call\"],\n {\"Alice\": -2, \"Bob\": 2},\n \"Lose Big\"\n)\n\n# Bob sees Alice Bet and calls, incorrectly believing she is bluffing, Alice wins big\ng.make_outcome(\n g.root.children[\"King\"].children[\"Bet\"].children[\"Call\"],\n {\"Alice\": 2, \"Bob\": -2},\n \"Win Big\"\n)\n\n# Bob does not call Alice's Bet, Alice wins small\ng.make_outcome(\n [g.root.children[\"King\"].children[\"Bet\"].children[\"Fold\"],\n g.root.children[\"Queen\"].children[\"Bet\"].children[\"Fold\"]],\n {\"Alice\": 1, \"Bob\": -1},\n \"Win\"\n)"
"source": [
"# Alice folds, Bob wins small\n",
"g.make_outcome(\n",
" gbt.H.path(..., \"Fold\"),\n",
" {\"Alice\": -1, \"Bob\": 1},\n",
" \"Lose\"\n",
")\n",
"\n",
"# Bob sees Alice Bet and calls, correctly believing she is bluffing, Bob wins big\n",
"g.make_outcome(\n",
" gbt.H.path(\"Queen\", \"Bet\", \"Call\"),\n",
" {\"Alice\": -2, \"Bob\": 2},\n",
" \"Lose Big\"\n",
")\n",
"\n",
"# Bob sees Alice Bet and calls, incorrectly believing she is bluffing, Alice wins big\n",
"g.make_outcome(\n",
" gbt.H.path(\"King\", \"Bet\", \"Call\"),\n",
" {\"Alice\": 2, \"Bob\": -2},\n",
" \"Win Big\"\n",
")\n",
"\n",
"# Bob does not call Alice's Bet, Alice wins small\n",
"g.make_outcome(\n",
" gbt.H.path(..., \"Bet\", \"Fold\"),\n",
" {\"Alice\": 1, \"Bob\": -1},\n",
" \"Win\"\n",
")"
]
},
{
"cell_type": "code",
Expand Down Expand Up @@ -819,7 +849,7 @@
"outputs": [],
"source": [
"small_game = gbt.Game.new_tree()\n",
"small_game.append_event(small_game.root, [\"a\", \"b\", \"c\"], [gbt.Rational(1, 3)] * 3)\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())"
]
},
Expand All @@ -837,8 +867,8 @@
"outputs": [],
"source": [
"small_game.make_event(\n",
" [small_game.root],\n",
" [gbt.Rational(1, 4), gbt.Rational(1, 2), gbt.Rational(1, 4)]\n",
" 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())"
]
Expand All @@ -859,8 +889,8 @@
"outputs": [],
"source": [
"small_game.make_event(\n",
" [small_game.root],\n",
" [gbt.Decimal(\".25\"), gbt.Decimal(\".50\"), gbt.Decimal(\".25\")]\n",
" 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())"
]
Expand All @@ -884,7 +914,7 @@
"metadata": {},
"outputs": [],
"source": [
"small_game.make_event([small_game.root], [\"1/4\", \"1/2\", \"1/4\"])\n",
"small_game.make_event(gbt.H.path(), {\"a\": \"1/4\", \"b\": \"1/2\", \"c\": \"1/4\"})\n",
"list(small_game.root.action_probs.values())"
]
},
Expand All @@ -895,7 +925,7 @@
"metadata": {},
"outputs": [],
"source": [
"small_game.make_event([small_game.root], [\".25\", \".50\", \".25\"])\n",
"small_game.make_event(gbt.H.path(), {\"a\": \".25\", \"b\": \".50\", \"c\": \".25\"})\n",
"list(small_game.root.action_probs.values())"
]
},
Expand All @@ -919,7 +949,7 @@
"metadata": {},
"outputs": [],
"source": [
"small_game.make_event([small_game.root], [.25, .50, .25])\n",
"small_game.make_event(gbt.H.path(), {\"a\": .25, \"b\": .50, \"c\": .25})\n",
"list(small_game.root.action_probs.values())"
]
},
Expand All @@ -937,7 +967,12 @@
"id": "1991d288",
"metadata": {},
"outputs": [],
"source": "try:\n small_game.make_event([small_game.root], [1/3, 1/3, 1/3])\nexcept ValueError as e:\n print(\"ValueError:\", e)\n"
"source": [
"try:\n",
" small_game.make_event(gbt.H.path(), {\"a\": 1/3, \"b\": 1/3, \"c\": 1/3})\n",
"except ValueError as e:\n",
" print(\"ValueError:\", e)"
]
},
{
"cell_type": "markdown",
Expand Down
Loading
Loading