diff --git a/doc/pygambit.user.rst b/doc/pygambit.user.rst index 36b69c915..194de8bfd 100644 --- a/doc/pygambit.user.rst +++ b/doc/pygambit.user.rst @@ -184,9 +184,22 @@ and so on. Therefore, to create a two-player symmetric game, as in this example for the second player is transposed before passing to :py:meth:`.Game.from_arrays`. There is a reverse function :py:meth:`.Game.to_arrays` that produces -the players' payoff tables given a strategic game. The output is the list of numpy arrays, +the players' payoff tables given a strategic game. The output is the list of numpy arrays, where the number of produced arrays is equal to the number of players. +.. ipython:: python + + m, m_transposed = g.to_arrays() + m + +There is a parameter `dtype`` passed to :py:meth:`.Game.to_arrays` that configures the data type of +payoffs in the generated arrays. Supported types are :py:class:`.Rational` (default) and float. + +.. ipython:: python + + m, m_transposed = g.to_arrays(dtype=float) + m + .. _pygambit.user.numbers: Representation of numerical data of a game diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 4ede523c2..06bac3db1 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -323,6 +323,7 @@ class Game: See Also -------- from_dict : Create strategic game and set player labels + to_array: Generate the payoff tables for players represented as numpy arrays """ g = cython.declare(Game) arrays = [np.array(a) for a in arrays] @@ -336,36 +337,36 @@ class Game: g.title = title return g - def to_arrays(self) -> typing.List[np.array]: - """Reverse function to from_arrays: + def to_arrays(self, dtype: typing.Type = Rational) -> typing.List[np.array]: + """Generate the payoff tables for players represented as numpy arrays. - To_arrays, for a given game, generates players’ payoff tables represented as numpy arrays. - The number of produced arrays is equal to the number of players. + Parameters + ---------- + dtype : type + The type to which payoff values will be converted and + the resulting arrays will be of that dtype Returns ------- - list of np.arrays + list of np.array - Raises - ------ - UndefinedOperationError - If the game does not have a tree representation. + See Also + -------- + from_arrays : Create game from list-like of array-like """ arrays = [] - if self.is_tree: - raise UndefinedOperationError( - "Operation only defined for games with a strategic representation" - ) - - if len(self.players) == 0: - raise RuntimeError("There are no players in the game") - shape = tuple(len(player.strategies) for player in self.players) for player in self.players: - array = np.zeros(shape=shape) + array = np.zeros(shape=shape, dtype=object) for profile in itertools.product(*(range(s) for s in shape)): - array[profile] = self[profile][player] + try: + array[profile] = dtype(self[profile][player]) + except (ValueError, TypeError, IndexError, KeyError): + raise ValueError( + f"Payoff '{self[profile][player]}' cannot be" + f"converted to requested type '{dtype}'" + ) from None arrays.append(array) return arrays diff --git a/tests/test_game.py b/tests/test_game.py index 2bd3bcf01..538181abc 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -13,12 +13,6 @@ def test_from_arrays(): assert len(game.players[1].strategies) == 2 -def test_extensive_form_to_arrays(): - game = gbt.Game.new_tree() - with pytest.raises(ValueError): - _ = game.to_arrays() - - def test_empty_array_to_arrays(): game = gbt.Game.from_arrays([]) a = game.to_arrays() @@ -26,9 +20,24 @@ def test_empty_array_to_arrays(): assert (a[0] == np.array([])).all() -def test_different_num_representations_to_arrays(): +def test_to_arrays_wrong_type(): + m = np.array([[8, 2], [10, 5]]) + game = gbt.Game.from_arrays(m, m.transpose()) + with pytest.raises(ValueError): + _ = game.to_arrays(dtype=dict) + + +def test_different_num_representations_to_arrays_fraction(): game = gbt.Game.from_arrays([1, 2 / 1, "6/2", 0.25, ".99"]) A = game.to_arrays()[0] + correct_output = [gbt.Rational(1, 1), gbt.Rational(2, 1), gbt.Rational(3, 1), + gbt.Rational(1, 4), gbt.Rational(99, 100)] + assert (correct_output == A).all() + + +def test_different_num_representations_to_arrays_float(): + game = gbt.Game.from_arrays([1, 2 / 1, "6/2", 0.25, ".99"]) + A = game.to_arrays(dtype=float)[0] correct_output = [1.0, 2.0, 3.0, 0.25, 0.99] assert (correct_output == A).all()