Skip to content

[API]: Lazy evaluation of all relations between game elements #946

@tturocy

Description

@tturocy

As part of developing our "algebra of games" idea, we want to move towards "lazy evaluation" of relationships among the constituent parts of games. See content migrated from discussion below for the rationale of why this makes the API more consistent.

For the moment we will change this in pygambit only (with C++ intended for 17.0 alongside more substantial implementation changes).

We will need to change (at least) the implementation of the following:

  • Node.outcome
  • Node.infoset
  • Node.player
  • Node.parent

It is not a coincidence that these are all node-related properties. We are intending that the node (history) is going to be the principal way of manipulating extensive games in 17.0, with objects referenced relative to a node (or set of nodes).

The implementation path for this in pygambit would be creating a different type of object which instead of keeping, for example, an infoset, will keep a node, and then access the node's information set on demand when needed.

Discussed in #863

Originally posted by tturocy April 30, 2026
This is something which is envisaged as part of the algebra-of-games API planned for 17.0.

Presently, relations like Node.outcome or Node.infoset are evaluated "immediately" and resolve to an object which is realised physically in memory (ultimately it's a shared pointer to an allocated structure). We have developed a sense that we want to move away from this, and to treat these as a form of selector.

There is another justification for this, which is that the existing API is inconsistent in this regard. Consider the sequence below:

In [1]: import pygambit as gbt
g = gbt.
In [2]: g = gbt.Game.new_tree(players=["1", "2"])

In [3]: first_gen = g.root.children

In [4]: len(first_gen)
Out[4]: 0

In [5]: g.append_move(g.root, "1", actions=["U", "D"])

In [6]: len(first_gen)
Out[6]: 2

As implemented, Node.children actually returns an object which is evaluated, in effect, lazily on iteration. So if the set of children of the node change, the output of iterating the collection changes.

However, predicates that resolve to individual objects rather than collections are evaluated immediately. For example,

In [1]: import pygambit as gbt

In [2]: g = gbt.Game.new_tree(players=["1", "2"])

In [3]: infoset = g.root.infoset

In [4]: infoset

In [5]: g.append_move(g.root, "1", actions=["U", "D"])

In [6]: infoset == g.root.infoset
Out[6]: False

This is (in my view) an argument in favour of revising the semantics even for 16.7 for these properties. I claim that most of the time, the intention when referring to objects via these relations is in the sense of a predicate. g.root.infoset means "the information set to which the root node of the game belongs", which may change as the game mutates; g.root.outcome would mean "the outcome associated with the root node (if any)", which likewise might change.

Metadata

Metadata

Assignees

Labels

cythonItems which involve coding in CythonpythonItems which involve coding in Python

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions