Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import a list of valid terminal node references. #438

Conversation

VarsosC
Copy link

@VarsosC VarsosC commented Jan 18, 2024

Import a list of valid terminal node references for the append_move and append_infoset functions. Further, the test_node and test_infosets test function have been modified to check the newly acquired functionality.

@VarsosC VarsosC requested a review from tturocy January 19, 2024 08:35
@VarsosC VarsosC linked an issue Jan 19, 2024 that may be closed by this pull request
@@ -1018,6 +1018,9 @@ class Game:
f"{funcname}(): {argname} must be Node or str, not {node.__class__.__name__}"
)

if not isinstance(argname, (Node, str)) and not hasattr(argname, "__iter__"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unreachable code, and actually is not needed.

@@ -1100,47 +1103,79 @@ class Game:
f"{funcname}(): {argname} must be Action or str, not {action.__class__.__name__}"
)

def append_move(self, node: typing.Union[Node, str],
NodeReference = typing.Union[Node, str]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defining these here means they are members of the class Game. They should be defined at global scope in an appropriate place.

else:
nodes = [nodes]

resolved_nodes = [self._resolve_node(nd, "append_move") for nd in nodes]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to write this as one line:

resolved_nodes = [self._resolve_node(n, "append_move") for n in nodes if hasiter(nodes, "__iter__") else [nodes]]

Rationale: This way the type of nodes does not get changed.

src/pygambit/game.pxi Outdated Show resolved Hide resolved
resolved_node.node.deref().AppendMove(resolved_player.player, len(actions))
for label, action in zip(actions, resolved_node.infoset.actions):
action.label = label

def append_infoset(self, node: typing.Union[Node, str],
infoset = resolved_node.infoset
resolved_infoset = cython.cast(Infoset, self._resolve_infoset(infoset, "append_infoset"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to call _resolve_infoset here because resolved_node.infoset is already an Infoset. (The argument to _resolve_infoset identifying the function is also incorrect but that is moot.)

@@ -52,3 +60,47 @@ def test_infoset_add_action_error():
game = games.read_from_file("basic_extensive_game.efg")
with pytest.raises(gbt.MismatchError):
game.add_action(game.players[0].infosets[0], game.players[1].infosets[0].actions[0])

def test_append_move_single_infoset_ist_of_nodes():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spelling error

game.root.children[0].children[1]]
game.append_move(nodes, "Player 3", ["B", "F"])

assert len(game.players[2].infosets) == 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more transparent to be consistent and index player by label in the assertion.,


def test_append_move_infoset_members_list_of_nodes():
"""Test that the newly created infoset from a list of nodes is inherited
with as many members as the cardinality of the list.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would not a better test be to confirm that the correct nodes are in fact in the same information set, rather than just the length is correct?

game.root.children[0].children[0].infoset)


def test_append_infoset_node_list_with_duplciate_nodes():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spelling error

tests/test_node.py Show resolved Hide resolved
Copy link
Member

@tturocy tturocy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still is failing on some very simple cases.

The works in master (and should work as it is correct):

In [1]: import pygambit as gbt

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

In [3]: g.root.label = "ROOT"

In [4]: g.append_move("ROOT", "Alice", ["L", "R"])
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[4], line 1
----> 1 g.append_move("ROOT", "Alice", ["L", "R"])

File src/pygambit/game.pxi:1124, in pygambit.gambit.Game.append_move()

File src/pygambit/game.pxi:1016, in pygambit.gambit.Game._resolve_node()

KeyError: "append_move(): no node with label 'R'"

This produces an incorrect error because the checks for duplicate nodes are not correct.

In [1]: import pygambit as gbt

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

In [3]: g.root.label = "ROOT"

In [4]: g.append_move(["ROOT", g.root], "Alice", ["L", "R"])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[4], line 1
----> 1 g.append_move(["ROOT", g.root], "Alice", ["L", "R"])

File src/pygambit/game.pxi:1146, in pygambit.gambit.Game.append_move()

ValueError: Undefined operation on game

Copy link
Member

@tturocy tturocy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see comments.

For future please ensure that the pull request contains just one commit (i.e. it's been rebased and squashed), so that if it is acceptable it's merge-ready.

@@ -1122,14 +1122,14 @@ class Game:

resolved_nodes = (
[self._resolve_node(n, "append_move") for n in nodes]
if hasattr(nodes, "__iter__")
if (hasattr(nodes, "__iter__") and not isinstance(nodes, str))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parentheses are redundant.

else [self._resolve_node(nodes, "append_move")]
)

if any(len(rn.children) > 0 for rn in resolved_nodes):
raise UndefinedOperationError("append_move(): `node` must be a terminal node")

if hasattr(nodes, "__iter__") and (len(nodes) != len(set(nodes))):
if hasattr(nodes, "__iter__") and any(nodes.count(n) > 1 for n in nodes):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This misses the point of the previous error cases.

The error here is that resolved_node should be used, not nodes.

else [self._resolve_node(nodes, "append_move")]
)

if any(len(rs.children) > 0 for rs in resolved_nodes):
raise UndefinedOperationError("append_move(): `node` must be a terminal node")

if hasattr(nodes, "__iter__") and len(nodes) != len(set(nodes)):
if hasattr(nodes, "__iter__") and any(nodes.count(n) > 1 for n in nodes):
raise ValueError("append_move(): Each `node` must be listed only once.")

if hasattr(nodes, "__iter__") and (len(nodes) == 0):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise multiple issues:

  • The parentheses are redundant;
  • However, this check should be on resolved_nodes instead of nodes in which case checking for whether it's iterable is redundant;
  • Then the correct way (per PEP 8) to check whether a list is empty is using not, not len() == 0.

@VarsosC VarsosC force-pushed the ENH-366-incorporating-lists-of-node-references-for-various-calls branch from 937721f to 3d3b125 Compare February 2, 2024 07:27
resolved_player = cython.cast(Player, self._resolve_player(player, "append_move"))
if len(resolved_node.children) > 0:
raise UndefinedOperationError("append_move(): `node` must be a terminal node")
if len(actions) == 0:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

len() == 0 is not Pythonic - should use not actions

if len(actions) == 0:
raise UndefinedOperationError("append_move(): `actions` must be a nonempty list")

resolved_nodes = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The outer parentheses are redundant. You can just use the square brackets for the list directly.

if any(len(n.children) > 0 for n in resolved_nodes):
raise UndefinedOperationError("append_move(): `node` must be a terminal node")

if any(resolved_nodes.count(n) > 1 for n in resolved_nodes):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quadratic - .count() is linear in the length of the list and then you're iterating the list.

Much more straightforward and more efficient is len(resolved_nodes) != len(set(resolved_nodes))

@VarsosC VarsosC force-pushed the ENH-366-incorporating-lists-of-node-references-for-various-calls branch from 3d3b125 to ffa5792 Compare February 2, 2024 13:00
@tturocy tturocy force-pushed the ENH-366-incorporating-lists-of-node-references-for-various-calls branch from ffa5792 to c23c4bf Compare February 8, 2024 17:25
@tturocy tturocy force-pushed the ENH-366-incorporating-lists-of-node-references-for-various-calls branch from c23c4bf to 4ab6060 Compare February 8, 2024 17:31
@tturocy tturocy merged commit 3e9a9d6 into master Feb 9, 2024
19 checks passed
@tturocy tturocy deleted the ENH-366-incorporating-lists-of-node-references-for-various-calls branch February 9, 2024 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ENH: Atomic add of move at multiple nodes
2 participants