diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index da49ec907..fd64dbd2f 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -33,6 +33,7 @@ Creating, reading, and writing games read_efg read_nfg read_agg + read_bagg Game.new_tree Game.new_table diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index e2a9b9203..5e7b272c0 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -466,6 +466,7 @@ cdef extern from "util.h": c_Game ParseEfgGame(string, bint) except +IOError c_Game ParseNfgGame(string, bint) except +IOError c_Game ParseAggGame(string, bint) except +IOError + c_Game ParseBaggGame(string, bint) except +IOError string WriteEfgFile(c_Game) string WriteNfgFile(c_Game) string WriteNfgFileSupport(c_StrategySupportProfile) except +IOError diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 811d70489..928371786 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -80,7 +80,7 @@ def read_gbt(filepath_or_buffer: str | pathlib.Path | io.IOBase, See Also -------- - read_efg, read_nfg, read_agg + read_efg, read_nfg, read_agg, read_bagg """ return read_game(filepath_or_buffer, normalize_labels, parser=ParseGbtGame) @@ -111,7 +111,7 @@ def read_efg(filepath_or_buffer: str | pathlib.Path | io.IOBase, See Also -------- - read_gbt, read_nfg, read_agg + read_gbt, read_nfg, read_agg, read_bagg """ return read_game(filepath_or_buffer, normalize_labels, parser=ParseEfgGame) @@ -142,7 +142,7 @@ def read_nfg(filepath_or_buffer: str | pathlib.Path | io.IOBase, See Also -------- - read_gbt, read_efg, read_agg + read_gbt, read_efg, read_agg, read_bagg """ return read_game(filepath_or_buffer, normalize_labels, parser=ParseNfgGame) @@ -173,11 +173,42 @@ def read_agg(filepath_or_buffer: str | pathlib.Path | io.IOBase, See Also -------- - read_gbt, read_efg, read_nfg + read_gbt, read_efg, read_nfg, read_bagg """ return read_game(filepath_or_buffer, normalize_labels, parser=ParseAggGame) +def read_bagg(filepath_or_buffer: str | pathlib.Path | io.IOBase, + normalize_labels: bool = False) -> Game: + """Construct a game from its serialised representation in a BAGG file. + + Parameters + ---------- + filepath_or_buffer : str, pathlib.Path or io.IOBase + The path to the file containing the game representation or file-like object + normalize_labels : bool (default False) + Ensure all labels are nonempty and unique within their scopes. + This will be enforced in a future version of Gambit. + + Returns + ------- + Game + A game constructed from the representation in the file. + + Raises + ------ + IOError + If the file cannot be opened or read + ValueError + If the contents of the file are not a valid game representation. + + See Also + -------- + read_gbt, read_efg, read_nfg, read_agg + """ + return read_game(filepath_or_buffer, normalize_labels, parser=ParseBaggGame) + + @cython.cclass class GameNodes: """Represents the set of nodes in a game.""" diff --git a/src/pygambit/util.h b/src/pygambit/util.h index bfd09baf3..89d5d9dc8 100644 --- a/src/pygambit/util.h +++ b/src/pygambit/util.h @@ -31,6 +31,7 @@ #include #include "gambit.h" #include "games/gameagg.h" +#include "games/gamebagg.h" #include "games/nash.h" using namespace std; @@ -61,6 +62,12 @@ Game ParseAggGame(std::string const &s, bool p_normalizeLabels) return ReadAggFile(f); } +Game ParseBaggGame(std::string const &s, bool p_normalizeLabels) +{ + std::istringstream f(s); + return ReadBaggFile(f); +} + std::string WriteEfgFile(const Game &p_game) { std::ostringstream f; diff --git a/tests/test_io.py b/tests/test_io.py index e654b98d8..575abe5a3 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -49,6 +49,20 @@ def test_read_agg_invalid(): gbt.read_agg(game_path) +def test_read_bagg(): + game_path = os.path.join("contrib", "games", "Bayesian-Coffee-3-2-2-3.bagg") + game = gbt.read_bagg(game_path) + assert isinstance(game, gbt.Game) + + +def test_read_bagg_invalid(): + game_path = os.path.join( + "tests", "test_games", "2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg" + ) + with pytest.raises(ValueError): + gbt.read_bagg(game_path) + + def test_read_gbt_invalid(): game_path = os.path.join( "tests", "test_games", "2x2x2_nfg_from_local_max_cut_2_pure_1_mixed_eq.nfg"