diff --git a/.clang-tidy b/.clang-tidy index 9c397309e..3d82e1db0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -7,15 +7,12 @@ Checks: | -modernize-avoid-c-arrays, -modernize-pass-by-value, -modernize-use-nodiscard, - -modernize-concat-nested-namespaces, cppcoreguidelines-pro-type-cstyle-cast, cppcoreguidelines-pro-type-member-init, cppcoreguidelines-prefer-member-initializer, misc-const-correctness WarningsAsErrors: '*' -# Don't bother with wxWidgets headers -HeaderFilterRegex: '^((?!labenski).)*$' -AnalyzeTemporaryDtors: false +HeaderFilterRegex: '.*' FormatStyle: none User: arbiter CheckOptions: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 68eeac632..5d6b95ccb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run clang-format style check for C/C++ - uses: jidicula/clang-format-action@v4.14.0 + uses: jidicula/clang-format-action@v4.15.0 with: clang-format-version: '17' check-path: 'src' diff --git a/.github/workflows/osxbinary.yml b/.github/workflows/osxbinary.yml new file mode 100644 index 000000000..c2563c746 --- /dev/null +++ b/.github/workflows/osxbinary.yml @@ -0,0 +1,37 @@ +name: MacOS static GUI binary + +on: + push: + tags: + - 'v*' + schedule: + - cron: '0 6 * * 4' + +jobs: + macos-13: + runs-on: macos-13 + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + brew install automake autoconf + curl -L -O https://github.com/wxWidgets/wxWidgets/releases/download/v3.2.6/wxWidgets-3.2.6.tar.bz2 + tar xjf wxWidgets-3.2.6.tar.bz2 + cd wxWidgets-3.2.6 + mkdir build-release + cd build-release + ../configure --disable-shared --disable-sys-libs + make -j4 + sudo make install + - run: aclocal + - run: automake --add-missing + - run: autoconf + - run: ./configure + - run: make + - run: sudo make install + - run: make osx-dmg + - uses: actions/upload-artifact@v4 + with: + name: artifact-osx-13 + path: "*.dmg" diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index b4c0a8155..1108d4af3 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -10,7 +10,7 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name strategy: matrix: - python-version: ['3.9', '3.12'] + python-version: ['3.9', '3.13'] steps: - uses: actions/checkout@v4 @@ -21,10 +21,10 @@ jobs: - name: Set up dependencies run: | python -m pip install --upgrade pip - pip install setuptools cython pytest wheel lxml numpy scipy + pip install setuptools build cython pytest pytest-skip-slow wheel lxml numpy scipy - name: Build source distribution run: - python setup.py sdist + python -m build - name: Build from source distribution run: | cd dist @@ -32,12 +32,58 @@ jobs: - name: Run tests run: pytest + macos-13: + runs-on: macos-13 + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + strategy: + matrix: + python-version: ['3.13'] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Set up dependencies + run: | + python -m pip install --upgrade pip + pip install cython pytest pytest-skip-slow wheel lxml numpy scipy + - name: Build extension + run: | + python -m pip install -v . + - name: Run tests + run: pytest + + macos-14: + runs-on: macos-14 + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + strategy: + matrix: + python-version: ['3.13'] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Set up dependencies + run: | + python -m pip install --upgrade pip + pip install cython pytest pytest-skip-slow wheel lxml numpy scipy + - name: Build extension + run: | + python -m pip install -v . + - name: Run tests + run: pytest + windows: runs-on: windows-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name strategy: matrix: - python-version: ['3.12'] + python-version: ['3.13'] steps: - uses: actions/checkout@v4 @@ -48,7 +94,7 @@ jobs: - name: Set up dependencies run: | python -m pip install --upgrade pip - pip install cython pytest wheel lxml numpy scipy + pip install cython pytest pytest-skip-slow wheel lxml numpy scipy - name: Build extension run: | python -m pip install -v . diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 08e66c385..7125996bd 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -34,8 +34,8 @@ jobs: make sudo make install - macos-13: - runs-on: macos-13 + macos: + runs-on: macos-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - uses: actions/checkout@v4 @@ -50,26 +50,7 @@ jobs: - run: make osx-dmg - uses: actions/upload-artifact@v4 with: - name: artifact-osx-13 - path: "*.dmg" - - macos-14: - runs-on: macos-14 - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - steps: - - uses: actions/checkout@v4 - - name: Install dependencies - run: brew install automake autoconf wxwidgets - - run: aclocal - - run: automake --add-missing - - run: autoconf - - run: ./configure - - run: make - - run: sudo make install - - run: make osx-dmg - - uses: actions/upload-artifact@v4 - with: - name: artifact-osx-14 + name: artifact-osx path: "*.dmg" windows: diff --git a/ChangeLog b/ChangeLog index e8fa06ebc..c9ab9f6ac 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,11 +2,28 @@ ## [16.4.0] - unreleased -### Removed +### General +- Officially added support for Python 3.13. +### Removed - The deprecated functions `Game.read_game`, `Game.parse_game` and `Game.write` functions have been removed as planned. (#357) +### Added +- Implement `GetPlays()` (C++) and `get_plays` (Python) to compute the set of terminal nodes consistent + with a node, information set, or action (#517) +- Implement `GameStrategyRep::GetAction` (C++) and `Strategy.action` (Python) retrieving the action + prescribed by a strategy at an information set +- Tests for creation of the reduced strategic form from an extensive-form game (currently only + for games with perfect recall) + + +## [16.3.1] - unreleased + +### Fixed +- Corrected a regression in which information sets were prematurely invalidated (and therefore + `delete this` called on them) when removing the last node from an information set. + ## [16.3.0] - 2025-01-13 @@ -50,6 +67,12 @@ Explorer, as GTE no longer reads files in this format. +## [16.2.2] - unreleased + +### Fixed +- `Game.copy_tree` and `Game.move_tree` implementations reversed the roles of the + `src` and `dest` nodes (#499) + ## [16.2.1] - 2025-01-06 ### Fixed @@ -60,7 +83,6 @@ - Attempting to call the default constructor on Game objects (rather than one of the factory functions) now raises a more informative exception (#463) - ## [16.2.0] - 2024-04-05 ### Fixed diff --git a/Makefile.am b/Makefile.am index 5aa62457d..2a19386a9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -245,6 +245,8 @@ EXTRA_DIST = \ src/README.rst core_SOURCES = \ + src/core/core.h \ + src/core/util.h \ src/core/array.h \ src/core/list.h \ src/core/vector.h \ @@ -312,12 +314,7 @@ game_SOURCES = \ src/games/nash.h linalg_SOURCES = \ - src/solvers/linalg/basis.cc \ - src/solvers/linalg/basis.h \ - src/solvers/linalg/bfs.h \ - src/solvers/linalg/btableau.cc \ src/solvers/linalg/btableau.h \ - src/solvers/linalg/btableau.imp \ src/solvers/linalg/lpsolve.cc \ src/solvers/linalg/lpsolve.h \ src/solvers/linalg/lpsolve.imp \ @@ -391,6 +388,7 @@ gambit_enummixed_SOURCES = \ src/solvers/enummixed/clique.h \ src/solvers/enummixed/enummixed.cc \ src/solvers/enummixed/enummixed.h \ + src/tools/util.h \ src/tools/enummixed/enummixed.cc gambit_nashsupport_SOURCES = \ @@ -419,11 +417,13 @@ gambit_enumpoly_SOURCES = \ src/solvers/enumpoly/efgpoly.cc \ src/solvers/enumpoly/nfgpoly.cc \ src/solvers/enumpoly/enumpoly.h \ + src/tools/util.h \ src/tools/enumpoly/enumpoly.cc gambit_enumpure_SOURCES = \ ${core_SOURCES} ${game_SOURCES} \ src/solvers/enumpure/enumpure.h \ + src/tools/util.h \ src/tools/enumpure/enumpure.cc gambit_gnm_SOURCES = \ @@ -440,6 +440,7 @@ gambit_ipa_SOURCES = \ src/solvers/ipa/ipa.cc \ src/solvers/ipa/ipa.h \ src/tools/gt/nfggt.cc \ + src/tools/util.h \ src/tools/gt/nfgipa.cc gambit_lcp_SOURCES = \ @@ -448,6 +449,7 @@ gambit_lcp_SOURCES = \ src/solvers/lcp/efglcp.cc \ src/solvers/lcp/nfglcp.cc \ src/solvers/lcp/lcp.h \ + src/tools/util.h \ src/tools/lcp/lcp.cc gambit_liap_SOURCES = \ @@ -455,6 +457,7 @@ gambit_liap_SOURCES = \ src/solvers/liap/efgliap.cc \ src/solvers/liap/nfgliap.cc \ src/solvers/liap/liap.h \ + src/tools/util.h \ src/tools/liap/liap.cc gambit_logit_SOURCES = \ @@ -466,21 +469,22 @@ gambit_logit_SOURCES = \ src/solvers/logit/logit.h \ src/solvers/logit/efglogit.cc \ src/solvers/logit/nfglogit.cc \ + src/tools/util.h \ src/tools/logit/logit.cc gambit_lp_SOURCES = \ ${core_SOURCES} ${game_SOURCES} \ ${linalg_SOURCES} \ - src/solvers/lp/efglp.cc \ - src/solvers/lp/efglp.h \ - src/solvers/lp/nfglp.cc \ - src/solvers/lp/nfglp.h \ + src/solvers/lp/lp.cc \ + src/solvers/lp/lp.h \ + src/tools/util.h \ src/tools/lp/lp.cc gambit_simpdiv_SOURCES = \ ${core_SOURCES} ${game_SOURCES} \ src/solvers/simpdiv/simpdiv.cc \ src/solvers/simpdiv/simpdiv.h \ + src/tools/util.h \ src/tools/simpdiv/nfgsimpdiv.cc gambit_SOURCES = \ @@ -634,8 +638,7 @@ msw-msi: light -ext WixUIExtension gambit.wixobj clang-tidy: - clang-tidy ${top_srcdir}/src/core/*.cc -- --std=c++17 -I ${top_srcdir}/src -I ${top_srcdir}/src/labenski/include -DVERSION="" ${WX_CXXFLAGS} - clang-tidy ${top_srcdir}/src/games/*.cc ${top_srcdir}/src/games/*/*.cc -- --std=c++17 -I ${top_srcdir}/src -I ${top_srcdir}/src/labenski/include -DVERSION="" ${WX_CXXFLAGS} - clang-tidy ${top_srcdir}/src/solvers/*/*.cc -- --std=c++17 -I ${top_srcdir}/src -I ${top_srcdir}/src/labenski/include -DVERSION="" ${WX_CXXFLAGS} - clang-tidy ${top_srcdir}/src/tools/*/*.cc -- --std=c++17 -I ${top_srcdir}/src -I ${top_srcdir}/src/labenski/include -DVERSION="" ${WX_CXXFLAGS} - clang-tidy ${top_srcdir}/src/gui/*.cc -- --std=c++17 -I ${top_srcdir}/src -I ${top_srcdir}/src/labenski/include -DVERSION="" ${WX_CXXFLAGS} + clang-tidy ${top_srcdir}/src/core/*.cc -- --std=c++17 -I ${top_srcdir}/src -DVERSION="" + clang-tidy ${top_srcdir}/src/games/*.cc ${top_srcdir}/src/games/*/*.cc -- --std=c++17 -I ${top_srcdir}/src -DVERSION="" + clang-tidy ${top_srcdir}/src/solvers/*/*.cc -- --std=c++17 -I ${top_srcdir}/src -DVERSION="" + clang-tidy ${top_srcdir}/src/tools/*/*.cc -- --std=c++17 -I ${top_srcdir}/src -DVERSION="" diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 6eb2e566f..0b9de26e9 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -145,6 +145,7 @@ Information about the game Node.infoset Node.player Node.is_successor_of + Node.plays .. autosummary:: @@ -157,6 +158,7 @@ Information about the game Infoset.actions Infoset.members Infoset.precedes + Infoset.plays .. autosummary:: @@ -166,6 +168,7 @@ Information about the game Action.infoset Action.precedes Action.prob + Action.plays .. autosummary:: @@ -175,6 +178,7 @@ Information about the game Strategy.game Strategy.player Strategy.number + Strategy.action Player behavior @@ -212,8 +216,8 @@ Probability distributions over strategies MixedStrategyProfile.strategy_value MixedStrategyProfile.strategy_regret MixedStrategyProfile.player_regret - MixedStrategyProfile.max_regret MixedStrategyProfile.strategy_value_deriv + MixedStrategyProfile.max_regret MixedStrategyProfile.liap_value MixedStrategyProfile.as_behavior MixedStrategyProfile.normalize @@ -239,14 +243,16 @@ Probability distributions over behavior MixedBehaviorProfile.__getitem__ MixedBehaviorProfile.__setitem__ MixedBehaviorProfile.payoff - MixedBehaviorProfile.action_regret MixedBehaviorProfile.action_value + MixedBehaviorProfile.action_regret MixedBehaviorProfile.infoset_value + MixedBehaviorProfile.infoset_regret MixedBehaviorProfile.node_value MixedBehaviorProfile.realiz_prob MixedBehaviorProfile.infoset_prob MixedBehaviorProfile.belief MixedBehaviorProfile.is_defined_at + MixedBehaviorProfile.max_regret MixedBehaviorProfile.liap_value MixedBehaviorProfile.as_strategy MixedBehaviorProfile.normalize diff --git a/pyproject.toml b/pyproject.toml index b71562d38..fcc5107c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,42 @@ [build-system] requires = ["setuptools", "wheel", "Cython"] +build-backend = "setuptools.build_meta" + +[project] +name = "pygambit" +version = "16.3.0" +description = "The package for computation in game theory" +readme = "src/README.rst" +requires-python = ">=3.9" +license = "GPL-2.0-or-later" +authors = [ + {name = "Theodore Turocy", email = "ted.turocy@gmail.com"}, + {name = "Rahul Savani", email = "rahul.savani@liverpool.ac.uk"} +] +keywords = ["game theory", "Nash equilibrium"] +classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Scientific/Engineering :: Mathematics" +] +dependencies = [ + "numpy", + "scipy", +] + +[project.urls] +Homepage = "https://www.gambit-project.org" +Documentation = "https://gambitproject.readthedocs.io" +Repository = "https://github.com/gambitproject/gambit.git" +Issues = "https://github.com/gambitproject/gambit/issues" +Changelog = "https://github.com/gambitproject/gambit/blob/master/ChangeLog" + [tool.ruff] line-length = 99 @@ -33,3 +70,16 @@ indent-style = "space" [tool.cython-lint] max-line-length = 99 + + +[tool.pytest.ini_options] +addopts = "--strict-markers" +markers = [ + "nash_enummixed_strategy: tests of enummixed_solve in strategies", + "nash_lcp_strategy: tests of lcp_solve in strategies", + "nash_lcp_behavior: tests of lcp_solve in behaviors", + "nash_lp_strategy: tests of lp_solve in strategies", + "nash_lp_behavior: tests of lp_solve in behaviors", + "nash: all tests of Nash equilibrium solvers", + "slow: all time-consuming tests", +] diff --git a/setup.py b/setup.py index cd542da4d..7801dd292 100644 --- a/setup.py +++ b/setup.py @@ -99,44 +99,7 @@ def solver_library_config(library_name: str, paths: list) -> tuple: ) ) - -def readme(): - with open("src/README.rst") as f: - return f.read() - - setuptools.setup( - name="pygambit", - version="16.3.0", - description="The package for computation in game theory", - long_description=readme(), - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: Implementation :: CPython", - "Topic :: Scientific/Engineering :: Mathematics" - ], - keywords="game theory Nash equilibrium", - license="GPL2+", - author="Theodore Turocy", - author_email="ted.turocy@gmail.com", - url="https://www.gambit-project.org", - project_urls={ - "Documentation": "https://gambitproject.readthedocs.io/", - "Source": "https://github.com/gambitproject/gambit", - "Tracker": "https://github.com/gambitproject/gambit/issues", - }, - python_requires=">=3.9", - install_requires=[ - "numpy", - "scipy", - "deprecated", - ], libraries=[cppgambit_bimatrix, cppgambit_liap, cppgambit_logit, cppgambit_simpdiv, cppgambit_gtracer, cppgambit_enumpoly, cppgambit_games, cppgambit_core], diff --git a/src/core/array.h b/src/core/array.h index 2719aa172..066ade8af 100644 --- a/src/core/array.h +++ b/src/core/array.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/array.h +// FILE: src/core/array.h // A basic bounds-checked array type // // This program is free software; you can redistribute it and/or modify @@ -20,12 +20,14 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#ifndef LIBGAMBIT_ARRAY_H -#define LIBGAMBIT_ARRAY_H +#ifndef GAMBIT_CORE_ARRAY_H +#define GAMBIT_CORE_ARRAY_H #include #include +#include "util.h" + namespace Gambit { /// \brief An extension of std::vector to have arbitrary offset index @@ -282,4 +284,4 @@ template void erase_atindex(Array &p_array, int p_index) } // end namespace Gambit -#endif // LIBGAMBIT_ARRAY_H +#endif // GAMBIT_CORE_ARRAY_H diff --git a/src/solvers/linalg/btableau.cc b/src/core/core.h similarity index 68% rename from src/solvers/linalg/btableau.cc rename to src/core/core.h index a47774b0c..726235445 100644 --- a/src/solvers/linalg/btableau.cc +++ b/src/core/core.h @@ -2,8 +2,8 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/liblinear/btableau.cc -// Instantiation of base tableau classes +// FILE: src/core/core.h +// Core (game theory-independent) declarations and utilities for Gambit // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -20,20 +20,15 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include "gambit.h" -#include "core/matrix.imp" -#include "btableau.imp" +#ifndef GAMBIT_CORE_CORE_H +#define GAMBIT_CORE_CORE_H -namespace Gambit { +#include "util.h" +#include "array.h" +#include "list.h" +#include "recarray.h" +#include "vector.h" +#include "matrix.h" +#include "rational.h" -namespace linalg { - -template class BaseTableau; -template class BaseTableau; - -template class TableauInterface; -template class TableauInterface; - -} // namespace linalg - -} // end namespace Gambit +#endif // GAMBIT_CORE_CORE_H diff --git a/src/core/function.cc b/src/core/function.cc index d18dfbc91..680bfa234 100644 --- a/src/core/function.cc +++ b/src/core/function.cc @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/function.cc +// FILE: src/core/function.cc // Implementation of function and function minimization routines // // These routines derive from the N-dimensional function minimization @@ -28,7 +28,7 @@ // #include -#include "gambit.h" + #include "function.h" using namespace Gambit; @@ -46,7 +46,7 @@ using namespace Gambit; void FunctionOnSimplices::Project(Vector &x, const Array &lengths) const { int index = 1; - for (int part = 1; part <= lengths.size(); part++) { + for (size_t part = 1; part <= lengths.size(); part++) { double avg = 0.0; int j; for (j = 1; j <= lengths[part]; j++, index++) { @@ -70,7 +70,7 @@ void FunctionOnSimplices::Project(Vector &x, const Array &lengths) void ConjugatePRMinimizer::AlphaXPlusY(double alpha, const Vector &x, Vector &y) { - for (int i = 1; i <= y.size(); i++) { + for (size_t i = 1; i <= y.size(); i++) { y[i] += alpha * x[i]; } } @@ -96,7 +96,7 @@ void ConjugatePRMinimizer::IntermediatePoint(const Function &fdf, const Vector &x return; /* MAX ITERATIONS */ } - double dw = w - u; - double dv = v - u; + const double dw = w - u; + const double dv = v - u; double du = 0.0; - double e1 = ((fv - fu) * dw * dw + (fu - fw) * dv * dv); - double e2 = 2.0 * ((fv - fu) * dw + (fu - fw) * dv); + const double e1 = ((fv - fu) * dw * dw + (fu - fw) * dv * dv); + const double e2 = 2.0 * ((fv - fu) * dw + (fu - fw) * dv); if (e2 != 0.0) { du = e1 / e2; @@ -272,7 +272,7 @@ void ConjugatePRMinimizer::Set(const Function &fdf, const Vector &x, dou p = gradient; g0 = gradient; - double gnorm = std::sqrt(gradient.NormSquared()); + const double gnorm = std::sqrt(gradient.NormSquared()); pnorm = gnorm; g0norm = gnorm; } @@ -282,9 +282,12 @@ void ConjugatePRMinimizer::Restart() { iter = 0; } bool ConjugatePRMinimizer::Iterate(const Function &fdf, Vector &x, double &f, Vector &gradient, Vector &dx) { - double fa = f, fb, fc; + const double fa = f; + double fb, fc; double dir; - double stepa = 0.0, stepb, stepc = step, tol = m_tol; + const double stepa = 0.0; + double stepb; + const double stepc = step, tol = m_tol; double g1norm; double pg; diff --git a/src/core/function.h b/src/core/function.h index 3762268ee..0a4e14c18 100644 --- a/src/core/function.h +++ b/src/core/function.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/function.h +// FILE: src/core/function.h // Interface to function and function minimization routines // // This program is free software; you can redistribute it and/or modify @@ -20,10 +20,10 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#ifndef LIBGAMBIT_FUNCTION_H -#define LIBGAMBIT_FUNCTION_H +#ifndef GAMBIT_CORE_FUNCTION_H +#define GAMBIT_CORE_FUNCTION_H -#include "core/vector.h" +#include "vector.h" namespace Gambit { @@ -106,4 +106,4 @@ class ConjugatePRMinimizer : public FunctionMinimizer { } // end namespace Gambit -#endif // LIBGAMBIT_FUNCTION_H +#endif // GAMBIT_CORE_FUNCTION_H diff --git a/src/core/integer.cc b/src/core/integer.cc index aad169b33..2ab3f9774 100644 --- a/src/core/integer.cc +++ b/src/core/integer.cc @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/integer.cc +// FILE: src/core/integer.cc // Implementation of an arbitrary-length integer class // // The original copyright and license are included below. @@ -35,14 +35,14 @@ Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include - -#include "integer.h" #include #include #include #include #include -#include "gambit.h" + +#include "integer.h" +#include "util.h" namespace Gambit { @@ -79,14 +79,14 @@ long lg(unsigned long x) /* All routines assume SHORT_PER_LONG > 1 */ #define SHORT_PER_LONG ((unsigned)(((sizeof(long) + sizeof(short) - 1) / sizeof(short)))) -#define CHAR_PER_LONG ((unsigned)sizeof(long)) +// #define CHAR_PER_LONG ((unsigned)sizeof(long)) /* minimum and maximum sizes for an IntegerRep */ #define MIN_INTREP_SIZE 16 -#define MAX_INTREP_SIZE I_MAXNUM +// #define MAX_INTREP_SIZE I_MAXNUM #ifndef MALLOC_MIN_OVERHEAD #define MALLOC_MIN_OVERHEAD 4 @@ -176,7 +176,7 @@ static inline void nonnil(const IntegerRep *rep) static IntegerRep *Inew(int newlen) { - unsigned int siz = sizeof(IntegerRep) + newlen * sizeof(short) + MALLOC_MIN_OVERHEAD; + const unsigned int siz = sizeof(IntegerRep) + newlen * sizeof(short) + MALLOC_MIN_OVERHEAD; unsigned int allocsiz = MIN_INTREP_SIZE; while (allocsiz < siz) { allocsiz <<= 1; // find a power of 2 @@ -287,7 +287,7 @@ IntegerRep *Icopy(IntegerRep *old, const IntegerRep *src) rep->sgn = I_POSITIVE; } else { - int newlen = src->len; + const int newlen = src->len; if (old == nullptr || newlen > old->sz) { if (old != nullptr && !STATIC_IntegerRep(old)) { delete old; @@ -311,7 +311,7 @@ IntegerRep *Icopy(IntegerRep *old, const IntegerRep *src) IntegerRep *Icopy_long(IntegerRep *old, long x) { - int newsgn = (x >= 0); + const int newsgn = (x >= 0); IntegerRep *rep = Icopy_ulong(old, newsgn ? x : -x); rep->sgn = newsgn; return rep; @@ -427,7 +427,7 @@ long Itolong(const IntegerRep *rep) int Iislong(const IntegerRep *rep) { - unsigned int l = rep->len; + const unsigned int l = rep->len; if (l < SHORT_PER_LONG) { return 1; } @@ -455,7 +455,7 @@ int Iislong(const IntegerRep *rep) double Itodouble(const IntegerRep *rep) { double d = 0.0; - double bound = DBL_MAX / 2.0; + const double bound = DBL_MAX / 2.0; for (int i = rep->len - 1; i >= 0; --i) { auto a = (unsigned short)(I_RADIX >> 1); while (a != 0) { @@ -484,7 +484,7 @@ double Itodouble(const IntegerRep *rep) int Iisdouble(const IntegerRep *rep) { double d = 0.0; - double bound = DBL_MAX / 2.0; + const double bound = DBL_MAX / 2.0; for (int i = rep->len - 1; i >= 0; --i) { auto a = (unsigned short)(I_RADIX >> 1); while (a != 0) { @@ -507,7 +507,7 @@ double ratio(const Integer &num, const Integer &den) { Integer q, r; divide(num, den, q, r); - double d1 = q.as_double(); + const double d1 = q.as_double(); if (d1 >= DBL_MAX || d1 <= -DBL_MAX || sign(r) == 0) { return d1; @@ -581,8 +581,8 @@ int ucompare(const IntegerRep *x, const IntegerRep *y) int compare(const IntegerRep *x, long y) { - int xl = x->len; - int xsgn = x->sgn; + const int xl = x->len; + const int xsgn = x->sgn; if (y == 0) { if (xl == 0) { return 0; @@ -595,7 +595,7 @@ int compare(const IntegerRep *x, long y) } } else { - int ysgn = y >= 0; + const int ysgn = y >= 0; unsigned long uy = (ysgn) ? y : -y; int diff = xsgn - ysgn; if (diff == 0) { @@ -622,7 +622,7 @@ int compare(const IntegerRep *x, long y) int ucompare(const IntegerRep *x, long y) { - int xl = x->len; + const int xl = x->len; if (y == 0) { return xl; } @@ -652,14 +652,14 @@ IntegerRep *add(const IntegerRep *x, int negatex, const IntegerRep *y, int negat nonnil(x); nonnil(y); - int xl = x->len; - int yl = y->len; + const int xl = x->len; + const int yl = y->len; - int xsgn = (negatex && xl != 0) ? !x->sgn : x->sgn; - int ysgn = (negatey && yl != 0) ? !y->sgn : y->sgn; + const int xsgn = (negatex && xl != 0) ? !x->sgn : x->sgn; + const int ysgn = (negatey && yl != 0) ? !y->sgn : y->sgn; - int xrsame = x == r; - int yrsame = y == r; + const int xrsame = x == r; + const int yrsame = y == r; if (yl == 0) { r = Ialloc(r, x->s, xl, xsgn, xl); @@ -713,7 +713,7 @@ IntegerRep *add(const IntegerRep *x, int negatex, const IntegerRep *y, int negat } } else { - int comp = ucompare(x, y); + const int comp = ucompare(x, y); if (comp == 0) { r = Icopy_zero(r); } @@ -768,11 +768,11 @@ IntegerRep *add(const IntegerRep *x, int negatex, const IntegerRep *y, int negat IntegerRep *add(const IntegerRep *x, int negatex, long y, IntegerRep *r) { nonnil(x); - int xl = x->len; - int xsgn = (negatex && xl != 0) ? !x->sgn : x->sgn; - int xrsame = x == r; + const int xl = x->len; + const int xsgn = (negatex && xl != 0) ? !x->sgn : x->sgn; + const int xrsame = x == r; - int ysgn = (y >= 0); + const int ysgn = (y >= 0); unsigned long uy = (ysgn) ? y : -y; if (y == 0) { @@ -794,7 +794,7 @@ IntegerRep *add(const IntegerRep *x, int negatex, long y, IntegerRep *r) const unsigned short *topa = &(as[xl]); unsigned long sum = 0; while (as < topa && uy != 0) { - unsigned long u = extract(uy); + const unsigned long u = extract(uy); uy = down(uy); sum += (unsigned long)(*as++) + u; *rs++ = extract(sum); @@ -880,13 +880,13 @@ IntegerRep *multiply(const IntegerRep *x, const IntegerRep *y, IntegerRep *r) { nonnil(x); nonnil(y); - int xl = x->len; - int yl = y->len; - int rl = xl + yl; - int rsgn = x->sgn == y->sgn; - int xrsame = x == r; - int yrsame = y == r; - int xysame = x == y; + const int xl = x->len; + const int yl = y->len; + const int rl = xl + yl; + const int rsgn = x->sgn == y->sgn; + const int xrsame = x == r; + const int yrsame = y == r; + const int xysame = x == y; if (xl == 0 || yl == 0) { r = Icopy_zero(r); @@ -1026,7 +1026,7 @@ IntegerRep *multiply(const IntegerRep *x, const IntegerRep *y, IntegerRep *r) IntegerRep *multiply(const IntegerRep *x, long y, IntegerRep *r) { nonnil(x); - int xl = x->len; + const int xl = x->len; if (xl == 0 || y == 0) { r = Icopy_zero(r); @@ -1035,8 +1035,8 @@ IntegerRep *multiply(const IntegerRep *x, long y, IntegerRep *r) r = Icopy(r, x); } else { - int ysgn = y >= 0; - int rsgn = x->sgn == ysgn; + const int ysgn = y >= 0; + const int rsgn = x->sgn == ysgn; unsigned long uy = (ysgn) ? y : -y; unsigned short tmp[SHORT_PER_LONG]; int yl = 0; @@ -1045,8 +1045,8 @@ IntegerRep *multiply(const IntegerRep *x, long y, IntegerRep *r) uy = down(uy); } - int rl = xl + yl; - int xrsame = x == r; + const int rl = xl + yl; + const int xrsame = x == r; if (xrsame) { r = Iresize(r, rl); } @@ -1115,8 +1115,8 @@ static void do_divide(unsigned short *rs, const unsigned short *ys, int yl, unsi int ql) { const unsigned short *topy = &(ys[yl]); - unsigned short d1 = ys[yl - 1]; - unsigned short d2 = ys[yl - 2]; + const unsigned short d1 = ys[yl - 1]; + const unsigned short d2 = ys[yl - 2]; int l = ql - 1; int i = l + yl; @@ -1127,7 +1127,7 @@ static void do_divide(unsigned short *rs, const unsigned short *ys, int yl, unsi qhat = (unsigned short)I_MAXNUM; } else { - unsigned long lr = up((unsigned long)rs[i]) | rs[i - 1]; + const unsigned long lr = up((unsigned long)rs[i]) | rs[i - 1]; qhat = (unsigned short)(lr / d1); } @@ -1197,11 +1197,11 @@ static int unscale(const unsigned short *x, int xl, unsigned short y, unsigned s unsigned long rem = 0; while (qs >= botq) { rem = up(rem) | *xs--; - unsigned long u = rem / y; + const unsigned long u = rem / y; *qs-- = extract(u); rem -= u * y; } - int r = extract(rem); + const int r = extract(rem); return r; } else // same loop, a bit faster if just need rem @@ -1211,10 +1211,10 @@ static int unscale(const unsigned short *x, int xl, unsigned short y, unsigned s unsigned long rem = 0; while (xs >= botx) { rem = up(rem) | *xs--; - unsigned long u = rem / y; + const unsigned long u = rem / y; rem -= u * y; } - int r = extract(rem); + const int r = extract(rem); return r; } } @@ -1223,17 +1223,17 @@ IntegerRep *div(const IntegerRep *x, const IntegerRep *y, IntegerRep *q) { nonnil(x); nonnil(y); - int xl = x->len; - int yl = y->len; + const int xl = x->len; + const int yl = y->len; if (yl == 0) { throw Gambit::ZeroDivideException(); } - int comp = ucompare(x, y); - int xsgn = x->sgn; - int ysgn = y->sgn; + const int comp = ucompare(x, y); + const int xsgn = x->sgn; + const int ysgn = y->sgn; - int samesign = xsgn == ysgn; + const int samesign = xsgn == ysgn; if (comp < 0) { q = Icopy_zero(q); @@ -1259,7 +1259,7 @@ IntegerRep *div(const IntegerRep *x, const IntegerRep *y, IntegerRep *q) scpy(x->s, r->s, xl); } - int ql = xl - yl + 1; + const int ql = xl - yl + 1; q = Icalloc(q, ql); do_divide(r->s, yy->s, yl, q->s, ql); @@ -1279,14 +1279,14 @@ IntegerRep *div(const IntegerRep *x, const IntegerRep *y, IntegerRep *q) IntegerRep *div(const IntegerRep *x, long y, IntegerRep *q) { nonnil(x); - int xl = x->len; + const int xl = x->len; if (y == 0) { throw Gambit::ZeroDivideException(); } unsigned short ys[SHORT_PER_LONG]; unsigned long u; - int ysgn = y >= 0; + const int ysgn = y >= 0; if (ysgn) { u = y; } @@ -1304,8 +1304,8 @@ IntegerRep *div(const IntegerRep *x, long y, IntegerRep *q) comp = docmp(x->s, ys, xl); } - int xsgn = x->sgn; - int samesign = xsgn == ysgn; + const int xsgn = x->sgn; + const int samesign = xsgn == ysgn; if (comp < 0) { q = Icopy_zero(q); @@ -1332,7 +1332,7 @@ IntegerRep *div(const IntegerRep *x, long y, IntegerRep *q) scpy(x->s, r->s, xl); } - int ql = xl - yl + 1; + const int ql = xl - yl + 1; q = Icalloc(q, ql); do_divide(r->s, ys, yl, q->s, ql); @@ -1351,13 +1351,13 @@ void divide(const Integer &Ix, long y, Integer &Iq, long &rem) const IntegerRep *x = Ix.rep; nonnil(x); IntegerRep *q = Iq.rep; - int xl = x->len; + const int xl = x->len; if (y == 0) { throw Gambit::ZeroDivideException(); } unsigned short ys[SHORT_PER_LONG]; unsigned long u; - int ysgn = y >= 0; + const int ysgn = y >= 0; if (ysgn) { u = y; } @@ -1375,8 +1375,8 @@ void divide(const Integer &Ix, long y, Integer &Iq, long &rem) comp = docmp(x->s, ys, xl); } - int xsgn = x->sgn; - int samesign = xsgn == ysgn; + const int xsgn = x->sgn; + const int samesign = xsgn == ysgn; if (comp < 0) { rem = Itolong(x); @@ -1405,7 +1405,7 @@ void divide(const Integer &Ix, long y, Integer &Iq, long &rem) scpy(x->s, r->s, xl); } - int ql = xl - yl + 1; + const int ql = xl - yl + 1; q = Icalloc(q, ql); @@ -1439,16 +1439,16 @@ void divide(const Integer &Ix, const Integer &Iy, Integer &Iq, Integer &Ir) IntegerRep *q = Iq.rep; IntegerRep *r = Ir.rep; - int xl = x->len; - int yl = y->len; + const int xl = x->len; + const int yl = y->len; if (yl == 0) { throw Gambit::ZeroDivideException(); } - int comp = ucompare(x, y); - int xsgn = x->sgn; - int ysgn = y->sgn; + const int comp = ucompare(x, y); + const int xsgn = x->sgn; + const int ysgn = y->sgn; - int samesign = xsgn == ysgn; + const int samesign = xsgn == ysgn; if (comp < 0) { q = Icopy_zero(q); @@ -1460,7 +1460,7 @@ void divide(const Integer &Ix, const Integer &Iy, Integer &Iq, Integer &Ir) } else if (yl == 1) { q = Icopy(q, x); - int rem = unscale(q->s, q->len, y->s[0], q->s); + const int rem = unscale(q->s, q->len, y->s[0], q->s); r = Icopy_long(r, rem); if (rem != 0) { r->sgn = xsgn; @@ -1479,7 +1479,7 @@ void divide(const Integer &Ix, const Integer &Iy, Integer &Iq, Integer &Ir) scpy(x->s, r->s, xl); } - int ql = xl - yl + 1; + const int ql = xl - yl + 1; q = Icalloc(q, ql); do_divide(r->s, yy->s, yl, q->s, ql); @@ -1503,15 +1503,15 @@ IntegerRep *mod(const IntegerRep *x, const IntegerRep *y, IntegerRep *r) { nonnil(x); nonnil(y); - int xl = x->len; - int yl = y->len; + const int xl = x->len; + const int yl = y->len; // if (yl == 0) (*lib_error_handler)("Integer", "attempted division by zero"); if (yl == 0) { throw Gambit::ZeroDivideException(); } - int comp = ucompare(x, y); - int xsgn = x->sgn; + const int comp = ucompare(x, y); + const int xsgn = x->sgn; if (comp < 0) { r = Icopy(r, x); @@ -1520,7 +1520,7 @@ IntegerRep *mod(const IntegerRep *x, const IntegerRep *y, IntegerRep *r) r = Icopy_zero(r); } else if (yl == 1) { - int rem = unscale(x->s, xl, y->s[0], nullptr); + const int rem = unscale(x->s, xl, y->s[0], nullptr); r = Icopy_long(r, rem); if (rem != 0) { r->sgn = xsgn; @@ -1557,13 +1557,13 @@ IntegerRep *mod(const IntegerRep *x, const IntegerRep *y, IntegerRep *r) IntegerRep *mod(const IntegerRep *x, long y, IntegerRep *r) { nonnil(x); - int xl = x->len; + const int xl = x->len; if (y == 0) { throw Gambit::ZeroDivideException(); } unsigned short ys[SHORT_PER_LONG]; unsigned long u; - int ysgn = y >= 0; + const int ysgn = y >= 0; if (ysgn) { u = y; } @@ -1581,7 +1581,7 @@ IntegerRep *mod(const IntegerRep *x, long y, IntegerRep *r) comp = docmp(x->s, ys, xl); } - int xsgn = x->sgn; + const int xsgn = x->sgn; if (comp < 0) { r = Icopy(r, x); @@ -1590,7 +1590,7 @@ IntegerRep *mod(const IntegerRep *x, long y, IntegerRep *r) r = Icopy_zero(r); } else if (yl == 1) { - int rem = unscale(x->s, xl, ys[0], nullptr); + const int rem = unscale(x->s, xl, ys[0], nullptr); r = Icopy_long(r, rem); if (rem != 0) { r->sgn = xsgn; @@ -1624,21 +1624,21 @@ IntegerRep *mod(const IntegerRep *x, long y, IntegerRep *r) IntegerRep *lshift(const IntegerRep *x, long y, IntegerRep *r) { nonnil(x); - int xl = x->len; + const int xl = x->len; if (xl == 0 || y == 0) { r = Icopy(r, x); return r; } - int xrsame = x == r; - int rsgn = x->sgn; + const int xrsame = x == r; + const int rsgn = x->sgn; - long ay = (y < 0) ? -y : y; - int bw = (int)(ay / I_SHIFT); - int sw = (int)(ay % I_SHIFT); + const long ay = (y < 0) ? -y : y; + const int bw = (int)(ay / I_SHIFT); + const int sw = (int)(ay % I_SHIFT); if (y > 0) { - int rl = bw + xl + 1; + const int rl = bw + xl + 1; if (xrsame) { r = Iresize(r, rl); } @@ -1661,7 +1661,7 @@ IntegerRep *lshift(const IntegerRep *x, long y, IntegerRep *r) } } else { - int rl = xl - bw; + const int rl = xl - bw; if (rl < 0) { r = Icopy_zero(r); } @@ -1672,7 +1672,7 @@ IntegerRep *lshift(const IntegerRep *x, long y, IntegerRep *r) else { r = Icalloc(r, rl); } - int rw = I_SHIFT - sw; + const int rw = I_SHIFT - sw; unsigned short *rs = r->s; unsigned short *topr = &(rs[rl]); const unsigned short *botx = (xrsame) ? rs : x->s; @@ -1712,11 +1712,11 @@ IntegerRep *bitop(const IntegerRep *x, const IntegerRep *y, IntegerRep *r, char { nonnil(x); nonnil(y); - int xl = x->len; - int yl = y->len; - int xsgn = x->sgn; - int xrsame = x == r; - int yrsame = y == r; + const int xl = x->len; + const int yl = y->len; + const int xsgn = x->sgn; + const int xrsame = x == r; + const int yrsame = y == r; if (xrsame || yrsame) { r = Iresize(r, calc_len(xl, yl, 0)); } @@ -1775,7 +1775,7 @@ IntegerRep *bitop(const IntegerRep *x, long y, IntegerRep *r, char op) nonnil(x); unsigned short tmp[SHORT_PER_LONG]; unsigned long u; - int newsgn = (y >= 0); + const int newsgn = (y >= 0); if (newsgn) { u = y; } @@ -1789,10 +1789,10 @@ IntegerRep *bitop(const IntegerRep *x, long y, IntegerRep *r, char op) u = down(u); } - int xl = x->len; - int yl = l; - int xsgn = x->sgn; - int xrsame = x == r; + const int xl = x->len; + const int yl = l; + const int xsgn = x->sgn; + const int xrsame = x == r; if (xrsame) { r = Iresize(r, calc_len(xl, yl, 0)); } @@ -1853,7 +1853,7 @@ IntegerRep *Compl(const IntegerRep *src, IntegerRep *r) unsigned short *s = r->s; unsigned short *top = &(s[r->len - 1]); while (s < top) { - unsigned short cmp = ~(*s); + const unsigned short cmp = ~(*s); *s++ = cmp; } unsigned short a = *s; @@ -1873,9 +1873,9 @@ IntegerRep *Compl(const IntegerRep *src, IntegerRep *r) void(setbit)(Integer &x, long b) { if (b >= 0) { - int bw = (int)((unsigned long)b / I_SHIFT); - int sw = (int)((unsigned long)b % I_SHIFT); - int xl = x.rep ? x.rep->len : 0; + const int bw = (int)((unsigned long)b / I_SHIFT); + const int sw = (int)((unsigned long)b % I_SHIFT); + const int xl = x.rep ? x.rep->len : 0; if (xl <= bw) { x.rep = Iresize(x.rep, calc_len(xl, bw + 1, 0)); } @@ -1891,8 +1891,8 @@ void clearbit(Integer &x, long b) x.rep = &ZeroRep; } else { - int bw = (int)((unsigned long)b / I_SHIFT); - int sw = (int)((unsigned long)b % I_SHIFT); + const int bw = (int)((unsigned long)b / I_SHIFT); + const int sw = (int)((unsigned long)b % I_SHIFT); if (x.rep->len > bw) { x.rep->s[bw] &= ~(1 << sw); } @@ -1904,8 +1904,8 @@ void clearbit(Integer &x, long b) int testbit(const Integer &x, long b) { if (x.rep != nullptr && b >= 0) { - int bw = (int)((unsigned long)b / I_SHIFT); - int sw = (int)((unsigned long)b % I_SHIFT); + const int bw = (int)((unsigned long)b / I_SHIFT); + const int sw = (int)((unsigned long)b % I_SHIFT); return (bw < x.rep->len && (x.rep->s[bw] & (1 << sw)) != 0); } else { @@ -1920,8 +1920,8 @@ IntegerRep *gcd(const IntegerRep *x, const IntegerRep *y) { nonnil(x); nonnil(y); - int ul = x->len; - int vl = y->len; + const int ul = x->len; + const int vl = y->len; if (vl == 0) { return Ialloc(nullptr, x->s, ul, I_POSITIVE, ul); @@ -1936,7 +1936,7 @@ IntegerRep *gcd(const IntegerRep *x, const IntegerRep *y) // find shift so that both not even long k = 0; - int l = (ul <= vl) ? ul : vl; + const int l = (ul <= vl) ? ul : vl; int cont = 1, i; for (i = 0; i < l && cont; ++i) { unsigned long a = (i < ul) ? u->s[i] : 0; @@ -1970,7 +1970,7 @@ IntegerRep *gcd(const IntegerRep *x, const IntegerRep *y) while (t->len != 0) { long s = 0; // shift t until odd cont = 1; - int tl = t->len; + const int tl = t->len; for (i = 0; i < tl && cont; ++i) { unsigned long a = t->s[i]; for (unsigned int j = 0; j < I_SHIFT; ++j) { @@ -2013,7 +2013,7 @@ IntegerRep *gcd(const IntegerRep *x, const IntegerRep *y) long lg(const IntegerRep *x) { nonnil(x); - int xl = x->len; + const int xl = x->len; if (xl == 0) { return 0; } @@ -2039,7 +2039,7 @@ IntegerRep *power(const IntegerRep *x, long y, IntegerRep *r) sgn = I_NEGATIVE; } - int xl = x->len; + const int xl = x->len; if (y == 0 || (xl == 1 && x->s[0] == 1)) { r = Icopy_one(r, sgn); @@ -2051,7 +2051,7 @@ IntegerRep *power(const IntegerRep *x, long y, IntegerRep *r) r = Icopy(r, x); } else { - int maxsize = (int)(((lg(x) + 1) * y) / I_SHIFT + 2); // pre-allocate space + const int maxsize = (int)(((lg(x) + 1) * y) / I_SHIFT + 2); // pre-allocate space IntegerRep *b = Ialloc(nullptr, x->s, xl, I_POSITIVE, maxsize); b->len = xl; r = Icalloc(r, maxsize); @@ -2103,7 +2103,7 @@ IntegerRep *negate(const IntegerRep *src, IntegerRep *dest) Integer sqrt(const Integer &x) { Integer r; - int s = sign(x); + const int s = sign(x); if (s < 0) { x.error("Attempted square root of negative Integer"); } @@ -2181,7 +2181,7 @@ Integer lcm(const Integer &x, const Integer &y) IntegerRep *atoIntegerRep(const char *s, int base) { - int sl = strlen(s); + const int sl = strlen(s); IntegerRep *r = Icalloc(nullptr, (int)(sl * (lg(base) + 1) / I_SHIFT + 1)); if (s != nullptr) { char sgn; @@ -2359,7 +2359,7 @@ std::istream &operator>>(std::istream &s, Integer &y) } else { if (ch >= '0' && ch <= '9') { - long digit = ch - '0'; + const long digit = ch - '0'; y *= 10; y += digit; } @@ -2380,8 +2380,8 @@ std::istream &operator>>(std::istream &s, Integer &y) int Integer::OK() const { if (rep != nullptr) { - int l = rep->len; - int s = rep->sgn; + const int l = rep->len; + const int s = rep->sgn; int v = l <= rep->sz || STATIC_IntegerRep(rep); // length within bounds v &= s == 0 || s == 1; // legal sign Icheck(rep); // and correctly adjusted diff --git a/src/core/integer.h b/src/core/integer.h index 51b28e33c..7a67d2d11 100644 --- a/src/core/integer.h +++ b/src/core/integer.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/integer.h +// FILE: src/core/integer.h // Interface to an arbitrary-length integer class // // The original copyright and license are included below. @@ -25,8 +25,8 @@ License along with this library; if not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ -#ifndef LIBGAMBIT_INTEGER_H -#define LIBGAMBIT_INTEGER_H +#ifndef GAMBIT_CORE_INTEGER_H +#define GAMBIT_CORE_INTEGER_H #include @@ -249,4 +249,4 @@ extern Integer lcm(const Integer &x, const Integer &y); // least common mult } // end namespace Gambit -#endif // LIBGAMBIT_INTEGER_H +#endif // GAMBIT_CORE_INTEGER_H diff --git a/src/core/list.h b/src/core/list.h index 12225530e..64d6718a4 100644 --- a/src/core/list.h +++ b/src/core/list.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/list.h +// FILE: src/core/list.h // A generic linked-list container class // // This program is free software; you can redistribute it and/or modify @@ -20,8 +20,8 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#ifndef LIBGAMBIT_LIST_H -#define LIBGAMBIT_LIST_H +#ifndef GAMBIT_CORE_LIST_H +#define GAMBIT_CORE_LIST_H #include @@ -129,4 +129,4 @@ template class List { } // namespace Gambit -#endif // LIBGAMBIT_LIST_H +#endif // GAMBIT_CORE_LIST_H diff --git a/src/core/matrix.cc b/src/core/matrix.cc index 54189fa35..29ec79b30 100644 --- a/src/core/matrix.cc +++ b/src/core/matrix.cc @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/matrix.cc +// FILE: src/core/matrix.cc // Instantiation of common matrix types // // This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include "gambit.h" +#include "core.h" #include "matrix.imp" namespace Gambit { diff --git a/src/core/matrix.h b/src/core/matrix.h index 241c51645..fecad1d55 100644 --- a/src/core/matrix.h +++ b/src/core/matrix.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/matrix.h +// FILE: src/core/matrix.h // Interface to a matrix class // // This program is free software; you can redistribute it and/or modify @@ -20,8 +20,8 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#ifndef LIBGAMBIT_MATRIX_H -#define LIBGAMBIT_MATRIX_H +#ifndef GAMBIT_CORE_MATRIX_H +#define GAMBIT_CORE_MATRIX_H #include "recarray.h" #include "vector.h" @@ -80,14 +80,10 @@ template class Matrix : public RectArray { Matrix operator*(const Matrix &) const; Vector operator*(const Vector &) const; Matrix operator*(const T &) const; - Matrix &operator*=(const Matrix &); Matrix &operator*=(const T &); Matrix operator/(const T &) const; Matrix &operator/=(const T &); - - /// Kronecker product - Matrix operator&(const Matrix &) const; //@ /// @name Other operations @@ -103,4 +99,4 @@ template Vector operator*(const Vector &, const Matrix &); } // end namespace Gambit -#endif // LIBGAMBIT_MATRIX_H +#endif // GAMBIT_CORE_MATRIX_H diff --git a/src/core/matrix.imp b/src/core/matrix.imp index 41f432619..9922b5e43 100644 --- a/src/core/matrix.imp +++ b/src/core/matrix.imp @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/matrix.imp +// FILE: src/core/matrix.imp // Implementation of matrix method functions // // This program is free software; you can redistribute it and/or modify @@ -86,7 +86,7 @@ template Matrix Matrix::operator+(const Matrix &M) const throw DimensionException(); } - Matrix tmp(this->minrow, this->maxrow, this->mincol, this->maxcol); + const Matrix tmp(this->minrow, this->maxrow, this->mincol, this->maxcol); for (int i = this->minrow; i <= this->maxrow; i++) { T *src1 = this->data[i] + this->mincol; T *src2 = M.data[i] + this->mincol; @@ -106,7 +106,7 @@ template Matrix Matrix::operator-(const Matrix &M) const throw DimensionException(); } - Matrix tmp(this->minrow, this->maxrow, this->mincol, this->maxcol); + const Matrix tmp(this->minrow, this->maxrow, this->mincol, this->maxcol); for (int i = this->minrow; i <= this->maxrow; i++) { T *src1 = this->data[i] + this->mincol; T *src2 = M.data[i] + this->mincol; @@ -240,7 +240,7 @@ template Vector operator*(const Vector &v, const Matrix &M) template Matrix Matrix::operator*(const T &s) const { - Matrix tmp(this->minrow, this->maxrow, this->mincol, this->maxcol); + const Matrix tmp(this->minrow, this->maxrow, this->mincol, this->maxcol); for (int i = this->minrow; i <= this->maxrow; i++) { T *src = this->data[i] + this->mincol; T *dst = tmp.data[i] + this->mincol; @@ -253,26 +253,6 @@ template Matrix Matrix::operator*(const T &s) const return tmp; } -// in-place multiplication by square matrix -template Matrix &Matrix::operator*=(const Matrix &M) -{ - if (this->mincol != M.minrow || this->maxcol != M.maxrow) { - throw DimensionException(); - } - if (M.minrow != M.mincol || M.maxrow != M.maxcol) { - throw DimensionException(); - } - - Vector row(this->mincol, this->maxcol); - Vector result(this->mincol, this->maxcol); - for (int i = this->minrow; i <= this->maxrow; i++) { - this->GetRow(i, row); - M.RMultiply(row, result); - this->SetRow(i, result); - } - return (*this); -} - template Matrix &Matrix::operator*=(const T &s) { for (int i = this->minrow; i <= this->maxrow; i++) { @@ -281,7 +261,6 @@ template Matrix &Matrix::operator*=(const T &s) while (j--) { *(dst++) *= s; } - // assert((dst - 1) == this->data[i] + this->maxcol); } return (*this); } @@ -292,7 +271,7 @@ template Matrix Matrix::operator/(const T &s) const throw ZeroDivideException(); } - Matrix tmp(this->minrow, this->maxrow, this->mincol, this->maxcol); + const Matrix tmp(this->minrow, this->maxrow, this->mincol, this->maxcol); for (int i = this->minrow; i <= this->maxrow; i++) { T *src = this->data[i] + this->mincol; T *dst = tmp.data[i] + this->mincol; @@ -300,7 +279,6 @@ template Matrix Matrix::operator/(const T &s) const while (j--) { *(dst++) = *(src++) / s; } - // assert((src - 1) == this->data[i] + this->maxcol); } return tmp; } @@ -317,35 +295,10 @@ template Matrix &Matrix::operator/=(const T &s) while (j--) { *(dst++) /= s; } - // assert((dst - 1) == this->data[i] + this->maxcol); } return (*this); } -//------------------------------------------------------------------------- -// Matrix: Kronecker Product -//------------------------------------------------------------------------- - -template Matrix Matrix::operator&(const Matrix &M) const -{ - Matrix tmp(1, (this->maxrow - this->minrow + 1) * (M.maxrow - M.minrow + 1), 1, - (this->maxcol - this->mincol + 1) * (M.maxcol - M.mincol + 1)); - - for (int i = 0; i <= this->maxrow - this->minrow; i++) { - for (int j = 1; j <= M.maxrow - M.minrow + 1; j++) { - for (int k = 0; k <= this->maxcol - this->mincol; k++) { - for (int l = 1; l <= M.maxcol - M.mincol + 1; l++) { - - tmp((M.maxrow - M.minrow + 1) * i + j, (M.maxcol - M.mincol + 1) * k + l) = - (*this)(i + this->minrow, k + this->mincol) * M(j + M.minrow - 1, l + M.mincol - 1); - } - } - } - } - - return tmp; -} - //------------------------------------------------------------------------- // Matrix: Transpose //------------------------------------------------------------------------- diff --git a/src/core/rational.cc b/src/core/rational.cc index b80559fd8..3eb24558e 100644 --- a/src/core/rational.cc +++ b/src/core/rational.cc @@ -2,7 +2,7 @@ // This file is part of Gambit Copyright (c) 1994-2025, The Gambit // Project (https://www.gambit-project.org) // -// FILE: src/libgambit/rational.cc +// FILE: src/core/rational.cc // Implementation of a rational number class // // @@ -26,20 +26,20 @@ Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include - -#include "gambit.h" -#include "rational.h" #include #include #include +#include "util.h" +#include "rational.h" + namespace Gambit { static const Integer Int_One(1); void Rational::normalize() { - int s = sign(den); + const int s = sign(den); if (s == 0) { throw ZeroDivideException(); } @@ -48,7 +48,7 @@ void Rational::normalize() num.negate(); } - Integer g = gcd(num, den); + const Integer g = gcd(num, den); if (ucompare(g, Int_One) != 0) { num /= g; den /= g; @@ -115,10 +115,10 @@ void div(const Rational &x, const Rational &y, Rational &r) void Rational::invert() { - Integer tmp = num; + const Integer tmp = num; num = den; den = tmp; - int s = sign(den); + const int s = sign(den); if (s == 0) { throw ZeroDivideException(); } @@ -130,8 +130,8 @@ void Rational::invert() int compare(const Rational &x, const Rational &y) { - int xsgn = sign(x.num); - int ysgn = sign(y.num); + const int xsgn = sign(x.num); + const int ysgn = sign(y.num); int d = xsgn - ysgn; if (d == 0 && xsgn != 0) { d = compare(x.num * y.den, x.den * y.num); @@ -142,7 +142,7 @@ int compare(const Rational &x, const Rational &y) Rational::Rational(double x) : num(0), den(1) { if (x != 0.0) { - int neg = x < 0; + const int neg = x < 0; if (neg) { x = -x; } @@ -180,7 +180,7 @@ Integer trunc(const Rational &x) { return x.num / x.den; } Rational pow(const Rational &x, const Integer &y) { - long yy = y.as_long(); + const long yy = y.as_long(); return pow(x, yy); } diff --git a/src/core/rational.h b/src/core/rational.h index f09bd6575..780411d58 100644 --- a/src/core/rational.h +++ b/src/core/rational.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/rational.h +// FILE: src/core/rational.h // Interface to a rational number class // // The original copyright and license are included below. @@ -25,12 +25,13 @@ License along with this library; if not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ -#ifndef LIBGAMBIT_RATIONAL_H -#define LIBGAMBIT_RATIONAL_H +#ifndef GAMBIT_CORE_RATIONAL_H +#define GAMBIT_CORE_RATIONAL_H -#include "integer.h" #include +#include "integer.h" + namespace Gambit { /// A representation of an arbitrary-precision rational number @@ -120,4 +121,4 @@ inline int sign(const Rational &x) { return sign(x.num); } } // end namespace Gambit -#endif // LIBGAMBIT_RATIONAL_H +#endif // GAMBIT_CORE_RATIONAL_H diff --git a/src/core/recarray.h b/src/core/recarray.h index a4d2b8e1e..f809b8817 100644 --- a/src/core/recarray.h +++ b/src/core/recarray.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/recarray.h +// FILE: src/core/recarray.h // Rectangular array base class // // This program is free software; you can redistribute it and/or modify @@ -20,10 +20,10 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#ifndef LIBGAMBIT_RECARRAY_H -#define LIBGAMBIT_RECARRAY_H +#ifndef GAMBIT_CORE_RECARRAY_H +#define GAMBIT_CORE_RECARRAY_H -#include "gambit.h" +#include "array.h" namespace Gambit { @@ -33,10 +33,35 @@ template class RectArray { int minrow, maxrow, mincol, maxcol; T **data; + /// @name Range checking functions; returns true only if valid index/size + //@{ + /// check for correct row index + bool CheckRow(int row) const { return (minrow <= row && row <= maxrow); } + /// check row vector for correct column boundaries + bool CheckRow(const Array &v) const + { + return (v.first_index() == mincol && v.last_index() == maxcol); + } + /// check for correct column index + bool CheckColumn(int col) const { return (mincol <= col && col <= maxcol); } + /// check column vector for correct row boundaries + bool CheckColumn(const Array &v) const + { + return (v.first_index() == minrow && v.last_index() == maxrow); + } + /// check row and column indices + bool Check(int row, int col) const { return CheckRow(row) && CheckColumn(col); } + /// check matrix for same row and column boundaries + bool CheckBounds(const RectArray &m) const + { + return (minrow == m.minrow && maxrow == m.maxrow && mincol == m.mincol && maxcol == m.maxcol); + } + //@} + public: /// @name Lifecycle //@{ - RectArray(); + RectArray() : minrow(1), maxrow(0), mincol(1), maxcol(0), data(nullptr) {} RectArray(unsigned int nrows, unsigned int ncols); RectArray(int minr, int maxr, int minc, int maxc); RectArray(const RectArray &); @@ -47,117 +72,72 @@ template class RectArray { /// @name General data access //@{ - int NumRows() const; - int NumColumns() const; - int MinRow() const; - int MaxRow() const; - int MinCol() const; - int MaxCol() const; + size_t NumRows() const { return maxrow - minrow + 1; } + size_t NumColumns() const { return maxcol - mincol + 1; } + int MinRow() const { return minrow; } + int MaxRow() const { return maxrow; } + int MinCol() const { return mincol; } + int MaxCol() const { return maxcol; } //@} /// @name Indexing operations //@{ - T &operator()(int r, int c); - const T &operator()(int r, int c) const; + T &operator()(int r, int c) + { + if (!Check(r, c)) { + throw IndexException(); + } + return data[r][c]; + } + const T &operator()(int r, int c) const + { + if (!Check(r, c)) { + throw IndexException(); + } + return data[r][c]; + } //@} /// @name Row and column rotation operators //@{ void RotateUp(int lo, int hi); void RotateDown(int lo, int hi); - void RotateLeft(int lo, int hi); - void RotateRight(int lo, int hi); //@} /// @name Row and column manipulation functions //@{ - void SwitchRow(int, Array &); - void SwitchRows(int, int); + void SwitchRows(int i, int j) + { + if (!CheckRow(i) || !CheckRow(j)) { + throw IndexException(); + } + std::swap(data[i], data[j]); + } void GetRow(int, Array &) const; - void SetRow(int, const Array &); - void SwitchColumn(int, Array &); - void SwitchColumns(int, int); void GetColumn(int, Array &) const; void SetColumn(int, const Array &); - - /// Returns the transpose of the rectangular array - RectArray Transpose() const; - - /// @name Range checking functions; returns true only if valid index/size - //@{ - /// check for correct row index - bool CheckRow(int row) const; - /// check row vector for correct column boundaries - bool CheckRow(const Array &) const; - /// check for correct column index - bool CheckColumn(int col) const; - /// check column vector for correct row boundaries - bool CheckColumn(const Array &) const; - /// check row and column indices - bool Check(int row, int col) const; - /// check matrix for same row and column boundaries - bool CheckBounds(const RectArray &) const; - //@ + //@} }; -//------------------------------------------------------------------------ -// RectArray: Bounds-checking member functions -//------------------------------------------------------------------------ - -template bool RectArray::CheckRow(int row) const -{ - return (minrow <= row && row <= maxrow); -} - -template bool RectArray::CheckRow(const Array &v) const -{ - return (v.first_index() == mincol && v.last_index() == maxcol); -} - -template bool RectArray::CheckColumn(int col) const -{ - return (mincol <= col && col <= maxcol); -} - -template bool RectArray::CheckColumn(const Array &v) const -{ - return (v.first_index() == minrow && v.last_index() == maxrow); -} - -template bool RectArray::Check(int row, int col) const -{ - return (CheckRow(row) && CheckColumn(col)); -} - -template bool RectArray::CheckBounds(const RectArray &m) const -{ - return (minrow == m.minrow && maxrow == m.maxrow && mincol == m.mincol && maxcol == m.maxcol); -} - //------------------------------------------------------------------------ // RectArray: Constructors, destructor, constructive operators //------------------------------------------------------------------------ -template -RectArray::RectArray() : minrow(1), maxrow(0), mincol(1), maxcol(0), data(nullptr) -{ -} - template RectArray::RectArray(unsigned int rows, unsigned int cols) - : minrow(1), maxrow(rows), mincol(1), maxcol(cols) + : minrow(1), maxrow(rows), mincol(1), maxcol(cols), + data((rows > 0) ? new T *[maxrow] - 1 : nullptr) { - data = (rows > 0) ? new T *[maxrow] - 1 : nullptr; for (int i = 1; i <= maxrow; data[i++] = (cols > 0) ? new T[maxcol] - 1 : nullptr) ; } template RectArray::RectArray(int minr, int maxr, int minc, int maxc) - : minrow(minr), maxrow(maxr), mincol(minc), maxcol(maxc) + : minrow(minr), maxrow(maxr), mincol(minc), maxcol(maxc), + data((maxrow >= minrow) ? new T *[maxrow - minrow + 1] - minrow : nullptr) { - data = (maxrow >= minrow) ? new T *[maxrow - minrow + 1] - minrow : nullptr; for (int i = minrow; i <= maxrow; data[i++] = (maxcol - mincol + 1) ? new T[maxcol - mincol + 1] - mincol : nullptr) ; @@ -165,9 +145,9 @@ RectArray::RectArray(int minr, int maxr, int minc, int maxc) template RectArray::RectArray(const RectArray &a) - : minrow(a.minrow), maxrow(a.maxrow), mincol(a.mincol), maxcol(a.maxcol) + : minrow(a.minrow), maxrow(a.maxrow), mincol(a.mincol), maxcol(a.maxcol), + data((maxrow >= minrow) ? new T *[maxrow - minrow + 1] - minrow : nullptr) { - data = (maxrow >= minrow) ? new T *[maxrow - minrow + 1] - minrow : nullptr; for (int i = minrow; i <= maxrow; i++) { data[i] = (maxcol >= mincol) ? new T[maxcol - mincol + 1] - mincol : nullptr; for (int j = mincol; j <= maxcol; j++) { @@ -191,8 +171,7 @@ template RectArray::~RectArray() template RectArray &RectArray::operator=(const RectArray &a) { if (this != &a) { - int i; - for (i = minrow; i <= maxrow; i++) { + for (int i = minrow; i <= maxrow; i++) { if (data[i]) { delete[] (data[i] + mincol); } @@ -208,7 +187,7 @@ template RectArray &RectArray::operator=(const RectArray &a) data = (maxrow >= minrow) ? new T *[maxrow - minrow + 1] - minrow : nullptr; - for (i = minrow; i <= maxrow; i++) { + for (int i = minrow; i <= maxrow; i++) { data[i] = (maxcol >= mincol) ? new T[maxcol - mincol + 1] - mincol : nullptr; for (int j = mincol; j <= maxcol; j++) { data[i][j] = a.data[i][j]; @@ -219,37 +198,6 @@ template RectArray &RectArray::operator=(const RectArray &a) return *this; } -//------------------------------------------------------------------------ -// RectArray: Data access members -//------------------------------------------------------------------------ - -template int RectArray::NumRows() const { return maxrow - minrow + 1; } - -template int RectArray::NumColumns() const { return maxcol - mincol + 1; } - -template int RectArray::MinRow() const { return minrow; } -template int RectArray::MaxRow() const { return maxrow; } -template int RectArray::MinCol() const { return mincol; } -template int RectArray::MaxCol() const { return maxcol; } - -template T &RectArray::operator()(int r, int c) -{ - if (!Check(r, c)) { - throw IndexException(); - } - - return data[r][c]; -} - -template const T &RectArray::operator()(int r, int c) const -{ - if (!Check(r, c)) { - throw IndexException(); - } - - return data[r][c]; -} - //------------------------------------------------------------------------ // RectArray: Row and column rotation //------------------------------------------------------------------------ @@ -280,71 +228,10 @@ template void RectArray::RotateDown(int lo, int hi) data[lo] = temp; } -template void RectArray::RotateLeft(int lo, int hi) -{ - if (lo < mincol || hi < lo || maxcol < hi) { - throw IndexException(); - } - - T temp; - for (int i = minrow; i <= maxrow; i++) { - T *row = data[i]; - temp = row[lo]; - for (int j = lo; j < hi; j++) { - row[j] = row[j + 1]; - } - row[hi] = temp; - } -} - -template void RectArray::RotateRight(int lo, int hi) -{ - if (lo < mincol || hi < lo || maxcol < hi) { - throw IndexException(); - } - - for (int i = minrow; i <= maxrow; i++) { - T *row = data[i]; - T temp = row[hi]; - for (int j = hi; j > lo; j--) { - row[j] = row[j - 1]; - } - row[lo] = temp; - } -} - //------------------------------------------------------------------------- // RectArray: Row manipulation functions //------------------------------------------------------------------------- -template void RectArray::SwitchRow(int row, Array &v) -{ - if (!CheckRow(row)) { - throw IndexException(); - } - if (!CheckRow(v)) { - throw DimensionException(); - } - - T *rowptr = data[row]; - T tmp; - for (int i = mincol; i <= maxcol; i++) { - tmp = rowptr[i]; - rowptr[i] = v[i]; - v[i] = tmp; - } -} - -template void RectArray::SwitchRows(int i, int j) -{ - if (!CheckRow(i) || !CheckRow(j)) { - throw IndexException(); - } - T *temp = data[j]; - data[j] = data[i]; - data[i] = temp; -} - template void RectArray::GetRow(int row, Array &v) const { if (!CheckRow(row)) { @@ -353,61 +240,16 @@ template void RectArray::GetRow(int row, Array &v) const if (!CheckRow(v)) { throw DimensionException(); } - T *rowptr = data[row]; for (int i = mincol; i <= maxcol; i++) { v[i] = rowptr[i]; } } -template void RectArray::SetRow(int row, const Array &v) -{ - if (!CheckRow(row)) { - throw IndexException(); - } - if (!CheckRow(v)) { - throw DimensionException(); - } - - T *rowptr = data[row]; - for (int i = mincol; i <= maxcol; i++) { - rowptr[i] = v[i]; - } -} - //------------------------------------------------------------------------- // RectArray: Column manipulation functions //------------------------------------------------------------------------- -template void RectArray::SwitchColumn(int col, Array &v) -{ - if (!CheckColumn(col)) { - throw IndexException(); - } - if (!CheckColumn(v)) { - throw DimensionException(); - } - - for (int i = minrow; i <= maxrow; i++) { - T tmp = data[i][col]; - data[i][col] = v[i]; - v[i] = tmp; - } -} - -template void RectArray::SwitchColumns(int a, int b) -{ - if (!CheckColumn(a) || !CheckColumn(b)) { - throw IndexException(); - } - - for (int i = minrow; i <= maxrow; i++) { - T tmp = data[i][a]; - data[i][a] = data[i][b]; - data[i][b] = tmp; - } -} - template void RectArray::GetColumn(int col, Array &v) const { if (!CheckColumn(col)) { @@ -416,7 +258,6 @@ template void RectArray::GetColumn(int col, Array &v) const if (!CheckColumn(v)) { throw DimensionException(); } - for (int i = minrow; i <= maxrow; i++) { v[i] = data[i][col]; } @@ -430,29 +271,11 @@ template void RectArray::SetColumn(int col, const Array &v) if (!CheckColumn(v)) { throw DimensionException(); } - for (int i = minrow; i <= maxrow; i++) { data[i][col] = v[i]; } } -//------------------------------------------------------------------------- -// RectArray: Transpose -//------------------------------------------------------------------------- - -template RectArray RectArray::Transpose() const -{ - RectArray tmp(mincol, maxcol, minrow, maxrow); - - for (int i = minrow; i <= maxrow; i++) { - for (int j = mincol; j <= maxrow; j++) { - tmp(j, i) = (*this)(i, j); - } - } - - return tmp; -} - } // end namespace Gambit -#endif // LIBGAMBIT_RECARRAY_H +#endif // GAMBIT_CORE_RECARRAY_H diff --git a/src/core/sqmatrix.cc b/src/core/sqmatrix.cc index 0f5f64fb2..20f0295c9 100644 --- a/src/core/sqmatrix.cc +++ b/src/core/sqmatrix.cc @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/sqmatrix.cc +// FILE: src/core/sqmatrix.cc // Instantiation of common square matrix types // // This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include "core/sqmatrix.imp" +#include "sqmatrix.imp" #include "rational.h" template class Gambit::SquareMatrix; diff --git a/src/core/sqmatrix.h b/src/core/sqmatrix.h index 44bb41195..e1b5854a2 100644 --- a/src/core/sqmatrix.h +++ b/src/core/sqmatrix.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/sqmatrix.h +// FILE: src/core/sqmatrix.h // Implementation of square matrices // // This program is free software; you can redistribute it and/or modify @@ -20,8 +20,8 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#ifndef LIBGAMBIT_SQMATRIX_H -#define LIBGAMBIT_SQMATRIX_H +#ifndef GAMBIT_CORE_SQMATRIX_H +#define GAMBIT_CORE_SQMATRIX_H #include "matrix.h" @@ -49,4 +49,4 @@ template class SquareMatrix : public Matrix { } // end namespace Gambit -#endif // LIBGAMBIT_SQMATRIX_H +#endif // GAMBIT_CORE_SQMATRIX_H diff --git a/src/core/sqmatrix.imp b/src/core/sqmatrix.imp index d1354c3a8..e9109b70a 100644 --- a/src/core/sqmatrix.imp +++ b/src/core/sqmatrix.imp @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/sqmatrix.imp +// FILE: src/core/sqmatrix.imp // Implementation of square matrices // // This program is free software; you can redistribute it and/or modify diff --git a/src/core/tinyxml.cc b/src/core/tinyxml.cc index 0f10adaf4..810af95cf 100644 --- a/src/core/tinyxml.cc +++ b/src/core/tinyxml.cc @@ -22,7 +22,7 @@ must not be misrepresented as being the original software. distribution. */ -#include "core/tinyxml.h" +#include "tinyxml.h" #ifdef TIXML_USE_STL #include @@ -120,12 +120,6 @@ TiXmlBase::StringToBuffer::StringToBuffer(const TIXML_STRING &str) TiXmlBase::StringToBuffer::~StringToBuffer() { delete[] buffer; } // End strange bug fix. --> -TiXmlNode::TiXmlNode(NodeType _type) - : TiXmlBase(), parent(nullptr), type(_type), firstChild(nullptr), lastChild(nullptr), - prev(nullptr), next(nullptr) -{ -} - TiXmlNode::~TiXmlNode() { TiXmlNode *node = firstChild; @@ -837,7 +831,7 @@ void TiXmlDocument::operator=(const TiXmlDocument ©) bool TiXmlDocument::LoadFile(TiXmlEncoding encoding) { // See STL_STRING_BUG below. - StringToBuffer buf(value); + const StringToBuffer buf(value); if (buf.buffer && LoadFile(buf.buffer, encoding)) { return true; @@ -849,13 +843,8 @@ bool TiXmlDocument::LoadFile(TiXmlEncoding encoding) bool TiXmlDocument::SaveFile() const { // See STL_STRING_BUG below. - StringToBuffer buf(value); - - if (buf.buffer && SaveFile(buf.buffer)) { - return true; - } - - return false; + const StringToBuffer buf(value); + return (buf.buffer && SaveFile(buf.buffer)); } bool TiXmlDocument::LoadFile(const char *filename, TiXmlEncoding encoding) diff --git a/src/core/tinyxml.h b/src/core/tinyxml.h index 89bd551a5..209cf7c63 100644 --- a/src/core/tinyxml.h +++ b/src/core/tinyxml.h @@ -102,11 +102,11 @@ const int TIXML_PATCH_VERSION = 2; in the XML file. */ struct TiXmlCursor { - TiXmlCursor() { Clear(); } + TiXmlCursor() = default; void Clear() { row = col = -1; } - int row; // 0 based. - int col; // 0 based. + int row{-1}; // 0 based. + int col{-1}; // 0 based. }; // Only used by Attribute::Query functions @@ -145,7 +145,7 @@ class TiXmlBase { friend class TiXmlDocument; public: - TiXmlBase() : userData(nullptr) {} + TiXmlBase() = default; virtual ~TiXmlBase() = default; /** All TinyXml classes can print themselves to a filestream. @@ -267,7 +267,7 @@ class TiXmlBase { { assert(p); if (encoding == TIXML_ENCODING_UTF8) { - *length = utf8ByteTable[*((unsigned char *)p)]; + *length = utf8ByteTable[*(reinterpret_cast(p))]; assert(*length >= 0 && *length < 5); } else { @@ -312,7 +312,7 @@ class TiXmlBase { TiXmlCursor location; /// Field containing a generic user pointer - void *userData; + void *userData{nullptr}; // None of these methods are reliable for any language except English. // Good for approximation, not great for accuracy. @@ -438,7 +438,7 @@ class TiXmlNode : public TiXmlBase { /// STL std::string form. void SetValue(const std::string &_value) { - StringToBuffer buf(_value); + StringToBuffer const buf(_value); SetValue(buf.buffer ? buf.buffer : ""); } #endif @@ -652,55 +652,19 @@ class TiXmlNode : public TiXmlBase { /// Returns true if this node has no children. bool NoChildren() const { return !firstChild; } - const TiXmlDocument *ToDocument() const - { - return (type == DOCUMENT) ? (const TiXmlDocument *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. - const TiXmlElement *ToElement() const - { - return (type == ELEMENT) ? (const TiXmlElement *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. - const TiXmlComment *ToComment() const - { - return (type == COMMENT) ? (const TiXmlComment *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. - const TiXmlUnknown *ToUnknown() const - { - return (type == UNKNOWN) ? (const TiXmlUnknown *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. - const TiXmlText *ToText() const - { - return (type == TEXT) ? (const TiXmlText *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. - const TiXmlDeclaration *ToDeclaration() const - { - return (type == DECLARATION) ? (const TiXmlDeclaration *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. + const TiXmlDocument *ToDocument() const; + const TiXmlElement *ToElement() const; + const TiXmlComment *ToComment() const; + const TiXmlUnknown *ToUnknown() const; + const TiXmlText *ToText() const; + const TiXmlDeclaration *ToDeclaration() const; - TiXmlDocument *ToDocument() - { - return (type == DOCUMENT) ? (TiXmlDocument *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. - TiXmlElement *ToElement() - { - return (type == ELEMENT) ? (TiXmlElement *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. - TiXmlComment *ToComment() - { - return (type == COMMENT) ? (TiXmlComment *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. - TiXmlUnknown *ToUnknown() - { - return (type == UNKNOWN) ? (TiXmlUnknown *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. - TiXmlText *ToText() - { - return (type == TEXT) ? (TiXmlText *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. - TiXmlDeclaration *ToDeclaration() - { - return (type == DECLARATION) ? (TiXmlDeclaration *)this : nullptr; - } ///< Cast to a more defined type. Will return null not of the requested type. + TiXmlDocument *ToDocument(); + TiXmlElement *ToElement(); + TiXmlComment *ToComment(); + TiXmlUnknown *ToUnknown(); + TiXmlText *ToText(); + TiXmlDeclaration *ToDeclaration(); /** Create an exact duplicate of this node and return it. The memory must be deleted by the caller. @@ -708,7 +672,7 @@ class TiXmlNode : public TiXmlBase { virtual TiXmlNode *Clone() const = 0; protected: - TiXmlNode(NodeType _type); + TiXmlNode(NodeType _type) : TiXmlBase(), type(_type) {} // Copy to the allocated object. Shared functionality between Clone, Copy constructor, // and the assignment operator. @@ -722,16 +686,16 @@ class TiXmlNode : public TiXmlBase { // Figure out what is at *p, and parse it. Returns null if it is not an xml node. TiXmlNode *Identify(const char *start, TiXmlEncoding encoding); - TiXmlNode *parent; + TiXmlNode *parent{nullptr}; NodeType type; - TiXmlNode *firstChild; - TiXmlNode *lastChild; + TiXmlNode *firstChild{nullptr}; + TiXmlNode *lastChild{nullptr}; TIXML_STRING value; - TiXmlNode *prev; - TiXmlNode *next; + TiXmlNode *prev{nullptr}; + TiXmlNode *next{nullptr}; private: TiXmlNode(const TiXmlNode &); // not implemented. @@ -750,29 +714,21 @@ class TiXmlAttribute : public TiXmlBase { public: /// Construct an empty attribute. - TiXmlAttribute() : TiXmlBase() - { - document = nullptr; - prev = next = nullptr; - } + TiXmlAttribute() : TiXmlBase(), document(nullptr) { prev = next = nullptr; } #ifdef TIXML_USE_STL /// std::string constructor. TiXmlAttribute(const std::string &_name, const std::string &_value) + : document(nullptr), name(_name), value(_value) { - name = _name; - value = _value; - document = nullptr; prev = next = nullptr; } #endif /// Construct an attribute with a name and value. TiXmlAttribute(const char *_name, const char *_value) + : document(nullptr), name(_name), value(_value) { - name = _name; - value = _value; - document = nullptr; prev = next = nullptr; } @@ -804,13 +760,13 @@ class TiXmlAttribute : public TiXmlBase { /// STL std::string form. void SetName(const std::string &_name) { - StringToBuffer buf(_name); + const StringToBuffer buf(_name); SetName(buf.buffer ? buf.buffer : "error"); } /// STL std::string form. void SetValue(const std::string &_value) { - StringToBuffer buf(_value); + const StringToBuffer buf(_value); SetValue(buf.buffer ? buf.buffer : "error"); } #endif @@ -884,12 +840,10 @@ class TiXmlAttributeSet { const TiXmlAttribute *Find(const char *name) const; TiXmlAttribute *Find(const char *name); -private: - //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element), - //*ME: this class must be also use a hidden/disabled copy-constructor !!! - TiXmlAttributeSet(const TiXmlAttributeSet &); // not allowed - void operator=(const TiXmlAttributeSet &); // not allowed (as TiXmlAttribute) + TiXmlAttributeSet(const TiXmlAttributeSet &) = delete; // not allowed + void operator=(const TiXmlAttributeSet &) = delete; // not allowed (as TiXmlAttribute) +private: TiXmlAttribute sentinel; }; @@ -948,7 +902,7 @@ class TiXmlElement : public TiXmlNode { int QueryFloatAttribute(const char *name, float *_value) const { double d; - int result = QueryDoubleAttribute(name, &d); + const int result = QueryDoubleAttribute(name, &d); if (result == TIXML_SUCCESS) { *_value = (float)d; } @@ -982,8 +936,8 @@ class TiXmlElement : public TiXmlNode { /// STL std::string form. void SetAttribute(const std::string &name, const std::string &_value) { - StringToBuffer n(name); - StringToBuffer v(_value); + const StringToBuffer n(name); + const StringToBuffer v(_value); if (n.buffer && v.buffer) { SetAttribute(n.buffer, v.buffer); } @@ -991,7 +945,7 @@ class TiXmlElement : public TiXmlNode { ///< STL std::string form. void SetAttribute(const std::string &name, int _value) { - StringToBuffer n(name); + const StringToBuffer n(name); if (n.buffer) { SetAttribute(n.buffer, _value); } @@ -1139,23 +1093,24 @@ class TiXmlText : public TiXmlNode { normal, encoded text. If you want it be output as a CDATA text element, set the parameter _cdata to 'true' */ - TiXmlText(const char *initValue) : TiXmlNode(TiXmlNode::TEXT) + TiXmlText(const char *initValue) : TiXmlNode(TiXmlNode::TEXT), cdata(false) { SetValue(initValue); - cdata = false; } ~TiXmlText() override = default; #ifdef TIXML_USE_STL /// Constructor. - TiXmlText(const std::string &initValue) : TiXmlNode(TiXmlNode::TEXT) + TiXmlText(const std::string &initValue) : TiXmlNode(TiXmlNode::TEXT), cdata(false) { SetValue(initValue); - cdata = false; } #endif - TiXmlText(const TiXmlText ©) : TiXmlNode(TiXmlNode::TEXT) { copy.CopyTo(this); } + TiXmlText(const TiXmlText ©) : TiXmlNode(TiXmlNode::TEXT), cdata(copy.cdata) + { + copy.CopyTo(this); + } void operator=(const TiXmlText &base) { base.CopyTo(this); } /// Write this text object to a FILE stream. @@ -1314,12 +1269,12 @@ class TiXmlDocument : public TiXmlNode { bool LoadFile(const std::string &filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING) ///< STL std::string version. { - StringToBuffer f(filename); + const StringToBuffer f(filename); return (f.buffer && LoadFile(f.buffer, encoding)); } bool SaveFile(const std::string &filename) const ///< STL std::string version. { - StringToBuffer f(filename); + const StringToBuffer f(filename); return (f.buffer && SaveFile(f.buffer)); } #endif @@ -1517,9 +1472,9 @@ class TiXmlDocument : public TiXmlNode { class TiXmlHandle { public: /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. - TiXmlHandle(TiXmlNode *_node) { this->node = _node; } + TiXmlHandle(TiXmlNode *_node) : node(_node) {} /// Copy constructor - TiXmlHandle(const TiXmlHandle &ref) { this->node = ref.node; } + TiXmlHandle(const TiXmlHandle &ref) = default; TiXmlHandle operator=(const TiXmlHandle &ref) { this->node = ref.node; @@ -1590,6 +1545,56 @@ class TiXmlHandle { TiXmlNode *node; }; +inline const TiXmlDocument *TiXmlNode::ToDocument() const +{ + return (type == DOCUMENT) ? dynamic_cast(this) : nullptr; +} +inline const TiXmlElement *TiXmlNode::ToElement() const +{ + return (type == ELEMENT) ? dynamic_cast(this) : nullptr; +} +inline const TiXmlComment *TiXmlNode::ToComment() const +{ + return (type == COMMENT) ? dynamic_cast(this) : nullptr; +} +inline const TiXmlUnknown *TiXmlNode::ToUnknown() const +{ + return (type == UNKNOWN) ? dynamic_cast(this) : nullptr; +} +inline const TiXmlText *TiXmlNode::ToText() const +{ + return (type == TEXT) ? dynamic_cast(this) : nullptr; +} +inline const TiXmlDeclaration *TiXmlNode::ToDeclaration() const +{ + return (type == DECLARATION) ? dynamic_cast(this) : nullptr; +} + +inline TiXmlDocument *TiXmlNode::ToDocument() +{ + return (type == DOCUMENT) ? dynamic_cast(this) : nullptr; +} +inline TiXmlElement *TiXmlNode::ToElement() +{ + return (type == ELEMENT) ? dynamic_cast(this) : nullptr; +} +inline TiXmlComment *TiXmlNode::ToComment() +{ + return (type == COMMENT) ? dynamic_cast(this) : nullptr; +} +inline TiXmlUnknown *TiXmlNode::ToUnknown() +{ + return (type == UNKNOWN) ? dynamic_cast(this) : nullptr; +} +inline TiXmlText *TiXmlNode::ToText() +{ + return (type == TEXT) ? dynamic_cast(this) : nullptr; +} +inline TiXmlDeclaration *TiXmlNode::ToDeclaration() +{ + return (type == DECLARATION) ? dynamic_cast(this) : nullptr; +} + #ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/src/core/tinyxmlerror.cc b/src/core/tinyxmlerror.cc index 832cb1259..1c1466b01 100644 --- a/src/core/tinyxmlerror.cc +++ b/src/core/tinyxmlerror.cc @@ -22,7 +22,7 @@ must not be misrepresented as being the original software. distribution. */ -#include "core/tinyxml.h" +#include "tinyxml.h" // The goal of the seperate error file is to make the first // step towards localization. tinyxml (currently) only supports diff --git a/src/core/tinyxmlparser.cc b/src/core/tinyxmlparser.cc index 1f566daf5..08feb571b 100644 --- a/src/core/tinyxmlparser.cc +++ b/src/core/tinyxmlparser.cc @@ -22,10 +22,11 @@ must not be misrepresented as being the original software. distribution. */ -#include "core/tinyxml.h" #include #include +#include "tinyxml.h" + // #define DEBUG_PARSER // Note tha "PutString" hardcodes the same list. This @@ -366,7 +367,7 @@ const char *TiXmlBase::SkipWhiteSpace(const char *p, TiXmlEncoding encoding) return false; } - int c = in->peek(); + const int c = in->peek(); // At this scope, we can't get to a document. So fail silently. if (!IsWhiteSpace(c) || c <= 0) { return true; @@ -380,7 +381,7 @@ const char *TiXmlBase::SkipWhiteSpace(const char *p, TiXmlEncoding encoding) { // assert( character > 0 && character < 128 ); // else it won't work in utf-8 while (in->good()) { - int c = in->peek(); + const int c = in->peek(); if (c == character) { return true; } @@ -422,7 +423,7 @@ const char *TiXmlBase::ReadName(const char *p, TIXML_STRING *name, TiXmlEncoding const char *TiXmlBase::GetEntity(const char *p, char *value, int *length, TiXmlEncoding encoding) { // Presume an entity, and pull it out. - TIXML_STRING ent; + const TIXML_STRING ent; int i; *length = 0; @@ -620,9 +621,9 @@ void TiXmlDocument::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) } while (in->good()) { - int tagIndex = (int)tag->length(); + const int tagIndex = (int)tag->length(); while (in->good() && in->peek() != '>') { - int c = in->get(); + const int c = in->get(); if (c <= 0) { SetError(TIXML_ERROR_EMBEDDED_NULL, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); break; @@ -638,7 +639,7 @@ void TiXmlDocument::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) if (node) { node->StreamIn(in, tag); - bool isElement = node->ToElement() != nullptr; + const bool isElement = node->ToElement() != nullptr; delete node; node = nullptr; @@ -852,7 +853,7 @@ void TiXmlElement::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) // We're called with some amount of pre-parsing. That is, some of "this" // element is in "tag". Go ahead and stream to the closing ">" while (in->good()) { - int c = in->get(); + const int c = in->get(); if (c <= 0) { TiXmlDocument *document = GetDocument(); if (document) { @@ -903,7 +904,7 @@ void TiXmlElement::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) return; } assert(in->peek() == '<'); - int tagIndex = (int)tag->length(); + const int tagIndex = (int)tag->length(); bool closingTag = false; bool firstCharFound = false; @@ -913,7 +914,7 @@ void TiXmlElement::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) return; } - int c = in->peek(); + const int c = in->peek(); if (c <= 0) { TiXmlDocument *document = GetDocument(); if (document) { @@ -944,7 +945,7 @@ void TiXmlElement::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) return; } - int c = in->get(); + const int c = in->get(); if (c <= 0) { TiXmlDocument *document = GetDocument(); if (document) { @@ -1170,7 +1171,7 @@ const char *TiXmlElement::ReadValue(const char *p, TiXmlParsingData *data, TiXml void TiXmlUnknown::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) { while (in->good()) { - int c = in->get(); + const int c = in->get(); if (c <= 0) { TiXmlDocument *document = GetDocument(); if (document) { @@ -1226,7 +1227,7 @@ const char *TiXmlUnknown::Parse(const char *p, TiXmlParsingData *data, TiXmlEnco void TiXmlComment::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) { while (in->good()) { - int c = in->get(); + const int c = in->get(); if (c <= 0) { TiXmlDocument *document = GetDocument(); if (document) { @@ -1342,7 +1343,7 @@ const char *TiXmlAttribute::Parse(const char *p, TiXmlParsingData *data, TiXmlEn void TiXmlText::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) { if (cdata) { - int c = in->get(); + const int c = in->get(); if (c <= 0) { TiXmlDocument *document = GetDocument(); if (document) { @@ -1360,7 +1361,7 @@ void TiXmlText::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) } else { while (in->good()) { - int c = in->peek(); + const int c = in->peek(); if (c == '<') { return; } @@ -1412,7 +1413,7 @@ const char *TiXmlText::Parse(const char *p, TiXmlParsingData *data, TiXmlEncodin return p; } else { - bool ignoreWhite = true; + const bool ignoreWhite = true; const char *end = "<"; p = ReadText(p, &value, ignoreWhite, end, false, encoding); @@ -1427,7 +1428,7 @@ const char *TiXmlText::Parse(const char *p, TiXmlParsingData *data, TiXmlEncodin void TiXmlDeclaration::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) { while (in->good()) { - int c = in->get(); + const int c = in->get(); if (c <= 0) { TiXmlDocument *document = GetDocument(); if (document) { @@ -1501,7 +1502,7 @@ const char *TiXmlDeclaration::Parse(const char *p, TiXmlParsingData *data, TiXml bool TiXmlText::Blank() const { - for (char c : value) { + for (const char c : value) { if (!IsWhiteSpace(c)) { return false; } diff --git a/src/core/util.h b/src/core/util.h new file mode 100644 index 000000000..3ce19f265 --- /dev/null +++ b/src/core/util.h @@ -0,0 +1,136 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) +// +// FILE: src/core/util.h +// Core (game theory-independent) utilities for Gambit +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#ifndef GAMBIT_CORE_UTIL_H +#define GAMBIT_CORE_UTIL_H + +#include +#include +#include +#include +#include +#include + +namespace Gambit { + +// Naming compatible with Boost's lexical_cast concept for potential future compatibility. + +template D lexical_cast(const S &p_value) +{ + std::ostringstream s; + s << p_value; + return s.str(); +} + +template D lexical_cast(const S &p_value, int p_prec) +{ + std::ostringstream s; + s.setf(std::ios::fixed); + s << std::setprecision(p_prec) << p_value; + return s.str(); +} + +inline double abs(double x) { return std::fabs(x); } + +inline double sqr(double x) { return x * x; } + +template bool contains(const C &p_container, const T &p_value) +{ + return std::find(p_container.cbegin(), p_container.cend(), p_value) != p_container.cend(); +} + +template bool contains(const std::map &map, const Key &key) +// TODO: remove when we move to C++20 which already includes a "contains" method +{ + return map.find(key) != map.end(); +} +//======================================================================== +// Exception classes +//======================================================================== + +/// A base class for all Gambit exceptions +class Exception : public std::runtime_error { +public: + Exception() : std::runtime_error("") {} + explicit Exception(const std::string &s) : std::runtime_error(s) {} + ~Exception() noexcept override = default; +}; + +/// Exception thrown on out-of-range index +class IndexException : public Exception { +public: + IndexException() : Exception("Index out of range") {} + explicit IndexException(const std::string &s) : Exception(s) {} + ~IndexException() noexcept override = default; +}; + +/// Exception thrown on invalid index ranges +class RangeException : public Exception { +public: + RangeException() : Exception("Invalid index range") {} + explicit RangeException(const std::string &s) : Exception(s) {} + ~RangeException() noexcept override = default; +}; + +/// Exception thrown on dimension mismatches +class DimensionException : public Exception { +public: + DimensionException() : Exception("Mismatched dimensions") {} + explicit DimensionException(const std::string &s) : Exception(s) {} + ~DimensionException() noexcept override = default; +}; + +/// Exception thrown on invalid value +class ValueException : public Exception { +public: + ValueException() : Exception("Invalid value") {} + explicit ValueException(const std::string &s) : Exception(s) {} + ~ValueException() noexcept override = default; +}; + +/// Exception thrown on a failed assertion +class AssertionException : public Exception { +public: + AssertionException() : Exception("Failed assertion") {} + explicit AssertionException(const std::string &s) : Exception(s) {} + ~AssertionException() noexcept override = default; +}; + +/// Exception thrown on attempted division by zero +class ZeroDivideException : public Exception { +public: + ZeroDivideException() : Exception("Attempted division by zero") {} + explicit ZeroDivideException(const std::string &s) : Exception(s) {} + ~ZeroDivideException() noexcept override = default; +}; + +/// An exception thrown when attempting to dereference a null pointer +class NullException : public Exception { +public: + NullException() : Exception("Dereferenced null pointer") {} + explicit NullException(const std::string &s) : Exception(s) {} + ~NullException() noexcept override = default; +}; + +} // end namespace Gambit + +#endif // GAMBIT_CORE_UTIL_H diff --git a/src/core/vector.h b/src/core/vector.h index 4fac3e25f..614393514 100644 --- a/src/core/vector.h +++ b/src/core/vector.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/vector.h +// FILE: src/core/vector.h // A vector class // // This program is free software; you can redistribute it and/or modify @@ -20,11 +20,13 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#ifndef LIBGAMBIT_VECTOR_H -#define LIBGAMBIT_VECTOR_H +#ifndef GAMBIT_CORE_VECTOR_H +#define GAMBIT_CORE_VECTOR_H #include +#include "array.h" + namespace Gambit { template class Matrix; @@ -164,4 +166,4 @@ template class Vector : public Array { } // end namespace Gambit -#endif // LIBGAMBIT_VECTOR_H +#endif // GAMBIT_CORE_VECTOR_H diff --git a/src/gambit.h b/src/gambit.h index 1222bdee9..eee97f0e9 100644 --- a/src/gambit.h +++ b/src/gambit.h @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/libgambit/gambit.h +// FILE: src/gambit.h // Top-level include file for Gambit library // // This program is free software; you can redistribute it and/or modify @@ -20,127 +20,10 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#ifndef LIBGAMBIT_H -#define LIBGAMBIT_H +#ifndef GAMBIT_H +#define GAMBIT_H -#include -#include -#include -#include -#include -#include -#include - -namespace Gambit { - -// Naming compatible with Boost's lexical_cast concept for potential future compatibility. - -template D lexical_cast(const S &p_value) -{ - std::ostringstream s; - s << p_value; - return s.str(); -} - -template D lexical_cast(const S &p_value, int p_prec) -{ - std::ostringstream s; - s.setf(std::ios::fixed); - s << std::setprecision(p_prec) << p_value; - return s.str(); -} - -inline double abs(double x) { return std::fabs(x); } - -inline double sqr(double x) { return x * x; } - -template bool contains(const C &p_container, const T &p_value) -{ - return std::find(p_container.cbegin(), p_container.cend(), p_value) != p_container.cend(); -} - -template bool contains(const std::map &map, const Key &key) -// TODO: remove when we move to C++20 which already includes a "contains" method -{ - return map.find(key) != map.end(); -} -//======================================================================== -// Exception classes -//======================================================================== - -/// A base class for all Gambit exceptions -class Exception : public std::runtime_error { -public: - Exception() : std::runtime_error("") {} - explicit Exception(const std::string &s) : std::runtime_error(s) {} - ~Exception() noexcept override = default; -}; - -/// Exception thrown on out-of-range index -class IndexException : public Exception { -public: - IndexException() : Exception("Index out of range") {} - explicit IndexException(const std::string &s) : Exception(s) {} - ~IndexException() noexcept override = default; -}; - -/// Exception thrown on invalid index ranges -class RangeException : public Exception { -public: - RangeException() : Exception("Invalid index range") {} - explicit RangeException(const std::string &s) : Exception(s) {} - ~RangeException() noexcept override = default; -}; - -/// Exception thrown on dimension mismatches -class DimensionException : public Exception { -public: - DimensionException() : Exception("Mismatched dimensions") {} - explicit DimensionException(const std::string &s) : Exception(s) {} - ~DimensionException() noexcept override = default; -}; - -/// Exception thrown on invalid value -class ValueException : public Exception { -public: - ValueException() : Exception("Invalid value") {} - explicit ValueException(const std::string &s) : Exception(s) {} - ~ValueException() noexcept override = default; -}; - -/// Exception thrown on a failed assertion -class AssertionException : public Exception { -public: - AssertionException() : Exception("Failed assertion") {} - explicit AssertionException(const std::string &s) : Exception(s) {} - ~AssertionException() noexcept override = default; -}; - -/// Exception thrown on attempted division by zero -class ZeroDivideException : public Exception { -public: - ZeroDivideException() : Exception("Attempted division by zero") {} - explicit ZeroDivideException(const std::string &s) : Exception(s) {} - ~ZeroDivideException() noexcept override = default; -}; - -/// An exception thrown when attempting to dereference a null pointer -class NullException : public Exception { -public: - NullException() : Exception("Dereferenced null pointer") {} - explicit NullException(const std::string &s) : Exception(s) {} - ~NullException() noexcept override = default; -}; - -} // end namespace Gambit - -#include "core/array.h" -#include "core/list.h" -#include "core/recarray.h" -#include "core/vector.h" -#include "core/matrix.h" - -#include "core/rational.h" +#include "core/core.h" #include "games/game.h" #include "games/writer.h" @@ -153,4 +36,4 @@ class NullException : public Exception { #include "games/stratpure.h" #include "games/stratmixed.h" -#endif // LIBGAMBIT_H +#endif // GAMBIT_H diff --git a/src/games/agg/agg.cc b/src/games/agg/agg.cc index c99e2f19c..fe792b65f 100644 --- a/src/games/agg/agg.cc +++ b/src/games/agg/agg.cc @@ -38,12 +38,11 @@ AGG::AGG(int numPlayers, std::vector &_actions, int numANodes, int _numPNod vector &projTypes, vector> &projS, vector>> &proj, vector> &projF, vector>> &Po, vector &P, vector &_payoffs) - : numPlayers(numPlayers), totalActions(0), maxActions(0), numActionNodes(numANodes), - numPNodes(_numPNodes), actionSets(_actionSets), neighbors(neighb), projectionTypes(projTypes), - payoffs(_payoffs), projection(proj), projectedStrat(projS), fullProjectedStrat(projS), - projFunctions(projF), Porder(Po), Pr(P), isPure(numANodes, true), - node2Action(numANodes, vector(numPlayers)), cache(numPlayers + 1), - player2Class(numPlayers), kSymStrategyOffset(1, 0) + : numPlayers(numPlayers), numActionNodes(numANodes), numPNodes(_numPNodes), + actionSets(_actionSets), neighbors(neighb), projectionTypes(projTypes), payoffs(_payoffs), + projection(proj), projectedStrat(projS), fullProjectedStrat(projS), projFunctions(projF), + Porder(Po), Pr(P), isPure(numANodes, true), node2Action(numANodes, vector(numPlayers)), + cache(numPlayers + 1), player2Class(numPlayers), kSymStrategyOffset(1, 0) { // actions @@ -97,7 +96,7 @@ AGG::AGG(int numPlayers, std::vector &_actions, int numANodes, int _numPNod // set isPure for (int i = 0; i < numANodes; i++) { if (neighb.at(i).size() > 0) { - int maxNode = *(max_element(neighb.at(i).begin(), neighb.at(i).end())); + const int maxNode = *(max_element(neighb.at(i).begin(), neighb.at(i).end())); isPure[i] = (maxNode < numANodes); } } @@ -120,7 +119,7 @@ namespace { void stripComment(istream &in) { in >> ws; - char c = in.peek(); + const char c = in.peek(); stringbuf discard(ios_base::out); if (c == AGG::COMMENT_CHAR) { in.get(discard); @@ -225,7 +224,7 @@ std::shared_ptr AGG::makeAGG(istream &in) for (i = 0; i < S; i++) { neighb_size = neighb[i].size(); for (j = 0; j < neighb_size; j++) { - projtype t = + const projtype t = (neighb[i][j] < S) ? std::make_shared() : projTypes[neighb[i][j] - S]; projF[i].push_back(t); } @@ -323,9 +322,9 @@ void AGG::setProjections(vector> &projS, vector= S) { - projtype f = projTypes[neighb[Node][k] - S]; + const projtype f = projTypes[neighb[Node][k] - S]; assert(f); - pair::iterator, multiset::iterator> p = + const pair::iterator, multiset::iterator> p = an[neighb[Node][k] - S].equal_range(AS[i][j]); multiset blah(p.first, p.second); proj[Node][i][j][k] = (*f)(blah); @@ -348,13 +347,13 @@ void AGG::getAn(multiset &dest, vector> &neighb, vector &s) { assert(player >= 0 && player < numPlayers); - int Node = actionSets[player][s[player]]; - int keylen = neighbors[Node].size(); + const int Node = actionSets[player][s[player]]; + const int keylen = neighbors[Node].size(); config pureprofile(projection[Node][0][s[0]]); for (int i = 1; i < numPlayers; i++) { for (int j = 0; j < keylen; j++) { @@ -521,7 +520,7 @@ void AGG::getSymPayoffVector(AggNumberVector &dest, StrategyProfile &s) AggNumber AGG::getSymMixedPayoff(int node, StrategyProfile &s) { - int numNei = neighbors[node].size(); + const int numNei = neighbors[node].size(); if (!isPure[node]) { // then compute EU using trie_map::power() doProjection(node, 0, s); @@ -578,8 +577,10 @@ AggNumber AGG::getSymMixedPayoff(int node, StrategyProfile &s) break; } // update prob - AggNumber i_prob = (support.at(gc.i) != -1) ? s[neighbors[node][support[gc.i]]] : null_prob; - AggNumber d_prob = (support.at(gc.d) != -1) ? s[neighbors[node][support[gc.d]]] : null_prob; + const AggNumber i_prob = + (support.at(gc.i) != -1) ? s[neighbors[node][support[gc.i]]] : null_prob; + const AggNumber d_prob = + (support.at(gc.d) != -1) ? s[neighbors[node][support[gc.d]]] : null_prob; assert(i_prob > (AggNumber)0 && d_prob > (AggNumber)0); prob *= ((AggNumber)(gc.get().at(gc.d) + 1)) * i_prob / (AggNumber)(gc.get().at(gc.i)) / d_prob; @@ -596,7 +597,7 @@ AggNumber AGG::getSymMixedPayoff(int node, StrategyProfile &s) void AGG::getSymConfigProb(int plClass, StrategyProfile &s, int ownPlClass, int act, aggdistrib &dest, int plClass2, int act2) { - int node = uniqueActionSets.at(ownPlClass).at(act); + const int node = uniqueActionSets.at(ownPlClass).at(act); int numPl = playerClasses.at(plClass).size(); assert(numPl > 0); @@ -607,10 +608,10 @@ void AGG::getSymConfigProb(int plClass, StrategyProfile &s, int ownPlClass, int numPl--; } dest.reset(); - int numNei = neighbors.at(node).size(); + const int numNei = neighbors.at(node).size(); if (!isPure[node]) { - int player = playerClasses[plClass].at(0); + const int player = playerClasses[plClass].at(0); projectedStrat[node][player].reset(); if (numPl > 0) { for (int j = 0; j < actions[player]; j++) { @@ -651,7 +652,7 @@ void AGG::getSymConfigProb(int plClass, StrategyProfile &s, int ownPlClass, int // do projection & get support int self = -1; // index of self in the neighbor list int ind2 = -1; // index of act2 in the neighbor list - int p = playerClasses[plClass][0]; + const int p = playerClasses[plClass][0]; for (int i = 0; i < numNei; ++i) { if (neighbors[node][i] == node) { self = i; @@ -660,7 +661,7 @@ void AGG::getSymConfigProb(int plClass, StrategyProfile &s, int ownPlClass, int ind2 = i; } - int a = node2Action.at(neighbors[node][i]).at(p); + const int a = node2Action.at(neighbors[node][i]).at(p); if (a >= 0 && s[a] > (AggNumber)0) { support.push_back(i); null_prob -= s[a]; @@ -673,7 +674,7 @@ void AGG::getSymConfigProb(int plClass, StrategyProfile &s, int ownPlClass, int // gray code GrayComposition gc(numPl, support.size()); - AggNumber prob0 = + const AggNumber prob0 = (support.at(0) >= 0) ? s[node2Action[neighbors[node].at(support[0])][p]] : null_prob; AggNumber prob = pow(prob0, numPl); @@ -703,9 +704,9 @@ void AGG::getSymConfigProb(int plClass, StrategyProfile &s, int ownPlClass, int break; } // update prob - AggNumber i_prob = + const AggNumber i_prob = (support.at(gc.i) != -1) ? s[node2Action[neighbors[node][support[gc.i]]][p]] : null_prob; - AggNumber d_prob = + const AggNumber d_prob = (support.at(gc.d) != -1) ? s[node2Action[neighbors[node][support[gc.d]]][p]] : null_prob; assert(i_prob > (AggNumber)0 && d_prob > (AggNumber)0); prob *= @@ -750,9 +751,9 @@ void AGG::getKSymPayoffVector(AggNumberVector &dest, int playerClass, StrategyPr AggNumber AGG::getKSymMixedPayoff(int playerClass, int act, vector &s) { - int numPC = playerClasses.size(); + const int numPC = playerClasses.size(); - int numNei = neighbors[uniqueActionSets[playerClass][act]].size(); + const int numNei = neighbors[uniqueActionSets[playerClass][act]].size(); static aggdistrib d, temp; d.reset(); @@ -768,8 +769,8 @@ AggNumber AGG::getKSymMixedPayoff(int playerClass, int act, vector= 0 && pClass1 == pClass2 && playerClasses.at(pClass1).size() <= 1) { return 0; @@ -854,7 +855,7 @@ void AGG::makeMAPPINGpayoff(std::istream &in, aggpayoff &pay, int numNei) } // insert - pair::iterator, bool> r = pay.insert(make_pair(key, u)); + const pair::iterator, bool> r = pay.insert(make_pair(key, u)); if (!r.second) { std::stringstream str; str << "ERROR: overwriting utility at ["; diff --git a/src/games/agg/agg.h b/src/games/agg/agg.h index dd92aac1d..48ee7de21 100644 --- a/src/games/agg/agg.h +++ b/src/games/agg/agg.h @@ -55,9 +55,9 @@ using payofftype = enum { COMPLETE, MAPPING, ADDITIVE }; class AGG { public: - typedef std::vector config; - typedef std::vector ActionSet; - typedef std::vector PlayerSet; + using config = std::vector; + using ActionSet = std::vector; + using PlayerSet = std::vector; static const char COMMENT_CHAR = '#'; static const char LBRACKET = '['; @@ -170,8 +170,8 @@ class AGG { int numPlayers; std::vector actions; std::vector strategyOffset; - int totalActions; - int maxActions; + int totalActions{0}; + int maxActions{0}; int numActionNodes; // |S| int numPNodes; // |P| diff --git a/src/games/agg/bagg.cc b/src/games/agg/bagg.cc index 516e17145..ab58e63b6 100644 --- a/src/games/agg/bagg.cc +++ b/src/games/agg/bagg.cc @@ -25,7 +25,7 @@ BAGG::BAGG(int N, int S, vector &numTypes, vector &TDist, strategyOffset[0] = 0; for (int i = 0; i < numPlayers; ++i) { for (int j = 0; j < numTypes[i]; ++j) { - int idx = typeOffset[i] + j; + const int idx = typeOffset[i] + j; strategyOffset[idx + 1] = strategyOffset[idx] + typeActionSets[i][j].size(); } } @@ -53,7 +53,7 @@ void stripComment(istream &in) const char COMMENT_CHAR = '#'; in >> ws; - char c = in.peek(); + const char c = in.peek(); stringbuf discard(ios_base::out); if (c == COMMENT_CHAR) { in.get(discard); @@ -157,7 +157,7 @@ std::shared_ptr BAGG::makeBAGG(istream &in) aggss << aggActionSets[i].size() << endl; } for (int i = 0; i < N; i++) { - for (int j : aggActionSets[i]) { + for (const int j : aggActionSets[i]) { aggss << j << " "; } aggss << endl; @@ -170,7 +170,7 @@ std::shared_ptr BAGG::makeBAGG(istream &in) aggss << ln << endl; } - std::shared_ptr aggPtr = AGG::makeAGG(aggss); + const std::shared_ptr aggPtr = AGG::makeAGG(aggss); return std::make_shared(N, S, numTypes, TDist, typeActionSets, typeAction2ActionIndex, aggPtr); } @@ -214,13 +214,13 @@ void BAGG::getAGGStrat(StrategyProfile &as, const StrategyProfile &s, int player if (pl != player) { for (int t = 0; t < numTypes[pl]; ++t) { for (size_t act = 0; act < typeActionSets[pl][t].size(); ++act) { - int aact = typeAction2ActionIndex[pl][t][act]; + const int aact = typeAction2ActionIndex[pl][t][act]; as[aact + aggPtr->firstAction(pl)] += indepTypeDist[pl][t] * s[act + firstAction(pl, t)]; } } } else { - int aact = typeAction2ActionIndex[player][tp][action]; + const int aact = typeAction2ActionIndex[player][tp][action]; as[aact + aggPtr->firstAction(player)] = 1; } } @@ -251,7 +251,7 @@ void BAGG::getSymAGGStrat(StrategyProfile &as, const StrategyProfile &s) } for (int t = 0; t < numTypes[0]; ++t) { for (size_t act = 0; act < typeActionSets[0][t].size(); ++act) { - int aact = typeAction2ActionIndex[0][t][act]; + const int aact = typeAction2ActionIndex[0][t][act]; as[aact] += indepTypeDist[0][t] * s[act + firstAction(0, t)]; } } diff --git a/src/games/agg/gray.h b/src/games/agg/gray.h index 2561ce043..0b9bbdc9f 100644 --- a/src/games/agg/gray.h +++ b/src/games/agg/gray.h @@ -26,19 +26,13 @@ #include -namespace Gambit { - -namespace agg { +namespace Gambit::agg { class GrayComposition { friend class AGG; public: - GrayComposition(int _n, int _k) - : n(_n), k(_k), p(0), i(-1), d(-1), finished(false), current(k, 0) - { - current.at(0) = n; - } + GrayComposition(int _n, int _k) : n(_n), k(_k), current(k, 0) { current.at(0) = n; } bool eof() const { return finished; } @@ -110,14 +104,12 @@ class GrayComposition { private: int n, k; - int p; // idx to first positive - int i, d; - bool finished; + int p{0}; // idx to first positive + int i{-1}, d{-1}; + bool finished{false}; std::vector current; }; -} // namespace agg - -} // end namespace Gambit +} // end namespace Gambit::agg #endif // GAMBIT_AGG_GRAY_H diff --git a/src/games/agg/proj_func.h b/src/games/agg/proj_func.h index e46297f53..97b268f5e 100644 --- a/src/games/agg/proj_func.h +++ b/src/games/agg/proj_func.h @@ -31,13 +31,11 @@ #include #include -namespace Gambit { - -namespace agg { +namespace Gambit::agg { // types of contribution-independent function: // sum, existence, highest, lowest and their extended versions -typedef enum { +using TypeEnum = enum { P_SUM = 0, P_EXIST = 1, P_HIGH = 2, @@ -46,7 +44,7 @@ typedef enum { P_EXIST2 = 11, P_HIGH2 = 12, P_LOW2 = 13 -} TypeEnum; +}; // proj_func: contribution-independent function struct proj_func { @@ -58,7 +56,7 @@ struct proj_func { proj_func(TypeEnum tp, int def) : Type(tp), Default(def) {} - proj_func(TypeEnum tp, std::istream &in, int S) : Type(tp) + proj_func(TypeEnum tp, std::istream &in, int S) : Type(tp), Default(0) { in >> Default; if (in.eof() || in.bad()) { @@ -126,7 +124,7 @@ struct proj_func_SUM2 : public proj_func { int operator()(std::multiset &s) const override { int res = Default; - for (int it : s) { + for (const int it : s) { res += weights.at(it); } return res; @@ -149,7 +147,7 @@ struct proj_func_EXIST2 : public proj_func { if (Default < 0) { throw std::runtime_error("proj_func_EXIST2() error: default value should be nonnegative"); } - for (int weight : weights) { + for (const int weight : weights) { if (weight < 0) { throw std::runtime_error("proj_func_EXIST2() error: weights should be nonnegative"); } @@ -300,8 +298,6 @@ inline projtype make_proj_func(TypeEnum type, std::istream &in, int S, int P) } } -} // namespace agg - -} // end namespace Gambit +} // end namespace Gambit::agg #endif // GAMBIT_AGG_PROJFUNC_H diff --git a/src/games/agg/trie_map.h b/src/games/agg/trie_map.h index 62fa14fc7..c72a079b2 100644 --- a/src/games/agg/trie_map.h +++ b/src/games/agg/trie_map.h @@ -34,9 +34,7 @@ #include #include "proj_func.h" -namespace Gambit { - -namespace agg { +namespace Gambit::agg { // forward declarations @@ -125,7 +123,7 @@ template class trie_map { // insert or add trie_map &operator+=(const value_type &x) { - std::pair::iterator, bool> r = insert(x); + const std::pair::iterator, bool> r = insert(x); if (!r.second) { (*r.first).second += x.second; } @@ -222,7 +220,8 @@ template class trie_map { if (n == nullptr) { return; } - size_type i, s = n->children.size(); + size_type i; + const size_type s = n->children.size(); bool is_leaf = true; for (i = 0; i < s; ++i) { if (n->children[i]) { @@ -276,8 +275,6 @@ template const double trie_map::THRESH = 1e-12; template std::ostream &operator<<(std::ostream &s, const trie_map &t); -} // namespace agg - -} // end namespace Gambit +} // end namespace Gambit::agg #endif // GAMBIT_AGG_TRIEMAP_H diff --git a/src/games/agg/trie_map.imp b/src/games/agg/trie_map.imp index dd434747d..08981228d 100644 --- a/src/games/agg/trie_map.imp +++ b/src/games/agg/trie_map.imp @@ -24,9 +24,7 @@ #include #include "trie_map.h" -namespace Gambit { - -namespace agg { +namespace Gambit::agg { template std::pair::iterator, bool> @@ -113,7 +111,7 @@ template trie_map &trie_map::operator=(const trie_map &other) template void trie_map::swap(trie_map &other) { if (this != &other) { - trie_map temp = *this; + const trie_map temp = *this; *this = other; other = temp; } @@ -361,7 +359,7 @@ void trie_map::div(const std::vector &denom, TrieNode *n, int current, { int i; if (pivot != current) { - int s = n->children.size(); + const int s = n->children.size(); for (i = 0; i < s; i++) { if (n->children[i]) { div(denom, n->children[i], current + 1, pivot); @@ -401,8 +399,8 @@ template void trie_map::deleteNodes(TrieNode *n) if (!n) { return; } - size_type i, s = n->children.size(); - for (i = 0; i < s; ++i) { + const size_type s = n->children.size(); + for (size_type i = 0; i < s; ++i) { if (n->children[i]) { deleteNodes(n->children[i]); } @@ -412,8 +410,8 @@ template void trie_map::deleteNodes(TrieNode *n) template void trie_map::div_helper_mul::add(const std::vector &conf, V y) { - size_t i, keylen = conf.size(); - double th(THRESH / (double)denom[pivot]); + const size_t keylen = conf.size(); + const double th(THRESH / (double)denom[pivot]); // if (y<=th&&y>=-th) return; if (!dest) { @@ -425,7 +423,7 @@ template void trie_map::div_helper_mul::add(const std::vector return; } TrieNode *ptr = dest; - for (i = pivot + 1; i < keylen; ++i) { + for (size_t i = pivot + 1; i < keylen; ++i) { if ((*ptr)[conf[i]] == nullptr) { if ((double)y > th || (double)y < -th) { std::stringstream str; @@ -454,11 +452,11 @@ template void trie_map::div_helper_mul::operator()(iterator p) if (p == endp) { return; } - size_t i, keylen = p->first.size(); + const size_t keylen = p->first.size(); // assert(keylen==denom.size()); V null_prob(((V)1) - denom[pivot]); // V th(THRESH); - for (i = pivot + 1; i < keylen; ++i) { + for (size_t i = pivot + 1; i < keylen; ++i) { if (denom[i] > (V)0) { p->first[i]++; add(p->first, -denom[i] * p->second); @@ -472,6 +470,4 @@ template void trie_map::div_helper_mul::operator()(iterator p) } } -} // namespace agg - -} // end namespace Gambit +} // end namespace Gambit::agg diff --git a/src/games/behavmixed.cc b/src/games/behavmixed.cc index a98c54576..d57066999 100644 --- a/src/games/behavmixed.cc +++ b/src/games/behavmixed.cc @@ -87,17 +87,18 @@ void MixedBehaviorProfile::BehaviorStrat(GamePlayer &player, GameNode &p_node template void MixedBehaviorProfile::RealizationProbs(const MixedStrategyProfile &mp, - GamePlayer &player, const Array &actions, - GameTreeNodeRep *node, - std::map &map_nvals, + GamePlayer &player, + const std::map &actions, + GameNodeRep *node, std::map &map_nvals, std::map &map_bvals) { T prob; - for (int i = 1; i <= node->children.size(); i++) { + for (size_t i = 1; i <= node->m_children.size(); i++) { if (node->GetPlayer() && !node->GetPlayer()->IsChance()) { if (node->GetPlayer() == player) { - if (actions[node->GetInfoset()->GetNumber()] == i) { + if (contains(actions, node->m_infoset) && + actions.at(node->GetInfoset()) == static_cast(i)) { prob = T(1); } else { @@ -105,7 +106,7 @@ void MixedBehaviorProfile::RealizationProbs(const MixedStrategyProfile &mp } } else if (GetSupport().Contains(node->GetInfoset()->GetAction(i))) { - int num_actions = GetSupport().GetActions(node->GetInfoset()).size(); + const int num_actions = GetSupport().GetActions(node->GetInfoset()).size(); prob = T(1) / T(num_actions); } else { @@ -113,10 +114,10 @@ void MixedBehaviorProfile::RealizationProbs(const MixedStrategyProfile &mp } } else { // n.GetPlayer() == 0 - prob = T(node->infoset->GetActionProb(i)); + prob = T(node->m_infoset->GetActionProb(node->m_infoset->GetAction(i))); } - GameTreeNodeRep *child = node->children[i]; + GameNodeRep *child = node->m_children[i - 1]; map_bvals[child] = prob * map_bvals[node]; map_nvals[child] += map_bvals[child]; @@ -140,8 +141,7 @@ MixedBehaviorProfile::MixedBehaviorProfile(const MixedStrategyProfile &p_p } } - GameTreeNodeRep *root = - dynamic_cast(m_support.GetGame()->GetRoot().operator->()); + GameNodeRep *root = m_support.GetGame()->GetRoot(); const StrategySupportProfile &support = p_profile.GetSupport(); GameRep *game = m_support.GetGame(); @@ -150,7 +150,7 @@ MixedBehaviorProfile::MixedBehaviorProfile(const MixedStrategyProfile &p_p std::map map_nvals, map_bvals; for (auto strategy : support.GetStrategies(player)) { if (p_profile[strategy] > T(0)) { - const Array &actions = strategy->m_behav; + const auto &actions = strategy->m_behav; map_bvals[root] = p_profile[strategy]; RealizationProbs(p_profile, player, actions, root, map_nvals, map_bvals); } @@ -203,7 +203,7 @@ template void MixedBehaviorProfile::SetCentroid() template void MixedBehaviorProfile::UndefinedToCentroid() { CheckVersion(); - Game efg = m_support.GetGame(); + const Game efg = m_support.GetGame(); for (auto infoset : efg->GetInfosets()) { if (GetInfosetProb(infoset) > T(0)) { continue; @@ -289,8 +289,8 @@ template T MixedBehaviorProfile::GetInfosetProb(const GameInfoset & CheckVersion(); ComputeSolutionData(); T prob = T(0); - for (auto iset : iset->GetMembers()) { - prob += map_realizProbs[iset]; + for (auto member : iset->GetMembers()) { + prob += map_realizProbs[member]; } return prob; } @@ -333,16 +333,12 @@ template T MixedBehaviorProfile::GetActionProb(const GameAction &ac { CheckVersion(); if (action->GetInfoset()->GetPlayer()->IsChance()) { - GameTreeInfosetRep *infoset = - dynamic_cast(action->GetInfoset().operator->()); - return static_cast(infoset->GetActionProb(action->GetNumber())); + return static_cast(action->GetInfoset()->GetActionProb(action)); } - else if (!m_support.Contains(action)) { + if (!m_support.Contains(action)) { return T(0); } - else { - return m_probs[m_profileIndex.at(action)]; - } + return m_probs[m_profileIndex.at(action)]; } template const T &MixedBehaviorProfile::GetPayoff(const GameAction &act) const @@ -381,7 +377,7 @@ void MixedBehaviorProfile::GetPayoff(const GameNode &node, const T &prob, const GamePlayer &player, T &value) const { if (node->GetOutcome()) { - value += prob * static_cast(node->GetOutcome()->GetPayoff(player)); + value += prob * node->GetOutcome()->GetPayoff(player); } if (!node->IsTerminal()) { @@ -426,16 +422,16 @@ T MixedBehaviorProfile::DiffActionValue(const GameAction &p_action, CheckVersion(); ComputeSolutionData(); T deriv = T(0); - GameInfoset infoset = p_action->GetInfoset(); - GamePlayer player = p_action->GetInfoset()->GetPlayer(); + const GameInfoset infoset = p_action->GetInfoset(); + const GamePlayer player = p_action->GetInfoset()->GetPlayer(); for (auto member : infoset->GetMembers()) { - GameNode child = member->GetChild(p_action); + const GameNode child = member->GetChild(p_action); deriv += DiffRealizProb(member, p_oppAction) * (map_nodeValues[child][player] - map_actionValues[p_action]); - deriv += map_realizProbs[member] * - DiffNodeValue(member->GetChild(p_action->GetNumber()), player, p_oppAction); + deriv += + map_realizProbs[member] * DiffNodeValue(member->GetChild(p_action), player, p_oppAction); } return deriv / GetInfosetProb(p_action->GetInfoset()); @@ -451,7 +447,7 @@ T MixedBehaviorProfile::DiffRealizProb(const GameNode &p_node, bool isPrec = false; GameNode node = p_node; while (node->GetParent()) { - GameAction prevAction = node->GetPriorAction(); + const GameAction prevAction = node->GetPriorAction(); if (prevAction != p_oppAction) { deriv *= GetActionProb(prevAction); } @@ -471,29 +467,25 @@ T MixedBehaviorProfile::DiffNodeValue(const GameNode &p_node, const GamePlaye CheckVersion(); ComputeSolutionData(); - if (p_node->NumChildren() > 0) { - GameInfoset infoset = p_node->GetInfoset(); - - if (infoset == p_oppAction->GetInfoset()) { - // We've encountered the action; since we assume perfect recall, - // we won't encounter it again, and the downtree value must - // be the same. - return map_nodeValues[p_node->GetChild(p_oppAction)][p_player]; - } - else { - T deriv = T(0); - for (auto action : infoset->GetActions()) { - deriv += (DiffNodeValue(p_node->GetChild(action), p_player, p_oppAction) * - GetActionProb(action)); - } - return deriv; - } - } - else { + if (p_node->IsTerminal()) { // If we reach a terminal node and haven't encountered p_oppAction, // derivative wrt this path is zero. return T(0); } + if (p_node->GetInfoset() == p_oppAction->GetInfoset()) { + // We've encountered the action; since we assume perfect recall, + // we won't encounter it again, and the downtree value must + // be the same. + return map_nodeValues[p_node->GetChild(p_oppAction)][p_player]; + } + else { + T deriv = T(0); + for (auto action : p_node->GetInfoset()->GetActions()) { + deriv += + (DiffNodeValue(p_node->GetChild(action), p_player, p_oppAction) * GetActionProb(action)); + } + return deriv; + } } //======================================================================== @@ -518,9 +510,9 @@ void MixedBehaviorProfile::ComputePass2_beliefs_nodeValues_actionValues( const GameNode &node) const { if (node->GetOutcome()) { - GameOutcome outcome = node->GetOutcome(); + const GameOutcome outcome = node->GetOutcome(); for (auto player : m_support.GetGame()->GetPlayers()) { - map_nodeValues[node][player] += static_cast(outcome->GetPayoff(player)); + map_nodeValues[node][player] += outcome->GetPayoff(player); } } @@ -528,7 +520,7 @@ void MixedBehaviorProfile::ComputePass2_beliefs_nodeValues_actionValues( return; } - GameInfoset iset = node->GetInfoset(); + const GameInfoset iset = node->GetInfoset(); auto nodes = iset->GetMembers(); T infosetProb = std::accumulate(nodes.begin(), nodes.end(), T(0), @@ -550,7 +542,7 @@ void MixedBehaviorProfile::ComputePass2_beliefs_nodeValues_actionValues( for (auto child : node->GetChildren()) { ComputePass2_beliefs_nodeValues_actionValues(child); - GameAction act = child->GetPriorAction(); + const GameAction act = child->GetPriorAction(); for (auto player : m_support.GetGame()->GetPlayers()) { map_nodeValues[node][player] += GetActionProb(act) * map_nodeValues[child][player]; diff --git a/src/games/behavmixed.h b/src/games/behavmixed.h index 4cabe0424..6416c0be0 100644 --- a/src/games/behavmixed.h +++ b/src/games/behavmixed.h @@ -23,6 +23,8 @@ #ifndef LIBGAMBIT_BEHAV_H #define LIBGAMBIT_BEHAV_H +#include + #include "game.h" namespace Gambit { @@ -62,8 +64,9 @@ template class MixedBehaviorProfile { /// @name Converting mixed strategies to behavior //@{ void BehaviorStrat(GamePlayer &, GameNode &, std::map &, std::map &); - void RealizationProbs(const MixedStrategyProfile &, GamePlayer &, const Array &, - GameTreeNodeRep *, std::map &, std::map &); + void RealizationProbs(const MixedStrategyProfile &, GamePlayer &, + const std::map &, GameNodeRep *, + std::map &, std::map &); //@} /// Check underlying game has not changed; raise exception if it has @@ -224,7 +227,7 @@ template MixedBehaviorProfile GameRep::NewRandomBehaviorProfile(Generator &generator) const { auto profile = MixedBehaviorProfile(Game(const_cast(this))); - std::exponential_distribution<> dist(1); + std::exponential_distribution<> dist(1); // NOLINT(misc-const-correctness) for (auto player : GetPlayers()) { for (auto infoset : player->GetInfosets()) { for (auto action : infoset->GetActions()) { @@ -242,7 +245,8 @@ MixedBehaviorProfile GameRep::NewRandomBehaviorProfile(int p_denom, auto profile = MixedBehaviorProfile(Game(const_cast(this))); for (auto player : GetPlayers()) { for (auto infoset : player->GetInfosets()) { - std::list dist = UniformOnSimplex(p_denom, infoset->NumActions(), generator); + std::list dist = + UniformOnSimplex(p_denom, infoset->GetActions().size(), generator); auto prob = dist.cbegin(); for (auto action : infoset->GetActions()) { profile[action] = *prob; diff --git a/src/games/behavpure.cc b/src/games/behavpure.cc index 22771317f..e023785e0 100644 --- a/src/games/behavpure.cc +++ b/src/games/behavpure.cc @@ -49,7 +49,7 @@ T PureBehaviorProfile::GetPayoff(const GameNode &p_node, const GamePlayer &p_pla T payoff(0); if (p_node->GetOutcome()) { - payoff += static_cast(p_node->GetOutcome()->GetPayoff(p_player)); + payoff += p_node->GetOutcome()->GetPayoff(p_player); } if (!p_node->IsTerminal()) { @@ -82,21 +82,6 @@ template T PureBehaviorProfile::GetPayoff(const GameAction &p_action) template double PureBehaviorProfile::GetPayoff(const GameAction &) const; template Rational PureBehaviorProfile::GetPayoff(const GameAction &) const; -bool PureBehaviorProfile::IsAgentNash() const -{ - for (const auto &player : m_efg->GetPlayers()) { - auto current = GetPayoff(player); - for (const auto &infoset : player->GetInfosets()) { - for (const auto &action : infoset->GetActions()) { - if (GetPayoff(action) > current) { - return false; - } - } - } - } - return true; -} - MixedBehaviorProfile PureBehaviorProfile::ToMixedBehaviorProfile() const { MixedBehaviorProfile temp(m_efg); diff --git a/src/games/behavpure.h b/src/games/behavpure.h index a76a6e655..885517004 100644 --- a/src/games/behavpure.h +++ b/src/games/behavpure.h @@ -23,6 +23,7 @@ #ifndef GAMBIT_GAMES_BEHAVPURE_H #define GAMBIT_GAMES_BEHAVPURE_H +#include "game.h" #include "behavspt.h" namespace Gambit { @@ -42,6 +43,8 @@ class PureBehaviorProfile { explicit PureBehaviorProfile(const Game &); //@} + Game GetGame() const { return m_efg; } + bool operator==(const PureBehaviorProfile &p_other) const { return m_profile == p_other.m_profile; @@ -62,9 +65,6 @@ class PureBehaviorProfile { /// Get the payoff to playing the action, conditional on the profile template T GetPayoff(const GameAction &) const; - /// Is the profile a pure strategy agent Nash equilibrium? - bool IsAgentNash() const; - /// Convert to a mixed behavior representation MixedBehaviorProfile ToMixedBehaviorProfile() const; //@} diff --git a/src/games/behavspt.cc b/src/games/behavspt.cc index 5ba2ce18c..71eec0931 100644 --- a/src/games/behavspt.cc +++ b/src/games/behavspt.cc @@ -39,7 +39,7 @@ BehaviorSupportProfile::BehaviorSupportProfile(const Game &p_efg) : m_efg(p_efg) } // Initialize the list of reachable information sets and nodes - for (int pl = 0; pl <= GetGame()->NumPlayers(); pl++) { + for (size_t pl = 0; pl <= GetGame()->NumPlayers(); pl++) { const GamePlayer player = (pl == 0) ? GetGame()->GetChance() : GetGame()->GetPlayer(pl); for (const auto &infoset : player->GetInfosets()) { m_infosetReachable[infoset] = true; @@ -112,7 +112,7 @@ void ReachableInfosets(const BehaviorSupportProfile &p_support, const GameNode & return; } - GameInfoset infoset = p_node->GetInfoset(); + const GameInfoset infoset = p_node->GetInfoset(); if (!infoset->GetPlayer()->IsChance()) { p_reached.insert(infoset); for (const auto &action : p_support.GetActions(infoset)) { @@ -131,7 +131,7 @@ void ReachableInfosets(const BehaviorSupportProfile &p_support, const GameNode & bool BehaviorSupportProfile::Dominates(const GameAction &a, const GameAction &b, bool p_strict) const { - GameInfoset infoset = a->GetInfoset(); + const GameInfoset infoset = a->GetInfoset(); if (infoset != b->GetInfoset()) { throw UndefinedException(); } diff --git a/src/games/file.cc b/src/games/file.cc index f8dedf533..9e5d83680 100644 --- a/src/games/file.cc +++ b/src/games/file.cc @@ -306,9 +306,9 @@ class TableFileGame { std::vector m_players; size_t NumPlayers() const { return m_players.size(); } - Array NumStrategies() const + std::vector NumStrategies() const { - Array ret(m_players.size()); + std::vector ret(m_players.size()); std::transform(m_players.begin(), m_players.end(), ret.begin(), [](const TableFilePlayer &player) { return player.m_strategies.size(); }); return ret; @@ -414,10 +414,10 @@ void ReadOutcomeList(GameFileLexer &p_parser, Game &p_nfg) void ParseOutcomeBody(GameFileLexer &p_parser, Game &p_nfg) { ReadOutcomeList(p_parser, p_nfg); - StrategySupportProfile profile(p_nfg); + const StrategySupportProfile profile(p_nfg); for (auto iter : StrategyContingencies(profile)) { p_parser.ExpectCurrentToken(TOKEN_NUMBER, "outcome index"); - if (int outcomeId = std::stoi(p_parser.GetLastText())) { + if (const int outcomeId = std::stoi(p_parser.GetLastText())) { iter->SetOutcome(p_nfg->GetOutcome(outcomeId)); } p_parser.GetNextToken(); @@ -426,9 +426,9 @@ void ParseOutcomeBody(GameFileLexer &p_parser, Game &p_nfg) void ParsePayoffBody(GameFileLexer &p_parser, Game &p_nfg) { - StrategySupportProfile profile(p_nfg); + const StrategySupportProfile profile(p_nfg); for (auto iter : StrategyContingencies(profile)) { - for (auto player : p_nfg->GetPlayers()) { + for (const auto &player : p_nfg->GetPlayers()) { p_parser.ExpectCurrentToken(TOKEN_NUMBER, "numerical payoff"); iter->GetOutcome()->SetPayoff(player, Number(p_parser.GetLastText())); p_parser.GetNextToken(); @@ -486,7 +486,7 @@ void ReadPlayers(GameFileLexer &p_state, Game &p_game, TreeData &p_treeData) void ParseOutcome(GameFileLexer &p_state, Game &p_game, TreeData &p_treeData, GameNode &p_node) { p_state.ExpectCurrentToken(TOKEN_NUMBER, "index of outcome"); - int outcomeId = std::stoi(p_state.GetLastText()); + const int outcomeId = std::stoi(p_state.GetLastText()); p_state.GetNextToken(); if (p_state.GetCurrentToken() == TOKEN_TEXT) { @@ -500,7 +500,7 @@ void ParseOutcome(GameFileLexer &p_state, Game &p_game, TreeData &p_treeData, Ga p_treeData.m_outcomeMap[outcomeId] = outcome; } outcome->SetLabel(p_state.GetLastText()); - p_node->SetOutcome(outcome); + p_game->SetOutcome(p_node, outcome); p_state.ExpectNextToken(TOKEN_LBRACE, "'{'"); p_state.GetNextToken(); @@ -516,7 +516,7 @@ void ParseOutcome(GameFileLexer &p_state, Game &p_game, TreeData &p_treeData, Ga // The node entry does not contain information about the outcome. // This means the outcome should have been defined already. try { - p_node->SetOutcome(p_treeData.m_outcomeMap.at(outcomeId)); + p_game->SetOutcome(p_node, p_treeData.m_outcomeMap.at(outcomeId)); } catch (std::out_of_range) { p_state.OnParseError("Outcome not defined"); @@ -532,7 +532,7 @@ void ParseChanceNode(GameFileLexer &p_state, Game &p_game, GameNode &p_node, Tre p_node->SetLabel(p_state.GetLastText()); p_state.ExpectNextToken(TOKEN_NUMBER, "infoset id"); - int infosetId = atoi(p_state.GetLastText().c_str()); + const int infosetId = atoi(p_state.GetLastText().c_str()); GameInfoset infoset = p_treeData.m_infosetMap[0][infosetId]; if (p_state.GetNextToken() == TOKEN_TEXT) { @@ -540,7 +540,7 @@ void ParseChanceNode(GameFileLexer &p_state, Game &p_game, GameNode &p_node, Tre std::list action_labels; Array probs; - std::string label = p_state.GetLastText(); + const std::string label = p_state.GetLastText(); p_state.ExpectNextToken(TOKEN_LBRACE, "'{'"); p_state.GetNextToken(); do { @@ -553,7 +553,7 @@ void ParseChanceNode(GameFileLexer &p_state, Game &p_game, GameNode &p_node, Tre p_state.GetNextToken(); if (!infoset) { - infoset = p_node->AppendMove(p_game->GetChance(), action_labels.size()); + infoset = p_game->AppendMove(p_node, p_game->GetChance(), action_labels.size()); p_treeData.m_infosetMap[0][infosetId] = infoset; infoset->SetLabel(label); auto action_label = action_labels.begin(); @@ -565,11 +565,11 @@ void ParseChanceNode(GameFileLexer &p_state, Game &p_game, GameNode &p_node, Tre } else { // TODO: Verify actions match up to any previous specifications - p_node->AppendMove(infoset); + p_game->AppendMove(p_node, infoset); } } else if (infoset) { - p_node->AppendMove(infoset); + p_game->AppendMove(p_node, infoset); } else { p_state.OnParseError("Referencing an undefined infoset"); @@ -587,17 +587,17 @@ void ParsePersonalNode(GameFileLexer &p_state, Game p_game, GameNode p_node, Tre p_node->SetLabel(p_state.GetLastText()); p_state.ExpectNextToken(TOKEN_NUMBER, "player id"); - int playerId = std::stoi(p_state.GetLastText()); - GamePlayer player = p_game->GetPlayer(playerId); + const int playerId = std::stoi(p_state.GetLastText()); + const GamePlayer player = p_game->GetPlayer(playerId); p_state.ExpectNextToken(TOKEN_NUMBER, "infoset id"); - int infosetId = std::stoi(p_state.GetLastText()); + const int infosetId = std::stoi(p_state.GetLastText()); GameInfoset infoset = p_treeData.m_infosetMap[playerId][infosetId]; if (p_state.GetNextToken() == TOKEN_TEXT) { // Information set data is specified std::list action_labels; - std::string label = p_state.GetLastText(); + const std::string label = p_state.GetLastText(); p_state.ExpectNextToken(TOKEN_LBRACE, "'{'"); p_state.GetNextToken(); @@ -609,7 +609,7 @@ void ParsePersonalNode(GameFileLexer &p_state, Game p_game, GameNode p_node, Tre p_state.GetNextToken(); if (!infoset) { - infoset = p_node->AppendMove(player, action_labels.size()); + infoset = p_game->AppendMove(p_node, player, action_labels.size()); p_treeData.m_infosetMap[playerId][infosetId] = infoset; infoset->SetLabel(label); auto action_label = action_labels.begin(); @@ -620,11 +620,11 @@ void ParsePersonalNode(GameFileLexer &p_state, Game p_game, GameNode p_node, Tre } else { // TODO: Verify actions match up to previous specifications - p_node->AppendMove(infoset); + p_game->AppendMove(p_node, infoset); } } else if (infoset) { - p_node->AppendMove(infoset); + p_game->AppendMove(p_node, infoset); } else { p_state.OnParseError("Referencing an undefined infoset"); diff --git a/src/games/game.cc b/src/games/game.cc index 7ea040d5d..2da5f4258 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -21,18 +21,17 @@ // #include -#include #include #include +#include #include "gambit.h" #include "writer.h" -// The references to the table and tree representations violate the logic +// The references to the tree representations violate the logic // of separating implementation types. This will be fixed when we move // editing operations into the game itself instead of in the member-object // classes. -#include "gametable.h" #include "gametree.h" namespace Gambit { @@ -41,9 +40,11 @@ namespace Gambit { // class GameOutcomeRep //======================================================================== -GameOutcomeRep::GameOutcomeRep(GameRep *p_game, int p_number) - : m_game(p_game), m_number(p_number), m_payoffs(m_game->NumPlayers()) +GameOutcomeRep::GameOutcomeRep(GameRep *p_game, int p_number) : m_game(p_game), m_number(p_number) { + for (const auto &player : m_game->m_players) { + m_payoffs[player] = Number(); + } } //======================================================================== @@ -51,6 +52,7 @@ GameOutcomeRep::GameOutcomeRep(GameRep *p_game, int p_number) //======================================================================== GameAction GameStrategyRep::GetAction(const GameInfoset &p_infoset) const + { if (p_infoset->GetPlayer() != m_player) { throw MismatchException(); @@ -61,21 +63,15 @@ GameAction GameStrategyRep::GetAction(const GameInfoset &p_infoset) const void GameStrategyRep::DeleteStrategy() { - if (m_player->GetGame()->IsTree()) { - throw UndefinedException(); + if (p_infoset->GetPlayer() != m_player) { + throw MismatchException(); } - if (m_player->NumStrategies() == 1) { - return; + try { + return *std::next(p_infoset->GetActions().cbegin(), m_behav.at(p_infoset) - 1); } - - m_player->GetGame()->IncrementVersion(); - m_player->m_strategies.erase( - std::find(m_player->m_strategies.begin(), m_player->m_strategies.end(), this)); - for (int st = 1; st <= m_player->m_strategies.size(); st++) { - m_player->m_strategies[st]->m_number = st; + catch (std::out_of_range &) { + return nullptr; } - // m_player->m_game->RebuildTable(); - this->Invalidate(); } //======================================================================== @@ -83,11 +79,10 @@ void GameStrategyRep::DeleteStrategy() //======================================================================== GamePlayerRep::GamePlayerRep(GameRep *p_game, int p_id, int p_strats) - : m_game(p_game), m_number(p_id), m_strategies(p_strats) + : m_game(p_game), m_number(p_id) { for (int j = 1; j <= p_strats; j++) { - m_strategies[j] = new GameStrategyRep(this); - m_strategies[j]->m_number = j; + m_strategies.push_back(new GameStrategyRep(this, j, "")); } } @@ -101,175 +96,152 @@ GamePlayerRep::~GamePlayerRep() } } -Array GamePlayerRep::GetStrategies() const +void GamePlayerRep::MakeStrategy(const std::map &p_behavMap) { - m_game->BuildComputedValues(); - Array ret(m_strategies.size()); - std::transform(m_strategies.cbegin(), m_strategies.cend(), ret.begin(), - [](GameStrategyRep *s) -> GameStrategy { return s; }); - return ret; + std::map array_behav; + for (auto [infoset, action] : p_behavMap) { + array_behav[infoset] = action->GetNumber(); + } + return MakeStrategy(array_behav); } -GameStrategy GamePlayerRep::NewStrategy() +void GamePlayerRep::MakeStrategy(const std::map &behav) { - if (m_game->IsTree()) { - throw UndefinedException(); + auto *strategy = new GameStrategyRep(this, m_strategies.size() + 1, ""); + strategy->m_behav = behav; + for (const auto &infoset : m_infosets) { + strategy->m_label += + (contains(strategy->m_behav, infoset)) ? std::to_string(strategy->m_behav[infoset]) : "*"; + } + if (strategy->m_label.empty()) { + strategy->m_label = "*"; } - - m_game->IncrementVersion(); - auto *strategy = new GameStrategyRep(this); m_strategies.push_back(strategy); - strategy->m_number = m_strategies.size(); - strategy->m_offset = -1; // this flags this action as new - dynamic_cast(m_game)->RebuildTable(); - return strategy; } -void GamePlayerRep::MakeStrategy() +void GamePlayerRep::MakeReducedStratsPR(const PlayerConsequences &p_consequences) { - Array c(NumInfosets()); + m_strategies.clear(); + std::map current_strat; - for (int i = 1; i <= NumInfosets(); i++) { - if (m_infosets[i]->flag == 1) { - c[i] = m_infosets[i]->whichbranch; - } - else { - c[i] = 0; - } + if (m_infosets.empty()) { + MakeStrategy(current_strat); + return; } - auto *strategy = new GameStrategyRep(this); - m_strategies.push_back(strategy); - strategy->m_number = m_strategies.size(); - strategy->m_behav = c; - strategy->m_label = ""; - - // We generate a default labeling -- probably should be changed in future - if (!strategy->m_behav.empty()) { - for (int iset = 1; iset <= strategy->m_behav.size(); iset++) { - if (strategy->m_behav[iset] > 0) { - strategy->m_label += lexical_cast(strategy->m_behav[iset]); - } - else { - strategy->m_label += "*"; - } - } - } - else { - strategy->m_label = "*"; + // Initial frontier -- UNORDERED SET of roots; the range constructor builds an ORDERED SET + if (!p_consequences.root_infosets.empty()) { + const std::set initial_frontier( + p_consequences.root_infosets.begin(), p_consequences.root_infosets.end()); + BuildStratsRecursive(p_consequences, current_strat, initial_frontier); } } -void GamePlayerRep::MakeReducedStrats(GameTreeNodeRep *n, GameTreeNodeRep *nn) +// related to Bernhard's third algorithm +void GamePlayerRep::BuildStratsRecursive(const PlayerConsequences &p_consequences, + std::map ¤t_strat, + std::set frontier) { - int i; - GameTreeNodeRep *m, *mm; + if (frontier.empty()) { + MakeStrategy(current_strat); + return; + } + + // infoset with the smallest number + const GameInfoset infoset_to_explore = *frontier.begin(); + frontier.erase(frontier.begin()); - if (!n->GetParent()) { - n->ptr = nullptr; + for (const auto &action : infoset_to_explore->GetActions()) { + current_strat[infoset_to_explore] = action; + + // Recursion: The new frontier is a copy of the old one, plus new consequences. + auto next_frontier = frontier; + const auto &consequences = p_consequences.transitions.at(infoset_to_explore).at(action); + next_frontier.insert(consequences.begin(), consequences.end()); + + BuildStratsRecursive(p_consequences, current_strat, next_frontier); + + current_strat.erase(infoset_to_explore); } +} - if (n->NumChildren() > 0) { - if (n->infoset->m_player == this) { - if (n->infoset->flag == 0) { +void GamePlayerRep::MakeReducedStrats(GameNodeRep *n, GameNodeRep *nn, + std::map &behav, + std::map &ptr, + std::map &whichbranch) +{ + if (!n->IsTerminal()) { + if (n->m_infoset->m_player == this) { + if (!contains(behav, n->m_infoset)) { // we haven't visited this infoset before - n->infoset->flag = 1; - for (i = 1; i <= n->NumChildren(); i++) { - GameTreeNodeRep *m = n->children[i]; - n->whichbranch = m; - n->infoset->whichbranch = i; - MakeReducedStrats(m, nn); + for (size_t i = 1; i <= n->m_children.size(); i++) { + GameNodeRep *m = n->m_children[i - 1]; + whichbranch[n] = m; + behav[n->m_infoset] = i; + MakeReducedStrats(m, nn, behav, ptr, whichbranch); } - n->infoset->flag = 0; + behav.erase(n->m_infoset); } else { // we have visited this infoset, take same action - MakeReducedStrats(n->children[n->infoset->whichbranch], nn); + MakeReducedStrats(n->m_children[behav[n->m_infoset] - 1], nn, behav, ptr, whichbranch); } } else { - n->ptr = nullptr; if (nn != nullptr) { - n->ptr = nn->m_parent; + ptr[n] = nn->m_parent; } - n->whichbranch = n->children[1]; - if (n->infoset) { - n->infoset->whichbranch = 0; + else { + ptr.erase(n); } - MakeReducedStrats(n->children[1], n->children[1]); + whichbranch[n] = n->m_children.front(); + MakeReducedStrats(n->m_children.front(), n->m_children.front(), behav, ptr, whichbranch); } } else if (nn) { - for (;; nn = nn->m_parent->ptr->whichbranch) { - if (!nn->GetNextSibling()) { - m = nullptr; - } - else { - m = dynamic_cast(nn->GetNextSibling().operator->()); - } - if (m || nn->m_parent->ptr == nullptr) { + GameNodeRep *m; + for (;; nn = whichbranch.at(ptr.at(nn->m_parent))) { + m = nn->GetNextSibling(); + if (m || !contains(ptr, nn->m_parent)) { break; } } if (m) { - mm = m->m_parent->whichbranch; - m->m_parent->whichbranch = m; - MakeReducedStrats(m, m); - m->m_parent->whichbranch = mm; + GameNodeRep *mm = whichbranch.at(m->m_parent); + whichbranch[m->m_parent] = m; + MakeReducedStrats(m, m, behav, ptr, whichbranch); + whichbranch[m->m_parent] = mm; } else { - MakeStrategy(); + MakeStrategy(behav); } } else { - MakeStrategy(); + MakeStrategy(behav); } } -GameInfoset GamePlayerRep::GetInfoset(int p_index) const { return m_infosets[p_index]; } - -Array GamePlayerRep::GetInfosets() const -{ - Array ret(m_infosets.size()); - std::transform(m_infosets.cbegin(), m_infosets.cend(), ret.begin(), - [](GameTreeInfosetRep *s) -> GameInfoset { return s; }); - return ret; -} - -int GamePlayerRep::NumSequences() const +size_t GamePlayerRep::NumSequences() const { if (!m_game->IsTree()) { throw UndefinedException(); } - return std::accumulate( - m_infosets.cbegin(), m_infosets.cend(), 1, - [](int ct, GameTreeInfosetRep *s) -> int { return ct + s->m_actions.size(); }); + return std::transform_reduce(m_infosets.cbegin(), m_infosets.cend(), 1, std::plus<>(), + [](const GameInfosetRep *s) { return s->m_actions.size(); }); } //======================================================================== // class GameRep //======================================================================== -Array GameRep::GetPlayers() const +GameRep::~GameRep() { - Array ret(NumPlayers()); - for (int pl = 1; pl <= NumPlayers(); pl++) { - ret[pl] = GetPlayer(pl); + for (auto player : m_players) { + player->Invalidate(); } - return ret; -} - -Array GameRep::GetStrategies() const -{ - Array ret(MixedProfileLength()); - auto output = ret.begin(); - for (auto player : GetPlayers()) { - for (auto strategy : player->GetStrategies()) { - *output = strategy; - ++output; - } + for (auto outcome : m_outcomes) { + outcome->Invalidate(); } - return ret; } //------------------------------------------------------------------------ @@ -352,11 +324,11 @@ template MixedStrategyProfileRep *MixedStrategyProfileRep::Norma { auto norm = Copy(); for (auto player : m_support.GetGame()->GetPlayers()) { - T sum = (T)0; + T sum = static_cast(0); for (auto strategy : m_support.GetStrategies(player)) { sum += (*this)[strategy]; } - if (sum == (T)0) { + if (sum == static_cast(0)) { continue; } for (auto strategy : m_support.GetStrategies(player)) { @@ -368,7 +340,7 @@ template MixedStrategyProfileRep *MixedStrategyProfileRep::Norma template T MixedStrategyProfileRep::GetRegret(const GameStrategy &p_strategy) const { - GamePlayer player = p_strategy->GetPlayer(); + const GamePlayer player = p_strategy->GetPlayer(); T payoff = GetPayoffDeriv(player->GetNumber(), p_strategy); T brpayoff = payoff; for (auto strategy : player->GetStrategies()) { @@ -407,46 +379,31 @@ template MixedStrategyProfile::MixedStrategyProfile(const MixedBehaviorProfile &p_profile) : m_rep(new TreeMixedStrategyProfileRep(p_profile)) { - Game game = p_profile.GetGame(); - auto *efg = dynamic_cast(game.operator->()); - for (int pl = 1; pl <= m_rep->m_support.GetGame()->NumPlayers(); pl++) { - GamePlayer player = m_rep->m_support.GetGame()->GetPlayer(pl); - for (int st = 1; st <= player->NumStrategies(); st++) { - T prob = (T)1; - - for (int iset = 1; iset <= efg->GetPlayer(pl)->NumInfosets(); iset++) { - if (efg->m_players[pl]->m_strategies[st]->m_behav[iset] > 0) { - GameInfoset infoset = player->GetInfoset(iset); - prob *= - p_profile[infoset->GetAction(efg->m_players[pl]->m_strategies[st]->m_behav[iset])]; + auto *efg = dynamic_cast(p_profile.GetGame().operator->()); + for (const auto &player : efg->m_players) { + for (const auto &strategy : player->m_strategies) { + auto prob = static_cast(1); + for (const auto &infoset : player->m_infosets) { + if (strategy->m_behav[infoset] > 0) { + prob *= p_profile[infoset->GetAction(strategy->m_behav[infoset])]; } } - (*this)[m_rep->m_support.GetGame()->GetPlayer(pl)->GetStrategy(st)] = prob; + (*m_rep)[strategy] = prob; } } } -template -MixedStrategyProfile::MixedStrategyProfile(const MixedStrategyProfile &p_profile) - : m_rep(p_profile.m_rep->Copy()) -{ - InvalidateCache(); -} - template MixedStrategyProfile & MixedStrategyProfile::operator=(const MixedStrategyProfile &p_profile) { if (this != &p_profile) { InvalidateCache(); - delete m_rep; - m_rep = p_profile.m_rep->Copy(); + m_rep.reset(p_profile.m_rep->Copy()); } return *this; } -template MixedStrategyProfile::~MixedStrategyProfile() { delete m_rep; } - //======================================================================== // MixedStrategyProfile: General data access //======================================================================== @@ -456,10 +413,8 @@ template Vector MixedStrategyProfile::operator[](const GamePlaye CheckVersion(); auto strategies = m_rep->m_support.GetStrategies(p_player); Vector probs(strategies.size()); - int st = 1; - for (auto strategy : strategies) { - probs[st] = (*this)[strategy]; - } + std::transform(strategies.begin(), strategies.end(), probs.begin(), + [this](const GameStrategy &s) { return (*m_rep)[s]; }); return probs; } @@ -468,9 +423,10 @@ template MixedStrategyProfile MixedStrategyProfile::ToFullSuppor CheckVersion(); MixedStrategyProfile full(m_rep->m_support.GetGame()->NewMixedStrategyProfile(T(0))); - for (auto player : m_rep->m_support.GetGame()->GetPlayers()) { - for (auto strategy : player->GetStrategies()) { - full[strategy] = (m_rep->m_support.Contains(strategy)) ? (*this)[strategy] : T(0); + for (const auto &player : m_rep->m_support.GetGame()->GetPlayers()) { + for (const auto &strategy : player->GetStrategies()) { + full[strategy] = + (m_rep->m_support.Contains(strategy)) ? (*m_rep)[strategy] : static_cast(0); } } return full; @@ -479,6 +435,7 @@ template MixedStrategyProfile MixedStrategyProfile::ToFullSuppor //======================================================================== // MixedStrategyProfile: Computation of interesting quantities //======================================================================== + template void MixedStrategyProfile::ComputePayoffs() const { if (!map_profile_payoffs.empty()) { @@ -486,10 +443,10 @@ template void MixedStrategyProfile::ComputePayoffs() const // so don't compute anything, simply return return; } - for (auto player : m_rep->m_support.GetPlayers()) { + for (const auto &player : m_rep->m_support.GetPlayers()) { map_profile_payoffs[player] = GetPayoff(player); // values of the player's strategies - for (auto strategy : m_rep->m_support.GetStrategies(player)) { + for (const auto &strategy : m_rep->m_support.GetStrategies(player)) { map_strategy_payoffs[player][strategy] = GetPayoff(strategy); } } @@ -500,13 +457,10 @@ template T MixedStrategyProfile::GetLiapValue() const CheckVersion(); ComputePayoffs(); - T liapValue = T(0); - for (auto player : m_rep->m_support.GetPlayers()) { + auto liapValue = static_cast(0); + for (auto [player, payoff] : map_profile_payoffs) { for (auto v : map_strategy_payoffs[player]) { - T regret = v.second - map_profile_payoffs[player]; - if (regret > T(0)) { - liapValue += regret * regret; // penalty if not best response - } + liapValue += sqr(std::max(v.second - payoff, static_cast(0))); } } return liapValue; diff --git a/src/games/game.h b/src/games/game.h index ce3df9b50..075ca92a4 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -23,10 +23,9 @@ #ifndef LIBGAMBIT_GAME_H #define LIBGAMBIT_GAME_H -#include #include #include -#include +#include #include "number.h" #include "gameobject.h" @@ -57,6 +56,79 @@ using GameNode = GameObjectPtr; class GameRep; using Game = GameObjectPtr; +template class ElementCollection { + P m_owner{nullptr}; + const std::vector *m_container{nullptr}; + +public: + class iterator { + P m_owner{nullptr}; + const std::vector *m_container{nullptr}; + size_t m_index{0}; + + public: + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = GameObjectPtr; + using pointer = value_type *; + using reference = value_type &; + + iterator() = default; + iterator(const P &p_owner, const std::vector *p_container, size_t p_index = 0) + : m_owner(p_owner), m_container(p_container), m_index(p_index) + { + } + iterator(const iterator &) = default; + ~iterator() = default; + iterator &operator=(const iterator &) = default; + + bool operator==(const iterator &p_iter) const + { + return m_owner == p_iter.m_owner && m_container == p_iter.m_container && + m_index == p_iter.m_index; + } + bool operator!=(const iterator &p_iter) const + { + return m_owner != p_iter.m_owner || m_container != p_iter.m_container || + m_index != p_iter.m_index; + } + + iterator &operator++() + { + m_index++; + return *this; + } + iterator &operator--() + { + m_index--; + return *this; + } + value_type operator*() const { return m_container->at(m_index); } + }; + + ElementCollection() = default; + explicit ElementCollection(const P &p_owner, const std::vector *p_container) + : m_owner(p_owner), m_container(p_container) + { + } + ElementCollection(const ElementCollection &) = default; + ~ElementCollection() = default; + ElementCollection &operator=(const ElementCollection &) = default; + + bool operator==(const ElementCollection &p_other) const + { + return m_owner == p_other.m_owner && m_container == p_other.m_container; + } + size_t size() const { return m_container->size(); } + GameObjectPtr front() const { return m_container->front(); } + GameObjectPtr back() const { return m_container->back(); } + + iterator begin() const { return {m_owner, m_container, 0}; } + iterator end() const { return {m_owner, m_container, (m_owner) ? m_container->size() : 0}; } + iterator cbegin() const { return {m_owner, m_container, 0}; } + iterator cend() const { return {m_owner, m_container, (m_owner) ? m_container->size() : 0}; } +}; + // // Forward declarations of classes defined elsewhere. // @@ -117,11 +189,10 @@ class GameOutcomeRep : public GameObject { friend class GameTreeRep; friend class GameTableRep; -private: GameRep *m_game; int m_number; std::string m_label; - Array m_payoffs; + std::map m_payoffs; /// @name Lifecycle //@{ @@ -143,12 +214,8 @@ class GameOutcomeRep : public GameObject { /// Sets the text label associated with the outcome void SetLabel(const std::string &p_label) { m_label = p_label; } - /// Gets the payoff associated with the outcome to player 'pl' - const Number &GetPayoff(int pl) const { return m_payoffs[pl]; } /// Gets the payoff associated with the outcome to the player - const Number &GetPayoff(const GamePlayer &p_player) const; - /// Sets the payoff to player 'pl' - void SetPayoff(int pl, const Number &p_value); + template const T &GetPayoff(const GamePlayer &p_player) const; /// Sets the payoff to the player void SetPayoff(const GamePlayer &p_player, const Number &p_value); //@} @@ -156,61 +223,88 @@ class GameOutcomeRep : public GameObject { /// An action at an information set in an extensive game class GameActionRep : public GameObject { -protected: - GameActionRep() = default; + friend class GameTreeRep; + friend class GameInfosetRep; + template friend class MixedBehaviorProfile; + + int m_number; + std::string m_label; + GameInfosetRep *m_infoset; + + GameActionRep(int p_number, const std::string &p_label, GameInfosetRep *p_infoset) + : m_number(p_number), m_label(p_label), m_infoset(p_infoset) + { + } ~GameActionRep() override = default; public: - virtual int GetNumber() const = 0; - virtual GameInfoset GetInfoset() const = 0; - - virtual const std::string &GetLabel() const = 0; - virtual void SetLabel(const std::string &p_label) = 0; + int GetNumber() const { return m_number; } + GameInfoset GetInfoset() const; - virtual bool Precedes(const GameNode &) const = 0; + const std::string &GetLabel() const { return m_label; } + void SetLabel(const std::string &p_label) { m_label = p_label; } - virtual void DeleteAction() = 0; + bool Precedes(const GameNode &) const; }; /// An information set in an extensive game class GameInfosetRep : public GameObject { -protected: - GameInfosetRep() = default; - ~GameInfosetRep() override = default; + friend class GameTreeRep; + friend class GamePlayerRep; + friend class GameNodeRep; + template friend class MixedBehaviorProfile; + + GameRep *m_game; + int m_number; + std::string m_label; + GamePlayerRep *m_player; + std::vector m_actions; + std::vector m_members; + std::vector m_probs; + + GameInfosetRep(GameRep *p_efg, int p_number, GamePlayerRep *p_player, int p_actions); + ~GameInfosetRep() override; + + void RenumberActions() + { + std::for_each(m_actions.begin(), m_actions.end(), + [act = 1](GameActionRep *a) mutable { a->m_number = act++; }); + } public: - virtual Game GetGame() const = 0; - virtual int GetNumber() const = 0; + using Actions = ElementCollection; + using Members = ElementCollection; - virtual GamePlayer GetPlayer() const = 0; - virtual void SetPlayer(GamePlayer p) = 0; + Game GetGame() const; + int GetNumber() const { return m_number; } - virtual bool IsChanceInfoset() const = 0; + GamePlayer GetPlayer() const; - virtual void SetLabel(const std::string &p_label) = 0; - virtual const std::string &GetLabel() const = 0; + bool IsChanceInfoset() const; - virtual GameAction InsertAction(GameAction p_where = nullptr) = 0; + void SetLabel(const std::string &p_label) { m_label = p_label; } + const std::string &GetLabel() const { return m_label; } /// @name Actions //@{ - /// Returns the number of actions available at the information set - virtual int NumActions() const = 0; /// Returns the p_index'th action at the information set - virtual GameAction GetAction(int p_index) const = 0; + GameAction GetAction(int p_index) const { return m_actions.at(p_index - 1); } /// Returns the actions available at the information set - virtual Array GetActions() const = 0; + Actions GetActions() const { return Actions(this, &m_actions); } //@} - virtual int NumMembers() const = 0; - virtual GameNode GetMember(int p_index) const = 0; - virtual Array GetMembers() const = 0; + GameNode GetMember(int p_index) const { return m_members.at(p_index - 1); } + Members GetMembers() const { return Members(this, &m_members); } - virtual bool Precedes(GameNode) const = 0; + bool Precedes(GameNode) const; - virtual const Number &GetActionProb(int i) const = 0; - virtual const Number &GetActionProb(const GameAction &) const = 0; - virtual void Reveal(GamePlayer) = 0; + const Number &GetActionProb(const GameAction &p_action) const + { + if (p_action->GetInfoset() != GameInfoset(const_cast(this))) { + throw MismatchException(); + } + return m_probs.at(p_action->GetNumber() - 1); + } }; /// \brief A strategy in a game. @@ -237,17 +331,17 @@ class GameStrategyRep : public GameObject { template friend class TableMixedStrategyProfileRep; template friend class MixedBehaviorProfile; -private: - int m_number; GamePlayerRep *m_player; - long m_offset; + int m_number; + long m_offset{-1L}; std::string m_label; - Array m_behav; + std::map m_behav; /// @name Lifecycle //@{ /// Creates a new strategy for the given player. - explicit GameStrategyRep(GamePlayerRep *p_player) : m_number(0), m_player(p_player), m_offset(0L) + explicit GameStrategyRep(GamePlayerRep *p_player, int p_number, const std::string &p_label) + : m_player(p_player), m_number(p_number), m_label(p_label) { } //@} @@ -274,37 +368,57 @@ class GameStrategyRep : public GameObject { }; /// A player in a game +struct PlayerConsequences { + std::set root_infosets; + std::map>> transitions; +}; +struct InfosetNumberCmp { + bool operator()(const GameInfoset &a, const GameInfoset &b) const + { + return a->GetNumber() < b->GetNumber(); + } +}; class GamePlayerRep : public GameObject { + friend class GameRep; friend class GameExplicitRep; friend class GameTreeRep; friend class GameTableRep; friend class GameAGGRep; friend class GameBAGGRep; - friend class GameTreeInfosetRep; - friend class GameStrategyRep; - friend class GameTreeNodeRep; + friend class GameInfosetRep; + friend class GameNodeRep; friend class StrategySupportProfile; template friend class MixedBehaviorProfile; template friend class MixedStrategyProfile; /// @name Building reduced form strategies //@{ - void MakeStrategy(); - void MakeReducedStrats(class GameTreeNodeRep *, class GameTreeNodeRep *); + void MakeStrategy(const std::map &p_behavMap); + void MakeStrategy(const std::map &); + void MakeReducedStratsPR(const PlayerConsequences &p_consequences); + void BuildStratsRecursive(const PlayerConsequences &p_consequences, + std::map ¤t_strat, + std::set frontier); + void MakeReducedStrats(class GameNodeRep *, class GameNodeRep *, + std::map &, + std::map &ptr, + std::map &whichbranch); //@} -private: GameRep *m_game; int m_number; std::string m_label; - Array m_infosets; - Array m_strategies; + std::vector m_infosets; + std::vector m_strategies; GamePlayerRep(GameRep *p_game, int p_id) : m_game(p_game), m_number(p_id) {} GamePlayerRep(GameRep *p_game, int p_id, int m_strats); ~GamePlayerRep() override; public: + using Infosets = ElementCollection; + using Strategies = ElementCollection; + int GetNumber() const { return m_number; } Game GetGame() const; @@ -315,91 +429,85 @@ class GamePlayerRep : public GameObject { /// @name Information sets //@{ - /// Returns the number of information sets at which the player makes a choice - int NumInfosets() const { return m_infosets.size(); } /// Returns the p_index'th information set - GameInfoset GetInfoset(int p_index) const; - /// Returns the information sets for the players - Array GetInfosets() const; + GameInfoset GetInfoset(int p_index) const { return m_infosets.at(p_index - 1); } + /// Returns the information sets for the player + Infosets GetInfosets() const { return Infosets(this, &m_infosets); } /// @name Strategies //@{ - /// Returns the number of strategies available to the player - int NumStrategies() const; /// Returns the st'th strategy for the player GameStrategy GetStrategy(int st) const; /// Returns the array of strategies available to the player - Array GetStrategies() const; - /// Creates a new strategy for the player - GameStrategy NewStrategy(); + Strategies GetStrategies() const; //@} /// @name Sequences //@{ /// Returns the number of sequences available to the player - int NumSequences() const; + size_t NumSequences() const; //@} }; /// A node in an extensive game class GameNodeRep : public GameObject { -protected: - GameNodeRep() = default; - ~GameNodeRep() override = default; - -public: - virtual Game GetGame() const = 0; + friend class GameTreeRep; + friend class GameActionRep; + friend class GameInfosetRep; + friend class GamePlayerRep; + friend class PureBehaviorProfile; + template friend class MixedBehaviorProfile; - virtual const std::string &GetLabel() const = 0; - virtual void SetLabel(const std::string &p_label) = 0; + int m_number{0}; + GameRep *m_game; + std::string m_label; + GameInfosetRep *m_infoset{nullptr}; + GameNodeRep *m_parent; + GameOutcomeRep *m_outcome{nullptr}; + std::vector m_children; - virtual int GetNumber() const = 0; + GameNodeRep(GameRep *e, GameNodeRep *p); + ~GameNodeRep() override; - virtual int NumChildren() const = 0; - virtual GameNode GetChild(int i) const = 0; - virtual GameNode GetChild(const GameAction &) const = 0; - virtual Array GetChildren() const = 0; + void DeleteOutcome(GameOutcomeRep *outc); - virtual GameInfoset GetInfoset() const = 0; - virtual void SetInfoset(GameInfoset) = 0; - virtual GameInfoset LeaveInfoset() = 0; +public: + using Children = ElementCollection; - virtual bool IsTerminal() const = 0; - virtual GamePlayer GetPlayer() const = 0; - virtual GameAction GetPriorAction() const = 0; - virtual GameNode GetParent() const = 0; - virtual GameNode GetNextSibling() const = 0; - virtual GameNode GetPriorSibling() const = 0; + Game GetGame() const; - virtual GameOutcome GetOutcome() const = 0; - virtual void SetOutcome(const GameOutcome &p_outcome) = 0; + const std::string &GetLabel() const { return m_label; } + void SetLabel(const std::string &p_label) { m_label = p_label; } - virtual bool IsSuccessorOf(GameNode from) const = 0; - virtual bool IsSubgameRoot() const = 0; + int GetNumber() const { return m_number; } + GameNode GetChild(const GameAction &p_action) + { + if (p_action->GetInfoset() != m_infoset) { + throw MismatchException(); + } + return m_children.at(p_action->GetNumber() - 1); + } + Children GetChildren() const { return Children(this, &m_children); } - virtual void DeleteParent() = 0; - virtual void DeleteTree() = 0; + GameInfoset GetInfoset() const { return m_infoset; } - virtual void CopyTree(GameNode src) = 0; - virtual void MoveTree(GameNode src) = 0; + bool IsTerminal() const { return m_children.empty(); } + GamePlayer GetPlayer() const { return (m_infoset) ? m_infoset->GetPlayer() : nullptr; } + GameAction GetPriorAction() const; // returns null if root node + GameNode GetParent() const { return m_parent; } + GameNode GetNextSibling() const; + GameNode GetPriorSibling() const; - /// Create a separate Game object containing the subgame rooted at the node - virtual Game CopySubgame() const = 0; + GameOutcome GetOutcome() const { return m_outcome; } - virtual GameInfoset AppendMove(GamePlayer p_player, int p_actions) = 0; - virtual GameInfoset AppendMove(GameInfoset p_infoset) = 0; - virtual GameInfoset InsertMove(GamePlayer p_player, int p_actions) = 0; - virtual GameInfoset InsertMove(GameInfoset p_infoset) = 0; + bool IsSuccessorOf(GameNode from) const; + bool IsSubgameRoot() const; }; /// This is the class for representing an arbitrary finite game. class GameRep : public BaseGameRep { friend class GameOutcomeRep; - friend class GameTreeInfosetRep; - friend class GameTreeActionRep; - friend class GameStrategyRep; - friend class GamePlayerRep; - friend class GameTreeNodeRep; + friend class GameNodeRep; friend class PureStrategyProfileRep; friend class TablePureStrategyProfileRep; template friend class MixedBehaviorProfile; @@ -407,23 +515,94 @@ class GameRep : public BaseGameRep { template friend class TableMixedStrategyProfileRep; protected: + std::vector m_players; + std::vector m_outcomes; std::string m_title, m_comment; - unsigned int m_version; + unsigned int m_version{0}; - GameRep() : m_version(0) {} + GameRep() = default; /// @name Managing the representation //@{ /// Mark that the content of the game has changed void IncrementVersion() { m_version++; } - /// Build any computed values anew - virtual void BuildComputedValues() {} + //@} public: + using Players = ElementCollection; + using Outcomes = ElementCollection; + + class Nodes { + private: + Game m_owner{nullptr}; + std::stack m_stack{}; + Nodes(Game game) : m_owner(game) {} + + public: + Nodes() = default; + + Nodes(Game game, GameNode start_node) : m_owner(game) + { + if (!start_node) { + return; + } + if (start_node->GetGame() != m_owner) { + throw MismatchException(); + } + if (start_node != m_owner->GetRoot()) { + throw std::invalid_argument("Node iteration cannot be initiated for subtrees."); + } + m_stack.push(start_node); + } + + GameNode operator*() const + { + if (m_stack.empty()) { + throw std::runtime_error("Cannot dereference an end iterator"); + } + return m_stack.top(); + } + + Nodes &operator++() + { + if (m_stack.empty()) { + throw std::out_of_range("Cannot increment an end iterator"); + } + const GameNode n = m_stack.top(); + m_stack.pop(); + auto children = n->GetChildren(); + auto it = children.end(); + while (it != children.begin()) { + --it; // Decrement first, then dereference + m_stack.push(*it); + } + return *this; + } + + bool operator==(const Nodes &other) const + { + if (m_owner != other.m_owner) { + return false; + } + if (m_stack.empty() && other.m_stack.empty()) { + return true; + } + if (m_stack.empty() || other.m_stack.empty()) { + return false; + } + return m_stack.top() == other.m_stack.top(); + } + + bool operator!=(const Nodes &other) const { return !(*this == other); } + + friend class GameRep; + }; + /// @name Lifecycle //@{ /// Clean up the game - ~GameRep() override = default; + ~GameRep() override; + /// Create a copy of the game, as a new game virtual Game Copy() const = 0; //@} @@ -433,7 +612,7 @@ class GameRep : public BaseGameRep { /// Returns true if the game has a game tree representation virtual bool IsTree() const = 0; - /// Returns true if the game has a action-graph game representation + /// Returns true if the game has an action-graph game representation virtual bool IsAgg() const { return false; } /// Get the text label associated with the game @@ -452,20 +631,24 @@ class GameRep : public BaseGameRep { /// Returns true if the game is constant-sum virtual bool IsConstSum() const = 0; - /// Returns the smallest payoff in any outcome of the game - virtual Rational GetMinPayoff(int pl = 0) const = 0; - /// Returns the largest payoff in any outcome of the game - virtual Rational GetMaxPayoff(int pl = 0) const = 0; - - /// Returns true if the game is perfect recall. If not, - /// a pair of violating information sets is returned in the parameters. - virtual bool IsPerfectRecall(GameInfoset &, GameInfoset &) const = 0; + /// Returns the smallest payoff to any player in any outcome of the game + virtual Rational GetMinPayoff() const = 0; + /// Returns the smallest payoff to the player in any outcome of the game + virtual Rational GetMinPayoff(const GamePlayer &p_player) const = 0; + /// Returns the largest payoff to any player in any outcome of the game + virtual Rational GetMaxPayoff() const = 0; + /// Returns the largest payoff to the player in any outcome of the game + virtual Rational GetMaxPayoff(const GamePlayer &p_player) const = 0; + + /// Returns the set of terminal nodes which are descendants of node + virtual std::vector GetPlays(GameNode node) const { throw UndefinedException(); } + /// Returns the set of terminal nodes which are descendants of members of an infoset + virtual std::vector GetPlays(GameInfoset infoset) const { throw UndefinedException(); } + /// Returns the set of terminal nodes which are descendants of members of an action + virtual std::vector GetPlays(GameAction action) const { throw UndefinedException(); } + /// Returns true if the game is perfect recall - bool IsPerfectRecall() const - { - GameInfoset s, t; - return IsPerfectRecall(s, t); - } + virtual bool IsPerfectRecall() const = 0; //@} /// @name Writing data files @@ -476,7 +659,7 @@ class GameRep : public BaseGameRep { throw UndefinedException(); } /// Write the game in .efg format to the specified stream - virtual void WriteEfgFile(std::ostream &, const GameNode &subtree = 0) const + virtual void WriteEfgFile(std::ostream &, const GameNode &subtree = nullptr) const { throw UndefinedException(); } @@ -484,20 +667,71 @@ class GameRep : public BaseGameRep { virtual void WriteNfgFile(std::ostream &p_stream) const; //@} + virtual void SetPlayer(GameInfoset p_infoset, GamePlayer p_player) + { + throw UndefinedException(); + } + virtual GameInfoset AppendMove(GameNode p_node, GamePlayer p_player, int p_actions) + { + throw UndefinedException(); + } + virtual GameInfoset AppendMove(GameNode p_node, GameInfoset p_infoset) + { + throw UndefinedException(); + } + virtual GameInfoset InsertMove(GameNode p_node, GamePlayer p_player, int p_actions) + { + throw UndefinedException(); + } + virtual GameInfoset InsertMove(GameNode p_node, GameInfoset p_infoset) + { + throw UndefinedException(); + } + virtual void DeleteParent(GameNode) { throw UndefinedException(); } + virtual void DeleteTree(GameNode) { throw UndefinedException(); } + virtual void CopyTree(GameNode dest, GameNode src) { throw UndefinedException(); } + /// Create a separate Game object containing the subgame rooted at the node + virtual Game CopySubgame(GameNode) const { throw UndefinedException(); } + virtual void MoveTree(GameNode dest, GameNode src) { throw UndefinedException(); } + virtual void Reveal(GameInfoset, GamePlayer) { throw UndefinedException(); } + virtual void SetInfoset(GameNode, GameInfoset) { throw UndefinedException(); } + virtual GameInfoset LeaveInfoset(GameNode) { throw UndefinedException(); } + virtual GameAction InsertAction(GameInfoset, GameAction p_where = nullptr) + { + throw UndefinedException(); + } + virtual void DeleteAction(GameAction) { throw UndefinedException(); } + virtual void SetOutcome(GameNode, const GameOutcome &p_outcome) { throw UndefinedException(); } + /// @name Dimensions of the game //@{ /// The number of strategies for each player virtual Array NumStrategies() const = 0; /// Gets the i'th strategy in the game, numbered globally virtual GameStrategy GetStrategy(int p_index) const = 0; - /// Gets the set of strategies in the game - Array GetStrategies() const; + /// Creates a new strategy for the player + virtual GameStrategy NewStrategy(const GamePlayer &p_player, const std::string &p_label) + { + throw UndefinedException(); + } + /// Remove the strategy from the game + virtual void DeleteStrategy(const GameStrategy &p_strategy) { throw UndefinedException(); } /// Returns the number of strategy contingencies in the game - virtual int NumStrategyContingencies() const = 0; + int NumStrategyContingencies() const + { + BuildComputedValues(); + return std::transform_reduce(m_players.begin(), m_players.end(), 0, std::multiplies<>(), + [](const GamePlayerRep *p) { return p->m_strategies.size(); }); + } /// Returns the total number of actions in the game virtual int BehavProfileLength() const = 0; /// Returns the total number of strategies in the game - virtual int MixedProfileLength() const = 0; + int MixedProfileLength() const + { + BuildComputedValues(); + return std::transform_reduce(m_players.begin(), m_players.end(), 0, std::plus<>(), + [](const GamePlayerRep *p) { return p->m_strategies.size(); }); + } //@} virtual PureStrategyProfile NewPureStrategyProfile() const = 0; @@ -534,11 +768,11 @@ class GameRep : public BaseGameRep { /// @name Players //@{ /// Returns the number of players in the game - virtual int NumPlayers() const = 0; + size_t NumPlayers() const { return m_players.size(); } /// Returns the pl'th player in the game - virtual GamePlayer GetPlayer(int pl) const = 0; + GamePlayer GetPlayer(int pl) const { return m_players.at(pl - 1); } /// Returns the set of players in the game - virtual Array GetPlayers() const; + Players GetPlayers() const { return Players(this, &m_players); } /// Returns the chance (nature) player virtual GamePlayer GetChance() const = 0; /// Creates a new player in the game, with no moves @@ -548,25 +782,21 @@ class GameRep : public BaseGameRep { /// @name Information sets //@{ /// Returns the iset'th information set in the game (numbered globally) - virtual GameInfoset GetInfoset(int iset) const = 0; + virtual GameInfoset GetInfoset(int iset) const { throw UndefinedException(); } /// Returns the set of information sets in the game - virtual Array GetInfosets() const = 0; - /// Returns an array with the number of information sets per personal player - virtual Array NumInfosets() const = 0; - /// Returns the act'th action in the game (numbered globally) - virtual GameAction GetAction(int act) const = 0; + virtual std::vector GetInfosets() const { throw UndefinedException(); } //@} /// @name Outcomes //@{ - /// Returns the number of outcomes defined in the game - virtual int NumOutcomes() const = 0; /// Returns the index'th outcome defined in the game - virtual GameOutcome GetOutcome(int index) const = 0; + GameOutcome GetOutcome(int index) const { return m_outcomes.at(index - 1); } + /// Returns the set of outcomes in the game + Outcomes GetOutcomes() const { return Outcomes(this, &m_outcomes); } /// Creates a new outcome in the game - virtual GameOutcome NewOutcome() = 0; + virtual GameOutcome NewOutcome() { throw UndefinedException(); } /// Deletes the specified outcome from the game - virtual void DeleteOutcome(const GameOutcome &) = 0; + virtual void DeleteOutcome(const GameOutcome &) { throw UndefinedException(); } //@} /// @name Nodes @@ -574,7 +804,9 @@ class GameRep : public BaseGameRep { /// Returns the root node of the game virtual GameNode GetRoot() const = 0; /// Returns the number of nodes in the game - virtual int NumNodes() const = 0; + virtual size_t NumNodes() const = 0; + /// Returns the number of non-terminal nodes in the game + virtual size_t NumNonterminalNodes() const = 0; //@} /// @name Modification @@ -582,6 +814,13 @@ class GameRep : public BaseGameRep { /// Set the probability distribution of actions at a chance node virtual Game SetChanceProbs(const GameInfoset &, const Array &) = 0; //@} + + /// Node iterators + Nodes begin() const { return {this, GetRoot()}; } + Nodes end() const { return {this}; } + + /// Build any computed values anew + virtual void BuildComputedValues() const {} }; //======================================================================= @@ -592,18 +831,25 @@ class GameRep : public BaseGameRep { // all classes to be defined. inline Game GameOutcomeRep::GetGame() const { return m_game; } -inline const Number &GameOutcomeRep::GetPayoff(const GamePlayer &p_player) const + +template const T &GameOutcomeRep::GetPayoff(const GamePlayer &p_player) const { - if (p_player->GetGame() != GetGame()) { + try { + return static_cast(m_payoffs.at(p_player)); + } + catch (const std::out_of_range &) { throw MismatchException(); } - return m_payoffs[p_player->GetNumber()]; } -inline void GameOutcomeRep::SetPayoff(int pl, const Number &p_value) +template <> inline const Number &GameOutcomeRep::GetPayoff(const GamePlayer &p_player) const { - m_game->IncrementVersion(); - m_payoffs[pl] = p_value; + try { + return m_payoffs.at(p_player); + } + catch (const std::out_of_range &) { + throw MismatchException(); + } } inline void GameOutcomeRep::SetPayoff(const GamePlayer &p_player, const Number &p_value) @@ -611,30 +857,36 @@ inline void GameOutcomeRep::SetPayoff(const GamePlayer &p_player, const Number & if (p_player->GetGame() != GetGame()) { throw MismatchException(); } + m_payoffs[p_player] = p_value; m_game->IncrementVersion(); - m_payoffs[p_player->GetNumber()] = p_value; } inline GamePlayer GameStrategyRep::GetPlayer() const { return m_player; } +inline Game GameInfosetRep::GetGame() const { return m_game; } +inline GamePlayer GameInfosetRep::GetPlayer() const { return m_player; } +inline bool GameInfosetRep::IsChanceInfoset() const { return m_player->IsChance(); } + inline Game GamePlayerRep::GetGame() const { return m_game; } -inline int GamePlayerRep::NumStrategies() const +inline GameStrategy GamePlayerRep::GetStrategy(int st) const { m_game->BuildComputedValues(); - return m_strategies.size(); + return m_strategies.at(st - 1); } -inline GameStrategy GamePlayerRep::GetStrategy(int st) const +inline GamePlayerRep::Strategies GamePlayerRep::GetStrategies() const { m_game->BuildComputedValues(); - return m_strategies[st]; + return Strategies(this, &m_strategies); } +inline Game GameNodeRep::GetGame() const { return m_game; } + //======================================================================= /// Factory function to create new game tree Game NewTree(); /// Factory function to create new game table -Game NewTable(const Array &p_dim, bool p_sparseOutcomes = false); +Game NewTable(const std::vector &p_dim, bool p_sparseOutcomes = false); /// @brief Reads a game representation in .efg format /// @@ -671,7 +923,9 @@ Game ReadGame(std::istream &p_stream); template std::list UniformOnSimplex(int p_denom, size_t p_dim, Generator &generator) { - std::uniform_int_distribution dist(1, p_denom + int(p_dim) - 1); + // NOLINTBEGIN(misc-const-correctness) + std::uniform_int_distribution dist(1, p_denom + static_cast(p_dim) - 1); + // NOLINTEND(misc-const-correctness) std::set cutoffs; while (cutoffs.size() < p_dim - 1) { cutoffs.insert(dist(generator)); diff --git a/src/games/gameagg.cc b/src/games/gameagg.cc index 3ce014aaa..2e58bbd0b 100644 --- a/src/games/gameagg.cc +++ b/src/games/gameagg.cc @@ -34,12 +34,14 @@ namespace Gambit { class AGGPureStrategyProfileRep : public PureStrategyProfileRep { public: explicit AGGPureStrategyProfileRep(const Game &p_game) : PureStrategyProfileRep(p_game) {} - PureStrategyProfileRep *Copy() const override { return new AGGPureStrategyProfileRep(*this); } + std::shared_ptr Copy() const override + { + return std::make_shared(*this); + } - void SetStrategy(const GameStrategy &) override; GameOutcome GetOutcome() const override { throw UndefinedException(); } void SetOutcome(GameOutcome p_outcome) override { throw UndefinedException(); } - Rational GetPayoff(int pl) const override; + Rational GetPayoff(const GamePlayer &) const override; Rational GetStrategyValue(const GameStrategy &) const override; }; @@ -47,28 +49,23 @@ class AGGPureStrategyProfileRep : public PureStrategyProfileRep { // AGGPureStrategyProfileRep: Data access and manipulation //------------------------------------------------------------------------ -void AGGPureStrategyProfileRep::SetStrategy(const GameStrategy &s) -{ - m_profile[s->GetPlayer()->GetNumber()] = s; -} - -Rational AGGPureStrategyProfileRep::GetPayoff(int pl) const +Rational AGGPureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const { - std::shared_ptr aggPtr = dynamic_cast(*m_nfg).aggPtr; + const std::shared_ptr aggPtr = dynamic_cast(*m_nfg).aggPtr; std::vector s(aggPtr->getNumPlayers()); for (int i = 1; i <= aggPtr->getNumPlayers(); i++) { - s[i - 1] = m_profile[i]->GetNumber() - 1; + s[i - 1] = m_profile.at(m_nfg->GetPlayer(i))->GetNumber() - 1; } - return Rational(aggPtr->getPurePayoff(pl - 1, s)); + return Rational(aggPtr->getPurePayoff(p_player->GetNumber() - 1, s)); } Rational AGGPureStrategyProfileRep::GetStrategyValue(const GameStrategy &p_strategy) const { - int player = p_strategy->GetPlayer()->GetNumber(); - std::shared_ptr aggPtr = dynamic_cast(*m_nfg).aggPtr; + const int player = p_strategy->GetPlayer()->GetNumber(); + const std::shared_ptr aggPtr = dynamic_cast(*m_nfg).aggPtr; std::vector s(aggPtr->getNumPlayers()); for (int i = 1; i <= aggPtr->getNumPlayers(); i++) { - s[i - 1] = m_profile[i]->GetNumber() - 1; + s[i - 1] = m_profile.at(m_nfg->GetPlayer(i))->GetNumber() - 1; } s[player - 1] = p_strategy->GetNumber() - 1; return Rational(aggPtr->getPurePayoff(player - 1, s)); @@ -102,8 +99,9 @@ template T AGGMixedStrategyProfileRep::GetPayoff(int pl) const std::vector s(g.aggPtr->getNumActions()); for (int i = 0; i < g.aggPtr->getNumPlayers(); ++i) { for (int j = 0; j < g.aggPtr->getNumActions(i); ++j) { - GameStrategy strategy = this->m_support.GetGame()->GetPlayer(i + 1)->GetStrategy(j + 1); - int ind = this->m_profileIndex.at(strategy); + const GameStrategy strategy = + this->m_support.GetGame()->GetPlayer(i + 1)->GetStrategy(j + 1); + const int ind = this->m_profileIndex.at(strategy); s[g.aggPtr->firstAction(i) + j] = (ind == -1) ? (T)0 : this->m_probs[ind]; } } @@ -124,7 +122,8 @@ T AGGMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &ps) } else { for (int j = 0; j < g.aggPtr->getNumActions(i); ++j) { - GameStrategy strategy = this->m_support.GetGame()->GetPlayer(i + 1)->GetStrategy(j + 1); + const GameStrategy strategy = + this->m_support.GetGame()->GetPlayer(i + 1)->GetStrategy(j + 1); const int &ind = this->m_profileIndex.at(strategy); s[g.aggPtr->firstAction(i) + j] = (ind == -1) ? (T)0 : this->m_probs[ind]; } @@ -160,8 +159,9 @@ T AGGMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &ps1, } else { for (int j = 0; j < g.aggPtr->getNumActions(i); ++j) { - GameStrategy strategy = this->m_support.GetGame()->GetPlayer(i + 1)->GetStrategy(j + 1); - int ind = this->m_profileIndex.at(strategy); + const GameStrategy strategy = + this->m_support.GetGame()->GetPlayer(i + 1)->GetStrategy(j + 1); + const int ind = this->m_profileIndex.at(strategy); s[g.aggPtr->firstAction(i) + j] = (ind == -1) ? (T)0 : this->m_probs[ind]; } } @@ -180,10 +180,9 @@ GameAGGRep::GameAGGRep(std::shared_ptr p_aggPtr) : aggPtr(p_aggPtr) { for (int pl = 1; pl <= aggPtr->getNumPlayers(); pl++) { m_players.push_back(new GamePlayerRep(this, pl, aggPtr->getNumActions(pl - 1))); - m_players[pl]->m_label = lexical_cast(pl); - for (int st = 1; st <= m_players[pl]->NumStrategies(); st++) { - m_players[pl]->m_strategies[st]->SetLabel(lexical_cast(st)); - } + m_players.back()->m_label = lexical_cast(pl); + std::for_each(m_players.back()->m_strategies.begin(), m_players.back()->m_strategies.end(), + [st = 1](GameStrategyRep *s) mutable { s->SetLabel(std::to_string(st++)); }); } } @@ -202,20 +201,20 @@ Game GameAGGRep::Copy() const Array GameAGGRep::NumStrategies() const { Array ns; - for (int pl = 1; pl <= aggPtr->getNumPlayers(); pl++) { - ns.push_back(m_players[pl]->NumStrategies()); + for (const auto &player : m_players) { + ns.push_back(player->GetStrategies().size()); } return ns; } GameStrategy GameAGGRep::GetStrategy(int p_index) const { - for (int pl = 1; pl <= aggPtr->getNumPlayers(); pl++) { - if (m_players[pl]->NumStrategies() >= p_index) { - return m_players[pl]->GetStrategy(p_index); + for (const auto &player : m_players) { + if (static_cast(player->GetStrategies().size()) >= p_index) { + return player->GetStrategy(p_index); } else { - p_index -= m_players[pl]->NumStrategies(); + p_index -= player->GetStrategies().size(); } } throw IndexException(); @@ -227,7 +226,8 @@ GameStrategy GameAGGRep::GetStrategy(int p_index) const PureStrategyProfile GameAGGRep::NewPureStrategyProfile() const { - return PureStrategyProfile(new AGGPureStrategyProfileRep(const_cast(this))); + return PureStrategyProfile( + std::make_shared(const_cast(this))); } MixedStrategyProfile GameAGGRep::NewMixedStrategyProfile(double) const @@ -261,14 +261,14 @@ bool GameAGGRep::IsConstSum() const { auto profile = NewPureStrategyProfile(); Rational sum(0); - for (int pl = 1; pl <= m_players.size(); pl++) { - sum += profile->GetPayoff(pl); + for (const auto &player : m_players) { + sum += profile->GetPayoff(player); } for (auto iter : StrategyContingencies(Game(this))) { Rational newsum(0); - for (int pl = 1; pl <= m_players.size(); pl++) { - newsum += iter->GetPayoff(pl); + for (const auto &player : m_players) { + newsum += iter->GetPayoff(player); } if (newsum != sum) { return false; diff --git a/src/games/gameagg.h b/src/games/gameagg.h index 98fc1409b..c7e14c74e 100644 --- a/src/games/gameagg.h +++ b/src/games/gameagg.h @@ -33,20 +33,12 @@ class GameAGGRep : public GameRep { private: std::shared_ptr aggPtr; - Array m_players; public: /// @name Lifecycle //@{ /// Constructor explicit GameAGGRep(std::shared_ptr); - /// Destructor - ~GameAGGRep() override - { - for (auto player : m_players) { - player->Invalidate(); - } - } /// Create a copy of the game, as a new game Game Copy() const override; //@} @@ -60,9 +52,6 @@ class GameAGGRep : public GameRep { GameStrategy GetStrategy(int p_index) const override; /// Returns the total number of actions in the game int BehavProfileLength() const override { throw UndefinedException(); } - /// Returns the total number of strategies in the game - int MixedProfileLength() const override { return aggPtr->getNumActions(); } - int NumStrategyContingencies() const override { throw UndefinedException(); } //@} PureStrategyProfile NewPureStrategyProfile() const override; @@ -75,59 +64,36 @@ class GameAGGRep : public GameRep { /// @name Players //@{ - /// Returns the number of players in the game - int NumPlayers() const override { return aggPtr->getNumPlayers(); } - /// Returns the pl'th player in the game - GamePlayer GetPlayer(int pl) const override { return m_players[pl]; } /// Returns the chance (nature) player GamePlayer GetChance() const override { throw UndefinedException(); } /// Creates a new player in the game, with no moves GamePlayer NewPlayer() override { throw UndefinedException(); } //@} - /// @name Information sets - //@{ - /// Returns the iset'th information set in the game (numbered globally) - GameInfoset GetInfoset(int iset) const override { throw UndefinedException(); } - /// Returns the set of information sets in the game - Array GetInfosets() const override { throw UndefinedException(); } - /// Returns an array with the number of information sets per personal player - Array NumInfosets() const override { throw UndefinedException(); } - /// Returns the act'th action in the game (numbered globally) - GameAction GetAction(int act) const override { throw UndefinedException(); } - //@} - - /// @name Outcomes - //@{ - /// Returns the number of outcomes defined in the game - int NumOutcomes() const override { throw UndefinedException(); } - /// Returns the index'th outcome defined in the game - GameOutcome GetOutcome(int index) const override { throw UndefinedException(); } - /// Creates a new outcome in the game - GameOutcome NewOutcome() override { throw UndefinedException(); } - /// Deletes the specified outcome from the game - void DeleteOutcome(const GameOutcome &) override { throw UndefinedException(); } - //@} - /// @name Nodes //@{ /// Returns the root node of the game GameNode GetRoot() const override { throw UndefinedException(); } /// Returns the number of nodes in the game - int NumNodes() const override { throw UndefinedException(); } + size_t NumNodes() const override { throw UndefinedException(); } + /// Returns the number of non-terminal nodes in the game + size_t NumNonterminalNodes() const override { throw UndefinedException(); } //@} /// @name General data access //@{ bool IsTree() const override { return false; } bool IsAgg() const override { return true; } - bool IsPerfectRecall(GameInfoset &, GameInfoset &) const override { return true; } + bool IsPerfectRecall() const override { return true; } bool IsConstSum() const override; - /// Returns the smallest payoff in any outcome of the game - Rational GetMinPayoff(int) const override { return Rational(aggPtr->getMinPayoff()); } - /// Returns the largest payoff in any outcome of the game - Rational GetMaxPayoff(int) const override { return Rational(aggPtr->getMaxPayoff()); } - + /// Returns the smallest payoff to any player in any outcome of the game + Rational GetMinPayoff() const override { return Rational(aggPtr->getMinPayoff()); } + /// Returns the smallest payoff to the player in any outcome of the game + Rational GetMinPayoff(const GamePlayer &) const override { throw UndefinedException(); } + /// Returns the largest payoff to any player in any outcome of the game + Rational GetMaxPayoff() const override { return Rational(aggPtr->getMaxPayoff()); } + /// Returns the largest payoff to the player in any outcome of the game + Rational GetMaxPayoff(const GamePlayer &) const override { throw UndefinedException(); } //@} /// @name Modification @@ -151,10 +117,10 @@ class GameAGGRep : public GameRep { /// @return A handle to the game representation constructed /// @throw InvalidFileException If the stream does not contain a valid serialisation /// of a game in .agg format. -inline Game ReadAggFile(std::istream &in) +inline Game ReadAggFile(std::istream &p_stream) { try { - return new GameAGGRep(agg::AGG::makeAGG(in)); + return new GameAGGRep(agg::AGG::makeAGG(p_stream)); } catch (std::runtime_error &ex) { throw InvalidFileException(ex.what()); diff --git a/src/games/gamebagg.cc b/src/games/gamebagg.cc index 0df2d5bf5..ae9365892 100644 --- a/src/games/gamebagg.cc +++ b/src/games/gamebagg.cc @@ -34,11 +34,13 @@ namespace Gambit { class BAGGPureStrategyProfileRep : public PureStrategyProfileRep { public: explicit BAGGPureStrategyProfileRep(const Game &p_game) : PureStrategyProfileRep(p_game) {} - PureStrategyProfileRep *Copy() const override { return new BAGGPureStrategyProfileRep(*this); } - void SetStrategy(const GameStrategy &) override; + std::shared_ptr Copy() const override + { + return std::make_shared(*this); + } GameOutcome GetOutcome() const override { throw UndefinedException(); } void SetOutcome(GameOutcome p_outcome) override { throw UndefinedException(); } - Rational GetPayoff(int pl) const override; + Rational GetPayoff(const GamePlayer &) const override; Rational GetStrategyValue(const GameStrategy &) const override; }; @@ -46,34 +48,29 @@ class BAGGPureStrategyProfileRep : public PureStrategyProfileRep { // BAGGPureStrategyProfileRep: Data access and manipulation //------------------------------------------------------------------------ -void BAGGPureStrategyProfileRep::SetStrategy(const GameStrategy &s) -{ - m_profile[s->GetPlayer()->GetNumber()] = s; -} - -Rational BAGGPureStrategyProfileRep::GetPayoff(int pl) const +Rational BAGGPureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const { - std::shared_ptr baggPtr = dynamic_cast(*m_nfg).baggPtr; + const std::shared_ptr baggPtr = dynamic_cast(*m_nfg).baggPtr; std::vector s(m_nfg->NumPlayers()); - for (int i = 1; i <= m_nfg->NumPlayers(); i++) { - s[i - 1] = m_profile[i]->GetNumber() - 1; + for (size_t i = 1; i <= m_nfg->NumPlayers(); i++) { + s[i - 1] = m_profile.at(m_nfg->GetPlayer(i))->GetNumber() - 1; } - int bp = dynamic_cast(*m_nfg).agent2baggPlayer[pl]; - int tp = pl - 1 - baggPtr->typeOffset[bp - 1]; + const int bp = dynamic_cast(*m_nfg).agent2baggPlayer[p_player->GetNumber()]; + const int tp = p_player->GetNumber() - 1 - baggPtr->typeOffset[bp - 1]; return Rational(baggPtr->getPurePayoff(bp - 1, tp, s)); } Rational BAGGPureStrategyProfileRep::GetStrategyValue(const GameStrategy &p_strategy) const { - int player = p_strategy->GetPlayer()->GetNumber(); - std::shared_ptr baggPtr = dynamic_cast(*m_nfg).baggPtr; + const int player = p_strategy->GetPlayer()->GetNumber(); + const std::shared_ptr baggPtr = dynamic_cast(*m_nfg).baggPtr; std::vector s(m_nfg->NumPlayers()); - for (int i = 1; i <= m_nfg->NumPlayers(); i++) { - s[i - 1] = m_profile[i]->GetNumber() - 1; + for (size_t i = 1; i <= m_nfg->NumPlayers(); i++) { + s[i - 1] = m_profile.at(m_nfg->GetPlayer(i))->GetNumber() - 1; } s[player - 1] = p_strategy->GetNumber() - 1; - int bp = dynamic_cast(*m_nfg).agent2baggPlayer[player]; - int tp = player - 1 - baggPtr->typeOffset[bp - 1]; + const int bp = dynamic_cast(*m_nfg).agent2baggPlayer[player]; + const int tp = player - 1 - baggPtr->typeOffset[bp - 1]; return Rational(baggPtr->getPurePayoff(bp - 1, tp, s)); } @@ -112,10 +109,10 @@ template T BAGGMixedStrategyProfileRep::GetPayoff(int pl) const btype = tp; } for (int j = 0; j < ns[g.baggPtr->typeOffset[i] + tp + 1]; ++j, ++offs) { - GameStrategy strategy = this->m_support.GetGame() - ->GetPlayer(g.baggPtr->typeOffset[i] + tp + 1) - ->GetStrategy(j + 1); - int ind = this->m_profileIndex.at(strategy); + const GameStrategy strategy = this->m_support.GetGame() + ->GetPlayer(g.baggPtr->typeOffset[i] + tp + 1) + ->GetStrategy(j + 1); + const int ind = this->m_profileIndex.at(strategy); s.at(offs) = (ind == -1) ? (T)0 : this->m_probs[ind]; } } @@ -143,10 +140,10 @@ T BAGGMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &ps) } else { for (int j = 0; j < g.baggPtr->getNumActions(i, tp); ++j) { - GameStrategy strategy = this->m_support.GetGame() - ->GetPlayer(g.baggPtr->typeOffset[i] + tp + 1) - ->GetStrategy(j + 1); - int ind = this->m_profileIndex.at(strategy); + const GameStrategy strategy = this->m_support.GetGame() + ->GetPlayer(g.baggPtr->typeOffset[i] + tp + 1) + ->GetStrategy(j + 1); + const int ind = this->m_profileIndex.at(strategy); s.at(g.baggPtr->firstAction(i, tp) + j) = (ind == -1) ? Rational(0) : this->m_probs[ind]; } } @@ -189,10 +186,10 @@ T BAGGMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &ps1 } else { for (unsigned int j = 0; j < g.baggPtr->typeActionSets.at(i).at(tp).size(); ++j) { - GameStrategy strategy = this->m_support.GetGame() - ->GetPlayer(g.baggPtr->typeOffset[i] + tp + 1) - ->GetStrategy(j + 1); - int ind = this->m_profileIndex.at(strategy); + const GameStrategy strategy = this->m_support.GetGame() + ->GetPlayer(g.baggPtr->typeOffset[i] + tp + 1) + ->GetStrategy(j + 1); + const int ind = this->m_profileIndex.at(strategy); s.at(g.baggPtr->firstAction(i, tp) + j) = static_cast((ind == -1) ? T(0) : this->m_probs[ind]); } @@ -216,11 +213,10 @@ GameBAGGRep::GameBAGGRep(std::shared_ptr _baggPtr) for (int pl = 1; pl <= baggPtr->getNumPlayers(); pl++) { for (int j = 0; j < baggPtr->getNumTypes(pl - 1); j++, k++) { m_players.push_back(new GamePlayerRep(this, k, baggPtr->getNumActions(pl - 1, j))); - m_players[k]->m_label = lexical_cast(k); + m_players.back()->m_label = std::to_string(k); agent2baggPlayer[k] = pl; - for (int st = 1; st <= m_players[k]->NumStrategies(); st++) { - m_players[k]->m_strategies[st]->SetLabel(lexical_cast(st)); - } + std::for_each(m_players.back()->m_strategies.begin(), m_players.back()->m_strategies.end(), + [st = 1](GameStrategyRep *s) mutable { s->SetLabel(std::to_string(st++)); }); } } } @@ -240,24 +236,16 @@ Game GameBAGGRep::Copy() const Array GameBAGGRep::NumStrategies() const { Array ns; - for (int pl = 1; pl <= NumPlayers(); pl++) { - ns.push_back(m_players[pl]->m_strategies.size()); + for (const auto &player : m_players) { + ns.push_back(player->m_strategies.size()); } return ns; } -int GameBAGGRep::MixedProfileLength() const -{ - int res = 0; - for (int pl = 1; pl <= NumPlayers(); pl++) { - res += m_players[pl]->m_strategies.size(); - } - return res; -} - PureStrategyProfile GameBAGGRep::NewPureStrategyProfile() const { - return PureStrategyProfile(new BAGGPureStrategyProfileRep(const_cast(this))); + return PureStrategyProfile( + std::make_shared(const_cast(this))); } MixedStrategyProfile GameBAGGRep::NewMixedStrategyProfile(double) const diff --git a/src/games/gamebagg.h b/src/games/gamebagg.h index b41cb7a6f..04c2c5645 100644 --- a/src/games/gamebagg.h +++ b/src/games/gamebagg.h @@ -34,7 +34,6 @@ class GameBAGGRep : public GameRep { private: std::shared_ptr baggPtr; Array agent2baggPlayer; - Array m_players; public: /// @name Lifecycle @@ -58,12 +57,8 @@ class GameBAGGRep : public GameRep { Array NumStrategies() const override; /// Gets the i'th strategy in the game, numbered globally GameStrategy GetStrategy(int p_index) const override { throw UndefinedException(); } - /// Returns the number of strategy contingencies in the game - int NumStrategyContingencies() const override { throw UndefinedException(); } /// Returns the total number of actions in the game int BehavProfileLength() const override { throw UndefinedException(); } - /// Returns the total number of strategies in the game - int MixedProfileLength() const override; //@} PureStrategyProfile NewPureStrategyProfile() const override; @@ -76,58 +71,36 @@ class GameBAGGRep : public GameRep { /// @name Players //@{ - /// Returns the number of players in the game - int NumPlayers() const override { return m_players.size(); } - /// Returns the pl'th player in the game - GamePlayer GetPlayer(int pl) const override { return m_players[pl]; } /// Returns the chance (nature) player GamePlayer GetChance() const override { throw UndefinedException(); } /// Creates a new player in the game, with no moves GamePlayer NewPlayer() override { throw UndefinedException(); } //@} - /// @name Information sets - //@{ - /// Returns the iset'th information set in the game (numbered globally) - GameInfoset GetInfoset(int iset) const override { throw UndefinedException(); } - /// Returns the set of information sets in the game - Array GetInfosets() const override { throw UndefinedException(); } - /// Returns an array with the number of information sets per personal player - Array NumInfosets() const override { throw UndefinedException(); } - /// Returns the act'th action in the game (numbered globally) - GameAction GetAction(int act) const override { throw UndefinedException(); } - //@} - - /// @name Outcomes - //@{ - /// Returns the number of outcomes defined in the game - int NumOutcomes() const override { throw UndefinedException(); } - /// Returns the index'th outcome defined in the game - GameOutcome GetOutcome(int index) const override { throw UndefinedException(); } - /// Creates a new outcome in the game - GameOutcome NewOutcome() override { throw UndefinedException(); } - /// Deletes the specified outcome from the game - void DeleteOutcome(const GameOutcome &) override { throw UndefinedException(); } - //@} - /// @name Nodes //@{ /// Returns the root node of the game GameNode GetRoot() const override { throw UndefinedException(); } /// Returns the number of nodes in the game - int NumNodes() const override { throw UndefinedException(); } + size_t NumNodes() const override { throw UndefinedException(); } + /// Returns the number of non-terminal nodes in the game + size_t NumNonterminalNodes() const override { throw UndefinedException(); } //@} /// @name General data access //@{ bool IsTree() const override { return false; } virtual bool IsBagg() const { return true; } - bool IsPerfectRecall(GameInfoset &, GameInfoset &) const override { return true; } + bool IsPerfectRecall() const override { return true; } bool IsConstSum() const override { throw UndefinedException(); } - /// Returns the smallest payoff in any outcome of the game - Rational GetMinPayoff(int) const override { return Rational(baggPtr->getMinPayoff()); } - /// Returns the largest payoff in any outcome of the game - Rational GetMaxPayoff(int) const override { return Rational(baggPtr->getMaxPayoff()); } + /// Returns the smallest payoff to any player in any outcome of the game + Rational GetMinPayoff() const override { return Rational(baggPtr->getMinPayoff()); } + /// Returns the smallest payoff to the player in any outcome of the game + Rational GetMinPayoff(const GamePlayer &) const override { throw UndefinedException(); } + /// Returns the largest payoff to any player in any outcome of the game + Rational GetMaxPayoff() const override { return Rational(baggPtr->getMaxPayoff()); } + /// Returns the largest payoff to the player in any outcome of the game + Rational GetMaxPayoff(const GamePlayer &) const override { throw UndefinedException(); } //@} /// @name Writing data files diff --git a/src/games/gameexpl.cc b/src/games/gameexpl.cc index f8e70a210..6e41afa97 100644 --- a/src/games/gameexpl.cc +++ b/src/games/gameexpl.cc @@ -33,71 +33,45 @@ namespace Gambit { //======================================================================== //------------------------------------------------------------------------ -// GameExplicitRep: Lifecycle +// GameExplicitRep: General data access //------------------------------------------------------------------------ -GameExplicitRep::~GameExplicitRep() +Rational GameExplicitRep::GetMinPayoff() const { - for (auto player : m_players) { - player->Invalidate(); - } - for (auto outcome : m_outcomes) { - outcome->Invalidate(); - } + return std::accumulate( + std::next(m_players.begin()), m_players.end(), GetMinPayoff(m_players.front()), + [this](const Rational &r, const GamePlayer &p) { return std::min(r, GetMinPayoff(p)); }); } -//------------------------------------------------------------------------ -// GameExplicitRep: General data access -//------------------------------------------------------------------------ - -Rational GameExplicitRep::GetMinPayoff(int player) const +Rational GameExplicitRep::GetMinPayoff(const GamePlayer &p_player) const { - int p1, p2; - if (m_outcomes.empty()) { return Rational(0); } - - if (player) { - p1 = p2 = player; - } - else { - p1 = 1; - p2 = NumPlayers(); - } - - Rational minpay = static_cast(m_outcomes.front()->GetPayoff(p1)); - for (auto outcome : m_outcomes) { - for (int p = p1; p <= p2; p++) { - minpay = std::min(minpay, static_cast(outcome->GetPayoff(p))); - } - } - return minpay; + return std::accumulate(std::next(m_outcomes.begin()), m_outcomes.end(), + m_outcomes.front()->GetPayoff(p_player), + [&p_player](const Rational &r, const GameOutcomeRep *c) { + return std::min(r, c->GetPayoff(p_player)); + }); } -Rational GameExplicitRep::GetMaxPayoff(int player) const +Rational GameExplicitRep::GetMaxPayoff() const { - int p1, p2; + return std::accumulate( + std::next(m_players.begin()), m_players.end(), GetMaxPayoff(m_players.front()), + [this](const Rational &r, const GamePlayer &p) { return std::max(r, GetMaxPayoff(p)); }); +} +Rational GameExplicitRep::GetMaxPayoff(const GamePlayer &p_player) const +{ if (m_outcomes.empty()) { return Rational(0); } - - if (player) { - p1 = p2 = player; - } - else { - p1 = 1; - p2 = NumPlayers(); - } - - Rational maxpay = static_cast(m_outcomes.front()->GetPayoff(p1)); - for (auto outcome : m_outcomes) { - for (int p = p1; p <= p2; p++) { - maxpay = std::max(maxpay, static_cast(outcome->GetPayoff(p))); - } - } - return maxpay; + return std::accumulate(std::next(m_outcomes.begin()), m_outcomes.end(), + m_outcomes.front()->GetPayoff(p_player), + [&p_player](const Rational &r, const GameOutcomeRep *c) { + return std::max(r, c->GetPayoff(p_player)); + }); } //------------------------------------------------------------------------ @@ -106,42 +80,28 @@ Rational GameExplicitRep::GetMaxPayoff(int player) const Array GameExplicitRep::NumStrategies() const { - const_cast(this)->BuildComputedValues(); - Array dim(m_players.size()); - for (size_t pl = 1; pl <= m_players.size(); pl++) { - dim[pl] = m_players[pl]->m_strategies.size(); + BuildComputedValues(); + Array dim; + for (const auto &player : m_players) { + dim.push_back(player->m_strategies.size()); } return dim; } GameStrategy GameExplicitRep::GetStrategy(int p_index) const { - const_cast(this)->BuildComputedValues(); - for (int pl = 1, i = 1; pl <= m_players.size(); pl++) { - for (int st = 1; st <= m_players[pl]->m_strategies.size(); st++, i++) { - if (p_index == i) { - return m_players[pl]->m_strategies[st]; + BuildComputedValues(); + int i = 1; + for (const auto &player : m_players) { + for (const auto &strategy : player->m_strategies) { + if (p_index == i++) { + return strategy; } } } throw IndexException(); } -int GameExplicitRep::NumStrategyContingencies() const -{ - const_cast(this)->BuildComputedValues(); - return std::accumulate(m_players.begin(), m_players.end(), 1, [](int ncont, GamePlayerRep *p) { - return ncont * p->m_strategies.size(); - }); -} - -int GameExplicitRep::MixedProfileLength() const -{ - const_cast(this)->BuildComputedValues(); - return std::accumulate(m_players.begin(), m_players.end(), 0, - [](int size, GamePlayerRep *p) { return size + p->m_strategies.size(); }); -} - //------------------------------------------------------------------------ // GameExplicitRep: Outcomes //------------------------------------------------------------------------ diff --git a/src/games/gameexpl.h b/src/games/gameexpl.h index 97a2a18b7..fc77947ae 100644 --- a/src/games/gameexpl.h +++ b/src/games/gameexpl.h @@ -30,23 +30,17 @@ namespace Gambit { class GameExplicitRep : public GameRep { template friend class MixedStrategyProfile; -protected: - Array m_players; - Array m_outcomes; - public: - /// @name Lifecycle - //@{ - /// Destructor - ~GameExplicitRep() override; - //@} - /// @name General data access //@{ - /// Returns the smallest payoff in any outcome of the game - Rational GetMinPayoff(int pl = 0) const override; - /// Returns the largest payoff in any outcome of the game - Rational GetMaxPayoff(int pl = 0) const override; + /// Returns the smallest payoff to any player in any outcome of the game + Rational GetMinPayoff() const override; + /// Returns the smallest payoff to the player in any outcome of the game + Rational GetMinPayoff(const GamePlayer &) const override; + /// Returns the largest payoff to any player in any outcome of the game + Rational GetMaxPayoff() const override; + /// Returns the largest payoff to the player in any outcome of the game + Rational GetMaxPayoff(const GamePlayer &) const override; //@} /// @name Dimensions of the game @@ -55,26 +49,10 @@ class GameExplicitRep : public GameRep { Array NumStrategies() const override; /// Gets the i'th strategy in the game, numbered globally GameStrategy GetStrategy(int p_index) const override; - /// Returns the number of strategy contingencies in the game - int NumStrategyContingencies() const override; - /// Returns the total number of strategies in the game - int MixedProfileLength() const override; - //@} - - /// @name Players - //@{ - /// Returns the number of players in the game - int NumPlayers() const override { return m_players.size(); } - /// Returns the pl'th player in the game - GamePlayer GetPlayer(int pl) const override { return m_players[pl]; } //@} /// @name Outcomes //@{ - /// Returns the number of outcomes defined in the game - int NumOutcomes() const override { return m_outcomes.size(); } - /// Returns the index'th outcome defined in the game - GameOutcome GetOutcome(int index) const override { return m_outcomes[index]; } /// Creates a new outcome in the game GameOutcome NewOutcome() override; diff --git a/src/games/gameobject.h b/src/games/gameobject.h index 4d8996fad..36754789c 100644 --- a/src/games/gameobject.h +++ b/src/games/gameobject.h @@ -36,14 +36,14 @@ namespace Gambit { /// operations on it. class GameObject { protected: - int m_refCount; - bool m_valid; + int m_refCount{0}; + bool m_valid{true}; public: /// @name Lifecycle //@{ /// Constructor; initializes reference count - GameObject() : m_refCount(0), m_valid(true) {} + GameObject() = default; /// Destructor virtual ~GameObject() = default; @@ -86,13 +86,13 @@ class GameObject { class BaseGameRep { protected: - int m_refCount; + int m_refCount{0}; public: /// @name Lifecycle //@{ - /// Constructor; initializes reference count - BaseGameRep() : m_refCount(0) {} + /// Constructor + BaseGameRep() = default; /// Destructor virtual ~BaseGameRep() = default; diff --git a/src/games/gametable.cc b/src/games/gametable.cc index 573e12b6a..5ae53b04a 100644 --- a/src/games/gametable.cc +++ b/src/games/gametable.cc @@ -34,17 +34,16 @@ namespace Gambit { class TablePureStrategyProfileRep : public PureStrategyProfileRep { protected: - long m_index{1L}; + long m_index{0L}; - PureStrategyProfileRep *Copy() const override; + std::shared_ptr Copy() const override; public: explicit TablePureStrategyProfileRep(const Game &p_game); - long GetIndex() const override { return m_index; } void SetStrategy(const GameStrategy &) override; GameOutcome GetOutcome() const override; void SetOutcome(GameOutcome p_outcome) override; - Rational GetPayoff(int pl) const override; + Rational GetPayoff(const GamePlayer &) const override; Rational GetStrategyValue(const GameStrategy &) const override; }; @@ -55,17 +54,17 @@ class TablePureStrategyProfileRep : public PureStrategyProfileRep { TablePureStrategyProfileRep::TablePureStrategyProfileRep(const Game &p_nfg) : PureStrategyProfileRep(p_nfg) { - for (int pl = 1; pl <= m_nfg->NumPlayers(); pl++) { - m_index += m_profile[pl]->m_offset; + for (auto [player, strategy] : m_profile) { + m_index += strategy->m_offset; } } -PureStrategyProfileRep *TablePureStrategyProfileRep::Copy() const +std::shared_ptr TablePureStrategyProfileRep::Copy() const { - return new TablePureStrategyProfileRep(*this); + return std::make_shared(*this); } -Game NewTable(const Array &p_dim, bool p_sparseOutcomes /*= false*/) +Game NewTable(const std::vector &p_dim, bool p_sparseOutcomes /*= false*/) { return new GameTableRep(p_dim, p_sparseOutcomes); } @@ -76,8 +75,8 @@ Game NewTable(const Array &p_dim, bool p_sparseOutcomes /*= false*/) void TablePureStrategyProfileRep::SetStrategy(const GameStrategy &s) { - m_index += s->m_offset - m_profile[s->GetPlayer()->GetNumber()]->m_offset; - m_profile[s->GetPlayer()->GetNumber()] = s; + m_index += s->m_offset - m_profile.at(s->GetPlayer())->m_offset; + m_profile[s->GetPlayer()] = s; } GameOutcome TablePureStrategyProfileRep::GetOutcome() const @@ -90,11 +89,11 @@ void TablePureStrategyProfileRep::SetOutcome(GameOutcome p_outcome) dynamic_cast(*m_nfg).m_results[m_index] = p_outcome; } -Rational TablePureStrategyProfileRep::GetPayoff(int pl) const +Rational TablePureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const { GameOutcomeRep *outcome = dynamic_cast(*m_nfg).m_results[m_index]; if (outcome) { - return static_cast(outcome->GetPayoff(pl)); + return outcome->GetPayoff(p_player); } else { return Rational(0); @@ -103,12 +102,12 @@ Rational TablePureStrategyProfileRep::GetPayoff(int pl) const Rational TablePureStrategyProfileRep::GetStrategyValue(const GameStrategy &p_strategy) const { - int player = p_strategy->GetPlayer()->GetNumber(); + const auto &player = p_strategy->GetPlayer(); GameOutcomeRep *outcome = dynamic_cast(*m_nfg) - .m_results[m_index - m_profile[player]->m_offset + p_strategy->m_offset]; + .m_results[m_index - m_profile.at(player)->m_offset + p_strategy->m_offset]; if (outcome) { - return static_cast(outcome->GetPayoff(player)); + return outcome->GetPayoff(player); } else { return Rational(0); @@ -117,7 +116,8 @@ Rational TablePureStrategyProfileRep::GetStrategyValue(const GameStrategy &p_str PureStrategyProfile GameTableRep::NewPureStrategyProfile() const { - return PureStrategyProfile(new TablePureStrategyProfileRep(const_cast(this))); + return PureStrategyProfile( + std::make_shared(const_cast(this))); } //======================================================================== @@ -158,12 +158,12 @@ template MixedStrategyProfileRep *TableMixedStrategyProfileRep:: template T TableMixedStrategyProfileRep::GetPayoff(int pl, int index, int current) const { - if (current > this->m_support.GetGame()->NumPlayers()) { - Game game = this->m_support.GetGame(); + if (current > static_cast(this->m_support.GetGame()->NumPlayers())) { + const Game game = this->m_support.GetGame(); auto &g = dynamic_cast(*game); GameOutcomeRep *outcome = g.m_results[index]; if (outcome) { - return static_cast(outcome->GetPayoff(pl)); + return outcome->GetPayoff(this->m_support.GetGame()->GetPlayer(pl)); } else { return T(0); @@ -181,7 +181,7 @@ T TableMixedStrategyProfileRep::GetPayoff(int pl, int index, int current) con template T TableMixedStrategyProfileRep::GetPayoff(int pl) const { - return GetPayoff(pl, 1, 1); + return GetPayoff(pl, 0, 1); } template @@ -191,12 +191,12 @@ void TableMixedStrategyProfileRep::GetPayoffDeriv(int pl, int const_pl, int c if (cur_pl == const_pl) { cur_pl++; } - if (cur_pl > this->m_support.GetGame()->NumPlayers()) { - Game game = this->m_support.GetGame(); + if (cur_pl > static_cast(this->m_support.GetGame()->NumPlayers())) { + const Game game = this->m_support.GetGame(); auto &g = dynamic_cast(*game); GameOutcomeRep *outcome = g.m_results[index]; if (outcome) { - value += prob * static_cast(outcome->GetPayoff(pl)); + value += prob * outcome->GetPayoff(this->m_support.GetGame()->GetPlayer(pl)); } } else { @@ -212,7 +212,7 @@ template T TableMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &strategy) const { T value = T(0); - GetPayoffDeriv(pl, strategy->GetPlayer()->GetNumber(), 1, strategy->m_offset + 1, T(1), value); + GetPayoffDeriv(pl, strategy->GetPlayer()->GetNumber(), 1, strategy->m_offset, T(1), value); return value; } @@ -224,12 +224,12 @@ void TableMixedStrategyProfileRep::GetPayoffDeriv(int pl, int const_pl1, int while (cur_pl == const_pl1 || cur_pl == const_pl2) { cur_pl++; } - if (cur_pl > this->m_support.GetGame()->NumPlayers()) { - Game game = this->m_support.GetGame(); + if (cur_pl > static_cast(this->m_support.GetGame()->NumPlayers())) { + const Game game = this->m_support.GetGame(); auto &g = dynamic_cast(*game); GameOutcomeRep *outcome = g.m_results[index]; if (outcome) { - value += prob * static_cast(outcome->GetPayoff(pl)); + value += prob * outcome->GetPayoff(this->m_support.GetGame()->GetPlayer(pl)); } } else { @@ -254,7 +254,7 @@ T TableMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &st T value = T(0); GetPayoffDeriv(pl, player1->GetNumber(), player2->GetNumber(), 1, - strategy1->m_offset + strategy2->m_offset + 1, T(1), value); + strategy1->m_offset + strategy2->m_offset, T(1), value); return value; } @@ -265,42 +265,26 @@ template class TableMixedStrategyProfileRep; // GameTableRep: Lifecycle //------------------------------------------------------------------------ -namespace { -/// This convenience function computes the Cartesian product of the -/// elements in dim. -int Product(const Array &dim) +GameTableRep::GameTableRep(const std::vector &dim, bool p_sparseOutcomes /* = false */) + : m_results(std::accumulate(dim.begin(), dim.end(), 1, std::multiplies<>())) { - int accum = 1; - for (auto d : dim) { - accum *= d; - } - return accum; -} - -} // end anonymous namespace - -GameTableRep::GameTableRep(const Array &dim, bool p_sparseOutcomes /* = false */) -{ - m_results = Array(Product(dim)); - for (int pl = 1; pl <= dim.size(); pl++) { - m_players.push_back(new GamePlayerRep(this, pl, dim[pl])); - m_players[pl]->m_label = lexical_cast(pl); - for (int st = 1; st <= m_players[pl]->NumStrategies(); st++) { - m_players[pl]->m_strategies[st]->SetLabel(lexical_cast(st)); - } + for (const auto &nstrat : dim) { + m_players.push_back(new GamePlayerRep(this, m_players.size() + 1, nstrat)); + m_players.back()->m_label = lexical_cast(m_players.size()); + std::for_each( + m_players.back()->m_strategies.begin(), m_players.back()->m_strategies.end(), + [st = 1](GameStrategyRep *s) mutable { s->SetLabel(lexical_cast(st++)); }); } IndexStrategies(); if (p_sparseOutcomes) { - for (int cont = 1; cont <= m_results.size(); m_results[cont++] = 0) - ; + std::fill(m_results.begin(), m_results.end(), nullptr); } else { - m_outcomes = Array(m_results.size()); - for (int i = 1; i <= m_outcomes.size(); i++) { - m_outcomes[i] = new GameOutcomeRep(this, i); - } - m_results = m_outcomes; + m_outcomes = std::vector(m_results.size()); + std::generate(m_outcomes.begin(), m_outcomes.end(), + [this, outc = 1]() mutable { return new GameOutcomeRep(this, outc++); }); + std::copy(m_outcomes.begin(), m_outcomes.end(), m_results.begin()); } } @@ -320,14 +304,14 @@ bool GameTableRep::IsConstSum() const { auto profile = NewPureStrategyProfile(); Rational sum(0); - for (int pl = 1; pl <= m_players.size(); pl++) { - sum += profile->GetPayoff(pl); + for (const auto &player : m_players) { + sum += profile->GetPayoff(player); } for (auto iter : StrategyContingencies(Game(this))) { Rational newsum(0); - for (int pl = 1; pl <= m_players.size(); pl++) { - newsum += iter->GetPayoff(pl); + for (const auto &player : m_players) { + newsum += iter->GetPayoff(player); } if (newsum != sum) { return false; @@ -370,7 +354,7 @@ void GameTableRep::WriteNfgFile(std::ostream &p_file) const p_file << "{ " + QuoteString(outcome->GetLabel()) << ' ' << FormatList( players, - [outcome](const GamePlayer &p) { return std::string(outcome->GetPayoff(p)); }, + [outcome](const GamePlayer &p) { return outcome->GetPayoff(p); }, true, false) << " }" << std::endl; } @@ -392,7 +376,7 @@ GamePlayer GameTableRep::NewPlayer() auto player = new GamePlayerRep(this, m_players.size() + 1, 1); m_players.push_back(player); for (auto outcome : m_outcomes) { - outcome->m_payoffs.push_back(Number()); + outcome->m_payoffs[player] = Number(); } return player; } @@ -404,16 +388,47 @@ GamePlayer GameTableRep::NewPlayer() void GameTableRep::DeleteOutcome(const GameOutcome &p_outcome) { IncrementVersion(); - for (int i = 1; i <= m_results.size(); i++) { - if (m_results[i] == p_outcome) { - m_results[i] = 0; - } - } - p_outcome->Invalidate(); + std::replace(m_results.begin(), m_results.end(), p_outcome, GameOutcome(nullptr)); m_outcomes.erase(std::find(m_outcomes.begin(), m_outcomes.end(), p_outcome)); - for (int outc = 1; outc <= m_outcomes.size(); outc++) { - m_outcomes[outc]->m_number = outc; + p_outcome->Invalidate(); + std::for_each(m_outcomes.begin(), m_outcomes.end(), + [outc = 1](GameOutcomeRep *c) mutable { c->m_number = outc++; }); +} + +//------------------------------------------------------------------------ +// GameTableRep: Strategies +//------------------------------------------------------------------------ + +GameStrategy GameTableRep::NewStrategy(const GamePlayer &p_player, const std::string &p_label) +{ + if (p_player->GetGame() != this) { + throw MismatchException(); } + IncrementVersion(); + p_player->m_strategies.push_back( + new GameStrategyRep(p_player, p_player->m_strategies.size(), p_label)); + RebuildTable(); + return p_player->m_strategies.back(); +} + +void GameTableRep::DeleteStrategy(const GameStrategy &p_strategy) +{ + GamePlayerRep *player = p_strategy->GetPlayer(); + if (player->m_game != this) { + throw MismatchException(); + } + if (player->GetStrategies().size() == 1) { + return; + } + + IncrementVersion(); + player->m_strategies.erase( + std::find(player->m_strategies.begin(), player->m_strategies.end(), p_strategy)); + std::for_each(player->m_strategies.begin(), player->m_strategies.end(), + [st = 1](GameStrategyRep *s) mutable { s->m_number = st++; }); + // Note that we do not reindex strategies, and so we do not need to re-build the + // table of outcomes. + p_strategy->Invalidate(); } //------------------------------------------------------------------------ @@ -453,37 +468,34 @@ GameTableRep::NewMixedStrategyProfile(const Rational &, const StrategySupportPro void GameTableRep::RebuildTable() { long size = 1L; - Array offsets(m_players.size()); - for (int pl = 1; pl <= m_players.size(); pl++) { - offsets[pl] = size; - size *= m_players[pl]->NumStrategies(); + std::vector offsets; + for (const auto &player : m_players) { + offsets.push_back(size); + size *= player->m_strategies.size(); } - Array newResults(size); - for (int i = 1; i <= newResults.size(); newResults[i++] = 0) - ; + std::vector newResults(size); + std::fill(newResults.begin(), newResults.end(), nullptr); for (auto iter : StrategyContingencies(StrategySupportProfile(const_cast(this)))) { - long newindex = 1L; - for (int pl = 1; pl <= m_players.size(); pl++) { - if (iter->GetStrategy(pl)->m_offset < 0) { + long newindex = 0L; + for (const auto &player : m_players) { + if (iter->GetStrategy(player)->m_offset < 0) { // This is a contingency involving a new strategy... skip newindex = -1L; break; } else { - newindex += (iter->GetStrategy(pl)->m_number - 1) * offsets[pl]; + newindex += (iter->GetStrategy(player)->m_number - 1) * offsets[player->m_number - 1]; } } - if (newindex >= 1) { - newResults[newindex] = m_results[iter->GetIndex()]; + if (newindex >= 0) { + newResults[newindex] = iter->GetOutcome(); } } - - m_results = newResults; - + m_results.swap(newResults); IndexStrategies(); } diff --git a/src/games/gametable.h b/src/games/gametable.h index 17ddb2077..874851d70 100644 --- a/src/games/gametable.h +++ b/src/games/gametable.h @@ -36,7 +36,7 @@ class GameTableRep : public GameExplicitRep { template friend class TableMixedStrategyProfileRep; private: - Array m_results; + std::vector m_results; /// @name Private auxiliary functions //@{ @@ -49,7 +49,7 @@ class GameTableRep : public GameExplicitRep { //@{ /// Construct a new table game with the given dimension /// If p_sparseOutcomes = true, outcomes for all contingencies are left null - explicit GameTableRep(const Array &p_dim, bool p_sparseOutcomes = false); + explicit GameTableRep(const std::vector &p_dim, bool p_sparseOutcomes = false); Game Copy() const override; //@} @@ -57,7 +57,7 @@ class GameTableRep : public GameExplicitRep { //@{ bool IsTree() const override { return false; } bool IsConstSum() const override; - bool IsPerfectRecall(GameInfoset &, GameInfoset &) const override { return true; } + bool IsPerfectRecall() const override { return true; } //@} /// @name Dimensions of the game @@ -74,24 +74,14 @@ class GameTableRep : public GameExplicitRep { GamePlayer NewPlayer() override; //@} - /// @name Information sets - //@{ - /// Returns the iset'th information set in the game (numbered globally) - GameInfoset GetInfoset(int iset) const override { throw UndefinedException(); } - /// Returns the set of information sets in the game - Array GetInfosets() const override { throw UndefinedException(); } - /// Returns an array with the number of information sets per personal player - Array NumInfosets() const override { throw UndefinedException(); } - /// Returns the act'th action in the game (numbered globally) - GameAction GetAction(int act) const override { throw UndefinedException(); } - //@} - /// @name Nodes //@{ /// Returns the root node of the game GameNode GetRoot() const override { throw UndefinedException(); } /// Returns the number of nodes in the game - int NumNodes() const override { throw UndefinedException(); } + size_t NumNodes() const override { throw UndefinedException(); } + /// Returns the number of non-terminal nodes in the game + size_t NumNonterminalNodes() const override { throw UndefinedException(); } //@} /// @name Outcomes @@ -100,6 +90,12 @@ class GameTableRep : public GameExplicitRep { void DeleteOutcome(const GameOutcome &) override; //@} + /// @name Strategies + //@{ + GameStrategy NewStrategy(const GamePlayer &, const std::string &) override; + void DeleteStrategy(const GameStrategy &p_strategy) override; + //@} + /// @name Writing data files //@{ /// Write the game to a file in .nfg outcome format diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 3d68f36d8..cdcc1da16 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -23,6 +23,7 @@ #include #include #include +#include #include "gambit.h" #include "gametree.h" @@ -50,13 +51,12 @@ template MixedStrategyProfileRep *TreeMixedStrategyProfileRep::C return new TreeMixedStrategyProfileRep(*this); } -template T TreeMixedStrategyProfileRep::GetPayoff(int pl) const +template void TreeMixedStrategyProfileRep::MakeBehavior() const { if (mixed_behav_profile_sptr.get() == nullptr) { - MixedStrategyProfile tmp(Copy()); - mixed_behav_profile_sptr = std::make_shared>(tmp); + mixed_behav_profile_sptr = + std::make_shared>(MixedStrategyProfile(Copy())); } - return mixed_behav_profile_sptr->GetPayoff(pl); } template void TreeMixedStrategyProfileRep::InvalidateCache() const @@ -64,54 +64,46 @@ template void TreeMixedStrategyProfileRep::InvalidateCache() const mixed_behav_profile_sptr = nullptr; } +template T TreeMixedStrategyProfileRep::GetPayoff(int pl) const +{ + MakeBehavior(); + return mixed_behav_profile_sptr->GetPayoff(pl); +} + template T TreeMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &strategy) const { - MixedStrategyProfile foo(Copy()); - for (auto s : this->m_support.GetStrategies(this->m_support.GetGame()->GetPlayer(pl))) { - foo[s] = static_cast(0); - } - foo[strategy] = static_cast(1); - return foo.GetPayoff(pl); + TreeMixedStrategyProfileRep tmp(*this); + tmp.SetStrategy(strategy); + return tmp.GetPayoff(pl); } template T TreeMixedStrategyProfileRep::GetPayoffDeriv(int pl, const GameStrategy &strategy1, const GameStrategy &strategy2) const { - GamePlayerRep *player1 = strategy1->GetPlayer(); - GamePlayerRep *player2 = strategy2->GetPlayer(); - if (player1 == player2) { - return T(0); + if (strategy1->GetPlayer() == strategy2->GetPlayer()) { + return static_cast(0); } - - MixedStrategyProfile foo(Copy()); - for (auto strategy : this->m_support.GetStrategies(player1)) { - foo[strategy] = T(0); - } - foo[strategy1] = T(1); - - for (auto strategy : this->m_support.GetStrategies(player2)) { - foo[strategy] = T(0); - } - foo[strategy2] = T(1); - - return foo.GetPayoff(pl); + TreeMixedStrategyProfileRep tmp(*this); + tmp.SetStrategy(strategy1); + tmp.SetStrategy(strategy2); + return tmp.GetPayoff(pl); } template class TreeMixedStrategyProfileRep; template class TreeMixedStrategyProfileRep; //======================================================================== -// class GameTreeActionRep +// class GameActionRep //======================================================================== -bool GameTreeActionRep::Precedes(const GameNode &n) const +bool GameActionRep::Precedes(const GameNode &n) const { GameNode node = n; while (node != node->GetGame()->GetRoot()) { - if (node->GetPriorAction() == GameAction(const_cast(this))) { + if (node->GetPriorAction() == GameAction(const_cast(this))) { return true; } else { @@ -121,174 +113,150 @@ bool GameTreeActionRep::Precedes(const GameNode &n) const return false; } -void GameTreeActionRep::DeleteAction() +void GameTreeRep::DeleteAction(GameAction p_action) { - if (m_infoset->NumActions() == 1) { - throw UndefinedException(); + GameActionRep *action = p_action; + auto *infoset = action->m_infoset; + if (infoset->m_game != this) { + throw MismatchException(); } - - m_infoset->GetGame()->IncrementVersion(); - int where; - for (where = 1; where <= m_infoset->m_actions.size() && m_infoset->m_actions[where] != this; - where++) - ; - - m_infoset->RemoveAction(where); - for (auto member : m_infoset->m_members) { - member->children[where]->DeleteTree(); - member->children[where]->Invalidate(); - erase_atindex(member->children, where); + if (infoset->m_actions.size() == 1) { + throw UndefinedException(); } - if (m_infoset->IsChanceInfoset()) { - m_infoset->m_efg->NormalizeChanceProbs(m_infoset); + IncrementVersion(); + auto where = std::find(infoset->m_actions.begin(), infoset->m_actions.end(), action); + auto offset = where - infoset->m_actions.begin(); + (*where)->Invalidate(); + infoset->m_actions.erase(where); + if (infoset->m_player->IsChance()) { + infoset->m_probs.erase(std::next(infoset->m_probs.begin(), offset)); + NormalizeChanceProbs(infoset); + } + infoset->RenumberActions(); + + for (auto member : infoset->m_members) { + auto it = std::next(member->m_children.begin(), offset); + DeleteTree(*it); + m_numNodes--; + (*it)->Invalidate(); + member->m_children.erase(it); } - m_infoset->m_efg->ClearComputedValues(); - m_infoset->m_efg->Canonicalize(); + ClearComputedValues(); + Canonicalize(); } -GameInfoset GameTreeActionRep::GetInfoset() const { return m_infoset; } +GameInfoset GameActionRep::GetInfoset() const { return m_infoset; } //======================================================================== -// class GameTreeInfosetRep +// class GameInfosetRep //======================================================================== -GameTreeInfosetRep::GameTreeInfosetRep(GameTreeRep *p_efg, int p_number, GamePlayerRep *p_player, - int p_actions) - : m_efg(p_efg), m_number(p_number), m_player(p_player), m_actions(p_actions), flag(0) +GameInfosetRep::GameInfosetRep(GameRep *p_efg, int p_number, GamePlayerRep *p_player, + int p_actions) + : m_game(p_efg), m_number(p_number), m_player(p_player), m_actions(p_actions) { - while (p_actions) { - m_actions[p_actions] = new GameTreeActionRep(p_actions, "", this); - p_actions--; - } - - m_player->m_infosets.push_back(this); - + std::generate(m_actions.begin(), m_actions.end(), + [this, i = 1]() mutable { return new GameActionRep(i++, "", this); }); if (p_player->IsChance()) { - m_probs = Array(m_actions.size()); - auto prob = lexical_cast(Rational(1, m_actions.size())); - for (int act = 1; act <= m_actions.size(); act++) { - m_probs[act] = prob; - } + m_probs = std::vector(m_actions.size()); + std::fill(m_probs.begin(), m_probs.end(), Rational(1, m_actions.size())); } + m_player->m_infosets.push_back(this); } -GameTreeInfosetRep::~GameTreeInfosetRep() +GameInfosetRep::~GameInfosetRep() { std::for_each(m_actions.begin(), m_actions.end(), [](GameActionRep *a) { a->Invalidate(); }); } -Game GameTreeInfosetRep::GetGame() const { return m_efg; } - -Array GameTreeInfosetRep::GetActions() const -{ - Array ret(m_actions.size()); - std::transform(m_actions.cbegin(), m_actions.end(), ret.begin(), - [](GameActionRep *a) -> GameAction { return a; }); - return ret; -} - -void GameTreeInfosetRep::SetPlayer(GamePlayer p_player) +void GameTreeRep::SetPlayer(GameInfoset p_infoset, GamePlayer p_player) { - if (p_player->GetGame() != m_efg) { + if (p_infoset->GetGame() != this || p_player->GetGame() != this) { throw MismatchException(); } - if (m_player->IsChance() || p_player->IsChance()) { + if (p_infoset->GetPlayer()->IsChance() || p_player->IsChance()) { throw UndefinedException(); } - if (m_player == p_player) { + if (p_infoset->GetPlayer() == p_player) { return; } - m_efg->IncrementVersion(); - m_player->m_infosets.erase( - std::find(m_player->m_infosets.begin(), m_player->m_infosets.end(), this)); - m_player = p_player; - p_player->m_infosets.push_back(this); + GamePlayerRep *oldPlayer = p_infoset->GetPlayer(); + IncrementVersion(); + oldPlayer->m_infosets.erase( + std::find(oldPlayer->m_infosets.begin(), oldPlayer->m_infosets.end(), p_infoset)); + p_infoset->m_player = p_player; + p_player->m_infosets.push_back(p_infoset); - m_efg->ClearComputedValues(); - m_efg->Canonicalize(); + ClearComputedValues(); + Canonicalize(); } -bool GameTreeInfosetRep::Precedes(GameNode p_node) const +bool GameInfosetRep::Precedes(GameNode p_node) const { - auto *node = dynamic_cast(p_node.operator->()); + auto node = p_node; while (node->m_parent) { - if (node->infoset == this) { + if (node->m_infoset == this) { return true; } - else { - node = node->m_parent; - } + node = node->m_parent; } return false; } -GameAction GameTreeInfosetRep::InsertAction(GameAction p_action /* =0 */) +GameAction GameTreeRep::InsertAction(GameInfoset p_infoset, GameAction p_action /* =nullptr */) { - if (p_action && p_action->GetInfoset() != this) { + if (p_action && p_action->GetInfoset() != p_infoset) { throw MismatchException(); } - - m_efg->IncrementVersion(); - int where = m_actions.size() + 1; - if (p_action) { - for (where = 1; m_actions[where] != p_action; where++) - ; + if (p_infoset->m_game != this) { + throw MismatchException(); } - auto *action = new GameTreeActionRep(where, "", this); - m_actions.insert(std::next(m_actions.cbegin(), where - 1), action); - if (m_player->IsChance()) { - m_probs.insert(std::next(m_probs.cbegin(), where - 1), Number("0")); - } + IncrementVersion(); + auto where = std::find(p_infoset->m_actions.begin(), p_infoset->m_actions.end(), p_action); + auto offset = where - p_infoset->m_actions.begin(); - for (int act = 1; act <= m_actions.size(); act++) { - m_actions[act]->m_number = act; + auto *action = new GameActionRep(offset + 1, "", p_infoset); + p_infoset->m_actions.insert(where, action); + if (p_infoset->m_player->IsChance()) { + p_infoset->m_probs.insert(std::next(p_infoset->m_probs.cbegin(), offset), Number()); } - - for (int i = 1; i <= m_members.size(); i++) { - m_members[i]->children.insert(std::next(m_members[i]->children.cbegin(), where - 1), - new GameTreeNodeRep(m_efg, m_members[i])); + p_infoset->RenumberActions(); + for (const auto &member : p_infoset->m_members) { + member->m_children.insert(std::next(member->m_children.cbegin(), offset), + new GameNodeRep(this, member)); } - m_efg->ClearComputedValues(); - m_efg->Canonicalize(); + m_numNodes += p_infoset->m_members.size(); + // m_numNonterminalNodes stays unchanged when an action is appended to an information set + ClearComputedValues(); + Canonicalize(); return action; } -void GameTreeInfosetRep::RemoveAction(int which) +void GameTreeRep::RemoveMember(GameInfosetRep *p_infoset, GameNodeRep *p_node) { - m_efg->IncrementVersion(); - m_actions[which]->Invalidate(); - erase_atindex(m_actions, which); - if (m_player->IsChance()) { - erase_atindex(m_probs, which); - } - for (; which <= m_actions.size(); which++) { - m_actions[which]->m_number = which; - } -} - -void GameTreeInfosetRep::RemoveMember(GameTreeNodeRep *p_node) -{ - m_efg->IncrementVersion(); - m_members.erase(std::find(m_members.begin(), m_members.end(), p_node)); - if (m_members.empty()) { - m_player->m_infosets.erase( - std::find(m_player->m_infosets.begin(), m_player->m_infosets.end(), this)); - Invalidate(); + IncrementVersion(); + p_infoset->m_members.erase( + std::find(p_infoset->m_members.begin(), p_infoset->m_members.end(), p_node)); + if (p_infoset->m_members.empty()) { + p_infoset->m_player->m_infosets.erase(std::find(p_infoset->m_player->m_infosets.begin(), + p_infoset->m_player->m_infosets.end(), + p_infoset)); int iset = 1; - for (auto &infoset : m_player->m_infosets) { + for (auto &infoset : p_infoset->m_player->m_infosets) { infoset->m_number = iset++; } + p_infoset->Invalidate(); } } -void GameTreeInfosetRep::Reveal(GamePlayer p_player) +void GameTreeRep::Reveal(GameInfoset p_atInfoset, GamePlayer p_player) { - m_efg->IncrementVersion(); - for (auto action : m_actions) { + IncrementVersion(); + for (auto action : p_atInfoset->GetActions()) { for (auto infoset : p_player->m_infosets) { // make copy of members to iterate correctly // (since the information set may be changed in the process) @@ -297,124 +265,102 @@ void GameTreeInfosetRep::Reveal(GamePlayer p_player) // This information set holds all members of information set // which follow 'action'. GameInfoset newiset = nullptr; - for (int m = 1; m <= members.size(); m++) { - if (action->Precedes(members[m])) { + for (auto &member : members) { + if (action->Precedes(member)) { if (!newiset) { - newiset = members[m]->LeaveInfoset(); + newiset = LeaveInfoset(member); } else { - members[m]->SetInfoset(newiset); + SetInfoset(member, newiset); } } } } } - m_efg->ClearComputedValues(); - m_efg->Canonicalize(); -} - -GameNode GameTreeInfosetRep::GetMember(int p_index) const { return m_members[p_index]; } - -Array GameTreeInfosetRep::GetMembers() const -{ - Array ret(m_members.size()); - std::transform(m_members.cbegin(), m_members.cend(), ret.begin(), - [](GameTreeNodeRep *n) -> GameNode { return n; }); - return ret; + ClearComputedValues(); + Canonicalize(); } -GamePlayer GameTreeInfosetRep::GetPlayer() const { return m_player; } - -bool GameTreeInfosetRep::IsChanceInfoset() const { return m_player->IsChance(); } - //======================================================================== -// class GameTreeNodeRep +// class GameNodeRep //======================================================================== -GameTreeNodeRep::GameTreeNodeRep(GameTreeRep *e, GameTreeNodeRep *p) - : number(0), m_efg(e), infoset(nullptr), m_parent(p), outcome(nullptr) -{ -} +GameNodeRep::GameNodeRep(GameRep *e, GameNodeRep *p) : m_game(e), m_parent(p) {} -GameTreeNodeRep::~GameTreeNodeRep() +GameNodeRep::~GameNodeRep() { - std::for_each(children.begin(), children.end(), [](GameNodeRep *n) { n->Invalidate(); }); + std::for_each(m_children.begin(), m_children.end(), [](GameNodeRep *n) { n->Invalidate(); }); } -Game GameTreeNodeRep::GetGame() const { return m_efg; } - -Array GameTreeNodeRep::GetChildren() const +GameNode GameNodeRep::GetNextSibling() const { - Array ret(children.size()); - std::transform(children.cbegin(), children.cend(), ret.begin(), - [](GameTreeNodeRep *n) -> GameNode { return n; }); - return ret; -} - -GameNode GameTreeNodeRep::GetNextSibling() const -{ - if (!m_parent || m_parent->children.back() == this) { + if (!m_parent || m_parent->m_children.back() == this) { return nullptr; } - return *std::next(std::find(m_parent->children.begin(), m_parent->children.end(), this)); + return *std::next(std::find(m_parent->m_children.begin(), m_parent->m_children.end(), this)); } -GameNode GameTreeNodeRep::GetPriorSibling() const +GameNode GameNodeRep::GetPriorSibling() const { - if (!m_parent || m_parent->children.front() == this) { + if (!m_parent || m_parent->m_children.front() == this) { return nullptr; } - return *std::prev(std::find(m_parent->children.begin(), m_parent->children.end(), this)); + return *std::prev(std::find(m_parent->m_children.begin(), m_parent->m_children.end(), this)); } -GameAction GameTreeNodeRep::GetPriorAction() const +GameAction GameNodeRep::GetPriorAction() const { if (!m_parent) { return nullptr; } - GameTreeInfosetRep *infoset = m_parent->infoset; - for (int i = 1; i <= infoset->NumActions(); i++) { - if (GameNode(const_cast(this)) == GetParent()->GetChild(i)) { - return infoset->GetAction(i); + for (const auto &action : m_parent->m_infoset->m_actions) { + if (m_parent->GetChild(action) == GameNode(this)) { + return action; } } return nullptr; } -void GameTreeNodeRep::DeleteOutcome(GameOutcomeRep *outc) +void GameNodeRep::DeleteOutcome(GameOutcomeRep *outc) { - m_efg->IncrementVersion(); - if (outc == outcome) { - outcome = nullptr; + m_game->IncrementVersion(); + if (outc == m_outcome) { + m_outcome = nullptr; } - for (auto child : children) { + for (auto child : m_children) { child->DeleteOutcome(outc); } } -void GameTreeNodeRep::SetOutcome(const GameOutcome &p_outcome) +void GameTreeRep::SetOutcome(GameNode p_node, const GameOutcome &p_outcome) { - m_efg->IncrementVersion(); - if (p_outcome != outcome) { - outcome = p_outcome; - m_efg->ClearComputedValues(); + IncrementVersion(); + if (p_node->GetGame() != this) { + throw MismatchException(); + } + if (p_outcome && p_outcome->GetGame() != this) { + throw MismatchException(); + } + if (p_outcome != p_node->m_outcome) { + p_node->m_outcome = p_outcome; + ClearComputedValues(); } } -bool GameTreeNodeRep::IsSuccessorOf(GameNode p_node) const +bool GameNodeRep::IsSuccessorOf(GameNode p_node) const { - auto *n = const_cast(this); + auto *n = const_cast(this); while (n && n != p_node) { n = n->m_parent; } return (n == p_node); } -bool GameTreeNodeRep::IsSubgameRoot() const +bool GameNodeRep::IsSubgameRoot() const { // First take care of a couple easy cases - if (children.empty() || infoset->NumMembers() > 1) { + if (m_children.empty() || m_infoset->m_members.size() > 1) { return false; } if (!m_parent) { @@ -424,14 +370,15 @@ bool GameTreeNodeRep::IsSubgameRoot() const // A node is a subgame root if and only if in every information set, // either all members succeed the node in the tree, // or all members do not succeed the node in the tree. - for (auto player : m_efg->GetPlayers()) { + for (auto player : m_game->GetPlayers()) { for (auto infoset : player->GetInfosets()) { - bool precedes = infoset->GetMember(1)->IsSuccessorOf(const_cast(this)); - for (int mem = 2; mem <= infoset->NumMembers(); mem++) { - if (infoset->GetMember(mem)->IsSuccessorOf(const_cast(this)) != - precedes) { - return false; - } + const bool precedes = + infoset->m_members.front()->IsSuccessorOf(const_cast(this)); + if (std::any_of(std::next(infoset->m_members.begin()), infoset->m_members.end(), + [this, precedes](GameNodeRep *m) { + return m->IsSuccessorOf(const_cast(this)) != precedes; + })) { + return false; } } } @@ -439,241 +386,275 @@ bool GameTreeNodeRep::IsSubgameRoot() const return true; } -void GameTreeNodeRep::DeleteParent() +void GameTreeRep::DeleteParent(GameNode p_node) { - if (!m_parent) { + if (p_node->GetGame() != this) { + throw MismatchException(); + } + auto *node = dynamic_cast(p_node.operator->()); + if (!node->m_parent) { return; } - m_efg->IncrementVersion(); - GameTreeNodeRep *oldParent = m_parent; + IncrementVersion(); + auto *oldParent = node->m_parent; - oldParent->children.erase( - std::find(oldParent->children.begin(), oldParent->children.end(), this)); - oldParent->DeleteTree(); - m_parent = oldParent->m_parent; - if (m_parent) { - std::replace(m_parent->children.begin(), m_parent->children.end(), oldParent, this); + oldParent->m_children.erase( + std::find(oldParent->m_children.begin(), oldParent->m_children.end(), node)); + DeleteTree(oldParent); + node->m_parent = oldParent->m_parent; + m_numNodes--; + if (node->m_parent) { + std::replace(node->m_parent->m_children.begin(), node->m_parent->m_children.end(), oldParent, + node); } else { - m_efg->m_root = this; + m_root = node; } oldParent->Invalidate(); - m_efg->ClearComputedValues(); - m_efg->Canonicalize(); + ClearComputedValues(); + Canonicalize(); } -void GameTreeNodeRep::DeleteTree() +void GameTreeRep::DeleteTree(GameNode p_node) { - m_efg->IncrementVersion(); - while (!children.empty()) { - children.front()->DeleteTree(); - children.front()->Invalidate(); - erase_atindex(children, 1); + if (p_node->GetGame() != this) { + throw MismatchException(); } - if (infoset) { - infoset->RemoveMember(this); - infoset = nullptr; + GameNodeRep *node = p_node; + if (!node->IsTerminal()) { + m_numNonterminalNodes--; } + IncrementVersion(); + while (!node->m_children.empty()) { + DeleteTree(node->m_children.back()); + m_numNodes--; + node->m_children.back()->Invalidate(); + node->m_children.pop_back(); + } + if (node->m_infoset) { + RemoveMember(node->m_infoset, node); + node->m_infoset = nullptr; + } + node->m_outcome = nullptr; + node->m_label = ""; - outcome = nullptr; - m_label = ""; - - m_efg->ClearComputedValues(); - m_efg->Canonicalize(); + ClearComputedValues(); + Canonicalize(); } -void GameTreeNodeRep::CopySubtree(GameTreeNodeRep *src, GameTreeNodeRep *stop) +void GameTreeRep::CopySubtree(GameNodeRep *dest, GameNodeRep *src, GameNodeRep *stop) { - m_efg->IncrementVersion(); + IncrementVersion(); if (src == stop) { - outcome = src->outcome; + dest->m_outcome = src->m_outcome; return; } - if (src->children.size()) { - AppendMove(src->infoset); - for (int i = 1; i <= src->children.size(); i++) { - children[i]->CopySubtree(src->children[i], stop); + if (!src->m_children.empty()) { + AppendMove(dest, src->m_infoset); + for (auto dest_child = dest->m_children.begin(), src_child = src->m_children.begin(); + src_child != src->m_children.end(); src_child++, dest_child++) { + CopySubtree(*dest_child, *src_child, stop); } } - m_label = src->m_label; - outcome = src->outcome; + dest->m_label = src->m_label; + dest->m_outcome = src->m_outcome; } -void GameTreeNodeRep::CopyTree(GameNode p_src) +void GameTreeRep::CopyTree(GameNode p_dest, GameNode p_src) { - if (p_src->GetGame() != m_efg) { + if (p_dest->GetGame() != this || p_src->GetGame() != this) { throw MismatchException(); } - if (p_src == this || !children.empty()) { + GameNodeRep *dest = p_dest; + GameNodeRep *src = p_src; + if (dest == src || !dest->m_children.empty()) { return; } - m_efg->IncrementVersion(); - auto *src = dynamic_cast(p_src.operator->()); - - if (!src->children.empty()) { - AppendMove(src->infoset); - for (int i = 1; i <= src->children.size(); i++) { - children[i]->CopySubtree(src->children[i], this); + IncrementVersion(); + if (!src->m_children.empty()) { + AppendMove(dest, src->m_infoset); + for (auto dest_child = dest->m_children.begin(), src_child = src->m_children.begin(); + src_child != src->m_children.end(); src_child++, dest_child++) { + CopySubtree(*dest_child, *src_child, dest); } - - m_efg->ClearComputedValues(); - m_efg->Canonicalize(); + ClearComputedValues(); + Canonicalize(); } } -void GameTreeNodeRep::MoveTree(GameNode p_src) +void GameTreeRep::MoveTree(GameNode p_dest, GameNode p_src) { - if (p_src->GetGame() != m_efg) { + if (p_dest->GetGame() != this || p_src->GetGame() != this) { throw MismatchException(); } - if (p_src == this || !children.empty() || IsSuccessorOf(p_src)) { + GameNodeRep *dest = p_dest; + GameNodeRep *src = p_src; + if (src == dest || !dest->m_children.empty() || p_dest->IsSuccessorOf(p_src)) { return; } - m_efg->IncrementVersion(); - auto *src = dynamic_cast(p_src.operator->()); - std::iter_swap(std::find(src->m_parent->children.begin(), src->m_parent->children.end(), src), - std::find(m_parent->children.begin(), m_parent->children.end(), this)); - std::swap(src->m_parent, m_parent); - m_label = ""; - outcome = nullptr; + IncrementVersion(); + std::iter_swap( + std::find(src->m_parent->m_children.begin(), src->m_parent->m_children.end(), src), + std::find(dest->m_parent->m_children.begin(), dest->m_parent->m_children.end(), dest)); + std::swap(src->m_parent, dest->m_parent); + dest->m_label = ""; + dest->m_outcome = nullptr; - m_efg->ClearComputedValues(); - m_efg->Canonicalize(); + ClearComputedValues(); + Canonicalize(); } -Game GameTreeNodeRep::CopySubgame() const +Game GameTreeRep::CopySubgame(GameNode p_root) const { + if (p_root->GetGame() != const_cast(this)) { + throw MismatchException(); + } std::ostringstream os; - m_efg->WriteEfgFile(os, const_cast(this)); + WriteEfgFile(os, p_root); std::istringstream is(os.str()); return ReadGame(is); } -void GameTreeNodeRep::SetInfoset(GameInfoset p_infoset) +void GameTreeRep::SetInfoset(GameNode p_node, GameInfoset p_infoset) { - if (p_infoset->GetGame() != m_efg) { + if (p_node->GetGame() != this || p_infoset->GetGame() != this) { throw MismatchException(); } - if (!infoset || infoset == p_infoset) { + GameNodeRep *node = p_node; + if (!node->m_infoset || node->m_infoset == p_infoset) { return; } - if (p_infoset->NumActions() != children.size()) { + if (p_infoset->m_actions.size() != node->m_children.size()) { throw DimensionException(); } - m_efg->IncrementVersion(); - infoset->RemoveMember(this); - dynamic_cast(p_infoset.operator->())->AddMember(this); - infoset = dynamic_cast(p_infoset.operator->()); + IncrementVersion(); + RemoveMember(node->m_infoset, node); + p_infoset->m_members.push_back(node); + node->m_infoset = p_infoset; - m_efg->ClearComputedValues(); - m_efg->Canonicalize(); + ClearComputedValues(); + Canonicalize(); } -GameInfoset GameTreeNodeRep::LeaveInfoset() +GameInfoset GameTreeRep::LeaveInfoset(GameNode p_node) { - if (!infoset) { + GameNodeRep *node = p_node; + if (node->m_game != this) { + throw MismatchException(); + } + if (!node->m_infoset) { return nullptr; } - m_efg->IncrementVersion(); - GameTreeInfosetRep *oldInfoset = infoset; + IncrementVersion(); + auto *oldInfoset = node->m_infoset; if (oldInfoset->m_members.size() == 1) { return oldInfoset; } GamePlayerRep *player = oldInfoset->m_player; - oldInfoset->RemoveMember(this); - infoset = new GameTreeInfosetRep(m_efg, player->m_infosets.size() + 1, player, children.size()); - infoset->AddMember(this); - for (int i = 1; i <= oldInfoset->m_actions.size(); i++) { - infoset->m_actions[i]->SetLabel(oldInfoset->m_actions[i]->GetLabel()); + RemoveMember(oldInfoset, node); + node->m_infoset = + new GameInfosetRep(this, player->m_infosets.size() + 1, player, node->m_children.size()); + node->m_infoset->m_members.push_back(node); + for (auto old_act = oldInfoset->m_actions.begin(), new_act = node->m_infoset->m_actions.begin(); + old_act != oldInfoset->m_actions.end(); ++old_act, ++new_act) { + (*new_act)->SetLabel((*old_act)->GetLabel()); } - - m_efg->ClearComputedValues(); - m_efg->Canonicalize(); - return infoset; + ClearComputedValues(); + Canonicalize(); + return node->m_infoset; } -GameInfoset GameTreeNodeRep::AppendMove(GamePlayer p_player, int p_actions) +GameInfoset GameTreeRep::AppendMove(GameNode p_node, GamePlayer p_player, int p_actions) { - if (p_actions <= 0 || !children.empty()) { + GameNodeRep *node = p_node; + if (p_actions <= 0 || !node->m_children.empty()) { throw UndefinedException(); } - if (p_player->GetGame() != m_efg) { + if (p_node->GetGame() != this || p_player->GetGame() != this) { throw MismatchException(); } - m_efg->IncrementVersion(); + IncrementVersion(); return AppendMove( - new GameTreeInfosetRep(m_efg, p_player->m_infosets.size() + 1, p_player, p_actions)); + p_node, new GameInfosetRep(this, p_player->m_infosets.size() + 1, p_player, p_actions)); } -GameInfoset GameTreeNodeRep::AppendMove(GameInfoset p_infoset) +GameInfoset GameTreeRep::AppendMove(GameNode p_node, GameInfoset p_infoset) { - if (!children.empty()) { + GameNodeRep *node = p_node; + if (!node->m_children.empty()) { throw UndefinedException(); } - if (p_infoset->GetGame() != m_efg) { + if (p_node->GetGame() != this || p_infoset->GetGame() != this) { throw MismatchException(); } - m_efg->IncrementVersion(); - infoset = dynamic_cast(p_infoset.operator->()); - infoset->AddMember(this); - for (int i = 1; i <= p_infoset->NumActions(); i++) { - children.push_back(new GameTreeNodeRep(m_efg, this)); - } - - m_efg->ClearComputedValues(); - m_efg->Canonicalize(); - return infoset; + IncrementVersion(); + node->m_infoset = p_infoset; + node->m_infoset->m_members.push_back(node); + std::for_each(node->m_infoset->m_actions.begin(), node->m_infoset->m_actions.end(), + [this, node](const GameActionRep *) { + node->m_children.push_back(new GameNodeRep(this, node)); + m_numNodes++; + }); + m_numNonterminalNodes++; + ClearComputedValues(); + Canonicalize(); + return node->m_infoset; } -GameInfoset GameTreeNodeRep::InsertMove(GamePlayer p_player, int p_actions) +GameInfoset GameTreeRep::InsertMove(GameNode p_node, GamePlayer p_player, int p_actions) { if (p_actions <= 0) { throw UndefinedException(); } - if (p_player->GetGame() != m_efg) { + if (p_player->GetGame() != this) { throw MismatchException(); } - m_efg->IncrementVersion(); + IncrementVersion(); return InsertMove( - new GameTreeInfosetRep(m_efg, p_player->m_infosets.size() + 1, p_player, p_actions)); + p_node, new GameInfosetRep(this, p_player->m_infosets.size() + 1, p_player, p_actions)); } -GameInfoset GameTreeNodeRep::InsertMove(GameInfoset p_infoset) +GameInfoset GameTreeRep::InsertMove(GameNode p_node, GameInfoset p_infoset) { - if (p_infoset->GetGame() != m_efg) { + if (p_infoset->GetGame() != this) { throw MismatchException(); } - m_efg->IncrementVersion(); - auto *newNode = new GameTreeNodeRep(m_efg, m_parent); - newNode->infoset = dynamic_cast(p_infoset.operator->()); - dynamic_cast(p_infoset.operator->())->AddMember(newNode); + IncrementVersion(); + GameNodeRep *node = p_node; + auto *newNode = new GameNodeRep(this, node->m_parent); + newNode->m_infoset = p_infoset; + p_infoset->m_members.push_back(newNode); - if (m_parent) { - std::replace(m_parent->children.begin(), m_parent->children.end(), this, newNode); + if (node->m_parent) { + std::replace(node->m_parent->m_children.begin(), node->m_parent->m_children.end(), node, + newNode); } else { - m_efg->m_root = newNode; + m_root = newNode; } - newNode->children.push_back(this); - m_parent = newNode; - - for (int i = 1; i < p_infoset->NumActions(); i++) { - newNode->children.push_back(new GameTreeNodeRep(m_efg, newNode)); - } + node->m_parent = newNode; + newNode->m_children.push_back(node); + std::for_each(std::next(newNode->m_infoset->m_actions.begin()), + newNode->m_infoset->m_actions.end(), [this, newNode](const GameActionRep *) { + newNode->m_children.push_back(new GameNodeRep(this, newNode)); + }); - m_efg->ClearComputedValues(); - m_efg->Canonicalize(); + // Total nodes added = 1 (newNode) + (NumActions - 1) (new children of newNode) = NumActions + m_numNodes += newNode->m_infoset->m_actions.size(); + m_numNonterminalNodes++; + ClearComputedValues(); + Canonicalize(); return p_infoset; } @@ -686,8 +667,7 @@ GameInfoset GameTreeNodeRep::InsertMove(GameInfoset p_infoset) //------------------------------------------------------------------------ GameTreeRep::GameTreeRep() - : m_computedValues(false), m_doCanon(true), m_root(new GameTreeNodeRep(this, nullptr)), - m_chance(new GamePlayerRep(this, 0)) + : m_root(new GameNodeRep(this, nullptr)), m_chance(new GamePlayerRep(this, 0)) { } @@ -719,22 +699,22 @@ class NotZeroSumException : public Exception { const char *what() const noexcept override { return "Game is not constant sum"; } }; -Rational SubtreeSum(const GameNode &p_node) +Rational SubtreeSum(GameNode p_node) { Rational sum(0); - if (p_node->NumChildren() > 0) { - sum = SubtreeSum(p_node->GetChild(1)); - for (int i = 2; i <= p_node->NumChildren(); i++) { - if (SubtreeSum(p_node->GetChild(i)) != sum) { - throw NotZeroSumException(); - } + if (!p_node->IsTerminal()) { + auto children = p_node->GetChildren(); + sum = SubtreeSum(children.front()); + if (std::any_of(std::next(children.begin()), children.end(), + [sum](GameNode n) { return SubtreeSum(n) != sum; })) { + throw NotZeroSumException(); } } if (p_node->GetOutcome()) { - for (int pl = 1; pl <= p_node->GetGame()->NumPlayers(); pl++) { - sum += static_cast(p_node->GetOutcome()->GetPayoff(pl)); + for (const auto &player : p_node->GetGame()->GetPlayers()) { + sum += p_node->GetOutcome()->GetPayoff(player); } } return sum; @@ -753,65 +733,167 @@ bool GameTreeRep::IsConstSum() const } } -bool GameTreeRep::IsPerfectRecall(GameInfoset &s1, GameInfoset &s2) const +bool GameTreeRep::IsPerfectRecall() const { - for (auto player : m_players) { - for (int i = 1; i <= player->NumInfosets(); i++) { - GameTreeInfosetRep *iset1 = player->m_infosets[i]; - for (int j = 1; j <= player->NumInfosets(); j++) { - GameTreeInfosetRep *iset2 = player->m_infosets[j]; - - bool precedes = false; - int action = 0; - - for (int m = 1; m <= iset2->NumMembers(); m++) { - int n; - for (n = 1; n <= iset1->NumMembers(); n++) { - if (iset2->GetMember(m)->IsSuccessorOf(iset1->GetMember(n)) && - iset1->GetMember(n) != iset2->GetMember(m)) { - precedes = true; - for (int act = 1; act <= iset1->NumActions(); act++) { - if (iset2->GetMember(m)->IsSuccessorOf(iset1->GetMember(n)->GetChild(act))) { - if (action != 0 && action != act) { - s1 = iset1; - s2 = iset2; - return false; - } - action = act; - } - } - break; - } - } + using Assignment = std::pair; + // Histories track the last actions of players taken to reach a given node + using History = std::map>; - if (i == j && precedes) { - s1 = iset1; - s2 = iset2; - return false; - } + std::map> last_actions_for_infoset; + std::map last_actions_for_node; - if (n > iset1->NumMembers() && precedes) { - s1 = iset1; - s2 = iset2; - return false; - } + History initial_history; + for (const auto &player : GetPlayers()) { + initial_history[player] = std::nullopt; + } + initial_history[GetChance()] = std::nullopt; + last_actions_for_node[GetRoot()] = initial_history; + + for (const GameNode n : *this) { + History &history = last_actions_for_node.at(n); + + if (n->IsTerminal()) { + last_actions_for_node.erase(n); + continue; + } + + const GamePlayer player = n->GetPlayer(); + const GameInfoset infoset = n->GetInfoset(); + const std::optional &player_last_action = history.at(player); + + auto [it, inserted] = last_actions_for_infoset.try_emplace(infoset, player_last_action); + + if (!inserted) { + if (it->second != player_last_action) { + if (!player->IsChance()) { + return false; } } } + + for (const GameAction action : infoset->GetActions()) { + const GameNode child = n->GetChild(action); + History child_history = history; + child_history.at(player) = std::make_pair(infoset, action); + last_actions_for_node[child] = child_history; + } + + last_actions_for_node.erase(n); } return true; } +void GameTreeRep::BuildPlayerConsequences() const +{ + using Assignment = std::pair; + using History = std::map>; + std::map last_actions_for_node; + + m_playerConsequences.clear(); + + std::vector players(GetPlayers().begin(), GetPlayers().end()); + players.push_back(GetChance()); + + for (const auto &player : players) { + auto &consequences = m_playerConsequences[player]; + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + consequences.transitions[infoset][action] = {}; + } + } + } + + History initial_history; + for (const auto &player : players) { + initial_history[player] = std::nullopt; + } + last_actions_for_node[GetRoot()] = initial_history; + + for (const GameNode n : *this) { + History &history = last_actions_for_node.at(n); + + if (n->IsTerminal()) { + last_actions_for_node.erase(n); + continue; + } + + const GamePlayer player = n->GetPlayer(); + const GameInfoset infoset = n->GetInfoset(); + const std::optional &player_last_action = history.at(player); + + if (player_last_action.has_value()) { + const auto &[prior_infoset, prior_action] = *player_last_action; + m_playerConsequences.at(player) + .transitions.at(prior_infoset) + .at(prior_action) + .insert(infoset); + } + else { + m_playerConsequences.at(player).root_infosets.insert(infoset); + } + + for (const GameAction action : infoset->GetActions()) { + const GameNode child = n->GetChild(action); + History child_history = history; + child_history.at(player) = std::make_pair(infoset, action); + last_actions_for_node[child] = child_history; + } + + last_actions_for_node.erase(n); + } + + std::cout << "\n--- Players' Consequences ---\n"; + for (const auto &[player, consequences] : m_playerConsequences) { + // Corrected check for empty infosets + if (player->GetInfosets().size() == 0) { + continue; + } + + std::cout << "Player " << player->GetNumber() << ":\n"; + + if (!consequences.root_infosets.empty()) { + std::cout << " Root Infoset(s): { "; + for (auto it = consequences.root_infosets.begin(); it != consequences.root_infosets.end(); + ++it) { + std::cout << "I" << (*it)->GetNumber() + << (std::next(it) != consequences.root_infosets.end() ? ", " : ""); + } + std::cout << " }\n"; + } + + for (const auto &[source_is, action_map] : consequences.transitions) { + std::cout << " From I" << source_is->GetNumber() << ":\n"; + for (const auto &[action, next_infosets] : action_map) { + std::cout << " Action " << action->GetNumber() << " (" << action->GetLabel() << ") -> "; + if (next_infosets.empty()) { + std::cout << " (terminal or other player's move)\n"; + } + else { + std::cout << "{ "; + for (auto it = next_infosets.begin(); it != next_infosets.end(); ++it) { + std::cout << "I" << (*it)->GetNumber() + << (std::next(it) != next_infosets.end() ? ", " : ""); + } + std::cout << " }\n"; + } + } + } + std::cout << "\n"; + } + std::cout << "---------------------------------------------\n" << std::endl; +} + //------------------------------------------------------------------------ // GameTreeRep: Managing the representation //------------------------------------------------------------------------ -void GameTreeRep::NumberNodes(GameTreeNodeRep *n, int &index) +void GameTreeRep::NumberNodes(GameNodeRep *n, int &index) { - n->number = index++; - for (int child = 1; child <= n->children.size(); NumberNodes(n->children[child++], index)) - ; + n->m_number = index++; + for (auto &child : n->m_children) { + NumberNodes(child, index); + } } void GameTreeRep::Canonicalize() @@ -822,20 +904,19 @@ void GameTreeRep::Canonicalize() int nodeindex = 1; NumberNodes(m_root, nodeindex); - for (int pl = 0; pl <= m_players.size(); pl++) { - GamePlayerRep *player = (pl) ? m_players[pl] : m_chance; + for (size_t pl = 0; pl <= m_players.size(); pl++) { + GamePlayerRep *player = (pl) ? m_players[pl - 1] : m_chance; // Sort nodes within information sets according to ID. // Coded using a bubble sort for simplicity; large games might // find a quicksort worthwhile. - for (int iset = 1; iset <= player->m_infosets.size(); iset++) { - GameTreeInfosetRep *infoset = player->m_infosets[iset]; - for (int i = 1; i < infoset->m_members.size(); i++) { - for (int j = 1; j < infoset->m_members.size() - i; j++) { - if (infoset->m_members[j + 1]->number < infoset->m_members[j]->number) { - GameTreeNodeRep *tmp = infoset->m_members[j]; - infoset->m_members[j] = infoset->m_members[j + 1]; - infoset->m_members[j + 1] = tmp; + for (auto &infoset : player->m_infosets) { + for (size_t i = 1; i < infoset->m_members.size(); i++) { + for (size_t j = 1; j < infoset->m_members.size() - i; j++) { + if (infoset->m_members[j]->m_number < infoset->m_members[j - 1]->m_number) { + GameNodeRep *tmp = infoset->m_members[j - 1]; + infoset->m_members[j - 1] = infoset->m_members[j]; + infoset->m_members[j] = tmp; } } } @@ -844,27 +925,26 @@ void GameTreeRep::Canonicalize() // Sort information sets by the smallest ID among their members // Coded using a bubble sort for simplicity; large games might // find a quicksort worthwhile. - for (int i = 1; i < player->m_infosets.size(); i++) { - for (int j = 1; j < player->m_infosets.size() - i; j++) { - int a = ((player->m_infosets[j + 1]->m_members.size()) - ? player->m_infosets[j + 1]->m_members[1]->number - : 0); - int b = ((player->m_infosets[j]->m_members.size()) - ? player->m_infosets[j]->m_members[1]->number - : 0); + for (size_t i = 1; i < player->m_infosets.size(); i++) { + for (size_t j = 1; j < player->m_infosets.size() - i; j++) { + const int a = ((player->m_infosets[j]->m_members.size()) + ? player->m_infosets[j]->m_members[0]->m_number + : 0); + const int b = ((player->m_infosets[j - 1]->m_members.size()) + ? player->m_infosets[j - 1]->m_members[0]->m_number + : 0); if (a < b || b == 0) { - GameTreeInfosetRep *tmp = player->m_infosets[j]; - player->m_infosets[j] = player->m_infosets[j + 1]; - player->m_infosets[j + 1] = tmp; + auto *tmp = player->m_infosets[j - 1]; + player->m_infosets[j - 1] = player->m_infosets[j]; + player->m_infosets[j] = tmp; } } } // Reassign information set IDs - for (int iset = 1; iset <= player->m_infosets.size(); iset++) { - player->m_infosets[iset]->m_number = iset; - } + std::for_each(player->m_infosets.begin(), player->m_infosets.end(), + [iset = 1](GameInfosetRep *s) mutable { s->m_number = iset++; }); } } @@ -876,21 +956,58 @@ void GameTreeRep::ClearComputedValues() const } player->m_strategies.clear(); } + const_cast(this)->m_nodePlays.clear(); m_computedValues = false; } -void GameTreeRep::BuildComputedValues() +void GameTreeRep::BuildComputedValues() const { if (m_computedValues) { return; } - Canonicalize(); - for (const auto &player : m_players) { - player->MakeReducedStrats(m_root, nullptr); + + const_cast(this)->Canonicalize(); + + if (this->IsPerfectRecall()) { + this->BuildPlayerConsequences(); + for (const auto &player : m_players) { + player->MakeReducedStratsPR(m_playerConsequences.at(player)); + } + } + else { + for (const auto &player : m_players) { + std::map behav; + std::map ptr, whichbranch; + player->MakeReducedStrats(m_root, nullptr, behav, ptr, whichbranch); + } } + m_computedValues = true; } +void GameTreeRep::BuildConsistentPlays() +{ + m_nodePlays.clear(); + BuildConsistentPlaysRecursiveImpl(m_root); +} + +std::vector GameTreeRep::BuildConsistentPlaysRecursiveImpl(GameNodeRep *node) +{ + std::vector consistent_plays; + if (node->IsTerminal()) { + consistent_plays = std::vector{node}; + } + else { + for (GameNodeRep *child : node->GetChildren()) { + auto child_consistent_plays = BuildConsistentPlaysRecursiveImpl(child); + consistent_plays.insert(consistent_plays.end(), child_consistent_plays.begin(), + child_consistent_plays.end()); + } + } + m_nodePlays[node] = consistent_plays; + return consistent_plays; +} + //------------------------------------------------------------------------ // GameTreeRep: Writing data files //------------------------------------------------------------------------ @@ -915,13 +1032,13 @@ void WriteEfgFile(std::ostream &f, const GameNode &n) } f << n->GetInfoset()->GetNumber() << " " << QuoteString(n->GetInfoset()->GetLabel()) << ' '; if (n->GetInfoset()->IsChanceInfoset()) { - f << FormatList(n->GetInfoset()->GetActions(), [n](const GameAction &a) { + f << FormatList(n->GetInfoset()->GetActions(), [](const GameAction &a) { return QuoteString(a->GetLabel()) + " " + std::string(a->GetInfoset()->GetActionProb(a)); }); } else { f << FormatList(n->GetInfoset()->GetActions(), - [n](const GameAction &a) { return QuoteString(a->GetLabel()); }); + [](const GameAction &a) { return QuoteString(a->GetLabel()); }); } f << ' '; } @@ -929,7 +1046,7 @@ void WriteEfgFile(std::ostream &f, const GameNode &n) f << n->GetOutcome()->GetNumber() << " " << QuoteString(n->GetOutcome()->GetLabel()) << ' ' << FormatList( n->GetGame()->GetPlayers(), - [n](const GamePlayer &p) { return std::string(n->GetOutcome()->GetPayoff(p)); }, true) + [n](const GamePlayer &p) { return n->GetOutcome()->GetPayoff(p); }, true) << std::endl; } else { @@ -954,8 +1071,7 @@ void GameTreeRep::WriteEfgFile(std::ostream &p_file, const GameNode &p_subtree / void GameTreeRep::WriteNfgFile(std::ostream &p_file) const { - // FIXME: Building computed values is logically const. - const_cast(this)->BuildComputedValues(); + BuildComputedValues(); GameRep::WriteNfgFile(p_file); } @@ -981,11 +1097,10 @@ int GameTreeRep::BehavProfileLength() const GamePlayer GameTreeRep::NewPlayer() { IncrementVersion(); - GamePlayerRep *player = nullptr; - player = new GamePlayerRep(this, m_players.size() + 1); + auto player = new GamePlayerRep(this, m_players.size() + 1); m_players.push_back(player); - for (int outc = 1; outc <= m_outcomes.last_index(); outc++) { - m_outcomes[outc]->m_payoffs.push_back(Number()); + for (auto &outcome : m_outcomes) { + outcome->m_payoffs[player] = Number(); } ClearComputedValues(); return player; @@ -1008,47 +1123,55 @@ GameInfoset GameTreeRep::GetInfoset(int p_index) const throw IndexException(); } -Array GameTreeRep::GetInfosets() const +std::vector GameTreeRep::GetInfosets() const { - auto infosets = Array( - std::accumulate(m_players.begin(), m_players.end(), 0, - [](int ct, GamePlayerRep *player) { return ct + player->NumInfosets(); })); - int i = 1; - for (auto player : m_players) { - for (auto infoset : player->m_infosets) { - infosets[i++] = infoset; - } + std::vector infosets; + for (const auto &player : m_players) { + std::copy(player->m_infosets.begin(), player->m_infosets.end(), std::back_inserter(infosets)); } return infosets; } -Array GameTreeRep::NumInfosets() const +//------------------------------------------------------------------------ +// GameTreeRep: Outcomes +//------------------------------------------------------------------------ + +std::vector GameTreeRep::GetPlays(GameNode node) const { - Array foo(m_players.size()); - for (int i = 1; i <= foo.size(); i++) { - foo[i] = m_players[i]->NumInfosets(); - } - return foo; + const_cast(this)->BuildConsistentPlays(); + + const std::vector &consistent_plays = m_nodePlays.at(node); + std::vector consistent_plays_copy; + consistent_plays_copy.reserve(consistent_plays.size()); + + std::transform(consistent_plays.cbegin(), consistent_plays.cend(), + std::back_inserter(consistent_plays_copy), + [](GameNodeRep *rep_ptr) -> GameNode { return {rep_ptr}; }); + + return consistent_plays_copy; } -GameAction GameTreeRep::GetAction(int p_index) const +std::vector GameTreeRep::GetPlays(GameInfoset infoset) const { - int index = 1; - for (auto player : m_players) { - for (auto infoset : player->m_infosets) { - for (auto action : infoset->m_actions) { - if (index++ == p_index) { - return action; - } - } - } + std::vector plays; + + for (const auto &node : infoset->GetMembers()) { + std::vector member_plays = GetPlays(node); + plays.insert(plays.end(), member_plays.begin(), member_plays.end()); } - throw IndexException(); + return plays; } -//------------------------------------------------------------------------ -// GameTreeRep: Outcomes -//------------------------------------------------------------------------ +std::vector GameTreeRep::GetPlays(GameAction action) const +{ + std::vector plays; + + for (const auto &node : action->GetInfoset()->GetMembers()) { + std::vector child_plays = GetPlays(node->GetChild(action)); + plays.insert(plays.end(), child_plays.begin(), child_plays.end()); + } + return plays; +} void GameTreeRep::DeleteOutcome(const GameOutcome &p_outcome) { @@ -1056,30 +1179,11 @@ void GameTreeRep::DeleteOutcome(const GameOutcome &p_outcome) m_root->DeleteOutcome(p_outcome); m_outcomes.erase(std::find(m_outcomes.begin(), m_outcomes.end(), p_outcome)); p_outcome->Invalidate(); - int outc = 1; - for (auto &outcome : m_outcomes) { - outcome->m_number = outc++; - } + std::for_each(m_outcomes.begin(), m_outcomes.end(), + [outc = 1](GameOutcomeRep *c) mutable { c->m_number = outc++; }); ClearComputedValues(); } -//------------------------------------------------------------------------ -// GameTreeRep: Nodes -//------------------------------------------------------------------------ - -namespace { -int CountNodes(GameNode p_node) -{ - int num = 1; - for (int i = 1; i <= p_node->NumChildren(); num += CountNodes(p_node->GetChild(i++))) - ; - return num; -} - -} // end anonymous namespace - -int GameTreeRep::NumNodes() const { return CountNodes(m_root); } - //------------------------------------------------------------------------ // GameTreeRep: Modification //------------------------------------------------------------------------ @@ -1093,56 +1197,47 @@ Game GameTreeRep::SetChanceProbs(const GameInfoset &p_infoset, const ArrayNumActions() != static_cast(p_probs.size())) { + if (p_infoset->m_actions.size() != p_probs.size()) { throw DimensionException("The number of probabilities given must match the number of actions"); } IncrementVersion(); - Rational sum(0); - for (auto prob : p_probs) { - if (static_cast(prob) < Rational(0)) { - throw ValueException("Probabilities must be non-negative numbers"); - } - sum += static_cast(prob); + if (std::any_of(p_probs.begin(), p_probs.end(), + [](const Number &x) { return static_cast(x) < Rational(0); })) { + throw ValueException("Probabilities must be non-negative numbers"); } + auto sum = std::accumulate( + p_probs.begin(), p_probs.end(), Rational(0), + [](const Rational &r, const Number &n) { return r + static_cast(n); }); if (sum != Rational(1)) { throw ValueException("Probabilities must sum to exactly one"); } - for (int act = 1; act <= p_infoset->NumActions(); act++) { - dynamic_cast(*p_infoset).m_probs[act] = p_probs[act]; - } + std::copy(p_probs.begin(), p_probs.end(), p_infoset->m_probs.begin()); ClearComputedValues(); return this; } -Game GameTreeRep::NormalizeChanceProbs(const GameInfoset &m_infoset) +Game GameTreeRep::NormalizeChanceProbs(const GameInfoset &p_infoset) { - if (m_infoset->GetGame() != this) { + if (p_infoset->GetGame() != this) { throw MismatchException(); } - if (!m_infoset->IsChanceInfoset()) { + if (!p_infoset->IsChanceInfoset()) { throw UndefinedException( "Action probabilities can only be normalized for chance information sets"); } IncrementVersion(); - Rational sum(0); - for (int act = 1; act <= m_infoset->NumActions(); act++) { - Rational action_prob(m_infoset->GetActionProb(act)); - sum += action_prob; - } - Array m_probs(m_infoset->NumActions()); + auto &probs = p_infoset->m_probs; + auto sum = std::accumulate( + probs.begin(), probs.end(), Rational(0), + [](const Rational &s, const Number &n) { return s + static_cast(n); }); if (sum == Rational(0)) { // all remaining moves have prob zero; split prob 1 equally among them - for (int act = 1; act <= m_infoset->NumActions(); act++) { - m_probs[act] = Rational(1, m_infoset->NumActions()); - } + std::fill(probs.begin(), probs.end(), Rational(1, probs.size())); } else { - for (int act = 1; act <= m_infoset->NumActions(); act++) { - Rational prob(m_infoset->GetActionProb(act)); - m_probs[act] = prob / sum; - } + std::transform(probs.begin(), probs.end(), probs.begin(), + [&sum](const Number &n) { return static_cast(n) / sum; }); } - m_infoset->GetGame()->SetChanceProbs(m_infoset, m_probs); return this; } @@ -1191,14 +1286,16 @@ GameTreeRep::NewMixedStrategyProfile(const Rational &, const StrategySupportProf class TreePureStrategyProfileRep : public PureStrategyProfileRep { protected: - PureStrategyProfileRep *Copy() const override; + std::shared_ptr Copy() const override + { + return std::make_shared(*this); + } public: TreePureStrategyProfileRep(const Game &p_game) : PureStrategyProfileRep(p_game) {} - void SetStrategy(const GameStrategy &) override; GameOutcome GetOutcome() const override { throw UndefinedException(); } void SetOutcome(GameOutcome p_outcome) override { throw UndefinedException(); } - Rational GetPayoff(int pl) const override; + Rational GetPayoff(const GamePlayer &) const override; Rational GetStrategyValue(const GameStrategy &) const override; }; @@ -1206,45 +1303,36 @@ class TreePureStrategyProfileRep : public PureStrategyProfileRep { // TreePureStrategyProfileRep: Lifecycle //------------------------------------------------------------------------ -PureStrategyProfileRep *TreePureStrategyProfileRep::Copy() const -{ - return new TreePureStrategyProfileRep(*this); -} - PureStrategyProfile GameTreeRep::NewPureStrategyProfile() const { - return PureStrategyProfile(new TreePureStrategyProfileRep(const_cast(this))); + return PureStrategyProfile( + std::make_shared(const_cast(this))); } //------------------------------------------------------------------------ // TreePureStrategyProfileRep: Data access and manipulation //------------------------------------------------------------------------ -void TreePureStrategyProfileRep::SetStrategy(const GameStrategy &s) -{ - m_profile[s->GetPlayer()->GetNumber()] = s; -} - -Rational TreePureStrategyProfileRep::GetPayoff(int pl) const +Rational TreePureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const { PureBehaviorProfile behav(m_nfg); - for (int i = 1; i <= m_nfg->NumPlayers(); i++) { - GamePlayer player = m_nfg->GetPlayer(i); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - int act = m_profile[i]->m_behav[iset]; - if (act) { - behav.SetAction(player->GetInfoset(iset)->GetAction(act)); + for (const auto &player : m_nfg->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + try { + behav.SetAction(infoset->GetAction(m_profile.at(player)->m_behav[infoset])); + } + catch (std::out_of_range &) { } } } - return behav.GetPayoff(m_nfg->GetPlayer(pl)); + return behav.GetPayoff(p_player); } Rational TreePureStrategyProfileRep::GetStrategyValue(const GameStrategy &p_strategy) const { - PureStrategyProfile copy(Copy()); + const PureStrategyProfile copy(Copy()); copy->SetStrategy(p_strategy); - return copy->GetPayoff(p_strategy->GetPlayer()->GetNumber()); + return copy->GetPayoff(p_strategy->GetPlayer()); } } // end namespace Gambit diff --git a/src/games/gametree.h b/src/games/gametree.h index e6fbef826..03a567882 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -27,199 +27,41 @@ namespace Gambit { -class GameTreeRep; - -class GameTreeActionRep : public GameActionRep { - friend class GameTreeRep; - friend class GameTreeInfosetRep; - template friend class MixedBehaviorProfile; - -private: - int m_number; - std::string m_label; - GameTreeInfosetRep *m_infoset; - - GameTreeActionRep(int p_number, const std::string &p_label, GameTreeInfosetRep *p_infoset) - : m_number(p_number), m_label(p_label), m_infoset(p_infoset) - { - } - ~GameTreeActionRep() override = default; - -public: - int GetNumber() const override { return m_number; } - GameInfoset GetInfoset() const override; - - const std::string &GetLabel() const override { return m_label; } - void SetLabel(const std::string &p_label) override { m_label = p_label; } - - bool Precedes(const GameNode &) const override; - - void DeleteAction() override; -}; - -class GameTreeInfosetRep : public GameInfosetRep { - friend class GameTreeRep; - friend class GameTreeActionRep; - friend class GamePlayerRep; - friend class GameTreeNodeRep; - template friend class MixedBehaviorProfile; - -protected: - GameTreeRep *m_efg; - int m_number; - std::string m_label; - GamePlayerRep *m_player; - Array m_actions; - Array m_members; - int flag, whichbranch{0}; - Array m_probs; - - GameTreeInfosetRep(GameTreeRep *p_efg, int p_number, GamePlayerRep *p_player, int p_actions); - ~GameTreeInfosetRep() override; - - /// Adds the node to the information set - void AddMember(GameTreeNodeRep *p_node) { m_members.push_back(p_node); } - /// Removes the node from the information set, invalidating if emptied - void RemoveMember(GameTreeNodeRep *); - - void RemoveAction(int which); - -public: - Game GetGame() const override; - int GetNumber() const override { return m_number; } - - GamePlayer GetPlayer() const override; - void SetPlayer(GamePlayer p) override; - - bool IsChanceInfoset() const override; - - void SetLabel(const std::string &p_label) override { m_label = p_label; } - const std::string &GetLabel() const override { return m_label; } - - GameAction InsertAction(GameAction p_where = nullptr) override; - - /// @name Actions - //@{ - /// Returns the number of actions available at the information set - int NumActions() const override { return m_actions.size(); } - /// Returns the p_index'th action at the information set - GameAction GetAction(int p_index) const override { return m_actions[p_index]; } - /// Returns the actions available at the information set - Array GetActions() const override; - //@} - - int NumMembers() const override { return m_members.size(); } - GameNode GetMember(int p_index) const override; - Array GetMembers() const override; - - bool Precedes(GameNode) const override; - - const Number &GetActionProb(int i) const override { return m_probs[i]; } - const Number &GetActionProb(const GameAction &p_action) const override - { - if (p_action->GetInfoset() != GameInfoset(const_cast(this))) { - throw MismatchException(); - } - return m_probs[p_action->GetNumber()]; - } - void Reveal(GamePlayer) override; -}; - -class GameTreeNodeRep : public GameNodeRep { - friend class GameTreeRep; - friend class GameTreeActionRep; - friend class GameTreeInfosetRep; - friend class GamePlayerRep; - friend class PureBehaviorProfile; - template friend class MixedBehaviorProfile; - -protected: - int number; - GameTreeRep *m_efg; - std::string m_label; - GameTreeInfosetRep *infoset; - GameTreeNodeRep *m_parent; - GameOutcomeRep *outcome; - Array children; - GameTreeNodeRep *whichbranch{nullptr}, *ptr{nullptr}; - - GameTreeNodeRep(GameTreeRep *e, GameTreeNodeRep *p); - ~GameTreeNodeRep() override; - - void DeleteOutcome(GameOutcomeRep *outc); - void CopySubtree(GameTreeNodeRep *, GameTreeNodeRep *); - -public: - Game GetGame() const override; - - const std::string &GetLabel() const override { return m_label; } - void SetLabel(const std::string &p_label) override { m_label = p_label; } - - int GetNumber() const override { return number; } - int NumChildren() const override { return children.size(); } - GameNode GetChild(int i) const override { return children[i]; } - GameNode GetChild(const GameAction &p_action) const override - { - if (p_action->GetInfoset() != infoset) { - throw MismatchException(); - } - return children[p_action->GetNumber()]; - } - Array GetChildren() const override; - - GameInfoset GetInfoset() const override { return infoset; } - void SetInfoset(GameInfoset) override; - GameInfoset LeaveInfoset() override; - - bool IsTerminal() const override { return children.empty(); } - GamePlayer GetPlayer() const override { return (infoset) ? infoset->GetPlayer() : nullptr; } - GameAction GetPriorAction() const override; // returns null if root node - GameNode GetParent() const override { return m_parent; } - GameNode GetNextSibling() const override; - GameNode GetPriorSibling() const override; - - GameOutcome GetOutcome() const override { return outcome; } - void SetOutcome(const GameOutcome &p_outcome) override; - - bool IsSuccessorOf(GameNode from) const override; - bool IsSubgameRoot() const override; - - void DeleteParent() override; - void DeleteTree() override; - - void CopyTree(GameNode src) override; - void MoveTree(GameNode src) override; - - Game CopySubgame() const override; - - GameInfoset AppendMove(GamePlayer p_player, int p_actions) override; - GameInfoset AppendMove(GameInfoset p_infoset) override; - GameInfoset InsertMove(GamePlayer p_player, int p_actions) override; - GameInfoset InsertMove(GameInfoset p_infoset) override; -}; - class GameTreeRep : public GameExplicitRep { - friend class GameTreeNodeRep; - friend class GameTreeInfosetRep; - friend class GameTreeActionRep; + friend class GameNodeRep; + friend class GameInfosetRep; + friend class GameActionRep; + using GameRep::Nodes; protected: - mutable bool m_computedValues, m_doCanon; - GameTreeNodeRep *m_root; + mutable bool m_computedValues{false}, m_doCanon{true}; + GameNodeRep *m_root; GamePlayerRep *m_chance; + std::size_t m_numNodes = 1; + std::size_t m_numNonterminalNodes = 0; + std::map> m_nodePlays; + mutable std::map m_playerConsequences; /// @name Private auxiliary functions //@{ - void NumberNodes(GameTreeNodeRep *, int &); + void NumberNodes(GameNodeRep *, int &); /// Normalize the probability distribution of actions at a chance node Game NormalizeChanceProbs(const GameInfoset &); + /// Compute an auxiliary map relating player's actions with the infosets it directly entails + void BuildPlayerConsequences() const; //@} /// @name Managing the representation //@{ void Canonicalize(); - void BuildComputedValues() override; + void BuildComputedValues() const override; + void BuildConsistentPlays(); void ClearComputedValues() const; + + /// Removes the node from the information set, invalidating if emptied + void RemoveMember(GameInfosetRep *, GameNodeRep *); + + void CopySubtree(GameNodeRep *, GameNodeRep *, GameNodeRep *); //@} public: @@ -234,8 +76,7 @@ class GameTreeRep : public GameExplicitRep { //@{ bool IsTree() const override { return true; } bool IsConstSum() const override; - using GameRep::IsPerfectRecall; - bool IsPerfectRecall(GameInfoset &, GameInfoset &) const override; + bool IsPerfectRecall() const override; /// Turn on or off automatic canonicalization of the game void SetCanonicalization(bool p_doCanon) const { @@ -259,14 +100,16 @@ class GameTreeRep : public GameExplicitRep { /// Returns the root node of the game GameNode GetRoot() const override { return m_root; } /// Returns the number of nodes in the game - int NumNodes() const override; + size_t NumNodes() const override { return m_numNodes; } + /// Returns the number of non-terminal nodes in the game + size_t NumNonterminalNodes() const override { return m_numNonterminalNodes; } //@} void DeleteOutcome(const GameOutcome &) override; /// @name Writing data files //@{ - void WriteEfgFile(std::ostream &, const GameNode &p_node = 0) const override; + void WriteEfgFile(std::ostream &, const GameNode &p_node = nullptr) const override; void WriteNfgFile(std::ostream &) const override; //@} @@ -281,16 +124,33 @@ class GameTreeRep : public GameExplicitRep { /// Returns the iset'th information set in the game (numbered globally) GameInfoset GetInfoset(int iset) const override; /// Returns the set of information sets in the game - Array GetInfosets() const override; - /// Returns an array with the number of information sets per personal player - Array NumInfosets() const override; - /// Returns the act'th action in the game (numbered globally) - GameAction GetAction(int act) const override; + std::vector GetInfosets() const override; //@} /// @name Modification //@{ + GameInfoset AppendMove(GameNode p_node, GamePlayer p_player, int p_actions) override; + GameInfoset AppendMove(GameNode p_node, GameInfoset p_infoset) override; + GameInfoset InsertMove(GameNode p_node, GamePlayer p_player, int p_actions) override; + GameInfoset InsertMove(GameNode p_node, GameInfoset p_infoset) override; + void CopyTree(GameNode dest, GameNode src) override; + void MoveTree(GameNode dest, GameNode src) override; + void DeleteParent(GameNode) override; + void DeleteTree(GameNode) override; + void SetPlayer(GameInfoset, GamePlayer) override; + void Reveal(GameInfoset, GamePlayer) override; + void SetInfoset(GameNode, GameInfoset) override; + GameInfoset LeaveInfoset(GameNode) override; Game SetChanceProbs(const GameInfoset &, const Array &) override; + GameAction InsertAction(GameInfoset, GameAction p_where = nullptr) override; + void DeleteAction(GameAction) override; + void SetOutcome(GameNode, const GameOutcome &p_outcome) override; + + std::vector GetPlays(GameNode node) const override; + std::vector GetPlays(GameInfoset infoset) const override; + std::vector GetPlays(GameAction action) const override; + + Game CopySubgame(GameNode) const override; //@} PureStrategyProfile NewPureStrategyProfile() const override; @@ -300,6 +160,9 @@ class GameTreeRep : public GameExplicitRep { NewMixedStrategyProfile(double, const StrategySupportProfile &) const override; MixedStrategyProfile NewMixedStrategyProfile(const Rational &, const StrategySupportProfile &) const override; + +private: + std::vector BuildConsistentPlaysRecursiveImpl(GameNodeRep *node); }; template class TreeMixedStrategyProfileRep : public MixedStrategyProfileRep { @@ -316,10 +179,11 @@ template class TreeMixedStrategyProfileRep : public MixedStrategyProfi T GetPayoffDeriv(int pl, const GameStrategy &) const override; T GetPayoffDeriv(int pl, const GameStrategy &, const GameStrategy &) const override; - void InvalidateCache() const override; - -protected: +private: mutable std::shared_ptr> mixed_behav_profile_sptr; + + void MakeBehavior() const; + void InvalidateCache() const override; }; } // namespace Gambit diff --git a/src/games/nash.cc b/src/games/nash.cc index 0e7fc910d..8f61bb751 100644 --- a/src/games/nash.cc +++ b/src/games/nash.cc @@ -24,198 +24,6 @@ namespace Gambit::Nash { -template -void MixedStrategyCSVRenderer::Render(const MixedStrategyProfile &p_profile, - const std::string &p_label) const -{ - m_stream << p_label; - for (size_t i = 1; i <= p_profile.MixedProfileLength(); i++) { - m_stream << "," << lexical_cast(p_profile[i], m_numDecimals); - } - m_stream << std::endl; -} - -template -void MixedStrategyDetailRenderer::Render(const MixedStrategyProfile &p_profile, - const std::string &p_label) const -{ - for (auto player : p_profile.GetGame()->GetPlayers()) { - m_stream << "Strategy profile for player " << player->GetNumber() << ":\n"; - - m_stream << "Strategy Prob Value\n"; - m_stream << "-------- ----------- -----------\n"; - - for (auto strategy : player->GetStrategies()) { - if (!strategy->GetLabel().empty()) { - m_stream << std::setw(8) << strategy->GetLabel() << " "; - } - else { - m_stream << std::setw(8) << strategy->GetNumber() << " "; - } - m_stream << std::setw(10); - m_stream << lexical_cast(p_profile[strategy], m_numDecimals); - m_stream << " "; - m_stream << std::setw(11); - m_stream << lexical_cast(p_profile.GetPayoff(strategy), m_numDecimals); - m_stream << std::endl; - } - } -} - -template -void BehavStrategyCSVRenderer::Render(const MixedBehaviorProfile &p_profile, - const std::string &p_label) const -{ - m_stream << p_label; - for (size_t i = 1; i <= p_profile.BehaviorProfileLength(); i++) { - m_stream << "," << lexical_cast(p_profile[i], m_numDecimals); - } - m_stream << std::endl; -} - -template -void BehavStrategyDetailRenderer::Render(const MixedBehaviorProfile &p_profile, - const std::string &p_label) const -{ - for (auto player : p_profile.GetGame()->GetPlayers()) { - m_stream << "Behavior profile for player " << player->GetNumber() << ":\n"; - - m_stream << "Infoset Action Prob Value\n"; - m_stream << "------- ------- ----------- -----------\n"; - - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - GameInfoset infoset = player->GetInfoset(iset); - - for (int act = 1; act <= infoset->NumActions(); act++) { - GameAction action = infoset->GetAction(act); - - if (!infoset->GetLabel().empty()) { - m_stream << std::setw(7) << infoset->GetLabel() << " "; - } - else { - m_stream << std::setw(7) << infoset->GetNumber() << " "; - } - if (!action->GetLabel().empty()) { - m_stream << std::setw(7) << action->GetLabel() << " "; - } - else { - m_stream << std::setw(7) << action->GetNumber() << " "; - } - m_stream << std::setw(11); - m_stream << lexical_cast(p_profile[action], m_numDecimals); - m_stream << " "; - m_stream << std::setw(11); - m_stream << lexical_cast(p_profile.GetPayoff(infoset->GetAction(act)), - m_numDecimals); - m_stream << std::endl; - } - } - - m_stream << std::endl; - m_stream << "Infoset Node Belief Prob\n"; - m_stream << "------- ------- ----------- -----------\n"; - - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - GameInfoset infoset = player->GetInfoset(iset); - - for (int n = 1; n <= infoset->NumMembers(); n++) { - GameNode node = infoset->GetMember(n); - if (!infoset->GetLabel().empty()) { - m_stream << std::setw(7) << infoset->GetLabel() << " "; - } - else { - m_stream << std::setw(7) << infoset->GetNumber() << " "; - } - if (!node->GetLabel().empty()) { - m_stream << std::setw(7) << node->GetLabel() << " "; - } - else { - m_stream << std::setw(7) << node->GetNumber() << " "; - } - m_stream << std::setw(11); - m_stream << lexical_cast(p_profile.GetBeliefProb(infoset->GetMember(n)), - m_numDecimals); - m_stream << " "; - m_stream << std::setw(11); - m_stream << lexical_cast(p_profile.GetRealizProb(infoset->GetMember(n)), - m_numDecimals); - m_stream << std::endl; - } - } - m_stream << std::endl; - } -} - -template class MixedStrategyRenderer; -template class MixedStrategyRenderer; - -template class MixedStrategyNullRenderer; -template class MixedStrategyNullRenderer; - -template class MixedStrategyCSVRenderer; -template class MixedStrategyCSVRenderer; - -template class MixedStrategyDetailRenderer; -template class MixedStrategyDetailRenderer; - -template class StrategyProfileRenderer; -template class StrategyProfileRenderer; - -template class BehavStrategyNullRenderer; -template class BehavStrategyNullRenderer; - -template class BehavStrategyCSVRenderer; -template class BehavStrategyCSVRenderer; - -template class BehavStrategyDetailRenderer; -template class BehavStrategyDetailRenderer; - -template -StrategySolver::StrategySolver( - std::shared_ptr> p_onEquilibrium /* = 0 */) - : m_onEquilibrium(p_onEquilibrium) -{ - if (m_onEquilibrium.get() == nullptr) { - m_onEquilibrium.reset(new MixedStrategyNullRenderer()); - } -} - -template -BehavSolver::BehavSolver(std::shared_ptr> p_onEquilibrium /* = 0 */) - : m_onEquilibrium(p_onEquilibrium) -{ - if (m_onEquilibrium.get() == nullptr) { - m_onEquilibrium.reset(new BehavStrategyNullRenderer()); - } -} - -template -BehavViaStrategySolver::BehavViaStrategySolver( - std::shared_ptr> p_solver, - std::shared_ptr> p_onEquilibrium /* = 0 */) - : BehavSolver(p_onEquilibrium), m_solver(p_solver) -{ -} - -template -List> BehavViaStrategySolver::Solve(const Game &p_game) const -{ - List> output = m_solver->Solve(p_game); - List> solutions; - for (const auto &profile : output) { - solutions.push_back(MixedBehaviorProfile(profile)); - } - return solutions; -} - -template -SubgameBehavSolver::SubgameBehavSolver( - std::shared_ptr> p_solver, - std::shared_ptr> p_onEquilibrium /* = 0 */) - : BehavSolver(p_onEquilibrium), m_solver(p_solver) -{ -} - // A nested anonymous namespace to privatize these functions namespace { @@ -279,7 +87,7 @@ template class SubgameSolution { for (const auto &subplayer : p_profile.GetGame()->GetPlayers()) { for (const auto &subinfoset : subplayer->GetInfosets()) { - GameInfoset infoset = p_infosetMap.at(subinfoset->GetLabel()); + const GameInfoset infoset = p_infosetMap.at(subinfoset->GetLabel()); const auto &subactions = subinfoset->GetActions(); auto subaction = subactions.begin(); for (const auto &action : infoset->GetActions()) { @@ -289,13 +97,13 @@ template class SubgameSolution { } } - GameOutcome outcome = p_subroot->GetOutcome(); + const GameOutcome outcome = p_subroot->GetOutcome(); const auto &subplayers = p_profile.GetGame()->GetPlayers(); auto subplayer = subplayers.begin(); for (const auto &player : p_subroot->GetGame()->GetPlayers()) { T value = p_profile.GetPayoff(*subplayer); if (outcome) { - value += static_cast(outcome->GetPayoff(*subplayer)); + value += outcome->GetPayoff(*subplayer); } solution.node_values[p_subroot]->SetPayoff(player, Number(static_cast(value))); ++subplayer; @@ -305,14 +113,14 @@ template class SubgameSolution { }; template -std::list> -SubgameBehavSolver::SolveSubgames(const GameNode &p_root, - const std::map &p_infosetMap) const +std::list> SolveSubgames(const GameNode &p_root, + const std::map &p_infosetMap, + BehaviorSolverType p_solver) { std::list> subsolutions = {{{}, {}}}; for (const auto &subroot : ChildSubgames(p_root)) { std::list> combined_solutions; - for (const auto &solution : SolveSubgames(subroot, p_infosetMap)) { + for (const auto &solution : SolveSubgames(subroot, p_infosetMap, p_solver)) { for (const auto &subsolution : subsolutions) { combined_solutions.push_back(subsolution.Combine(solution)); } @@ -326,19 +134,19 @@ SubgameBehavSolver::SolveSubgames(const GameNode &p_root, std::list> solutions; for (auto subsolution : subsolutions) { for (auto [subroot, outcome] : subsolution.GetNodeValues()) { - subroot->SetOutcome(outcome); + subroot->GetGame()->SetOutcome(subroot, outcome); } // This prevents double-counting of outcomes at roots of subgames. // By convention, we will just put the payoffs in the parent subgame. - Game subgame = p_root->CopySubgame(); - subgame->GetRoot()->SetOutcome(nullptr); + const Game subgame = p_root->GetGame()->CopySubgame(p_root); + subgame->SetOutcome(subgame->GetRoot(), nullptr); - for (const auto &solution : m_solver->Solve(subgame)) { + for (const auto &solution : p_solver(subgame)) { solutions.push_back(subsolution.Update(p_root, solution, p_infosetMap)); } } - p_root->DeleteTree(); + p_root->GetGame()->DeleteTree(p_root); return solutions; } @@ -357,9 +165,10 @@ MixedBehaviorProfile BuildProfile(const Game &p_game, const SubgameSolution -List> SubgameBehavSolver::Solve(const Game &p_game) const +List> SolveBySubgames(const Game &p_game, BehaviorSolverType p_solver, + BehaviorCallbackType p_onEquilibrium) { - Game efg = p_game->GetRoot()->CopySubgame(); + const Game efg = p_game->CopySubgame(p_game->GetRoot()); int index = 1; std::map infoset_map; @@ -375,25 +184,20 @@ List> SubgameBehavSolver::Solve(const Game &p_game) c } } - auto results = SolveSubgames(efg->GetRoot(), infoset_map); + auto results = SolveSubgames(efg->GetRoot(), infoset_map, p_solver); List> solutions; for (const auto &result : results) { solutions.push_back(BuildProfile(p_game, result)); - this->m_onEquilibrium->Render(solutions.back()); + p_onEquilibrium(solutions.back(), "NE"); } return solutions; } -template class StrategySolver; -template class StrategySolver; - -template class BehavSolver; -template class BehavSolver; - -template class BehavViaStrategySolver; -template class BehavViaStrategySolver; - -template class SubgameBehavSolver; -template class SubgameBehavSolver; +template List> +SolveBySubgames(const Game &p_game, BehaviorSolverType p_solver, + BehaviorCallbackType p_onEquilibrium); +template List> +SolveBySubgames(const Game &p_game, BehaviorSolverType p_solver, + BehaviorCallbackType p_onEquilibrium); } // namespace Gambit::Nash diff --git a/src/games/nash.h b/src/games/nash.h index 04fec4c08..907cf5fe9 100644 --- a/src/games/nash.h +++ b/src/games/nash.h @@ -26,207 +26,42 @@ #include #include "gambit.h" -namespace Gambit { - -namespace Nash { +namespace Gambit::Nash { +template using StrategyCallbackType = - std::function &, const std::string &)>; + std::function &, const std::string &)>; /// @brief A fallback callback function for mixed strategy profiles that does nothing -inline void NullStrategyCallback(const MixedStrategyProfile &, const std::string &) {} +template void NullStrategyCallback(const MixedStrategyProfile &, const std::string &) +{ +} +template using BehaviorCallbackType = - std::function &, const std::string &)>; + std::function &, const std::string &)>; /// @brief A fallback callback function for mixed behavior profiles that does nothing -inline void NullBehaviorCallback(const MixedBehaviorProfile &, const std::string &) {} - -//======================================================================== -// Profile renderer classes -//======================================================================== - -template class StrategyProfileRenderer { -public: - virtual ~StrategyProfileRenderer() = default; - virtual void Render(const MixedStrategyProfile &p_profile, - const std::string &p_label = "NE") const = 0; - virtual void Render(const MixedBehaviorProfile &p_profile, - const std::string &p_label = "NE") const = 0; -}; - -// -// Encapsulates the rendering of a mixed strategy profile to various text formats. -// Implements automatic conversion of behavior strategy profiles to mixed -// strategy profiles. -// -template class MixedStrategyRenderer : public StrategyProfileRenderer { -public: - ~MixedStrategyRenderer() override = default; - void Render(const MixedStrategyProfile &p_profile, - const std::string &p_label = "NE") const override = 0; - void Render(const MixedBehaviorProfile &p_profile, - const std::string &p_label = "NE") const override - { - Render(p_profile.ToMixedProfile(), p_label); +template void NullBehaviorCallback(const MixedBehaviorProfile &, const std::string &) +{ +} + +template +List> ToMixedBehaviorProfile(const List> &p_list) +{ + List> ret; + for (const auto &profile : p_list) { + ret.push_back(MixedBehaviorProfile(profile)); } -}; + return ret; +} -template class MixedStrategyNullRenderer : public MixedStrategyRenderer { -public: - ~MixedStrategyNullRenderer() override = default; - void Render(const MixedStrategyProfile &p_profile, - const std::string &p_label = "NE") const override - { - } -}; +template +using BehaviorSolverType = std::function>(const Game &)>; -template class MixedStrategyCSVRenderer : public MixedStrategyRenderer { -public: - explicit MixedStrategyCSVRenderer(std::ostream &p_stream, int p_numDecimals = 6) - : m_stream(p_stream), m_numDecimals(p_numDecimals) - { - } - ~MixedStrategyCSVRenderer() override = default; - void Render(const MixedStrategyProfile &p_profile, - const std::string &p_label = "NE") const override; - -private: - std::ostream &m_stream; - int m_numDecimals; -}; - -template class MixedStrategyDetailRenderer : public MixedStrategyRenderer { -public: - explicit MixedStrategyDetailRenderer(std::ostream &p_stream, int p_numDecimals = 6) - : m_stream(p_stream), m_numDecimals(p_numDecimals) - { - } - ~MixedStrategyDetailRenderer() override = default; - void Render(const MixedStrategyProfile &p_profile, - const std::string &p_label = "NE") const override; - -private: - std::ostream &m_stream; - int m_numDecimals; -}; - -// -// Encapsulates the rendering of a behavior profile to various text formats. -// -template class BehavStrategyRenderer : public StrategyProfileRenderer { -public: - ~BehavStrategyRenderer() override = default; - void Render(const MixedStrategyProfile &p_profile, - const std::string &p_label = "NE") const override - { - Render(MixedBehaviorProfile(p_profile), p_label); - } - void Render(const MixedBehaviorProfile &p_profile, - const std::string &p_label = "NE") const override = 0; -}; - -template class BehavStrategyNullRenderer : public BehavStrategyRenderer { -public: - ~BehavStrategyNullRenderer() override = default; - void Render(const MixedBehaviorProfile &p_profile, - const std::string &p_label = "NE") const override - { - } -}; - -template class BehavStrategyCSVRenderer : public BehavStrategyRenderer { -public: - explicit BehavStrategyCSVRenderer(std::ostream &p_stream, int p_numDecimals = 6) - : m_stream(p_stream), m_numDecimals(p_numDecimals) - { - } - ~BehavStrategyCSVRenderer() override = default; - void Render(const MixedBehaviorProfile &p_profile, - const std::string &p_label = "NE") const override; - -private: - std::ostream &m_stream; - int m_numDecimals; -}; - -template class BehavStrategyDetailRenderer : public BehavStrategyRenderer { -public: - explicit BehavStrategyDetailRenderer(std::ostream &p_stream, int p_numDecimals = 6) - : m_stream(p_stream), m_numDecimals(p_numDecimals) - { - } - ~BehavStrategyDetailRenderer() override = default; - void Render(const MixedBehaviorProfile &p_profile, - const std::string &p_label = "NE") const override; - -private: - std::ostream &m_stream; - int m_numDecimals; -}; - -//------------------------------------------------------------------------ -// Algorithm base classes -//------------------------------------------------------------------------ - -// Encapsulation of algorithms via the strategy pattern. - -template class StrategySolver { -public: - explicit StrategySolver(std::shared_ptr> p_onEquilibrium = nullptr); - virtual ~StrategySolver() = default; - - virtual List> Solve(const Game &) const = 0; - -protected: - std::shared_ptr> m_onEquilibrium; -}; - -template class BehavSolver { -public: - explicit BehavSolver(std::shared_ptr> p_onEquilibrium = nullptr); - virtual ~BehavSolver() = default; - - virtual List> Solve(const Game &) const = 0; - -protected: - std::shared_ptr> m_onEquilibrium; -}; - -// -// This is an adaptor class which allows a client expecting behavior profiles -// to call a solver which works in terms of strategy profiles -// -template class BehavViaStrategySolver : public BehavSolver { -public: - explicit BehavViaStrategySolver(std::shared_ptr> p_solver, - std::shared_ptr> p_onEquilibrium = 0); - ~BehavViaStrategySolver() override = default; - - List> Solve(const Game &) const override; - -protected: - std::shared_ptr> m_solver; -}; - -template class SubgameSolution; - -template class SubgameBehavSolver : public BehavSolver { -public: - explicit SubgameBehavSolver( - std::shared_ptr> p_solver, - std::shared_ptr> p_onEquilibrium = nullptr); - ~SubgameBehavSolver() override = default; - - List> Solve(const Game &) const override; - -protected: - std::shared_ptr> m_solver; - -private: - std::list> SolveSubgames(const GameNode &, - const std::map &) const; -}; +template +List> SolveBySubgames(const Game &, BehaviorSolverType p_solver, + BehaviorCallbackType p_onEquilibrium); // // Exception raised when maximum number of equilibria to compute @@ -239,8 +74,6 @@ class EquilibriumLimitReached : public Exception { const char *what() const noexcept override { return "Reached target number of equilibria"; } }; -} // namespace Nash - -} // namespace Gambit +} // namespace Gambit::Nash #endif // LIBGAMBIT_NASH_H diff --git a/src/games/stratmixed.h b/src/games/stratmixed.h index 09e39288e..12a9a4a23 100644 --- a/src/games/stratmixed.h +++ b/src/games/stratmixed.h @@ -50,6 +50,14 @@ template class MixedStrategyProfileRep { } /// Returns the probability the strategy is played T &operator[](const GameStrategy &p_strategy) { return m_probs[m_profileIndex.at(p_strategy)]; } + /// Set the strategy of the corresponding player to a pure strategy + void SetStrategy(const GameStrategy &p_strategy) + { + for (const auto &s : m_support.GetStrategies(p_strategy->GetPlayer())) { + (*this)[s] = static_cast(0); + } + (*this)[p_strategy] = static_cast(1); + } virtual T GetPayoff(int pl) const = 0; virtual T GetPayoffDeriv(int pl, const GameStrategy &) const = 0; @@ -65,7 +73,7 @@ template class MixedStrategyProfileRep { T GetRegret(const GamePlayer &) const; T GetMaxRegret() const; - virtual void InvalidateCache() const {}; + virtual void InvalidateCache() const {} }; /// \brief A probability distribution over strategies in a game @@ -75,14 +83,9 @@ template class MixedStrategyProfileRep { /// probabilities. template class MixedStrategyProfile { private: - MixedStrategyProfileRep *m_rep; - -public: - /// @name Lifecycle - //@{ - explicit MixedStrategyProfile(MixedStrategyProfileRep *p_rep) : m_rep(p_rep) {} - /// Convert a behavior strategy profile to a mixed strategy profile - explicit MixedStrategyProfile(const MixedBehaviorProfile &); + std::unique_ptr> m_rep; + mutable std::map> map_strategy_payoffs; + mutable std::map map_profile_payoffs; /// Check underlying game has not changed; raise exception if it has void CheckVersion() const @@ -91,14 +94,29 @@ template class MixedStrategyProfile { throw GameStructureChangedException(); } } + /// Used to read payoffs from cache or compute them and cache them if needed + void ComputePayoffs() const; + + /// Reset cache for payoffs and strategy values + void InvalidateCache() const + { + map_strategy_payoffs.clear(); + map_profile_payoffs.clear(); + m_rep->InvalidateCache(); + } public: /// @name Lifecycle //@{ + explicit MixedStrategyProfile(MixedStrategyProfileRep *p_rep) : m_rep(p_rep) {} + /// Convert a behavior strategy profile to a mixed strategy profile + explicit MixedStrategyProfile(const MixedBehaviorProfile &); /// Make a copy of the mixed strategy profile - MixedStrategyProfile(const MixedStrategyProfile &); + MixedStrategyProfile(const MixedStrategyProfile &p_profile) : m_rep(p_profile.m_rep->Copy()) + { + } /// Destructor - virtual ~MixedStrategyProfile(); + ~MixedStrategyProfile() = default; MixedStrategyProfile &operator=(const MixedStrategyProfile &); MixedStrategyProfile &operator=(const Vector &v) @@ -209,17 +227,6 @@ template class MixedStrategyProfile { /// @name Computation of interesting quantities //@{ - /// Used to read payoffs from cache or compute them and cache them if needed - void ComputePayoffs() const; - mutable std::map> map_strategy_payoffs; - mutable std::map map_profile_payoffs; - /// Reset cache for payoffs and strategy values - virtual void InvalidateCache() const - { - map_strategy_payoffs.clear(); - map_profile_payoffs.clear(); - m_rep->InvalidateCache(); - } /// Computes the payoff of the profile to player 'pl' T GetPayoff(int pl) const @@ -313,7 +320,7 @@ template MixedStrategyProfile GameRep::NewRandomStrategyProfile(Generator &generator) const { auto profile = NewMixedStrategyProfile(0.0); - std::exponential_distribution<> dist(1); + std::exponential_distribution<> dist(1); // NOLINT(misc-const-correctness) for (auto player : GetPlayers()) { for (auto strategy : player->GetStrategies()) { profile[strategy] = dist(generator); @@ -328,7 +335,8 @@ MixedStrategyProfile GameRep::NewRandomStrategyProfile(int p_denom, { auto profile = NewMixedStrategyProfile(Rational(0)); for (auto player : GetPlayers()) { - std::list dist = UniformOnSimplex(p_denom, player->NumStrategies(), generator); + const std::list dist = + UniformOnSimplex(p_denom, player->GetStrategies().size(), generator); auto prob = dist.cbegin(); for (auto strategy : player->GetStrategies()) { profile[strategy] = *prob; diff --git a/src/games/stratpure.cc b/src/games/stratpure.cc index a04ce3815..c0f2b9a36 100644 --- a/src/games/stratpure.cc +++ b/src/games/stratpure.cc @@ -29,76 +29,19 @@ namespace Gambit { // class PureStrategyProfileRep //======================================================================== -PureStrategyProfileRep::PureStrategyProfileRep(const Game &p_game) - : m_nfg(p_game), m_profile(p_game->NumPlayers()) +PureStrategyProfileRep::PureStrategyProfileRep(const Game &p_game) : m_nfg(p_game) { - for (int pl = 1; pl <= m_nfg->NumPlayers(); pl++) { - m_profile[pl] = m_nfg->GetPlayer(pl)->GetStrategy(1); + for (const auto &player : m_nfg->GetPlayers()) { + m_profile[player] = player->GetStrategies().front(); } } -bool PureStrategyProfileRep::IsNash() const -{ - for (auto player : m_nfg->GetPlayers()) { - Rational current = GetPayoff(player); - for (auto strategy : player->GetStrategies()) { - if (GetStrategyValue(strategy) > current) { - return false; - } - } - } - return true; -} - -bool PureStrategyProfileRep::IsStrictNash() const -{ - for (auto player : m_nfg->GetPlayers()) { - Rational current = GetPayoff(player); - for (auto strategy : player->GetStrategies()) { - if (GetStrategyValue(strategy) >= current) { - return false; - } - } - } - return true; -} - -bool PureStrategyProfileRep::IsBestResponse(const GamePlayer &p_player) const -{ - Rational current = GetPayoff(p_player); - for (auto strategy : p_player->GetStrategies()) { - if (GetStrategyValue(strategy) > current) { - return false; - } - } - return true; -} - -List PureStrategyProfileRep::GetBestResponse(const GamePlayer &p_player) const -{ - auto strategy = p_player->GetStrategy(1); - Rational max_payoff = GetStrategyValue(strategy); - List br; - br.push_back(strategy); - for (auto strategy : p_player->GetStrategies()) { - Rational this_payoff = GetStrategyValue(strategy); - if (this_payoff > max_payoff) { - br.clear(); - max_payoff = this_payoff; - } - if (this_payoff >= max_payoff) { - br.push_back(strategy); - } - } - return br; -} - MixedStrategyProfile PureStrategyProfileRep::ToMixedStrategyProfile() const { - MixedStrategyProfile temp(m_nfg->NewMixedStrategyProfile(Rational(0))); + auto temp = m_nfg->NewMixedStrategyProfile(Rational(0)); temp = Rational(0); - for (int pl = 1; pl <= m_nfg->NumPlayers(); pl++) { - temp[GetStrategy(m_nfg->GetPlayer(pl))] = Rational(1); + for (auto [player, strategy] : m_profile) { + temp[strategy] = Rational(1); } return temp; } diff --git a/src/games/stratpure.h b/src/games/stratpure.h index a9a40c6a2..04a694d3a 100644 --- a/src/games/stratpure.h +++ b/src/games/stratpure.h @@ -23,6 +23,8 @@ #ifndef GAMBIT_GAMES_STRATPURE_H #define GAMBIT_GAMES_STRATPURE_H +#include + #include "game.h" namespace Gambit { @@ -31,44 +33,36 @@ namespace Gambit { /// It specifies exactly one strategy for each player defined on the /// game. class PureStrategyProfileRep { - friend class GameTableRep; - - friend class GameTreeRep; - - friend class GameAGGRep; - friend class PureStrategyProfile; protected: Game m_nfg; - Array m_profile; + std::map m_profile; /// Construct a new strategy profile explicit PureStrategyProfileRep(const Game &p_game); - /// Create a copy of the strategy profile. - /// Caller is responsible for memory management of the created object. - virtual PureStrategyProfileRep *Copy() const = 0; - public: virtual ~PureStrategyProfileRep() = default; + /// Create a copy of the strategy profile. + virtual std::shared_ptr Copy() const = 0; + /// @name Data access and manipulation //@{ - /// Get the index uniquely identifying the strategy profile - virtual long GetIndex() const { throw UndefinedException(); } - - /// Get the strategy played by player pl - const GameStrategy &GetStrategy(int pl) const { return m_profile[pl]; } + const Game &GetGame() const { return m_nfg; } /// Get the strategy played by the player const GameStrategy &GetStrategy(const GamePlayer &p_player) const { - return m_profile[p_player->GetNumber()]; + return m_profile.at(p_player); } /// Set the strategy for a player - virtual void SetStrategy(const GameStrategy &) = 0; + virtual void SetStrategy(const GameStrategy &p_strategy) + { + m_profile[p_strategy->GetPlayer()] = p_strategy; + } /// Get the outcome that results from the profile virtual GameOutcome GetOutcome() const = 0; @@ -76,27 +70,12 @@ class PureStrategyProfileRep { /// Set the outcome that results from the profile virtual void SetOutcome(GameOutcome p_outcome) = 0; - /// Get the payoff to player pl that results from the profile - virtual Rational GetPayoff(int pl) const = 0; - /// Get the payoff to the player resulting from the profile - Rational GetPayoff(const GamePlayer &p_player) const { return GetPayoff(p_player->GetNumber()); } + virtual Rational GetPayoff(const GamePlayer &p_player) const = 0; /// Get the value of playing strategy against the profile virtual Rational GetStrategyValue(const GameStrategy &) const = 0; - /// Is the profile a pure strategy Nash equilibrium? - bool IsNash() const; - - /// Is the profile a strict pure stategy Nash equilibrium? - bool IsStrictNash() const; - - /// Is the specificed player playing a best response? - bool IsBestResponse(const GamePlayer &p_player) const; - - /// Get the list of best response strategies for a player - List GetBestResponse(const GamePlayer &p_player) const; - /// Convert to a mixed strategy representation MixedStrategyProfile ToMixedStrategyProfile() const; //@} @@ -104,27 +83,24 @@ class PureStrategyProfileRep { class PureStrategyProfile { private: - PureStrategyProfileRep *rep; + std::shared_ptr rep; public: PureStrategyProfile(const PureStrategyProfile &r) : rep(r.rep->Copy()) {} - explicit PureStrategyProfile(PureStrategyProfileRep *p_rep) : rep(p_rep) {} + explicit PureStrategyProfile(std::shared_ptr p_rep) : rep(p_rep) {} - ~PureStrategyProfile() { delete rep; } + ~PureStrategyProfile() = default; PureStrategyProfile &operator=(const PureStrategyProfile &r) { if (&r != this) { - delete rep; rep = r.rep->Copy(); } return *this; } - PureStrategyProfileRep *operator->() const { return rep; } - - explicit operator PureStrategyProfileRep *() const { return rep; } + std::shared_ptr operator->() const { return rep; } }; class StrategyContingencies { diff --git a/src/games/stratspt.cc b/src/games/stratspt.cc index c23ba97bf..f4fff6f20 100644 --- a/src/games/stratspt.cc +++ b/src/games/stratspt.cc @@ -146,8 +146,8 @@ bool StrategySupportProfile::Dominates(const GameStrategy &s, const GameStrategy bool equal = true; for (auto iter : StrategyContingencies(*this)) { - Rational ap = iter->GetStrategyValue(s); - Rational bp = iter->GetStrategyValue(t); + const Rational ap = iter->GetStrategyValue(s); + const Rational bp = iter->GetStrategyValue(t); if (p_strict && ap <= bp) { return false; } @@ -238,7 +238,7 @@ bool UndominatedForPlayer(const StrategySupportProfile &p_support, StrategySupportProfile &p_newSupport, const GamePlayer &p_player, bool p_strict, bool p_external) { - std::vector set((p_external) ? p_player->NumStrategies() + std::vector set((p_external) ? p_player->GetStrategies().size() : p_support.GetStrategies(p_player).size()); if (p_external) { auto strategies = p_player->GetStrategies(); diff --git a/src/games/stratspt.h b/src/games/stratspt.h index 038dda42d..7258d098d 100644 --- a/src/games/stratspt.h +++ b/src/games/stratspt.h @@ -102,9 +102,9 @@ class StrategySupportProfile { /// Returns the number of players in the game int NumPlayers() const { return m_nfg->NumPlayers(); } /// Returns the set of players in the game - Array GetPlayers() const { return m_nfg->GetPlayers(); } + GameRep::Players GetPlayers() const { return m_nfg->GetPlayers(); } /// Returns the set of strategies in the support for a player - Support GetStrategies(const GamePlayer &p_player) const { return Support(this, p_player); } + Support GetStrategies(const GamePlayer &p_player) const { return {this, p_player}; } /// Returns true exactly when the strategy is in the support. bool Contains(const GameStrategy &s) const { return contains(m_support.at(s->GetPlayer()), s); } diff --git a/src/games/writer.cc b/src/games/writer.cc index a58eac236..a3c4c2fe4 100644 --- a/src/games/writer.cc +++ b/src/games/writer.cc @@ -51,35 +51,35 @@ std::string WriteHTMLFile(const Game &p_game, const GamePlayer &p_rowPlayer, theHtml += ""; theHtml += ""; theHtml += ""; - for (int st = 1; st <= p_colPlayer->NumStrategies(); st++) { + for (const auto &strategy : p_colPlayer->GetStrategies()) { theHtml += ""; } theHtml += ""; - for (int st1 = 1; st1 <= p_rowPlayer->NumStrategies(); st1++) { - PureStrategyProfile profile = iter; - profile->SetStrategy(p_rowPlayer->GetStrategy(st1)); + for (const auto &row_strategy : p_rowPlayer->GetStrategies()) { + const PureStrategyProfile profile = iter; + profile->SetStrategy(row_strategy); theHtml += ""; theHtml += ""; - for (int st2 = 1; st2 <= p_colPlayer->NumStrategies(); st2++) { - profile->SetStrategy(p_colPlayer->GetStrategy(st2)); + for (const auto &col_strategy : p_colPlayer->GetStrategies()) { + profile->SetStrategy(col_strategy); theHtml += "
"; - theHtml += p_colPlayer->GetStrategy(st)->GetLabel(); + theHtml += strategy->GetLabel(); theHtml += "
"; - theHtml += p_rowPlayer->GetStrategy(st1)->GetLabel(); + theHtml += row_strategy->GetLabel(); theHtml += ""; - for (int pl = 1; pl <= p_game->NumPlayers(); pl++) { + for (const auto &player : p_game->GetPlayers()) { try { if (profile->GetOutcome()) { - theHtml += static_cast(profile->GetOutcome()->GetPayoff(pl)); + theHtml += profile->GetOutcome()->GetPayoff(player); } else { theHtml += "0"; } } catch (UndefinedException &) { - theHtml += lexical_cast(profile->GetPayoff(pl)); + theHtml += lexical_cast(profile->GetPayoff(player)); } - if (pl < p_game->NumPlayers()) { + if (player != p_game->GetPlayers().back()) { theHtml += ","; } } @@ -102,9 +102,9 @@ std::string WriteLaTeXFile(const Game &p_game, const GamePlayer &p_rowPlayer, for (auto iter : StrategyContingencies( p_game, {p_rowPlayer->GetStrategies().front(), p_colPlayer->GetStrategies().front()})) { theHtml += "\\begin{game}{"; - theHtml += lexical_cast(p_rowPlayer->NumStrategies()); + theHtml += std::to_string(p_rowPlayer->GetStrategies().size()); theHtml += "}{"; - theHtml += lexical_cast(p_colPlayer->NumStrategies()); + theHtml += std::to_string(p_colPlayer->GetStrategies().size()); theHtml += "}["; theHtml += p_rowPlayer->GetLabel(); theHtml += "]["; @@ -129,44 +129,44 @@ std::string WriteLaTeXFile(const Game &p_game, const GamePlayer &p_rowPlayer, theHtml += "\n&"; - for (int st = 1; st <= p_colPlayer->NumStrategies(); st++) { - theHtml += p_colPlayer->GetStrategy(st)->GetLabel(); - if (st < p_colPlayer->NumStrategies()) { + for (const auto &strategy : p_colPlayer->GetStrategies()) { + theHtml += strategy->GetLabel(); + if (strategy != p_colPlayer->GetStrategies().back()) { theHtml += " & "; } } theHtml += "\\\\\n"; - for (int st1 = 1; st1 <= p_rowPlayer->NumStrategies(); st1++) { - PureStrategyProfile profile = iter; - profile->SetStrategy(p_rowPlayer->GetStrategy(st1)); - theHtml += p_rowPlayer->GetStrategy(st1)->GetLabel(); + for (const auto &row_strategy : p_rowPlayer->GetStrategies()) { + const PureStrategyProfile profile = iter; + profile->SetStrategy(row_strategy); + theHtml += row_strategy->GetLabel(); theHtml += " & "; - for (int st2 = 1; st2 <= p_colPlayer->NumStrategies(); st2++) { - profile->SetStrategy(p_colPlayer->GetStrategy(st2)); + for (const auto &col_strategy : p_colPlayer->GetStrategies()) { + profile->SetStrategy(col_strategy); theHtml += " $"; - for (int pl = 1; pl <= p_game->NumPlayers(); pl++) { + for (const auto &player : p_game->GetPlayers()) { try { if (profile->GetOutcome()) { - theHtml += static_cast(profile->GetOutcome()->GetPayoff(pl)); + theHtml += profile->GetOutcome()->GetPayoff(player); } else { theHtml += "0"; } } catch (UndefinedException &) { - theHtml += lexical_cast(profile->GetPayoff(pl)); + theHtml += lexical_cast(profile->GetPayoff(player)); } - if (pl < p_game->NumPlayers()) { + if (player != p_game->GetPlayers().back()) { theHtml += ","; } } theHtml += "$ "; - if (st2 < p_colPlayer->NumStrategies()) { + if (col_strategy != p_colPlayer->GetStrategies().back()) { theHtml += " & "; } } - if (st1 < p_colPlayer->NumStrategies()) { + if (row_strategy != p_rowPlayer->GetStrategies().back()) { theHtml += "\\\\\n"; } } diff --git a/src/gui/analysis.cc b/src/gui/analysis.cc index e232c46a2..6d86a7b3c 100644 --- a/src/gui/analysis.cc +++ b/src/gui/analysis.cc @@ -195,14 +195,14 @@ template void gbtAnalysisProfileList::Load(TiXmlNode *p_analysis) node = node->NextSiblingElement()) { const char *type = node->ToElement()->Attribute("type"); if (!strcmp(type, "behav")) { - MixedBehaviorProfile profile = + const MixedBehaviorProfile profile = TextToBehavProfile(m_doc, wxString(node->FirstChild()->Value(), *wxConvCurrent)); m_behavProfiles.push_back(std::make_shared>(profile)); m_isBehav = true; m_current = m_behavProfiles.size(); } else { - MixedStrategyProfile profile = + const MixedStrategyProfile profile = TextToMixedProfile(m_doc, wxString(node->FirstChild()->Value(), *wxConvCurrent)); m_mixedProfiles.push_back(std::make_shared>(profile)); m_isBehav = false; @@ -213,7 +213,7 @@ template void gbtAnalysisProfileList::Load(TiXmlNode *p_analysis) template std::string gbtAnalysisProfileList::GetPayoff(int pl, int p_index) const { - int index = (p_index == -1) ? m_current : p_index; + const int index = (p_index == -1) ? m_current : p_index; try { if (m_doc->IsTree()) { @@ -233,7 +233,7 @@ template std::string gbtAnalysisProfileList::GetPayoff(int pl, int template std::string gbtAnalysisProfileList::GetRealizProb(const GameNode &p_node, int p_index) const { - int index = (p_index == -1) ? m_current : p_index; + const int index = (p_index == -1) ? m_current : p_index; try { return lexical_cast(m_behavProfiles[index]->GetRealizProb(p_node), @@ -247,7 +247,7 @@ std::string gbtAnalysisProfileList::GetRealizProb(const GameNode &p_node, int template std::string gbtAnalysisProfileList::GetBeliefProb(const GameNode &p_node, int p_index) const { - int index = (p_index == -1) ? m_current : p_index; + const int index = (p_index == -1) ? m_current : p_index; if (!p_node->GetPlayer()) { return ""; @@ -272,7 +272,7 @@ template std::string gbtAnalysisProfileList::GetNodeValue(const GameNode &p_node, int p_player, int p_index) const { - int index = (p_index == -1) ? m_current : p_index; + const int index = (p_index == -1) ? m_current : p_index; try { return lexical_cast(m_behavProfiles[index]->GetPayoff(p_node)[p_player], @@ -286,7 +286,7 @@ std::string gbtAnalysisProfileList::GetNodeValue(const GameNode &p_node, int template std::string gbtAnalysisProfileList::GetInfosetProb(const GameNode &p_node, int p_index) const { - int index = (p_index == -1) ? m_current : p_index; + const int index = (p_index == -1) ? m_current : p_index; if (!p_node->GetPlayer()) { return ""; @@ -304,7 +304,7 @@ std::string gbtAnalysisProfileList::GetInfosetProb(const GameNode &p_node, in template std::string gbtAnalysisProfileList::GetInfosetValue(const GameNode &p_node, int p_index) const { - int index = (p_index == -1) ? m_current : p_index; + const int index = (p_index == -1) ? m_current : p_index; if (!p_node->GetPlayer() || p_node->GetPlayer()->IsChance()) { return ""; @@ -329,11 +329,11 @@ template std::string gbtAnalysisProfileList::GetActionProb(const GameNode &p_node, int p_act, int p_index) const { - int index = (p_index == -1) ? m_current : p_index; + const int index = (p_index == -1) ? m_current : p_index; if (p_node->GetPlayer() && p_node->GetPlayer()->IsChance()) { - GameInfoset infoset = p_node->GetInfoset(); - return static_cast(infoset->GetActionProb(p_act)); + const GameInfoset infoset = p_node->GetInfoset(); + return static_cast(infoset->GetActionProb(infoset->GetAction(p_act))); } if (!p_node->GetPlayer()) { @@ -358,12 +358,12 @@ std::string gbtAnalysisProfileList::GetActionProb(const GameNode &p_node, int template std::string gbtAnalysisProfileList::GetActionProb(int p_action, int p_index) const { - int index = (p_index == -1) ? m_current : p_index; + const int index = (p_index == -1) ? m_current : p_index; try { const MixedBehaviorProfile &profile = *m_behavProfiles[index]; - if (!profile.IsDefinedAt(profile.GetGame()->GetAction(p_action)->GetInfoset())) { + if (!profile.IsDefinedAt(m_doc->GetAction(p_action)->GetInfoset())) { return "*"; } @@ -378,7 +378,7 @@ template std::string gbtAnalysisProfileList::GetActionValue(const GameNode &p_node, int p_act, int p_index) const { - int index = (p_index == -1) ? m_current : p_index; + const int index = (p_index == -1) ? m_current : p_index; if (!p_node->GetPlayer() || p_node->GetPlayer()->IsChance()) { return ""; @@ -403,7 +403,7 @@ std::string gbtAnalysisProfileList::GetActionValue(const GameNode &p_node, in template std::string gbtAnalysisProfileList::GetStrategyProb(int p_strategy, int p_index) const { - int index = (p_index == -1) ? m_current : p_index; + const int index = (p_index == -1) ? m_current : p_index; try { const MixedStrategyProfile &profile = *m_mixedProfiles[index]; @@ -417,11 +417,11 @@ std::string gbtAnalysisProfileList::GetStrategyProb(int p_strategy, int p_ind template std::string gbtAnalysisProfileList::GetStrategyValue(int p_strategy, int p_index) const { - int index = (p_index == -1) ? m_current : p_index; + const int index = (p_index == -1) ? m_current : p_index; try { const MixedStrategyProfile &profile = *m_mixedProfiles[index]; - GameStrategy strategy = profile.GetGame()->GetStrategy(p_strategy); + const GameStrategy strategy = profile.GetGame()->GetStrategy(p_strategy); return lexical_cast(profile.GetPayoff(strategy), m_doc->GetStyle().NumDecimals()); } catch (IndexException &) { diff --git a/src/gui/app.cc b/src/gui/app.cc index 06f3970cd..43eed1d8b 100644 --- a/src/gui/app.cc +++ b/src/gui/app.cc @@ -47,7 +47,7 @@ bool gbtApplication::OnInit() // m_fileHistory.Save(config); wxConfigBase::Get()->Read(_T("/General/CurrentDirectory"), &m_currentDir, _T("")); - wxBitmap bitmap(gambitbig_xpm); + const wxBitmap bitmap(gambitbig_xpm); /*wxSplashScreen *splash =*/ new wxSplashScreen(bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 2000, nullptr, -1, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER | wxSTAY_ON_TOP); @@ -55,7 +55,7 @@ bool gbtApplication::OnInit() // Process command line arguments, if any. for (int i = 1; i < wxApp::argc; i++) { - gbtAppLoadResult result = LoadFile(wxApp::argv[i]); + const gbtAppLoadResult result = LoadFile(wxApp::argv[i]); if (result == GBT_APP_OPEN_FAILED) { wxMessageDialog dialog( nullptr, wxT("Gambit could not open file '") + wxApp::argv[i] + wxT("' for reading."), @@ -74,7 +74,7 @@ bool gbtApplication::OnInit() // If we don't have any game files -- whether because none were // specified on the command line, or because those specified couldn't // be read -- create a default document. - Gambit::Game efg = Gambit::NewTree(); + const Gambit::Game efg = Gambit::NewTree(); efg->NewPlayer()->SetLabel("Player 1"); efg->NewPlayer()->SetLabel("Player 2"); efg->SetTitle("Untitled Extensive Game"); @@ -109,7 +109,7 @@ gbtAppLoadResult gbtApplication::LoadFile(const wxString &p_filename) } try { - Gambit::Game nfg = Gambit::ReadGame(infile); + const Gambit::Game nfg = Gambit::ReadGame(infile); m_fileHistory.AddFileToHistory(p_filename); m_fileHistory.Save(*wxConfigBase::Get()); @@ -131,12 +131,8 @@ void gbtApplication::SetCurrentDir(const wxString &p_dir) bool gbtApplication::AreDocumentsModified() const { - for (int i = 1; i <= m_documents.size(); i++) { - if (m_documents[i]->IsModified()) { - return true; - } - } - return false; + return std::any_of(m_documents.begin(), m_documents.end(), + std::mem_fn(&gbtGameDocument::IsModified)); } IMPLEMENT_APP(gbtApplication) diff --git a/src/gui/app.h b/src/gui/app.h index 0c0ddc2b9..4ab380b81 100644 --- a/src/gui/app.h +++ b/src/gui/app.h @@ -20,8 +20,8 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#ifndef GAMBIT_H -#define GAMBIT_H +#ifndef GAMBIT_APP_H +#define GAMBIT_APP_H #include #include // for wxConfig @@ -78,4 +78,4 @@ class gbtApplication : public wxApp { DECLARE_APP(gbtApplication) -#endif // GAMBIT_H +#endif // GAMBIT_APP_H diff --git a/src/gui/dleditmove.cc b/src/gui/dleditmove.cc index 134729cef..e240c0c77 100644 --- a/src/gui/dleditmove.cc +++ b/src/gui/dleditmove.cc @@ -52,7 +52,7 @@ class gbtActionSheet : public wxSheet { gbtActionSheet::gbtActionSheet(wxWindow *p_parent, const Gambit::GameInfoset &p_infoset) : wxSheet(p_parent, wxID_ANY), m_infoset(p_infoset) { - CreateGrid(p_infoset->NumActions(), (p_infoset->IsChanceInfoset()) ? 2 : 1); + CreateGrid(p_infoset->GetActions().size(), (p_infoset->IsChanceInfoset()) ? 2 : 1); SetRowLabelWidth(40); SetColLabelHeight(25); SetColLabelValue(0, wxT("Label")); @@ -60,12 +60,12 @@ gbtActionSheet::gbtActionSheet(wxWindow *p_parent, const Gambit::GameInfoset &p_ SetColLabelValue(1, wxT("Probability")); } - for (int act = 1; act <= p_infoset->NumActions(); act++) { - SetCellValue(wxSheetCoords(act - 1, 0), - wxString(p_infoset->GetAction(act)->GetLabel().c_str(), *wxConvCurrent)); + for (const auto &action : p_infoset->GetActions()) { + SetCellValue(wxSheetCoords(action->GetNumber() - 1, 0), + wxString(action->GetLabel().c_str(), *wxConvCurrent)); if (p_infoset->IsChanceInfoset()) { - SetCellValue(wxSheetCoords(act - 1, 1), - wxString(static_cast(p_infoset->GetActionProb(act)).c_str(), + SetCellValue(wxSheetCoords(action->GetNumber() - 1, 1), + wxString(static_cast(p_infoset->GetActionProb(action)).c_str(), *wxConvCurrent)); } } @@ -121,7 +121,7 @@ wxSheetCellAttr gbtActionSheet::GetAttr(const wxSheetCoords &p_coords, wxSheetAt return attr; } else if (IsCornerLabelCell(p_coords)) { - wxSheetCellAttr attr(GetSheetRefData()->m_defaultCornerLabelAttr); + const wxSheetCellAttr attr(GetSheetRefData()->m_defaultCornerLabelAttr); return attr; } @@ -160,10 +160,10 @@ wxBEGIN_EVENT_TABLE(gbtEditMoveDialog, labelSizer->Add(m_infosetName, 1, wxALL | wxEXPAND, 5); topSizer->Add(labelSizer, 0, wxALL | wxEXPAND, 0); - topSizer->Add( - new wxStaticText(this, wxID_STATIC, - wxString::Format(_("Number of members: %d"), p_infoset->NumMembers())), - 0, wxALL | wxALIGN_CENTER, 5); + topSizer->Add(new wxStaticText( + this, wxID_STATIC, + wxString::Format(_("Number of members: %d"), p_infoset->GetMembers().size())), + 0, wxALL | wxALIGN_CENTER, 5); auto *playerSizer = new wxBoxSizer(wxHORIZONTAL); playerSizer->Add(new wxStaticText(this, wxID_STATIC, _("Belongs to player")), 0, @@ -174,10 +174,9 @@ wxBEGIN_EVENT_TABLE(gbtEditMoveDialog, m_player->SetSelection(0); } else { - for (int pl = 1; pl <= p_infoset->GetGame()->NumPlayers(); pl++) { - m_player->Append( - wxString::Format(_T("%d: "), pl) + - wxString(p_infoset->GetGame()->GetPlayer(pl)->GetLabel().c_str(), *wxConvCurrent)); + for (const auto &player : p_infoset->GetGame()->GetPlayers()) { + m_player->Append(wxString::Format(_T("%d: "), player->GetNumber()) + + wxString(player->GetLabel().c_str(), *wxConvCurrent)); } m_player->SetSelection(p_infoset->GetPlayer()->GetNumber() - 1); } @@ -210,17 +209,16 @@ void gbtEditMoveDialog::OnOK(wxCommandEvent &p_event) p_event.Skip(); return; } - Array probs = m_actionSheet->GetActionProbs(); - Rational sum(0); - for (int act = 1; act <= m_infoset->NumActions(); act++) { - auto prob = static_cast(probs[act]); - if (prob < Gambit::Rational(0)) { - wxMessageBox("Action probabilities must be nonnegative numbers.", "Error"); - return; - } - sum += prob; + auto probs = m_actionSheet->GetActionProbs(); + if (std::any_of(probs.begin(), probs.end(), + [](const Number &p) { return static_cast(p) < Rational(0); })) { + wxMessageBox("Action probabilities must be non-negative numbers.", "Error"); + return; } - if (sum == Gambit::Rational(1)) { + if (std::accumulate(probs.begin(), probs.end(), Rational(0), + [](const Rational &s, const Number &p) { + return s + static_cast(p); + }) != Rational(1)) { p_event.Skip(); } else { diff --git a/src/gui/dleditnode.cc b/src/gui/dleditnode.cc index 6f7b36064..645a0d464 100644 --- a/src/gui/dleditnode.cc +++ b/src/gui/dleditnode.cc @@ -27,11 +27,13 @@ #include "gambit.h" #include "dleditnode.h" +using namespace Gambit; + //====================================================================== // class dialogEditNode //====================================================================== -dialogEditNode::dialogEditNode(wxWindow *p_parent, const Gambit::GameNode &p_node) +dialogEditNode::dialogEditNode(wxWindow *p_parent, const GameNode &p_node) : wxDialog(p_parent, wxID_ANY, _("Node properties"), wxDefaultPosition), m_node(p_node) { auto *topSizer = new wxBoxSizer(wxVERTICAL); @@ -47,13 +49,12 @@ dialogEditNode::dialogEditNode(wxWindow *p_parent, const Gambit::GameNode &p_nod infosetSizer->Add(new wxStaticText(this, wxID_STATIC, _("Information set")), 0, wxALL | wxCENTER, 5); m_infoset = new wxChoice(this, wxID_ANY); - if (p_node->NumChildren() > 0) { + if (!p_node->IsTerminal()) { m_infoset->Append(_("New information set")); if (p_node->GetInfoset()->IsChanceInfoset()) { int selection = 0; - for (int iset = 1; iset <= p_node->GetGame()->GetChance()->NumInfosets(); iset++) { - Gambit::GameInfoset infoset = p_node->GetGame()->GetChance()->GetInfoset(iset); - if (infoset->NumActions() == p_node->NumChildren()) { + for (const auto &infoset : p_node->GetGame()->GetChance()->GetInfosets()) { + if (infoset->GetActions().size() == p_node->GetChildren().size()) { m_infosetList.push_back(infoset); m_infoset->Append(wxString::Format(_("Chance infoset %d"), infoset->GetNumber())); if (infoset == p_node->GetInfoset()) { @@ -65,13 +66,12 @@ dialogEditNode::dialogEditNode(wxWindow *p_parent, const Gambit::GameNode &p_nod } else { int selection = 0; - for (int pl = 1; pl <= p_node->GetGame()->NumPlayers(); pl++) { - Gambit::GamePlayer player = p_node->GetGame()->GetPlayer(pl); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { - Gambit::GameInfoset infoset = player->GetInfoset(iset); - if (infoset->NumActions() == p_node->NumChildren()) { + for (const auto &player : p_node->GetGame()->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + if (infoset->GetActions().size() == p_node->GetChildren().size()) { m_infosetList.push_back(infoset); - m_infoset->Append(wxString::Format(_("Player %d, Infoset %d"), pl, iset)); + m_infoset->Append(wxString::Format(_("Player %d, Infoset %d"), player->GetNumber(), + infoset->GetNumber())); if (infoset == p_node->GetInfoset()) { selection = m_infosetList.size(); } @@ -106,18 +106,18 @@ dialogEditNode::dialogEditNode(wxWindow *p_parent, const Gambit::GameNode &p_nod m_outcome = new wxChoice(this, wxID_ANY); m_outcome->Append(_("(null)")); m_outcome->SetSelection(0); - Gambit::Game efg = p_node->GetGame(); - for (int outc = 1; outc <= efg->NumOutcomes(); outc++) { - Gambit::GameOutcome outcome = efg->GetOutcome(outc); - std::string item = Gambit::lexical_cast(outc) + ": " + outcome->GetLabel(); + const Game efg = p_node->GetGame(); + for (const auto &outcome : efg->GetOutcomes()) { + std::string item = + lexical_cast(outcome->GetNumber()) + ": " + outcome->GetLabel(); if (item.empty()) { - item = "Outcome" + Gambit::lexical_cast(outc); + item = "Outcome" + lexical_cast(outcome->GetNumber()); } - item += (" (" + static_cast(outcome->GetPayoff(1)) + ", " + - static_cast(outcome->GetPayoff(2))); + item += (" (" + outcome->GetPayoff(efg->GetPlayer(1)) + ", " + + outcome->GetPayoff(efg->GetPlayer(2))); if (efg->NumPlayers() > 2) { - item += ", " + static_cast(outcome->GetPayoff(3)); + item += ", " + outcome->GetPayoff(efg->GetPlayer(3)); if (efg->NumPlayers() > 3) { item += ",...)"; } @@ -131,7 +131,7 @@ dialogEditNode::dialogEditNode(wxWindow *p_parent, const Gambit::GameNode &p_nod m_outcome->Append(wxString(item.c_str(), *wxConvCurrent)); if (m_node->GetOutcome() == outcome) { - m_outcome->SetSelection(outc); + m_outcome->SetSelection(outcome->GetNumber()); } } outcomeSizer->Add(m_outcome, 1, wxALL | wxEXPAND, 5); @@ -152,7 +152,7 @@ dialogEditNode::dialogEditNode(wxWindow *p_parent, const Gambit::GameNode &p_nod CenterOnParent(); } -Gambit::GameInfoset dialogEditNode::GetInfoset() const +GameInfoset dialogEditNode::GetInfoset() const { if (m_infoset->GetSelection() == 0) { return nullptr; diff --git a/src/gui/dlefglogit.cc b/src/gui/dlefglogit.cc index 63aa3c571..866045068 100644 --- a/src/gui/dlefglogit.cc +++ b/src/gui/dlefglogit.cc @@ -84,7 +84,7 @@ wxString gbtLogitBehavList::GetCellValue(const wxSheetCoords &p_coords) return wxT("Lambda"); } else { - Gambit::GameAction action = m_doc->GetGame()->GetAction(p_coords.GetCol()); + const Gambit::GameAction action = m_doc->GetAction(p_coords.GetCol()); return (wxString::Format(wxT("%d: "), action->GetInfoset()->GetNumber()) + wxString(action->GetLabel().c_str(), *wxConvCurrent)); } @@ -114,7 +114,7 @@ static wxColour GetPlayerColor(gbtGameDocument *p_doc, int p_index) return *wxBLACK; } - Gambit::GameAction action = p_doc->GetGame()->GetAction(p_index); + const Gambit::GameAction action = p_doc->GetAction(p_index); return p_doc->GetStyle().GetPlayerColor(action->GetInfoset()->GetPlayer()->GetNumber()); } @@ -146,7 +146,7 @@ wxSheetCellAttr gbtLogitBehavList::GetAttr(const wxSheetCoords &p_coords, wxShee attr.SetAlignment(wxALIGN_RIGHT, wxALIGN_CENTER); attr.SetOrientation(wxHORIZONTAL); if (p_coords.GetCol() > 0) { - Gambit::GameAction action = m_doc->GetGame()->GetAction(p_coords.GetCol()); + const Gambit::GameAction action = m_doc->GetAction(p_coords.GetCol()); attr.SetForegroundColour( m_doc->GetStyle().GetPlayerColor(action->GetInfoset()->GetPlayer()->GetNumber())); if (action->GetInfoset()->GetNumber() % 2 == 0) { @@ -265,7 +265,7 @@ void gbtLogitBehavDialog::Start() wxString str(wxString(s.str().c_str(), *wxConvCurrent)); // It is possible that the whole string won't write on one go, so - // we should take this possibility into account. If the write doesn't + // we should take this possibility into account. If writing doesn't // complete the whole way, we take a 100-millisecond siesta and try // again. (This seems to primarily be an issue with -- you guessed it -- // Windows!) @@ -309,7 +309,7 @@ void gbtLogitBehavDialog::OnIdle(wxIdleEvent &p_event) } } -void gbtLogitBehavDialog::OnTimer(wxTimerEvent &p_event) { wxWakeUpIdle(); } +void gbtLogitBehavDialog::OnTimer(wxTimerEvent &) { wxWakeUpIdle(); } void gbtLogitBehavDialog::OnEndProcess(wxProcessEvent &p_event) { diff --git a/src/gui/dlefgreveal.cc b/src/gui/dlefgreveal.cc index a7f3c4e86..49a6d6413 100644 --- a/src/gui/dlefgreveal.cc +++ b/src/gui/dlefgreveal.cc @@ -28,6 +28,8 @@ #include "gambit.h" #include "dlefgreveal.h" +using namespace Gambit; + //========================================================================= // gbtRevealMoveDialog: Member functions //========================================================================= @@ -41,8 +43,8 @@ gbtRevealMoveDialog::gbtRevealMoveDialog(wxWindow *p_parent, gbtGameDocument *p_ auto *boxSizer = new wxBoxSizer(wxVERTICAL); - for (int pl = 1; pl <= m_doc->NumPlayers(); pl++) { - Gambit::GamePlayer player = m_doc->GetGame()->GetPlayer(pl); + for (size_t pl = 1; pl <= m_doc->NumPlayers(); pl++) { + auto player = m_doc->GetGame()->GetPlayer(pl); if (player->GetLabel().empty()) { m_players.push_back( new wxCheckBox(this, wxID_ANY, wxString(player->GetLabel().c_str(), *wxConvCurrent))); @@ -71,11 +73,11 @@ gbtRevealMoveDialog::gbtRevealMoveDialog(wxWindow *p_parent, gbtGameDocument *p_ CenterOnParent(); } -Gambit::Array gbtRevealMoveDialog::GetPlayers() const +Array gbtRevealMoveDialog::GetPlayers() const { - Gambit::Array players; + Array players; - for (int pl = 1; pl <= m_doc->NumPlayers(); pl++) { + for (size_t pl = 1; pl <= m_doc->NumPlayers(); pl++) { if (m_players[pl]->GetValue()) { players.push_back(m_doc->GetGame()->GetPlayer(pl)); } diff --git a/src/gui/dlgameprop.cc b/src/gui/dlgameprop.cc index 2af0c89ec..e6890d2c2 100644 --- a/src/gui/dlgameprop.cc +++ b/src/gui/dlgameprop.cc @@ -61,7 +61,7 @@ gbtGamePropertiesDialog::gbtGamePropertiesDialog(wxWindow *p_parent, gbtGameDocu new wxStaticText(this, wxID_STATIC, wxString(_("Filename: ")) + m_doc->GetFilename()), 0, wxALL, 5); - Gambit::Game game = m_doc->GetGame(); + const Gambit::Game game = m_doc->GetGame(); boxSizer->Add(new wxStaticText(this, wxID_STATIC, wxString::Format(_("Number of players: %d"), game->NumPlayers())), 0, wxALL, 5); diff --git a/src/gui/dlinsertmove.cc b/src/gui/dlinsertmove.cc index 26af777b7..31d6ad6e8 100644 --- a/src/gui/dlinsertmove.cc +++ b/src/gui/dlinsertmove.cc @@ -36,14 +36,13 @@ gbtInsertMoveDialog::gbtInsertMoveDialog(wxWindow *p_parent, gbtGameDocument *p_ { m_playerItem = new wxChoice(this, wxID_ANY); m_playerItem->Append(_("Insert move for the chance player")); - for (int pl = 1; pl <= m_doc->NumPlayers(); pl++) { + for (const auto &player : m_doc->GetGame()->GetPlayers()) { wxString s = _("Insert move for "); - Gambit::GamePlayer player = m_doc->GetGame()->GetPlayer(pl); if (player->GetLabel() != "") { s += wxString(player->GetLabel().c_str(), *wxConvCurrent); } else { - s += wxString::Format(_("player %d"), pl); + s += wxString::Format(_("player %d"), player->GetNumber()); } m_playerItem->Append(s); } @@ -54,24 +53,23 @@ gbtInsertMoveDialog::gbtInsertMoveDialog(wxWindow *p_parent, gbtGameDocument *p_ m_infosetItem = new wxChoice(this, wxID_ANY); m_infosetItem->Append(_("at a new information set")); - Gambit::GamePlayer player = m_doc->GetGame()->GetPlayer(1); - for (int iset = 1; iset <= player->NumInfosets(); iset++) { + const Gambit::GamePlayer player = m_doc->GetGame()->GetPlayer(1); + for (const auto &infoset : player->GetInfosets()) { wxString s = _("at information set "); - Gambit::GameInfoset infoset = player->GetInfoset(iset); if (infoset->GetLabel() != "") { s += wxString(infoset->GetLabel().c_str(), *wxConvCurrent); } else { - s += wxString::Format(wxT("%d"), iset); + s += wxString::Format(wxT("%d"), infoset->GetNumber()); } - s += wxString::Format(wxT(" (%d action"), infoset->NumActions()); - if (infoset->NumActions() > 1) { + s += wxString::Format(wxT(" (%d action"), infoset->GetActions().size()); + if (infoset->GetActions().size() > 1) { s += wxT("s"); } - s += wxString::Format(wxT(", %d member node"), infoset->NumMembers()); - if (infoset->NumMembers() > 1) { + s += wxString::Format(wxT(", %d member node"), infoset->GetMembers().size()); + if (infoset->GetMembers().size() > 1) { s += wxT("s)"); } else { @@ -113,13 +111,13 @@ gbtInsertMoveDialog::gbtInsertMoveDialog(wxWindow *p_parent, gbtGameDocument *p_ void gbtInsertMoveDialog::OnPlayer(wxCommandEvent &) { - int playerNumber = m_playerItem->GetSelection(); + const int playerNumber = m_playerItem->GetSelection(); Gambit::GamePlayer player; if (playerNumber == 0) { player = m_doc->GetGame()->GetChance(); } - else if (playerNumber <= m_doc->NumPlayers()) { + else if (playerNumber <= static_cast(m_doc->NumPlayers())) { player = m_doc->GetGame()->GetPlayer(playerNumber); } @@ -132,23 +130,22 @@ void gbtInsertMoveDialog::OnPlayer(wxCommandEvent &) return; } - for (int iset = 1; iset <= player->NumInfosets(); iset++) { + for (const auto &infoset : player->GetInfosets()) { wxString s = _("at information set "); - Gambit::GameInfoset infoset = player->GetInfoset(iset); if (infoset->GetLabel() != "") { s += wxString(infoset->GetLabel().c_str(), *wxConvCurrent); } else { - s += wxString::Format(wxT("%d"), iset); + s += wxString::Format(wxT("%d"), infoset->GetNumber()); } - s += wxString::Format(wxT(" (%d action"), infoset->NumActions()); - if (infoset->NumActions() > 1) { + s += wxString::Format(wxT(" (%d action"), infoset->GetActions().size()); + if (infoset->GetActions().size() > 1) { s += wxT("s"); } - s += wxString::Format(wxT(", %d member node"), infoset->NumMembers()); - if (infoset->NumMembers() > 1) { + s += wxString::Format(wxT(", %d member node"), infoset->GetMembers().size()); + if (infoset->GetMembers().size() > 1) { s += wxT("s)"); } else { @@ -163,10 +160,10 @@ void gbtInsertMoveDialog::OnPlayer(wxCommandEvent &) void gbtInsertMoveDialog::OnInfoset(wxCommandEvent &) { - int infosetNumber = m_infosetItem->GetSelection(); + const int infosetNumber = m_infosetItem->GetSelection(); if (infosetNumber > 0) { - int playerNumber = m_playerItem->GetSelection(); + const int playerNumber = m_playerItem->GetSelection(); Gambit::GameInfoset infoset; if (playerNumber == 0) { infoset = m_doc->GetGame()->GetChance()->GetInfoset(infosetNumber); @@ -175,7 +172,7 @@ void gbtInsertMoveDialog::OnInfoset(wxCommandEvent &) infoset = m_doc->GetGame()->GetPlayer(playerNumber)->GetInfoset(infosetNumber); } m_actions->Enable(false); - m_actions->SetValue(infoset->NumActions()); + m_actions->SetValue(infoset->GetActions().size()); } else { m_actions->Enable(true); @@ -184,16 +181,16 @@ void gbtInsertMoveDialog::OnInfoset(wxCommandEvent &) Gambit::GamePlayer gbtInsertMoveDialog::GetPlayer() const { - int playerNumber = m_playerItem->GetSelection(); + const int playerNumber = m_playerItem->GetSelection(); if (playerNumber == 0) { return m_doc->GetGame()->GetChance(); } - else if (playerNumber <= m_doc->NumPlayers()) { + else if (playerNumber <= static_cast(m_doc->NumPlayers())) { return m_doc->GetGame()->GetPlayer(playerNumber); } else { - Gambit::GamePlayer player = m_doc->GetGame()->NewPlayer(); + const Gambit::GamePlayer player = m_doc->GetGame()->NewPlayer(); player->SetLabel("Player " + Gambit::lexical_cast(m_doc->NumPlayers())); return player; } @@ -201,9 +198,9 @@ Gambit::GamePlayer gbtInsertMoveDialog::GetPlayer() const Gambit::GameInfoset gbtInsertMoveDialog::GetInfoset() const { - if (m_playerItem->GetSelection() <= m_doc->NumPlayers()) { - Gambit::GamePlayer player = GetPlayer(); - int infosetNumber = m_infosetItem->GetSelection(); + if (m_playerItem->GetSelection() <= static_cast(m_doc->NumPlayers())) { + const Gambit::GamePlayer player = GetPlayer(); + const int infosetNumber = m_infosetItem->GetSelection(); if (player && infosetNumber > 0) { return player->GetInfoset(infosetNumber); @@ -219,5 +216,5 @@ Gambit::GameInfoset gbtInsertMoveDialog::GetInfoset() const int gbtInsertMoveDialog::GetActions() const { - return ((GetInfoset()) ? GetInfoset()->NumActions() : m_actions->GetValue()); + return ((GetInfoset()) ? GetInfoset()->GetActions().size() : m_actions->GetValue()); } diff --git a/src/gui/dlnash.cc b/src/gui/dlnash.cc index 805602ccb..ed9dc08fe 100644 --- a/src/gui/dlnash.cc +++ b/src/gui/dlnash.cc @@ -140,7 +140,7 @@ void gbtNashChoiceDialog::OnCount(wxCommandEvent &p_event) void gbtNashChoiceDialog::OnMethod(wxCommandEvent &p_event) { - wxString method = m_methodChoice->GetString(p_event.GetSelection()); + const wxString method = m_methodChoice->GetString(p_event.GetSelection()); if (method == s_simpdiv || method == s_enummixed || method == s_gnm || method == s_ipa) { m_repChoice->SetSelection(1); @@ -156,12 +156,12 @@ bool gbtNashChoiceDialog::UseStrategic() const return (m_repChoice == nullptr || m_repChoice->GetSelection() == 1); } -gbtAnalysisOutput *gbtNashChoiceDialog::GetCommand() const +std::shared_ptr gbtNashChoiceDialog::GetCommand() const { - bool useEfg = m_repChoice && m_repChoice->GetSelection() == 0; - gbtAnalysisOutput *cmd = nullptr; + const bool useEfg = m_repChoice && m_repChoice->GetSelection() == 0; + std::shared_ptr cmd = nullptr; - wxString method = m_methodChoice->GetStringSelection(); + const wxString method = m_methodChoice->GetStringSelection(); wxString prefix, options, game, count; #ifdef __WXMAC__ @@ -195,90 +195,90 @@ gbtAnalysisOutput *gbtNashChoiceDialog::GetCommand() const if (method == s_recommended) { if (m_countChoice->GetSelection() == 0) { if (m_doc->NumPlayers() == 2 && m_doc->IsConstSum()) { - cmd = new gbtAnalysisProfileList(m_doc, useEfg); + cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("lp") + options); cmd->SetDescription(wxT("One equilibrium by solving a linear program ") + game); } else { - cmd = new gbtAnalysisProfileList(m_doc, useEfg); + cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("logit -e -d 10")); cmd->SetDescription(wxT("One equilibrium by logit tracing ") + game); } } else if (m_countChoice->GetSelection() == 1) { if (m_doc->NumPlayers() == 2) { - cmd = new gbtAnalysisProfileList(m_doc, useEfg); + cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("lcp") + options); cmd->SetDescription(wxT("Some equilibria by solving a linear complementarity program ") + game); } else { - cmd = new gbtAnalysisProfileList(m_doc, false); + cmd = std::make_shared>(m_doc, false); cmd->SetCommand(prefix + wxT("simpdiv -d 10 -n 20 -r 100") + options); cmd->SetDescription(wxT("Some equilibria by simplicial subdivision ") + game); } } else { if (m_doc->NumPlayers() == 2) { - cmd = new gbtAnalysisProfileList(m_doc, false); + cmd = std::make_shared>(m_doc, false); cmd->SetCommand(prefix + wxT("enummixed")); cmd->SetDescription( wxT("All equilibria by enumeration of mixed strategies in strategic game")); } else { - cmd = new gbtAnalysisProfileList(m_doc, useEfg); + cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("enumpoly -d 10") + options); cmd->SetDescription(wxT("All equilibria by solving polynomial systems ") + game); } } } else if (method == s_enumpure) { - cmd = new gbtAnalysisProfileList(m_doc, useEfg); + cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("enumpure") + options); cmd->SetDescription(count + wxT(" in pure strategies ") + game); } else if (method == s_enummixed) { - cmd = new gbtAnalysisProfileList(m_doc, false); + cmd = std::make_shared>(m_doc, false); cmd->SetCommand(prefix + wxT("enummixed") + options); cmd->SetDescription(count + wxT(" by enumeration of mixed strategies in strategic game")); } else if (method == s_enumpoly) { - cmd = new gbtAnalysisProfileList(m_doc, useEfg); + cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("enumpoly -d 10") + options); cmd->SetDescription(count + wxT(" by solving polynomial systems ") + game); } else if (method == s_gnm) { - cmd = new gbtAnalysisProfileList(m_doc, false); + cmd = std::make_shared>(m_doc, false); cmd->SetCommand(prefix + wxT("gnm -d 10") + options); cmd->SetDescription(count + wxT(" by global Newton tracing in strategic game")); } else if (method == s_ipa) { - cmd = new gbtAnalysisProfileList(m_doc, false); + cmd = std::make_shared>(m_doc, false); cmd->SetCommand(prefix + wxT("ipa -d 10") + options); cmd->SetDescription(count + wxT(" by iterated polymatrix approximation in strategic game")); } else if (method == s_lp) { - cmd = new gbtAnalysisProfileList(m_doc, useEfg); + cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("lp") + options); cmd->SetDescription(count + wxT(" by solving a linear program ") + game); } else if (method == s_lcp) { - cmd = new gbtAnalysisProfileList(m_doc, useEfg); + cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("lcp") + options); cmd->SetDescription(count + wxT(" by solving a linear complementarity program ") + game); } else if (method == s_liap) { - cmd = new gbtAnalysisProfileList(m_doc, useEfg); + cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("liap -d 10") + options); cmd->SetDescription(count + wxT(" by function minimization ") + game); } else if (method == s_logit) { - cmd = new gbtAnalysisProfileList(m_doc, useEfg); + cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("logit -e -d 10") + options); cmd->SetDescription(count + wxT(" by logit tracing ") + game); } else if (method == s_simpdiv) { - cmd = new gbtAnalysisProfileList(m_doc, false); + cmd = std::make_shared>(m_doc, false); cmd->SetCommand(prefix + wxT("simpdiv -d 10 -n 20 -r 100") + options); cmd->SetDescription(count + wxT(" by simplicial subdivision in strategic game")); } diff --git a/src/gui/dlnash.h b/src/gui/dlnash.h index 5b408a4ba..e0f2fccfa 100644 --- a/src/gui/dlnash.h +++ b/src/gui/dlnash.h @@ -37,7 +37,7 @@ class gbtNashChoiceDialog : public wxDialog { public: gbtNashChoiceDialog(wxWindow *, gbtGameDocument *); - gbtAnalysisOutput *GetCommand() const; + std::shared_ptr GetCommand() const; bool UseStrategic() const; }; diff --git a/src/gui/dlnashmon.cc b/src/gui/dlnashmon.cc index 84150d233..e908cd835 100644 --- a/src/gui/dlnashmon.cc +++ b/src/gui/dlnashmon.cc @@ -44,7 +44,7 @@ END_EVENT_TABLE() #include "bitmaps/stop.xpm" gbtNashMonitorDialog::gbtNashMonitorDialog(wxWindow *p_parent, gbtGameDocument *p_doc, - gbtAnalysisOutput *p_command) + std::shared_ptr p_command) : wxDialog(p_parent, wxID_ANY, wxT("Computing Nash equilibria"), wxDefaultPosition), m_doc(p_doc), m_process(nullptr), m_timer(this, GBT_ID_TIMER), m_output(p_command) { @@ -92,7 +92,7 @@ gbtNashMonitorDialog::gbtNashMonitorDialog(wxWindow *p_parent, gbtGameDocument * Start(p_command); } -void gbtNashMonitorDialog::Start(gbtAnalysisOutput *p_command) +void gbtNashMonitorDialog::Start(std::shared_ptr p_command) { if (!p_command->IsBehavior()) { // Make sure we have a normal form representation diff --git a/src/gui/dlnashmon.h b/src/gui/dlnashmon.h index 4abe9be51..f77b7f753 100644 --- a/src/gui/dlnashmon.h +++ b/src/gui/dlnashmon.h @@ -36,9 +36,9 @@ class gbtNashMonitorDialog : public wxDialog { wxStaticText *m_statusText, *m_countText; wxButton *m_stopButton, *m_okButton; wxTimer m_timer; - gbtAnalysisOutput *m_output; + std::shared_ptr m_output; - void Start(gbtAnalysisOutput *); + void Start(std::shared_ptr p_command); void OnStop(wxCommandEvent &); void OnTimer(wxTimerEvent &); @@ -46,7 +46,8 @@ class gbtNashMonitorDialog : public wxDialog { void OnEndProcess(wxProcessEvent &); public: - gbtNashMonitorDialog(wxWindow *p_parent, gbtGameDocument *p_doc, gbtAnalysisOutput *p_command); + gbtNashMonitorDialog(wxWindow *p_parent, gbtGameDocument *p_doc, + std::shared_ptr p_command); DECLARE_EVENT_TABLE() }; diff --git a/src/gui/dlnfglogit.cc b/src/gui/dlnfglogit.cc index 334d7113d..102fd7fc3 100644 --- a/src/gui/dlnfglogit.cc +++ b/src/gui/dlnfglogit.cc @@ -140,12 +140,11 @@ wxString LogitMixedSheet::GetCellValue(const wxSheetCoords &p_coords) } else { int index = 1; - for (int pl = 1; pl <= m_doc->GetGame()->NumPlayers(); pl++) { - GamePlayer player = m_doc->GetGame()->GetPlayer(pl); - for (int st = 1; st <= player->NumStrategies(); st++) { + for (const auto &player : m_doc->GetGame()->GetPlayers()) { + for (const auto &strategy : player->GetStrategies()) { if (index++ == p_coords.GetCol()) { - return (wxString::Format(wxT("%d: "), pl) + - wxString(player->GetStrategy(st)->GetLabel().c_str(), *wxConvCurrent)); + return (wxString::Format(wxT("%d: "), player->GetNumber()) + + wxString(strategy->GetLabel().c_str(), *wxConvCurrent)); } } } @@ -177,11 +176,10 @@ static wxColour GetPlayerColor(gbtGameDocument *p_doc, int p_index) } int index = 1; - for (int pl = 1; pl <= p_doc->GetGame()->NumPlayers(); pl++) { - GamePlayer player = p_doc->GetGame()->GetPlayer(pl); - for (int st = 1; st <= player->NumStrategies(); st++) { + for (const auto &player : p_doc->GetGame()->GetPlayers()) { + for (const auto &strategy : player->GetStrategies()) { if (index++ == p_index) { - return p_doc->GetStyle().GetPlayerColor(pl); + return p_doc->GetStyle().GetPlayerColor(player->GetNumber()); } } } @@ -274,13 +272,13 @@ gbtLogitPlotCtrl::gbtLogitPlotCtrl(wxWindow *p_parent, gbtGameDocument *p_doc) : wxPlotCtrl(p_parent) /* m_doc(p_doc), */ { SetAxisLabelColour(*wxBLUE); - wxFont labelFont(8, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD); + const wxFont labelFont(8, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD); SetAxisLabelFont(labelFont); SetAxisColour(*wxBLUE); SetAxisFont(labelFont); SetDrawSymbols(false); - // SetAxisFont resets the width of the y axis labels, assuming + // SetAxisFont resets the width of the y-axis labels, assuming // a fairly long label. int x = 6, y = 12, descent = 0, leading = 0; GetTextExtent(wxT("0.88"), &x, &y, &descent, &leading, &labelFont); @@ -306,7 +304,8 @@ void gbtLogitPlotCtrl::CalcXAxisTickPositions() double current = ceil(m_viewRect.GetLeft() / m_xAxisTick_step) * m_xAxisTick_step; m_xAxisTicks.Clear(); m_xAxisTickLabels.Clear(); - int i, x, windowWidth = GetPlotAreaRect().width; + int i, x; + const int windowWidth = GetPlotAreaRect().width; for (i = 0; i < m_xAxisTick_count; i++) { if (!IsFinite(current, wxT("axis label is not finite"))) { return; @@ -378,12 +377,12 @@ gbtLogitPlotStrategyList::gbtLogitPlotStrategyList(wxWindow *p_parent, gbtGameDo SetGridLineColour(*wxWHITE); for (int st = 1; st <= m_doc->GetGame()->MixedProfileLength(); st++) { - GameStrategy strategy = m_doc->GetGame()->GetStrategy(st); - GamePlayer player = strategy->GetPlayer(); - wxColour color = m_doc->GetStyle().GetPlayerColor(player->GetNumber()); + const GameStrategy strategy = m_doc->GetGame()->GetStrategy(st); + const GamePlayer player = strategy->GetPlayer(); + const wxColour color = m_doc->GetStyle().GetPlayerColor(player->GetNumber()); if (strategy->GetNumber() == 1) { - SetCellSpan(wxSheetCoords(st - 1, 0), wxSheetCoords(player->NumStrategies(), 1)); + SetCellSpan(wxSheetCoords(st - 1, 0), wxSheetCoords(player->GetStrategies().size(), 1)); SetCellValue(wxSheetCoords(st - 1, 0), wxString(player->GetLabel().c_str(), *wxConvCurrent)); SetAttrForegroundColour(wxSheetCoords(st - 1, 0), color); SetAttrAlignment(wxSheetCoords(st - 1, 0), wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL); @@ -491,8 +490,8 @@ void LogitPlotPanel::Plot() auto *curve = new wxPlotData(m_branch.NumPoints()); - GameStrategy strategy = m_doc->GetGame()->GetStrategy(st); - GamePlayer player = strategy->GetPlayer(); + const GameStrategy strategy = m_doc->GetGame()->GetStrategy(st); + const GamePlayer player = strategy->GetPlayer(); curve->SetFilename(wxString(player->GetLabel().c_str(), *wxConvCurrent) + wxT(":") + wxString(strategy->GetLabel().c_str(), *wxConvCurrent)); @@ -533,7 +532,7 @@ class LogitPrintout : public wxPrintout { bool OnPrintPage(int) override { - wxSize size = GetDC()->GetSize(); + const wxSize size = GetDC()->GetSize(); m_plot->DrawWholePlot(GetDC(), wxRect(50, 50, size.GetWidth() - 100, size.GetHeight() - 100)); return true; } @@ -698,7 +697,7 @@ void LogitMixedDialog::Start() wxString str(wxString(s.str().c_str(), *wxConvCurrent)); // It is possible that the whole string won't write on one go, so - // we should take this possibility into account. If the write doesn't + // we should take this possibility into account. If writing doesn't // complete the whole way, we take a 100-millisecond siesta and try // again. (This seems to primarily be an issue with -- you guessed it -- // Windows!) diff --git a/src/gui/efgdisplay.cc b/src/gui/efgdisplay.cc index 4efca4ecb..7e4c4f1b9 100644 --- a/src/gui/efgdisplay.cc +++ b/src/gui/efgdisplay.cc @@ -35,6 +35,8 @@ #include "menuconst.h" #include "dlexcept.h" +using namespace Gambit; + //-------------------------------------------------------------------------- // class gbtPayoffEditor //-------------------------------------------------------------------------- @@ -54,8 +56,10 @@ void gbtPayoffEditor::BeginEdit(gbtNodeEntry *p_entry, int p_player) m_entry = p_entry; m_outcome = p_entry->GetNode()->GetOutcome(); m_player = p_player; - SetValue( - wxString(static_cast(m_outcome->GetPayoff(p_player)).c_str(), *wxConvCurrent)); + SetValue(wxString( + m_outcome->GetPayoff(p_entry->GetNode()->GetGame()->GetPlayer(p_player)) + .c_str(), + *wxConvCurrent)); SetSize(wxSize(GetSize().GetWidth(), GetBestSize().GetHeight())); SetSelection(-1, -1); Show(true); @@ -108,13 +112,13 @@ class gbtPlayerDropTarget : public wxTextDropTarget { gbtEfgDisplay *m_owner; gbtGameDocument *m_model; - bool OnDropPlayer(const Gambit::GameNode &p_node, const wxString &p_text); - bool OnDropCopyNode(const Gambit::GameNode &p_node, const wxString &p_text); - bool OnDropMoveNode(const Gambit::GameNode &p_node, const wxString &p_text); - bool OnDropInfoset(const Gambit::GameNode &p_node, const wxString &p_text); - bool OnDropSetOutcome(const Gambit::GameNode &p_node, const wxString &p_text); - bool OnDropMoveOutcome(const Gambit::GameNode &p_node, const wxString &p_text); - bool OnDropCopyOutcome(const Gambit::GameNode &p_node, const wxString &p_text); + bool OnDropPlayer(const GameNode &p_node, const wxString &p_text); + bool OnDropCopyNode(const GameNode &p_node, const wxString &p_text); + bool OnDropMoveNode(const GameNode &p_node, const wxString &p_text); + bool OnDropInfoset(const GameNode &p_node, const wxString &p_text); + bool OnDropSetOutcome(const GameNode &p_node, const wxString &p_text); + bool OnDropMoveOutcome(const GameNode &p_node, const wxString &p_text); + bool OnDropCopyOutcome(const GameNode &p_node, const wxString &p_text); public: explicit gbtPlayerDropTarget(gbtEfgDisplay *p_owner) @@ -129,17 +133,17 @@ class gbtPlayerDropTarget : public wxTextDropTarget { // This recurses the subtree starting at 'p_node' looking for a node // with the ID 'p_id'. // -static Gambit::GameNode GetNode(const Gambit::GameNode &p_node, int p_id) +static GameNode GetNode(const GameNode &p_node, int p_id) { if (p_node->GetNumber() == p_id) { return p_node; } - else if (p_node->NumChildren() == 0) { + else if (p_node->IsTerminal()) { return nullptr; } else { - for (int i = 1; i <= p_node->NumChildren(); i++) { - Gambit::GameNode node = GetNode(p_node->GetChild(i), p_id); + for (const auto &child : p_node->GetChildren()) { + const auto node = GetNode(child, p_id); if (node) { return node; } @@ -148,13 +152,13 @@ static Gambit::GameNode GetNode(const Gambit::GameNode &p_node, int p_id) } } -bool gbtPlayerDropTarget::OnDropPlayer(const Gambit::GameNode &p_node, const wxString &p_text) +bool gbtPlayerDropTarget::OnDropPlayer(const GameNode &p_node, const wxString &p_text) { long pl; p_text.Right(p_text.Length() - 1).ToLong(&pl); - Gambit::Game efg = m_model->GetGame(); - Gambit::GamePlayer player = ((pl == 0) ? efg->GetChance() : efg->GetPlayer(pl)); - if (p_node->NumChildren() == 0) { + const Game efg = m_model->GetGame(); + const GamePlayer player = ((pl == 0) ? efg->GetChance() : efg->GetPlayer(pl)); + if (p_node->IsTerminal()) { m_model->DoInsertMove(p_node, player, 2); } else if (p_node->GetPlayer() == player) { @@ -166,60 +170,60 @@ bool gbtPlayerDropTarget::OnDropPlayer(const Gambit::GameNode &p_node, const wxS return true; } -bool gbtPlayerDropTarget::OnDropCopyNode(const Gambit::GameNode &p_node, const wxString &p_text) +bool gbtPlayerDropTarget::OnDropCopyNode(const GameNode &p_node, const wxString &p_text) { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); - Gambit::GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); + const GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); if (!srcNode) { return false; } - if (p_node->NumChildren() == 0 && srcNode->NumChildren() > 0) { + if (p_node->IsTerminal() && !srcNode->IsTerminal()) { m_model->DoCopyTree(p_node, srcNode); return true; } return false; } -bool gbtPlayerDropTarget::OnDropMoveNode(const Gambit::GameNode &p_node, const wxString &p_text) +bool gbtPlayerDropTarget::OnDropMoveNode(const GameNode &p_node, const wxString &p_text) { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); - Gambit::GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); + const GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); if (!srcNode) { return false; } - if (p_node->NumChildren() == 0 && srcNode->NumChildren() > 0) { + if (p_node->IsTerminal() && !srcNode->IsTerminal()) { m_model->DoMoveTree(p_node, srcNode); return true; } return false; } -bool gbtPlayerDropTarget::OnDropInfoset(const Gambit::GameNode &p_node, const wxString &p_text) +bool gbtPlayerDropTarget::OnDropInfoset(const GameNode &p_node, const wxString &p_text) { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); - Gambit::GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); + const GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); if (!srcNode) { return false; } - if (p_node->NumChildren() > 0 && p_node->NumChildren() == srcNode->NumChildren()) { + if (!p_node->IsTerminal() && p_node->GetChildren().size() == srcNode->GetChildren().size()) { m_model->DoSetInfoset(p_node, srcNode->GetInfoset()); return true; } - else if (p_node->NumChildren() == 0 && srcNode->NumChildren() > 0) { + else if (p_node->IsTerminal() && !srcNode->IsTerminal()) { m_model->DoAppendMove(p_node, srcNode->GetInfoset()); return true; } return false; } -bool gbtPlayerDropTarget::OnDropSetOutcome(const Gambit::GameNode &p_node, const wxString &p_text) +bool gbtPlayerDropTarget::OnDropSetOutcome(const GameNode &p_node, const wxString &p_text) { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); - Gambit::GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); + const GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); if (!srcNode || p_node == srcNode) { return false; } @@ -227,11 +231,11 @@ bool gbtPlayerDropTarget::OnDropSetOutcome(const Gambit::GameNode &p_node, const return true; } -bool gbtPlayerDropTarget::OnDropMoveOutcome(const Gambit::GameNode &p_node, const wxString &p_text) +bool gbtPlayerDropTarget::OnDropMoveOutcome(const GameNode &p_node, const wxString &p_text) { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); - Gambit::GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); + const GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); if (!srcNode || p_node == srcNode) { return false; } @@ -240,11 +244,11 @@ bool gbtPlayerDropTarget::OnDropMoveOutcome(const Gambit::GameNode &p_node, cons return true; } -bool gbtPlayerDropTarget::OnDropCopyOutcome(const Gambit::GameNode &p_node, const wxString &p_text) +bool gbtPlayerDropTarget::OnDropCopyOutcome(const GameNode &p_node, const wxString &p_text) { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); - Gambit::GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); + const GameNode srcNode = GetNode(m_model->GetGame()->GetRoot(), n); if (!srcNode || p_node == srcNode) { return false; } @@ -254,7 +258,7 @@ bool gbtPlayerDropTarget::OnDropCopyOutcome(const Gambit::GameNode &p_node, cons bool gbtPlayerDropTarget::OnDropText(wxCoord p_x, wxCoord p_y, const wxString &p_text) { - Gambit::Game efg = m_owner->GetDocument()->GetGame(); + const Game efg = m_owner->GetDocument()->GetGame(); int x, y; #if defined(__WXMSW__) @@ -271,7 +275,7 @@ bool gbtPlayerDropTarget::OnDropText(wxCoord p_x, wxCoord p_y, const wxString &p x = (int)((float)x / (.01 * m_owner->GetZoom())); y = (int)((float)y / (.01 * m_owner->GetZoom())); - Gambit::GameNode node = m_owner->GetLayout().NodeHitTest(x, y); + const GameNode node = m_owner->GetLayout().NodeHitTest(x, y); if (!node) { return false; } @@ -365,40 +369,30 @@ void gbtEfgDisplay::MakeMenus() // gbtEfgDisplay: Event-hook members //--------------------------------------------------------------------- -static Gambit::GameNode PriorSameIset(const Gambit::GameNode &n) +static GameNode PriorSameIset(const GameNode &n) { - Gambit::GameInfoset iset = n->GetInfoset(); + const GameInfoset iset = n->GetInfoset(); if (!iset) { return nullptr; } - for (int i = 1; i <= iset->NumMembers(); i++) { - if (iset->GetMember(i) == n) { - if (i > 1) { - return iset->GetMember(i - 1); - } - else { - return nullptr; - } - } + auto members = iset->GetMembers(); + auto node = std::find(members.begin(), members.end(), n); + if (node != members.begin()) { + return *std::prev(node); } return nullptr; } -static Gambit::GameNode NextSameIset(const Gambit::GameNode &n) +static GameNode NextSameIset(const GameNode &n) { - Gambit::GameInfoset iset = n->GetInfoset(); + const GameInfoset iset = n->GetInfoset(); if (!iset) { return nullptr; } - for (int i = 1; i <= iset->NumMembers(); i++) { - if (iset->GetMember(i) == n) { - if (i < iset->NumMembers()) { - return iset->GetMember(i + 1); - } - else { - return nullptr; - } - } + auto members = iset->GetMembers(); + auto node = std::find(members.begin(), members.end(), n); + if (node != members.end()) { + return *std::next(node); } return nullptr; } @@ -423,7 +417,7 @@ static Gambit::GameNode NextSameIset(const Gambit::GameNode &n) // void gbtEfgDisplay::OnKeyEvent(wxKeyEvent &p_event) { - Gambit::GameNode selectNode = m_doc->GetSelectNode(); + const GameNode selectNode = m_doc->GetSelectNode(); if (p_event.GetKeyCode() == 'R' || p_event.GetKeyCode() == 'r') { m_doc->SetSelectNode(m_doc->GetGame()->GetRoot()); @@ -439,9 +433,9 @@ void gbtEfgDisplay::OnKeyEvent(wxKeyEvent &p_event) else if (p_event.GetKeyCode() == WXK_TAB) { m_payoffEditor->EndEdit(); - Gambit::GameOutcome outcome = m_payoffEditor->GetOutcome(); - int player = m_payoffEditor->GetPlayer(); - Gambit::GameNode node = m_payoffEditor->GetNodeEntry()->GetNode(); + const GameOutcome outcome = m_payoffEditor->GetOutcome(); + const int player = m_payoffEditor->GetPlayer(); + const GameNode node = m_payoffEditor->GetNodeEntry()->GetNode(); try { m_doc->DoSetPayoff(outcome, player, m_payoffEditor->GetValue()); } @@ -462,14 +456,14 @@ void gbtEfgDisplay::OnKeyEvent(wxKeyEvent &p_event) PrepareDC(dc); OnDraw(dc); - if (player < m_doc->NumPlayers()) { + if (player < static_cast(m_doc->NumPlayers())) { gbtNodeEntry *entry = m_layout.GetNodeEntry(node); - wxRect rect = entry->GetPayoffExtent(player + 1); + const wxRect rect = entry->GetPayoffExtent(player + 1); int xx, yy; CalcScrolledPosition((int)(.01 * (rect.x - 3) * m_zoom), (int)(.01 * (rect.y - 3) * m_zoom), &xx, &yy); - int width = (int)(.01 * (rect.width + 10) * m_zoom); - int height = (int)(.01 * (rect.height + 6) * m_zoom); + const int width = (int)(.01 * (rect.width + 10) * m_zoom); + const int height = (int)(.01 * (rect.height + 6) * m_zoom); m_payoffEditor->SetSize(xx, yy, width, height); m_payoffEditor->BeginEdit(entry, player + 1); } @@ -488,13 +482,13 @@ void gbtEfgDisplay::OnKeyEvent(wxKeyEvent &p_event) switch (p_event.GetKeyCode()) { case 'M': case 'm': { - wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_MOVE); + const wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_MOVE); wxPostEvent(this, event); return; } case 'N': case 'n': { - wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_NODE); + const wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_NODE); wxPostEvent(this, event); return; } @@ -511,7 +505,7 @@ void gbtEfgDisplay::OnKeyEvent(wxKeyEvent &p_event) } return; case WXK_UP: { - Gambit::GameNode prior = + const GameNode prior = ((!p_event.AltDown()) ? m_layout.PriorSameLevel(selectNode) : PriorSameIset(selectNode)); if (prior) { m_doc->SetSelectNode(prior); @@ -520,7 +514,7 @@ void gbtEfgDisplay::OnKeyEvent(wxKeyEvent &p_event) return; } case WXK_DOWN: { - Gambit::GameNode next = + const GameNode next = ((!p_event.AltDown()) ? m_layout.NextSameLevel(selectNode) : NextSameIset(selectNode)); if (next) { m_doc->SetSelectNode(next); @@ -532,12 +526,12 @@ void gbtEfgDisplay::OnKeyEvent(wxKeyEvent &p_event) EnsureNodeVisible(m_doc->GetSelectNode()); return; case WXK_DELETE: { - wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_DELETE_TREE); + const wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_DELETE_TREE); wxPostEvent(this, event); return; } case WXK_BACK: { - wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_DELETE_PARENT); + const wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_DELETE_PARENT); wxPostEvent(this, event); return; } @@ -550,8 +544,8 @@ void gbtEfgDisplay::OnKeyEvent(wxKeyEvent &p_event) void gbtEfgDisplay::OnAcceptPayoffEdit(wxCommandEvent &) { m_payoffEditor->EndEdit(); - Gambit::GameOutcome outcome = m_payoffEditor->GetOutcome(); - int player = m_payoffEditor->GetPlayer(); + const GameOutcome outcome = m_payoffEditor->GetOutcome(); + const int player = m_payoffEditor->GetPlayer(); try { m_doc->DoSetPayoff(outcome, player, m_payoffEditor->GetValue()); } @@ -588,14 +582,14 @@ void gbtEfgDisplay::OnUpdate() // Force a rebuild on every change for now. RefreshTree(); - Gambit::GameNode selectNode = m_doc->GetSelectNode(); + const GameNode selectNode = m_doc->GetSelectNode(); m_nodeMenu->Enable(wxID_UNDO, m_doc->CanUndo()); m_nodeMenu->Enable(wxID_REDO, m_doc->CanRedo()); m_nodeMenu->Enable(GBT_MENU_EDIT_INSERT_MOVE, selectNode); m_nodeMenu->Enable(GBT_MENU_EDIT_INSERT_ACTION, selectNode && selectNode->GetInfoset()); m_nodeMenu->Enable(GBT_MENU_EDIT_REVEAL, selectNode && selectNode->GetInfoset()); - m_nodeMenu->Enable(GBT_MENU_EDIT_DELETE_TREE, selectNode && selectNode->NumChildren() > 0); + m_nodeMenu->Enable(GBT_MENU_EDIT_DELETE_TREE, selectNode && !selectNode->IsTerminal()); m_nodeMenu->Enable(GBT_MENU_EDIT_DELETE_PARENT, selectNode && selectNode->GetParent()); m_nodeMenu->Enable(GBT_MENU_EDIT_REMOVE_OUTCOME, selectNode && selectNode->GetOutcome()); m_nodeMenu->Enable(GBT_MENU_EDIT_NODE, selectNode); @@ -652,7 +646,7 @@ void gbtEfgDisplay::OnDraw(wxDC &p_dc) { p_dc.SetUserScale(.01 * m_zoom, .01 * m_zoom); p_dc.Clear(); - int maxX = m_layout.MaxX(); + const int maxX = m_layout.MaxX(); m_layout.Render(p_dc, false); // When we draw, we might change the location of the right margin // (because of the outcome labels). Make sure scrollbars are adjusted @@ -664,13 +658,13 @@ void gbtEfgDisplay::OnDraw(wxDC &p_dc) void gbtEfgDisplay::OnDraw(wxDC &p_dc, double p_zoom) { - // Bit of a hack: this allows us to set zoom separately in printout code - int saveZoom = m_zoom; + // A bit of a hack: this allows us to set zoom separately in printout code + const int saveZoom = m_zoom; m_zoom = int(100.0 * p_zoom); p_dc.SetUserScale(.01 * m_zoom, .01 * m_zoom); p_dc.Clear(); - int maxX = m_layout.MaxX(); + const int maxX = m_layout.MaxX(); // A second hack: this is usually only called by functions for hardcopy // output (printouts or graphics images). We want to suppress the // use of the "hints" for these. @@ -686,7 +680,7 @@ void gbtEfgDisplay::OnDraw(wxDC &p_dc, double p_zoom) m_zoom = saveZoom; } -void gbtEfgDisplay::EnsureNodeVisible(const Gambit::GameNode &p_node) +void gbtEfgDisplay::EnsureNodeVisible(const GameNode &p_node) { if (!p_node) { return; @@ -750,7 +744,7 @@ void gbtEfgDisplay::OnLeftClick(wxMouseEvent &p_event) x = (int)((float)x / (.01 * m_zoom)); y = (int)((float)y / (.01 * m_zoom)); - Gambit::GameNode node = m_layout.NodeHitTest(x, y); + const GameNode node = m_layout.NodeHitTest(x, y); if (node != m_doc->GetSelectNode()) { m_doc->SetSelectNode(node); } @@ -767,10 +761,10 @@ void gbtEfgDisplay::OnLeftDoubleClick(wxMouseEvent &p_event) x = (int)((float)x / (.01 * m_zoom)); y = (int)((float)y / (.01 * m_zoom)); - Gambit::GameNode node = m_layout.NodeHitTest(x, y); + GameNode node = m_layout.NodeHitTest(x, y); if (node) { m_doc->SetSelectNode(node); - wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_NODE); + const wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_NODE); wxPostEvent(this, event); return; } @@ -787,13 +781,13 @@ void gbtEfgDisplay::OnLeftDoubleClick(wxMouseEvent &p_event) OnDraw(dc); gbtNodeEntry *entry = m_layout.GetNodeEntry(node); - wxRect rect = entry->GetPayoffExtent(1); + const wxRect rect = entry->GetPayoffExtent(1); int xx, yy; CalcScrolledPosition((int)(.01 * (rect.x - 3) * m_zoom), (int)(.01 * (rect.y - 3) * m_zoom), &xx, &yy); - int width = (int)(.01 * (rect.width + 10) * m_zoom); - int height = (int)(.01 * (rect.height + 6) * m_zoom); + const int width = (int)(.01 * (rect.width + 10) * m_zoom); + const int height = (int)(.01 * (rect.height + 6) * m_zoom); m_payoffEditor->SetSize(xx, yy, width, height); m_payoffEditor->BeginEdit(entry, 1); return; @@ -801,14 +795,14 @@ void gbtEfgDisplay::OnLeftDoubleClick(wxMouseEvent &p_event) // Editing an existing outcome gbtNodeEntry *entry = m_layout.GetNodeEntry(node); - for (int pl = 1; pl <= m_doc->NumPlayers(); pl++) { - wxRect rect = entry->GetPayoffExtent(pl); + for (size_t pl = 1; pl <= m_doc->NumPlayers(); pl++) { + const wxRect rect = entry->GetPayoffExtent(pl); if (rect.Contains(x, y)) { int xx, yy; CalcScrolledPosition((int)(.01 * (rect.x - 3) * m_zoom), (int)(.01 * (rect.y - 3) * m_zoom), &xx, &yy); - int width = (int)(.01 * (rect.width + 10) * m_zoom); - int height = (int)(.01 * (rect.height + 6) * m_zoom); + const int width = (int)(.01 * (rect.width + 10) * m_zoom); + const int height = (int)(.01 * (rect.height + 6) * m_zoom); m_payoffEditor->SetSize(xx, yy, width, height); m_payoffEditor->BeginEdit(entry, pl); return; @@ -822,7 +816,7 @@ void gbtEfgDisplay::OnLeftDoubleClick(wxMouseEvent &p_event) node = m_layout.BranchAboveHitTest(x, y); if (node) { m_doc->SetSelectNode(node); - wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_MOVE); + const wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_MOVE); wxPostEvent(this, event); return; } @@ -832,7 +826,7 @@ void gbtEfgDisplay::OnLeftDoubleClick(wxMouseEvent &p_event) node = m_layout.BranchBelowHitTest(x, y); if (node) { m_doc->SetSelectNode(node); - wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_MOVE); + const wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_MOVE); wxPostEvent(this, event); return; } @@ -850,23 +844,15 @@ void gbtEfgDisplay::OnMouseMotion(wxMouseEvent &p_event) x = (int)((float)x / (.01 * GetZoom())); y = (int)((float)y / (.01 * GetZoom())); - Gambit::GameNode node = m_layout.NodeHitTest(x, y); - - if (node && node->NumChildren() > 0) { - Gambit::GamePlayer player = node->GetPlayer(); - wxString label; - if (p_event.ShiftDown()) { - label = wxT("i"); - } - else { - label = wxString(player->GetLabel().c_str(), *wxConvCurrent); - } + GameNode node = m_layout.NodeHitTest(x, y); + if (node && !node->IsTerminal()) { + const GamePlayer player = node->GetPlayer(); if (p_event.ControlDown()) { // Copy subtree - wxBitmap bitmap(tree_xpm); + const wxBitmap bitmap(tree_xpm); #if defined(__WXMSW__) or defined(__WXMAC__) - wxImage image = bitmap.ConvertToImage(); + const wxImage image = bitmap.ConvertToImage(); #else wxIcon image; image.CopyFromBitmap(bitmap); @@ -879,9 +865,9 @@ void gbtEfgDisplay::OnMouseMotion(wxMouseEvent &p_event) else if (p_event.ShiftDown()) { // Copy move (information set) // This should be the pawn icon! - wxBitmap bitmap(move_xpm); + const wxBitmap bitmap(move_xpm); #if defined(__WXMSW__) or defined(__WXMAC__) - wxImage image = bitmap.ConvertToImage(); + const wxImage image = bitmap.ConvertToImage(); #else wxIcon image; image.CopyFromBitmap(bitmap); @@ -894,9 +880,9 @@ void gbtEfgDisplay::OnMouseMotion(wxMouseEvent &p_event) } else { // Move subtree - wxBitmap bitmap(tree_xpm); + const wxBitmap bitmap(tree_xpm); #if defined(__WXMSW__) or defined(__WXMAC__) - wxImage image = bitmap.ConvertToImage(); + const wxImage image = bitmap.ConvertToImage(); #else wxIcon image; image.CopyFromBitmap(bitmap); @@ -913,9 +899,9 @@ void gbtEfgDisplay::OnMouseMotion(wxMouseEvent &p_event) node = m_layout.OutcomeHitTest(x, y); if (node && node->GetOutcome()) { - wxBitmap bitmap = MakeOutcomeBitmap(); + const wxBitmap bitmap = MakeOutcomeBitmap(); #if defined(__WXMSW__) or defined(__WXMAC__) - wxImage image = bitmap.ConvertToImage(); + const wxImage image = bitmap.ConvertToImage(); #else wxIcon image; image.CopyFromBitmap(bitmap); @@ -951,7 +937,7 @@ void gbtEfgDisplay::OnRightClick(wxMouseEvent &p_event) x = (int)((float)x / (.01 * m_zoom)); y = (int)((float)y / (.01 * m_zoom)); - Gambit::GameNode node = m_layout.NodeHitTest(x, y); + const GameNode node = m_layout.NodeHitTest(x, y); if (node != m_doc->GetSelectNode()) { m_doc->SetSelectNode(node); } diff --git a/src/gui/efglayout.cc b/src/gui/efglayout.cc index 460e347f7..4e5ac7d21 100644 --- a/src/gui/efglayout.cc +++ b/src/gui/efglayout.cc @@ -31,11 +31,13 @@ #include "gambit.h" #include "efgdisplay.h" +using namespace Gambit; + //----------------------------------------------------------------------- // class gbtNodeEntry: Member functions //----------------------------------------------------------------------- -gbtNodeEntry::gbtNodeEntry(Gambit::GameNode p_node) +gbtNodeEntry::gbtNodeEntry(GameNode p_node) : m_node(p_node), m_parent(nullptr), m_x(-1), m_y(-1), m_nextMember(nullptr), m_inSupport(true), m_size(20), m_token(GBT_NODE_TOKEN_CIRCLE), m_branchStyle(GBT_BRANCH_STYLE_LINE), m_branchLabel(GBT_BRANCH_LABEL_HORIZONTAL), m_branchLength(0), m_sublevel(0), m_actionProb(0) @@ -56,7 +58,7 @@ int gbtNodeEntry::GetChildNumber() const // Draws the node token itself, as well as the incoming branch // (if not the root node) // -void gbtNodeEntry::Draw(wxDC &p_dc, Gambit::GameNode p_selection, bool p_noHints) const +void gbtNodeEntry::Draw(wxDC &p_dc, GameNode p_selection, bool p_noHints) const { if (m_node->GetParent() && m_inSupport) { DrawIncomingBranch(p_dc); @@ -112,10 +114,10 @@ void gbtNodeEntry::Draw(wxDC &p_dc, Gambit::GameNode p_selection, bool p_noHints void gbtNodeEntry::DrawIncomingBranch(wxDC &p_dc) const { - int xStart = m_parent->m_x + m_parent->m_size; - int xEnd = m_x; - int yStart = m_parent->m_y; - int yEnd = m_y; + const int xStart = m_parent->m_x + m_parent->m_size; + const int xEnd = m_x; + const int yStart = m_parent->m_y; + const int yEnd = m_y; p_dc.SetPen(*wxThePenList->FindOrCreatePen(m_parent->m_color, 4, wxPENSTYLE_SOLID)); p_dc.SetTextForeground(m_parent->m_color); @@ -124,7 +126,7 @@ void gbtNodeEntry::DrawIncomingBranch(wxDC &p_dc) const p_dc.DrawLine(xStart, yStart, xEnd, yEnd); // Draw in the highlight indicating action probabilities - if (m_actionProb >= Gambit::Rational(0)) { + if (m_actionProb >= Rational(0)) { p_dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4, wxPENSTYLE_SOLID)); p_dc.DrawLine(xStart, yStart, xStart + (int)((double)(xEnd - xStart) * (double)m_actionProb), yStart + (int)((double)(yEnd - yStart) * (double)m_actionProb)); @@ -135,10 +137,10 @@ void gbtNodeEntry::DrawIncomingBranch(wxDC &p_dc) const p_dc.GetTextExtent(m_branchAboveLabel, &textWidth, &textHeight); // The angle of the branch - double theta = -atan((double)(yEnd - yStart) / (double)(xEnd - xStart)); + const double theta = -atan((double)(yEnd - yStart) / (double)(xEnd - xStart)); // The "centerpoint" of the branch - int xbar = (xStart + xEnd) / 2; - int ybar = (yStart + yEnd) / 2; + const int xbar = (xStart + xEnd) / 2; + const int ybar = (yStart + yEnd) / 2; if (m_branchLabel == GBT_BRANCH_LABEL_HORIZONTAL) { if (yStart >= yEnd) { @@ -202,7 +204,7 @@ void gbtNodeEntry::DrawIncomingBranch(wxDC &p_dc) const p_dc.DrawLine(xStart + m_branchLength, yEnd, xEnd, yEnd); // Draw in the highlight indicating action probabilities - if (m_actionProb >= Gambit::Rational(0)) { + if (m_actionProb >= Rational(0)) { p_dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 2, wxPENSTYLE_SOLID)); p_dc.DrawLine(xStart, yStart, xStart + (int)((double)m_branchLength * (double)m_actionProb), yStart + (int)((double)(yEnd - yStart) * (double)m_actionProb)); @@ -222,20 +224,21 @@ void gbtNodeEntry::DrawIncomingBranch(wxDC &p_dc) const } } -static wxPoint DrawFraction(wxDC &p_dc, wxPoint p_point, const Gambit::Rational &p_value) +static wxPoint DrawFraction(wxDC &p_dc, wxPoint p_point, const Rational &p_value) { p_dc.SetFont(wxFont(7, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD)); int numWidth, numHeight; - wxString num = wxString(lexical_cast(p_value.numerator()).c_str(), *wxConvCurrent); + const wxString num = + wxString(lexical_cast(p_value.numerator()).c_str(), *wxConvCurrent); p_dc.GetTextExtent(num, &numWidth, &numHeight); int denWidth, denHeight; - wxString den = + const wxString den = wxString(lexical_cast(p_value.denominator()).c_str(), *wxConvCurrent); p_dc.GetTextExtent(den, &denWidth, &denHeight); - int width = ((numWidth > denWidth) ? numWidth : denWidth); + const int width = ((numWidth > denWidth) ? numWidth : denWidth); p_dc.DrawLine(p_point.x, p_point.y, p_point.x + width + 4, p_point.y); p_dc.DrawText(num, p_point.x + 2 + (width - numWidth) / 2, p_point.y - 2 - numHeight); @@ -249,7 +252,7 @@ void gbtNodeEntry::DrawOutcome(wxDC &p_dc, bool p_noHints) const { wxPoint point(m_x + m_size + 20, m_y); - Gambit::GameOutcome outcome = m_node->GetOutcome(); + const GameOutcome outcome = m_node->GetOutcome(); if (!outcome) { if (p_noHints) { return; @@ -261,26 +264,25 @@ void gbtNodeEntry::DrawOutcome(wxDC &p_dc, bool p_noHints) const p_dc.GetTextExtent(wxT("(u)"), &width, &height); p_dc.DrawText(wxT("(u)"), point.x, point.y - height / 2); m_outcomeRect = wxRect(point.x, point.y - height / 2, width, height); - m_payoffRect = Gambit::Array(); + m_payoffRect = Array(); return; } int width, height = 25; - m_payoffRect = Gambit::Array(); - for (int pl = 1; pl <= m_node->GetGame()->NumPlayers(); pl++) { - Gambit::GamePlayer player = m_node->GetGame()->GetPlayer(pl); - p_dc.SetTextForeground(m_style->GetPlayerColor(pl)); + m_payoffRect = Array(); + for (const auto &player : m_node->GetGame()->GetPlayers()) { + p_dc.SetTextForeground(m_style->GetPlayerColor(player->GetNumber())); - std::string payoff = static_cast(outcome->GetPayoff(pl)); + const auto &payoff = outcome->GetPayoff(player); if (payoff.find('/') != std::string::npos) { - p_dc.SetPen(wxPen(m_style->GetPlayerColor(pl), 1, wxPENSTYLE_SOLID)); - int oldX = point.x; - point = DrawFraction(p_dc, point, static_cast(outcome->GetPayoff(pl))); + p_dc.SetPen(wxPen(m_style->GetPlayerColor(player->GetNumber()), 1, wxPENSTYLE_SOLID)); + const int oldX = point.x; + point = DrawFraction(p_dc, point, outcome->GetPayoff(player)); m_payoffRect.push_back(wxRect(oldX - 5, point.y - height / 2, point.x - oldX + 10, height)); } else { - wxString label = wxString(payoff.c_str(), *wxConvCurrent); + const wxString label = wxString(payoff.c_str(), *wxConvCurrent); p_dc.SetFont(wxFont(9, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD)); p_dc.GetTextExtent(label, &width, &height); p_dc.DrawText(label, point.x, point.y - height / 2); @@ -323,7 +325,7 @@ gbtTreeLayout::gbtTreeLayout(gbtEfgDisplay *p_parent, gbtGameDocument *p_doc) { } -Gambit::GameNode gbtTreeLayout::NodeHitTest(int p_x, int p_y) const +GameNode gbtTreeLayout::NodeHitTest(int p_x, int p_y) const { for (const auto &entry : m_nodeList) { if (entry->NodeHitTest(p_x, p_y)) { @@ -333,7 +335,7 @@ Gambit::GameNode gbtTreeLayout::NodeHitTest(int p_x, int p_y) const return nullptr; } -Gambit::GameNode gbtTreeLayout::OutcomeHitTest(int p_x, int p_y) const +GameNode gbtTreeLayout::OutcomeHitTest(int p_x, int p_y) const { for (const auto &entry : m_nodeList) { if (entry->OutcomeHitTest(p_x, p_y)) { @@ -343,7 +345,7 @@ Gambit::GameNode gbtTreeLayout::OutcomeHitTest(int p_x, int p_y) const return nullptr; } -Gambit::GameNode gbtTreeLayout::BranchAboveHitTest(int p_x, int p_y) const +GameNode gbtTreeLayout::BranchAboveHitTest(int p_x, int p_y) const { for (const auto &entry : m_nodeList) { if (entry->BranchAboveHitTest(p_x, p_y)) { @@ -353,7 +355,7 @@ Gambit::GameNode gbtTreeLayout::BranchAboveHitTest(int p_x, int p_y) const return nullptr; } -Gambit::GameNode gbtTreeLayout::BranchBelowHitTest(int p_x, int p_y) const +GameNode gbtTreeLayout::BranchBelowHitTest(int p_x, int p_y) const { for (const auto &entry : m_nodeList) { if (entry->BranchAboveHitTest(p_x, p_y)) { @@ -363,7 +365,7 @@ Gambit::GameNode gbtTreeLayout::BranchBelowHitTest(int p_x, int p_y) const return nullptr; } -Gambit::GameNode gbtTreeLayout::InfosetHitTest(int p_x, int p_y) const +GameNode gbtTreeLayout::InfosetHitTest(int p_x, int p_y) const { for (const auto &entry : m_nodeList) { if (entry->GetNextMember() && entry->GetNode()->GetInfoset()) { @@ -385,7 +387,7 @@ Gambit::GameNode gbtTreeLayout::InfosetHitTest(int p_x, int p_y) const wxString gbtTreeLayout::CreateNodeLabel(const gbtNodeEntry *p_entry, int p_which) const { - Gambit::GameNode n = p_entry->GetNode(); + const GameNode n = p_entry->GetNode(); try { switch (p_which) { @@ -443,7 +445,7 @@ wxString gbtTreeLayout::CreateNodeLabel(const gbtNodeEntry *p_entry, int p_which wxString gbtTreeLayout::CreateBranchLabel(const gbtNodeEntry *p_entry, int p_which) const { - Gambit::GameNode parent = p_entry->GetParent()->GetNode(); + const GameNode parent = p_entry->GetParent()->GetNode(); try { switch (p_which) { @@ -455,7 +457,8 @@ wxString gbtTreeLayout::CreateBranchLabel(const gbtNodeEntry *p_entry, int p_whi case GBT_BRANCH_LABEL_PROBS: if (parent->GetPlayer() && parent->GetPlayer()->IsChance()) { return {static_cast( - parent->GetInfoset()->GetActionProb(p_entry->GetChildNumber())) + parent->GetInfoset()->GetActionProb( + parent->GetInfoset()->GetAction(p_entry->GetChildNumber()))) .c_str(), *wxConvCurrent}; } @@ -483,7 +486,7 @@ wxString gbtTreeLayout::CreateBranchLabel(const gbtNodeEntry *p_entry, int p_whi } } -gbtNodeEntry *gbtTreeLayout::GetValidParent(const Gambit::GameNode &e) +gbtNodeEntry *gbtTreeLayout::GetValidParent(const GameNode &e) { gbtNodeEntry *n = GetNodeEntry(e->GetParent()); if (n) { @@ -494,15 +497,15 @@ gbtNodeEntry *gbtTreeLayout::GetValidParent(const Gambit::GameNode &e) } } -gbtNodeEntry *gbtTreeLayout::GetValidChild(const Gambit::GameNode &e) +gbtNodeEntry *gbtTreeLayout::GetValidChild(const GameNode &e) { - for (int i = 1; i <= e->NumChildren(); i++) { - gbtNodeEntry *n = GetNodeEntry(e->GetChild(i)); + for (const auto &child : e->GetChildren()) { + gbtNodeEntry *n = GetNodeEntry(child); if (n) { return n; } else { - n = GetValidChild(e->GetChild(i)); + n = GetValidChild(child); if (n) { return n; } @@ -511,7 +514,7 @@ gbtNodeEntry *gbtTreeLayout::GetValidChild(const Gambit::GameNode &e) return nullptr; } -gbtNodeEntry *gbtTreeLayout::GetEntry(const Gambit::GameNode &p_node) const +gbtNodeEntry *gbtTreeLayout::GetEntry(const GameNode &p_node) const { for (const auto &entry : m_nodeList) { if (entry->GetNode() == p_node) { @@ -521,7 +524,7 @@ gbtNodeEntry *gbtTreeLayout::GetEntry(const Gambit::GameNode &p_node) const return nullptr; } -Gambit::GameNode gbtTreeLayout::PriorSameLevel(const Gambit::GameNode &p_node) const +GameNode gbtTreeLayout::PriorSameLevel(const GameNode &p_node) const { gbtNodeEntry *entry = GetEntry(p_node); if (entry) { @@ -535,7 +538,7 @@ Gambit::GameNode gbtTreeLayout::PriorSameLevel(const Gambit::GameNode &p_node) c return nullptr; } -Gambit::GameNode gbtTreeLayout::NextSameLevel(const Gambit::GameNode &p_node) const +GameNode gbtTreeLayout::NextSameLevel(const GameNode &p_node) const { gbtNodeEntry *entry = GetEntry(p_node); if (entry) { @@ -550,9 +553,8 @@ Gambit::GameNode gbtTreeLayout::NextSameLevel(const Gambit::GameNode &p_node) co return nullptr; } -int gbtTreeLayout::LayoutSubtree(const Gambit::GameNode &p_node, - const Gambit::BehaviorSupportProfile &p_support, int &p_maxy, - int &p_miny, int &p_ycoord) +int gbtTreeLayout::LayoutSubtree(const GameNode &p_node, const BehaviorSupportProfile &p_support, + int &p_maxy, int &p_miny, int &p_ycoord) { int y1 = -1, yn = 0; const gbtStyle &settings = m_doc->GetStyle(); @@ -561,7 +563,7 @@ int gbtTreeLayout::LayoutSubtree(const Gambit::GameNode &p_node, entry->SetNextMember(nullptr); if (m_doc->GetStyle().RootReachable() && p_node->GetInfoset() && !p_node->GetInfoset()->GetPlayer()->IsChance()) { - Gambit::GameInfoset infoset = p_node->GetInfoset(); + const GameInfoset infoset = p_node->GetInfoset(); for (auto action : p_support.GetActions(infoset)) { yn = LayoutSubtree(p_node->GetChild(action), p_support, p_maxy, p_miny, p_ycoord); if (y1 == -1) { @@ -571,17 +573,16 @@ int gbtTreeLayout::LayoutSubtree(const Gambit::GameNode &p_node, entry->SetY((y1 + yn) / 2); } else { - if (p_node->NumChildren() > 0) { - for (int i = 1; i <= p_node->NumChildren(); i++) { - yn = LayoutSubtree(p_node->GetChild(i), p_support, p_maxy, p_miny, p_ycoord); + if (!p_node->IsTerminal()) { + for (const auto &action : p_node->GetInfoset()->GetActions()) { + yn = LayoutSubtree(p_node->GetChild(action), p_support, p_maxy, p_miny, p_ycoord); if (y1 == -1) { y1 = yn; } - if (!p_node->GetPlayer()->IsChance() && - !p_support.Contains(p_node->GetInfoset()->GetAction(i))) { + if (!p_node->GetPlayer()->IsChance() && !p_support.Contains(action)) { (*std::find_if(m_nodeList.begin(), m_nodeList.end(), [&](const gbtNodeEntry *e) { - return e->GetNode() == p_node->GetChild(i); + return e->GetNode() == p_node->GetChild(action); }))->SetInSupport(false); } } @@ -667,7 +668,7 @@ void gbtTreeLayout::CheckInfosetEntry(gbtNodeEntry *e) // processed and return infoset_entry = NextInfoset(e); for (const auto &e1 : m_nodeList) { - // if the infosets are the same and they are on the same level and e1 has been processed + // if the infosets are the same, and they are on the same level, and e1 has been processed if (e->GetNode()->GetInfoset() == e1->GetNode()->GetInfoset() && e->GetLevel() == e1->GetLevel() && e1->GetSublevel() > 0) { e->SetSublevel(e1->GetSublevel()); @@ -699,20 +700,15 @@ void gbtTreeLayout::CheckInfosetEntry(gbtNodeEntry *e) e->SetNextMember(infoset_entry); } -void gbtTreeLayout::FillInfosetTable(const Gambit::GameNode &n, - const Gambit::BehaviorSupportProfile &cur_sup) +void gbtTreeLayout::FillInfosetTable(const GameNode &n, const BehaviorSupportProfile &cur_sup) { const gbtStyle &draw_settings = m_doc->GetStyle(); gbtNodeEntry *entry = GetNodeEntry(n); - if (n->NumChildren() > 0) { - for (int i = 1; i <= n->NumChildren(); i++) { - bool in_sup = true; - if (n->GetPlayer()->GetNumber()) { - in_sup = cur_sup.Contains(n->GetInfoset()->GetAction(i)); - } - + if (!n->IsTerminal()) { + for (const auto &action : n->GetInfoset()->GetActions()) { + const bool in_sup = n->GetPlayer()->IsChance() || cur_sup.Contains(action); if (in_sup || !draw_settings.RootReachable()) { - FillInfosetTable(n->GetChild(i), cur_sup); + FillInfosetTable(n->GetChild(action), cur_sup); } } } @@ -726,7 +722,7 @@ void gbtTreeLayout::UpdateTableInfosets() { // Note that levels are numbered from 0, not 1. // create an array to hold max num for each level - Gambit::Array nums(0, m_maxLevel + 1); + Array nums(0, m_maxLevel + 1); for (int i = 0; i <= m_maxLevel + 1; nums[i++] = 0) ; @@ -757,7 +753,7 @@ void gbtTreeLayout::UpdateTableParents() } } -void gbtTreeLayout::Layout(const Gambit::BehaviorSupportProfile &p_support) +void gbtTreeLayout::Layout(const BehaviorSupportProfile &p_support) { // Kinda kludgey; probably should query draw settings whenever needed. m_infosetSpacing = (m_doc->GetStyle().InfosetJoin() == GBT_INFOSET_JOIN_LINES) ? 10 : 40; @@ -783,15 +779,15 @@ void gbtTreeLayout::Layout(const Gambit::BehaviorSupportProfile &p_support) m_maxY = maxy + 25; } -void gbtTreeLayout::BuildNodeList(const Gambit::GameNode &p_node, - const Gambit::BehaviorSupportProfile &p_support, int p_level) +void gbtTreeLayout::BuildNodeList(const GameNode &p_node, const BehaviorSupportProfile &p_support, + int p_level) { auto *entry = new gbtNodeEntry(p_node); entry->SetStyle(&m_doc->GetStyle()); m_nodeList.push_back(entry); entry->SetLevel(p_level); if (m_doc->GetStyle().RootReachable()) { - Gambit::GameInfoset infoset = p_node->GetInfoset(); + const GameInfoset infoset = p_node->GetInfoset(); if (infoset) { if (infoset->GetPlayer()->IsChance()) { for (const auto &child : p_node->GetChildren()) { @@ -813,7 +809,7 @@ void gbtTreeLayout::BuildNodeList(const Gambit::GameNode &p_node, m_maxLevel = std::max(p_level, m_maxLevel); } -void gbtTreeLayout::BuildNodeList(const Gambit::BehaviorSupportProfile &p_support) +void gbtTreeLayout::BuildNodeList(const BehaviorSupportProfile &p_support) { for (auto entry : m_nodeList) { delete entry; @@ -837,16 +833,16 @@ void gbtTreeLayout::GenerateLabels() entry->SetBranchBelowLabel(CreateBranchLabel(entry, settings.BranchBelowLabel())); entry->SetBranchBelowFont(settings.GetFont()); - Gambit::GameNode parent = entry->GetNode()->GetParent(); + const GameNode parent = entry->GetNode()->GetParent(); if (parent->GetPlayer()->IsChance()) { - entry->SetActionProb( - static_cast(parent->GetInfoset()->GetActionProb(entry->GetChildNumber()))); + entry->SetActionProb(static_cast(parent->GetInfoset()->GetActionProb( + parent->GetInfoset()->GetAction(entry->GetChildNumber())))); } else { - int profile = m_doc->GetCurrentProfile(); + const int profile = m_doc->GetCurrentProfile(); if (profile > 0) { try { - entry->SetActionProb((double)Gambit::lexical_cast( + entry->SetActionProb((double)lexical_cast( m_doc->GetProfiles().GetActionProb(parent, entry->GetChildNumber()))); } catch (ValueException &) { @@ -884,8 +880,8 @@ void gbtTreeLayout::RenderSubtree(wxDC &p_dc, bool p_noHints) const if (m_doc->GetStyle().InfosetConnect() != GBT_INFOSET_CONNECT_NONE && parentEntry->GetNextMember()) { - int nextX = parentEntry->GetNextMember()->X(); - int nextY = parentEntry->GetNextMember()->Y(); + const int nextX = parentEntry->GetNextMember()->X(); + const int nextY = parentEntry->GetNextMember()->Y(); if ((m_doc->GetStyle().InfosetConnect() != GBT_INFOSET_CONNECT_SAMELEVEL) || parentEntry->X() == nextX) { @@ -940,7 +936,7 @@ void gbtTreeLayout::RenderSubtree(wxDC &p_dc, bool p_noHints) const } } - if (entry->GetNode()->NumChildren() == 0) { + if (entry->GetNode()->IsTerminal()) { entry->Draw(p_dc, m_doc->GetSelectNode(), p_noHints); } diff --git a/src/gui/efgpanel.cc b/src/gui/efgpanel.cc index 2a82a5f97..024eee460 100644 --- a/src/gui/efgpanel.cc +++ b/src/gui/efgpanel.cc @@ -198,10 +198,10 @@ gbtTreePlayerIcon::gbtTreePlayerIcon(wxWindow *p_parent, int p_player) void gbtTreePlayerIcon::OnLeftClick(wxMouseEvent &) { - wxBitmap bitmap(person_xpm); + const wxBitmap bitmap(person_xpm); #if defined(__WXMSW__) or defined(__WXMAC__) - wxImage image = bitmap.ConvertToImage(); + const wxImage image = bitmap.ConvertToImage(); #else wxIcon image; image.CopyFromBitmap(bitmap); @@ -322,7 +322,7 @@ void gbtTreePlayerPanel::OnUpdate() return; } - wxColour color = m_doc->GetStyle().GetPlayerColor(m_player); + const wxColour color = m_doc->GetStyle().GetPlayerColor(m_player); m_playerLabel->SetForegroundColour(color); m_playerLabel->SetValue( @@ -330,11 +330,11 @@ void gbtTreePlayerPanel::OnUpdate() m_payoff->SetForegroundColour(color); if (m_doc->GetCurrentProfile() > 0) { - std::string pay = m_doc->GetProfiles().GetPayoff(m_player); + const std::string pay = m_doc->GetProfiles().GetPayoff(m_player); m_payoff->SetLabel(wxT("Payoff: ") + wxString(pay.c_str(), *wxConvCurrent)); GetSizer()->Show(m_payoff, true); - Gambit::GameNode node = m_doc->GetSelectNode(); + const Gambit::GameNode node = m_doc->GetSelectNode(); if (node) { m_nodeValue->SetForegroundColour(color); @@ -344,7 +344,7 @@ void gbtTreePlayerPanel::OnUpdate() if (node->GetInfoset() && node->GetPlayer()->GetNumber() == m_player) { m_nodeProb->SetForegroundColour(color); - std::string value = m_doc->GetProfiles().GetRealizProb(node); + value = m_doc->GetProfiles().GetRealizProb(node); m_nodeProb->SetLabel(wxT("Node reached: ") + wxString(value.c_str(), *wxConvCurrent)); GetSizer()->Show(m_nodeProb, true); @@ -407,7 +407,7 @@ void gbtTreePlayerPanel::OnSetColor(wxCommandEvent &) dialog.SetTitle(wxString::Format(_("Choose color for player %d"), m_player)); if (dialog.ShowModal() == wxID_OK) { - wxColour color = dialog.GetColourData().GetColour(); + const wxColour color = dialog.GetColourData().GetColour(); gbtStyle style = m_doc->GetStyle(); style.SetPlayerColor(m_player, color); m_doc->SetStyle(style); @@ -457,10 +457,10 @@ gbtTreeChanceIcon::gbtTreeChanceIcon(wxWindow *p_parent) void gbtTreeChanceIcon::OnLeftClick(wxMouseEvent &) { - wxBitmap bitmap(dice_xpm); + const wxBitmap bitmap(dice_xpm); #if defined(__WXMSW__) or defined(__WXMAC__) - wxImage image = bitmap.ConvertToImage(); + const wxImage image = bitmap.ConvertToImage(); #else wxIcon image; image.CopyFromBitmap(bitmap); @@ -537,7 +537,7 @@ void gbtTreeChancePanel::OnSetColor(wxCommandEvent &) dialog.SetTitle(wxT("Choose color for chance player")); if (dialog.ShowModal() == wxID_OK) { - wxColour color = dialog.GetColourData().GetColour(); + const wxColour color = dialog.GetColourData().GetColour(); gbtStyle style = m_doc->GetStyle(); style.SetChanceColor(color); m_doc->SetStyle(style); @@ -571,7 +571,7 @@ gbtTreePlayerToolbar::gbtTreePlayerToolbar(wxWindow *p_parent, gbtGameDocument * topSizer->Add(m_chancePanel, 0, wxALL | wxEXPAND, 5); - for (int pl = 1; pl <= m_doc->NumPlayers(); pl++) { + for (size_t pl = 1; pl <= m_doc->NumPlayers(); pl++) { m_playerPanels.push_back(new gbtTreePlayerPanel(this, m_doc, pl)); topSizer->Add(m_playerPanels[pl], 0, wxALL | wxEXPAND, 5); } @@ -595,18 +595,15 @@ void gbtTreePlayerToolbar::OnUpdate() m_playerPanels.pop_back(); } - for (int pl = 1; pl <= m_playerPanels.size(); pl++) { - m_playerPanels[pl]->OnUpdate(); - } - + std::for_each(m_playerPanels.begin(), m_playerPanels.end(), + std::mem_fn(&gbtTreePlayerPanel::OnUpdate)); GetSizer()->Layout(); } void gbtTreePlayerToolbar::PostPendingChanges() { - for (int pl = 1; pl <= m_playerPanels.size(); pl++) { - m_playerPanels[pl]->PostPendingChanges(); - } + std::for_each(m_playerPanels.begin(), m_playerPanels.end(), + std::mem_fn(&gbtTreePlayerPanel::PostPendingChanges)); } //===================================================================== @@ -716,8 +713,8 @@ bool gbtEfgPanel::GetBitmap(wxBitmap &p_bitmap, int p_marginX, int p_marginY) void gbtEfgPanel::GetSVG(const wxString &p_filename, int p_marginX, int p_marginY) { // The size of the image to be drawn - int maxX = m_treeWindow->GetLayout().MaxX(); - int maxY = m_treeWindow->GetLayout().MaxY(); + const int maxX = m_treeWindow->GetLayout().MaxX(); + const int maxY = m_treeWindow->GetLayout().MaxY(); wxSVGFileDC dc(p_filename, maxX + 2 * p_marginX, maxY + 2 * p_marginY); // For some reason, this needs to be initialized @@ -728,16 +725,16 @@ void gbtEfgPanel::GetSVG(const wxString &p_filename, int p_marginX, int p_margin void gbtEfgPanel::RenderGame(wxDC &p_dc, int p_marginX, int p_marginY) { // The size of the image to be drawn - int maxX = m_treeWindow->GetLayout().MaxX(); - int maxY = m_treeWindow->GetLayout().MaxY(); + const int maxX = m_treeWindow->GetLayout().MaxX(); + const int maxY = m_treeWindow->GetLayout().MaxY(); // Get the size of the DC in pixels wxCoord w, h; p_dc.GetSize(&w, &h); // Calculate a scaling factor - double scaleX = (double)w / (double)(maxX + 2 * p_marginX); - double scaleY = (double)h / (double)(maxY + 2 * p_marginY); + const double scaleX = (double)w / (double)(maxX + 2 * p_marginX); + const double scaleY = (double)h / (double)(maxY + 2 * p_marginY); double scale = (scaleX < scaleY) ? scaleX : scaleY; // Never zoom in if (scale > 1.0) { diff --git a/src/gui/efgprofile.cc b/src/gui/efgprofile.cc index 3ba0e85ce..d139474bb 100644 --- a/src/gui/efgprofile.cc +++ b/src/gui/efgprofile.cc @@ -58,7 +58,7 @@ void gbtBehavProfileList::OnLabelClick(wxSheetEvent &p_event) else { // Clicking on an action column sets the selected node to the first // member of that information set. - Gambit::GameAction action = m_doc->GetGame()->GetAction(p_event.GetCol() + 1); + const Gambit::GameAction action = m_doc->GetAction(p_event.GetCol() + 1); m_doc->SetSelectNode(action->GetInfoset()->GetMember(1)); } } @@ -74,7 +74,7 @@ wxString gbtBehavProfileList::GetCellValue(const wxSheetCoords &p_coords) return wxString::Format(wxT("%d"), p_coords.GetRow() + 1); } else if (IsColLabelCell(p_coords)) { - Gambit::GameAction action = m_doc->GetGame()->GetAction(p_coords.GetCol() + 1); + const Gambit::GameAction action = m_doc->GetAction(p_coords.GetCol() + 1); return (wxString::Format(wxT("%d: "), action->GetInfoset()->GetNumber()) + wxString(action->GetLabel().c_str(), *wxConvCurrent)); } @@ -88,13 +88,13 @@ wxString gbtBehavProfileList::GetCellValue(const wxSheetCoords &p_coords) static wxColour GetPlayerColor(gbtGameDocument *p_doc, int p_index) { - Gambit::GameAction action = p_doc->GetGame()->GetAction(p_index + 1); + const Gambit::GameAction action = p_doc->GetAction(p_index + 1); return p_doc->GetStyle().GetPlayerColor(action->GetInfoset()->GetPlayer()->GetNumber()); } wxSheetCellAttr gbtBehavProfileList::GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const { - int currentProfile = m_doc->GetCurrentProfile(); + const int currentProfile = m_doc->GetCurrentProfile(); if (IsRowLabelCell(p_coords)) { wxSheetCellAttr attr(GetSheetRefData()->m_defaultRowLabelAttr); @@ -136,7 +136,7 @@ wxSheetCellAttr gbtBehavProfileList::GetAttr(const wxSheetCoords &p_coords, wxSh attr.SetRenderer(wxSheetCellRenderer(new gbtRationalRendererRefData())); try { - Gambit::GameAction action = m_doc->GetGame()->GetAction(p_coords.GetCol() + 1); + const Gambit::GameAction action = m_doc->GetAction(p_coords.GetCol() + 1); attr.SetForegroundColour( m_doc->GetStyle().GetPlayerColor(action->GetInfoset()->GetPlayer()->GetNumber())); if (action->GetInfoset()->GetNumber() % 2 == 0) { @@ -162,7 +162,7 @@ void gbtBehavProfileList::OnUpdate() } const gbtAnalysisOutput &profiles = m_doc->GetProfiles(); - int profileLength = m_doc->GetGame()->BehavProfileLength(); + const int profileLength = m_doc->GetGame()->BehavProfileLength(); BeginBatch(); if (GetNumberRows() > profiles.NumProfiles()) { diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 648bd41cf..32b3a76dd 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -42,12 +42,6 @@ gbtBehavDominanceStack::gbtBehavDominanceStack(gbtGameDocument *p_doc, bool p_st Reset(); } -gbtBehavDominanceStack::~gbtBehavDominanceStack() -{ - for (int i = 1; i <= m_supports.size(); delete m_supports[i++]) - ; -} - void gbtBehavDominanceStack::SetStrict(bool p_strict) { if (m_strict != p_strict) { @@ -58,11 +52,9 @@ void gbtBehavDominanceStack::SetStrict(bool p_strict) void gbtBehavDominanceStack::Reset() { - for (int i = 1; i <= m_supports.size(); delete m_supports[i++]) - ; - m_supports = Gambit::Array(); + m_supports.clear(); if (m_doc->IsTree()) { - m_supports.push_back(new Gambit::BehaviorSupportProfile(m_doc->GetGame())); + m_supports.push_back(std::make_shared(m_doc->GetGame())); m_current = 1; } m_noFurther = false; @@ -79,10 +71,10 @@ bool gbtBehavDominanceStack::NextLevel() return false; } - Gambit::BehaviorSupportProfile newSupport = m_supports[m_current]->Undominated(m_strict); + const BehaviorSupportProfile newSupport = m_supports[m_current]->Undominated(m_strict); if (newSupport != *m_supports[m_current]) { - m_supports.push_back(new Gambit::BehaviorSupportProfile(newSupport)); + m_supports.push_back(std::make_shared(newSupport)); m_current++; return true; } @@ -113,12 +105,6 @@ gbtStrategyDominanceStack::gbtStrategyDominanceStack(gbtGameDocument *p_doc, boo Reset(); } -gbtStrategyDominanceStack::~gbtStrategyDominanceStack() -{ - for (int i = 1; i <= m_supports.size(); delete m_supports[i++]) - ; -} - void gbtStrategyDominanceStack::SetStrict(bool p_strict) { if (m_strict != p_strict) { @@ -129,10 +115,8 @@ void gbtStrategyDominanceStack::SetStrict(bool p_strict) void gbtStrategyDominanceStack::Reset() { - for (int i = 1; i <= m_supports.size(); delete m_supports[i++]) - ; - m_supports = Gambit::Array(); - m_supports.push_back(new Gambit::StrategySupportProfile(m_doc->GetGame())); + m_supports.clear(); + m_supports.push_back(std::make_shared(m_doc->GetGame())); m_current = 1; m_noFurther = false; } @@ -148,10 +132,10 @@ bool gbtStrategyDominanceStack::NextLevel() return false; } - Gambit::StrategySupportProfile newSupport = m_supports[m_current]->Undominated(m_strict); + const StrategySupportProfile newSupport = m_supports[m_current]->Undominated(m_strict); if (newSupport != *m_supports[m_current]) { - m_supports.push_back(new Gambit::StrategySupportProfile(newSupport)); + m_supports.push_back(std::make_shared(newSupport)); m_current++; return true; } @@ -176,7 +160,7 @@ bool gbtStrategyDominanceStack::PreviousLevel() // class gbtGameDocument //========================================================================= -gbtGameDocument::gbtGameDocument(Gambit::Game p_game) +gbtGameDocument::gbtGameDocument(Game p_game) : m_game(p_game), m_selectNode(nullptr), m_modified(false), m_behavSupports(this, true), m_stratSupports(this, true), m_currentProfileList(0) { @@ -214,7 +198,7 @@ bool gbtGameDocument::LoadDocument(const wxString &p_filename, bool p_saveUndo) if (efgfile) { try { std::istringstream s(efgfile->FirstChild()->Value()); - m_game = Gambit::ReadGame(s); + m_game = ReadGame(s); } catch (...) { return false; @@ -225,7 +209,7 @@ bool gbtGameDocument::LoadDocument(const wxString &p_filename, bool p_saveUndo) if (nfgfile) { try { std::istringstream s(nfgfile->FirstChild()->Value()); - m_game = Gambit::ReadGame(s); + m_game = ReadGame(s); } catch (...) { return false; @@ -240,7 +224,7 @@ bool gbtGameDocument::LoadDocument(const wxString &p_filename, bool p_saveUndo) m_behavSupports.Reset(); m_stratSupports.Reset(); - m_profiles = Gambit::Array(); + m_profiles.clear(); for (TiXmlNode *analysis = game->FirstChild("analysis"); analysis; analysis = analysis->NextSibling()) { @@ -260,12 +244,12 @@ bool gbtGameDocument::LoadDocument(const wxString &p_filename, bool p_saveUndo) } if (isFloat) { - auto *plist = new gbtAnalysisProfileList(this, false); + auto plist = std::make_shared>(this, false); plist->Load(analysis); m_profiles.push_back(plist); } else { - auto *plist = new gbtAnalysisProfileList(this, false); + auto plist = std::make_shared>(this, false); plist->Load(analysis); m_profiles.push_back(plist); } @@ -335,9 +319,8 @@ void gbtGameDocument::SaveDocument(std::ostream &p_file) const p_file << "\n"; } - for (int i = 1; i <= m_profiles.size(); i++) { - m_profiles[i]->Save(p_file); - } + std::for_each(m_profiles.begin(), m_profiles.end(), + [&p_file](std::shared_ptr a) { a->Save(p_file); }); p_file << "\n"; @@ -363,30 +346,39 @@ void gbtGameDocument::UpdateViews(gbtGameModificationType p_modifications) // computed profiles invalid for the edited game, it does mean // that, in general, they won't be Nash. For now, to avoid confusion, // we will wipe them out. - while (!m_profiles.empty()) { - delete m_profiles.back(); - m_profiles.pop_back(); - } + m_profiles.clear(); m_currentProfileList = 0; } - for (int i = 1; i <= m_views.size(); m_views[i++]->OnUpdate()) - ; + std::for_each(m_views.begin(), m_views.end(), std::mem_fn(&gbtGameView::OnUpdate)); } void gbtGameDocument::PostPendingChanges() { - for (int i = 1; i <= m_views.size(); m_views[i++]->PostPendingChanges()) - ; + std::for_each(m_views.begin(), m_views.end(), std::mem_fn(&gbtGameView::PostPendingChanges)); } void gbtGameDocument::BuildNfg() { if (m_game->IsTree()) { m_stratSupports.Reset(); - for (int i = 1; i <= m_profiles.size(); m_profiles[i++]->BuildNfg()) - ; + std::for_each(m_profiles.begin(), m_profiles.end(), std::mem_fn(&gbtAnalysisOutput::BuildNfg)); + } +} + +GameAction gbtGameDocument::GetAction(int p_index) const +{ + int index = 1; + for (auto player : m_game->GetPlayers()) { + for (auto infoset : player->GetInfosets()) { + for (auto action : infoset->GetActions()) { + if (index++ == p_index) { + return action; + } + } + } } + throw IndexException(); } void gbtGameDocument::SetStyle(const gbtStyle &p_style) @@ -411,13 +403,10 @@ void gbtGameDocument::Undo() m_game = nullptr; - while (!m_profiles.empty()) { - delete m_profiles.back(); - m_profiles.pop_back(); - } + m_profiles.clear(); m_currentProfileList = 0; - wxString tempfile = wxFileName::CreateTempFileName(wxT("gambit")); + const wxString tempfile = wxFileName::CreateTempFileName(wxT("gambit")); std::ofstream f((const char *)tempfile.mb_str()); f << m_undoList.back() << std::endl; f.close(); @@ -425,8 +414,7 @@ void gbtGameDocument::Undo() LoadDocument(tempfile, false); wxRemoveFile(tempfile); - for (int i = 1; i <= m_views.size(); m_views[i++]->OnUpdate()) - ; + std::for_each(m_views.begin(), m_views.end(), std::mem_fn(&gbtGameView::OnUpdate)); } void gbtGameDocument::Redo() @@ -436,13 +424,10 @@ void gbtGameDocument::Redo() m_game = nullptr; - while (!m_profiles.empty()) { - delete m_profiles.back(); - m_profiles.pop_back(); - } + m_profiles.clear(); m_currentProfileList = 0; - wxString tempfile = wxFileName::CreateTempFileName(wxT("gambit")); + const wxString tempfile = wxFileName::CreateTempFileName(wxT("gambit")); std::ofstream f((const char *)tempfile.mb_str()); f << m_undoList.back() << std::endl; f.close(); @@ -450,8 +435,7 @@ void gbtGameDocument::Redo() LoadDocument(tempfile, false); wxRemoveFile(tempfile); - for (int i = 1; i <= m_views.size(); m_views[i++]->OnUpdate()) - ; + std::for_each(m_views.begin(), m_views.end(), [](gbtGameView *v) { v->OnUpdate(); }); } void gbtGameDocument::SetCurrentProfile(int p_profile) @@ -460,7 +444,7 @@ void gbtGameDocument::SetCurrentProfile(int p_profile) UpdateViews(GBT_DOC_MODIFIED_VIEWS); } -void gbtGameDocument::AddProfileList(gbtAnalysisOutput *p_profs) +void gbtGameDocument::AddProfileList(std::shared_ptr p_profs) { m_profiles.push_back(p_profs); m_currentProfileList = m_profiles.size(); @@ -474,7 +458,7 @@ void gbtGameDocument::SetProfileList(int p_index) } /* -void gbtGameDocument::AddProfiles(const Gambit::List > +void gbtGameDocument::AddProfiles(const List > &p_profiles) { for (int i = 1; i <= p_profiles.Length(); i++) { @@ -485,14 +469,14 @@ void gbtGameDocument::AddProfiles(const Gambit::List &p_profile) +void gbtGameDocument::AddProfile(const MixedBehavProfile &p_profile) { m_profiles[m_currentProfileList].Append(p_profile); m_profiles[m_currentProfileList].SetCurrent(m_profiles[m_currentProfileList].NumProfiles()); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } -void gbtGameDocument::AddProfiles(const Gambit::List > +void gbtGameDocument::AddProfiles(const List > &p_profiles) { for (int i = 1; i <= p_profiles.Length(); i++) { @@ -503,7 +487,7 @@ void gbtGameDocument::AddProfiles(const Gambit::List &p_profile) +void gbtGameDocument::AddProfile(const MixedStrategyProfile &p_profile) { m_profiles[m_currentProfileList].Append(p_profile); m_profiles[m_currentProfileList].SetCurrent(m_profiles[m_currentProfileList].NumProfiles()); @@ -519,7 +503,7 @@ void gbtGameDocument::SetBehavElimStrength(bool p_strict) bool gbtGameDocument::NextBehavElimLevel() { - bool ret = m_behavSupports.NextLevel(); + const bool ret = m_behavSupports.NextLevel(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); return ret; } @@ -550,7 +534,7 @@ bool gbtGameDocument::GetStrategyElimStrength() const { return m_stratSupports.G bool gbtGameDocument::NextStrategyElimLevel() { - bool ret = m_stratSupports.NextLevel(); + const bool ret = m_stratSupports.NextLevel(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); return ret; } @@ -571,7 +555,7 @@ bool gbtGameDocument::CanStrategyElim() const { return m_stratSupports.CanElimin int gbtGameDocument::GetStrategyElimLevel() const { return m_stratSupports.GetLevel(); } -void gbtGameDocument::SetSelectNode(Gambit::GameNode p_node) +void gbtGameDocument::SetSelectNode(GameNode p_node) { m_selectNode = p_node; UpdateViews(GBT_DOC_MODIFIED_VIEWS); @@ -614,7 +598,7 @@ void gbtGameDocument::DoSetTitle(const wxString &p_title, const wxString &p_comm void gbtGameDocument::DoNewPlayer() { - GamePlayer player = m_game->NewPlayer(); + const GamePlayer player = m_game->NewPlayer(); player->SetLabel("Player " + lexical_cast(player->GetNumber())); if (!m_game->IsTree()) { player->GetStrategy(1)->SetLabel("1"); @@ -624,38 +608,37 @@ void gbtGameDocument::DoNewPlayer() void gbtGameDocument::DoSetPlayerLabel(GamePlayer p_player, const wxString &p_label) { - p_player->SetLabel(static_cast(p_label.mb_str())); + p_player->SetLabel(p_label.ToStdString()); UpdateViews(GBT_DOC_MODIFIED_LABELS); } void gbtGameDocument::DoNewStrategy(GamePlayer p_player) { - GameStrategy strategy = p_player->NewStrategy(); - strategy->SetLabel(lexical_cast(strategy->GetNumber())); + m_game->NewStrategy(p_player, std::to_string(p_player->GetStrategies().size() + 1)); UpdateViews(GBT_DOC_MODIFIED_GAME); } void gbtGameDocument::DoDeleteStrategy(GameStrategy p_strategy) { - p_strategy->DeleteStrategy(); + m_game->DeleteStrategy(p_strategy); UpdateViews(GBT_DOC_MODIFIED_GAME); } void gbtGameDocument::DoSetStrategyLabel(GameStrategy p_strategy, const wxString &p_label) { - p_strategy->SetLabel(static_cast(p_label.mb_str())); + p_strategy->SetLabel(p_label.ToStdString()); UpdateViews(GBT_DOC_MODIFIED_LABELS); } void gbtGameDocument::DoSetInfosetLabel(GameInfoset p_infoset, const wxString &p_label) { - p_infoset->SetLabel(static_cast(p_label.mb_str())); + p_infoset->SetLabel(p_label.ToStdString()); UpdateViews(GBT_DOC_MODIFIED_LABELS); } void gbtGameDocument::DoSetActionLabel(GameAction p_action, const wxString &p_label) { - p_action->SetLabel(static_cast(p_label.mb_str())); + p_action->SetLabel(p_label.ToStdString()); UpdateViews(GBT_DOC_MODIFIED_LABELS); } @@ -667,19 +650,19 @@ void gbtGameDocument::DoSetActionProbs(GameInfoset p_infoset, const ArraySetInfoset(p_infoset); + m_game->SetInfoset(p_node, p_infoset); UpdateViews(GBT_DOC_MODIFIED_GAME); } void gbtGameDocument::DoLeaveInfoset(GameNode p_node) { - p_node->LeaveInfoset(); + m_game->LeaveInfoset(p_node); UpdateViews(GBT_DOC_MODIFIED_GAME); } void gbtGameDocument::DoRevealAction(GameInfoset p_infoset, GamePlayer p_player) { - p_infoset->Reveal(p_player); + m_game->Reveal(p_infoset, p_player); UpdateViews(GBT_DOC_MODIFIED_GAME); } @@ -688,47 +671,47 @@ void gbtGameDocument::DoInsertAction(GameNode p_node) if (!p_node || !p_node->GetInfoset()) { return; } - GameAction action = p_node->GetInfoset()->InsertAction(); - action->SetLabel(lexical_cast(action->GetNumber())); + const GameAction action = m_game->InsertAction(p_node->GetInfoset()); + action->SetLabel(std::to_string(action->GetNumber())); UpdateViews(GBT_DOC_MODIFIED_GAME); } void gbtGameDocument::DoSetNodeLabel(GameNode p_node, const wxString &p_label) { - p_node->SetLabel(static_cast(p_label.mb_str())); + p_node->SetLabel(p_label.ToStdString()); UpdateViews(GBT_DOC_MODIFIED_LABELS); } void gbtGameDocument::DoAppendMove(GameNode p_node, GameInfoset p_infoset) { - p_node->AppendMove(p_infoset); + m_game->AppendMove(p_node, p_infoset); UpdateViews(GBT_DOC_MODIFIED_GAME); } void gbtGameDocument::DoInsertMove(GameNode p_node, GamePlayer p_player, unsigned int p_actions) { - GameInfoset infoset = p_node->InsertMove(p_player, p_actions); - for (int act = 1; act <= infoset->NumActions(); act++) { - infoset->GetAction(act)->SetLabel(lexical_cast(act)); - } + const GameInfoset infoset = m_game->InsertMove(p_node, p_player, p_actions); + auto actions = infoset->GetActions(); + std::for_each(actions.begin(), actions.end(), + [act = 1](const GameAction &a) mutable { a->SetLabel(std::to_string(act)); }); UpdateViews(GBT_DOC_MODIFIED_GAME); } void gbtGameDocument::DoInsertMove(GameNode p_node, GameInfoset p_infoset) { - p_node->InsertMove(p_infoset); + m_game->InsertMove(p_node, p_infoset); UpdateViews(GBT_DOC_MODIFIED_GAME); } void gbtGameDocument::DoCopyTree(GameNode p_destNode, GameNode p_srcNode) { - p_destNode->CopyTree(p_srcNode); + m_game->CopyTree(p_destNode, p_srcNode); UpdateViews(GBT_DOC_MODIFIED_GAME); } void gbtGameDocument::DoMoveTree(GameNode p_destNode, GameNode p_srcNode) { - p_destNode->MoveTree(p_srcNode); + m_game->MoveTree(p_destNode, p_srcNode); UpdateViews(GBT_DOC_MODIFIED_GAME); } @@ -737,13 +720,13 @@ void gbtGameDocument::DoDeleteParent(GameNode p_node) if (!p_node || !p_node->GetParent()) { return; } - p_node->DeleteParent(); + m_game->DeleteParent(p_node); UpdateViews(GBT_DOC_MODIFIED_GAME); } void gbtGameDocument::DoDeleteTree(GameNode p_node) { - p_node->DeleteTree(); + m_game->DeleteTree(p_node); UpdateViews(GBT_DOC_MODIFIED_GAME); } @@ -751,7 +734,7 @@ void gbtGameDocument::DoSetPlayer(GameInfoset p_infoset, GamePlayer p_player) { if (!p_player->IsChance() && !p_infoset->GetPlayer()->IsChance()) { // Currently don't support switching nodes to/from chance player - p_infoset->SetPlayer(p_player); + m_game->SetPlayer(p_infoset, p_player); UpdateViews(GBT_DOC_MODIFIED_GAME); } } @@ -760,14 +743,14 @@ void gbtGameDocument::DoSetPlayer(GameNode p_node, GamePlayer p_player) { if (!p_player->IsChance() && !p_node->GetPlayer()->IsChance()) { // Currently don't support switching nodes to/from chance player - p_node->GetInfoset()->SetPlayer(p_player); + m_game->SetPlayer(p_node->GetInfoset(), p_player); UpdateViews(GBT_DOC_MODIFIED_GAME); } } void gbtGameDocument::DoNewOutcome(GameNode p_node) { - p_node->SetOutcome(m_game->NewOutcome()); + m_game->SetOutcome(p_node, m_game->NewOutcome()); UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); } @@ -779,7 +762,7 @@ void gbtGameDocument::DoNewOutcome(const PureStrategyProfile &p_profile) void gbtGameDocument::DoSetOutcome(GameNode p_node, GameOutcome p_outcome) { - p_node->SetOutcome(p_outcome); + m_game->SetOutcome(p_node, p_outcome); UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); } @@ -788,24 +771,24 @@ void gbtGameDocument::DoRemoveOutcome(GameNode p_node) if (!p_node || !p_node->GetOutcome()) { return; } - p_node->SetOutcome(nullptr); + m_game->SetOutcome(p_node, nullptr); UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); } void gbtGameDocument::DoCopyOutcome(GameNode p_node, GameOutcome p_outcome) { - GameOutcome outcome = m_game->NewOutcome(); + const GameOutcome outcome = m_game->NewOutcome(); outcome->SetLabel("Outcome" + lexical_cast(outcome->GetNumber())); - for (int pl = 1; pl <= m_game->NumPlayers(); pl++) { - outcome->SetPayoff(pl, p_outcome->GetPayoff(pl)); + for (const auto &player : m_game->GetPlayers()) { + outcome->SetPayoff(player, p_outcome->GetPayoff(player)); } - p_node->SetOutcome(outcome); + m_game->SetOutcome(p_node, outcome); UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); } void gbtGameDocument::DoSetPayoff(GameOutcome p_outcome, int p_player, const wxString &p_value) { - p_outcome->SetPayoff(p_player, Number(p_value.ToStdString())); + p_outcome->SetPayoff(m_game->GetPlayer(p_player), Number(p_value.ToStdString())); UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); } diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 37161b179..5c76153a8 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -40,13 +40,13 @@ class gbtBehavDominanceStack { private: gbtGameDocument *m_doc; bool m_strict; - Gambit::Array m_supports; - int m_current{0}; + Array> m_supports; + size_t m_current{0}; bool m_noFurther; public: gbtBehavDominanceStack(gbtGameDocument *p_doc, bool p_strict); - ~gbtBehavDominanceStack(); + ~gbtBehavDominanceStack() = default; //! //! Returns the number of supports in the stack @@ -57,12 +57,12 @@ class gbtBehavDominanceStack { //! Get the i'th support in the stack //! (where i=1 is always the "full" support) //! - const Gambit::BehaviorSupportProfile &GetSupport(int i) const { return *m_supports[i]; } + const BehaviorSupportProfile &GetSupport(int i) const { return *m_supports[i]; } //! //! Get the current support //! - const Gambit::BehaviorSupportProfile &GetCurrent() const { return *m_supports[m_current]; } + const BehaviorSupportProfile &GetCurrent() const { return *m_supports[m_current]; } //! //! Get the level of iteration (1 = no iteration) @@ -111,13 +111,13 @@ class gbtStrategyDominanceStack { private: gbtGameDocument *m_doc; bool m_strict; - Gambit::Array m_supports; - int m_current{0}; + Array> m_supports; + size_t m_current{0}; bool m_noFurther; public: gbtStrategyDominanceStack(gbtGameDocument *p_doc, bool p_strict); - ~gbtStrategyDominanceStack(); + ~gbtStrategyDominanceStack() = default; //! //! Returns the number of supports in the stack @@ -128,12 +128,12 @@ class gbtStrategyDominanceStack { //! Get the i'th support in the stack //! (where i=1 is always the "full" support) //! - const Gambit::StrategySupportProfile &GetSupport(int i) const { return *m_supports[i]; } + const StrategySupportProfile &GetSupport(int i) const { return *m_supports[i]; } //! //! Get the current support //! - const Gambit::StrategySupportProfile &GetCurrent() const { return *m_supports[m_current]; } + const StrategySupportProfile &GetCurrent() const { return *m_supports[m_current]; } //! //! Get the level of iteration (1 = no iteration) @@ -211,7 +211,7 @@ class gbtGameDocument { friend class gbtGameView; private: - Gambit::Array m_views; + Array m_views; void AddView(gbtGameView *p_view) { m_views.push_back(p_view); } void RemoveView(gbtGameView *p_view) @@ -222,17 +222,17 @@ class gbtGameDocument { } } - Gambit::Game m_game; + Game m_game; wxString m_filename; gbtStyle m_style; - Gambit::GameNode m_selectNode; + GameNode m_selectNode; bool m_modified; gbtBehavDominanceStack m_behavSupports; gbtStrategyDominanceStack m_stratSupports; - Gambit::Array m_profiles; + Array> m_profiles; int m_currentProfileList; std::list m_undoList, m_redoList; @@ -240,7 +240,7 @@ class gbtGameDocument { void UpdateViews(gbtGameModificationType p_modifications); public: - explicit gbtGameDocument(Gambit::Game p_game); + explicit gbtGameDocument(Game p_game); ~gbtGameDocument(); //! @@ -253,7 +253,7 @@ class gbtGameDocument { void SaveDocument(std::ostream &) const; //@} - Gambit::Game GetGame() const { return m_game; } + Game GetGame() const { return m_game; } void BuildNfg(); const wxString &GetFilename() const { return m_filename; } @@ -265,9 +265,10 @@ class gbtGameDocument { const gbtStyle &GetStyle() const { return m_style; } void SetStyle(const gbtStyle &p_style); - int NumPlayers() const { return m_game->NumPlayers(); } + size_t NumPlayers() const { return m_game->NumPlayers(); } bool IsConstSum() const { return m_game->IsConstSum(); } bool IsTree() const { return m_game->IsTree(); } + GameAction GetAction(int p_index) const; //! //! @name Handling of undo/redo features @@ -286,7 +287,7 @@ class gbtGameDocument { //@{ const gbtAnalysisOutput &GetProfiles() const { return *m_profiles[m_currentProfileList]; } const gbtAnalysisOutput &GetProfiles(int p_index) const { return *m_profiles[p_index]; } - void AddProfileList(gbtAnalysisOutput *); + void AddProfileList(std::shared_ptr p_profs); void SetProfileList(int p_index); int NumProfileLists() const { return m_profiles.size(); } int GetCurrentProfileList() const { return m_currentProfileList; } @@ -297,10 +298,10 @@ class gbtGameDocument { } void SetCurrentProfile(int p_profile); /* - void AddProfiles(const Gambit::List > &); - void AddProfile(const Gambit::MixedBehavProfile &); - void AddProfiles(const Gambit::List > &); - void AddProfile(const Gambit::MixedStrategyProfile &); + void AddProfiles(const List > &); + void AddProfile(const MixedBehavProfile &); + void AddProfiles(const List > &); + void AddProfile(const MixedStrategyProfile &); */ //@} @@ -308,10 +309,7 @@ class gbtGameDocument { //! @name Handling of behavior supports //! //@{ - const Gambit::BehaviorSupportProfile &GetEfgSupport() const - { - return m_behavSupports.GetCurrent(); - } + const BehaviorSupportProfile &GetEfgSupport() const { return m_behavSupports.GetCurrent(); } void SetBehavElimStrength(bool p_strict); bool NextBehavElimLevel(); void PreviousBehavElimLevel(); @@ -324,10 +322,7 @@ class gbtGameDocument { //! @name Handling of strategy supports //! //@{ - const Gambit::StrategySupportProfile &GetNfgSupport() const - { - return m_stratSupports.GetCurrent(); - } + const StrategySupportProfile &GetNfgSupport() const { return m_stratSupports.GetCurrent(); } void SetStrategyElimStrength(bool p_strict); bool GetStrategyElimStrength() const; bool NextStrategyElimLevel(); @@ -337,8 +332,8 @@ class gbtGameDocument { int GetStrategyElimLevel() const; //@} - Gambit::GameNode GetSelectNode() const { return m_selectNode; } - void SetSelectNode(Gambit::GameNode); + GameNode GetSelectNode() const { return m_selectNode; } + void SetSelectNode(GameNode); /// Call to ask viewers to post any pending changes void PostPendingChanges(); diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index 34ca68b95..b0db44293 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -259,7 +259,7 @@ gbtGameFrame::gbtGameFrame(wxWindow *p_parent, gbtGameDocument *p_doc) // entries[9].Set(wxACCEL_NORMAL, WXK_BACK, GBT_MENU_EDIT_DELETE_PARENT); entries[8].Set(wxACCEL_CTRL, (int)'+', GBT_MENU_VIEW_ZOOMIN); entries[9].Set(wxACCEL_CTRL, (int)'-', GBT_MENU_VIEW_ZOOMOUT); - wxAcceleratorTable accel(10, entries); + const wxAcceleratorTable accel(10, entries); SetAcceleratorTable(accel); m_splitter = new wxSplitterWindow(this, wxID_ANY); @@ -320,7 +320,7 @@ void gbtGameFrame::OnUpdate() SetTitle(GetTitle() + wxT(" (unsaved changes)")); } - Gambit::GameNode selectNode = m_doc->GetSelectNode(); + const Gambit::GameNode selectNode = m_doc->GetSelectNode(); wxMenuBar *menuBar = GetMenuBar(); menuBar->Enable(GBT_MENU_FILE_EXPORT_EFG, m_doc->IsTree()); @@ -332,7 +332,7 @@ void gbtGameFrame::OnUpdate() menuBar->Enable(GBT_MENU_EDIT_INSERT_MOVE, selectNode != nullptr); menuBar->Enable(GBT_MENU_EDIT_INSERT_ACTION, selectNode && selectNode->GetInfoset()); menuBar->Enable(GBT_MENU_EDIT_REVEAL, selectNode && selectNode->GetInfoset()); - menuBar->Enable(GBT_MENU_EDIT_DELETE_TREE, selectNode && selectNode->NumChildren() > 0); + menuBar->Enable(GBT_MENU_EDIT_DELETE_TREE, selectNode && !selectNode->IsTerminal()); menuBar->Enable(GBT_MENU_EDIT_DELETE_PARENT, selectNode && selectNode->GetParent()); menuBar->Enable(GBT_MENU_EDIT_REMOVE_OUTCOME, selectNode && selectNode->GetOutcome()); menuBar->Enable(GBT_MENU_EDIT_NODE, selectNode != nullptr); @@ -626,7 +626,7 @@ void gbtGameFrame::MakeToolbar() void gbtGameFrame::OnFileNewEfg(wxCommandEvent &) { - Gambit::Game efg = Gambit::NewTree(); + const Gambit::Game efg = Gambit::NewTree(); efg->SetTitle("Untitled Extensive Game"); efg->NewPlayer()->SetLabel("Player 1"); efg->NewPlayer()->SetLabel("Player 2"); @@ -636,10 +636,10 @@ void gbtGameFrame::OnFileNewEfg(wxCommandEvent &) void gbtGameFrame::OnFileNewNfg(wxCommandEvent &) { - Gambit::Array dim(2); + std::vector dim(2); + dim[0] = 2; dim[1] = 2; - dim[2] = 2; - Gambit::Game nfg = Gambit::NewTable(dim); + const Gambit::Game nfg = Gambit::NewTable(dim); nfg->SetTitle("Untitled Strategic Game"); nfg->GetPlayer(1)->SetLabel("Player 1"); nfg->GetPlayer(2)->SetLabel("Player 2"); @@ -655,21 +655,21 @@ void gbtGameFrame::OnFileOpen(wxCommandEvent &) wxT("Gambit strategic games (*.nfg)|*.nfg|") wxT("All files (*.*)|*.*")); if (dialog.ShowModal() == wxID_OK) { - wxString filename = dialog.GetPath(); + const wxString filename = dialog.GetPath(); wxGetApp().SetCurrentDir(wxPathOnly(filename)); - gbtAppLoadResult result = wxGetApp().LoadFile(filename); + const gbtAppLoadResult result = wxGetApp().LoadFile(filename); if (result == GBT_APP_OPEN_FAILED) { - wxMessageDialog dialog( + wxMessageDialog msgdialog( this, wxT("Gambit could not open file '") + filename + wxT("' for reading."), wxT("Unable to open file"), wxOK | wxICON_ERROR); - dialog.ShowModal(); + msgdialog.ShowModal(); } else if (result == GBT_APP_PARSE_FAILED) { - wxMessageDialog dialog( + wxMessageDialog msgdialog( this, wxT("File '") + filename + wxT("' is not in a format Gambit recognizes."), wxT("Unable to read file"), wxOK | wxICON_ERROR); - dialog.ShowModal(); + msgdialog.ShowModal(); } } } @@ -910,8 +910,8 @@ void gbtGameFrame::OnFileExit(wxCommandEvent &p_event) void gbtGameFrame::OnFileMRUFile(wxCommandEvent &p_event) { - wxString filename = wxGetApp().GetHistoryFile(p_event.GetId() - wxID_FILE1); - gbtAppLoadResult result = wxGetApp().LoadFile(filename); + const wxString filename = wxGetApp().GetHistoryFile(p_event.GetId() - wxID_FILE1); + const gbtAppLoadResult result = wxGetApp().LoadFile(filename); if (result == GBT_APP_OPEN_FAILED) { wxMessageDialog dialog(this, @@ -1009,8 +1009,8 @@ void gbtGameFrame::OnEditReveal(wxCommandEvent &) if (dialog.ShowModal() == wxID_OK) { try { - for (int pl = 1; pl <= dialog.GetPlayers().size(); pl++) { - m_doc->DoRevealAction(m_doc->GetSelectNode()->GetInfoset(), dialog.GetPlayers()[pl]); + for (const auto &player : dialog.GetPlayers()) { + m_doc->DoRevealAction(m_doc->GetSelectNode()->GetInfoset(), player); } } catch (std::exception &ex) { @@ -1033,7 +1033,7 @@ void gbtGameFrame::OnEditNode(wxCommandEvent &) m_doc->DoSetOutcome(m_doc->GetSelectNode(), nullptr); } - if (m_doc->GetSelectNode()->NumChildren() > 0 && + if (!m_doc->GetSelectNode()->IsTerminal() && dialog.GetInfoset() != m_doc->GetSelectNode()->GetInfoset()) { if (dialog.GetInfoset() == nullptr) { m_doc->DoLeaveInfoset(m_doc->GetSelectNode()); @@ -1051,7 +1051,7 @@ void gbtGameFrame::OnEditNode(wxCommandEvent &) void gbtGameFrame::OnEditMove(wxCommandEvent &) { - Gambit::GameInfoset infoset = m_doc->GetSelectNode()->GetInfoset(); + const Gambit::GameInfoset infoset = m_doc->GetSelectNode()->GetInfoset(); if (!infoset) { return; } @@ -1065,8 +1065,8 @@ void gbtGameFrame::OnEditMove(wxCommandEvent &) m_doc->DoSetPlayer(infoset, m_doc->GetGame()->GetPlayer(dialog.GetPlayer())); } - for (int act = 1; act <= infoset->NumActions(); act++) { - m_doc->DoSetActionLabel(infoset->GetAction(act), dialog.GetActionName(act)); + for (const auto &action : infoset->GetActions()) { + m_doc->DoSetActionLabel(action, dialog.GetActionName(action->GetNumber())); } if (infoset->IsChanceInfoset()) { m_doc->DoSetActionProbs(infoset, dialog.GetActionProbs()); @@ -1136,7 +1136,7 @@ void gbtGameFrame::OnViewStrategic(wxCommandEvent &p_event) return; } - int ncont = m_doc->GetGame()->NumStrategyContingencies(); + const int ncont = m_doc->GetGame()->NumStrategyContingencies(); if (!m_nfgPanel && ncont >= 50000) { if (wxMessageBox( wxString::Format(wxT("This game has %d contingencies in strategic form.\n"), ncont) + @@ -1269,7 +1269,7 @@ void gbtGameFrame::OnToolsEquilibrium(wxCommandEvent &) if (dialog.ShowModal() == wxID_OK) { if (dialog.UseStrategic()) { - int ncont = m_doc->GetGame()->NumStrategyContingencies(); + const int ncont = m_doc->GetGame()->NumStrategyContingencies(); if (ncont >= 50000) { if (wxMessageBox(wxString::Format( wxT("This game has %d contingencies in strategic form.\n"), ncont) + @@ -1283,11 +1283,10 @@ void gbtGameFrame::OnToolsEquilibrium(wxCommandEvent &) } } - gbtAnalysisOutput *command = dialog.GetCommand(); + auto command = dialog.GetCommand(); - gbtNashMonitorDialog dialog(this, m_doc, command); - - dialog.ShowModal(); + gbtNashMonitorDialog monitordialog(this, m_doc, command); + monitordialog.ShowModal(); if (!m_splitter->IsSplit()) { if (m_efgPanel && m_efgPanel->IsShown()) { diff --git a/src/gui/nfgpanel.cc b/src/gui/nfgpanel.cc index e54a5cba1..fc6f09109 100644 --- a/src/gui/nfgpanel.cc +++ b/src/gui/nfgpanel.cc @@ -66,10 +66,10 @@ gbtTablePlayerIcon::gbtTablePlayerIcon(wxWindow *p_parent, int p_player) void gbtTablePlayerIcon::OnLeftClick(wxMouseEvent &) { - wxBitmap bitmap(person_xpm); + const wxBitmap bitmap(person_xpm); #if defined(__WXMSW__) or defined(__WXMAC__) - wxImage image = bitmap.ConvertToImage(); + const wxImage image = bitmap.ConvertToImage(); #else wxIcon image; image.CopyFromBitmap(bitmap); @@ -170,7 +170,7 @@ gbtTablePlayerPanel::gbtTablePlayerPanel(wxWindow *p_parent, gbtNfgPanel *p_nfgP void gbtTablePlayerPanel::OnUpdate() { - wxColour color = m_doc->GetStyle().GetPlayerColor(m_player); + const wxColour color = m_doc->GetStyle().GetPlayerColor(m_player); m_playerLabel->SetForegroundColour(color); m_playerLabel->SetValue( @@ -179,7 +179,7 @@ void gbtTablePlayerPanel::OnUpdate() if (m_doc->GetCurrentProfile() > 0) { m_payoff->SetForegroundColour(color); - std::string pay = m_doc->GetProfiles().GetPayoff(m_player); + const std::string pay = m_doc->GetProfiles().GetPayoff(m_player); m_payoff->SetLabel(wxT("Payoff: ") + wxString(pay.c_str(), *wxConvCurrent)); GetSizer()->Show(m_payoff, true); } @@ -216,7 +216,7 @@ void gbtTablePlayerPanel::OnSetColor(wxCommandEvent &) dialog.SetTitle(wxString::Format(_("Choose color for player %d"), m_player)); if (dialog.ShowModal() == wxID_OK) { - wxColour color = dialog.GetColourData().GetColour(); + const wxColour color = dialog.GetColourData().GetColour(); gbtStyle style = m_doc->GetStyle(); style.SetPlayerColor(m_player, color); m_doc->SetStyle(style); @@ -267,7 +267,7 @@ gbtTablePlayerToolbar::gbtTablePlayerToolbar(gbtNfgPanel *p_parent, gbtGameDocum { auto *topSizer = new wxBoxSizer(wxVERTICAL); - for (int pl = 1; pl <= m_doc->NumPlayers(); pl++) { + for (size_t pl = 1; pl <= m_doc->NumPlayers(); pl++) { m_playerPanels.push_back(new gbtTablePlayerPanel(this, p_parent, m_doc, pl)); topSizer->Add(m_playerPanels[pl], 0, wxALL | wxEXPAND, 5); } @@ -291,18 +291,15 @@ void gbtTablePlayerToolbar::OnUpdate() m_playerPanels.pop_back(); } - for (int pl = 1; pl <= m_playerPanels.size(); pl++) { - m_playerPanels[pl]->OnUpdate(); - } - + std::for_each(m_playerPanels.begin(), m_playerPanels.end(), + std::mem_fn(&gbtTablePlayerPanel::OnUpdate)); GetSizer()->Layout(); } void gbtTablePlayerToolbar::PostPendingChanges() { - for (int pl = 1; pl <= m_playerPanels.size(); pl++) { - m_playerPanels[pl]->PostPendingChanges(); - } + std::for_each(m_playerPanels.begin(), m_playerPanels.end(), + std::mem_fn(&gbtTablePlayerPanel::PostPendingChanges)); } //===================================================================== diff --git a/src/gui/nfgprofile.cc b/src/gui/nfgprofile.cc index 199786dd8..b859661d8 100644 --- a/src/gui/nfgprofile.cc +++ b/src/gui/nfgprofile.cc @@ -85,12 +85,11 @@ wxString gbtMixedProfileList::GetCellValue(const wxSheetCoords &p_coords) } else if (IsColLabelCell(p_coords)) { int index = 0; - for (int pl = 1; pl <= m_doc->GetGame()->NumPlayers(); pl++) { - Gambit::GamePlayer player = m_doc->GetGame()->GetPlayer(pl); - for (int st = 1; st <= player->NumStrategies(); st++) { + for (const auto &player : m_doc->GetGame()->GetPlayers()) { + for (const auto &strategy : player->GetStrategies()) { if (index++ == p_coords.GetCol()) { - return (wxString::Format(wxT("%d: "), pl) + - wxString(player->GetStrategy(st)->GetLabel().c_str(), *wxConvCurrent)); + return (wxString::Format(wxT("%d: "), player->GetNumber()) + + wxString(strategy->GetLabel().c_str(), *wxConvCurrent)); } } } @@ -100,7 +99,7 @@ wxString gbtMixedProfileList::GetCellValue(const wxSheetCoords &p_coords) return wxT("#"); } - int profile = RowToProfile(p_coords.GetRow()); + const int profile = RowToProfile(p_coords.GetRow()); if (IsProbabilityRow(p_coords.GetRow())) { return {m_doc->GetProfiles().GetStrategyProb(p_coords.GetCol() + 1, profile).c_str(), @@ -115,11 +114,10 @@ wxString gbtMixedProfileList::GetCellValue(const wxSheetCoords &p_coords) static wxColour GetPlayerColor(gbtGameDocument *p_doc, int p_index) { int index = 0; - for (int pl = 1; pl <= p_doc->GetGame()->NumPlayers(); pl++) { - Gambit::GamePlayer player = p_doc->GetGame()->GetPlayer(pl); - for (int st = 1; st <= player->NumStrategies(); st++) { + for (const auto &player : p_doc->GetGame()->GetPlayers()) { + for (const auto &strategy : player->GetStrategies()) { if (index++ == p_index) { - return p_doc->GetStyle().GetPlayerColor(pl); + return p_doc->GetStyle().GetPlayerColor(player->GetNumber()); } } } @@ -128,7 +126,7 @@ static wxColour GetPlayerColor(gbtGameDocument *p_doc, int p_index) wxSheetCellAttr gbtMixedProfileList::GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const { - int currentProfile = m_doc->GetCurrentProfile(); + const int currentProfile = m_doc->GetCurrentProfile(); if (IsRowLabelCell(p_coords)) { wxSheetCellAttr attr(GetSheetRefData()->m_defaultRowLabelAttr); @@ -183,12 +181,12 @@ void gbtMixedProfileList::OnUpdate() BeginBatch(); - int newRows = profiles.NumProfiles() * (m_showProbs + m_showPayoff); + const int newRows = profiles.NumProfiles() * (m_showProbs + m_showPayoff); DeleteRows(0, GetNumberRows()); InsertRows(0, newRows); - int profileLength = m_doc->GetGame()->MixedProfileLength(); - int newCols = profileLength; + const int profileLength = m_doc->GetGame()->MixedProfileLength(); + const int newCols = profileLength; DeleteCols(0, GetNumberCols()); InsertCols(0, newCols); diff --git a/src/gui/nfgtable.cc b/src/gui/nfgtable.cc index 14f18b985..9cc7d807e 100644 --- a/src/gui/nfgtable.cc +++ b/src/gui/nfgtable.cc @@ -208,10 +208,10 @@ void gbtRowPlayerWidget::OnCellRightClick(wxSheetEvent &p_event) } const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - wxSheetCoords coords = p_event.GetCoords(); + const wxSheetCoords coords = p_event.GetCoords(); - int player = m_table->GetRowPlayer(coords.GetCol() + 1); - int strat = m_table->RowToStrategy(coords.GetCol() + 1, coords.GetRow()); + const int player = m_table->GetRowPlayer(coords.GetCol() + 1); + const int strat = m_table->RowToStrategy(coords.GetCol() + 1, coords.GetRow()); m_doc->DoDeleteStrategy(GetStrategy(support, player, strat)); } @@ -228,8 +228,8 @@ wxString gbtRowPlayerWidget::GetCellValue(const wxSheetCoords &p_coords) const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - int player = m_table->GetRowPlayer(p_coords.GetCol() + 1); - int strat = m_table->RowToStrategy(p_coords.GetCol() + 1, p_coords.GetRow()); + const int player = m_table->GetRowPlayer(p_coords.GetCol() + 1); + const int strat = m_table->RowToStrategy(p_coords.GetCol() + 1, p_coords.GetRow()); return {GetStrategy(support, player, strat)->GetLabel().c_str(), *wxConvCurrent}; } @@ -238,8 +238,8 @@ void gbtRowPlayerWidget::SetCellValue(const wxSheetCoords &p_coords, const wxStr { const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - int player = m_table->GetRowPlayer(p_coords.GetCol() + 1); - int strat = m_table->RowToStrategy(p_coords.GetCol() + 1, p_coords.GetRow()); + const int player = m_table->GetRowPlayer(p_coords.GetCol() + 1); + const int strat = m_table->RowToStrategy(p_coords.GetCol() + 1, p_coords.GetRow()); m_doc->DoSetStrategyLabel(GetStrategy(support, player, strat), p_value); } @@ -272,12 +272,12 @@ void gbtRowPlayerWidget::DrawCell(wxDC &p_dc, const wxSheetCoords &p_coords) } const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - int player = m_table->GetRowPlayer(p_coords.GetCol() + 1); - int strat = m_table->RowToStrategy(p_coords.GetCol() + 1, p_coords.GetRow()); - Gambit::GameStrategy strategy = GetStrategy(support, player, strat); + const int player = m_table->GetRowPlayer(p_coords.GetCol() + 1); + const int strat = m_table->RowToStrategy(p_coords.GetCol() + 1, p_coords.GetRow()); + const Gambit::GameStrategy strategy = GetStrategy(support, player, strat); if (support.IsDominated(strategy, false)) { - wxRect rect = CellToRect(p_coords); + const wxRect rect = CellToRect(p_coords); if (support.IsDominated(strategy, true)) { p_dc.SetPen(wxPen(m_doc->GetStyle().GetPlayerColor(player), 2, wxPENSTYLE_SOLID)); } @@ -291,7 +291,7 @@ void gbtRowPlayerWidget::DrawCell(wxDC &p_dc, const wxSheetCoords &p_coords) void gbtRowPlayerWidget::OnUpdate() { - int newRows = m_table->NumRowContingencies(); + const int newRows = m_table->NumRowContingencies(); if (newRows > GetNumberRows()) { InsertRows(0, newRows - GetNumberRows()); } @@ -299,7 +299,7 @@ void gbtRowPlayerWidget::OnUpdate() DeleteRows(0, GetNumberRows() - newRows); } - int newCols = m_table->NumRowPlayers(); + const int newCols = m_table->NumRowPlayers(); if (newCols > GetNumberCols()) { InsertCols(0, newCols - GetNumberCols()); } @@ -315,7 +315,7 @@ void gbtRowPlayerWidget::OnUpdate() SetCellSpan(wxSheetCoords(row++, col), wxSheetCoords(1, 1))) ; - int span = m_table->NumRowsSpanned(col + 1); + const int span = m_table->NumRowsSpanned(col + 1); int row = 0; while (row < GetNumberRows()) { @@ -339,7 +339,7 @@ bool gbtRowPlayerWidget::DropText(wxCoord p_x, wxCoord p_y, const wxString &p_te } for (int col = 0; col < GetNumberCols(); col++) { - wxRect rect = CellToRect(wxSheetCoords(0, col)); + const wxRect rect = CellToRect(wxSheetCoords(0, col)); if (p_x >= rect.x && p_x < rect.x + rect.width / 2) { m_table->SetRowPlayer(col + 1, pl); @@ -426,17 +426,17 @@ void gbtColPlayerWidget::OnCellRightClick(wxSheetEvent &p_event) } const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - wxSheetCoords coords = p_event.GetCoords(); + const wxSheetCoords coords = p_event.GetCoords(); - int player = m_table->GetColPlayer(coords.GetRow() + 1); - int strat = m_table->RowToStrategy(coords.GetRow() + 1, coords.GetCol()); + const int player = m_table->GetColPlayer(coords.GetRow() + 1); + const int strat = m_table->RowToStrategy(coords.GetRow() + 1, coords.GetCol()); m_doc->DoDeleteStrategy(GetStrategy(support, player, strat)); } void gbtColPlayerWidget::OnUpdate() { - int newCols = m_table->NumColContingencies() * m_doc->NumPlayers(); + const int newCols = m_table->NumColContingencies() * m_doc->NumPlayers(); if (newCols > GetNumberCols()) { InsertCols(0, newCols - GetNumberCols()); } @@ -444,7 +444,7 @@ void gbtColPlayerWidget::OnUpdate() DeleteCols(0, GetNumberCols() - newCols); } - int newRows = m_table->NumColPlayers(); + const int newRows = m_table->NumColPlayers(); if (newRows > GetNumberRows()) { InsertRows(0, newRows - GetNumberRows()); } @@ -460,7 +460,7 @@ void gbtColPlayerWidget::OnUpdate() SetCellSpan(wxSheetCoords(row, col++), wxSheetCoords(1, 1))) ; - int span = m_table->NumColsSpanned(row + 1) * m_doc->NumPlayers(); + const int span = m_table->NumColsSpanned(row + 1) * m_doc->NumPlayers(); int col = 0; while (col < GetNumberCols()) { @@ -484,8 +484,8 @@ wxString gbtColPlayerWidget::GetCellValue(const wxSheetCoords &p_coords) const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - int player = m_table->GetColPlayer(p_coords.GetRow() + 1); - int strat = m_table->ColToStrategy(p_coords.GetRow() + 1, p_coords.GetCol()); + const int player = m_table->GetColPlayer(p_coords.GetRow() + 1); + const int strat = m_table->ColToStrategy(p_coords.GetRow() + 1, p_coords.GetCol()); return {GetStrategy(support, player, strat)->GetLabel().c_str(), *wxConvCurrent}; } @@ -494,8 +494,8 @@ void gbtColPlayerWidget::SetCellValue(const wxSheetCoords &p_coords, const wxStr { const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - int player = m_table->GetColPlayer(p_coords.GetRow() + 1); - int strat = m_table->ColToStrategy(p_coords.GetRow() + 1, p_coords.GetCol()); + const int player = m_table->GetColPlayer(p_coords.GetRow() + 1); + const int strat = m_table->ColToStrategy(p_coords.GetRow() + 1, p_coords.GetCol()); m_doc->DoSetStrategyLabel(GetStrategy(support, player, strat), p_value); } @@ -528,12 +528,12 @@ void gbtColPlayerWidget::DrawCell(wxDC &p_dc, const wxSheetCoords &p_coords) } const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - int player = m_table->GetColPlayer(p_coords.GetRow() + 1); - int strat = m_table->ColToStrategy(p_coords.GetRow() + 1, p_coords.GetCol()); - Gambit::GameStrategy strategy = GetStrategy(support, player, strat); + const int player = m_table->GetColPlayer(p_coords.GetRow() + 1); + const int strat = m_table->ColToStrategy(p_coords.GetRow() + 1, p_coords.GetCol()); + const Gambit::GameStrategy strategy = GetStrategy(support, player, strat); if (support.IsDominated(strategy, false)) { - wxRect rect = CellToRect(p_coords); + const wxRect rect = CellToRect(p_coords); if (support.IsDominated(strategy, true)) { p_dc.SetPen(wxPen(m_doc->GetStyle().GetPlayerColor(player), 2, wxPENSTYLE_SOLID)); } @@ -557,7 +557,7 @@ bool gbtColPlayerWidget::DropText(wxCoord p_x, wxCoord p_y, const wxString &p_te } for (int col = 0; col < GetNumberCols(); col++) { - wxRect rect = CellToRect(wxSheetCoords(col, 0)); + const wxRect rect = CellToRect(wxSheetCoords(col, 0)); if (p_y >= rect.y && p_y < rect.y + rect.height / 2) { m_table->SetColPlayer(col + 1, pl); @@ -638,7 +638,7 @@ gbtPayoffsWidget::gbtPayoffsWidget(gbtTableWidget *p_parent, gbtGameDocument *p_ // int gbtPayoffsWidget::ColToPlayer(int p_col) const { - int index = p_col % m_doc->NumPlayers() + 1; + const int index = p_col % m_doc->NumPlayers() + 1; if (index <= m_table->NumRowPlayers()) { return m_table->GetRowPlayer(index); } @@ -649,7 +649,7 @@ int gbtPayoffsWidget::ColToPlayer(int p_col) const void gbtPayoffsWidget::OnUpdate() { - int newCols = m_table->NumColContingencies() * m_doc->NumPlayers(); + const int newCols = m_table->NumColContingencies() * m_doc->NumPlayers(); if (newCols > GetNumberCols()) { InsertCols(0, newCols - GetNumberCols()); } @@ -657,7 +657,7 @@ void gbtPayoffsWidget::OnUpdate() DeleteCols(0, GetNumberCols() - newCols); } - int newRows = m_table->NumRowContingencies(); + const int newRows = m_table->NumRowContingencies(); if (newRows > GetNumberRows()) { InsertRows(0, newRows - GetNumberRows()); } @@ -674,8 +674,8 @@ wxString gbtPayoffsWidget::GetCellValue(const wxSheetCoords &p_coords) return wxT(""); } - Gambit::PureStrategyProfile profile = m_table->CellToProfile(p_coords); - int player = ColToPlayer(p_coords.GetCol()); + const Gambit::PureStrategyProfile profile = m_table->CellToProfile(p_coords); + auto player = m_doc->GetGame()->GetPlayer(ColToPlayer(p_coords.GetCol())); return {Gambit::lexical_cast(profile->GetPayoff(player)).c_str(), *wxConvCurrent}; } @@ -688,7 +688,7 @@ void gbtPayoffsWidget::SetCellValue(const wxSheetCoords &p_coords, const wxStrin profile = m_table->CellToProfile(p_coords); outcome = profile->GetOutcome(); } - int player = ColToPlayer(p_coords.GetCol()); + const int player = ColToPlayer(p_coords.GetCol()); try { m_doc->DoSetPayoff(outcome, player, p_value); } @@ -714,7 +714,7 @@ wxSheetCellAttr gbtPayoffsWidget::GetAttr(const wxSheetCoords &p_coords, wxSheet attr.SetFont(wxFont(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD)); attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER); attr.SetOrientation(wxHORIZONTAL); - int player = ColToPlayer(p_coords.GetCol()); + const int player = ColToPlayer(p_coords.GetCol()); attr.SetForegroundColour(m_doc->GetStyle().GetPlayerColor(player)); attr.SetRenderer(wxSheetCellRenderer(new gbtRationalRendererRefData())); attr.SetEditor(wxSheetCellEditor(new gbtRationalEditorRefData())); @@ -726,7 +726,7 @@ void gbtPayoffsWidget::DrawCellBorder(wxDC &p_dc, const wxSheetCoords &p_coords) { gbtTableWidgetBase::DrawCellBorder(p_dc, p_coords); - wxRect rect(CellToRect(p_coords)); + const wxRect rect(CellToRect(p_coords)); if (rect.width < 1 || rect.height < 1) { return; } @@ -755,17 +755,19 @@ void gbtPayoffsWidget::DrawCell(wxDC &p_dc, const wxSheetCoords &p_coords) return; } - Gambit::PureStrategyProfile profile = m_table->CellToProfile(p_coords); - int player = ColToPlayer(p_coords.GetCol()); + const Gambit::PureStrategyProfile profile = m_table->CellToProfile(p_coords); + auto player = m_doc->GetGame()->GetPlayer(ColToPlayer(p_coords.GetCol())); const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); if (support.IsDominated(profile->GetStrategy(player), false)) { - wxRect rect = CellToRect(p_coords); + const wxRect rect = CellToRect(p_coords); if (support.IsDominated(profile->GetStrategy(player), true)) { - p_dc.SetPen(wxPen(m_doc->GetStyle().GetPlayerColor(player), 2, wxPENSTYLE_SOLID)); + p_dc.SetPen( + wxPen(m_doc->GetStyle().GetPlayerColor(player->GetNumber()), 2, wxPENSTYLE_SOLID)); } else { - p_dc.SetPen(wxPen(m_doc->GetStyle().GetPlayerColor(player), 1, wxPENSTYLE_SHORT_DASH)); + p_dc.SetPen( + wxPen(m_doc->GetStyle().GetPlayerColor(player->GetNumber()), 1, wxPENSTYLE_SHORT_DASH)); } p_dc.DrawLine(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height); p_dc.DrawLine(rect.x + rect.width, rect.y, rect.x, rect.y + rect.height); @@ -928,7 +930,7 @@ void gbtTableWidget::OnPayoffScroll(wxSheetEvent &) //@{ void gbtTableWidget::OnRowSheetRow(wxSheetEvent &p_event) { - int height = m_rowSheet->GetRowHeight(p_event.GetRow()); + const int height = m_rowSheet->GetRowHeight(p_event.GetRow()); m_payoffSheet->SetDefaultRowHeight(height, true); m_payoffSheet->AdjustScrollbars(); m_payoffSheet->Refresh(); @@ -938,7 +940,7 @@ void gbtTableWidget::OnRowSheetRow(wxSheetEvent &p_event) void gbtTableWidget::OnPayoffRow(wxSheetEvent &p_event) { - int height = m_payoffSheet->GetRowHeight(p_event.GetRow()); + const int height = m_payoffSheet->GetRowHeight(p_event.GetRow()); m_payoffSheet->SetDefaultRowHeight(height, true); m_payoffSheet->AdjustScrollbars(); m_payoffSheet->Refresh(); @@ -953,7 +955,7 @@ void gbtTableWidget::OnPayoffRow(wxSheetEvent &p_event) //@{ void gbtTableWidget::OnColSheetColumn(wxSheetEvent &p_event) { - int width = m_colSheet->GetColWidth(p_event.GetCol()); + const int width = m_colSheet->GetColWidth(p_event.GetCol()); m_payoffSheet->SetDefaultColWidth(width, true); m_payoffSheet->AdjustScrollbars(); m_payoffSheet->Refresh(); @@ -963,7 +965,7 @@ void gbtTableWidget::OnColSheetColumn(wxSheetEvent &p_event) void gbtTableWidget::OnPayoffColumn(wxSheetEvent &p_event) { - int width = m_payoffSheet->GetColWidth(p_event.GetCol()); + const int width = m_payoffSheet->GetColWidth(p_event.GetCol()); m_payoffSheet->SetDefaultColWidth(width, true); m_payoffSheet->AdjustScrollbars(); m_payoffSheet->Refresh(); @@ -998,21 +1000,21 @@ void gbtTableWidget::OnBeginEdit(wxSheetEvent &) { m_doc->PostPendingChanges(); void gbtTableWidget::OnUpdate() { if (m_doc->NumPlayers() > m_rowPlayers.size() + m_colPlayers.size()) { - for (int pl = 1; pl <= m_doc->NumPlayers(); pl++) { + for (size_t pl = 1; pl <= m_doc->NumPlayers(); pl++) { if (!contains(m_rowPlayers, pl) && !contains(m_colPlayers, pl)) { m_rowPlayers.push_back(pl); } } } else if (m_doc->NumPlayers() < m_rowPlayers.size() + m_colPlayers.size()) { - for (int i = 1; i <= m_rowPlayers.size(); i++) { - if (m_rowPlayers[i] > m_doc->NumPlayers()) { + for (size_t i = 1; i <= m_rowPlayers.size(); i++) { + if (m_rowPlayers[i] > static_cast(m_doc->NumPlayers())) { erase_atindex(m_rowPlayers, i--); } } - for (int i = 1; i <= m_colPlayers.size(); i++) { - if (m_colPlayers[i] > m_doc->NumPlayers()) { + for (size_t i = 1; i <= m_colPlayers.size(); i++) { + if (m_colPlayers[i] > static_cast(m_doc->NumPlayers())) { erase_atindex(m_colPlayers, i--); } } @@ -1065,7 +1067,7 @@ void gbtTableWidget::SetRowPlayer(int index, int pl) } Array newPlayers; for (const auto &player : m_rowPlayers) { - if (newPlayers.size() == index - 1) { + if (static_cast(newPlayers.size()) == index - 1) { newPlayers.push_back(pl); } else if (player != pl) { @@ -1101,7 +1103,7 @@ int gbtTableWidget::NumRowsSpanned(int index) const int gbtTableWidget::RowToStrategy(int player, int row) const { - int strat = row / NumRowsSpanned(player); + const int strat = row / NumRowsSpanned(player); return (strat % NumStrategies(m_doc->GetNfgSupport(), GetRowPlayer(player)) + 1); } @@ -1112,7 +1114,7 @@ void gbtTableWidget::SetColPlayer(int index, int pl) } Array newPlayers; for (const auto &player : m_colPlayers) { - if (newPlayers.size() == index - 1) { + if (static_cast(newPlayers.size()) == index - 1) { newPlayers.push_back(pl); } else if (player != pl) { @@ -1143,7 +1145,7 @@ int gbtTableWidget::NumColsSpanned(int index) const int gbtTableWidget::ColToStrategy(int player, int col) const { - int strat = col / m_doc->NumPlayers() / NumColsSpanned(player); + const int strat = col / m_doc->NumPlayers() / NumColsSpanned(player); return (strat % NumStrategies(m_doc->GetNfgSupport(), GetColPlayer(player)) + 1); } @@ -1151,14 +1153,14 @@ Gambit::PureStrategyProfile gbtTableWidget::CellToProfile(const wxSheetCoords &p { const Gambit::StrategySupportProfile &support = m_doc->GetNfgSupport(); - Gambit::PureStrategyProfile profile = m_doc->GetGame()->NewPureStrategyProfile(); + const Gambit::PureStrategyProfile profile = m_doc->GetGame()->NewPureStrategyProfile(); for (int i = 1; i <= NumRowPlayers(); i++) { - int player = GetRowPlayer(i); + const int player = GetRowPlayer(i); profile->SetStrategy(GetStrategy(support, player, RowToStrategy(i, p_coords.GetRow()))); } for (int i = 1; i <= NumColPlayers(); i++) { - int player = GetColPlayer(i); + const int player = GetColPlayer(i); profile->SetStrategy(GetStrategy(support, player, ColToStrategy(i, p_coords.GetCol()))); } @@ -1198,11 +1200,11 @@ wxPrintout *gbtTableWidget::GetPrintout() bool gbtTableWidget::GetBitmap(wxBitmap &p_bitmap, int p_marginX, int p_marginY) { - int width = + const int width = (m_rowSheet->CellToRect(wxSheetCoords(0, m_rowSheet->GetNumberCols() - 1)).GetRight() + m_colSheet->CellToRect(wxSheetCoords(0, m_colSheet->GetNumberCols() - 1)).GetRight() + 2 * p_marginX); - int height = + const int height = (m_rowSheet->CellToRect(wxSheetCoords(m_rowSheet->GetNumberRows() - 1, 0)).GetBottom() + m_colSheet->CellToRect(wxSheetCoords(m_colSheet->GetNumberRows() - 1, 0)).GetBottom() + 2 * p_marginY); @@ -1222,11 +1224,11 @@ bool gbtTableWidget::GetBitmap(wxBitmap &p_bitmap, int p_marginX, int p_marginY) void gbtTableWidget::GetSVG(const wxString &p_filename, int p_marginX, int p_marginY) { - int width = + const int width = (m_rowSheet->CellToRect(wxSheetCoords(0, m_rowSheet->GetNumberCols() - 1)).GetRight() + m_colSheet->CellToRect(wxSheetCoords(0, m_colSheet->GetNumberCols() - 1)).GetRight() + 2 * p_marginX); - int height = + const int height = (m_rowSheet->CellToRect(wxSheetCoords(m_rowSheet->GetNumberRows() - 1, 0)).GetBottom() + m_colSheet->CellToRect(wxSheetCoords(m_colSheet->GetNumberRows() - 1, 0)).GetBottom() + 2 * p_marginY); @@ -1240,10 +1242,10 @@ void gbtTableWidget::GetSVG(const wxString &p_filename, int p_marginX, int p_mar void gbtTableWidget::RenderGame(wxDC &p_dc, int p_marginX, int p_marginY) { // The size of the image to be drawn - int maxX = + const int maxX = (m_rowSheet->CellToRect(wxSheetCoords(0, m_rowSheet->GetNumberCols() - 1)).GetRight() + m_colSheet->CellToRect(wxSheetCoords(0, m_colSheet->GetNumberCols() - 1)).GetRight()); - int maxY = + const int maxY = (m_rowSheet->CellToRect(wxSheetCoords(m_rowSheet->GetNumberRows() - 1, 0)).GetBottom() + m_colSheet->CellToRect(wxSheetCoords(m_colSheet->GetNumberRows() - 1, 0)).GetBottom()); @@ -1252,9 +1254,9 @@ void gbtTableWidget::RenderGame(wxDC &p_dc, int p_marginX, int p_marginY) p_dc.GetSize(&w, &h); // Calculate a scaling factor - double scaleX = (double)w / (double)(maxX + 2 * p_marginX); - double scaleY = (double)h / (double)(maxY + 2 * p_marginY); - double scale = (scaleX < scaleY) ? scaleX : scaleY; + const double scaleX = (double)w / (double)(maxX + 2 * p_marginX); + const double scaleY = (double)h / (double)(maxY + 2 * p_marginY); + const double scale = (scaleX < scaleY) ? scaleX : scaleY; // Here, zooming in is often a good idea, since the number of pixels // on a page is generally quite large p_dc.SetUserScale(scale, scale); @@ -1264,10 +1266,10 @@ void gbtTableWidget::RenderGame(wxDC &p_dc, int p_marginX, int p_marginY) auto posY = (double)((h - (maxY * scale)) / 2.0); // The X and Y coordinates of the upper left of the payoff table - int payoffX = + const int payoffX = (int)(m_rowSheet->CellToRect(wxSheetCoords(0, m_rowSheet->GetNumberCols() - 1)).GetRight() * scale); - int payoffY = + const int payoffY = (int)(m_colSheet->CellToRect(wxSheetCoords(m_colSheet->GetNumberRows() - 1, 0)).GetBottom() * scale); diff --git a/src/gui/renratio.cc b/src/gui/renratio.cc index 7453a458f..5769d16f9 100644 --- a/src/gui/renratio.cc +++ b/src/gui/renratio.cc @@ -79,7 +79,7 @@ static wxSize GetFractionExtent(wxDC &p_dc, const wxString &p_value) int denWidth, denHeight; p_dc.GetTextExtent(den, &denWidth, &denHeight); - int width = ((numWidth > denWidth) ? numWidth : denWidth); + const int width = ((numWidth > denWidth) ? numWidth : denWidth); return {width + 4, numHeight + denHeight}; } @@ -140,9 +140,9 @@ static void DrawFraction(wxDC &p_dc, wxRect p_rect, const wxString &p_value) int denWidth, denHeight; p_dc.GetTextExtent(den, &denWidth, &denHeight); - int width = ((numWidth > denWidth) ? numWidth : denWidth); + const int width = ((numWidth > denWidth) ? numWidth : denWidth); - wxPoint point(p_rect.x, p_rect.y + p_rect.height / 2); + const wxPoint point(p_rect.x, p_rect.y + p_rect.height / 2); p_dc.SetPen(wxPen(p_dc.GetTextForeground(), 1, wxPENSTYLE_SOLID)); p_dc.DrawText(num, point.x + (p_rect.width - numWidth) / 2, point.y - numHeight); @@ -158,9 +158,9 @@ void gbtRationalRendererRefData::DoDraw(wxSheet &grid, const wxSheetCellAttr &at wxRect rect = rectCell; rect.Inflate(-1); - int align = attr.GetAlignment(); + const int align = attr.GetAlignment(); - wxString value = grid.GetCellValue(coords); + const wxString value = grid.GetCellValue(coords); // int best_width = DoGetBestSize(grid, attr, dc, value).GetWidth(); // wxSheetCoords cellSpan(grid.GetCellSpan(coords)); // shouldn't get here if <=0 // int cell_rows = cellSpan.m_row; @@ -203,13 +203,13 @@ bool gbtRationalEditorRefData::Copy(const gbtRationalEditorRefData &p_other) void gbtRationalEditorRefData::StartingKey(wxKeyEvent &event) { - int keycode = event.GetKeyCode(); + const int keycode = event.GetKeyCode(); char tmpbuf[2]; tmpbuf[0] = (char)keycode; tmpbuf[1] = '\0'; - wxString strbuf(tmpbuf, *wxConvCurrent); + const wxString strbuf(tmpbuf, *wxConvCurrent); #if wxUSE_INTL - bool is_decimal_point = + const bool is_decimal_point = (strbuf == wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER)); #else bool is_decimal_point = (strbuf == _T(".")); @@ -225,13 +225,13 @@ void gbtRationalEditorRefData::StartingKey(wxKeyEvent &event) bool gbtRationalEditorRefData::IsAcceptedKey(wxKeyEvent &p_event) { if (wxSheetCellEditorRefData::IsAcceptedKey(p_event)) { - int keycode = p_event.GetKeyCode(); + const int keycode = p_event.GetKeyCode(); char tmpbuf[2]; tmpbuf[0] = (char)keycode; tmpbuf[1] = '\0'; - wxString strbuf(tmpbuf, *wxConvCurrent); + const wxString strbuf(tmpbuf, *wxConvCurrent); #if wxUSE_INTL - bool is_decimal_point = + const bool is_decimal_point = (strbuf == wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER)); #else bool is_decimal_point = (strbuf == _T(".")); diff --git a/src/gui/style.cc b/src/gui/style.cc index b3e785e57..01cf49960 100644 --- a/src/gui/style.cc +++ b/src/gui/style.cc @@ -50,7 +50,7 @@ static wxColour s_defaultColors[8] = { //! const wxColour &gbtStyle::GetPlayerColor(int pl) const { - while (pl > m_playerColors.size()) { + while (pl > static_cast(m_playerColors.size())) { m_playerColors.push_back(s_defaultColors[m_playerColors.size() % 8]); } @@ -81,7 +81,7 @@ void gbtStyle::SetDefaults() m_chanceColor = wxColour(154, 205, 50); m_terminalColor = *wxBLACK; - for (int pl = 1; pl <= m_playerColors.size(); pl++) { + for (size_t pl = 1; pl <= m_playerColors.size(); pl++) { m_playerColors[pl] = s_defaultColors[(pl - 1) % 8]; } } @@ -103,7 +103,7 @@ std::string gbtStyle::GetColorXML() const s << "blue=\"" << ((int)m_chanceColor.Blue()) << "\" "; s << "/>\n"; - for (int pl = 1; pl <= m_playerColors.size(); pl++) { + for (size_t pl = 1; pl <= m_playerColors.size(); pl++) { s << "\n"; s << "\n"; s << "\n"; - std::string infosetConnect[] = {"none", "same", "all"}; + const std::string infosetConnect[] = {"none", "same", "all"}; s << "\n"; s << "\n"; @@ -202,7 +202,7 @@ void gbtStyle::SetLayoutXML(TiXmlNode *p_node) nodes->ToElement()->QueryIntAttribute("spacing", &m_terminalSpacing); const char *chance = nodes->ToElement()->Attribute("chance"); if (chance) { - std::string s = chance; + const std::string s = chance; if (s == "line") { m_chanceToken = GBT_NODE_TOKEN_LINE; } @@ -222,7 +222,7 @@ void gbtStyle::SetLayoutXML(TiXmlNode *p_node) const char *player = nodes->ToElement()->Attribute("player"); if (player) { - std::string s = player; + const std::string s = player; if (s == "line") { m_playerToken = GBT_NODE_TOKEN_LINE; } @@ -242,7 +242,7 @@ void gbtStyle::SetLayoutXML(TiXmlNode *p_node) const char *terminal = nodes->ToElement()->Attribute("terminal"); if (terminal) { - std::string s = terminal; + const std::string s = terminal; if (s == "line") { m_terminalToken = GBT_NODE_TOKEN_LINE; } @@ -268,7 +268,7 @@ void gbtStyle::SetLayoutXML(TiXmlNode *p_node) const char *branch = branches->ToElement()->Attribute("branch"); if (branch) { - std::string s = branch; + const std::string s = branch; if (s == "line") { m_branchStyle = GBT_BRANCH_STYLE_LINE; } @@ -279,7 +279,7 @@ void gbtStyle::SetLayoutXML(TiXmlNode *p_node) const char *labels = branches->ToElement()->Attribute("labels"); if (labels) { - std::string s = labels; + const std::string s = labels; if (s == "horizontal") { m_branchLabels = GBT_BRANCH_LABEL_HORIZONTAL; } @@ -293,7 +293,7 @@ void gbtStyle::SetLayoutXML(TiXmlNode *p_node) if (infosets) { const char *connect = infosets->ToElement()->Attribute("connect"); if (connect) { - std::string s = connect; + const std::string s = connect; if (s == "none") { m_infosetConnect = GBT_INFOSET_CONNECT_NONE; } @@ -307,7 +307,7 @@ void gbtStyle::SetLayoutXML(TiXmlNode *p_node) const char *style = infosets->ToElement()->Attribute("style"); if (style) { - std::string s = style; + const std::string s = style; if (s == "lines") { m_infosetJoin = GBT_INFOSET_JOIN_LINES; } @@ -322,12 +322,12 @@ std::string gbtStyle::GetLabelXML() const { std::ostringstream s; s << "ToElement()->Attribute("abovenode"); if (abovenode) { - std::string s = abovenode; + const std::string s = abovenode; if (s == "none") { m_nodeAboveLabel = GBT_NODE_LABEL_NOTHING; } @@ -368,7 +368,7 @@ void gbtStyle::SetLabelXML(TiXmlNode *p_node) const char *belownode = p_node->ToElement()->Attribute("belownode"); if (belownode) { - std::string s = belownode; + const std::string s = belownode; if (s == "none") { m_nodeBelowLabel = GBT_NODE_LABEL_NOTHING; } @@ -397,7 +397,7 @@ void gbtStyle::SetLabelXML(TiXmlNode *p_node) const char *abovebranch = p_node->ToElement()->Attribute("abovebranch"); if (abovebranch) { - std::string s = abovebranch; + const std::string s = abovebranch; if (s == "none") { m_branchAboveLabel = GBT_BRANCH_LABEL_NOTHING; } @@ -414,7 +414,7 @@ void gbtStyle::SetLabelXML(TiXmlNode *p_node) const char *belowbranch = p_node->ToElement()->Attribute("belowbranch"); if (belowbranch) { - std::string s = belowbranch; + const std::string s = belowbranch; if (s == "none") { m_branchBelowLabel = GBT_BRANCH_LABEL_NOTHING; } diff --git a/src/gui/valnumber.cc b/src/gui/valnumber.cc index 6f1bbaf48..b19e5b539 100644 --- a/src/gui/valnumber.cc +++ b/src/gui/valnumber.cc @@ -112,7 +112,7 @@ bool gbtNumberValidator::Validate(wxWindow *p_parent) return true; } - wxString value(control->GetValue()); + const wxString value(control->GetValue()); if (!IsNumeric(value)) { wxMessageBox(_T("The value ") + value + _T(" in ") + m_validatorWindow->GetName() + @@ -168,7 +168,7 @@ bool gbtNumberValidator::TransferFromWindow() void gbtNumberValidator::OnChar(wxKeyEvent &p_event) { if (m_validatorWindow) { - int keyCode = (int)p_event.GetKeyCode(); + const int keyCode = (int)p_event.GetKeyCode(); // we don't filter special keys and Delete if (!(keyCode < WXK_SPACE || keyCode == WXK_DELETE || keyCode > WXK_START) && @@ -181,7 +181,7 @@ void gbtNumberValidator::OnChar(wxKeyEvent &p_event) } auto *control = dynamic_cast(m_validatorWindow); - wxString value = control->GetValue(); + const wxString value = control->GetValue(); if ((keyCode == '.' || keyCode == '/') && (value.Find('.') != -1 || value.Find('/') != -1)) { // At most one slash or decimal point is allowed diff --git a/src/labenski/.clang-tidy b/src/labenski/.clang-tidy new file mode 100644 index 000000000..3cc50f629 --- /dev/null +++ b/src/labenski/.clang-tidy @@ -0,0 +1,3 @@ +--- +Checks: | + -* diff --git a/src/pygambit/action.pxi b/src/pygambit/action.pxi index 14b9c29a1..576834b54 100644 --- a/src/pygambit/action.pxi +++ b/src/pygambit/action.pxi @@ -100,9 +100,18 @@ class Action: ) py_string = cython.cast( string, - self.action.deref().GetInfoset().deref().GetActionProb(self.action.deref().GetNumber()) + self.action.deref().GetInfoset().deref().GetActionProb(self.action) ) if "." in py_string.decode("ascii"): return decimal.Decimal(py_string.decode("ascii")) else: return Rational(py_string.decode("ascii")) + + @property + def plays(self) -> typing.List[Node]: + """Returns a list of all terminal `Node` objects consistent with it. + """ + return [ + Node.wrap(n) for n in + self.action.deref().GetInfoset().deref().GetGame().deref().GetPlays(self.action) + ] diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index cfa61c3db..7a41b6834 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -2,6 +2,7 @@ from libcpp cimport bool from libcpp.string cimport string from libcpp.memory cimport shared_ptr, unique_ptr from libcpp.list cimport list as stdlist +from libcpp.vector cimport vector as stdvector cdef extern from "gambit.h": @@ -72,10 +73,12 @@ cdef extern from "games/game.h": c_GameNodeRep *deref "operator->"() except +RuntimeError cdef cppclass c_GameAction "GameObjectPtr": + bool operator !() except + bool operator !=(c_GameAction) except + c_GameActionRep *deref "operator->"() except +RuntimeError cdef cppclass c_GameInfoset "GameObjectPtr": + bool operator ==(c_GameInfoset) except + bool operator !=(c_GameInfoset) except + c_GameInfosetRep *deref "operator->"() except +RuntimeError @@ -83,7 +86,7 @@ cdef extern from "games/game.h": c_GameStrategyRep *deref "operator->"() except +RuntimeError cdef cppclass c_PureStrategyProfile "PureStrategyProfile": - c_PureStrategyProfileRep *deref "operator->"() except + + shared_ptr[c_PureStrategyProfileRep] deref "operator->"() except + c_PureStrategyProfile(c_PureStrategyProfile) except + cdef cppclass c_PureBehaviorProfile "PureBehaviorProfile": @@ -102,35 +105,69 @@ cdef extern from "games/game.h": int GetNumber() except + c_GameInfoset GetInfoset() except + bint Precedes(c_GameNode) except + - void DeleteAction() except +ValueError string GetLabel() except + void SetLabel(string) except + cdef cppclass c_GameInfosetRep "GameInfosetRep": + cppclass Actions: + cppclass iterator: + c_GameAction operator *() + iterator operator++() + bint operator ==(iterator) + bint operator !=(iterator) + int size() except + + iterator begin() except + + iterator end() except + + + cppclass Members: + cppclass iterator: + c_GameNode operator *() + iterator operator++() + bint operator ==(iterator) + bint operator !=(iterator) + int size() except + + iterator begin() except + + iterator end() except + + int GetNumber() except + c_Game GetGame() except + c_GamePlayer GetPlayer() except + - void SetPlayer(c_GamePlayer) except + string GetLabel() except + void SetLabel(string) except + - int NumActions() except + c_GameAction GetAction(int) except +IndexError - c_GameAction InsertAction(c_GameAction) except +ValueError - - c_Number GetActionProb(int) except +IndexError - void SetActionProb(int, c_Number) except +IndexError + Actions GetActions() except + + c_Number GetActionProb(c_GameAction) except +IndexError - int NumMembers() except + c_GameNode GetMember(int) except +IndexError + Members GetMembers() except + - void Reveal(c_GamePlayer) except + bint IsChanceInfoset() except + bint Precedes(c_GameNode) except + cdef cppclass c_GamePlayerRep "GamePlayerRep": + cppclass Infosets: + cppclass iterator: + c_GameInfoset operator *() + iterator operator++() + bint operator ==(iterator) + bint operator !=(iterator) + int size() except + + iterator begin() except + + iterator end() except + + + cppclass Strategies: + cppclass iterator: + c_GameStrategy operator *() + iterator operator++() + bint operator ==(iterator) + bint operator !=(iterator) + int size() except + + iterator begin() except + + iterator end() except + + c_Game GetGame() except + int GetNumber() except + int IsChance() except + @@ -138,12 +175,11 @@ cdef extern from "games/game.h": string GetLabel() except + void SetLabel(string) except + - int NumStrategies() except + c_GameStrategy GetStrategy(int) except +IndexError + Strategies GetStrategies() except + - int NumInfosets() except + c_GameInfoset GetInfoset(int) except +IndexError - c_GameStrategy NewStrategy() except + + Infosets GetInfosets() except + cdef cppclass c_GameOutcomeRep "GameOutcomeRep": c_Game GetGame() except + @@ -152,10 +188,20 @@ cdef extern from "games/game.h": string GetLabel() except + void SetLabel(string) except + - c_Number GetPayoff(c_GamePlayer) except +IndexError + T GetPayoff[T](c_GamePlayer) except +IndexError void SetPayoff(c_GamePlayer, c_Number) except +IndexError cdef cppclass c_GameNodeRep "GameNodeRep": + cppclass Children: + cppclass iterator: + c_GameNode operator *() + iterator operator++() + bint operator ==(iterator) + bint operator !=(iterator) + int size() except + + iterator begin() except + + iterator end() except + + c_Game GetGame() except + int GetNumber() except + @@ -163,14 +209,11 @@ cdef extern from "games/game.h": void SetLabel(string) except + c_GameInfoset GetInfoset() except + - void SetInfoset(c_GameInfoset) except +ValueError - c_GameInfoset LeaveInfoset() except + c_GamePlayer GetPlayer() except + c_GameNode GetParent() except + - int NumChildren() except + - c_GameNode GetChild(int) except +IndexError + Children GetChildren() except + + c_GameNode GetChild(c_GameAction) except +IndexError c_GameOutcome GetOutcome() except + - void SetOutcome(c_GameOutcome) except + c_GameNode GetPriorSibling() except + c_GameNode GetNextSibling() except + bint IsTerminal() except + @@ -178,16 +221,33 @@ cdef extern from "games/game.h": bint IsSubgameRoot() except + c_GameAction GetPriorAction() except + - c_GameInfoset AppendMove(c_GamePlayer, int) except +ValueError - c_GameInfoset AppendMove(c_GameInfoset) except +ValueError - c_GameInfoset InsertMove(c_GamePlayer, int) except +ValueError - c_GameInfoset InsertMove(c_GameInfoset) except +ValueError - void DeleteParent() except + - void DeleteTree() except + - void CopyTree(c_GameNode) except +ValueError - void MoveTree(c_GameNode) except +ValueError - cdef cppclass c_GameRep "GameRep": + cppclass Players: + cppclass iterator: + c_GamePlayer operator *() + iterator operator++() + bint operator ==(iterator) + bint operator !=(iterator) + int size() except + + iterator begin() except + + iterator end() except + + + cppclass Outcomes: + cppclass iterator: + c_GameOutcome operator *() + iterator operator++() + bint operator ==(iterator) + bint operator !=(iterator) + int size() except + + iterator begin() except + + iterator end() except + + + cppclass Nodes: + bint operator ==(Nodes) + bint operator !=(Nodes) + c_GameNode operator *() + Nodes operator++() + int IsTree() except + string GetTitle() except + @@ -198,19 +258,23 @@ cdef extern from "games/game.h": int NumPlayers() except + c_GamePlayer GetPlayer(int) except +IndexError - Array[c_GamePlayer] GetPlayers() except + + Players GetPlayers() except + c_GamePlayer GetChance() except + c_GamePlayer NewPlayer() except + int NumOutcomes() except + c_GameOutcome GetOutcome(int) except +IndexError + Outcomes GetOutcomes() except + c_GameOutcome NewOutcome() except + void DeleteOutcome(c_GameOutcome) except + int NumNodes() except + + int NumNonterminalNodes() except + c_GameNode GetRoot() except + c_GameStrategy GetStrategy(int) except +IndexError + c_GameStrategy NewStrategy(c_GamePlayer, string) except + + void DeleteStrategy(c_GameStrategy) except + int MixedProfileLength() except + c_GameInfoset GetInfoset(int) except +IndexError @@ -220,17 +284,39 @@ cdef extern from "games/game.h": int BehavProfileLength() except + bool IsConstSum() except + - c_Rational GetMinPayoff(int) except + - c_Rational GetMaxPayoff(int) except + + c_Rational GetMinPayoff() except + + c_Rational GetMinPayoff(c_GamePlayer) except + + c_Rational GetMaxPayoff() except + + c_Rational GetMaxPayoff(c_GamePlayer) except + + stdvector[c_GameNode] GetPlays(c_GameNode) except + + stdvector[c_GameNode] GetPlays(c_GameInfoset) except + + stdvector[c_GameNode] GetPlays(c_GameAction) except + bool IsPerfectRecall() except + + c_GameInfoset AppendMove(c_GameNode, c_GamePlayer, int) except +ValueError + c_GameInfoset AppendMove(c_GameNode, c_GameInfoset) except +ValueError + c_GameInfoset InsertMove(c_GameNode, c_GamePlayer, int) except +ValueError + c_GameInfoset InsertMove(c_GameNode, c_GameInfoset) except +ValueError + void CopyTree(c_GameNode dest, c_GameNode src) except +ValueError + void MoveTree(c_GameNode dest, c_GameNode src) except +ValueError + void DeleteParent(c_GameNode) except + + void DeleteTree(c_GameNode) except + + void SetPlayer(c_GameInfoset, c_GamePlayer) except + + void Reveal(c_GameInfoset, c_GamePlayer) except + + void SetInfoset(c_GameNode, c_GameInfoset) except +ValueError + c_GameInfoset LeaveInfoset(c_GameNode) except + + c_GameAction InsertAction(c_GameInfoset, c_GameAction) except +ValueError + void DeleteAction(c_GameAction) except +ValueError + void SetOutcome(c_GameNode, c_GameOutcome) except + c_Game SetChanceProbs(c_GameInfoset, Array[c_Number]) except + + Nodes begin() except + + Nodes end() except + c_PureStrategyProfile NewPureStrategyProfile() # except + doesn't compile c_MixedStrategyProfile[T] NewMixedStrategyProfile[T](T) # except + doesn't compile c_Game NewTree() except + - c_Game NewTable(Array[int]) except + + c_Game NewTable(stdvector[int]) except + cdef extern from "games/stratpure.h": @@ -386,37 +472,19 @@ cdef extern from "solvers/enumpure/enumpure.h": c_List[c_MixedBehaviorProfile[c_Rational]] EnumPureAgentSolve(c_Game) except +RuntimeError cdef extern from "solvers/enummixed/enummixed.h": - c_List[c_MixedStrategyProfile[double]] EnumMixedStrategySolveDouble( - c_Game - ) except +RuntimeError - c_List[c_MixedStrategyProfile[c_Rational]] EnumMixedStrategySolveRational( - c_Game - ) except +RuntimeError - c_List[c_MixedStrategyProfile[c_Rational]] EnumMixedStrategySolveLrs( - c_Game - ) except +RuntimeError + c_List[c_MixedStrategyProfile[T]] EnumMixedStrategySolve[T](c_Game) except +RuntimeError cdef extern from "solvers/lcp/lcp.h": - c_List[c_MixedStrategyProfile[double]] LcpStrategySolveDouble( + c_List[c_MixedStrategyProfile[T]] LcpStrategySolve[T]( c_Game, int p_stopAfter, int p_maxDepth ) except +RuntimeError - c_List[c_MixedStrategyProfile[c_Rational]] LcpStrategySolveRational( + c_List[c_MixedBehaviorProfile[T]] LcpBehaviorSolve[T]( c_Game, int p_stopAfter, int p_maxDepth ) except +RuntimeError - c_List[c_MixedBehaviorProfile[double]] LcpBehaviorSolveDouble( - c_Game, int p_stopAfter, int p_maxDepth - ) except +RuntimeError - c_List[c_MixedBehaviorProfile[c_Rational]] LcpBehaviorSolveRational( - c_Game, int p_stopAfter, int p_maxDepth - ) except +RuntimeError - -cdef extern from "solvers/lp/nfglp.h": - c_List[c_MixedStrategyProfile[double]] LpStrategySolveDouble(c_Game) except +RuntimeError - c_List[c_MixedStrategyProfile[c_Rational]] LpStrategySolveRational(c_Game) except +RuntimeError -cdef extern from "solvers/lp/efglp.h": - c_List[c_MixedBehaviorProfile[double]] LpBehaviorSolveDouble(c_Game) except +RuntimeError - c_List[c_MixedBehaviorProfile[c_Rational]] LpBehaviorSolveRational(c_Game) except +RuntimeError +cdef extern from "solvers/lp/lp.h": + c_List[c_MixedStrategyProfile[T]] LpStrategySolve[T](c_Game) except +RuntimeError + c_List[c_MixedBehaviorProfile[T]] LpBehaviorSolve[T](c_Game) except +RuntimeError cdef extern from "solvers/liap/liap.h": c_List[c_MixedStrategyProfile[double]] LiapStrategySolve( diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 0c6a8d26c..31f6261f5 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -23,13 +23,14 @@ from collections import deque import io import itertools import pathlib -import warnings import numpy as np import scipy.stats import pygambit.gameiter +from cython.operator cimport dereference, preincrement + ctypedef string (*GameWriter)(const c_Game &) except +IOError ctypedef c_Game (*GameParser)(const string &) except +IOError @@ -161,6 +162,88 @@ def read_agg(filepath_or_buffer: typing.Union[str, pathlib.Path, io.IOBase]) -> return read_game(filepath_or_buffer, parser=ParseAggGame) +@cython.cclass +class GameNodes: + """Represents the set of nodes in a game.""" + game = cython.declare(c_Game) + + def __init__(self, *args, **kwargs) -> None: + raise ValueError("Cannot create GameNodes outside a Game.") + + @staticmethod + @cython.cfunc + def wrap(game: c_Game) -> GameNodes: + obj: GameNodes = GameNodes.__new__(GameNodes) + obj.game = game + return obj + + def __repr__(self) -> str: + return f"GameNodes(game={Game.wrap(self.game)})" + + def __len__(self) -> int: + """The number of nodes in the game.""" + if not self.game.deref().IsTree(): + return 0 + return self.game.deref().NumNodes() + + def __iter__(self) -> typing.Iterator[Node]: + """ + A generator that efficiently iterates over the game nodes using + the underlying C++ iterator, without using cdef for local variables. + """ + if not self.game.deref().IsTree(): + return + + # Assign the C++ iterators to regular Python variables. + # Cython creates lightweight proxy objects automatically. + it = self.game.deref().begin() + end_it = self.game.deref().end() + + # The loop still uses fast C++-backed operations on the proxies. + while it != end_it: + # deref(it) gets the underlying C++ GameNode, which is then + # immediately passed to Node.wrap(). No intermediate cdef needed. + yield Node.wrap(dereference(it)) + + # inc(it) calls the C++ pre-increment operator on the proxy. + preincrement(it) + + +@cython.cclass +class GameNonterminalNodes: + """Represents the set of nodes in a game.""" + game = cython.declare(c_Game) + + def __init__(self, *args, **kwargs) -> None: + raise ValueError("Cannot create GameNonterminalNodes outside a Game.") + + @staticmethod + @cython.cfunc + def wrap(game: c_Game) -> GameNonterminalNodes: + obj: GameNonterminalNodes = GameNonterminalNodes.__new__(GameNonterminalNodes) + obj.game = game + return obj + + def __repr__(self) -> str: + return f"GameNonterminalNodes(game={Game.wrap(self.game)})" + + def __len__(self) -> int: + """The number of non-terminal nodes in the game.""" + if not self.game.deref().IsTree(): + return 0 + return self.game.deref().NumNonterminalNodes() + + def __iter__(self) -> typing.Iterator[Node]: + def dfs(node): + if not node.is_terminal: + yield node + for child in node.children: + yield from dfs(child) + if not self.game.deref().IsTree(): + return + yield from dfs(Node.wrap(self.game.deref().GetRoot())) + + @cython.cclass class GameOutcomes: """Represents the set of outcomes in a game.""" @@ -181,11 +264,11 @@ class GameOutcomes: def __len__(self) -> int: """The number of outcomes in the game.""" - return self.game.deref().NumOutcomes() + return self.game.deref().GetOutcomes().size() def __iter__(self) -> typing.Iterator[Outcome]: - for i in range(self.game.deref().NumOutcomes()): - yield Outcome.wrap(self.game.deref().GetOutcome(i + 1)) + for outcome in self.game.deref().GetOutcomes(): + yield Outcome.wrap(outcome) def __getitem__(self, index: typing.Union[int, str]) -> Outcome: if isinstance(index, str): @@ -225,8 +308,8 @@ class GamePlayers: return self.game.deref().NumPlayers() def __iter__(self) -> typing.Iterator[Player]: - for i in range(self.game.deref().NumPlayers()): - yield Player.wrap(self.game.deref().GetPlayer(i + 1)) + for player in self.game.deref().GetPlayers(): + yield Player.wrap(player) def __getitem__(self, index: typing.Union[int, str]) -> Player: if isinstance(index, str): @@ -352,7 +435,7 @@ class GameStrategies: return obj def __repr__(self) -> str: - return f"GameOutcomes(game={self.game})" + return f"GameStrategies(game={self.game})" def __len__(self) -> int: return sum(len(p.strategies) for p in self.game.players) @@ -449,10 +532,7 @@ class Game: Game The newly-created strategic game. """ - cdef Array[int] d - for v in dim: - d.push_back(v) - g = Game.wrap(NewTable(d)) + g = Game.wrap(NewTable(list(dim))) g.title = title return g @@ -668,6 +748,27 @@ class Game: """The set of outcomes in the game.""" return GameOutcomes.wrap(self.game) + @property + def nodes(self) -> GameNodes: + """The set of nodes in the game. + + Iteration over this property yields the nodes in the order of depth-first search. + + .. versionchanged:: 16.4 + Changed from a method ``nodes()`` to a property. + + """ + return GameNodes.wrap(self.game) + + @property + def _nonterminal_nodes(self) -> GameNonterminalNodes: + """The set of non-terminal nodes in the game. + + Iteration over this property yields the non-terminal nodes in the order of depth-first + search. + """ + return GameNonterminalNodes.wrap(self.game) + @property def contingencies(self) -> pygambit.gameiter.Contingencies: """An iterator over the contingencies in the game.""" @@ -705,12 +806,12 @@ class Game: @property def min_payoff(self) -> typing.Union[decimal.Decimal, Rational]: """The minimum payoff in the game.""" - return rat_to_py(self.game.deref().GetMinPayoff(0)) + return rat_to_py(self.game.deref().GetMinPayoff()) @property def max_payoff(self) -> typing.Union[decimal.Decimal, Rational]: """The maximum payoff in the game.""" - return rat_to_py(self.game.deref().GetMaxPayoff(0)) + return rat_to_py(self.game.deref().GetMaxPayoff()) def set_chance_probs(self, infoset: typing.Union[Infoset, str], probs: typing.Sequence): """Set the action probabilities at chance information set `infoset`. @@ -760,14 +861,14 @@ class Game: ) for (pl, st) in enumerate(args): - deref(psp).deref().SetStrategy( + deref(deref(psp).deref()).SetStrategy( self.game.deref().GetPlayer(pl+1).deref().GetStrategy(st+1) ) if self.is_tree: return TreeGameOutcome.wrap(self.game, psp) else: - outcome = Outcome.wrap(deref(psp).deref().GetOutcome()) + outcome = Outcome.wrap(deref(deref(psp).deref()).GetOutcome()) if outcome.outcome != cython.cast(c_GameOutcome, NULL): return outcome else: @@ -1063,37 +1164,6 @@ class Game: raise ValueError("attempted to remove the last strategy for player") return profile - def nodes( - self, - subtree: typing.Optional[typing.Union[Node, str]] = None - ) -> typing.List[Node]: - """Return a list of nodes in the game tree. If `subtree` is not None, returns - the nodes in the subtree rooted at that node. - - Nodes are returned in prefix-traversal order: a node appears prior to the list of - nodes in the subtrees rooted at the node's children. - - Parameters - ---------- - subtree : Node or str, optional - If specified, return only the nodes in the subtree rooted at `subtree`. - - Raises - ------ - MismatchError - If `node` is a `Node` from a different game. - """ - if not self.is_tree: - return [] - if subtree: - resolved_node = cython.cast(Node, self._resolve_node(subtree, "nodes", "subtree")) - else: - resolved_node = self.root - return ( - [resolved_node] + - [n for child in resolved_node.children for n in self.nodes(child)] - ) - @cython.cfunc def _to_format( self, @@ -1109,7 +1179,7 @@ class Game: filepath_or_buffer.write(serialized_game) else: with open(filepath_or_buffer, "w") as f: - f.write(serialized_game) + f.write(serialized_game.decode()) def to_efg( self, @@ -1370,7 +1440,7 @@ class Game: raise ValueError( f"{funcname}(): {argname} cannot be an empty string or all spaces" ) - for n in self.nodes(): + for n in self.nodes: if n.label == node: return n raise KeyError(f"{funcname}(): no node with label '{node}'") @@ -1503,12 +1573,12 @@ class Game: raise UndefinedOperationError("append_move(): `nodes` must be terminal nodes") resolved_node = cython.cast(Node, resolved_nodes[0]) - resolved_node.node.deref().AppendMove(resolved_player.player, len(actions)) + self.game.deref().AppendMove(resolved_node.node, resolved_player.player, len(actions)) for label, action in zip(actions, resolved_node.infoset.actions): action.label = label resolved_infoset = cython.cast(Infoset, resolved_node.infoset) for n in resolved_nodes[1:]: - cython.cast(Node, n).node.deref().AppendMove(resolved_infoset.infoset) + self.game.deref().AppendMove(cython.cast(Node, n).node, resolved_infoset.infoset) def append_infoset(self, nodes: typing.Union[NodeReference, NodeReferenceSet], infoset: typing.Union[Infoset, str]) -> None: @@ -1529,7 +1599,7 @@ class Game: if any(len(n.children) > 0 for n in resolved_nodes): raise UndefinedOperationError("append_infoset(): `nodes` must be terminal nodes") for n in resolved_nodes: - cython.cast(Node, n).node.deref().AppendMove(resolved_infoset.infoset) + self.game.deref().AppendMove(cython.cast(Node, n).node, resolved_infoset.infoset) def insert_move(self, node: typing.Union[Node, str], player: typing.Union[Player, str], actions: int) -> None: @@ -1548,7 +1618,7 @@ class Game: resolved_player = cython.cast(Player, self._resolve_player(player, "insert_move")) if actions < 1: raise UndefinedOperationError("insert_move(): `actions` must be a positive number") - resolved_node.node.deref().InsertMove(resolved_player.player, actions) + self.game.deref().InsertMove(resolved_node.node, resolved_player.player, actions) def insert_infoset(self, node: typing.Union[Node, str], infoset: typing.Union[Infoset, str]) -> None: @@ -1563,10 +1633,19 @@ class Game: """ resolved_node = cython.cast(Node, self._resolve_node(node, "insert_infoset")) resolved_infoset = cython.cast(Infoset, self._resolve_infoset(infoset, "insert_infoset")) - resolved_node.node.deref().InsertMove(resolved_infoset.infoset) + self.game.deref().InsertMove(resolved_node.node, resolved_infoset.infoset) def copy_tree(self, src: typing.Union[Node, str], dest: typing.Union[Node, str]) -> None: - """Copy the subtree rooted at 'src' to 'dest'. + """Copy the subtree rooted at the node `src` to the node `dest`. + + Each node in the subtree copied to follow `dest` is placed in the same information set + as the corresponding node in the original subtree under `src`. + + It is permitted for `dest` to be a descendant of `src`. + The operation uses the subtree rooted at `src` as it is at the time the function is called, + so no infinite recursion is triggered. + + The outcome associated with `dest` is not changed by this operation. Parameters ---------- @@ -1586,7 +1665,7 @@ class Game: resolved_dest = cython.cast(Node, self._resolve_node(dest, "copy_tree", "dest")) if not resolved_dest.is_terminal: raise UndefinedOperationError("copy_tree(): `dest` must be a terminal node.") - resolved_src.node.deref().CopyTree(resolved_dest.node) + self.game.deref().CopyTree(resolved_dest.node, resolved_src.node) def move_tree(self, src: typing.Union[Node, str], dest: typing.Union[Node, str]) -> None: """Move the subtree rooted at 'src' to 'dest'. @@ -1611,7 +1690,7 @@ class Game: raise UndefinedOperationError("move_tree(): `dest` must be a terminal node.") if resolved_dest.is_successor_of(resolved_src): raise UndefinedOperationError("move_tree(): `dest` cannot be a successor of `src`.") - resolved_src.node.deref().MoveTree(resolved_dest.node) + self.game.deref().MoveTree(resolved_dest.node, resolved_src.node) def delete_parent(self, node: typing.Union[Node, str]) -> None: """Delete the parent node of `node`. `node` replaces its parent in the tree. All other @@ -1630,7 +1709,7 @@ class Game: If `node` is a `Node` from a different game. """ resolved_node = cython.cast(Node, self._resolve_node(node, "delete_parent")) - resolved_node.node.deref().DeleteParent() + self.game.deref().DeleteParent(resolved_node.node) def delete_tree(self, node: typing.Union[Node, str]) -> None: """Truncate the game tree at `node`, deleting the subtree beneath it. @@ -1647,7 +1726,7 @@ class Game: If `node` is a `Node` from a different game. """ resolved_node = cython.cast(Node, self._resolve_node(node, "delete_tree")) - resolved_node.node.deref().DeleteTree() + self.game.deref().DeleteTree(resolved_node.node) def add_action(self, infoset: typing.Union[typing.Infoset, str], @@ -1671,14 +1750,15 @@ class Game: """ resolved_infoset = cython.cast(Infoset, self._resolve_infoset(infoset, "add_action")) if before is None: - resolved_infoset.infoset.deref().InsertAction(cython.cast(c_GameAction, NULL)) + self.game.deref().InsertAction(resolved_infoset.infoset, + cython.cast(c_GameAction, NULL)) else: resolved_action = cython.cast( Action, self._resolve_action(before, "add_action", "before") ) if resolved_infoset != resolved_action.infoset: raise MismatchError("add_action(): must specify an action from the same infoset") - resolved_infoset.infoset.deref().InsertAction(resolved_action.action) + self.game.deref().InsertAction(resolved_infoset.infoset, resolved_action.action) def delete_action(self, action: typing.Union[Action, str]) -> None: """Deletes `action` from its information set. The subtrees which @@ -1699,7 +1779,7 @@ class Game: raise UndefinedOperationError( "delete_action(): cannot delete the only action at an information set" ) - resolved_action.action.deref().DeleteAction() + self.game.deref().DeleteAction(resolved_action.action) def leave_infoset(self, node: typing.Union[Node, str]): """Remove this node from its information set. If this node is the only node @@ -1711,7 +1791,7 @@ class Game: The node to move to a new singleton information set. """ resolved_node = cython.cast(Node, self._resolve_node(node, "leave_infoset")) - resolved_node.node.deref().LeaveInfoset() + self.game.deref().LeaveInfoset(resolved_node.node) def set_infoset(self, node: typing.Union[Node, str], @@ -1738,7 +1818,7 @@ class Game: raise ValueError( "set_infoset(): `infoset` must have same number of actions as `node` has children." ) - resolved_node.node.deref().SetInfoset(resolved_infoset.infoset) + self.game.deref().SetInfoset(resolved_node.node, resolved_infoset.infoset) def reveal(self, infoset: typing.Union[Infoset, str], @@ -1767,7 +1847,7 @@ class Game: """ resolved_infoset = cython.cast(Infoset, self._resolve_infoset(infoset, "reveal")) resolved_player = cython.cast(Player, self._resolve_player(player, "reveal")) - resolved_infoset.infoset.deref().Reveal(resolved_player.player) + self.game.deref().Reveal(resolved_infoset.infoset, resolved_player.player) def add_player(self, label: str = "") -> Player: """Add a new player to the game. @@ -1806,7 +1886,7 @@ class Game: """ resolved_player = cython.cast(Player, self._resolve_player(player, "set_player")) resolved_infoset = cython.cast(Infoset, self._resolve_infoset(infoset, "set_player")) - resolved_infoset.infoset.deref().SetPlayer(resolved_player.player) + self.game.deref().SetPlayer(resolved_infoset.infoset, resolved_player.player) def add_outcome(self, payoffs: typing.Optional[typing.List] = None, @@ -1884,10 +1964,10 @@ class Game: """ resolved_node = cython.cast(Node, self._resolve_node(node, "set_outcome")) if outcome is None: - resolved_node.node.deref().SetOutcome(cython.cast(c_GameOutcome, NULL)) + self.game.deref().SetOutcome(resolved_node.node, cython.cast(c_GameOutcome, NULL)) return resolved_outcome = cython.cast(Outcome, self._resolve_outcome(outcome, "set_outcome")) - resolved_node.node.deref().SetOutcome(resolved_outcome.outcome) + self.game.deref().SetOutcome(resolved_node.node, resolved_outcome.outcome) def add_strategy(self, player: typing.Union[Player, str], label: str = None) -> Strategy: """Add a new strategy to the set of strategies for `player`. @@ -1917,10 +1997,10 @@ class Game: ) resolved_player = cython.cast(Player, self._resolve_player(player, "add_strategy")) - s = Strategy.wrap(resolved_player.player.deref().NewStrategy()) - if label is not None: - s.label = str(label) - return s + return Strategy.wrap( + self.game.deref().NewStrategy(resolved_player.player, + (str(label) if label is not None else "").encode()) + ) def delete_strategy(self, strategy: typing.Union[Strategy, str]) -> None: """Delete `strategy` from the game. @@ -1947,39 +2027,5 @@ class Game: ) if len(resolved_strategy.player.strategies) == 1: raise UndefinedOperationError("Cannot delete the only strategy for a player") - resolved_strategy.strategy.deref().DeleteStrategy() - - def compute_images(self) -> dict[Node, set[Outcome]]: - """Recursively compute images in outcomes for each node in the game tree. - - For each node in the tree, calculates the set of outcomes that can be reached from it. - Returns - ------- - dict[Node, set[Outcome]] - A dictionary mapping each node to the set of outcomes that can be reached from it: - - For terminal nodes, this is either {node.outcome} or an empty set - - For non-terminal nodes, this is the union of all images in outcomes of children - - Notes - ----- - This traverses the game tree using depth-first search, building the sets of outcomes - from the bottom up. - """ - images_in_outcomes = {} - - def dfs(node) -> set[Outcome]: - if node.is_terminal: - if node.outcome is None: - images_in_outcomes[node] = set() - else: - images_in_outcomes[node] = {node.outcome} - else: - union = set() - for child in node.children: - union |= dfs(child) - images_in_outcomes[node] = union - return images_in_outcomes[node] - - dfs(self.root) - return images_in_outcomes + self.game.deref().DeleteStrategy(resolved_strategy.strategy) \ No newline at end of file diff --git a/src/pygambit/infoset.pxi b/src/pygambit/infoset.pxi index aef0afb9e..10bd0c1d6 100644 --- a/src/pygambit/infoset.pxi +++ b/src/pygambit/infoset.pxi @@ -39,11 +39,11 @@ class InfosetMembers: return f"InfosetMembers(infoset={Infoset.wrap(self.infoset)})" def __len__(self) -> int: - return self.infoset.deref().NumMembers() + return self.infoset.deref().GetMembers().size() def __iter__(self) -> typing.Iterator[Node]: - for i in range(self.infoset.deref().NumMembers()): - yield Node.wrap(self.infoset.deref().GetMember(i + 1)) + for member in self.infoset.deref().GetMembers(): + yield Node.wrap(member) def __getitem__(self, index: typing.Union[int, str]) -> Node: if isinstance(index, str): @@ -80,11 +80,11 @@ class InfosetActions: def __len__(self): """The number of actions at the information set.""" - return self.infoset.deref().NumActions() + return self.infoset.deref().GetActions().size() def __iter__(self) -> typing.Iterator[Action]: - for i in range(self.infoset.deref().NumActions()): - yield Action.wrap(self.infoset.deref().GetAction(i + 1)) + for action in self.infoset.deref().GetActions(): + yield Action.wrap(action) def __getitem__(self, index: typing.Union[int, str]) -> Action: if isinstance(index, str): @@ -175,3 +175,11 @@ class Infoset: def player(self) -> Player: """The player who has the move at this information set.""" return Player.wrap(self.infoset.deref().GetPlayer()) + + @property + def plays(self) -> typing.List[Node]: + """Returns a list of all terminal `Node` objects consistent with it. + """ + return [ + Node.wrap(n) for n in self.infoset.deref().GetGame().deref().GetPlays(self.infoset) + ] diff --git a/src/pygambit/nash.pxi b/src/pygambit/nash.pxi index f45403e42..4ed8e90a2 100644 --- a/src/pygambit/nash.pxi +++ b/src/pygambit/nash.pxi @@ -68,51 +68,51 @@ def _enumpure_agent_solve(game: Game) -> typing.List[MixedBehaviorProfileRationa def _enummixed_strategy_solve_double(game: Game) -> typing.List[MixedStrategyProfileDouble]: - return _convert_mspd(EnumMixedStrategySolveDouble(game.game)) + return _convert_mspd(EnumMixedStrategySolve[double](game.game)) def _enummixed_strategy_solve_rational(game: Game) -> typing.List[MixedStrategyProfileRational]: - return _convert_mspr(EnumMixedStrategySolveRational(game.game)) + return _convert_mspr(EnumMixedStrategySolve[c_Rational](game.game)) def _lcp_behavior_solve_double( game: Game, stop_after: int, max_depth: int ) -> typing.List[MixedBehaviorProfileDouble]: - return _convert_mbpd(LcpBehaviorSolveDouble(game.game, stop_after, max_depth)) + return _convert_mbpd(LcpBehaviorSolve[double](game.game, stop_after, max_depth)) def _lcp_behavior_solve_rational( game: Game, stop_after: int, max_depth: int ) -> typing.List[MixedBehaviorProfileRational]: - return _convert_mbpr(LcpBehaviorSolveRational(game.game, stop_after, max_depth)) + return _convert_mbpr(LcpBehaviorSolve[c_Rational](game.game, stop_after, max_depth)) def _lcp_strategy_solve_double( game: Game, stop_after: int, max_depth: int ) -> typing.List[MixedStrategyProfileDouble]: - return _convert_mspd(LcpStrategySolveDouble(game.game, stop_after, max_depth)) + return _convert_mspd(LcpStrategySolve[double](game.game, stop_after, max_depth)) def _lcp_strategy_solve_rational( game: Game, stop_after: int, max_depth: int ) -> typing.List[MixedStrategyProfileRational]: - return _convert_mspr(LcpStrategySolveRational(game.game, stop_after, max_depth)) + return _convert_mspr(LcpStrategySolve[c_Rational](game.game, stop_after, max_depth)) def _lp_behavior_solve_double(game: Game) -> typing.List[MixedBehaviorProfileDouble]: - return _convert_mbpd(LpBehaviorSolveDouble(game.game)) + return _convert_mbpd(LpBehaviorSolve[double](game.game)) def _lp_behavior_solve_rational(game: Game) -> typing.List[MixedBehaviorProfileRational]: - return _convert_mbpr(LpBehaviorSolveRational(game.game)) + return _convert_mbpr(LpBehaviorSolve[c_Rational](game.game)) def _lp_strategy_solve_double(game: Game) -> typing.List[MixedStrategyProfileDouble]: - return _convert_mspd(LpStrategySolveDouble(game.game)) + return _convert_mspd(LpStrategySolve[double](game.game)) def _lp_strategy_solve_rational(game: Game) -> typing.List[MixedStrategyProfileRational]: - return _convert_mspr(LpStrategySolveRational(game.game)) + return _convert_mspr(LpStrategySolve[c_Rational](game.game)) def _liap_strategy_solve(start: MixedStrategyProfileDouble, diff --git a/src/pygambit/node.pxi b/src/pygambit/node.pxi index 0cc0da91a..4124406b0 100644 --- a/src/pygambit/node.pxi +++ b/src/pygambit/node.pxi @@ -36,14 +36,14 @@ class NodeChildren: return obj def __len__(self) -> int: - return self.parent.deref().NumChildren() + return self.parent.deref().GetChildren().size() def __repr__(self) -> str: return f"NodeChildren(parent={Node.wrap(self.parent)})" def __iter__(self) -> typing.Iterator[Node]: - for i in range(self.parent.deref().NumChildren()): - yield Node.wrap(self.parent.deref().GetChild(i + 1)) + for child in self.parent.deref().GetChildren(): + yield Node.wrap(child) def __getitem__(self, index: typing.Union[int, str]) -> Node: if isinstance(index, str): @@ -56,7 +56,11 @@ class NodeChildren: raise ValueError(f"Node has multiple children with label '{index}'") return matches[0] if isinstance(index, int): - return Node.wrap(self.parent.deref().GetChild(index + 1)) + if self.parent.deref().GetInfoset() == cython.cast(c_GameInfoset, NULL): + raise IndexError("Index out of range") + return Node.wrap(self.parent.deref().GetChild( + self.parent.deref().GetInfoset().deref().GetAction(index + 1) + )) raise TypeError(f"Child index must be int or str, not {index.__class__.__name__}") @@ -203,3 +207,9 @@ class Node: if self.node.deref().GetOutcome() == cython.cast(c_GameOutcome, NULL): return None return Outcome.wrap(self.node.deref().GetOutcome()) + + @property + def plays(self) -> typing.List[Node]: + """Returns a list of all terminal `Node` objects consistent with it. + """ + return [Node.wrap(n) for n in self.node.deref().GetGame().deref().GetPlays(self.node)] diff --git a/src/pygambit/outcome.pxi b/src/pygambit/outcome.pxi index 35b0f42a4..f5771508e 100644 --- a/src/pygambit/outcome.pxi +++ b/src/pygambit/outcome.pxi @@ -85,9 +85,7 @@ class Outcome: resolved_player = cython.cast(Player, self.game._resolve_player(player, "Outcome.__getitem__")) payoff = ( - cython.cast(bytes, - self.outcome.deref().GetPayoff(resolved_player.player).as_string()) - .decode("ascii") + self.outcome.deref().GetPayoff[string](resolved_player.player).decode("ascii") ) if "." in payoff: return decimal.Decimal(payoff) @@ -170,7 +168,7 @@ class TreeGameOutcome: """ resolved_player = cython.cast(Player, self.game._resolve_player(player, "Outcome.__getitem__")) - return rat_to_py(deref(self.psp).deref().GetPayoff(resolved_player.player)) + return rat_to_py(deref(deref(self.psp).deref()).GetPayoff(resolved_player.player)) def delete(self): raise UndefinedOperationError("Cannot modify outcomes in a derived strategic game.") @@ -180,7 +178,7 @@ class TreeGameOutcome: """The text label associated with this outcome.""" return "(%s)" % ( ",".join( - [deref(self.psp).deref().GetStrategy(cython.cast(Player, player).player) + [deref(deref(self.psp).deref()).GetStrategy(cython.cast(Player, player).player) .deref().GetLabel().c_str() for player in self.game.players] ) diff --git a/src/pygambit/player.pxi b/src/pygambit/player.pxi index bc8eb0f23..083578471 100644 --- a/src/pygambit/player.pxi +++ b/src/pygambit/player.pxi @@ -42,11 +42,11 @@ class PlayerInfosets: def __len__(self) -> int: """The number of information sets at which the player has the decision.""" - return self.player.deref().NumInfosets() + return self.player.deref().GetInfosets().size() def __iter__(self) -> typing.Iterator[Infoset]: - for i in range(self.player.deref().NumInfosets()): - yield Infoset.wrap(self.player.deref().GetInfoset(i + 1)) + for infoset in self.player.deref().GetInfosets(): + yield Infoset.wrap(infoset) def __getitem__(self, index: typing.Union[int, str]) -> Infoset: if isinstance(index, str): @@ -127,11 +127,11 @@ class PlayerStrategies: def __len__(self): """The number of strategies for the player in the game.""" - return self.player.deref().NumStrategies() + return self.player.deref().GetStrategies().size() def __iter__(self) -> typing.Iterator[Strategy]: - for i in range(self.player.deref().NumStrategies()): - yield Strategy.wrap(self.player.deref().GetStrategy(i + 1)) + for strategy in self.player.deref().GetStrategies(): + yield Strategy.wrap(strategy) def __getitem__(self, index: typing.Union[int, str]) -> Strategy: if isinstance(index, str): @@ -227,9 +227,9 @@ class Player: @property def min_payoff(self) -> Rational: """Returns the smallest payoff for the player in any outcome of the game.""" - return rat_to_py(self.player.deref().GetGame().deref().GetMinPayoff(self.number + 1)) + return rat_to_py(self.player.deref().GetGame().deref().GetMinPayoff(self.player)) @property def max_payoff(self) -> Rational: """Returns the largest payoff for the player in any outcome of the game.""" - return rat_to_py(self.player.deref().GetGame().deref().GetMaxPayoff(self.number + 1)) + return rat_to_py(self.player.deref().GetGame().deref().GetMaxPayoff(self.player)) diff --git a/src/pygambit/strategy.pxi b/src/pygambit/strategy.pxi index b228efbde..7e0611572 100644 --- a/src/pygambit/strategy.pxi +++ b/src/pygambit/strategy.pxi @@ -74,5 +74,45 @@ class Strategy: """The number of the strategy.""" return self.strategy.deref().GetNumber() - 1 - def action(self, infoset: Infoset) -> Action: - return Action.wrap(self.strategy.deref().GetAction(infoset.infoset)) + def action(self, infoset: typing.Union[Infoset, str]) -> typing.Optional[Action]: + """Get the action prescribed by a strategy for a given information set. + + .. versionadded:: 16.4.0 + + Parameters + ---------- + infoset + The information set for which to find the prescribed action. + Can be an Infoset object or its string label. + + Returns + ------- + Action or None + The prescribed action or None if the strategy is not defined for this + information set, that is, the information set is unreachable under this strategy. + + Raises + ------ + UndefinedOperationError + If the game is not an extensive-form (tree) game. + ValueError + If the information set belongs to a different player than the strategy. + """ + if not self.game.is_tree: + raise UndefinedOperationError( + "Strategy.action is only defined for strategies in extensive-form games." + ) + + resolved_infoset: Infoset = self.game._resolve_infoset(infoset, "Strategy.action") + + if resolved_infoset.player != self.player: + raise ValueError( + f"Information set {resolved_infoset} belongs to player " + f"'{resolved_infoset.player.label}', but this strategy " + f"belongs to player '{self.player.label}'." + ) + + action: c_GameAction = self.strategy.deref().GetAction(resolved_infoset.infoset) + if not action: + return None + return Action.wrap(action) diff --git a/src/solvers/enummixed/clique.cc b/src/solvers/enummixed/clique.cc index 6a1ebb89d..8a73cc48f 100644 --- a/src/solvers/enummixed/clique.cc +++ b/src/solvers/enummixed/clique.cc @@ -23,13 +23,12 @@ #include "clique.h" #include "gambit.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { CliqueEnumerator::CliqueEnumerator(Array &edgelist, int maxinp1, int maxinp2) : firstedge(std::min(maxinp1, maxinp2) + 1), maxinp1(maxinp1), maxinp2(maxinp2) { - int numco = getconnco(firstedge, edgelist); + const int numco = getconnco(firstedge, edgelist); workonco(numco, firstedge, edgelist); } @@ -305,7 +304,7 @@ void CliqueEnumerator::findfixpoint(int stk[], // stack building up stk[tmplist+count] containing the disconnected points */ for (j = scother; (j < ecother) && (count < *minnod); j++) { - int k = stk[j]; + const int k = stk[j]; if (!(binspect1 ? connected[p][k] : connected[k][p])) { stk[(*tmplist) + count] = k; count++; @@ -416,7 +415,7 @@ int CliqueEnumerator::getconnco(Array &firstedge, Array &edgelist) zero if e is index to the last edge */ { - int numco, newedge; + int numco; Array co1(0, maxinp1 - 1), co2(0, maxinp2 - 1); // components of node1,2 int i, j; // indices to left and right nodes @@ -429,7 +428,7 @@ int CliqueEnumerator::getconnco(Array &firstedge, Array &edgelist) } numco = 0; - for (newedge = 1; newedge <= edgelist.size(); newedge++) { + for (size_t newedge = 1; newedge <= edgelist.size(); newedge++) { i = edgelist[newedge].node1; j = edgelist[newedge].node2; @@ -596,5 +595,4 @@ void CliqueEnumerator::workonco(int numco, Array &firstedge, Array &e } } -} // namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash diff --git a/src/solvers/enummixed/clique.h b/src/solvers/enummixed/clique.h index 48d84d28a..d042a8f97 100644 --- a/src/solvers/enummixed/clique.h +++ b/src/solvers/enummixed/clique.h @@ -181,8 +181,7 @@ #include #include "gambit.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { const int MAXM = 700; // max. no of left nodes for incidence matrix const int MAXN = MAXM; // max. no of right nodes for incidence matrix @@ -265,7 +264,6 @@ class CliqueEnumerator { void workonco(int numco, Array &firstedge, Array &edgelist); }; -} // namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash #endif // GAMBIT_ENUMMIXED_CLIQUE_H diff --git a/src/solvers/enummixed/enummixed.cc b/src/solvers/enummixed/enummixed.cc index c95c980fa..183348602 100644 --- a/src/solvers/enummixed/enummixed.cc +++ b/src/solvers/enummixed/enummixed.cc @@ -25,11 +25,18 @@ #include "solvers/enummixed/enummixed.h" #include "clique.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { using namespace Gambit::linalg; +bool EqZero(const double &x) +{ + const double eps = ::pow(10.0, -15.0); + return (x <= eps && x >= -eps); +} + +bool EqZero(const Rational &x) { return x == Rational(0); } + template List>> EnumMixedStrategySolution::GetCliques() const { @@ -46,7 +53,7 @@ List>> EnumMixedStrategySolution::GetCliques() c edgelist[i].node2 = m_node2[i]; } - CliqueEnumerator clique(edgelist, m_v2 + 1, m_v1 + 1); + const CliqueEnumerator clique(edgelist, m_v2 + 1, m_v1 + 1); m_cliques1 = clique.GetCliques1(); m_cliques2 = clique.GetCliques2(); } @@ -54,14 +61,14 @@ List>> EnumMixedStrategySolution::GetCliques() c List>> solution; for (size_t cl = 1; cl <= m_cliques1.size(); cl++) { solution.push_back(List>()); - for (int i = 1; i <= m_cliques1[cl].size(); i++) { - for (int j = 1; j <= m_cliques2[cl].size(); j++) { + for (size_t i = 1; i <= m_cliques1[cl].size(); i++) { + for (size_t j = 1; j <= m_cliques2[cl].size(); j++) { MixedStrategyProfile profile(m_game->NewMixedStrategyProfile(static_cast(0))); - for (int k = 1; k <= m_key1[m_cliques1[cl][i]].size(); k++) { + for (size_t k = 1; k <= m_key1[m_cliques1[cl][i]].size(); k++) { profile[k] = m_key1[m_cliques1[cl][i]][k]; } - for (int k = 1; k <= m_key2[m_cliques2[cl][j]].size(); k++) { + for (size_t k = 1; k <= m_key2[m_cliques2[cl][j]].size(); k++) { profile[k + m_key1[m_cliques1[cl][i]].size()] = m_key2[m_cliques2[cl][j]][k]; } solution[cl].push_back(profile); @@ -73,7 +80,7 @@ List>> EnumMixedStrategySolution::GetCliques() c template std::shared_ptr> -EnumMixedStrategySolver::SolveDetailed(const Game &p_game) const +EnumMixedStrategySolveDetailed(const Game &p_game, StrategyCallbackType p_onEquilibrium) { if (p_game->NumPlayers() != 2) { throw UndefinedException("Method only valid for two-player games."); @@ -84,7 +91,7 @@ EnumMixedStrategySolver::SolveDetailed(const Game &p_game) const } std::shared_ptr> solution(new EnumMixedStrategySolution(p_game)); - PureStrategyProfile profile = p_game->NewPureStrategyProfile(); + const PureStrategyProfile profile = p_game->NewPureStrategyProfile(); Rational min = p_game->GetMinPayoff(); if (min > Rational(0)) { @@ -97,7 +104,7 @@ EnumMixedStrategySolver::SolveDetailed(const Game &p_game) const max = Rational(0); } - Rational fac(1, max - min); + const Rational fac(1, max - min); // Construct matrices A1, A2 Matrix A1(1, p_game->GetPlayer(1)->GetStrategies().size(), 1, @@ -106,11 +113,11 @@ EnumMixedStrategySolver::SolveDetailed(const Game &p_game) const p_game->GetPlayer(1)->GetStrategies().size()); for (size_t i = 1; i <= p_game->GetPlayer(1)->GetStrategies().size(); i++) { - profile->SetStrategy(p_game->GetPlayer(1)->GetStrategies()[i]); + profile->SetStrategy(p_game->GetPlayer(1)->GetStrategy(i)); for (size_t j = 1; j <= p_game->GetPlayer(2)->GetStrategies().size(); j++) { - profile->SetStrategy(p_game->GetPlayer(2)->GetStrategies()[j]); - A1(i, j) = fac * (profile->GetPayoff(1) - min); - A2(j, i) = fac * (profile->GetPayoff(2) - min); + profile->SetStrategy(p_game->GetPlayer(2)->GetStrategy(j)); + A1(i, j) = fac * (profile->GetPayoff(p_game->GetPlayer(1)) - min); + A2(j, i) = fac * (profile->GetPayoff(p_game->GetPlayer(2)) - min); } } @@ -121,8 +128,8 @@ EnumMixedStrategySolver::SolveDetailed(const Game &p_game) const b2 = (T)-1; // enumerate vertices of A1 x + b1 <= 0 and A2 x + b2 <= 0 - VertexEnumerator poly1(A1, b1); - VertexEnumerator poly2(A2, b2); + const VertexEnumerator poly1(A1, b1); + const VertexEnumerator poly2(A2, b2); const auto &verts1(poly1.VertexList()); const auto &verts2(poly2.VertexList()); @@ -164,17 +171,17 @@ EnumMixedStrategySolver::SolveDetailed(const Game &p_game) const eqm = static_cast(0); for (size_t k = 1; k <= p_game->GetPlayer(1)->GetStrategies().size(); k++) { if (bfs1.count(k)) { - eqm[p_game->GetPlayer(1)->GetStrategies()[k]] = -bfs1[k]; + eqm[p_game->GetPlayer(1)->GetStrategy(k)] = -bfs1[k]; } } for (size_t k = 1; k <= p_game->GetPlayer(2)->GetStrategies().size(); k++) { if (bfs2.count(k)) { - eqm[p_game->GetPlayer(2)->GetStrategies()[k]] = -bfs2[k]; + eqm[p_game->GetPlayer(2)->GetStrategy(k)] = -bfs2[k]; } } eqm = eqm.Normalize(); solution->m_extremeEquilibria.push_back(eqm); - this->m_onEquilibrium->Render(eqm); + p_onEquilibrium(eqm, "NE"); // note: The keys give the mixed strategy associated with each node. // The keys should also keep track of the basis @@ -198,22 +205,12 @@ EnumMixedStrategySolver::SolveDetailed(const Game &p_game) const return solution; } -template <> bool EnumMixedStrategySolver::EqZero(const double &x) -{ - double eps = ::pow(10.0, -15.0); - return (x <= eps && x >= -eps); -} - -template <> bool EnumMixedStrategySolver::EqZero(const Rational &x) -{ - return (x == Rational(0)); -} - -template class EnumMixedStrategySolver; -template class EnumMixedStrategySolver; - template class EnumMixedStrategySolution; template class EnumMixedStrategySolution; -} // namespace Nash -} // end namespace Gambit +template std::shared_ptr> +EnumMixedStrategySolveDetailed(const Game &p_game, StrategyCallbackType p_onEquilibrium); +template std::shared_ptr> +EnumMixedStrategySolveDetailed(const Game &p_game, StrategyCallbackType p_onEquilibrium); + +} // end namespace Gambit::Nash diff --git a/src/solvers/enummixed/enummixed.h b/src/solvers/enummixed/enummixed.h index 3ca083708..9ab1eaf9b 100644 --- a/src/solvers/enummixed/enummixed.h +++ b/src/solvers/enummixed/enummixed.h @@ -25,8 +25,7 @@ #include "games/nash.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { template class EnumMixedStrategySolver; @@ -35,8 +34,6 @@ template class EnumMixedStrategySolver; /// mixed strategies. /// template class EnumMixedStrategySolution { - friend class EnumMixedStrategySolver; - public: explicit EnumMixedStrategySolution(const Game &p_game) : m_game(p_game) {} ~EnumMixedStrategySolution() = default; @@ -46,7 +43,6 @@ template class EnumMixedStrategySolution { List>> GetCliques() const; -private: Game m_game; List> m_extremeEquilibria; @@ -54,7 +50,7 @@ template class EnumMixedStrategySolution { ///@{ List> m_key1, m_key2; List m_node1, m_node2; // IDs of each component of the extreme equilibria - int m_v1, m_v2; + int m_v1{0}, m_v2{0}; ///@} /// Representation of the connectedness of the extreme equilibria @@ -62,37 +58,19 @@ template class EnumMixedStrategySolution { mutable List> m_cliques1, m_cliques2; }; -template class EnumMixedStrategySolver : public StrategySolver { -public: - explicit EnumMixedStrategySolver( - std::shared_ptr> p_onEquilibrium = nullptr) - : StrategySolver(p_onEquilibrium) - { - } - ~EnumMixedStrategySolver() override = default; - - std::shared_ptr> SolveDetailed(const Game &p_game) const; - List> Solve(const Game &p_game) const override - { - return SolveDetailed(p_game)->GetExtremeEquilibria(); - } - -private: - /// Implement fuzzy equality for floating-point version when testing Nashness - static bool EqZero(const T &x); -}; - -inline List> EnumMixedStrategySolveDouble(const Game &p_game) -{ - return EnumMixedStrategySolver().Solve(p_game); -} +template +std::shared_ptr> +EnumMixedStrategySolveDetailed(const Game &p_game, + StrategyCallbackType p_onEquilibrium = NullStrategyCallback); -inline List> EnumMixedStrategySolveRational(const Game &p_game) +template +List> +EnumMixedStrategySolve(const Game &p_game, + StrategyCallbackType p_onEquilibrium = NullStrategyCallback) { - return EnumMixedStrategySolver().Solve(p_game); + return EnumMixedStrategySolveDetailed(p_game, p_onEquilibrium)->m_extremeEquilibria; } -} // namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash #endif // GAMBIT_NASH_ENUMMIXED_H diff --git a/src/solvers/enumpoly/behavextend.cc b/src/solvers/enumpoly/behavextend.cc index 9b61a4daf..ccbcc1b4c 100644 --- a/src/solvers/enumpoly/behavextend.cc +++ b/src/solvers/enumpoly/behavextend.cc @@ -51,11 +51,11 @@ void DeviationInfosets(List &answer, const BehaviorSupportProfile & const GamePlayer &p_player, const GameNode &p_node, const GameAction &p_action) { - GameNode child = p_node->GetChild(p_action); + const GameNode child = p_node->GetChild(p_action); if (child->IsTerminal()) { return; } - GameInfoset iset = child->GetInfoset(); + const GameInfoset iset = child->GetInfoset(); if (iset->GetPlayer() == p_player) { size_t insert = 0; bool done = false; @@ -94,9 +94,9 @@ PolynomialSystem ActionProbsSumToOneIneqs(const MixedBehaviorProfileGetPlayers()) { for (auto infoset : player->GetInfosets()) { if (!big_supp.HasAction(infoset)) { - int index_base = var_index.at(infoset); + const int index_base = var_index.at(infoset); Polynomial factor(BehavStratSpace, 1.0); - for (int k = 1; k < infoset->NumActions(); k++) { + for (size_t k = 1; k < infoset->GetActions().size(); k++) { factor -= Polynomial(BehavStratSpace, index_base + k, 1); } answer.push_back(factor); @@ -112,14 +112,12 @@ std::list DeviationSupports(const BehaviorSupportProfile std::list answer; Array active_act_no(isetlist.size()); - for (int k = 1; k <= active_act_no.size(); k++) { - active_act_no[k] = 0; - } + std::fill(active_act_no.begin(), active_act_no.end(), 0); BehaviorSupportProfile new_supp(big_supp); for (size_t i = 1; i <= isetlist.size(); i++) { - for (int j = 1; j < isetlist[i]->NumActions(); j++) { + for (size_t j = 1; j < isetlist[i]->GetActions().size(); j++) { new_supp.RemoveAction(isetlist[i]->GetAction(j)); } new_supp.AddAction(isetlist[i]->GetAction(1)); @@ -136,10 +134,11 @@ std::list DeviationSupports(const BehaviorSupportProfile } answer.push_back(new_supp); - int iset_cursor = isetlist.size(); + size_t iset_cursor = isetlist.size(); while (iset_cursor > 0) { if (active_act_no[iset_cursor] == 0 || - active_act_no[iset_cursor] == isetlist[iset_cursor]->NumActions()) { + active_act_no[iset_cursor] == + static_cast(isetlist[iset_cursor]->GetActions().size())) { iset_cursor--; } else { @@ -184,11 +183,11 @@ bool NashNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, const GameInfoset &iset, const GameAction &act) { while (tempnode != p_solution.GetGame()->GetRoot()) { - GameAction last_action = tempnode->GetPriorAction(); - GameInfoset last_infoset = last_action->GetInfoset(); + const GameAction last_action = tempnode->GetPriorAction(); + const GameInfoset last_infoset = last_action->GetInfoset(); if (last_infoset->IsChanceInfoset()) { - node_prob *= static_cast(last_infoset->GetActionProb(last_action->GetNumber())); + node_prob *= static_cast(last_infoset->GetActionProb(last_action)); } else if (dsupp.HasAction(last_infoset)) { if (last_infoset == iset) { @@ -207,14 +206,14 @@ bool NashNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, } } else { - int initial_var_no = var_index.at(last_infoset); - if (last_action->GetNumber() < last_infoset->NumActions()) { - int varno = initial_var_no + last_action->GetNumber(); + const int initial_var_no = var_index.at(last_infoset); + if (last_action != last_infoset->GetActions().back()) { + const int varno = initial_var_no + last_action->GetNumber(); node_prob *= Polynomial(BehavStratSpace, varno, 1); } else { Polynomial factor(BehavStratSpace, 1.0); - for (int k = 1; k < last_infoset->NumActions(); k++) { + for (size_t k = 1; k < last_infoset->GetActions().size(); k++) { factor -= Polynomial(BehavStratSpace, initial_var_no + k, 1); } node_prob *= factor; @@ -256,7 +255,7 @@ PolynomialSystem NashExpectedPayoffDiffPolys( if (NashNodeProbabilityPoly(p_solution, node_prob, BehavStratSpace, support, var_index, node, infoset, action)) { if (node->GetOutcome()) { - node_prob *= static_cast(node->GetOutcome()->GetPayoff(player)); + node_prob *= node->GetOutcome()->GetPayoff(player); } next_poly += node_prob; } @@ -284,8 +283,7 @@ PolynomialSystem ExtendsToNashIneqs(const MixedBehaviorProfile & } // end anonymous namespace -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { bool ExtendsToNash(const MixedBehaviorProfile &p_solution, const BehaviorSupportProfile &little_supp, @@ -296,11 +294,10 @@ bool ExtendsToNash(const MixedBehaviorProfile &p_solution, int num_vars = 0; std::map var_index; for (auto player : p_solution.GetGame()->GetPlayers()) { - List list_for_pl; for (auto infoset : player->GetInfosets()) { var_index[infoset] = num_vars; if (!big_supp.HasAction(infoset)) { - num_vars += infoset->NumActions() - 1; + num_vars += infoset->GetActions().size() - 1; } } } @@ -308,7 +305,7 @@ bool ExtendsToNash(const MixedBehaviorProfile &p_solution, // We establish the space auto BehavStratSpace = std::make_shared(num_vars); - PolynomialSystem inequalities = + const PolynomialSystem inequalities = ExtendsToNashIneqs(p_solution, BehavStratSpace, little_supp, big_supp, var_index); // set up the rectangle of search Vector bottoms(num_vars), tops(num_vars); @@ -317,8 +314,7 @@ bool ExtendsToNash(const MixedBehaviorProfile &p_solution, return PolynomialFeasibilitySolver(inequalities).HasSolution(Rectangle(bottoms, tops)); } -} // namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash namespace { @@ -330,11 +326,11 @@ bool ANFNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, int i, int j) { while (tempnode != p_solution.GetGame()->GetRoot()) { - GameAction last_action = tempnode->GetPriorAction(); - GameInfoset last_infoset = last_action->GetInfoset(); + const GameAction last_action = tempnode->GetPriorAction(); + const GameInfoset last_infoset = last_action->GetInfoset(); if (last_infoset->IsChanceInfoset()) { - node_prob *= static_cast(last_infoset->GetActionProb(last_action->GetNumber())); + node_prob *= static_cast(last_infoset->GetActionProb(last_action)); } else if (big_supp.HasAction(last_infoset)) { if (last_infoset == p_solution.GetGame()->GetPlayer(pl)->GetInfoset(i)) { @@ -350,14 +346,14 @@ bool ANFNodeProbabilityPoly(const MixedBehaviorProfile &p_solution, } } else { - int initial_var_no = var_index.at(last_infoset); - if (last_action->GetNumber() < last_infoset->NumActions()) { - int varno = initial_var_no + last_action->GetNumber(); + const int initial_var_no = var_index.at(last_infoset); + if (last_action != last_infoset->GetActions().back()) { + const int varno = initial_var_no + last_action->GetNumber(); node_prob *= Polynomial(BehavStratSpace, varno, 1); } else { Polynomial factor(BehavStratSpace, 1.0); - for (int k = 1; k < last_infoset->NumActions(); k++) { + for (size_t k = 1; k < last_infoset->GetActions().size(); k++) { factor -= Polynomial(BehavStratSpace, initial_var_no + k, 1); } node_prob *= factor; @@ -397,7 +393,7 @@ PolynomialSystem ANFExpectedPayoffDiffPolys(const MixedBehaviorProfileGetNumber(), infoset->GetNumber(), action->GetNumber())) { if (terminal->GetOutcome()) { - node_prob *= static_cast(terminal->GetOutcome()->GetPayoff(player)); + node_prob *= terminal->GetOutcome()->GetPayoff(player); } next_poly += node_prob; } @@ -424,8 +420,7 @@ PolynomialSystem ExtendsToANFNashIneqs(const MixedBehaviorProfile &p_solution, const BehaviorSupportProfile &little_supp, @@ -438,14 +433,14 @@ bool ExtendsToAgentNash(const MixedBehaviorProfile &p_solution, for (auto infoset : player->GetInfosets()) { var_index[infoset] = num_vars; if (!big_supp.HasAction(infoset)) { - num_vars += infoset->NumActions() - 1; + num_vars += infoset->GetActions().size() - 1; } } } // We establish the space auto BehavStratSpace = std::make_shared(num_vars); - PolynomialSystem inequalities = + const PolynomialSystem inequalities = ExtendsToANFNashIneqs(p_solution, BehavStratSpace, little_supp, big_supp, var_index); // set up the rectangle of search @@ -456,5 +451,4 @@ bool ExtendsToAgentNash(const MixedBehaviorProfile &p_solution, return PolynomialFeasibilitySolver(inequalities).HasSolution(Rectangle(bottoms, tops)); } -} // namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash diff --git a/src/solvers/enumpoly/behavextend.h b/src/solvers/enumpoly/behavextend.h index d3d804d0a..2488e2ea6 100644 --- a/src/solvers/enumpoly/behavextend.h +++ b/src/solvers/enumpoly/behavextend.h @@ -25,8 +25,7 @@ #include "gambit.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { // This asks whether there is a Nash extension of the MixedBehaviorProfile to // all information sets at which the behavioral probabilities are not @@ -47,7 +46,6 @@ bool ExtendsToAgentNash(const MixedBehaviorProfile &p_solution, const BehaviorSupportProfile &p_littleSupport, const BehaviorSupportProfile &p_bigSupport); -} // namespace Nash -} // namespace Gambit +} // namespace Gambit::Nash #endif // BEHAVEXTEND_H diff --git a/src/solvers/enumpoly/efgpoly.cc b/src/solvers/enumpoly/efgpoly.cc index ef151f16e..ea015b894 100644 --- a/src/solvers/enumpoly/efgpoly.cc +++ b/src/solvers/enumpoly/efgpoly.cc @@ -71,7 +71,7 @@ Polynomial BuildSequenceVariable(ProblemData &p_data, const GameSequence if (seq == p_sequence) { continue; } - if (int constraint_coef = + if (const int constraint_coef = p_data.sfg.GetConstraintEntry(p_sequence->GetInfoset(), seq->action)) { equation += BuildSequenceVariable(p_data, seq, var) * double(constraint_coef); } @@ -116,7 +116,7 @@ Polynomial GetPayoff(ProblemData &p_data, const GamePlayer &p_player) void IndifferenceEquations(ProblemData &p_data, PolynomialSystem &p_equations) { for (auto player : p_data.sfg.GetPlayers()) { - Polynomial payoff = GetPayoff(p_data, player); + const Polynomial payoff = GetPayoff(p_data, player); for (auto sequence : p_data.sfg.GetSequences(player)) { try { p_equations.push_back(payoff.PartialDerivative(p_data.var.at(sequence))); @@ -180,7 +180,8 @@ std::list> SolveSupport(const BehaviorSupportProfil std::list> solutions; for (auto root : roots) { - MixedBehaviorProfile sol(data.sfg.ToMixedBehaviorProfile(ToSequenceProbs(data, root))); + const MixedBehaviorProfile sol( + data.sfg.ToMixedBehaviorProfile(ToSequenceProbs(data, root))); if (ExtendsToNash(sol, BehaviorSupportProfile(sol.GetGame()), BehaviorSupportProfile(sol.GetGame()))) { solutions.push_back(sol); @@ -191,15 +192,15 @@ std::list> SolveSupport(const BehaviorSupportProfil } // end anonymous namespace -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { List> EnumPolyBehaviorSolve(const Game &p_game, int p_stopAfter, double p_maxregret, EnumPolyMixedBehaviorObserverFunctionType p_onEquilibrium, EnumPolyBehaviorSupportObserverFunctionType p_onSupport) { - if (double scale = p_game->GetMaxPayoff() - p_game->GetMinPayoff() != 0.0) { + const double scale = p_game->GetMaxPayoff() - p_game->GetMinPayoff(); + if (scale != 0.0) { p_maxregret *= scale; } @@ -211,7 +212,7 @@ EnumPolyBehaviorSolve(const Game &p_game, int p_stopAfter, double p_maxregret, bool isSingular = false; for (auto solution : SolveSupport(support, isSingular, std::max(p_stopAfter - int(ret.size()), 0))) { - MixedBehaviorProfile fullProfile = solution.ToFullSupport(); + const MixedBehaviorProfile fullProfile = solution.ToFullSupport(); if (fullProfile.GetMaxRegret() < p_maxregret) { p_onEquilibrium(fullProfile); ret.push_back(fullProfile); @@ -220,12 +221,11 @@ EnumPolyBehaviorSolve(const Game &p_game, int p_stopAfter, double p_maxregret, if (isSingular) { p_onSupport("singular", support); } - if (p_stopAfter > 0 && ret.size() >= p_stopAfter) { + if (p_stopAfter > 0 && static_cast(ret.size()) >= p_stopAfter) { break; } } return ret; } -} // namespace Nash -} // namespace Gambit +} // namespace Gambit::Nash diff --git a/src/solvers/enumpoly/enumpoly.h b/src/solvers/enumpoly/enumpoly.h index 1cd1d4c54..46a3c0c1d 100644 --- a/src/solvers/enumpoly/enumpoly.h +++ b/src/solvers/enumpoly/enumpoly.h @@ -27,8 +27,7 @@ #include "games/nash.h" #include "solvers/nashsupport/nashsupport.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { using EnumPolyMixedStrategyObserverFunctionType = std::function &)>; @@ -66,7 +65,6 @@ List> EnumPolyBehaviorSolve( EnumPolyMixedBehaviorObserverFunctionType p_onEquilibrium = EnumPolyNullMixedBehaviorObserver, EnumPolyBehaviorSupportObserverFunctionType p_onSupport = EnumPolyNullBehaviorSupportObserver); -} // namespace Nash -} // namespace Gambit +} // namespace Gambit::Nash #endif // GAMBIT_SOLVERS_ENUMPOLY_ENUMPOLY_H diff --git a/src/solvers/enumpoly/gameseq.cc b/src/solvers/enumpoly/gameseq.cc index 46ba39b1c..1205c459f 100644 --- a/src/solvers/enumpoly/gameseq.cc +++ b/src/solvers/enumpoly/gameseq.cc @@ -71,7 +71,7 @@ void GameSequenceForm::FillTableau(const GameNode &n, const Rational &prob, if (n->GetOutcome()) { for (auto player : m_support.GetGame()->GetPlayers()) { GetPayoffEntry(p_currentSequences, player) += - prob * static_cast(n->GetOutcome()->GetPayoff(player)); + prob * n->GetOutcome()->GetPayoff(player); } } if (!n->GetInfoset()) { @@ -119,7 +119,8 @@ GameSequenceForm::ToMixedBehaviorProfile(const std::map &x if (sequence->action == nullptr) { continue; } - if (double parent_prob = x.at(sequence->parent.lock()) > 0) { + const double parent_prob = x.at(sequence->parent.lock()); + if (parent_prob > 0) { b[sequence->action] = x.at(sequence) / parent_prob; } else { diff --git a/src/solvers/enumpoly/gameseq.h b/src/solvers/enumpoly/gameseq.h index ae2dc53ad..54f43c0eb 100644 --- a/src/solvers/enumpoly/gameseq.h +++ b/src/solvers/enumpoly/gameseq.h @@ -41,7 +41,7 @@ struct GameSequenceRep { { } - GameInfoset GetInfoset(void) const { return (action) ? action->GetInfoset() : nullptr; } + GameInfoset GetInfoset() const { return (action) ? action->GetInfoset() : nullptr; } bool operator<(const GameSequenceRep &other) const { @@ -161,8 +161,8 @@ class GameSequenceForm { }); } - iterator begin() const { return iterator(m_sfg, false); } - iterator end() const { return iterator(m_sfg, true); } + iterator begin() const { return {m_sfg, false}; } + iterator end() const { return {m_sfg, true}; } }; class PlayerSequences { @@ -249,8 +249,8 @@ class GameSequenceForm { bool operator!=(const iterator &it) const { return !(*this == it); } }; - iterator begin() { return iterator(m_sfg); } - iterator end() { return iterator(m_sfg, true); } + iterator begin() { return {m_sfg}; } + iterator end() { return {m_sfg, true}; } }; explicit GameSequenceForm(const BehaviorSupportProfile &p_support) : m_support(p_support) @@ -263,18 +263,15 @@ class GameSequenceForm { const BehaviorSupportProfile &GetSupport() const { return m_support; } - Sequences GetSequences() const { return Sequences(this); } + Sequences GetSequences() const { return {this}; } - PlayerSequences GetSequences(const GamePlayer &p_player) const - { - return PlayerSequences(this, p_player); - } + PlayerSequences GetSequences(const GamePlayer &p_player) const { return {this, p_player}; } - Contingencies GetContingencies() const { return Contingencies(this); } + Contingencies GetContingencies() const { return {this}; } - Array GetPlayers() const { return m_support.GetGame()->GetPlayers(); } + GameRep::Players GetPlayers() const { return m_support.GetGame()->GetPlayers(); } - Infosets GetInfosets() const { return Infosets(this); } + Infosets GetInfosets() const { return {this}; } const Rational &GetPayoff(const std::map &p_profile, const GamePlayer &p_player) const diff --git a/src/solvers/enumpoly/indexproduct.h b/src/solvers/enumpoly/indexproduct.h index 204c20add..13235a727 100644 --- a/src/solvers/enumpoly/indexproduct.h +++ b/src/solvers/enumpoly/indexproduct.h @@ -80,8 +80,8 @@ class CartesianIndexProduct { CartesianIndexProduct &operator=(const CartesianIndexProduct &) = default; - iterator begin() { return iterator(this, false); } - iterator end() { return iterator(this, true); } + iterator begin() { return {this, false}; } + iterator end() { return {this, true}; } }; } // namespace Gambit diff --git a/src/solvers/enumpoly/ndarray.h b/src/solvers/enumpoly/ndarray.h index b3181cc14..aebefbf0e 100644 --- a/src/solvers/enumpoly/ndarray.h +++ b/src/solvers/enumpoly/ndarray.h @@ -59,17 +59,19 @@ template class NDArray { } public: - NDArray() : m_vector_dim(0) {} + NDArray() : m_vector_dim(0), m_offsets_sum(0) {} explicit NDArray(const Array &p_index_dim, int p_vector_dim) : m_index_dim(p_index_dim), m_vector_dim(p_vector_dim), m_offsets(p_index_dim.size() + 1), - m_storage( - std::accumulate(m_index_dim.begin(), m_index_dim.end(), 1, std::multiplies()) * - m_vector_dim) + m_offsets_sum(0), + m_storage(std::accumulate(m_index_dim.begin(), m_index_dim.end(), 1, std::multiplies<>()) * + m_vector_dim) { m_offsets.front() = 1; std::partial_sum(m_index_dim.begin(), m_index_dim.end(), std::next(m_offsets.begin()), - std::multiplies()); + std::multiplies<>()); + // NOLINTBEGIN(cppcoreguidelines-prefer-member-initializer) m_offsets_sum = std::accumulate(m_offsets.begin(), m_offsets.end(), 0); + // NOLINTEND(cppcoreguidelines-prefer-member-initializer) } NDArray(const NDArray &) = default; diff --git a/src/solvers/enumpoly/nfgpoly.cc b/src/solvers/enumpoly/nfgpoly.cc index d057afb54..45927d13b 100644 --- a/src/solvers/enumpoly/nfgpoly.cc +++ b/src/solvers/enumpoly/nfgpoly.cc @@ -98,8 +98,7 @@ ConstructEquations(std::shared_ptr space, const StrategySupportPr } // end anonymous namespace -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { std::list> EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_singular, @@ -109,7 +108,7 @@ EnumPolyStrategySupportSolve(const StrategySupportProfile &support, bool &is_sin support.GetGame()->NumPlayers()); auto strategy_poly = BuildStrategyVariables(space, support); - PolynomialSystem equations = ConstructEquations(space, support, strategy_poly); + const PolynomialSystem equations = ConstructEquations(space, support, strategy_poly); Vector bottoms(space->GetDimension()), tops(space->GetDimension()); bottoms = 0; @@ -144,7 +143,8 @@ EnumPolyStrategySolve(const Game &p_game, int p_stopAfter, double p_maxregret, EnumPolyMixedStrategyObserverFunctionType p_onEquilibrium, EnumPolyStrategySupportObserverFunctionType p_onSupport) { - if (double scale = p_game->GetMaxPayoff() - p_game->GetMinPayoff() != 0.0) { + const double scale = p_game->GetMaxPayoff() - p_game->GetMinPayoff(); + if (scale != 0.0) { p_maxregret *= scale; } @@ -155,8 +155,8 @@ EnumPolyStrategySolve(const Game &p_game, int p_stopAfter, double p_maxregret, p_onSupport("candidate", support); bool is_singular; for (auto solution : EnumPolyStrategySupportSolve( - support, is_singular, std::max(p_stopAfter - int(ret.size()), 0))) { - MixedStrategyProfile fullProfile = solution.ToFullSupport(); + support, is_singular, std::max(p_stopAfter - static_cast(ret.size()), 0))) { + const MixedStrategyProfile fullProfile = solution.ToFullSupport(); if (fullProfile.GetMaxRegret() < p_maxregret) { p_onEquilibrium(fullProfile); ret.push_back(fullProfile); @@ -166,12 +166,11 @@ EnumPolyStrategySolve(const Game &p_game, int p_stopAfter, double p_maxregret, if (is_singular) { p_onSupport("singular", support); } - if (p_stopAfter > 0 && ret.size() >= p_stopAfter) { + if (p_stopAfter > 0 && static_cast(ret.size()) >= p_stopAfter) { break; } } return ret; } -} // namespace Nash -} // namespace Gambit +} // namespace Gambit::Nash diff --git a/src/solvers/enumpoly/poly.h b/src/solvers/enumpoly/poly.h index 36bce7a86..6e0830ee6 100644 --- a/src/solvers/enumpoly/poly.h +++ b/src/solvers/enumpoly/poly.h @@ -32,7 +32,7 @@ class VariableSpace { public: struct Variable { std::string name; - int number; + int number{0}; }; explicit VariableSpace(size_t nvars) diff --git a/src/solvers/enumpoly/poly.imp b/src/solvers/enumpoly/poly.imp index 0327f32d7..854189da4 100644 --- a/src/solvers/enumpoly/poly.imp +++ b/src/solvers/enumpoly/poly.imp @@ -87,7 +87,7 @@ List> Polynomial::Mult(const List> &One, for (size_t i = 1; i <= One.size(); i++) { for (size_t j = 1; j <= Two.size(); j++) { - Monomial next = One[i] * Two[j]; + const Monomial next = One[i] * Two[j]; if (answer.empty()) { answer.push_back(next); @@ -111,7 +111,7 @@ List> Polynomial::Mult(const List> &One, } while (bot < top - 1) { - int test = bot + (top - bot) / 2; + const int test = bot + (top - bot) / 2; if (answer[test].ExpV() == next.ExpV()) { bot = top = test; } @@ -138,7 +138,7 @@ List> Polynomial::Mult(const List> &One, template Polynomial Polynomial::DivideByPolynomial(const Polynomial &den) const { - Polynomial zero(m_space, static_cast(0)); + const Polynomial zero(m_space, static_cast(0)); if (den == zero) { throw ZeroDivideException(); @@ -163,9 +163,9 @@ template Polynomial Polynomial::DivideByPolynomial(const Polynom Polynomial remainder = *this; while (remainder != zero) { - Polynomial quot = remainder.LeadingCoefficient(last) / den.LeadingCoefficient(last); - Polynomial power_of_last(m_space, last, - remainder.DegreeOfVar(last) - den.DegreeOfVar(last)); + const Polynomial quot = remainder.LeadingCoefficient(last) / den.LeadingCoefficient(last); + const Polynomial power_of_last(m_space, last, + remainder.DegreeOfVar(last) - den.DegreeOfVar(last)); result += quot * power_of_last; remainder -= quot * power_of_last * den; } @@ -199,7 +199,7 @@ template Polynomial Polynomial::PartialDerivative(int varnumber) template Polynomial Polynomial::LeadingCoefficient(int varnumber) const { Polynomial newPoly(*this); - int degree = DegreeOfVar(varnumber); + const int degree = DegreeOfVar(varnumber); newPoly.m_terms = List>(); for (size_t j = 1; j <= m_terms.size(); j++) { if (m_terms[j].ExpV()[varnumber] == degree) { diff --git a/src/solvers/enumpoly/polyfeasible.h b/src/solvers/enumpoly/polyfeasible.h index 34ca13e4b..8566876ea 100644 --- a/src/solvers/enumpoly/polyfeasible.h +++ b/src/solvers/enumpoly/polyfeasible.h @@ -44,7 +44,7 @@ class PolynomialFeasibilitySolver { private: PolynomialSystem m_system; PolynomialSystemDerivatives m_systemDerivs; - double m_epsilon; + double m_epsilon{1.0e-6}; bool IsASolution(const Vector &v) const { @@ -56,7 +56,7 @@ class PolynomialFeasibilitySolver { for (int i = 1; i <= m_system.size(); i++) { if (m_systemDerivs[precedence[i]].PolyEverywhereNegativeIn(r)) { if (i != 1) { // We have found a new "most likely to never be positive" - int tmp = precedence[i]; + const int tmp = precedence[i]; for (int j = 1; j <= i - 1; j++) { precedence[i - j + 1] = precedence[i - j]; } @@ -86,7 +86,7 @@ class PolynomialFeasibilitySolver { public: explicit PolynomialFeasibilitySolver(const PolynomialSystem &given) - : m_system(given), m_systemDerivs(given), m_epsilon(1.0e-6) + : m_system(given), m_systemDerivs(given) { } PolynomialFeasibilitySolver(const PolynomialFeasibilitySolver &) = delete; diff --git a/src/solvers/enumpoly/polypartial.imp b/src/solvers/enumpoly/polypartial.imp index f500eabd5..9248c5251 100644 --- a/src/solvers/enumpoly/polypartial.imp +++ b/src/solvers/enumpoly/polypartial.imp @@ -56,7 +56,7 @@ T PolynomialDerivatives::MaximalNonconstantContribution(const Node &n, const wrtos[i]++; T increment = Gambit::abs(child.data.Evaluate(p)); - for (int j = 1; j <= p.size(); j++) { + for (size_t j = 1; j <= p.size(); j++) { for (int k = 1; k <= wrtos[j]; k++) { increment *= halvesoflengths[j]; increment /= static_cast(k); @@ -84,9 +84,9 @@ template bool PolynomialDerivatives::PolyHasNoRootsIn(const Rectang if (m_treeroot.data.IsMultiaffine()) { return MultiaffinePolyHasNoRootsIn(r); } - Vector center = r.Center(); + const Vector center = r.Center(); T constant = Gambit::abs(m_treeroot.data.Evaluate(center)); - Vector HalvesOfSideLengths = r.SideLengths() / 2; + const Vector HalvesOfSideLengths = r.SideLengths() / 2; return MaximalNonconstantContribution(center, HalvesOfSideLengths) < constant; } @@ -119,7 +119,7 @@ template bool PolynomialDerivatives::MultiaffinePolyEverywhereNegativeIn(const Rectangle &r) const { if (GetDimension() == 0) { - Vector point(GetDimension()); + const Vector point(GetDimension()); return m_treeroot.data.Evaluate(point) < static_cast(0); } diff --git a/src/solvers/enumpoly/polysolver.cc b/src/solvers/enumpoly/polysolver.cc index af109a3e8..ef5d3102b 100644 --- a/src/solvers/enumpoly/polysolver.cc +++ b/src/solvers/enumpoly/polysolver.cc @@ -149,8 +149,8 @@ bool PolynomialSystemSolver::HasNoOtherRootsIn(const Rectangle &r, Vector PolynomialSystemSolver::NewtonStep(const Vector &point) const { - Vector evals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); - Matrix deriv = m_derivatives.DerivativeMatrix(point, NumEquations()); + const Vector evals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); + const Matrix deriv = m_derivatives.DerivativeMatrix(point, NumEquations()); auto transpose = deriv.Transpose(); auto sqmat = SquareMatrix(deriv * transpose); if (std::abs(sqmat.Determinant()) <= 1.0e-9) { @@ -161,8 +161,8 @@ Vector PolynomialSystemSolver::NewtonStep(const Vector &point) c Vector PolynomialSystemSolver::ImprovingNewtonStep(const Vector &point) const { - Vector evals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); - Matrix deriv = m_derivatives.DerivativeMatrix(point, NumEquations()); + const Vector evals = m_derivatives.ValuesOfRootPolys(point, NumEquations()); + const Matrix deriv = m_derivatives.DerivativeMatrix(point, NumEquations()); auto transpose = deriv.Transpose(); auto sqmat = SquareMatrix(deriv * transpose); if (std::abs(sqmat.Determinant()) <= 1.0e-9) { @@ -182,7 +182,7 @@ std::list> PolynomialSystemSolver::FindRoots(const Rectangle> roots; if (NumEquations() == 0) { - roots.push_back(Vector()); + roots.emplace_back(); } else { FindRoots(roots, r, max_roots); @@ -191,7 +191,7 @@ std::list> PolynomialSystemSolver::FindRoots(const Rectangle> &rootlist, - const Rectangle &r, const int max_roots) const + const Rectangle &r, const size_t max_roots) const { if (SystemHasNoRootsIn(r)) { return; @@ -199,7 +199,7 @@ void PolynomialSystemSolver::FindRoots(std::list> &rootlist, Vector point = r.Center(); if (NewtonRootInRectangle(r, point) && HasNoOtherRootsIn(r, point)) { - // If all inequalities are satisfied and we haven't found the point before, add to the + // If all inequalities are satisfied, and we haven't found the point before, add to the // list of roots if (std::all_of(std::next(m_derivatives.begin(), NumEquations()), m_derivatives.end(), [&point](const PolynomialDerivatives &d) { diff --git a/src/solvers/enumpoly/polysolver.h b/src/solvers/enumpoly/polysolver.h index 10212b452..7e9d5ecb1 100644 --- a/src/solvers/enumpoly/polysolver.h +++ b/src/solvers/enumpoly/polysolver.h @@ -70,7 +70,8 @@ class PolynomialSystemSolver { bool HasNoOtherRootsIn(const Rectangle &, const Vector &) const; - void FindRoots(std::list> &, const Rectangle &, int) const; + void FindRoots(std::list> &rootlist, const Rectangle &r, + const size_t max_roots) const; public: explicit PolynomialSystemSolver(const PolynomialSystem &p_system) diff --git a/src/solvers/enumpure/enumpure.h b/src/solvers/enumpure/enumpure.h index 37ff41a2c..571651749 100644 --- a/src/solvers/enumpure/enumpure.h +++ b/src/solvers/enumpure/enumpure.h @@ -25,26 +25,28 @@ #include "games/nash.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { + +inline bool IsNash(const PureStrategyProfile &p_profile) +{ + for (const auto &player : p_profile->GetGame()->GetPlayers()) { + const Rational current = p_profile->GetPayoff(player); + for (auto strategy : player->GetStrategies()) { + if (p_profile->GetStrategyValue(strategy) > current) { + return false; + } + } + } + return true; +} /// /// Enumerate pure-strategy Nash equilibria of a game. By definition, /// pure-strategy equilibrium uses the strategic representation of a game. /// -class EnumPureStrategySolver : public StrategySolver { -public: - explicit EnumPureStrategySolver( - std::shared_ptr> p_onEquilibrium = nullptr) - : StrategySolver(p_onEquilibrium) - { - } - ~EnumPureStrategySolver() override = default; - - List> Solve(const Game &p_game) const override; -}; - -inline List> EnumPureStrategySolver::Solve(const Game &p_game) const +inline List> EnumPureStrategySolve( + const Game &p_game, + StrategyCallbackType p_onEquilibrium = NullStrategyCallback) { if (!p_game->IsPerfectRecall()) { throw UndefinedException( @@ -52,18 +54,27 @@ inline List> EnumPureStrategySolver::Solve(const } List> solutions; for (auto citer : StrategyContingencies(p_game)) { - if (citer->IsNash()) { - MixedStrategyProfile profile = citer->ToMixedStrategyProfile(); - m_onEquilibrium->Render(profile); - solutions.push_back(profile); + if (IsNash(citer)) { + solutions.push_back(citer->ToMixedStrategyProfile()); + p_onEquilibrium(solutions.back(), "NE"); } } return solutions; } -inline List> EnumPureStrategySolve(const Game &p_game) +inline bool IsAgentNash(const PureBehaviorProfile &p_profile) { - return EnumPureStrategySolver().Solve(p_game); + for (const auto &player : p_profile.GetGame()->GetPlayers()) { + auto current = p_profile.GetPayoff(player); + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + if (p_profile.GetPayoff(action) > current) { + return false; + } + } + } + } + return true; } /// @@ -73,38 +84,21 @@ inline List> EnumPureStrategySolve(const Game &p_ /// set (rather than possible deviations by the same player at multiple /// information sets. /// -class EnumPureAgentSolver : public BehavSolver { -public: - explicit EnumPureAgentSolver( - std::shared_ptr> p_onEquilibrium = nullptr) - : BehavSolver(p_onEquilibrium) - { - } - ~EnumPureAgentSolver() override = default; - - List> Solve(const Game &) const override; -}; - -inline List> EnumPureAgentSolver::Solve(const Game &p_game) const +inline List> +EnumPureAgentSolve(const Game &p_game, + BehaviorCallbackType p_onEquilibrium = NullBehaviorCallback) { List> solutions; - BehaviorSupportProfile support(p_game); + const BehaviorSupportProfile support(p_game); for (auto citer : BehaviorContingencies(BehaviorSupportProfile(p_game))) { - if (citer.IsAgentNash()) { - MixedBehaviorProfile profile = citer.ToMixedBehaviorProfile(); - m_onEquilibrium->Render(profile); - solutions.push_back(profile); + if (IsAgentNash(citer)) { + solutions.push_back(citer.ToMixedBehaviorProfile()); + p_onEquilibrium(solutions.back(), "NE"); } } return solutions; } -inline List> EnumPureAgentSolve(const Game &p_game) -{ - return EnumPureAgentSolver().Solve(p_game); -} - -} // end namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash #endif // GAMBIT_NASH_ENUMPURE_H diff --git a/src/solvers/gnm/gnm.cc b/src/solvers/gnm/gnm.cc index 1ec47879c..d463acdb9 100644 --- a/src/solvers/gnm/gnm.cc +++ b/src/solvers/gnm/gnm.cc @@ -42,8 +42,9 @@ Solve(const Game &p_game, const std::shared_ptr &p_rep, const cvector & if (p_lambdaEnd >= 0.0) { throw std::out_of_range("Value of lambdaEnd must be negative"); } - bool nonzero = std::accumulate(p_pert.cbegin(), p_pert.cend(), false, - [](bool accum, double value) { return accum || value != 0.0; }); + const bool nonzero = + std::accumulate(p_pert.cbegin(), p_pert.cend(), false, + [](bool accum, double value) { return accum || value != 0.0; }); if (!nonzero) { throw UndefinedException("Perturbation vector must have at least one nonzero component."); } @@ -66,22 +67,23 @@ Solve(const Game &p_game, const std::shared_ptr &p_rep, const cvector & } // namespace -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { List> GNMStrategySolve(const Game &p_game, double p_lambdaEnd, int p_steps, int p_localNewtonInterval, int p_localNewtonMaxits, - StrategyCallbackType p_callback) + StrategyCallbackType p_callback) { if (!p_game->IsPerfectRecall()) { throw UndefinedException( "Computing equilibria of games with imperfect recall is not supported."); } - std::shared_ptr A = BuildGame(p_game, true); + const std::shared_ptr A = BuildGame(p_game, true); MixedStrategyProfile pert = p_game->NewMixedStrategyProfile(0.0); - for (auto strategy : p_game->GetStrategies()) { - pert[strategy] = 0.0; + for (const auto &player : p_game->GetPlayers()) { + for (const auto &strategy : player->GetStrategies()) { + pert[strategy] = 0.0; + } } for (auto player : p_game->GetPlayers()) { pert[player->GetStrategies().front()] = 1.0; @@ -94,16 +96,15 @@ List> GNMStrategySolve(const MixedStrategyProfile p_callback) { if (!p_pert.GetGame()->IsPerfectRecall()) { throw UndefinedException( "Computing equilibria of games with imperfect recall is not supported."); } - std::shared_ptr A = BuildGame(p_pert.GetGame(), true); + const std::shared_ptr A = BuildGame(p_pert.GetGame(), true); return Solve(p_pert.GetGame(), A, ToPerturbation(p_pert), p_lambdaEnd, p_steps, p_localNewtonInterval, p_localNewtonMaxits, p_callback); } -} // namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash diff --git a/src/solvers/gnm/gnm.h b/src/solvers/gnm/gnm.h index 38e94a753..47ce0ff99 100644 --- a/src/solvers/gnm/gnm.h +++ b/src/solvers/gnm/gnm.h @@ -26,8 +26,7 @@ #include "games/nash.h" #include "solvers/gtracer/gtracer.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { const double GNM_LAMBDA_END_DEFAULT = -10.0; const int GNM_LOCAL_NEWTON_INTERVAL_DEFAULT = 3; @@ -36,16 +35,16 @@ const int GNM_STEPS_DEFAULT = 100; List> GNMStrategySolve(const Game &p_game, double p_lambdaEnd, int p_steps, int p_localNewtonInterval, - int p_localNewtonMaxits, StrategyCallbackType p_callback = NullStrategyCallback); + int p_localNewtonMaxits, + StrategyCallbackType p_callback = NullStrategyCallback); /// @brief Compute the mixed strategy equilibria accessible via the initial ray determined /// by \p p_profile using the Global Newton method List> GNMStrategySolve(const MixedStrategyProfile &p_profile, double p_lambdaEnd, int p_steps, int p_localNewtonInterval, int p_localNewtonMaxits, - StrategyCallbackType p_callback = NullStrategyCallback); + StrategyCallbackType p_callback = NullStrategyCallback); -} // namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash #endif // GAMBIT_NASH_GNM_H diff --git a/src/solvers/gtracer/aggame.cc b/src/solvers/gtracer/aggame.cc index 5d43cb287..a09976909 100644 --- a/src/solvers/gtracer/aggame.cc +++ b/src/solvers/gtracer/aggame.cc @@ -23,13 +23,13 @@ #include "aggame.h" -namespace Gambit { -namespace gametracer { +namespace Gambit::gametracer { void aggame::computePartialP_PureNode(int player1, int act1, std::vector &tasks) const { - int i, j, Node = aggPtr->actionSets[player1][act1]; - int numNei = aggPtr->neighbors[Node].size(); + int i, j; + const int Node = aggPtr->actionSets[player1][act1]; + const int numNei = aggPtr->neighbors[Node].size(); // assert(aggPtr->isPure[Node]||tasks.size()==0); std::vector strat(numNei); @@ -90,8 +90,8 @@ void aggame::computePartialP_bisect(int player1, int act1, std::vector::ite aggPtr->Pr[*start].reset(); return; } - int Node = aggPtr->actionSets[player1][act1]; - int numNei = aggPtr->neighbors[Node].size(); + const int Node = aggPtr->actionSets[player1][act1]; + const int numNei = aggPtr->neighbors[Node].size(); int player2; std::vector::iterator ptr, mid = start + (endp - start) / 2; @@ -333,8 +333,8 @@ void aggame::computeUndisturbedPayoff(agg::AggNumber &undisturbedPayoff, bool &h if (has) { return; } - int Node = aggPtr->actionSets[player1][act1]; - int numNei = aggPtr->neighbors[Node].size(); + const int Node = aggPtr->actionSets[player1][act1]; + const int numNei = aggPtr->neighbors[Node].size(); if (player2 == player1) { undisturbedPayoff = aggPtr->Pr[player2].inner_prod(aggPtr->payoffs[Node]); } @@ -352,8 +352,8 @@ void aggame::savePayoff(cmatrix &dest, int player1, int act1, int player2, int a bool partial) const { - int Node = aggPtr->actionSets[player1][act1]; - int numNei = aggPtr->neighbors[Node].size(); + const int Node = aggPtr->actionSets[player1][act1]; + const int numNei = aggPtr->neighbors[Node].size(); if (!partial) { std::pair, agg::AggNumber> pair1(aggPtr->projection[Node][player2][act2], @@ -381,15 +381,15 @@ void aggame::savePayoff(cmatrix &dest, int player1, int act1, int player2, int a void aggame::computePayoff(cmatrix &dest, int player1, int act1, int player2, int act2, agg::trie_map &cache) const { - int Node = aggPtr->actionSets[player1][act1]; - int numNei = aggPtr->neighbors[Node].size(); + const int Node = aggPtr->actionSets[player1][act1]; + const int numNei = aggPtr->neighbors[Node].size(); std::pair, agg::AggNumber> insPair(aggPtr->projection[Node][player2][act2], 0); insPair.first.reserve(numNei + 3); insPair.first.push_back(player1); insPair.first.push_back(act1); insPair.first.push_back(player2); - std::pair::iterator, bool> r = cache.insert(insPair); + const std::pair::iterator, bool> r = cache.insert(insPair); if (!r.second) { dest(act1 + firstAction(player1), act2 + firstAction(player2)) = r.first->second; } @@ -403,7 +403,7 @@ void aggame::computePayoff(cmatrix &dest, int player1, int act1, int player2, in void aggame::KSymPayoffMatrix(cmatrix &dest, const cvector &s, agg::AggNumber fuzz) const { - std::vector sp(s.values(), s.values() + s.getm()); + const std::vector sp(s.values(), s.values() + s.getm()); // simple implementation using expected payoffs: for (int rowcls = 0; rowcls < getNumPlayerClasses(); ++rowcls) { for (int rowa = 0; rowa < getNumKSymActions(rowcls); ++rowa) { @@ -440,5 +440,4 @@ void aggame::KSymPayoffMatrix(cmatrix &dest, const cvector &s, agg::AggNumber fu } } -} // namespace gametracer -} // end namespace Gambit +} // end namespace Gambit::gametracer diff --git a/src/solvers/gtracer/aggame.h b/src/solvers/gtracer/aggame.h index a3f5f2490..3dd5bdebb 100644 --- a/src/solvers/gtracer/aggame.h +++ b/src/solvers/gtracer/aggame.h @@ -30,8 +30,7 @@ #include "gambit.h" #include "games/gameagg.h" -namespace Gambit { -namespace gametracer { +namespace Gambit::gametracer { class aggame : public gnmgame { public: @@ -67,7 +66,7 @@ class aggame : public gnmgame { void getPayoffVector(cvector &dest, int player, const cvector &s) const override { auto ss = const_cast(s); - std::vector sp(ss.values(), ss.values() + ss.getm()); + const std::vector sp(ss.values(), ss.values() + ss.getm()); std::vector d(aggPtr->getNumActions(player)); aggPtr->getPayoffVector(d, player, sp); std::copy(d.begin(), d.end(), dest.values()); @@ -105,7 +104,6 @@ class aggame : public gnmgame { int act1, int player2) const; }; -} // namespace gametracer -} // end namespace Gambit +} // end namespace Gambit::gametracer #endif // GAMBIT_GTRACER_AGGAME_H diff --git a/src/solvers/gtracer/cmatrix.cc b/src/solvers/gtracer/cmatrix.cc index 9d75ef254..cc106b18b 100644 --- a/src/solvers/gtracer/cmatrix.cc +++ b/src/solvers/gtracer/cmatrix.cc @@ -27,8 +27,7 @@ #include "core/matrix.h" #include "cmatrix.h" -namespace Gambit { -namespace gametracer { +namespace Gambit::gametracer { int cmatrix::LUdecomp(cmatrix &LU, std::vector &ix) const { @@ -258,5 +257,4 @@ double cmatrix::trace() const return sum; } -} // namespace gametracer -} // end namespace Gambit +} // end namespace Gambit::gametracer diff --git a/src/solvers/gtracer/cmatrix.h b/src/solvers/gtracer/cmatrix.h index 2d93deb50..2f8b34906 100644 --- a/src/solvers/gtracer/cmatrix.h +++ b/src/solvers/gtracer/cmatrix.h @@ -30,8 +30,7 @@ #include #include -namespace Gambit { -namespace gametracer { +namespace Gambit::gametracer { class cvector { friend class cmatrix; @@ -108,7 +107,7 @@ class cvector { cvector operator-() const { - cvector ret(m); + cvector const ret(m); for (int i = 0; i < m; i++) { ret.x[i] = -x[i]; } @@ -156,17 +155,17 @@ class cvector { double &operator[](int i) { return x[i]; } /// Return a forward iterator starting at the beginning of the vector - iterator begin() { return iterator(this, 0); } + iterator begin() { return {this, 0}; } /// Return a forward iterator past the end of the vector - iterator end() { return iterator(this, m); } + iterator end() { return {this, m}; } /// Return a const forward iterator starting at the beginning of the vector - const_iterator begin() const { return const_iterator(this, 0); } + const_iterator begin() const { return {this, 0}; } /// Return a const forward iterator past the end of the vector - const_iterator end() const { return const_iterator(this, m); } + const_iterator end() const { return {this, m}; } /// Return a const forward iterator starting at the beginning of the vector - const_iterator cbegin() const { return const_iterator(this, 0); } + const_iterator cbegin() const { return {this, 0}; } /// Return a const forward iterator past the end of the vector - const_iterator cend() const { return const_iterator(this, m); } + const_iterator cend() const { return {this, m}; } cvector &operator+=(const cvector &v) { @@ -380,47 +379,31 @@ inline std::ostream &operator<<(std::ostream &s, const cvector &v) class cmatrix { public: - explicit cmatrix(int m = 1, int n = 1) - { - this->m = m; - this->n = n; - s = m * n; - x = new double[s]; - } + explicit cmatrix(int m = 1, int n = 1) : m(m), n(n), s(m * n), x(new double[s]) {} ~cmatrix() { delete[] x; } cmatrix(const cmatrix &ma, bool transpose = false) + : m((transpose) ? ma.n : ma.m), n((transpose) ? ma.m : ma.n), s(m * n), x(new double[s]) { - s = ma.m * ma.n; - x = new double[s]; if (transpose) { - int i, j, c; - n = ma.m; - m = ma.n; - c = 0; - for (i = 0; i < m; i++) { - for (j = 0; j < n; j++, c++) { + int c = 0; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++, c++) { x[c] = ma.x[i + j * m]; } } } else { - n = ma.n; - m = ma.m; - int i; - for (i = 0; i < s; i++) { + for (int i = 0; i < s; i++) { x[i] = ma.x[i]; } } } cmatrix(int m, int n, const double &a, bool diaonly = false) + : m(m), n(n), s(m * n), x(new double[s]) { - this->m = m; - this->n = n; - s = m * n; - x = new double[s]; if (diaonly) { int i; // for(i=0;im = m; - this->n = n; - s = m * n; - x = new double[s]; // for(int i=0;i &Eq, int steps, double fuzz, Index = 1, // index of the equilibrium we're moving towards stepsLeft; // number of linear steps remaining until we hit the boundary - int N = A.getNumPlayers(), + const int + N = A.getNumPlayers(), M = A.getNumActions(); // the two most important cvector sizes, stored locally for brevity double det, // determinant of the jacobian newV, // utility variable @@ -156,7 +156,8 @@ void GNM(gnmgame &A, cvector &g, std::list &Eq, int steps, double fuzz, err(M), backup(M); // utility variables for use as intermediate values in computations - cmatrix Y1(M, M), Y2(M, M), Y3(M, M); + const cmatrix Y1(M, M); + const cmatrix Y2(M, M), Y3(M, M); cvector G(N), yn1(N), ym1(M), ym2(M), ym3(M); // INITIALIZATION @@ -434,5 +435,4 @@ void GNM(gnmgame &A, cvector &g, std::list &Eq, int steps, double fuzz, } } -} // namespace gametracer -} // end namespace Gambit +} // end namespace Gambit::gametracer diff --git a/src/solvers/gtracer/gnmgame.cc b/src/solvers/gtracer/gnmgame.cc index d2b5cc39e..7955e2c13 100644 --- a/src/solvers/gtracer/gnmgame.cc +++ b/src/solvers/gtracer/gnmgame.cc @@ -28,8 +28,7 @@ #include "cmatrix.h" #include "gnmgame.h" -namespace Gambit { -namespace gametracer { +namespace Gambit::gametracer { gnmgame::gnmgame(const std::vector &p_actions) : strategyOffset(p_actions.size() + 1), numPlayers(p_actions.size()), @@ -157,5 +156,4 @@ void gnmgame::normalizeStrategy(cvector &s) const } } -} // namespace gametracer -} // end namespace Gambit +} // end namespace Gambit::gametracer diff --git a/src/solvers/gtracer/gnmgame.h b/src/solvers/gtracer/gnmgame.h index e812f555f..46b913a71 100644 --- a/src/solvers/gtracer/gnmgame.h +++ b/src/solvers/gtracer/gnmgame.h @@ -27,8 +27,7 @@ #include "cmatrix.h" -namespace Gambit { -namespace gametracer { +namespace Gambit::gametracer { class gnmgame { public: @@ -56,7 +55,7 @@ class gnmgame { virtual double getSymMixedPayoff(const cvector &s) { cvector fulls(getNumActions()); - int nact = getNumActions(0); + const int nact = getNumActions(0); for (int i = 0; i < getNumPlayers(); ++i) { for (int j = 0; j < nact; ++j) { fulls[j + firstAction(i)] = s[j]; @@ -118,7 +117,6 @@ class gnmgame { int maxActions; }; -} // namespace gametracer -} // end namespace Gambit +} // end namespace Gambit::gametracer #endif // GAMBIT_GTRACER_GNMGAME_H diff --git a/src/solvers/gtracer/gtracer.cc b/src/solvers/gtracer/gtracer.cc index ae58123dc..974e1f398 100644 --- a/src/solvers/gtracer/gtracer.cc +++ b/src/solvers/gtracer/gtracer.cc @@ -26,22 +26,21 @@ #include "gtracer.h" #include "gambit.h" -namespace Gambit { -namespace gametracer { +namespace Gambit::gametracer { std::shared_ptr BuildGame(const Game &p_game, bool p_scaled) { if (p_game->IsAgg()) { return std::shared_ptr(new aggame(dynamic_cast(*p_game))); } - Rational maxPay = p_game->GetMaxPayoff(); - Rational minPay = p_game->GetMinPayoff(); - double scale = (p_scaled && maxPay > minPay) ? 1.0 / (maxPay - minPay) : 1.0; + const Rational maxPay = p_game->GetMaxPayoff(); + const Rational minPay = p_game->GetMinPayoff(); + const double scale = (p_scaled && maxPay > minPay) ? 1.0 / (maxPay - minPay) : 1.0; auto players = p_game->GetPlayers(); std::vector actions(players.size()); std::transform(players.cbegin(), players.cend(), actions.begin(), - [](const GamePlayer &p) { return p->NumStrategies(); }); + [](const GamePlayer &p) { return p->GetStrategies().size(); }); std::shared_ptr A(new nfgame(actions)); std::vector profile(players.size()); @@ -58,9 +57,14 @@ std::shared_ptr BuildGame(const Game &p_game, bool p_scaled) cvector ToPerturbation(const MixedStrategyProfile &p_pert) { - auto strategies = p_pert.GetGame()->GetStrategies(); - cvector g(strategies.size()); - std::transform(strategies.cbegin(), strategies.cend(), g.begin(), + std::vector all_strategies; + for (const auto &player : p_pert.GetGame()->GetPlayers()) { + for (const auto &strategy : player->GetStrategies()) { + all_strategies.push_back(strategy); + } + } + cvector g(all_strategies.size()); + std::transform(all_strategies.cbegin(), all_strategies.cend(), g.begin(), [p_pert](const GameStrategy &s) { return p_pert[s]; }); for (auto player : p_pert.GetGame()->GetPlayers()) { bool is_tie = false; @@ -89,12 +93,13 @@ MixedStrategyProfile ToProfile(const Game &p_game, const cvector &p_prof { MixedStrategyProfile msp = p_game->NewMixedStrategyProfile(0.0); auto value = p_profile.cbegin(); - for (auto strategy : p_game->GetStrategies()) { - msp[strategy] = *value; - ++value; + for (const auto &player : p_game->GetPlayers()) { + for (const auto &strategy : player->GetStrategies()) { + msp[strategy] = *value; + ++value; + } } return msp; } -} // namespace gametracer -} // namespace Gambit +} // namespace Gambit::gametracer diff --git a/src/solvers/gtracer/gtracer.h b/src/solvers/gtracer/gtracer.h index 814337986..236287eb2 100644 --- a/src/solvers/gtracer/gtracer.h +++ b/src/solvers/gtracer/gtracer.h @@ -31,8 +31,7 @@ #include "aggame.h" #include "gambit.h" -namespace Gambit { -namespace gametracer { +namespace Gambit::gametracer { /// @brief Executes the GNM algorithm on a game /// @param g perturbation ray @@ -93,7 +92,6 @@ cvector ToPerturbation(const MixedStrategyProfile &p_profile); /// @brief Convert a Gametracer vector to a mixed strategy profile on a Gambit game MixedStrategyProfile ToProfile(const Game &p_game, const cvector &p_profile); -} // namespace gametracer -} // end namespace Gambit +} // end namespace Gambit::gametracer #endif // GAMBIT_GTRACER_GTRACER_H diff --git a/src/solvers/gtracer/ipa.cc b/src/solvers/gtracer/ipa.cc index 927f925a7..05ac58708 100644 --- a/src/solvers/gtracer/ipa.cc +++ b/src/solvers/gtracer/ipa.cc @@ -24,8 +24,7 @@ #include "gtracer.h" -namespace Gambit { -namespace gametracer { +namespace Gambit::gametracer { int indexOf(const std::vector &list, int target) { @@ -40,8 +39,9 @@ int indexOf(const std::vector &list, int target) int Pivot(cmatrix &T, int pr, int pc, std::vector &row, std::vector &col, double &D, int numActions, int numPlayers) { - double pivot = T(pr, pc); - int i0, j0, p, sgn = pivot < 0 ? -1 : 1; + const double pivot = T(pr, pc); + int i0, j0, p; + const int sgn = pivot < 0 ? -1 : 1; for (i0 = 0; i0 < numActions + numPlayers; i0++) { if (i0 != pr) { @@ -79,10 +79,10 @@ int Pivot(cmatrix &T, int pr, int pc, std::vector &row, std::vector &c void LemkeHowson(const gnmgame &game, cvector &dest, cmatrix &T, std::vector &Im) { const double BIGFLOAT = 3.0e+28F; - int numActions = game.getNumActions(), numPlayers = game.getNumPlayers(); + const int numActions = game.getNumActions(), numPlayers = game.getNumPlayers(); double D = 1; - int cg = numActions + numPlayers; - int K = cg + 1; + const int cg = numActions + numPlayers; + const int K = cg + 1; int pc, pr, p; double m; std::vector col(numActions + numPlayers + 2); @@ -144,7 +144,7 @@ void LemkeHowson(const gnmgame &game, cvector &dest, cmatrix &T, std::vector &Im) int IPA(const gnmgame &A, const cvector &g, cvector &zh, double alpha, double fuzz, cvector &ans, unsigned int maxiter, bool p_verbose) { - int N = A.getNumPlayers(), - M = A.getNumActions(); // For easy reference - std::vector Im(N); // best actions in perturbed game + const int N = A.getNumPlayers(), + M = A.getNumActions(); // For easy reference + std::vector Im(N); // best actions in perturbed game cmatrix DG(M, M), O(N, N, 0), // matrix of zeroes S(N, M, 0), // @@ -301,7 +301,7 @@ int IPA(const gnmgame &A, const cvector &g, cvector &zh, double alpha, double fu ym1 = z; ym1 -= sh; - double ell = (ym1 * u) / u.norm2(); // dot product + const double ell = (ym1 * u) / u.norm2(); // dot product if (ell <= 0.0 || B) { zh = u; zh *= ell; @@ -371,5 +371,4 @@ int IPA(const gnmgame &A, const cvector &g, cvector &zh, double alpha, double fu return 0; } -} // namespace gametracer -} // end namespace Gambit +} // end namespace Gambit::gametracer diff --git a/src/solvers/gtracer/nfgame.cc b/src/solvers/gtracer/nfgame.cc index 064c11c93..3c79952d7 100644 --- a/src/solvers/gtracer/nfgame.cc +++ b/src/solvers/gtracer/nfgame.cc @@ -28,8 +28,7 @@ #include "nfgame.h" #include "gambit.h" -namespace Gambit { -namespace gametracer { +namespace Gambit::gametracer { nfgame::nfgame(const std::vector &actions) : gnmgame(actions), @@ -181,5 +180,4 @@ double nfgame::localPayoff(const cvector &s, double *m, int n) const } } -} // namespace gametracer -} // end namespace Gambit +} // end namespace Gambit::gametracer diff --git a/src/solvers/gtracer/nfgame.h b/src/solvers/gtracer/nfgame.h index dbc3784bd..ef1612f8b 100644 --- a/src/solvers/gtracer/nfgame.h +++ b/src/solvers/gtracer/nfgame.h @@ -28,8 +28,7 @@ #include "gnmgame.h" #include "cmatrix.h" -namespace Gambit { -namespace gametracer { +namespace Gambit::gametracer { class nfgame : public gnmgame { public: @@ -88,7 +87,6 @@ inline std::ostream &operator<<(std::ostream &s, nfgame &g) return s; } -} // namespace gametracer -} // end namespace Gambit +} // end namespace Gambit::gametracer #endif // GAMBIT_GTRACER_NFGAME_H diff --git a/src/solvers/ipa/ipa.cc b/src/solvers/ipa/ipa.cc index 91fda7de3..227ea8afe 100644 --- a/src/solvers/ipa/ipa.cc +++ b/src/solvers/ipa/ipa.cc @@ -27,15 +27,16 @@ using namespace Gambit::gametracer; -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { List> IPAStrategySolve(const Game &p_game, - StrategyCallbackType p_callback) + StrategyCallbackType p_callback) { MixedStrategyProfile pert = p_game->NewMixedStrategyProfile(0.0); - for (auto strategy : p_game->GetStrategies()) { - pert[strategy] = 0.0; + for (const auto &player : p_game->GetPlayers()) { + for (const auto &strategy : player->GetStrategies()) { + pert[strategy] = 0.0; + } } for (auto player : p_game->GetPlayers()) { pert[player->GetStrategies().front()] = 1.0; @@ -44,15 +45,15 @@ List> IPAStrategySolve(const Game &p_game, } List> IPAStrategySolve(const MixedStrategyProfile &p_pert, - StrategyCallbackType p_callback) + StrategyCallbackType p_callback) { if (!p_pert.GetGame()->IsPerfectRecall()) { throw UndefinedException( "Computing equilibria of games with imperfect recall is not supported."); } - std::shared_ptr A = BuildGame(p_pert.GetGame(), false); - cvector g(ToPerturbation(p_pert)); + const std::shared_ptr A = BuildGame(p_pert.GetGame(), false); + const cvector g(ToPerturbation(p_pert)); cvector ans(A->getNumActions()); cvector zh(A->getNumActions(), 1.0); while (true) { @@ -69,5 +70,4 @@ List> IPAStrategySolve(const MixedStrategyProfile> -IPAStrategySolve(const Game &p_game, StrategyCallbackType p_callback = NullStrategyCallback); +IPAStrategySolve(const Game &p_game, + StrategyCallbackType p_callback = NullStrategyCallback); List> IPAStrategySolve(const MixedStrategyProfile &p_pert, - StrategyCallbackType p_callback = NullStrategyCallback); + StrategyCallbackType p_callback = NullStrategyCallback); -} // namespace Nash -} // namespace Gambit +} // namespace Gambit::Nash #endif // GAMBIT_NASH_IPA_H diff --git a/src/solvers/lcp/efglcp.cc b/src/solvers/lcp/efglcp.cc index c6b1dbd18..6c396cbd6 100644 --- a/src/solvers/lcp/efglcp.cc +++ b/src/solvers/lcp/efglcp.cc @@ -21,13 +21,35 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include #include "gambit.h" #include "solvers/linalg/lemketab.h" #include "solvers/lcp/lcp.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { + +template class NashLcpBehaviorSolver { +public: + NashLcpBehaviorSolver(int p_stopAfter, int p_maxDepth, + BehaviorCallbackType p_onEquilibrium = NullBehaviorCallback) + : m_onEquilibrium(p_onEquilibrium), m_stopAfter(p_stopAfter), m_maxDepth(p_maxDepth) + { + } + ~NashLcpBehaviorSolver() = default; + + List> Solve(const Game &) const; + +private: + BehaviorCallbackType m_onEquilibrium; + int m_stopAfter, m_maxDepth; + + class Solution; + + void FillTableau(Matrix &, const GameNode &, T, int, int, Solution &) const; + void AllLemke(const Game &, int dup, linalg::LemkeTableau &B, int depth, Matrix &, + Solution &) const; + void GetProfile(const linalg::LemkeTableau &tab, MixedBehaviorProfile &, const Vector &, + const GameNode &n, int, int, Solution &) const; +}; template class NashLcpBehaviorSolver::Solution { public: @@ -48,14 +70,15 @@ template class NashLcpBehaviorSolver::Solution { template NashLcpBehaviorSolver::Solution::Solution(const Game &p_game) : ns1(p_game->GetPlayer(1)->NumSequences()), ns2(p_game->GetPlayer(2)->NumSequences()), - ni1(p_game->GetPlayer(1)->NumInfosets() + 1), ni2(p_game->GetPlayer(2)->NumInfosets() + 1), + ni1(p_game->GetPlayer(1)->GetInfosets().size() + 1), + ni2(p_game->GetPlayer(2)->GetInfosets().size() + 1), maxpay(p_game->GetMaxPayoff() + Rational(1)) { for (const auto &player : p_game->GetPlayers()) { int offset = 1; for (const auto &infoset : player->GetInfosets()) { infosetOffset[infoset] = offset; - offset += infoset->NumActions(); + offset += infoset->GetActions().size(); } } } @@ -97,10 +120,10 @@ List> NashLcpBehaviorSolver::Solve(const Game &p_game "Computing equilibria of games with imperfect recall is not supported."); } - linalg::BFS cbfs; + const linalg::BFS cbfs; Solution solution(p_game); - int ntot = solution.ns1 + solution.ns2 + solution.ni1 + solution.ni2; + const int ntot = solution.ns1 + solution.ns2 + solution.ni1 + solution.ni2; Matrix A(1, ntot, 0, ntot); A = static_cast(0); FillTableau(A, p_game->GetRoot(), static_cast(1), 1, 1, solution); @@ -139,7 +162,7 @@ List> NashLcpBehaviorSolver::Solve(const Game &p_game GetProfile(tab, profile, sol, p_game->GetRoot(), 1, 1, solution); profile.UndefinedToCentroid(); solution.m_equilibria.push_back(profile); - this->m_onEquilibrium->Render(profile); + this->m_onEquilibrium(profile, "NE"); } } catch (std::runtime_error &e) { @@ -194,7 +217,7 @@ void NashLcpBehaviorSolver::AllLemke(const Game &p_game, int j, linalg::Lemke GetProfile(BCopy, profile, sol, p_game->GetRoot(), 1, 1, p_solution); profile.UndefinedToCentroid(); if (newsol) { - this->m_onEquilibrium->Render(profile); + m_onEquilibrium(profile, "NE"); p_solution.m_equilibria.push_back(profile); if (m_stopAfter > 0 && p_solution.EquilibriumCount() >= m_stopAfter) { throw EquilibriumLimitReached(); @@ -217,21 +240,21 @@ template void NashLcpBehaviorSolver::FillTableau(Matrix &A, const GameNode &n, T prob, int s1, int s2, Solution &p_solution) const { - int ns1 = p_solution.ns1; - int ns2 = p_solution.ns2; - int ni1 = p_solution.ni1; + const int ns1 = p_solution.ns1; + const int ns2 = p_solution.ns2; + const int ni1 = p_solution.ni1; - GameOutcome outcome = n->GetOutcome(); + const GameOutcome outcome = n->GetOutcome(); if (outcome) { - A(s1, ns1 + s2) += - Rational(prob) * (static_cast(outcome->GetPayoff(1)) - p_solution.maxpay); - A(ns1 + s2, s1) += - Rational(prob) * (static_cast(outcome->GetPayoff(2)) - p_solution.maxpay); + A(s1, ns1 + s2) += Rational(prob) * (outcome->GetPayoff(n->GetGame()->GetPlayer(1)) - + p_solution.maxpay); + A(ns1 + s2, s1) += Rational(prob) * (outcome->GetPayoff(n->GetGame()->GetPlayer(2)) - + p_solution.maxpay); } if (n->IsTerminal()) { return; } - GameInfoset infoset = n->GetInfoset(); + const GameInfoset infoset = n->GetInfoset(); if (n->GetPlayer()->IsChance()) { for (const auto &action : infoset->GetActions()) { FillTableau(A, n->GetChild(action), @@ -240,7 +263,7 @@ void NashLcpBehaviorSolver::FillTableau(Matrix &A, const GameNode &n, T pr } } else if (n->GetPlayer()->GetNumber() == 1) { - int infoset_idx = ns1 + ns2 + infoset->GetNumber() + 1; + const int infoset_idx = ns1 + ns2 + infoset->GetNumber() + 1; A(s1, infoset_idx) = static_cast(-1); A(infoset_idx, s1) = static_cast(1); int snew = p_solution.infosetOffset.at(infoset); @@ -252,7 +275,7 @@ void NashLcpBehaviorSolver::FillTableau(Matrix &A, const GameNode &n, T pr } } else { - int infoset_idx = ns1 + ns2 + ni1 + n->GetInfoset()->GetNumber() + 1; + const int infoset_idx = ns1 + ns2 + ni1 + n->GetInfoset()->GetNumber() + 1; A(ns1 + s2, infoset_idx) = static_cast(-1); A(infoset_idx, ns1 + s2) = static_cast(1); int snew = p_solution.infosetOffset.at(n->GetInfoset()); @@ -271,7 +294,7 @@ void NashLcpBehaviorSolver::GetProfile(const linalg::LemkeTableau &tab, const GameNode &n, int s1, int s2, Solution &p_solution) const { - int ns1 = p_solution.ns1; + const int ns1 = p_solution.ns1; if (n->IsTerminal()) { return; @@ -287,9 +310,9 @@ void NashLcpBehaviorSolver::GetProfile(const linalg::LemkeTableau &tab, snew++; v[action] = static_cast(0); if (tab.Member(s1)) { - int ind = tab.Find(s1); + const int ind = tab.Find(s1); if (sol[ind] > p_solution.eps && tab.Member(snew)) { - int ind2 = tab.Find(snew); + const int ind2 = tab.Find(snew); if (sol[ind2] > p_solution.eps) { v[action] = sol[ind2] / sol[ind]; } @@ -304,9 +327,9 @@ void NashLcpBehaviorSolver::GetProfile(const linalg::LemkeTableau &tab, snew++; v[action] = static_cast(0); if (tab.Member(ns1 + s2)) { - int ind = tab.Find(ns1 + s2); + const int ind = tab.Find(ns1 + s2); if (sol[ind] > p_solution.eps && tab.Member(ns1 + snew)) { - int ind2 = tab.Find(ns1 + snew); + const int ind2 = tab.Find(ns1 + snew); if (sol[ind2] > p_solution.eps) { v[action] = sol[ind2] / sol[ind]; } @@ -317,8 +340,16 @@ void NashLcpBehaviorSolver::GetProfile(const linalg::LemkeTableau &tab, } } -template class NashLcpBehaviorSolver; -template class NashLcpBehaviorSolver; +template +List> LcpBehaviorSolve(const Game &p_game, int p_stopAfter, int p_maxDepth, + BehaviorCallbackType p_onEquilibrium) +{ + return NashLcpBehaviorSolver(p_stopAfter, p_maxDepth, p_onEquilibrium).Solve(p_game); +} + +template List> LcpBehaviorSolve(const Game &, int, int, + BehaviorCallbackType); +template List> LcpBehaviorSolve(const Game &, int, int, + BehaviorCallbackType); -} // namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash diff --git a/src/solvers/lcp/lcp.h b/src/solvers/lcp/lcp.h index 4b556911f..c84352cd2 100644 --- a/src/solvers/lcp/lcp.h +++ b/src/solvers/lcp/lcp.h @@ -25,83 +25,18 @@ #include "games/nash.h" -namespace Gambit { +namespace Gambit::Nash { -namespace linalg { -template class LHTableau; -template class LemkeTableau; -} // namespace linalg +template +List> +LcpStrategySolve(const Game &p_game, int p_stopAfter, int p_maxDepth, + StrategyCallbackType p_onEquilibrium = NullStrategyCallback); -namespace Nash { +template +List> +LcpBehaviorSolve(const Game &p_game, int p_stopAfter, int p_maxDepth, + BehaviorCallbackType p_onEquilibrium = NullBehaviorCallback); -template class NashLcpStrategySolver : public StrategySolver { -public: - NashLcpStrategySolver(int p_stopAfter, int p_maxDepth, - std::shared_ptr> p_onEquilibrium = nullptr) - : StrategySolver(p_onEquilibrium), m_stopAfter(p_stopAfter), m_maxDepth(p_maxDepth) - { - } - ~NashLcpStrategySolver() override = default; - - List> Solve(const Game &) const override; - -private: - int m_stopAfter, m_maxDepth; - - class Solution; - - bool OnBFS(const Game &, linalg::LHTableau &, Solution &) const; - void AllLemke(const Game &, int j, linalg::LHTableau &, Solution &, int) const; -}; - -inline List> LcpStrategySolveDouble(const Game &p_game, - int p_stopAfter, int p_maxDepth) -{ - return NashLcpStrategySolver(p_stopAfter, p_maxDepth).Solve(p_game); -} - -inline List> -LcpStrategySolveRational(const Game &p_game, int p_stopAfter, int p_maxDepth) -{ - return NashLcpStrategySolver(p_stopAfter, p_maxDepth).Solve(p_game); -} - -template class NashLcpBehaviorSolver : public BehavSolver { -public: - NashLcpBehaviorSolver(int p_stopAfter, int p_maxDepth, - std::shared_ptr> p_onEquilibrium = nullptr) - : BehavSolver(p_onEquilibrium), m_stopAfter(p_stopAfter), m_maxDepth(p_maxDepth) - { - } - ~NashLcpBehaviorSolver() override = default; - - List> Solve(const Game &) const override; - -private: - int m_stopAfter, m_maxDepth; - - class Solution; - - void FillTableau(Matrix &, const GameNode &, T, int, int, Solution &) const; - void AllLemke(const Game &, int dup, linalg::LemkeTableau &B, int depth, Matrix &, - Solution &) const; - void GetProfile(const linalg::LemkeTableau &tab, MixedBehaviorProfile &, const Vector &, - const GameNode &n, int, int, Solution &) const; -}; - -inline List> LcpBehaviorSolveDouble(const Game &p_game, - int p_stopAfter, int p_maxDepth) -{ - return NashLcpBehaviorSolver(p_stopAfter, p_maxDepth).Solve(p_game); -} - -inline List> -LcpBehaviorSolveRational(const Game &p_game, int p_stopAfter, int p_maxDepth) -{ - return NashLcpBehaviorSolver(p_stopAfter, p_maxDepth).Solve(p_game); -} - -} // end namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash #endif // GAMBIT_NASH_LCP_H diff --git a/src/solvers/lcp/nfglcp.cc b/src/solvers/lcp/nfglcp.cc index 69e587659..6271942c9 100644 --- a/src/solvers/lcp/nfglcp.cc +++ b/src/solvers/lcp/nfglcp.cc @@ -20,25 +20,22 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include - #include "gambit.h" #include "solvers/linalg/lhtab.h" #include "solvers/lcp/lcp.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { // Anonymous namespace to encapsulate local utility functions namespace { template Matrix Make_A1(const Game &p_game) { - int n1 = p_game->GetPlayer(1)->GetStrategies().size(); - int n2 = p_game->GetPlayer(2)->GetStrategies().size(); + const int n1 = p_game->GetPlayer(1)->GetStrategies().size(); + const int n2 = p_game->GetPlayer(2)->GetStrategies().size(); Matrix A1(1, n1, n1 + 1, n1 + n2); - PureStrategyProfile profile = p_game->NewPureStrategyProfile(); + const PureStrategyProfile profile = p_game->NewPureStrategyProfile(); Rational min = p_game->GetMinPayoff(); if (min > Rational(0)) { @@ -51,13 +48,13 @@ template Matrix Make_A1(const Game &p_game) max = Rational(0); } - Rational fac(1, max - min); + const Rational fac(1, max - min); for (int i = 1; i <= n1; i++) { - profile->SetStrategy(p_game->GetPlayer(1)->GetStrategies()[i]); + profile->SetStrategy(p_game->GetPlayer(1)->GetStrategy(i)); for (int j = 1; j <= n2; j++) { - profile->SetStrategy(p_game->GetPlayer(2)->GetStrategies()[j]); - A1(i, n1 + j) = fac * (profile->GetPayoff(1) - min); + profile->SetStrategy(p_game->GetPlayer(2)->GetStrategy(j)); + A1(i, n1 + j) = fac * (profile->GetPayoff(p_game->GetPlayer(1)) - min); } } return A1; @@ -65,11 +62,11 @@ template Matrix Make_A1(const Game &p_game) template Matrix Make_A2(const Game &p_game) { - int n1 = p_game->GetPlayer(1)->GetStrategies().size(); - int n2 = p_game->GetPlayer(2)->GetStrategies().size(); + const int n1 = p_game->GetPlayer(1)->GetStrategies().size(); + const int n2 = p_game->GetPlayer(2)->GetStrategies().size(); Matrix A2(n1 + 1, n1 + n2, 1, n1); - PureStrategyProfile profile = p_game->NewPureStrategyProfile(); + const PureStrategyProfile profile = p_game->NewPureStrategyProfile(); Rational min = p_game->GetMinPayoff(); if (min > Rational(0)) { @@ -82,13 +79,13 @@ template Matrix Make_A2(const Game &p_game) max = Rational(0); } - Rational fac(1, max - min); + const Rational fac(1, max - min); for (int i = 1; i <= n1; i++) { - profile->SetStrategy(p_game->GetPlayer(1)->GetStrategies()[i]); + profile->SetStrategy(p_game->GetPlayer(1)->GetStrategy(i)); for (int j = 1; j <= n2; j++) { - profile->SetStrategy(p_game->GetPlayer(2)->GetStrategies()[j]); - A2(n1 + j, i) = fac * (profile->GetPayoff(2) - min); + profile->SetStrategy(p_game->GetPlayer(2)->GetStrategy(j)); + A2(n1 + j, i) = fac * (profile->GetPayoff(p_game->GetPlayer(2)) - min); } } return A2; @@ -112,6 +109,27 @@ template Vector Make_b2(const Game &p_game) } // end anonymous namespace +template class NashLcpStrategySolver { +public: + NashLcpStrategySolver(int p_stopAfter, int p_maxDepth, + StrategyCallbackType p_onEquilibrium = NullStrategyCallback) + : m_onEquilibrium(p_onEquilibrium), m_stopAfter(p_stopAfter), m_maxDepth(p_maxDepth) + { + } + ~NashLcpStrategySolver() = default; + + List> Solve(const Game &) const; + +private: + StrategyCallbackType m_onEquilibrium; + int m_stopAfter, m_maxDepth; + + class Solution; + + bool OnBFS(const Game &, linalg::LHTableau &, Solution &) const; + void AllLemke(const Game &, int j, linalg::LHTableau &, Solution &, int) const; +}; + template class NashLcpStrategySolver::Solution { public: List> m_bfsList; @@ -134,15 +152,15 @@ template bool NashLcpStrategySolver::OnBFS(const Game &p_game, linalg::LHTableau &p_tableau, Solution &p_solution) const { - Gambit::linalg::BFS cbfs(p_tableau.GetBFS()); + const Gambit::linalg::BFS cbfs(p_tableau.GetBFS()); if (p_solution.Contains(cbfs)) { return false; } p_solution.push_back(cbfs); MixedStrategyProfile profile(p_game->NewMixedStrategyProfile(static_cast(0.0))); - int n1 = p_game->GetPlayer(1)->GetStrategies().size(); - int n2 = p_game->GetPlayer(2)->GetStrategies().size(); + const int n1 = p_game->GetPlayer(1)->GetStrategies().size(); + const int n2 = p_game->GetPlayer(2)->GetStrategies().size(); T sum = (T)0; for (int j = 1; j <= n1; j++) { @@ -156,7 +174,7 @@ bool NashLcpStrategySolver::OnBFS(const Game &p_game, linalg::LHTableau &p } for (int j = 1; j <= n1; j++) { - GameStrategy strategy = p_game->GetPlayer(1)->GetStrategies()[j]; + const GameStrategy strategy = p_game->GetPlayer(1)->GetStrategy(j); if (cbfs.count(j)) { profile[strategy] = cbfs[j] / sum; } @@ -173,7 +191,7 @@ bool NashLcpStrategySolver::OnBFS(const Game &p_game, linalg::LHTableau &p } for (int j = 1; j <= n2; j++) { - GameStrategy strategy = p_game->GetPlayer(2)->GetStrategies()[j]; + const GameStrategy strategy = p_game->GetPlayer(2)->GetStrategy(j); if (cbfs.count(n1 + j)) { profile[strategy] = cbfs[n1 + j] / sum; } @@ -182,7 +200,7 @@ bool NashLcpStrategySolver::OnBFS(const Game &p_game, linalg::LHTableau &p } } - this->m_onEquilibrium->Render(profile); + m_onEquilibrium(profile, "NE"); p_solution.m_equilibria.push_back(profile); if (m_stopAfter > 0 && p_solution.EquilibriumCount() >= m_stopAfter) { @@ -235,10 +253,10 @@ List> NashLcpStrategySolver::Solve(const Game &p_game Solution solution; try { - Matrix A1 = Make_A1(p_game); - Vector b1 = Make_b1(p_game); - Matrix A2 = Make_A2(p_game); - Vector b2 = Make_b2(p_game); + const Matrix A1 = Make_A1(p_game); + const Vector b1 = Make_b1(p_game); + const Matrix A2 = Make_A2(p_game); + const Vector b2 = Make_b2(p_game); linalg::LHTableau B(A1, A2, b1, b2); if (m_stopAfter != 1) { @@ -259,8 +277,16 @@ List> NashLcpStrategySolver::Solve(const Game &p_game return solution.m_equilibria; } -template class NashLcpStrategySolver; -template class NashLcpStrategySolver; +template +List> LcpStrategySolve(const Game &p_game, int p_stopAfter, int p_maxDepth, + StrategyCallbackType p_onEquilibrium) +{ + return NashLcpStrategySolver(p_stopAfter, p_maxDepth, p_onEquilibrium).Solve(p_game); +} + +template List> LcpStrategySolve(const Game &, int, int, + StrategyCallbackType); +template List> LcpStrategySolve(const Game &, int, int, + StrategyCallbackType); -} // namespace Nash -} // end namespace Gambit +} // end namespace Gambit::Nash diff --git a/src/solvers/liap/efgliap.cc b/src/solvers/liap/efgliap.cc index b085bbcae..b65dd4940 100644 --- a/src/solvers/liap/efgliap.cc +++ b/src/solvers/liap/efgliap.cc @@ -26,8 +26,7 @@ #include "core/function.h" #include "liap.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { //------------------------------------------------------------------------ // class AgentLyapunovFunction @@ -48,7 +47,7 @@ class AgentLyapunovFunction : public FunctionOnSimplices { for (const auto &player : m_game->GetPlayers()) { for (const auto &infoset : player->GetInfosets()) { - m_shape.push_back(infoset->NumActions()); + m_shape.push_back(infoset->GetActions().size()); } } } @@ -115,7 +114,7 @@ bool AgentLyapunovFunction::Gradient(const Vector &x, Vector &gr { const double DELTA = .00001; m_profile = x; - for (int i = 1; i <= x.size(); i++) { + for (size_t i = 1; i <= x.size(); i++) { m_profile[i] += DELTA; double value = PenalizedLiapValue(m_profile); m_profile[i] -= 2.0 * DELTA; @@ -146,7 +145,7 @@ MixedBehaviorProfile EnforceNonnegativity(const MixedBehaviorProfile> LiapBehaviorSolve(const MixedBehaviorProfile &p_start, double p_maxregret, int p_maxitsN, - BehaviorCallbackType p_callback) + BehaviorCallbackType p_callback) { if (!p_start.GetGame()->IsPerfectRecall()) { throw UndefinedException( @@ -158,8 +157,8 @@ List> LiapBehaviorSolve(const MixedBehaviorProfile< MixedBehaviorProfile p(p_start); p_callback(p, "start"); - AgentLyapunovFunction F(p); - Matrix xi(p.BehaviorProfileLength(), p.BehaviorProfileLength()); + const AgentLyapunovFunction F(p); + const Matrix xi(p.BehaviorProfileLength(), p.BehaviorProfileLength()); ConjugatePRMinimizer minimizer(p.BehaviorProfileLength()); Vector gradient(p.BehaviorProfileLength()), dx(p.BehaviorProfileLength()); double fval; @@ -187,5 +186,4 @@ List> LiapBehaviorSolve(const MixedBehaviorProfile< return solutions; } -} // namespace Nash -} // namespace Gambit +} // namespace Gambit::Nash diff --git a/src/solvers/liap/liap.h b/src/solvers/liap/liap.h index 28d716ba2..2deb802c4 100644 --- a/src/solvers/liap/liap.h +++ b/src/solvers/liap/liap.h @@ -25,18 +25,16 @@ #include "games/nash.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { List> LiapBehaviorSolve(const MixedBehaviorProfile &p_start, double p_maxregret, int p_maxitsN, - BehaviorCallbackType p_callback = NullBehaviorCallback); + BehaviorCallbackType p_callback = NullBehaviorCallback); List> LiapStrategySolve(const MixedStrategyProfile &p_start, double p_maxregret, int p_maxitsN, - StrategyCallbackType p_callback = NullStrategyCallback); + StrategyCallbackType p_callback = NullStrategyCallback); -} // namespace Nash -} // namespace Gambit +} // namespace Gambit::Nash #endif // GAMBIT_NASH_LIAP_H diff --git a/src/solvers/liap/nfgliap.cc b/src/solvers/liap/nfgliap.cc index 3160ee8a3..14bcb625e 100644 --- a/src/solvers/liap/nfgliap.cc +++ b/src/solvers/liap/nfgliap.cc @@ -26,8 +26,7 @@ #include "core/function.h" #include "liap.h" -namespace Gambit { -namespace Nash { +namespace Gambit::Nash { //------------------------------------------------------------------------ // class StrategicLyapunovFunction @@ -101,7 +100,8 @@ double StrategicLyapunovFunction::LiapDerivValue(const MixedStrategyProfileGetPlayers()) { for (auto strategy : player->GetStrategies()) { - double loss = sqr(m_scale) * (p_profile.GetPayoff(strategy) - p_profile.GetPayoff(player)); + const double loss = + sqr(m_scale) * (p_profile.GetPayoff(strategy) - p_profile.GetPayoff(player)); if (loss <= 0.0) { continue; } @@ -149,7 +149,7 @@ MixedStrategyProfile EnforceNonnegativity(const MixedStrategyProfile> LiapStrategySolve(const MixedStrategyProfile &p_start, double p_maxregret, int p_maxitsN, - StrategyCallbackType p_callback) + StrategyCallbackType p_callback) { if (!p_start.GetGame()->IsPerfectRecall()) { throw UndefinedException( @@ -161,7 +161,7 @@ List> LiapStrategySolve(const MixedStrategyProfile< MixedStrategyProfile p(p_start); p_callback(p, "start"); - StrategicLyapunovFunction F(p); + const StrategicLyapunovFunction F(p); ConjugatePRMinimizer minimizer(p.MixedProfileLength()); Vector gradient(p.MixedProfileLength()), dx(p.MixedProfileLength()); double fval; @@ -190,5 +190,4 @@ List> LiapStrategySolve(const MixedStrategyProfile< return solutions; } -} // namespace Nash -} // namespace Gambit +} // namespace Gambit::Nash diff --git a/src/solvers/linalg/basis.cc b/src/solvers/linalg/basis.cc deleted file mode 100644 index 09404fafb..000000000 --- a/src/solvers/linalg/basis.cc +++ /dev/null @@ -1,204 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/liblinear/basis.cc -// Implementation of Basis class -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "basis.h" - -using namespace Gambit; -using namespace Gambit::linalg; - -// --------------------------------------------------------------------------- -// Class Basis -// --------------------------------------------------------------------------- - -// ----------------------- -// C-tor, D-tor, Operators -// ----------------------- - -Basis::Basis(int first, int last, int firstlabel, int lastlabel) - : basis(first, last), cols(firstlabel, lastlabel), slacks(first, last), - colBlocked(firstlabel, lastlabel), rowBlocked(first, last) -{ - int i; - for (i = cols.first_index(); i <= cols.last_index(); i++) { - cols[i] = 0; - colBlocked[i] = false; - } - - for (i = basis.first_index(); i <= basis.last_index(); i++) { - basis[i] = -i; - slacks[i] = i; - rowBlocked[i] = false; - } - IsBasisIdent = true; -} - -Basis::Basis(const Basis &bas) - - = default; - -Basis::~Basis() = default; - -Basis &Basis::operator=(const Basis &orig) -{ - if (this != &orig) { - basis = orig.basis; - cols = orig.cols; - slacks = orig.slacks; - rowBlocked = orig.rowBlocked; - colBlocked = orig.colBlocked; - IsBasisIdent = orig.IsBasisIdent; - } - return *this; -} - -// ------------------------- -// Public Members -// ------------------------- - -int Basis::First() const { return basis.first_index(); } - -int Basis::Last() const { return basis.last_index(); } - -int Basis::MinCol() const { return cols.first_index(); } - -int Basis::MaxCol() const { return cols.last_index(); } - -bool Basis::IsRegColumn(int col) const -{ - return col >= cols.first_index() && col <= cols.last_index(); -} - -bool Basis::IsSlackColumn(int col) const -{ - return -col >= basis.first_index() && -col <= basis.last_index(); -} - -int Basis::Pivot(int outindex, int col) -{ - int outlabel = basis[outindex]; - - if (IsSlackColumn(col)) { - slacks[-col] = outindex; - } - else if (IsRegColumn(col)) { - cols[col] = outindex; - } - else { - throw Gambit::IndexException(); // not a valid column to pivot in. - } - - if (IsSlackColumn(outlabel)) { - slacks[-outlabel] = 0; - } - else if (IsRegColumn(outlabel)) { - cols[outlabel] = 0; - } - else { - // Note: here, should back out outindex. - throw Gambit::IndexException(); // not a valid column to pivot out. - } - - basis[outindex] = col; - CheckBasis(); - - return outlabel; -} - -bool Basis::Member(int col) const -{ - int ret; - - if (IsSlackColumn(col)) { - ret = slacks[-col]; - } - else if (IsRegColumn(col)) { - ret = cols[col]; - } - else { - ret = 0; - } - - return (ret != 0); -} - -int Basis::Find(int col) const -{ - int ret; - - if (IsSlackColumn(col)) { - ret = slacks[-col]; - } - else if (IsRegColumn(col)) { - ret = cols[col]; - } - else { - throw Gambit::IndexException(); - } - - return ret; -} - -int Basis::Label(int index) const { return basis[index]; } - -void Basis::Mark(int col) -{ - if (IsSlackColumn(col)) { - rowBlocked[-col] = true; - } - else if (IsRegColumn(col)) { - colBlocked[col] = true; - } -} - -void Basis::UnMark(int col) -{ - if (IsSlackColumn(col)) { - rowBlocked[-col] = false; - } - else if (IsRegColumn(col)) { - colBlocked[col] = false; - } -} - -bool Basis::IsBlocked(int col) const -{ - if (IsSlackColumn(col)) { - return rowBlocked[-col]; - } - else if (IsRegColumn(col)) { - return colBlocked[col]; - } - return false; -} - -void Basis::CheckBasis() -{ - bool check = true; - - for (int i = basis.first_index(); i <= basis.last_index() && check; i++) { - if (basis[i] != -i) { - check = false; - } - } - - IsBasisIdent = check; -} diff --git a/src/solvers/linalg/basis.h b/src/solvers/linalg/basis.h deleted file mode 100644 index a4c27d5ac..000000000 --- a/src/solvers/linalg/basis.h +++ /dev/null @@ -1,98 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/liblinear/basis.h -// Declaration of basis class for tableaus -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#ifndef BASIS_H -#define BASIS_H - -#include "gambit.h" - -namespace Gambit { - -namespace linalg { - -//--------------------------------------------------------------------------- -// Class Basis -//--------------------------------------------------------------------------- - -class Basis { - -private: - Array basis; // current members of basis (neg for slacks) - Array cols; // location of col in basis (0 if not in basis) - Array slacks; // location of slacks in basis - Array colBlocked; - Array rowBlocked; - bool IsBasisIdent; - -public: - //------------------------------------------- - // Constructors, Destructor, Operators - //------------------------------------------- - - Basis(int first, int last, int firstlabel, int lastlabel); - Basis(const Basis &); - virtual ~Basis(); - - Basis &operator=(const Basis &); - - //------------------------------ - // Public Members - //------------------------------ - - int First() const; // First basis index - int Last() const; // Last basis index - int MinCol() const; // First Column label - int MaxCol() const; // Last Column label - - bool IsRegColumn(int col) const; - bool IsSlackColumn(int col) const; - - // remove outindex, insert label, return outlabel - int Pivot(int outindex, int col); - - // return true iff label is a Basis member - bool Member(int label) const; - - // finds Basis index corresponding to label number, - int Find(int label) const; - - // finds label of variable corresponding to Basis index - int Label(int index) const; - - // marks/unmarks label to block it from entering basis - void Mark(int label); - void UnMark(int label); - - // returns true if label is blocked from entering basis - bool IsBlocked(int label) const; - - // Check if Basis is Ident - virtual void CheckBasis(); - // returns whether the basis is the identity matrix - bool IsIdent() const { return IsBasisIdent; } -}; - -} // namespace linalg - -} // end namespace Gambit - -#endif // BASIS_H diff --git a/src/solvers/linalg/bfs.h b/src/solvers/linalg/bfs.h deleted file mode 100644 index 109479df8..000000000 --- a/src/solvers/linalg/bfs.h +++ /dev/null @@ -1,83 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/liblinear/bfs.h -// Interface to basic feasible solution class -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#ifndef BFS_H -#define BFS_H - -#include "gambit.h" -#include - -namespace Gambit { - -namespace linalg { - -template class BFS { -private: - std::map m_map; - T m_default; - -public: - // Lifecycle - BFS() : m_default(0) {} - ~BFS() = default; - - // define two BFS's to be equal if their bases are equal - bool operator==(const BFS &M) const - { - if (m_map.size() != M.m_map.size()) { - return false; - } - - for (auto iter = m_map.begin(); iter != m_map.end(); iter++) { - if (M.m_map.count((*iter).first) == 0) { - return false; - } - } - return true; - } - bool operator!=(const BFS &M) const { return !(*this == M); } - - // Provide map-like operations - int count(int key) const { return (m_map.count(key) > 0); } - - void insert(int key, const T &value) - { - m_map.erase(key); - m_map.insert(std::pair(key, value)); - } - - const T &operator[](int key) const - { - if (m_map.count(key) == 1) { - return const_cast &>(m_map)[key]; - } - else { - return m_default; - } - } -}; - -} // namespace linalg - -} // end namespace Gambit - -#endif // BFS_H diff --git a/src/solvers/linalg/btableau.h b/src/solvers/linalg/btableau.h index f7bd2f8f1..af7d0e384 100644 --- a/src/solvers/linalg/btableau.h +++ b/src/solvers/linalg/btableau.h @@ -23,112 +23,274 @@ #ifndef BTABLEAU_H #define BTABLEAU_H -#include "bfs.h" -#include "basis.h" +#include "gambit.h" -namespace Gambit { +namespace Gambit::linalg { -namespace linalg { +template class BFS { +private: + std::map m_map; + T m_default; + +public: + // Lifecycle + BFS() : m_default(0) {} + ~BFS() = default; + + // define two BFS's to be equal if their bases are equal + bool operator==(const BFS &M) const + { + if (m_map.size() != M.m_map.size()) { + return false; + } + + for (auto iter = m_map.begin(); iter != m_map.end(); iter++) { + if (M.m_map.count((*iter).first) == 0) { + return false; + } + } + return true; + } + bool operator!=(const BFS &M) const { return !(*this == M); } + + // Provide map-like operations + int count(int key) const { return (m_map.count(key) > 0); } + + void insert(int key, const T &value) + { + m_map.erase(key); + m_map.insert(std::pair(key, value)); + } + + const T &operator[](int key) const + { + if (m_map.count(key) == 1) { + return const_cast &>(m_map)[key]; + } + else { + return m_default; + } + } +}; + +class Basis { +private: + Array basis; // current members of basis (neg for slacks) + Array cols; // location of col in basis (0 if not in basis) + Array slacks; // location of slacks in basis + bool is_ident{true}; + +public: + Basis(int first, int last, int firstlabel, int lastlabel) + : basis(first, last), cols(firstlabel, lastlabel), slacks(first, last) + { + std::fill(cols.begin(), cols.end(), 0); + std::iota(slacks.begin(), slacks.end(), slacks.first_index()); + std::generate(basis.begin(), basis.end(), + [n = -basis.first_index()]() mutable { return n--; }); + } + Basis(const Basis &) = default; + ~Basis() = default; + + Basis &operator=(const Basis &) = default; + + int First() const { return basis.first_index(); } + int Last() const { return basis.last_index(); } + int MinCol() const { return cols.first_index(); } + int MaxCol() const { return cols.last_index(); } + + bool IsRegColumn(int col) const { return col >= cols.first_index() && col <= cols.last_index(); } + bool IsSlackColumn(int col) const + { + return -col >= basis.first_index() && -col <= basis.last_index(); + } + + // remove outindex, insert label, return outlabel + int Pivot(int outindex, int col) + { + const int outlabel = basis[outindex]; + + if (IsSlackColumn(col)) { + slacks[-col] = outindex; + } + else if (IsRegColumn(col)) { + cols[col] = outindex; + } + else { + throw IndexException(); // not a valid column to pivot in. + } + + if (IsSlackColumn(outlabel)) { + slacks[-outlabel] = 0; + } + else if (IsRegColumn(outlabel)) { + cols[outlabel] = 0; + } + else { + // Note: here, should back out outindex. + throw IndexException(); // not a valid column to pivot out. + } + + basis[outindex] = col; + CheckBasis(); + + return outlabel; + } + + // return true iff label is a Basis member + bool Member(int label) const + { + return ((IsSlackColumn(label) && slacks[-label] != 0) || + (IsRegColumn(label) && cols[label] != 0)); + } + // finds Basis index corresponding to label number + int Find(int label) const + { + if (IsSlackColumn(label)) { + return slacks[-label]; + } + else if (IsRegColumn(label)) { + return cols[label]; + } + else { + throw Gambit::IndexException(); + } + } + + // finds label of variable corresponding to Basis index + int Label(int index) const { return basis[index]; } + + // Check if Basis is Ident + void CheckBasis() + { + is_ident = true; + for (int i = basis.first_index(); i <= basis.last_index(); i++) { + if (basis[i] != -i) { + is_ident = false; + return; + } + } + } + // returns whether the basis is the identity matrix + bool IsIdent() const { return is_ident; } +}; inline void epsilon(double &v, int i = 8) { v = std::pow(10.0, (double)-i); } inline void epsilon(Rational &v, int /*i*/ = 8) { v = Rational(0); } -template class BaseTableau { +class BadPivot : public Exception { public: - class BadPivot : public Exception { - public: - ~BadPivot() noexcept override = default; - const char *what() const noexcept override { return "Bad Pivot in BaseTableau"; } - }; - - virtual ~BaseTableau() = default; - - bool ColIndex(int) const; - bool RowIndex(int) const; - bool ValidIndex(int) const; - virtual int MinRow() const = 0; - virtual int MaxRow() const = 0; - virtual int MinCol() const = 0; - virtual int MaxCol() const = 0; - - virtual bool Member(int i) const = 0; - // is variable i is a member of basis - virtual int Label(int i) const = 0; - // return variable in i'th position of Tableau - virtual int Find(int i) const = 0; - // return position of variable i - - // pivoting - virtual bool CanPivot(int outgoing, int incoming) const = 0; - virtual void Pivot(int outrow, int col) = 0; - // perform pivot operation -- outgoing is row, incoming is column - void CompPivot(int outlabel, int col); - virtual long NumPivots() const = 0; - - // raw Tableau functions - virtual void Refactor() = 0; + ~BadPivot() noexcept override = default; + const char *what() const noexcept override { return "Bad pivot"; } }; -// --------------------------------------------------------------------------- -// TableauInterface Stuff -// --------------------------------------------------------------------------- - -template class TableauInterface : public BaseTableau { +template class TableauInterface { protected: - const Matrix *A; // should this be private? - const Vector *b; // should this be private? + Matrix A; // should this be private? + Vector b; // should this be private? Basis basis; Vector solution; // current solution vector. should this be private? - long npivots; T eps1, eps2; Array artificial; // artificial variables public: - TableauInterface(const Matrix &A, const Vector &b); - TableauInterface(const Matrix &A, const Array &art, const Vector &b); - TableauInterface(const TableauInterface &); - ~TableauInterface() override = default; + TableauInterface(const Matrix &A, const Vector &b) + : A(A), b(b), basis(A.MinRow(), A.MaxRow(), A.MinCol(), A.MaxCol()), + solution(A.MinRow(), A.MaxRow()), artificial(A.MaxCol() + 1, A.MaxCol()) - TableauInterface &operator=(const TableauInterface &); + { + // These are the values recommended by Murtagh (1981) for 15 digit + // accuracy in LP problems + // Note: for Rational, eps1 and eps2 resolve to 0 + linalg::epsilon(eps1, 5); + linalg::epsilon(eps2); + } + TableauInterface(const Matrix &A, const Array &art, const Vector &b) + : A(A), b(b), basis(A.MinRow(), A.MaxRow(), A.MinCol(), A.MaxCol() + art.size()), + solution(A.MinRow(), A.MaxRow()), artificial(A.MaxCol() + 1, A.MaxCol() + art.size()) + { + linalg::epsilon(eps1, 5); + linalg::epsilon(eps2); + for (size_t i = 0; i < art.size(); i++) { + artificial[A.MaxCol() + 1 + i] = art[art.first_index() + i]; + } + } + TableauInterface(const TableauInterface &) = default; + virtual ~TableauInterface() = default; - // information + TableauInterface &operator=(const TableauInterface &) = default; - int MinRow() const override; - int MaxRow() const override; - int MinCol() const override; - int MaxCol() const override; + // information - Basis &GetBasis(); - const Matrix &Get_A() const; - const Vector &Get_b() const; + int MinRow() const { return A.MinRow(); } + int MaxRow() const { return A.MaxRow(); } + int MinCol() const { return basis.MinCol(); } + int MaxCol() const { return basis.MaxCol(); } - bool Member(int i) const override; - int Label(int i) const override; // return variable in i'th position of Tableau - int Find(int i) const override; // return Tableau position of variable i + bool ColIndex(int x) const { return MinCol() <= x && x <= MaxCol(); } + bool RowIndex(int x) const { return MinRow() <= x && x <= MaxRow(); } + bool ValidIndex(int x) const { return ColIndex(x) || RowIndex(-x); } - long NumPivots() const override; - long &NumPivots(); + Basis &GetBasis() { return basis; } - void Mark(int label); // marks label to block it from entering basis - void UnMark(int label); // unmarks label - bool IsBlocked(int label) const; // returns true if label is blocked + bool Member(int i) const { return basis.Member(i); } + // return variable in i'th position of Tableau + int Label(int i) const { return basis.Label(i); } + // return Tableau position of variable i + int Find(int i) const { return basis.Find(i); } virtual void BasisVector(Vector &x) const = 0; // solve M x = (*b) - void GetColumn(int, Vector &) const; // raw column - void GetBasis(Basis &) const; // return Basis for current Tableau + virtual void GetColumn(int col, Vector &ret) const + { + if (IsArtifColumn(col)) { + ret = (T)0; + ret[artificial[col]] = (T)1; + } + else if (basis.IsRegColumn(col)) { + A.GetColumn(col, ret); + } + else if (basis.IsSlackColumn(col)) { + ret = (T)0; + ret[-col] = (T)1; + } + } - BFS GetBFS1() const; - BFS GetBFS(); // used in lpsolve for some reason + BFS GetBFS1() const + { + Vector sol(basis.First(), basis.Last()); + BasisVector(sol); - bool CanPivot(int outgoing, int incoming) const override = 0; - void Pivot(int outrow, int col) override = 0; // pivot -- outgoing is row, incoming is column - virtual void SolveColumn(int, Vector &) = 0; // column in new basis - virtual void Solve(const Vector &b, Vector &x) = 0; // solve M x = b - virtual void SolveT(const Vector &c, Vector &y) = 0; // solve y M = c + BFS cbfs; + for (int i = -MaxRow(); i <= -MinRow(); i++) { + if (Member(i)) { + cbfs.insert(i, sol[basis.Find(i)]); + } + } + for (int i = MinCol(); i <= MaxCol(); i++) { + if (Member(i)) { + cbfs.insert(i, sol[basis.Find(i)]); + } + } + return cbfs; + } + + BFS GetBFS() const + { + Vector sol(basis.First(), basis.Last()); + BasisVector(sol); - void Refactor() override = 0; - virtual void SetRefactor(int) = 0; + BFS cbfs; + for (int i = MinCol(); i <= MaxCol(); i++) { + if (Member(i)) { + cbfs.insert(i, sol[basis.Find(i)]); + } + } + return cbfs; + } + + virtual void Pivot(int outrow, int col) = 0; // pivot -- outgoing is row, incoming is column + virtual void SolveColumn(int, Vector &) = 0; // column in new basis // miscellaneous functions bool EqZero(const T &x) const { return (LeZero(x) && GeZero(x)); } @@ -137,12 +299,20 @@ template class TableauInterface : public BaseTableau { bool LeZero(const T &x) const { return (x <= eps2); } bool GeZero(const T &x) const { return (x >= -eps2); } - T Epsilon(int i = 2) const; - bool IsArtifColumn(int col) const; -}; + T Epsilon(int i = 2) const + { + if (i != 1 && i != 2) { + throw Gambit::DimensionException(); + } + return (i == 1) ? eps1 : eps2; + } -} // namespace linalg + bool IsArtifColumn(int col) const + { + return (col >= artificial.first_index() && col <= artificial.last_index()); + } +}; -} // end namespace Gambit +} // end namespace Gambit::linalg #endif // BTABLEAU_H diff --git a/src/solvers/linalg/btableau.imp b/src/solvers/linalg/btableau.imp deleted file mode 100644 index 1958690aa..000000000 --- a/src/solvers/linalg/btableau.imp +++ /dev/null @@ -1,211 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/liblinear/btableau.imp -// Implementation of base tableau classes -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -// --------------------------------------------------------------------------- -// BaseTableau method definitions -// --------------------------------------------------------------------------- - -#include "btableau.h" - -namespace Gambit { - -namespace linalg { - -template bool BaseTableau::ColIndex(int x) const -{ - return MinCol() <= x && x <= MaxCol(); -} - -template bool BaseTableau::RowIndex(int x) const -{ - return MinRow() <= x && x <= MaxRow(); -} - -template bool BaseTableau::ValidIndex(int x) const -{ - return (ColIndex(x) || RowIndex(-x)); -} - -template void BaseTableau::CompPivot(int outlabel, int col) -{ - Pivot(Find(outlabel), col); - Pivot(Find(-col), -outlabel); -} - -// --------------------------------------------------------------------------- -// TableauInterface method definitions -// --------------------------------------------------------------------------- - -// Constructors and Destructor - -template -TableauInterface::TableauInterface(const Gambit::Matrix &A, const Gambit::Vector &b) - : A(&A), b(&b), basis(A.MinRow(), A.MaxRow(), A.MinCol(), A.MaxCol()), - solution(A.MinRow(), A.MaxRow()), npivots(0), artificial(A.MaxCol() + 1, A.MaxCol()) - -{ - // These are the values recommended by Murtagh (1981) for 15 digit - // accuracy in LP problems - // Note: for Rational, eps1 and eps2 resolve to 0 - Gambit::linalg::epsilon(eps1, 5); - Gambit::linalg::epsilon(eps2); -} - -template -TableauInterface::TableauInterface(const Gambit::Matrix &A, const Gambit::Array &art, - const Gambit::Vector &b) - : A(&A), b(&b), basis(A.MinRow(), A.MaxRow(), A.MinCol(), A.MaxCol() + art.size()), - solution(A.MinRow(), A.MaxRow()), npivots(0), - artificial(A.MaxCol() + 1, A.MaxCol() + art.size()) -{ - Gambit::linalg::epsilon(eps1, 5); - Gambit::linalg::epsilon(eps2); - for (int i = 0; i < art.size(); i++) { - artificial[A.MaxCol() + 1 + i] = art[art.first_index() + i]; - } -} - -template -TableauInterface::TableauInterface(const TableauInterface &orig) - : A(orig.A), b(orig.b), basis(orig.basis), solution(orig.solution), npivots(orig.npivots), - eps1(orig.eps1), eps2(orig.eps2), artificial(orig.artificial) -{ -} - -template -TableauInterface &TableauInterface::operator=(const TableauInterface &orig) -{ - if (this != &orig) { - A = orig.A; - b = orig.b; - basis = orig.basis; - solution = orig.solution; - npivots = orig.npivots; - artificial = orig.artificial; - } - return *this; -} - -// getting information - -template int TableauInterface::MinRow() const { return A->MinRow(); } - -template int TableauInterface::MaxRow() const { return A->MaxRow(); } - -template int TableauInterface::MinCol() const { return basis.MinCol(); } - -template int TableauInterface::MaxCol() const { return basis.MaxCol(); } - -template Basis &TableauInterface::GetBasis() { return basis; } - -template const Gambit::Matrix &TableauInterface::Get_A() const { return *A; } - -template const Gambit::Vector &TableauInterface::Get_b() const { return *b; } - -template bool TableauInterface::Member(int i) const { return basis.Member(i); } - -template int TableauInterface::Label(int i) const { return basis.Label(i); } - -template int TableauInterface::Find(int i) const { return basis.Find(i); } - -template long TableauInterface::NumPivots() const { return npivots; } - -template long &TableauInterface::NumPivots() { return npivots; } - -template void TableauInterface::Mark(int label) { basis.Mark(label); } - -template void TableauInterface::UnMark(int label) { basis.UnMark(label); } - -template bool TableauInterface::IsBlocked(int label) const -{ - return basis.IsBlocked(label); -} - -template void TableauInterface::GetColumn(int col, Gambit::Vector &ret) const -{ - if (IsArtifColumn(col)) { - ret = (T)0; - ret[artificial[col]] = (T)1; - } - else if (basis.IsRegColumn(col)) { - A->GetColumn(col, ret); - } - else if (basis.IsSlackColumn(col)) { - ret = (T)0; - ret[-col] = (T)1; - } -} - -template void TableauInterface::GetBasis(Basis &out) const { out = basis; } - -template BFS TableauInterface::GetBFS() -{ - Gambit::Vector sol(basis.First(), basis.Last()); - BasisVector(sol); - - BFS cbfs; - for (int i = MinCol(); i <= MaxCol(); i++) { - if (Member(i)) { - cbfs.insert(i, sol[basis.Find(i)]); - } - } - return cbfs; -} - -template BFS TableauInterface::GetBFS1() const -{ - Gambit::Vector sol(basis.First(), basis.Last()); - BasisVector(sol); - - BFS cbfs; - int i; - for (i = -MaxRow(); i <= -MinRow(); i++) { - if (Member(i)) { - cbfs.insert(i, sol[basis.Find(i)]); - } - } - for (i = MinCol(); i <= MaxCol(); i++) { - if (Member(i)) { - cbfs.insert(i, sol[basis.Find(i)]); - } - } - return cbfs; -} - -// miscellaneous functions - -template T TableauInterface::Epsilon(int i) const -{ - if (i != 1 && i != 2) { - throw Gambit::DimensionException(); - } - return (i == 1) ? eps1 : eps2; -} - -template bool TableauInterface::IsArtifColumn(int col) const -{ - return (col >= artificial.first_index() && col <= artificial.last_index()); -} - -} // namespace linalg - -} // end namespace Gambit diff --git a/src/solvers/linalg/lemketab.cc b/src/solvers/linalg/lemketab.cc index d16ff30df..4b14983ab 100644 --- a/src/solvers/linalg/lemketab.cc +++ b/src/solvers/linalg/lemketab.cc @@ -22,13 +22,9 @@ #include "lemketab.imp" -namespace Gambit { - -namespace linalg { +namespace Gambit::linalg { template class LemkeTableau; template class LemkeTableau; -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg diff --git a/src/solvers/linalg/lemketab.h b/src/solvers/linalg/lemketab.h index b89b44d33..dd7f930a1 100644 --- a/src/solvers/linalg/lemketab.h +++ b/src/solvers/linalg/lemketab.h @@ -25,14 +25,10 @@ #include "tableau.h" -namespace Gambit { -namespace linalg { +namespace Gambit::linalg { template class LemkeTableau : public Tableau { -protected: - // T eps2; public: - // LTableau(void); class BadPivot : public Exception { public: ~BadPivot() noexcept override = default; @@ -43,19 +39,15 @@ template class LemkeTableau : public Tableau { ~BadExitIndex() noexcept override = default; const char *what() const noexcept override { return "Bad Exit Index in LTableau"; } }; - LemkeTableau(const Matrix &A, const Vector &b); - explicit LemkeTableau(const Tableau &); + LemkeTableau(const Matrix &A, const Vector &b) : Tableau(A, b) {} ~LemkeTableau() override = default; int SF_PivotIn(int i); int SF_ExitIndex(int i); int SF_LCPPath(int dup); // follow a path of ACBFS's from one CBFS to another - int PivotIn(int i); int ExitIndex(int i); - int LemkePath(int dup); // follow a path of ACBFS's from one CBFS to another }; -} // namespace linalg -} // end namespace Gambit +} // end namespace Gambit::linalg #endif // GAMBIT_LINALG_LEMKETAB_H diff --git a/src/solvers/linalg/lemketab.imp b/src/solvers/linalg/lemketab.imp index 58bc1fb40..99f0c1911 100644 --- a/src/solvers/linalg/lemketab.imp +++ b/src/solvers/linalg/lemketab.imp @@ -22,51 +22,19 @@ #include "lemketab.h" -namespace Gambit { -namespace linalg { +namespace Gambit::linalg { //--------------------------------------------------------------------------- // Lemke Tableau: member functions //--------------------------------------------------------------------------- -template -LemkeTableau::LemkeTableau(const Matrix &A, const Vector &b) : Tableau(A, b) -{ -} - -template LemkeTableau::LemkeTableau(const Tableau &tab) : Tableau(tab) {} - template int LemkeTableau::SF_PivotIn(int inlabel) { - //* gout << "\n inlabel = " << inlabel; - int outindex = SF_ExitIndex(inlabel); - // gout << " outindex = " << outindex; - if (outindex == 0) { - //* gout << "\nPivotIn: outindex=0, inlabel=" << inlabel; - return inlabel; - } - int outlabel = this->Label(outindex); - //* gout << " outlabel = " << outlabel; - //* gout << " outindex = " << outindex; - this->Pivot(outindex, inlabel); - return outlabel; -} - -template int LemkeTableau::PivotIn(int inlabel) -{ - // gout << "\n inlabel = " << inlabel; - int outindex = ExitIndex(inlabel); - // gout << " outindex = " << outindex; + const int outindex = SF_ExitIndex(inlabel); if (outindex == 0) { return inlabel; } - int outlabel = this->Label(outindex); - if (outlabel == 0) { - throw BadPivot(); - } - - // gout << " outlabel = " << outlabel; - // gout << " outindex = " << outindex; + const int outlabel = this->Label(outindex); this->Pivot(outindex, inlabel); return outlabel; } @@ -74,29 +42,21 @@ template int LemkeTableau::PivotIn(int inlabel) template int LemkeTableau::SF_ExitIndex(int inlabel) { Array BestSet; - int i, c; + int c; T ratio, tempmax; Vector incol(this->MinRow(), this->MaxRow()); Vector col(this->MinRow(), this->MaxRow()); this->SolveColumn(inlabel, incol); - //* gout << "\nincol = " << incol; // Find all row indices for which column col has positive entries. - for (i = this->MinRow(); i <= this->MaxRow(); i++) { + for (int i = this->MinRow(); i <= this->MaxRow(); i++) { if (incol[i] > this->eps2) { BestSet.push_back(i); } } if (BestSet.size() == 0) { - //* gout << "\nBestSet.Length() == 0, Find(0): " << Find(0); return 0; } - // Is this really needed? - /* - if(BestSet.Length()==0 - && incol[Find(0)]<=eps2 && incol[Find(0)] >= (-eps2) ) - return Find(0); - */ if (BestSet.size() <= 0) { throw BadExitIndex(); } @@ -107,40 +67,30 @@ template int LemkeTableau::SF_ExitIndex(int inlabel) // a similar ratio, until only one candidate remains. c = this->MinRow() - 1; this->BasisVector(col); - // gout << "\nLength = " << BestSet.Length(); - //* gout << "\n x = " << col << "\n"; while (BestSet.size() > 1) { if (c > this->MaxRow()) { throw BadExitIndex(); } if (c >= this->MinRow()) { this->SolveColumn(-c, col); - // gout << "\n-c = " << -c << " col = " << col; } // Initialize tempmax. tempmax = col[BestSet[1]] / incol[BestSet[1]]; // Find the maximum ratio. - for (i = 2; i <= BestSet.size(); i++) { + for (size_t i = 2; i <= BestSet.size(); i++) { ratio = col[BestSet[i]] / incol[BestSet[i]]; - //* if (ratio > tempmax) tempmax = ratio; if (ratio < tempmax) { tempmax = ratio; } } - // if (tempmax <= (T 2)*eps1) throw BadExitIndex(); // Remove nonmaximizers from the list of candidate columns. - for (i = BestSet.size(); i >= 1; i--) { + for (size_t i = BestSet.size(); i >= 1; i--) { ratio = col[BestSet[i]] / incol[BestSet[i]]; - //* if (ratio < tempmax -eps1) if (ratio > tempmax + this->eps2) { erase_atindex(BestSet, i); } } - // else { - // if(!Member(FindColumn(c))) throw BadExitIndex(); - // if (BestSet.Contains(c_row)) return c_row; - // } c++; } if (BestSet.empty()) { @@ -165,7 +115,6 @@ template int LemkeTableau::ExitIndex(int inlabel) Vector col(this->MinRow(), this->MaxRow()); this->SolveColumn(inlabel, incol); - // gout << "\nincol = " << incol; // Find all row indices for which column col has positive entries. for (i = this->MinRow(); i <= this->MaxRow(); i++) { if (incol[i] > this->eps2) { @@ -174,7 +123,6 @@ template int LemkeTableau::ExitIndex(int inlabel) } // Is this really needed? if (BestSet.size() == 0) { - // gout << "\nBestSet.Length() == 0, Find(0):\n" << Find(0); if (BestSet.size() == 0 && incol[this->Find(0)] <= this->eps2 && incol[this->Find(0)] >= (-this->eps2)) { return this->Find(0); @@ -190,39 +138,30 @@ template int LemkeTableau::ExitIndex(int inlabel) // a similar ratio, until only one candidate remains. c = this->MinRow() - 1; this->BasisVector(col); - // gout << "\nLength = " << BestSet.Length(); - // gout << "\n x = " << col << "\n"; while (BestSet.size() > 1) { - // this is where ITEM 001 is failing if (c > this->MaxRow()) { throw BadExitIndex(); } if (c >= this->MinRow()) { this->SolveColumn(-c, col); - // gout << "\n-c = " << -c << " col = " << col; } // Initialize tempmax. tempmax = col[BestSet[1]] / incol[BestSet[1]]; // Find the maximum ratio. - for (i = 2; i <= BestSet.size(); i++) { - ratio = col[BestSet[i]] / incol[BestSet[i]]; + for (size_t j = 2; j <= BestSet.size(); j++) { + ratio = col[BestSet[j]] / incol[BestSet[j]]; if (ratio > tempmax) { tempmax = ratio; } } - // if(tempmax <= (T 2)*eps1) throw BadExitIndex(); // Remove nonmaximizers from the list of candidate columns. - for (i = BestSet.size(); i >= 1; i--) { - ratio = col[BestSet[i]] / incol[BestSet[i]]; + for (size_t j = BestSet.size(); j >= 1; j--) { + ratio = col[BestSet[j]] / incol[BestSet[j]]; if (ratio < tempmax - this->eps1) { - erase_atindex(BestSet, i); + erase_atindex(BestSet, j); } } - // else { - // if(!Member(FindColumn(c))) throw BadExitIndex(); - // if (BestSet.Contains(c_row)) return c_row; - // } c++; } if (BestSet.empty()) { @@ -239,24 +178,11 @@ template int LemkeTableau::SF_LCPPath(int dup) { int enter, exit; enter = dup; - /* - if(dup) - Pivot(dup,0); - else { - enter = -SF_PivotIn(dup); - if(enter==0) throw BadPivot(); - } - */ // Central loop - pivot until another CBFS is found - long nits = 0; do { // Talk about optimism! This is dumb, but better than nothing (I guess): - nits++; - //* gout << "\nBasis:\n"; - //* Dump(gout); exit = SF_PivotIn(enter); if (exit == enter) { - //* gout << "\nenter, exit: " << enter << " " << exit; return 0; } enter = -exit; @@ -264,34 +190,4 @@ template int LemkeTableau::SF_LCPPath(int dup) return 1; } -template int LemkeTableau::LemkePath(int dup) -{ - // if (!At_CBFS()) return 0; - int enter, exit; - // if(params.plev >=2) { - // (*params.output) << "\nbegin path " << dup << "\n"; - // Dump(*params.output); - // } - // gout << "\nbegin path " << dup << "\n"; - // Dump(gout); - enter = dup; - if (this->Member(dup)) { - enter = -dup; - } - // Central loop - pivot until another CBFS is found - do { - exit = PivotIn(enter); - // if(params.plev >=2) - // Dump(*params.output); - // Dump(gout); - - enter = -exit; - } while ((exit != dup) && (exit != -dup)); - // Quit when at a CBFS. - // if(params.plev >=2 ) (*params.output) << "\nend of path " << dup; - // gout << "\nend of path " << dup; - return 1; -} - -} // namespace linalg -} // end namespace Gambit +} // end namespace Gambit::linalg diff --git a/src/solvers/linalg/lhtab.cc b/src/solvers/linalg/lhtab.cc index 40249ea1f..c7550ae25 100644 --- a/src/solvers/linalg/lhtab.cc +++ b/src/solvers/linalg/lhtab.cc @@ -22,13 +22,9 @@ #include "lhtab.imp" -namespace Gambit { - -namespace linalg { +namespace Gambit::linalg { template class LHTableau; template class LHTableau; -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg diff --git a/src/solvers/linalg/lhtab.h b/src/solvers/linalg/lhtab.h index 8ad205d47..3584482ef 100644 --- a/src/solvers/linalg/lhtab.h +++ b/src/solvers/linalg/lhtab.h @@ -25,49 +25,45 @@ #include "lemketab.h" -namespace Gambit { -namespace linalg { +namespace Gambit::linalg { -template class LHTableau : public BaseTableau { +template class LHTableau { public: /// @name Lifecycle //@{ - LHTableau(const Matrix &A1, const Matrix &A2, const Vector &b1, const Vector &b2); - ~LHTableau() override = default; + LHTableau(const Matrix &A1, const Matrix &A2, const Vector &b1, const Vector &b2) + : T1(A1, b1), T2(A2, b2), tmp1(b1.first_index(), b1.last_index()), + tmp2(b2.first_index(), b2.last_index()), solution(b1.first_index(), b2.last_index()) + { + } + LHTableau(const LHTableau &) = default; + ~LHTableau() = default; - LHTableau &operator=(const LHTableau &); + LHTableau &operator=(const LHTableau &) = delete; //@} /// @name General information //@{ - int MinRow() const override { return T1.MinRow(); } - int MaxRow() const override { return T2.MaxRow(); } - int MinCol() const override { return T2.MinCol(); } - int MaxCol() const override { return T1.MaxCol(); } - T Epsilon() const { return T1.Epsilon(); } - bool Member(int i) const override { return T1.Member(i) || T2.Member(i); } + int MinRow() const { return T1.MinRow(); } + int MaxRow() const { return T2.MaxRow(); } + int MinCol() const { return T2.MinCol(); } + int MaxCol() const { return T1.MaxCol(); } + + bool ColIndex(int x) const { return MinCol() <= x && x <= MaxCol(); } + bool RowIndex(int x) const { return MinRow() <= x && x <= MaxRow(); } + + bool Member(int i) const { return T1.Member(i) || T2.Member(i); } /// Return variable in i'th position of Tableau - int Label(int i) const override; + int Label(int i) const; /// Return Tableau position of variable i - int Find(int i) const override; + int Find(int i) const; //@} /// @name Pivoting operations //@{ - bool CanPivot(int outgoing, int incoming) const override; /// Perform apivot operation -- outgoing is row, incoming is column - void Pivot(int outrow, int inlabel) override; - long NumPivots() const override { return T1.NumPivots() + T2.NumPivots(); } - //@} - - /// @name Raw Tableau functions - //@{ - void Refactor() override - { - T1.Refactor(); - T2.Refactor(); - } + void Pivot(int outrow, int inlabel); //@} /// @name Miscellaneous functions @@ -86,7 +82,6 @@ template class LHTableau : public BaseTableau { Vector solution; }; -} // namespace linalg -} // end namespace Gambit +} // end namespace Gambit::linalg #endif // GAMBIT_LINALG_LHTAB_H diff --git a/src/solvers/linalg/lhtab.imp b/src/solvers/linalg/lhtab.imp index f91701912..67afb8f86 100644 --- a/src/solvers/linalg/lhtab.imp +++ b/src/solvers/linalg/lhtab.imp @@ -23,32 +23,7 @@ #include "lhtab.h" #include "gambit.h" -namespace Gambit { -namespace linalg { - -//--------------------------------------------------------------------------- -// LHTableau: Lifecycle -//--------------------------------------------------------------------------- - -template -LHTableau::LHTableau(const Matrix &A1, const Matrix &A2, const Vector &b1, - const Vector &b2) - : T1(A1, b1), T2(A2, b2), tmp1(b1.first_index(), b1.last_index()), - tmp2(b2.first_index(), b2.last_index()), solution(b1.first_index(), b2.last_index()) -{ -} - -template LHTableau &LHTableau::operator=(const LHTableau &orig) -{ - if (this != &orig) { - T1 = orig.T1; - T2 = orig.T2; - tmp1 = orig.tmp1; - tmp2 = orig.tmp2; - solution = orig.solution; - } - return *this; -} +namespace Gambit::linalg { //--------------------------------------------------------------------------- // LHTableau: General information @@ -80,12 +55,6 @@ template int LHTableau::Find(int i) const // LHTableau: Pivoting operations //--------------------------------------------------------------------------- -template bool LHTableau::CanPivot(int outlabel, int inlabel) const -{ - return ((T1.ValidIndex(outlabel) && T1.CanPivot(outlabel, inlabel)) || - (T2.ValidIndex(outlabel) && T2.CanPivot(outlabel, inlabel))); -} - template void LHTableau::Pivot(int outrow, int inlabel) { if (!this->RowIndex(outrow)) { @@ -113,7 +82,7 @@ template Gambit::linalg::BFS LHTableau::GetBFS() for (int i = tmp2.first_index(); i <= tmp2.last_index(); i++) { solution[i] = tmp2[i]; } - Gambit::linalg::BFS cbfs; + BFS cbfs; for (int i = MinCol(); i <= MaxCol(); i++) { if (Member(i)) { cbfs.insert(i, solution[Find(i)]); @@ -124,8 +93,8 @@ template Gambit::linalg::BFS LHTableau::GetBFS() template int LHTableau::PivotIn(int inlabel) { - int outindex = ExitIndex(inlabel); - int outlabel = Label(outindex); + const int outindex = ExitIndex(inlabel); + const int outlabel = Label(outindex); if (outlabel == 0) { return 0; } @@ -163,5 +132,4 @@ template int LHTableau::LemkePath(int dup) return 1; } -} // namespace linalg -} // end namespace Gambit +} // end namespace Gambit::linalg diff --git a/src/solvers/linalg/lpsolve.cc b/src/solvers/linalg/lpsolve.cc index 3a4516761..6e2d2f111 100644 --- a/src/solvers/linalg/lpsolve.cc +++ b/src/solvers/linalg/lpsolve.cc @@ -23,11 +23,9 @@ #include "lpsolve.imp" #include "core/rational.h" -namespace Gambit { -namespace linalg { +namespace Gambit::linalg { template class LPSolve; template class LPSolve; -} // namespace linalg -} // end namespace Gambit +} // end namespace Gambit::linalg diff --git a/src/solvers/linalg/lpsolve.h b/src/solvers/linalg/lpsolve.h index 028a92556..04902fa8c 100644 --- a/src/solvers/linalg/lpsolve.h +++ b/src/solvers/linalg/lpsolve.h @@ -25,11 +25,8 @@ #include "gambit.h" #include "lptab.h" -#include "bfs.h" -namespace Gambit { - -namespace linalg { +namespace Gambit::linalg { /// /// This class implements a LP solver. Its constructor takes as input a @@ -44,41 +41,35 @@ namespace linalg { /// template class LPSolve { private: - bool well_formed, feasible, bounded; - int flag, nvars, neqns, nequals; + bool well_formed{true}, feasible{true}, bounded{true}; + int flag{0}, nvars, neqns, nequals; T total_cost, eps1, eps2, eps3, tmin; BFS opt_bfs, dual_bfs; LPTableau tab; - Array *UB, *LB; - Array *ub, *lb; - Vector *xx, *cost; + Array UB, LB; + Array ub, lb; + Vector xx, cost; Vector y, x, d; void Solve(int phase = 0); int Enter(); int Exit(int); - static Array Artificials(const Vector &); - public: LPSolve(const Matrix &A, const Vector &B, const Vector &C, int nequals); // nequals = number of equalities (last nequals rows) - ~LPSolve(); + ~LPSolve() = default; T OptimumCost() const { return total_cost; } - const Vector &OptimumVector() const { return (*xx); } - const List> &GetAll(); + const Vector &OptimumVector() const { return xx; } const LPTableau &GetTableau() const { return tab; } const BFS &OptimumBFS() const { return opt_bfs; } bool IsWellFormed() const { return well_formed; } bool IsFeasible() const { return feasible; } bool IsBounded() const { return bounded; } - long NumPivots() const { return tab.NumPivots(); } }; -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg #endif // LPSOLVE_H diff --git a/src/solvers/linalg/lpsolve.imp b/src/solvers/linalg/lpsolve.imp index 1bd48e07b..dbc962d07 100644 --- a/src/solvers/linalg/lpsolve.imp +++ b/src/solvers/linalg/lpsolve.imp @@ -22,15 +22,25 @@ #include "lpsolve.h" -namespace Gambit { +namespace Gambit::linalg { -namespace linalg { +template Array MakeArtificials(const Vector &b) +{ + Array ret; + for (int i = b.first_index(); i <= b.last_index(); i++) { + if (b[i] < static_cast(0)) { + ret.push_back(i); + } + } + return ret; +} template LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, int nequals) - : well_formed(true), feasible(true), bounded(true), nvars(c.size()), neqns(b.size()), - nequals(nequals), total_cost(0), tmin(0), tab(A, Artificials(b), b), UB(nullptr), LB(nullptr), - ub(nullptr), lb(nullptr), xx(nullptr), cost(nullptr), y(b.size()), x(b.size()), d(b.size()) + : nvars(c.size()), neqns(b.size()), nequals(nequals), total_cost(0), tmin(0), + tab(A, MakeArtificials(b), b), UB(nvars + neqns), LB(nvars + neqns), ub(nvars + neqns), + lb(nvars + neqns), xx(nvars + neqns), cost(nvars + neqns), y(b.size()), x(b.size()), + d(b.size()) { // These are the values recommended by Murtagh (1981) for 15 digit // accuracy in LP problems @@ -45,75 +55,63 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, } // initialize data - int i, j, num_inequals, xlab, num_artific; - - num_inequals = A.NumRows() - nequals; - num_artific = Artificials(b).size(); + int xlab; + const int num_inequals = A.NumRows() - nequals; + const int num_artific = MakeArtificials(b).size(); nvars += num_artific; - UB = new Array(nvars + neqns); - LB = new Array(nvars + neqns); - ub = new Array(nvars + neqns); - lb = new Array(nvars + neqns); - xx = new Vector(nvars + neqns); - cost = new Vector(nvars + neqns); - - for (j = (*UB).first_index(); j <= (*UB).last_index(); j++) { - (*UB)[j] = false; - (*LB)[j] = false; - (*ub)[j] = (T)0; - (*lb)[j] = (T)0; - } + std::fill(UB.begin(), UB.end(), false); + std::fill(LB.begin(), LB.end(), false); + std::fill(ub.begin(), ub.end(), static_cast(0)); + std::fill(lb.begin(), lb.end(), static_cast(0)); // Define Phase I upper and lower bounds - for (i = 1; i <= nvars; i++) { - (*LB)[i] = true; // original and artificial variables - // (*lb)[i]=(T)0; // have lower bounds of 0 + for (int i = 1; i <= nvars; i++) { + LB[i] = true; // original and artificial variables have lower bounds of 0 } // for slack variables - for (i = 1; i <= neqns; i++) { + for (int i = 1; i <= neqns; i++) { if (b[i] >= (T)0) { - (*LB)[nvars + i] = true; + LB[nvars + i] = true; } else { - (*UB)[nvars + i] = true; + UB[nvars + i] = true; } } // define Phase 1 unit cost vector - (*cost) = (T)0; - for (i = 1; i <= neqns; i++) { - (*cost)[nvars + i] = (T)0; - if ((*UB)[nvars + i]) { - (*cost)[nvars + i] = (T)1; + cost = static_cast(0); + for (int i = 1; i <= neqns; i++) { + cost[nvars + i] = static_cast(0); + if (UB[nvars + i]) { + cost[nvars + i] = static_cast(1); } else if (i > num_inequals) { - (*cost)[nvars + i] = -(T)1; + cost[nvars + i] = static_cast(-1); } } // Initialize the tableau - - tab.SetCost(*cost); + tab.SetCost(cost); // set xx to be initial feasible solution to phase II - for (i = 1; i <= (*xx).size(); i++) { - if ((*LB)[i]) { - (*xx)[i] = (*lb)[i]; + for (size_t i = 1; i <= xx.size(); i++) { + if (LB[i]) { + xx[i] = lb[i]; } - else if ((*UB)[i]) { - (*xx)[i] = (*ub)[i]; + else if (UB[i]) { + xx[i] = ub[i]; } else { - (*xx)[i] = (T)0; + xx[i] = static_cast(0); } } tab.BasisVector(x); - for (i = 1; i <= x.size(); i++) { + for (size_t i = 1; i <= x.size(); i++) { xlab = tab.Label(i); if (xlab < 0) { xlab = nvars - xlab; } - (*xx)[xlab] = x[i]; + xx[xlab] = x[i]; } Solve(1); @@ -128,25 +126,20 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, // Define Phase II upper and lower bounds for slack variables - for (i = num_inequals + 1; i <= neqns; i++) { - (*UB)[nvars + i] = true; + for (int i = num_inequals + 1; i <= neqns; i++) { + UB[nvars + i] = true; } - for (i = 1; i <= neqns; i++) { + for (int i = 1; i <= neqns; i++) { if (b[i] < (T)0) { - (*LB)[nvars + i] = true; + LB[nvars + i] = true; } } // install Phase II unit cost vector + cost = static_cast(0); + std::copy(c.begin(), c.end(), cost.begin()); - for (i = c.first_index(); i <= c.last_index(); i++) { - (*cost)[i] = c[i]; - } - for (i = c.last_index() + 1; i <= nvars + neqns; i++) { - (*cost)[i] = (T)0; - } - - tab.SetCost(*cost); + tab.SetCost(cost); Solve(2); total_cost = tab.TotalCost(); @@ -154,30 +147,19 @@ LPSolve::LPSolve(const Matrix &A, const Vector &b, const Vector &c, opt_bfs = tab.GetBFS(); dual_bfs = tab.DualBFS(); - for (i = 1; i <= neqns; i++) { + for (int i = 1; i <= neqns; i++) { if (dual_bfs.count(-i)) { opt_bfs.insert(-i, dual_bfs[-i]); } } } -template Array LPSolve::Artificials(const Vector &b) -{ - Array ret; - for (int i = b.first_index(); i <= b.last_index(); i++) { - if (b[i] < (T)0) { - ret.push_back(i); - } - } - return ret; -} - template void LPSolve::Solve(int phase) { - int i, in, xlab; + int in, xlab; int outlab = 0; int out = 0; - Vector a(neqns); + const Vector a(neqns); do { // step 1: Solve y B = c_B @@ -197,18 +179,18 @@ template void LPSolve::Solve(int phase) outlab = tab.Label(out); } // update xx - for (i = 1; i <= x.size(); i++) { + for (size_t i = 1; i <= x.size(); i++) { xlab = tab.Label(i); if (xlab < 0) { xlab = nvars - xlab; } - (*xx)[xlab] = (*xx)[xlab] + (T)flag * tmin * d[i]; + xx[xlab] = xx[xlab] + (T)flag * tmin * d[i]; } if (in > 0) { - (*xx)[in] -= (T)flag * tmin; + xx[in] -= (T)flag * tmin; } if (in < 0) { - (*xx)[nvars - in] -= (T)flag * tmin; + xx[nvars - in] -= (T)flag * tmin; } } } while (outlab == in && outlab != 0); @@ -224,27 +206,24 @@ template void LPSolve::Solve(int phase) template int LPSolve::Enter() { - int i, in; - T rc; - in = 0; - - T test = (T)0; - for (i = 1; i <= nvars + neqns; i++) { + int in = 0; + T test = static_cast(0); + for (int i = 1; i <= nvars + neqns; i++) { int lab = i; if (i > nvars) { lab = nvars - i; } if (!tab.Member(lab)) { - rc = tab.RelativeCost(lab); + T rc = tab.RelativeCost(lab); if (rc > test + eps1) { - if (!(*UB)[i] || ((*UB)[i] && (*xx)[i] - (*ub)[i] < -eps1)) { + if (!UB[i] || (UB[i] && xx[i] - ub[i] < -eps1)) { test = rc; in = lab; flag = -1; } } if (-rc > test + eps1) { - if (!(*LB)[i] || ((*LB)[i] && (*xx)[i] - (*lb)[i] > eps1)) { + if (!LB[i] || (LB[i] && xx[i] - lb[i] > eps1)) { test = -rc; in = lab; flag = 1; @@ -270,11 +249,11 @@ template int LPSolve::Exit(int in) } if (flag == -1) { t = (T)1000000000; - if (d[j] > eps2 && (*LB)[col]) { - t = ((*xx)[col] - (*lb)[col]) / d[j]; + if (d[j] > eps2 && LB[col]) { + t = (xx[col] - lb[col]) / d[j]; } - if (d[j] < -eps2 && (*UB)[col]) { - t = ((*xx)[col] - (*ub)[col]) / d[j]; + if (d[j] < -eps2 && UB[col]) { + t = (xx[col] - ub[col]) / d[j]; } if (t >= -eps2 && t < tmin - eps2) { tmin = t; @@ -283,11 +262,11 @@ template int LPSolve::Exit(int in) } if (flag == 1) { t = (T)1000000000; - if (d[j] > eps2 && (*UB)[col]) { - t = ((*ub)[col] - (*xx)[col]) / d[j]; + if (d[j] > eps2 && UB[col]) { + t = (ub[col] - xx[col]) / d[j]; } - if (d[j] < -eps2 && (*LB)[col]) { - t = ((*lb)[col] - (*xx)[col]) / d[j]; + if (d[j] < -eps2 && LB[col]) { + t = (lb[col] - xx[col]) / d[j]; } if (t >= -eps2 && t < tmin - eps2) { tmin = t; @@ -300,11 +279,11 @@ template int LPSolve::Exit(int in) col = nvars - in; } t = (T)1000000000; - if (flag == -1 && (*UB)[col]) { - t = (*ub)[col] - (*xx)[col]; + if (flag == -1 && UB[col]) { + t = ub[col] - xx[col]; } - if (flag == 1 && (*LB)[col]) { - t = (*xx)[col] - (*lb)[col]; + if (flag == 1 && LB[col]) { + t = xx[col] - lb[col]; } if (t > eps2 && t < tmin - eps2) { tmin = t; @@ -314,28 +293,4 @@ template int LPSolve::Exit(int in) return out; } -template LPSolve::~LPSolve() -{ - if (UB) { - delete UB; - } - if (LB) { - delete LB; - } - if (ub) { - delete ub; - } - if (lb) { - delete lb; - } - if (xx) { - delete xx; - } - if (cost) { - delete cost; - } -} - -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg diff --git a/src/solvers/linalg/lptab.h b/src/solvers/linalg/lptab.h index b8c43a0fa..c0f22cd3a 100644 --- a/src/solvers/linalg/lptab.h +++ b/src/solvers/linalg/lptab.h @@ -45,10 +45,8 @@ template class LPTableau : public Tableau { // cost information void SetCost(const Vector &); // unit column cost := 0 - const Vector &GetCost() const { return cost; } - const Vector &GetUnitCost() const { return unitcost; } - T TotalCost() const; // cost of current solution - T RelativeCost(int) const; // negative index convention + T TotalCost() const; // cost of current solution + T RelativeCost(int) const; // negative index convention const Vector &GetDualVector() const { return dual; } void Refactor() override; @@ -57,12 +55,6 @@ template class LPTableau : public Tableau { bool IsDualReversePivot(int i, int j); BFS DualBFS() const; - // returns the label of the index of the last artificial variable - int GetLastLabel() { return this->artificial.last_index(); } - - // select Basis elements according to Tableau rows and cols - void BasisSelect(const Array &rowv, Vector &colv) const; - // as above, but unit column elements nonzero void BasisSelect(const Array &unitv, const Array &rowv, Vector &colv) const; }; diff --git a/src/solvers/linalg/lptab.imp b/src/solvers/linalg/lptab.imp index 3d586a31c..cac1bab7f 100644 --- a/src/solvers/linalg/lptab.imp +++ b/src/solvers/linalg/lptab.imp @@ -78,7 +78,7 @@ template <> void LPTableau::SetCost(const Vector &c) if (c.first_index() != cost.first_index()) { throw DimensionException(); } - if (c.last_index() != (cost.last_index() + unitcost.size())) { + if (c.last_index() != static_cast(cost.last_index() + unitcost.size())) { throw DimensionException(); } for (int i = c.first_index(); i <= cost.last_index(); i++) { @@ -158,7 +158,7 @@ template std::list> LPTableau::ReversePivots() this->BasisVector(solution); for (int j = -this->MaxRow(); j <= this->MaxCol(); j++) { - if (j && !this->Member(j) && !this->IsBlocked(j)) { + if (j && !this->Member(j)) { this->SolveColumn(j, tmpcol); // find all i where prior tableau is primal feasible Array best_set; @@ -245,7 +245,7 @@ template std::list> LPTableau::ReversePivots() } else { flag = false; - for (k = -this->b->last_index(); k < enter && !flag; k++) { + for (k = -this->b.last_index(); k < enter && !flag; k++) { if (k != 0) { if (k < 0) { a_ik = tmpdual[-k]; @@ -300,7 +300,7 @@ template bool LPTableau::IsDualReversePivot(int i, int j) } ratio = c_j / a_ij; - for (int k = -this->b->last_index(); k <= cost.last_index(); k++) { + for (int k = -this->b.last_index(); k <= cost.last_index(); k++) { if (k != 0) { if (k < 0) { a_ik = tmpdual[-k]; @@ -319,7 +319,7 @@ template bool LPTableau::IsDualReversePivot(int i, int j) // check that i would be the column to enter in prior tableau - int enter = this->Label(i); + const int enter = this->Label(i); if (enter < 0) { a_ik = tmpdual[-enter]; } @@ -336,7 +336,7 @@ template bool LPTableau::IsDualReversePivot(int i, int j) } ratio = c_k / a_ik; - for (int k = -this->b->last_index(); k <= cost.last_index(); k++) { + for (int k = -this->b.last_index(); k <= cost.last_index(); k++) { if (k != 0) { if (k < 0) { a_ik = tmpdual[-k]; @@ -367,7 +367,7 @@ template bool LPTableau::IsDualReversePivot(int i, int j) return false; } - for (int k = this->b->first_index(); k <= this->b->last_index(); k++) { + for (int k = this->b.first_index(); k <= this->b.last_index(); k++) { if (k != i) { b_k = solution[k] - b_i * tmpcol[k]; if (this->GtZero(b_k) && this->Label(k) < j) { @@ -390,18 +390,6 @@ template BFS LPTableau::DualBFS() const return cbfs; } -template void LPTableau::BasisSelect(const Array &rowv, Vector &colv) const -{ - for (int i = this->basis.First(); i <= this->basis.Last(); i++) { - if (this->basis.Label(i) < 0) { - colv[i] = static_cast(0); - } - else { - colv[i] = rowv[this->basis.Label(i)]; - } - } -} - template void LPTableau::BasisSelect(const Array &unitv, const Array &rowv, Vector &colv) const { diff --git a/src/solvers/linalg/ludecomp.h b/src/solvers/linalg/ludecomp.h index 28130227c..6f690fbfc 100644 --- a/src/solvers/linalg/ludecomp.h +++ b/src/solvers/linalg/ludecomp.h @@ -24,7 +24,7 @@ #define LUDECOMP_H #include "gambit.h" -#include "basis.h" +#include "btableau.h" namespace Gambit::linalg { diff --git a/src/solvers/linalg/ludecomp.imp b/src/solvers/linalg/ludecomp.imp index a0194fa1d..b3569c742 100644 --- a/src/solvers/linalg/ludecomp.imp +++ b/src/solvers/linalg/ludecomp.imp @@ -83,7 +83,7 @@ template void LUDecomposition::update(int col, int matcol) if (copycount != 0) { throw BadCount(); } - int m = basis.Last() - basis.First() + 1; + const int m = basis.Last() - basis.First() + 1; iterations++; if ((refactor_number > 0 && iterations >= refactor_number) || @@ -114,7 +114,7 @@ template void LUDecomposition::refactor() } iterations = 0; - int m = basis.Last() - basis.First() + 1; + const int m = basis.Last() - basis.First() + 1; total_operations = (m - 1) * m * (2 * m - 1) / 6; if (parent != nullptr) { parent->copycount--; @@ -300,7 +300,7 @@ template void LUDecomposition::yLP_Trans(Vector &y) const template void LUDecomposition::yLP_mult(const Vector &y, int j, Vector &ans) const { - int ell = j + y.first_index(); + const int ell = j + y.first_index(); for (int i = y.first_index(); i <= y.last_index(); i++) { if (i != L[j].second.col) { @@ -328,7 +328,7 @@ template void LUDecomposition::LPd_Trans(Vector &d) const template void LUDecomposition::LPd_mult(Vector &d, int j, Vector &ans) const { - int k = j + d.first_index(); + const int k = j + d.first_index(); std::swap(d[k], d[L[j].first]); for (int i = d.first_index(); i <= d.last_index(); i++) { if (i == L[j].second.col) { @@ -343,9 +343,9 @@ template void LUDecomposition::LPd_mult(Vector &d, int j, Vector template bool LUDecomposition::RefactorCheck() { - int m = basis.Last() - basis.First() + 1; - int i = iterations * (iterations * m + 2 * m * m); - int k = total_operations + iterations * m + 2 * m * m; + const int m = basis.Last() - basis.First() + 1; + const int i = iterations * (iterations * m + 2 * m * m); + const int k = total_operations + iterations * m + 2 * m * m; return i > k; } diff --git a/src/solvers/linalg/tableau.cc b/src/solvers/linalg/tableau.cc index c533560fb..3967e68f7 100644 --- a/src/solvers/linalg/tableau.cc +++ b/src/solvers/linalg/tableau.cc @@ -22,9 +22,7 @@ #include "tableau.h" -namespace Gambit { - -namespace linalg { +namespace Gambit::linalg { // --------------------------------------------------------------------------- // Tableau method definitions @@ -49,8 +47,6 @@ Tableau::Tableau(const Tableau &orig) { } -Tableau::~Tableau() = default; - Tableau &Tableau::operator=(const Tableau &orig) { TableauInterface::operator=(orig); @@ -65,38 +61,19 @@ Tableau &Tableau::operator=(const Tableau &orig) // pivoting operations // -bool Tableau::CanPivot(int outlabel, int col) const -{ - const_cast *>(this)->SolveColumn(col, tmpcol); - double val = tmpcol[basis.Find(outlabel)]; - if (val <= eps2 && val >= -eps2) { - return false; - } - return true; -} - void Tableau::Pivot(int outrow, int col) { if (!RowIndex(outrow) || !ValidIndex(col)) { throw BadPivot(); } - - // int outlabel = Label(outrow); - // gout << "\noutrow:" << outrow; - // gout << " outlabel: " << outlabel; - // gout << " inlabel: " << col; - // BigDump(gout); basis.Pivot(outrow, col); B.update(outrow, col); - Solve(*b, solution); - npivots++; - // BigDump(gout); + Solve(b, solution); } void Tableau::SolveColumn(int col, Vector &out) { - //** can we use tmpcol here, instead of allocating new vector? Vector tmpcol2(MinRow(), MaxRow()); GetColumn(col, tmpcol2); Solve(tmpcol2, out); @@ -112,55 +89,27 @@ void Tableau::Refactor() { B.refactor(); //** is re-solve necessary here? - Solve(*b, solution); + Solve(b, solution); } -void Tableau::SetRefactor(int n) { B.SetRefactor(n); } - void Tableau::SetConst(const Vector &bnew) { - if (bnew.first_index() != b->first_index() || bnew.last_index() != b->last_index()) { + if (bnew.first_index() != b.first_index() || bnew.last_index() != b.last_index()) { throw DimensionException(); } - b = &bnew; - Solve(*b, solution); -} - -//** this function is not currently used. Drop it? -void Tableau::SetBasis(const Basis &in) -{ - basis = in; - B.refactor(); - Solve(*b, solution); + b = bnew; + Solve(b, solution); } void Tableau::Solve(const Vector &b, Vector &x) { B.solve(b, x); } -void Tableau::SolveT(const Vector &c, Vector &y) -{ - B.solveT(c, y); - //** gout << "\nTableau::SolveT(), y: " << y; - // gout << "\nc: " << c; -} - -bool Tableau::IsFeasible() -{ - //** is it really necessary to solve first here? - Solve(*b, solution); - for (int i = solution.first_index(); i <= solution.last_index(); i++) { - if (solution[i] >= eps2) { - return false; - } - } - return true; -} +void Tableau::SolveT(const Vector &c, Vector &y) { B.solveT(c, y); } bool Tableau::IsLexMin() { - int i, j; - for (i = MinRow(); i <= MaxRow(); i++) { + for (int i = MinRow(); i <= MaxRow(); i++) { if (EqZero(solution[i])) { - for (j = -MaxRow(); j < Label(i); j++) { + for (int j = -MaxRow(); j < Label(i); j++) { if (j != 0) { SolveColumn(j, tmpcol); if (LtZero(tmpcol[i])) { @@ -214,7 +163,7 @@ Tableau::Tableau(const Matrix &A, const Vector &b) } for (int i = b.first_index(); i <= b.last_index(); i++) { - Rational x = b[i] * (Rational)totdenom; + const Rational x = b[i] * (Rational)totdenom; if (x.denominator() != 1) { throw BadDenom(); } @@ -222,7 +171,7 @@ Tableau::Tableau(const Matrix &A, const Vector &b) } for (int i = MinRow(); i <= MaxRow(); i++) { for (int j = MinCol(); j <= MaxCol(); j++) { - Rational x = A(i, j) * (Rational)totdenom; + const Rational x = A(i, j) * (Rational)totdenom; if (x.denominator() != 1) { throw BadDenom(); } @@ -251,7 +200,7 @@ Tableau::Tableau(const Matrix &A, const Array &art, } for (int i = b.first_index(); i <= b.last_index(); i++) { - Rational x = b[i] * (Rational)totdenom; + const Rational x = b[i] * (Rational)totdenom; if (x.denominator() != 1) { throw BadDenom(); } @@ -259,7 +208,7 @@ Tableau::Tableau(const Matrix &A, const Array &art, } for (int i = MinRow(); i <= MaxRow(); i++) { for (int j = MinCol(); j <= A.MaxCol(); j++) { - Rational x = A(i, j) * (Rational)totdenom; + const Rational x = A(i, j) * (Rational)totdenom; if (x.denominator() != 1) { throw BadDenom(); } @@ -274,12 +223,6 @@ Tableau::Tableau(const Matrix &A, const Array &art, } } -Tableau::Tableau(const Tableau &orig) - - = default; - -Tableau::~Tableau() = default; - Tableau &Tableau::operator=(const Tableau &orig) { TableauInterface::operator=(orig); @@ -320,40 +263,15 @@ Matrix Tableau::GetInverse() } // pivoting operations - -bool Tableau::CanPivot(int outlabel, int col) const -{ - const_cast *>(this)->MySolveColumn(col, tmpcol); - Rational val = tmpcol[basis.Find(outlabel)]; - if (val == (Rational)0) { - return false; - } - // if(val <=eps2 && val >= -eps2) return 0; - return true; -} - void Tableau::Pivot(int outrow, int in_col) { - // gout << "\nIn Tableau::Pivot() "; - // gout << " outrow:" << outrow; - // gout << " inlabel: " << in_col; if (!RowIndex(outrow) || !ValidIndex(in_col)) { throw BadPivot(); } - int outlabel = Label(outrow); - - // gout << "\noutrow:" << outrow; - // gout << " outlabel: " << outlabel; - // gout << " inlabel: " << in_col; - - // BigDump(gout); - // gout << "\ndenom: " << denom << " totdenom: " << totdenom; - // gout << " product: " << denom*totdenom; - // gout << "\nTabdat: loc 1\n " << Tabdat; - // gout << "\nInverse: loc 1\n " << GetInverse(); + const int outlabel = Label(outrow); int col; - int row(outrow); + const int row(outrow); int i, j; // loop-control variables col = remap(in_col); @@ -392,27 +310,16 @@ void Tableau::Pivot(int outrow, int in_col) } } // Step 4 - Integer old_denom = denom; + const Integer old_denom = denom; denom = Tabdat(row, col); Tabdat(row, col) = old_denom; - // BigDump(gout); - npivots++; basis.Pivot(outrow, in_col); nonbasic[col] = outlabel; for (i = solution.first_index(); i <= solution.last_index(); i++) { - //** solution[i] = (Rational)(Coeff[i])/(Rational)(denom*totdenom); solution[i] = Rational(Coeff[i] * sign(denom * totdenom)); } - - // gout << "Bottom \n" << Tabdat << '\n'; - // BigDump(gout); - // gout << "\ndenom: " << denom << " totdenom: " << totdenom; - // gout << "\nTabdat: loc 2\n " << Tabdat; - // gout << "\nInverse: loc 2\n " << GetInverse(); - - // Refactor(); } void Tableau::SolveColumn(int in_col, Vector &out) @@ -423,7 +330,7 @@ void Tableau::SolveColumn(int in_col, Vector &out) out[Find(in_col)] = Rational(abs(denom)); } else { - int col = remap(in_col); + const int col = remap(in_col); Tabdat.GetColumn(col, tempcol); for (int i = tempcol.first_index(); i <= tempcol.last_index(); i++) { out[i] = (Rational)(tempcol[i]) * (Rational)(sign(denom * totdenom)); @@ -448,7 +355,7 @@ void Tableau::MySolveColumn(int in_col, Vector &out) out[Find(in_col)] = Rational(abs(denom)); } else { - int col = remap(in_col); + const int col = remap(in_col); Tabdat.GetColumn(col, tempcol); for (int i = tempcol.first_index(); i <= tempcol.last_index(); i++) { out[i] = (Rational)(tempcol[i]) * (Rational)(sign(denom * totdenom)); @@ -467,35 +374,20 @@ void Tableau::GetColumn(int col, Vector &out) const void Tableau::Refactor() { Vector mytmpcol(tmpcol); - // BigDump(gout); - //** Note -- we may need to recompute totdenom here, if A and b have changed. - // gout << "\ndenom: " << denom << " totdenom: " << totdenom; - totdenom = lcm(find_lcd(*A), find_lcd(*b)); + totdenom = lcm(find_lcd(A), find_lcd(b)); if (totdenom <= 0) { throw BadDenom(); } - // gout << "\ndenom: " << denom << " totdenom: " << totdenom; - int i, j; - Matrix inv(GetInverse()); + const Matrix inv(GetInverse()); Matrix Tabnew(Tabdat.MinRow(), Tabdat.MaxRow(), Tabdat.MinCol(), Tabdat.MaxCol()); for (i = nonbasic.first_index(); i <= nonbasic.last_index(); i++) { GetColumn(nonbasic[i], mytmpcol); - // if(nonbasic[i]>=0) mytmpcol*=Rational(totdenom); Tabnew.SetColumn(i, inv * mytmpcol * (Rational)sign(denom * totdenom)); - // gout << "\nMyTmpCol \n" << mytmpcol; } - // gout << "\nInv: \n" << inv; - // gout << "\nTabdat:\n" << Tabdat; - // gout << "\nTabnew:\n" << Tabnew; - Vector Coeffnew(Coeff.first_index(), Coeff.last_index()); - Coeffnew = inv * (*b) * Rational(totdenom) * Rational(sign(denom * totdenom)); - - // gout << "\nCoeff:\n" << Coeff; - // gout << "\nCoeffew:\n" << Coeffnew; - + Coeffnew = inv * b * Rational(totdenom) * Rational(sign(denom * totdenom)); for (i = Tabdat.MinRow(); i <= Tabdat.MaxRow(); i++) { if (Coeffnew[i].denominator() != 1) { throw BadDenom(); @@ -511,23 +403,12 @@ void Tableau::Refactor() // BigDump(gout); } -void Tableau::SetRefactor(int) {} - void Tableau::SetConst(const Vector &bnew) { - b = &bnew; + b = bnew; Refactor(); } -//** this function is not currently used. Drop it? -void Tableau::SetBasis(const Basis &in) -{ - basis = in; - //** this has to be changed -- Need to start over and pivot to new basis. - // B.refactor(); - // B.solve(*b, solution); -} - // solve M x = b void Tableau::Solve(const Vector &b, Vector &x) { @@ -542,22 +423,11 @@ void Tableau::SolveT(const Vector &c, Vector &y) y = (c * GetInverse()) / (Rational)abs(denom); } -bool Tableau::IsFeasible() -{ - for (int i = solution.first_index(); i <= solution.last_index(); i++) { - if (solution[i] >= eps2) { - return false; - } - } - return true; -} - bool Tableau::IsLexMin() { - int i, j; - for (i = MinRow(); i <= MaxRow(); i++) { + for (int i = MinRow(); i <= MaxRow(); i++) { if (EqZero(solution[i])) { - for (j = -MaxRow(); j < Label(i); j++) { + for (int j = -MaxRow(); j < Label(i); j++) { if (j != 0) { SolveColumn(j, tmpcol); if (LtZero(tmpcol[i])) { @@ -581,8 +451,4 @@ void Tableau::BasisVector(Vector &out) const } } -Integer Tableau::TotDenom() const { return totdenom; } - -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg diff --git a/src/solvers/linalg/tableau.h b/src/solvers/linalg/tableau.h index 23314cbb0..adef2008c 100644 --- a/src/solvers/linalg/tableau.h +++ b/src/solvers/linalg/tableau.h @@ -26,17 +26,7 @@ #include "btableau.h" #include "ludecomp.h" -namespace Gambit { - -namespace linalg { - -template class Tableau; -template class LPTableau; - -// --------------------------------------------------------------------------- -// We have different implementations of Tableau for double and gbtRational, -// but with the same interface -// --------------------------------------------------------------------------- +namespace Gambit::linalg { template <> class Tableau : public TableauInterface { public: @@ -44,27 +34,22 @@ template <> class Tableau : public TableauInterface { Tableau(const Matrix &A, const Vector &b); Tableau(const Matrix &A, const Array &art, const Vector &b); Tableau(const Tableau &); - ~Tableau() override; + ~Tableau() override = default; Tableau &operator=(const Tableau &); // pivoting - bool CanPivot(int outgoing, int incoming) const override; void Pivot(int outrow, int col) override; // pivot -- outgoing is row, incoming is column - void BasisVector(Vector &x) const override; // solve M x = (*b) - void SolveColumn(int, Vector &) override; // column in new basis - void Solve(const Vector &b, Vector &x) override; // solve M x = b - void SolveT(const Vector &c, Vector &y) override; // solve y M = c + void BasisVector(Vector &x) const override; // solve M x = (*b) + void SolveColumn(int, Vector &) override; // column in new basis + // solve M x = b + void Solve(const Vector &b, Vector &x); + // solve y M = c + void SolveT(const Vector &c, Vector &y); // raw Tableau functions - - void Refactor() override; - void SetRefactor(int) override; - + virtual void Refactor(); void SetConst(const Vector &bnew); - void SetBasis(const Basis &); // set new Tableau - - bool IsFeasible(); bool IsLexMin(); private: @@ -91,44 +76,37 @@ template <> class Tableau : public TableauInterface { protected: Array nonbasic; //** nonbasic variables -- should be moved to Basis + Integer TotDenom() const { return totdenom; } + public: class BadDenom : public Exception { public: ~BadDenom() noexcept override = default; const char *what() const noexcept override { return "Bad denominator in Tableau"; } }; - // constructors and destructors + Tableau(const Matrix &A, const Vector &b); Tableau(const Matrix &A, const Array &art, const Vector &b); - Tableau(const Tableau &); - ~Tableau() override; + Tableau(const Tableau &) = default; + ~Tableau() override = default; Tableau &operator=(const Tableau &); // pivoting - bool CanPivot(int outgoing, int incoming) const override; void Pivot(int outrow, int col) override; // pivot -- outgoing is row, incoming is column - void SolveColumn(int, Vector &) override; // column in new basis - void GetColumn(int, Vector &) const; // column in new basis + void SolveColumn(int, Vector &) override; // column in new basis + void GetColumn(int, Vector &) const override; // column in new basis // raw Tableau functions - - void Refactor() override; - void SetRefactor(int) override; - + virtual void Refactor(); void SetConst(const Vector &bnew); - void SetBasis(const Basis &); // set new Tableau - void Solve(const Vector &b, Vector &x) override; // solve M x = b - void SolveT(const Vector &c, Vector &y) override; // solve y M = c + void Solve(const Vector &b, Vector &x); // solve M x = b + void SolveT(const Vector &c, Vector &y); // solve y M = c - bool IsFeasible(); bool IsLexMin(); void BasisVector(Vector &out) const override; - Integer TotDenom() const; }; -} // namespace linalg - -} // end namespace Gambit +} // end namespace Gambit::linalg #endif // TABLEAU_H diff --git a/src/solvers/linalg/vertenum.h b/src/solvers/linalg/vertenum.h index 483a26d65..8175b1a50 100644 --- a/src/solvers/linalg/vertenum.h +++ b/src/solvers/linalg/vertenum.h @@ -25,7 +25,6 @@ #include "gambit.h" #include "lptab.h" -#include "bfs.h" namespace Gambit::linalg { @@ -45,8 +44,8 @@ template class VertexEnumerator { private: bool mult_opt; size_t depth{0}; - int n; // N is the number of columns, which is the # of dimensions. - int k; // K is the number of inequalities given. + // int n; // N is the number of columns, which is the # of dimensions. + // int k; // K is the number of inequalities given. const Matrix &A; const Vector &b; Vector btemp; diff --git a/src/solvers/linalg/vertenum.imp b/src/solvers/linalg/vertenum.imp index 65b359bc6..b05829f1d 100644 --- a/src/solvers/linalg/vertenum.imp +++ b/src/solvers/linalg/vertenum.imp @@ -29,7 +29,7 @@ VertexEnumerator::VertexEnumerator(const Matrix &A, const Vector &b) : mult_opt(std::any_of(b.begin(), b.end(), [](const T &v) { return v == static_cast(0); })), A(A), b(b), btemp(b) { - btemp = static_cast(-1); + btemp = static_cast(-1); // NOLINT(cppcoreguidelines-prefer-member-initializer) LPTableau tab(A, b); Vector c(A.MinCol(), A.MaxCol()); c = static_cast(1); @@ -79,7 +79,7 @@ template void VertexEnumerator::DualSearch(LPTableau &tab) for (int i = b.first_index(); i <= b.last_index(); i++) { if (b[i] == static_cast(0)) { for (int j = -b.last_index(); j <= A.MaxCol(); j++) { - if (j && !tab.Member(j) && !tab.IsBlocked(j)) { + if (j && !tab.Member(j)) { if (tab.IsDualReversePivot(i, j)) { branches[depth] += 1; tab2 = tab; diff --git a/src/solvers/logit/efglogit.cc b/src/solvers/logit/efglogit.cc index a23470294..dad6d5697 100644 --- a/src/solvers/logit/efglogit.cc +++ b/src/solvers/logit/efglogit.cc @@ -32,27 +32,21 @@ namespace { double LogLike(const Vector &p_frequencies, const Vector &p_point) { - double logL = 0.0; - for (int i = 1; i <= p_frequencies.size(); i++) { - logL += p_frequencies[i] * log(p_point[i]); - } - return logL; + return std::inner_product(p_frequencies.begin(), p_frequencies.end(), p_point.begin(), 0.0, + std::plus<>(), + [](double freq, double prob) { return freq * std::log(prob); }); } double DiffLogLike(const Vector &p_frequencies, const Vector &p_tangent) { - double diff_logL = 0.0; - for (int i = 1; i <= p_frequencies.size(); i++) { - diff_logL += p_frequencies[i] * p_tangent[i]; - } - return diff_logL; + return std::inner_product(p_frequencies.begin(), p_frequencies.end(), p_tangent.begin(), 0.0); } MixedBehaviorProfile PointToProfile(const Game &p_game, const Vector &p_point) { MixedBehaviorProfile profile(p_game); - for (int i = 1; i < p_point.size(); i++) { - profile[i] = exp(p_point[i]); + for (size_t i = 1; i < p_point.size(); i++) { + profile[i] = std::exp(p_point[i]); } return profile; } @@ -70,7 +64,7 @@ Vector ProfileToPoint(const LogitQREMixedBehaviorProfile &p_profile) { Vector point(p_profile.size() + 1); for (size_t i = 1; i <= p_profile.size(); i++) { - point[i] = log(p_profile[i]); + point[i] = std::log(p_profile[i]); } point.back() = p_profile.GetLambda(); return point; @@ -229,20 +223,20 @@ void EquationSystem::RatioEquation::Gradient(const LogBehavProfile &p_pr void EquationSystem::GetValue(const Vector &p_point, Vector &p_lhs) const { - LogBehavProfile profile(PointToLogProfile(m_game, p_point)); - double lambda = p_point.back(); - for (int i = 1; i <= p_lhs.size(); i++) { - p_lhs[i] = m_equations[i]->Value(profile, lambda); - } + const LogBehavProfile profile(PointToLogProfile(m_game, p_point)); + const double lambda = p_point.back(); + std::transform( + m_equations.begin(), m_equations.end(), p_lhs.begin(), + [&profile, lambda](std::shared_ptr e) { return e->Value(profile, lambda); }); } void EquationSystem::GetJacobian(const Vector &p_point, Matrix &p_matrix) const { - LogBehavProfile profile(PointToLogProfile(m_game, p_point)); - double lambda = p_point.back(); + const LogBehavProfile profile(PointToLogProfile(m_game, p_point)); + const double lambda = p_point.back(); - for (int i = 1; i <= m_equations.size(); i++) { - Vector column(p_point.size()); + Vector column(p_point.size()); + for (size_t i = 1; i <= m_equations.size(); i++) { m_equations[i]->Gradient(profile, lambda, column); p_matrix.SetColumn(i, column); } @@ -267,7 +261,7 @@ class TracingCallbackFunction { void TracingCallbackFunction::AppendPoint(const Vector &p_point) { - MixedBehaviorProfile profile(PointToProfile(m_game, p_point)); + const MixedBehaviorProfile profile(PointToProfile(m_game, p_point)); m_profiles.push_back(LogitQREMixedBehaviorProfile(profile, p_point.back(), 1.0)); m_observer(m_profiles.back()); } @@ -300,7 +294,7 @@ EstimatorCallbackFunction::EstimatorCallbackFunction(const Game &p_game, void EstimatorCallbackFunction::EvaluatePoint(const Vector &p_point) { - MixedBehaviorProfile profile(PointToProfile(m_game, p_point)); + const MixedBehaviorProfile profile(PointToProfile(m_game, p_point)); auto qre = LogitQREMixedBehaviorProfile( profile, p_point.back(), LogLike(m_frequencies, static_cast &>(profile))); @@ -323,12 +317,12 @@ List LogitBehaviorSolve(const LogitQREMixedBehavio tracer.SetMaxDecel(p_maxAccel); tracer.SetStepsize(p_firstStep); - double scale = p_start.GetGame()->GetMaxPayoff() - p_start.GetGame()->GetMinPayoff(); + const double scale = p_start.GetGame()->GetMaxPayoff() - p_start.GetGame()->GetMinPayoff(); if (scale != 0.0) { p_regret *= scale; } - Game game = p_start.GetGame(); + const Game game = p_start.GetGame(); Vector x(ProfileToPoint(p_start)); TracingCallbackFunction callback(game, p_observer); EquationSystem system(game); @@ -357,7 +351,7 @@ LogitBehaviorSolveLambda(const LogitQREMixedBehaviorProfile &p_start, tracer.SetMaxDecel(p_maxAccel); tracer.SetStepsize(p_firstStep); - Game game = p_start.GetGame(); + const Game game = p_start.GetGame(); Vector x(ProfileToPoint(p_start)); TracingCallbackFunction callback(game, p_observer); EquationSystem system(game); @@ -385,13 +379,13 @@ LogitBehaviorEstimate(const MixedBehaviorProfile &p_frequencies, double double p_omega, double p_stopAtLocal, double p_firstStep, double p_maxAccel, MixedBehaviorObserverFunctionType p_observer) { - LogitQREMixedBehaviorProfile start(p_frequencies.GetGame()); + const LogitQREMixedBehaviorProfile start(p_frequencies.GetGame()); PathTracer tracer; tracer.SetMaxDecel(p_maxAccel); tracer.SetStepsize(p_firstStep); Vector x(ProfileToPoint(start)), restart(x); - Vector freq_vector(static_cast &>(p_frequencies)); + const Vector freq_vector(static_cast &>(p_frequencies)); EstimatorCallbackFunction callback( start.GetGame(), static_cast &>(p_frequencies), p_observer); EquationSystem system(start.GetGame()); diff --git a/src/solvers/logit/logbehav.h b/src/solvers/logit/logbehav.h index 2aea618de..78370a756 100644 --- a/src/solvers/logit/logbehav.h +++ b/src/solvers/logit/logbehav.h @@ -56,7 +56,7 @@ template class LogBehavProfile { std::map m_profileIndex; // structures for storing cached data - mutable bool m_cacheValid; + mutable bool m_cacheValid{false}; mutable std::map m_logRealizProbs; mutable std::map m_beliefs; mutable std::map> m_nodeValues; diff --git a/src/solvers/logit/logbehav.imp b/src/solvers/logit/logbehav.imp index 5885d1a4a..e6eba59b2 100644 --- a/src/solvers/logit/logbehav.imp +++ b/src/solvers/logit/logbehav.imp @@ -30,8 +30,7 @@ template LogBehavProfile::LogBehavProfile(const Game &p_game) - : m_game(p_game), m_probs(m_game->BehavProfileLength()), - m_logProbs(m_game->BehavProfileLength()), m_cacheValid(false) + : m_game(p_game), m_probs(m_game->BehavProfileLength()), m_logProbs(m_game->BehavProfileLength()) { int index = 1; for (const auto &player : p_game->GetPlayers()) { @@ -42,12 +41,10 @@ LogBehavProfile::LogBehavProfile(const Game &p_game) } } - for (auto infoset : m_game->GetInfosets()) { - if (infoset->NumActions() > 0) { - T center = (T(1) / T(infoset->NumActions())); - for (auto act : infoset->GetActions()) { - SetProb(act, center); - } + for (const auto &infoset : m_game->GetInfosets()) { + T center = T(1) / T(infoset->GetActions().size()); + for (const auto &act : infoset->GetActions()) { + SetProb(act, center); } } } @@ -68,25 +65,17 @@ template bool LogBehavProfile::operator==(const LogBehavProfile template T LogBehavProfile::GetActionProb(const GameAction &action) const { if (action->GetInfoset()->GetPlayer()->IsChance()) { - GameTreeInfosetRep *infoset = - dynamic_cast(action->GetInfoset().operator->()); - return static_cast(infoset->GetActionProb(action->GetNumber())); - } - else { - return GetProb(action); + return static_cast(action->GetInfoset()->GetActionProb(action)); } + return GetProb(action); } template T LogBehavProfile::GetLogActionProb(const GameAction &action) const { if (action->GetInfoset()->GetPlayer()->IsChance()) { - GameTreeInfosetRep *infoset = - dynamic_cast(action->GetInfoset().operator->()); - return log(static_cast(infoset->GetActionProb(action->GetNumber()))); - } - else { - return m_logProbs[m_profileIndex.at(action)]; + return log(static_cast(action->GetInfoset()->GetActionProb(action))); } + return m_logProbs[m_profileIndex.at(action)]; } template const T &LogBehavProfile::GetPayoff(const GameAction &act) const @@ -107,7 +96,7 @@ GameAction GetPrecedingAction(const GameNode &p_node, const GameInfoset &p_infos { GameNode node = p_node; while (node->GetParent()) { - GameAction prevAction = node->GetPriorAction(); + const GameAction prevAction = node->GetPriorAction(); if (prevAction->GetInfoset() == p_infoset) { return prevAction; } @@ -122,21 +111,21 @@ T LogBehavProfile::DiffActionValue(const GameAction &p_action, { ComputeSolutionData(); - GameInfoset infoset = p_action->GetInfoset(); - GamePlayer player = p_action->GetInfoset()->GetPlayer(); + const GameInfoset infoset = p_action->GetInfoset(); + const GamePlayer player = p_action->GetInfoset()->GetPlayer(); // derivs stores the ratio of the derivative of the realization probability // for each node, divided by the realization probability of the infoset, // times the probability with which p_oppAction is played std::map derivs; for (auto member : infoset->GetMembers()) { - GameAction act = GetPrecedingAction(member, p_oppAction->GetInfoset()); - derivs[member] = (act == p_oppAction) ? m_beliefs[member] : T(0); + const GameAction act = GetPrecedingAction(member, p_oppAction->GetInfoset()); + derivs[member] = (act == p_oppAction) ? m_beliefs[member] : static_cast(0); } - T deriv = T(0); + T deriv = static_cast(0); for (auto member : infoset->GetMembers()) { - GameNode child = member->GetChild(p_action); + const GameNode child = member->GetChild(p_action); deriv += derivs[member] * m_nodeValues[child][player]; deriv -= derivs[member] * GetPayoff(p_action); deriv += GetProb(p_oppAction) * m_beliefs[member] * DiffNodeValue(child, player, p_oppAction); @@ -151,13 +140,13 @@ T LogBehavProfile::DiffNodeValue(const GameNode &p_node, const GamePlayer &p_ { ComputeSolutionData(); - if (p_node->NumChildren() == 0) { + if (p_node->IsTerminal()) { // If we reach a terminal node and haven't encountered p_oppAction, // derivative wrt this path is zero. - return (T)0; + return static_cast(0); } - GameInfoset infoset = p_node->GetInfoset(); + const GameInfoset infoset = p_node->GetInfoset(); if (infoset == p_oppAction->GetInfoset()) { // We've encountered the action; since we assume perfect recall, // we won't encounter it again, and the downtree value must @@ -181,13 +170,13 @@ T LogBehavProfile::DiffNodeValue(const GameNode &p_node, const GamePlayer &p_ template void LogBehavProfile::ComputeSolutionDataPass2(const GameNode &node) const { if (node->GetOutcome()) { - GameOutcome outcome = node->GetOutcome(); + const GameOutcome outcome = node->GetOutcome(); for (auto player : m_game->GetPlayers()) { - m_nodeValues[node][player] += static_cast(outcome->GetPayoff(player)); + m_nodeValues[node][player] += outcome->GetPayoff(player); } } - GameInfoset infoset = node->GetInfoset(); + const GameInfoset infoset = node->GetInfoset(); if (!infoset) { return; } @@ -203,7 +192,7 @@ template void LogBehavProfile::ComputeSolutionDataPass2(const GameN for (auto child : node->GetChildren()) { ComputeSolutionDataPass2(child); - GameAction action = child->GetPriorAction(); + const GameAction action = child->GetPriorAction(); for (auto player : m_game->GetPlayers()) { m_nodeValues[node][player] += GetActionProb(action) * m_nodeValues[child][player]; } @@ -245,7 +234,7 @@ template void LogBehavProfile::ComputeSolutionData() const } if (infosetProb == T(0)) { for (auto member : infoset->GetMembers()) { - m_beliefs[member] = 1.0 / T(infoset->NumMembers()); + m_beliefs[member] = 1.0 / T(infoset->GetMembers().size()); } continue; } diff --git a/src/solvers/logit/nfglogit.cc b/src/solvers/logit/nfglogit.cc index ea2393d71..9111c12bf 100644 --- a/src/solvers/logit/nfglogit.cc +++ b/src/solvers/logit/nfglogit.cc @@ -34,8 +34,8 @@ namespace { MixedStrategyProfile PointToProfile(const Game &p_game, const Vector &p_point) { MixedStrategyProfile profile(p_game->NewMixedStrategyProfile(0.0)); - for (int i = 1; i < p_point.size(); i++) { - profile[i] = exp(p_point[i]); + for (size_t i = 1; i < p_point.size(); i++) { + profile[i] = std::exp(p_point[i]); } return profile; } @@ -44,7 +44,7 @@ Vector ProfileToPoint(const LogitQREMixedStrategyProfile &p_profile) { Vector point(p_profile.size() + 1); for (size_t i = 1; i <= p_profile.size(); i++) { - point[i] = log(p_profile[i]); + point[i] = std::log(p_profile[i]); } point.back() = p_profile.GetLambda(); return point; @@ -52,20 +52,14 @@ Vector ProfileToPoint(const LogitQREMixedStrategyProfile &p_profile) double LogLike(const Vector &p_frequencies, const Vector &p_point) { - double logL = 0.0; - for (int i = 1; i <= p_frequencies.size(); i++) { - logL += p_frequencies[i] * log(p_point[i]); - } - return logL; + return std::inner_product(p_frequencies.begin(), p_frequencies.end(), p_point.begin(), 0.0, + std::plus<>(), + [](double freq, double prob) { return freq * std::log(prob); }); } double DiffLogLike(const Vector &p_frequencies, const Vector &p_tangent) { - double diff_logL = 0.0; - for (int i = 1; i <= p_frequencies.size(); i++) { - diff_logL += p_frequencies[i] * p_tangent[i]; - } - return diff_logL; + return std::inner_product(p_frequencies.begin(), p_frequencies.end(), p_tangent.begin(), 0.0); } bool RegretTerminationFunction(const Game &p_game, const Vector &p_point, double p_regret) @@ -83,10 +77,10 @@ void GetValue(const Game &p_game, const Vector &p_point, Vector for (size_t i = 1; i <= profile.MixedProfileLength(); i++) { logprofile[i] = p_point[i]; } - double lambda = p_point.back(); + const double lambda = p_point.back(); p_lhs = 0.0; - for (int rowno = 0, pl = 1; pl <= p_game->NumPlayers(); pl++) { - GamePlayer player = p_game->GetPlayer(pl); + for (size_t rowno = 0, pl = 1; pl <= p_game->NumPlayers(); pl++) { + const GamePlayer player = p_game->GetPlayer(pl); for (size_t st = 1; st <= player->GetStrategies().size(); st++) { rowno++; if (st == 1) { @@ -113,18 +107,18 @@ void GetJacobian(const Game &p_game, const Vector &p_point, MatrixNumPlayers(); i++) { - GamePlayer player = p_game->GetPlayer(i); + for (size_t rowno = 0, i = 1; i <= p_game->NumPlayers(); i++) { + const GamePlayer player = p_game->GetPlayer(i); for (size_t j = 1; j <= player->GetStrategies().size(); j++) { rowno++; if (j == 1) { // This is a sum-to-one equation - for (int colno = 0, ell = 1; ell <= p_game->NumPlayers(); ell++) { - GamePlayer player2 = p_game->GetPlayer(ell); + for (size_t colno = 0, ell = 1; ell <= p_game->NumPlayers(); ell++) { + const GamePlayer player2 = p_game->GetPlayer(ell); for (size_t m = 1; m <= player2->GetStrategies().size(); m++) { colno++; if (i == ell) { @@ -137,8 +131,8 @@ void GetJacobian(const Game &p_game, const Vector &p_point, MatrixNumPlayers(); ell++) { - GamePlayer player2 = p_game->GetPlayer(ell); + for (size_t colno = 0, ell = 1; ell <= p_game->NumPlayers(); ell++) { + const GamePlayer player2 = p_game->GetPlayer(ell); for (size_t m = 1; m <= player2->GetStrategies().size(); m++) { colno++; if (i == ell) { @@ -185,7 +179,7 @@ class TracingCallbackFunction { void TracingCallbackFunction::AppendPoint(const Vector &p_point) { - MixedStrategyProfile profile(PointToProfile(m_game, p_point)); + const MixedStrategyProfile profile(PointToProfile(m_game, p_point)); m_profiles.push_back(LogitQREMixedStrategyProfile(profile, p_point.back(), 1.0)); m_observer(m_profiles.back()); } @@ -219,7 +213,7 @@ EstimatorCallbackFunction::EstimatorCallbackFunction(const Game &p_game, void EstimatorCallbackFunction::EvaluatePoint(const Vector &p_point) { - MixedStrategyProfile profile(PointToProfile(m_game, p_point)); + const MixedStrategyProfile profile(PointToProfile(m_game, p_point)); auto qre = LogitQREMixedStrategyProfile( profile, p_point.back(), LogLike(m_frequencies, static_cast &>(profile))); @@ -240,7 +234,7 @@ List LogitStrategySolve(const LogitQREMixedStrateg tracer.SetMaxDecel(p_maxAccel); tracer.SetStepsize(p_firstStep); - double scale = p_start.GetGame()->GetMaxPayoff() - p_start.GetGame()->GetMinPayoff(); + const double scale = p_start.GetGame()->GetMaxPayoff() - p_start.GetGame()->GetMinPayoff(); if (scale != 0.0) { p_regret *= scale; } @@ -304,7 +298,7 @@ LogitStrategyEstimate(const MixedStrategyProfile &p_frequencies, double tracer.SetStepsize(p_firstStep); Vector x(ProfileToPoint(start)), restart(x); - Vector freq_vector(static_cast &>(p_frequencies)); + const Vector freq_vector(static_cast &>(p_frequencies)); EstimatorCallbackFunction callback( start.GetGame(), static_cast &>(p_frequencies), p_observer); while (true) { diff --git a/src/solvers/logit/path.cc b/src/solvers/logit/path.cc index 3b6f80939..00743be76 100644 --- a/src/solvers/logit/path.cc +++ b/src/solvers/logit/path.cc @@ -50,19 +50,19 @@ void Givens(Matrix &b, Matrix &q, double &c1, double &c2, int l1 else { sn = std::sqrt(1.0 + sqr(c2 / c1)) * fabs(c1); } - double s1 = c1 / sn; - double s2 = c2 / sn; + const double s1 = c1 / sn; + const double s2 = c2 / sn; - for (int k = 1; k <= q.NumColumns(); k++) { - double sv1 = q(l1, k); - double sv2 = q(l2, k); + for (size_t k = 1; k <= q.NumColumns(); k++) { + const double sv1 = q(l1, k); + const double sv2 = q(l2, k); q(l1, k) = s1 * sv1 + s2 * sv2; q(l2, k) = -s2 * sv1 + s1 * sv2; } - for (int k = l3; k <= b.NumColumns(); k++) { - double sv1 = b(l1, k); - double sv2 = b(l2, k); + for (size_t k = l3; k <= b.NumColumns(); k++) { + const double sv1 = b(l1, k); + const double sv2 = b(l2, k); b(l1, k) = s1 * sv1 + s2 * sv2; b(l2, k) = -s2 * sv1 + s1 * sv2; } @@ -74,8 +74,8 @@ void Givens(Matrix &b, Matrix &q, double &c1, double &c2, int l1 void QRDecomp(Matrix &b, Matrix &q) { q.MakeIdent(); - for (int m = 1; m <= b.NumColumns(); m++) { - for (int k = m + 1; k <= b.NumRows(); k++) { + for (size_t m = 1; m <= b.NumColumns(); m++) { + for (size_t k = m + 1; k <= b.NumRows(); k++) { Givens(b, q, b(m, m), b(k, m), m, k, m + 1); } } @@ -84,17 +84,17 @@ void QRDecomp(Matrix &b, Matrix &q) void NewtonStep(Matrix &q, Matrix &b, Vector &u, Vector &y, double &d) { - for (int k = 1; k <= b.NumColumns(); k++) { - for (int l = 1; l <= k - 1; l++) { + for (size_t k = 1; k <= b.NumColumns(); k++) { + for (size_t l = 1; l <= k - 1; l++) { y[k] -= b(l, k) * y[l]; } y[k] /= b(k, k); } d = 0.0; - for (int k = 1; k <= b.NumRows(); k++) { + for (size_t k = 1; k <= b.NumRows(); k++) { double s = 0.0; - for (int l = 1; l <= b.NumColumns(); l++) { + for (size_t l = 1; l <= b.NumColumns(); l++) { s += q(l, k) * y[l]; } u[k] -= s; @@ -158,7 +158,7 @@ void PathTracer::TracePath( } // Predictor step - for (int k = 1; k <= x.size(); k++) { + for (size_t k = 1; k <= x.size(); k++) { u[k] = x[k] + h * p_omega * t[k]; } @@ -182,7 +182,7 @@ void PathTracer::TracePath( decel = std::max(decel, std::sqrt(dist / c_maxDist) * m_maxDecel); if (iter >= 2) { - double contr = dist / (disto + c_tol * c_eta); + const double contr = dist / (disto + c_tol * c_eta); if (contr > c_maxContr) { accept = false; break; @@ -203,7 +203,7 @@ void PathTracer::TracePath( // Obtain the tangent at the next step q.GetRow(q.NumRows(), newT); - double omega_flip = (t * newT < 0.0) ? -1.0 : 1.0; + const double omega_flip = (t * newT < 0.0) ? -1.0 : 1.0; if (omega_flip == -1.0) { // The orientation of the curve has changed, indicating a bifurcation. diff --git a/src/solvers/logit/path.h b/src/solvers/logit/path.h index 5dd1d47dd..18c6c6f87 100644 --- a/src/solvers/logit/path.h +++ b/src/solvers/logit/path.h @@ -65,7 +65,7 @@ inline void NullCallbackFunction(const Vector &) {} // class PathTracer { public: - PathTracer() : m_maxDecel(1.1), m_hStart(0.03) {} + PathTracer() = default; virtual ~PathTracer() = default; void SetMaxDecel(double p_maxDecel) { m_maxDecel = p_maxDecel; } @@ -83,7 +83,7 @@ class PathTracer { CriterionBracketFunctionType p_criterionBracker = NullCriterionBracketFunction) const; private: - double m_maxDecel, m_hStart; + double m_maxDecel{1.1}, m_hStart{0.03}; }; } // end namespace Gambit diff --git a/src/solvers/lp/efglp.cc b/src/solvers/lp/efglp.cc deleted file mode 100644 index 2164cdae5..000000000 --- a/src/solvers/lp/efglp.cc +++ /dev/null @@ -1,218 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/tools/lp/efglp.cc -// Implementation of algorithm to solve efgs via linear programming -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "gambit.h" -#include "solvers/linalg/lpsolve.h" -#include "efglp.h" - -using namespace Gambit; - -template class NashLpBehavSolver::GameData { -public: - int ns1, ns2, ni1, ni2; - Rational minpay; - std::map infosetOffset; - - explicit GameData(const Game &); - - void FillTableau(Matrix &A, const GameNode &n, const T &prob, int s1, int s2); - - void GetBehavior(MixedBehaviorProfile &v, const Array &, const Array &, - const GameNode &, int, int); -}; - -template NashLpBehavSolver::GameData::GameData(const Game &p_game) -{ - ns1 = p_game->GetPlayer(1)->NumSequences(); - ns2 = p_game->GetPlayer(2)->NumSequences(); - ni1 = p_game->GetPlayer(1)->NumInfosets() + 1; - ni2 = p_game->GetPlayer(2)->NumInfosets() + 1; - for (const auto &player : p_game->GetPlayers()) { - int offset = 1; - for (const auto &infoset : player->GetInfosets()) { - infosetOffset[infoset] = offset; - offset += infoset->NumActions(); - } - } - minpay = p_game->GetMinPayoff(); -} - -// -// Recursively fills the constraint matrix A for the subtree rooted at 'n'. -// -template -void NashLpBehavSolver::GameData::FillTableau(Matrix &A, const GameNode &n, const T &prob, - int s1, int s2) -{ - GameOutcome outcome = n->GetOutcome(); - if (outcome) { - A(s1, s2) += Rational(prob) * (static_cast(outcome->GetPayoff(1)) - minpay); - } - if (n->IsTerminal()) { - return; - } - GameInfoset infoset = n->GetInfoset(); - if (n->GetPlayer()->IsChance()) { - for (const auto &action : infoset->GetActions()) { - FillTableau(A, n->GetChild(action), prob * static_cast(infoset->GetActionProb(action)), - s1, s2); - } - } - else if (n->GetPlayer()->GetNumber() == 1) { - int col = ns2 + infoset->GetNumber() + 1; - int snew = infosetOffset.at(infoset); - A(s1, col) = static_cast(1); - for (const auto &child : n->GetChildren()) { - A(++snew, col) = static_cast(-1); - FillTableau(A, child, prob, snew, s2); - } - } - else { - int row = ns1 + infoset->GetNumber() + 1; - int snew = infosetOffset.at(infoset); - A(row, s2) = static_cast(-1); - for (const auto &child : n->GetChildren()) { - A(row, ++snew) = static_cast(1); - FillTableau(A, child, prob, s1, snew); - } - } -} - -// -// The routine to actually solve the LP -// This routine takes an LP of the form -// maximize c x subject to Ax>=b and x>=0, -// except the last 'nequals' constraints in A hold with equality. -// It expects the array p_primal to be the same length as the -// number of columns in A, and the routine returns the primal solution; -// similarly, the array p_dual should have the same length as the -// number of rows in A, and the routine returns the dual solution. -// -// To implement your own custom solver for this problem, simply -// replace this function. -// -template -bool NashLpBehavSolver::SolveLP(const Matrix &A, const Vector &b, const Vector &c, - int nequals, Array &p_primal, Array &p_dual) const -{ - linalg::LPSolve LP(A, b, c, nequals); - const auto &cbfs(LP.OptimumBFS()); - - for (int i = 1; i <= A.NumColumns(); i++) { - p_primal[i] = (cbfs.count(i)) ? cbfs[i] : static_cast(0); - } - for (int i = 1; i <= A.NumRows(); i++) { - p_dual[i] = (cbfs.count(-i)) ? cbfs[-i] : static_cast(0); - } - return true; -} - -// -// Recursively construct the behavior profile from the sequence form -// solution represented by 'p_primal' (containing player 2's -// sequences) and 'p_dual' (containing player 1's sequences). -// -// Any information sets not reached with positive probability have -// their action probabilities set to zero. -// -template -void NashLpBehavSolver::GameData::GetBehavior(MixedBehaviorProfile &v, - const Array &p_primal, const Array &p_dual, - const GameNode &n, int s1, int s2) -{ - if (n->IsTerminal()) { - return; - } - if (n->GetPlayer()->IsChance()) { - for (const auto &child : n->GetChildren()) { - GetBehavior(v, p_primal, p_dual, child, s1, s2); - } - } - else if (n->GetPlayer()->GetNumber() == 2) { - int snew = infosetOffset.at(n->GetInfoset()); - for (const auto &action : n->GetInfoset()->GetActions()) { - snew++; - v[action] = (p_primal[s1] > (T)0) ? p_primal[snew] / p_primal[s1] : (T)0; - GetBehavior(v, p_primal, p_dual, n->GetChild(action), snew, s2); - } - } - else { - int snew = infosetOffset.at(n->GetInfoset()); - for (const auto &action : n->GetInfoset()->GetActions()) { - snew++; - v[action] = (p_dual[s2] > (T)0) ? p_dual[snew] / p_dual[s2] : (T)0; - GetBehavior(v, p_primal, p_dual, n->GetChild(action), s1, snew); - } - } -} - -// -// Compute and print one equilibrium by solving a linear program based -// on the sequence form representation of the game. -// -template -List> NashLpBehavSolver::Solve(const Game &p_game) const -{ - if (p_game->NumPlayers() != 2) { - throw UndefinedException("Method only valid for two-player games."); - } - if (!p_game->IsConstSum()) { - throw UndefinedException("Method only valid for constant-sum games."); - } - if (!p_game->IsPerfectRecall()) { - throw UndefinedException( - "Computing equilibria of games with imperfect recall is not supported."); - } - - Gambit::linalg::BFS cbfs; - - GameData data = GameData(p_game); - - Matrix A(1, data.ns1 + data.ni2, 1, data.ns2 + data.ni1); - Vector b(1, data.ns1 + data.ni2); - Vector c(1, data.ns2 + data.ni1); - - A = (T)0; - b = (T)0; - c = (T)0; - - data.FillTableau(A, p_game->GetRoot(), (T)1, 1, 1); - A(1, data.ns2 + 1) = (T)-1; - A(data.ns1 + 1, 1) = (T)1; - - b[data.ns1 + 1] = (T)1; - c[data.ns2 + 1] = (T)-1; - - Array primal(A.NumColumns()), dual(A.NumRows()); - List> solution; - if (SolveLP(A, b, c, data.ni2, primal, dual)) { - MixedBehaviorProfile profile(p_game); - data.GetBehavior(profile, primal, dual, p_game->GetRoot(), 1, 1); - profile.UndefinedToCentroid(); - this->m_onEquilibrium->Render(profile); - solution.push_back(profile); - } - return solution; -} - -template class NashLpBehavSolver; -template class NashLpBehavSolver; diff --git a/src/solvers/lp/lp.cc b/src/solvers/lp/lp.cc new file mode 100644 index 000000000..5587b01aa --- /dev/null +++ b/src/solvers/lp/lp.cc @@ -0,0 +1,277 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) +// +// FILE: src/tools/lp/efglp.cc +// Implementation of algorithm to solve efgs via linear programming +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#include "gambit.h" +#include "solvers/lp/lp.h" +#include "solvers/linalg/lpsolve.h" + +namespace Gambit::Nash { + +template class GameData { +public: + int ns1, ns2; + Rational minpay; + std::map infosetOffset; + + explicit GameData(const Game &); + + void FillTableau(Matrix &A, const GameNode &n, const T &prob, int s1, int s2); + + void GetBehavior(MixedBehaviorProfile &v, const Array &, const Array &, + const GameNode &, int, int); +}; + +template GameData::GameData(const Game &p_game) : minpay(p_game->GetMinPayoff()) +{ + ns1 = p_game->GetPlayer(1)->NumSequences(); + ns2 = p_game->GetPlayer(2)->NumSequences(); + for (const auto &player : p_game->GetPlayers()) { + int offset = 1; + for (const auto &infoset : player->GetInfosets()) { + infosetOffset[infoset] = offset; + offset += infoset->GetActions().size(); + } + } +} + +// +// Recursively fills the constraint matrix A for the subtree rooted at 'n'. +// +template +void GameData::FillTableau(Matrix &A, const GameNode &n, const T &prob, int s1, int s2) +{ + const GameOutcome outcome = n->GetOutcome(); + if (outcome) { + A(s1, s2) += + Rational(prob) * (outcome->GetPayoff(n->GetGame()->GetPlayer(1)) - minpay); + } + if (n->IsTerminal()) { + return; + } + const GameInfoset infoset = n->GetInfoset(); + if (n->GetPlayer()->IsChance()) { + for (const auto &action : infoset->GetActions()) { + FillTableau(A, n->GetChild(action), prob * static_cast(infoset->GetActionProb(action)), + s1, s2); + } + } + else if (n->GetPlayer()->GetNumber() == 1) { + const int col = ns2 + infoset->GetNumber() + 1; + int snew = infosetOffset.at(infoset); + A(s1, col) = static_cast(1); + for (const auto &child : n->GetChildren()) { + A(++snew, col) = static_cast(-1); + FillTableau(A, child, prob, snew, s2); + } + } + else { + const int row = ns1 + infoset->GetNumber() + 1; + int snew = infosetOffset.at(infoset); + A(row, s2) = static_cast(-1); + for (const auto &child : n->GetChildren()) { + A(row, ++snew) = static_cast(1); + FillTableau(A, child, prob, s1, snew); + } + } +} + +// +// Recursively construct the behavior profile from the sequence form +// solution represented by 'p_primal' (containing player 2's +// sequences) and 'p_dual' (containing player 1's sequences). +// +// Any information sets not reached with positive probability have +// their action probabilities set to zero. +// +template +void GameData::GetBehavior(MixedBehaviorProfile &v, const Array &p_primal, + const Array &p_dual, const GameNode &n, int s1, int s2) +{ + if (n->IsTerminal()) { + return; + } + if (n->GetPlayer()->IsChance()) { + for (const auto &child : n->GetChildren()) { + GetBehavior(v, p_primal, p_dual, child, s1, s2); + } + } + else if (n->GetPlayer()->GetNumber() == 2) { + int snew = infosetOffset.at(n->GetInfoset()); + for (const auto &action : n->GetInfoset()->GetActions()) { + snew++; + v[action] = + (p_primal[s1] > static_cast(0)) ? p_primal[snew] / p_primal[s1] : static_cast(0); + GetBehavior(v, p_primal, p_dual, n->GetChild(action), snew, s2); + } + } + else { + int snew = infosetOffset.at(n->GetInfoset()); + for (const auto &action : n->GetInfoset()->GetActions()) { + snew++; + v[action] = (p_dual[s2] > static_cast(0)) ? p_dual[snew] / p_dual[s2] : static_cast(0); + GetBehavior(v, p_primal, p_dual, n->GetChild(action), s1, snew); + } + } +} + +// +// The routine to actually solve the LP +// This routine takes an LP of the form +// maximize c x subject to Ax>=b and x>=0, +// except the last 'nequals' constraints in A hold with equality. +// It expects the array p_primal to be the same length as the +// number of columns in A, and the routine returns the primal solution; +// similarly, the array p_dual should have the same length as the +// number of rows in A, and the routine returns the dual solution. +// +// To implement your own custom solver for this problem, simply +// replace this function. +// +template +void SolveLP(const Matrix &A, const Vector &b, const Vector &c, int nequals, + Array &p_primal, Array &p_dual) +{ + const linalg::LPSolve LP(A, b, c, nequals); + const auto &cbfs = LP.OptimumBFS(); + + for (size_t i = 1; i <= A.NumColumns(); i++) { + p_primal[i] = (cbfs.count(i)) ? cbfs[i] : static_cast(0); + } + for (size_t i = 1; i <= A.NumRows(); i++) { + p_dual[i] = (cbfs.count(-i)) ? cbfs[-i] : static_cast(0); + } +} + +template +List> LpBehaviorSolve(const Game &p_game, + BehaviorCallbackType p_onEquilibrium) +{ + if (p_game->NumPlayers() != 2) { + throw UndefinedException("Method only valid for two-player games."); + } + if (!p_game->IsConstSum()) { + throw UndefinedException("Method only valid for constant-sum games."); + } + if (!p_game->IsPerfectRecall()) { + throw UndefinedException( + "Computing equilibria of games with imperfect recall is not supported."); + } + + GameData data(p_game); + + Matrix A(1, data.ns1 + p_game->GetPlayer(2)->GetInfosets().size() + 1, 1, + data.ns2 + p_game->GetPlayer(1)->GetInfosets().size() + 1); + Vector b(1, data.ns1 + p_game->GetPlayer(2)->GetInfosets().size() + 1); + Vector c(1, data.ns2 + p_game->GetPlayer(1)->GetInfosets().size() + 1); + + A = static_cast(0); + b = static_cast(0); + c = static_cast(0); + + data.FillTableau(A, p_game->GetRoot(), static_cast(1), 1, 1); + A(1, data.ns2 + 1) = static_cast(-1); + A(data.ns1 + 1, 1) = static_cast(1); + + b[data.ns1 + 1] = static_cast(1); + c[data.ns2 + 1] = static_cast(-1); + + Array primal(A.NumColumns()), dual(A.NumRows()); + List> solution; + SolveLP(A, b, c, p_game->GetPlayer(2)->GetInfosets().size() + 1, primal, dual); + MixedBehaviorProfile profile(p_game); + data.GetBehavior(profile, primal, dual, p_game->GetRoot(), 1, 1); + profile.UndefinedToCentroid(); + p_onEquilibrium(profile, "NE"); + solution.push_back(profile); + return solution; +} + +template List> LpBehaviorSolve(const Game &, + BehaviorCallbackType); +template List> LpBehaviorSolve(const Game &, + BehaviorCallbackType); + +template +List> LpStrategySolve(const Game &p_game, + StrategyCallbackType p_onEquilibrium) +{ + if (p_game->NumPlayers() != 2) { + throw UndefinedException("Method only valid for two-player games."); + } + if (!p_game->IsConstSum()) { + throw UndefinedException("Method only valid for constant-sum games."); + } + if (!p_game->IsPerfectRecall()) { + throw UndefinedException( + "Computing equilibria of games with imperfect recall is not supported."); + } + + const int m = p_game->GetPlayer(1)->GetStrategies().size(); + const int k = p_game->GetPlayer(2)->GetStrategies().size(); + + Matrix A(1, k + 1, 1, m + 1); + Vector b(1, k + 1); + Vector c(1, m + 1); + const PureStrategyProfile profile = p_game->NewPureStrategyProfile(); + + const Rational minpay = p_game->GetMinPayoff() - Rational(1); + + for (int i = 1; i <= k; i++) { + profile->SetStrategy(p_game->GetPlayer(2)->GetStrategy(i)); + for (int j = 1; j <= m; j++) { + profile->SetStrategy(p_game->GetPlayer(1)->GetStrategy(j)); + A(i, j) = minpay - profile->GetPayoff(p_game->GetPlayer(1)); + } + A(i, m + 1) = static_cast(1); + } + for (int j = 1; j <= m; j++) { + A(k + 1, j) = static_cast(1); + } + A(k + 1, m + 1) = static_cast(0); + + b = static_cast(0); + b[k + 1] = static_cast(1); + c = static_cast(0); + c[m + 1] = static_cast(1); + + Array primal(A.NumColumns()), dual(A.NumRows()); + SolveLP(A, b, c, 1, primal, dual); + + MixedStrategyProfile eqm(p_game->NewMixedStrategyProfile(static_cast(0))); + for (int j = 1; j <= m; j++) { + eqm[p_game->GetPlayer(1)->GetStrategy(j)] = primal[j]; + } + for (int j = 1; j <= k; j++) { + eqm[p_game->GetPlayer(2)->GetStrategy(j)] = dual[j]; + } + p_onEquilibrium(eqm, "NE"); + List> solution; + solution.push_back(eqm); + return solution; +} + +template List> LpStrategySolve(const Game &, + StrategyCallbackType); +template List> LpStrategySolve(const Game &, + StrategyCallbackType); + +} // end namespace Gambit::Nash diff --git a/src/solvers/lp/efglp.h b/src/solvers/lp/lp.h similarity index 51% rename from src/solvers/lp/efglp.h rename to src/solvers/lp/lp.h index 87bebe861..5fdc693d9 100644 --- a/src/solvers/lp/efglp.h +++ b/src/solvers/lp/lp.h @@ -20,39 +20,23 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#ifndef LP_EFGLP_H -#define LP_EFGLP_H +#ifndef LP_LP_H +#define LP_LP_H #include "games/nash.h" -using namespace Gambit; -using namespace Gambit::Nash; +namespace Gambit::Nash { -template class NashLpBehavSolver : public BehavSolver { -public: - explicit NashLpBehavSolver(std::shared_ptr> p_onEquilibrium = nullptr) - : BehavSolver(p_onEquilibrium) - { - } - ~NashLpBehavSolver() override = default; +template +List> +LpStrategySolve(const Game &p_game, + StrategyCallbackType p_onEquilibrium = NullStrategyCallback); - List> Solve(const Game &) const override; +template +List> +LpBehaviorSolve(const Game &p_game, + BehaviorCallbackType p_onEquilibrium = NullBehaviorCallback); -private: - class GameData; +}; // namespace Gambit::Nash - virtual bool SolveLP(const Matrix &, const Vector &, const Vector &, int, Array &, - Array &) const; -}; - -inline List> LpBehaviorSolveDouble(const Game &p_game) -{ - return NashLpBehavSolver().Solve(p_game); -} - -inline List> LpBehaviorSolveRational(const Game &p_game) -{ - return NashLpBehavSolver().Solve(p_game); -} - -#endif // LP_EFGLP_H +#endif // LP_LP_H diff --git a/src/solvers/lp/nfglp.cc b/src/solvers/lp/nfglp.cc deleted file mode 100644 index 82884217d..000000000 --- a/src/solvers/lp/nfglp.cc +++ /dev/null @@ -1,135 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/tools/lp/nfglp.cc -// Implementation of algorithm to compute mixed strategy equilibria -// of constant sum normal form games via linear programming -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#include "gambit.h" -#include "solvers/linalg/lpsolve.h" -#include "nfglp.h" - -using namespace Gambit; - -// -// The routine to actually solve the LP -// This routine takes an LP of the form -// maximize c x subject to Ax>=b and x>=0, -// except the last 'nequals' constraints in A hold with equality. -// It expects the array p_primal to be the same length as the -// number of columns in A, and the routine returns the primal solution; -// similarly, the array p_dual should have the same length as the -// number of rows in A, and the routine returns the dual solution. -// -// To implement your own custom solver for this problem, simply -// replace this function. -// -template -bool NashLpStrategySolver::SolveLP(const Matrix &A, const Vector &b, const Vector &c, - int nequals, Array &p_primal, Array &p_dual) const -{ - Gambit::linalg::LPSolve LP(A, b, c, nequals); - const Gambit::linalg::BFS &cbfs(LP.OptimumBFS()); - - for (int i = 1; i <= A.NumColumns(); i++) { - if (cbfs.count(i)) { - p_primal[i] = cbfs[i]; - } - else { - p_primal[i] = (T)0; - } - } - - for (int i = 1; i <= A.NumRows(); i++) { - if (cbfs.count(-i)) { - p_dual[i] = cbfs[-i]; - } - else { - p_dual[i] = (T)0; - } - } - return true; -} - -// -// Compute and print one equilibrium by solving a linear program based -// on the strategic game representation. -// -template -List> NashLpStrategySolver::Solve(const Game &p_game) const -{ - if (p_game->NumPlayers() != 2) { - throw UndefinedException("Method only valid for two-player games."); - } - if (!p_game->IsConstSum()) { - throw UndefinedException("Method only valid for constant-sum games."); - } - if (!p_game->IsPerfectRecall()) { - throw UndefinedException( - "Computing equilibria of games with imperfect recall is not supported."); - } - - int m = p_game->GetPlayer(1)->GetStrategies().size(); - int k = p_game->GetPlayer(2)->GetStrategies().size(); - - Matrix A(1, k + 1, 1, m + 1); - Vector b(1, k + 1); - Vector c(1, m + 1); - PureStrategyProfile profile = p_game->NewPureStrategyProfile(); - - Rational minpay = p_game->GetMinPayoff() - Rational(1); - - for (int i = 1; i <= k; i++) { - profile->SetStrategy(p_game->GetPlayer(2)->GetStrategies()[i]); - for (int j = 1; j <= m; j++) { - profile->SetStrategy(p_game->GetPlayer(1)->GetStrategies()[j]); - A(i, j) = minpay - profile->GetPayoff(1); - } - A(i, m + 1) = (T)1; - } - for (int j = 1; j <= m; j++) { - A(k + 1, j) = (T)1; - } - A(k + 1, m + 1) = (T)0; - - b = (T)0; - b[k + 1] = (T)1; - c = (T)0; - c[m + 1] = (T)1; - - Array primal(A.NumColumns()), dual(A.NumRows()); - if (!SolveLP(A, b, c, 1, primal, dual)) { - return List>(); - } - - MixedStrategyProfile eqm(p_game->NewMixedStrategyProfile(static_cast(0))); - for (int j = 1; j <= m; j++) { - eqm[p_game->GetPlayer(1)->GetStrategies()[j]] = primal[j]; - } - for (int j = 1; j <= k; j++) { - eqm[p_game->GetPlayer(2)->GetStrategies()[j]] = dual[j]; - } - this->m_onEquilibrium->Render(eqm); - List> solution; - solution.push_back(eqm); - return solution; -} - -template class NashLpStrategySolver; -template class NashLpStrategySolver; diff --git a/src/solvers/lp/nfglp.h b/src/solvers/lp/nfglp.h deleted file mode 100644 index 712d1d3b3..000000000 --- a/src/solvers/lp/nfglp.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// This file is part of Gambit -// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) -// -// FILE: src/tools/lcp/nfglp.h -// Compute Nash equilibria via linear programming -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#ifndef LP_NFGLP_H -#define LP_NFGLP_H - -#include "games/nash.h" - -using namespace Gambit; -using namespace Gambit::Nash; - -template class NashLpStrategySolver : public StrategySolver { -public: - NashLpStrategySolver(std::shared_ptr> p_onEquilibrium = nullptr) - : StrategySolver(p_onEquilibrium) - { - } - ~NashLpStrategySolver() override = default; - - List> Solve(const Game &) const override; - -private: - virtual bool SolveLP(const Matrix &, const Vector &, const Vector &, int, Array &, - Array &) const; -}; - -inline List> LpStrategySolveDouble(const Game &p_game) -{ - return NashLpStrategySolver().Solve(p_game); -} - -inline List> LpStrategySolveRational(const Game &p_game) -{ - return NashLpStrategySolver().Solve(p_game); -} - -#endif // LP_NFGLP_H diff --git a/src/solvers/nashsupport/efgsupport.cc b/src/solvers/nashsupport/efgsupport.cc index 8b797d39c..9f8112fc0 100644 --- a/src/solvers/nashsupport/efgsupport.cc +++ b/src/solvers/nashsupport/efgsupport.cc @@ -74,7 +74,7 @@ void PossibleNashBehaviorSupports(const BehaviorSupportProfile &p_support, std::shared_ptr PossibleNashBehaviorSupports(const Game &p_game) { - BehaviorSupportProfile support(p_game); + const BehaviorSupportProfile support(p_game); auto result = std::make_shared(); std::list actions; diff --git a/src/solvers/nashsupport/nfgsupport.cc b/src/solvers/nashsupport/nfgsupport.cc index a58ad338f..9f76fffd7 100644 --- a/src/solvers/nashsupport/nfgsupport.cc +++ b/src/solvers/nashsupport/nfgsupport.cc @@ -123,7 +123,7 @@ class StrategySubsets { public: class iterator { private: - Array m_strategies; + GamePlayerRep::Strategies m_strategies; StrategySupport m_current; std::vector m_include; bool m_end; @@ -132,17 +132,19 @@ class StrategySubsets { void UpdateCurrent() { m_current.clear(); - for (size_t i = 0; i < m_include.size(); i++) { - if (m_include[i]) { - m_current.push_back(m_strategies[i + 1]); + auto strategy = m_strategies.begin(); + for (const auto &value : m_include) { + if (value) { + m_current.push_back(*strategy); } + ++strategy; } } public: using iterator_category = std::forward_iterator_tag; - iterator(const Array &p_strategies, size_t p_size, bool p_end = false) + iterator(const GamePlayerRep::Strategies &p_strategies, size_t p_size, bool p_end = false) : m_strategies(p_strategies), m_include(m_strategies.size()), m_end(p_end) { std::fill(m_include.begin(), m_include.begin() + p_size, true); @@ -243,17 +245,17 @@ PossibleNashStrategySupports(const Game &p_game) auto result = std::make_shared(); auto numActions = p_game->NumStrategies(); - int maxsize = + const int maxsize = std::accumulate(numActions.begin(), numActions.end(), 0) - p_game->NumPlayers() + 1; - int maxdiff = *std::max_element(numActions.cbegin(), numActions.cend()); + const int maxdiff = *std::max_element(numActions.cbegin(), numActions.cend()); - bool preferBalance = p_game->NumPlayers() == 2; + const bool preferBalance = p_game->NumPlayers() == 2; Array dim(2); dim[1] = (preferBalance) ? maxsize : maxdiff; dim[2] = (preferBalance) ? maxdiff : maxsize; CartesianRange range(dim); - std::list> solutions; + const std::list> solutions; for (auto x : range) { GenerateSizeDiff(p_game, ((preferBalance) ? x[1] : x[2]) + p_game->NumPlayers() - 1, ((preferBalance) ? x[2] : x[1]) - 1, diff --git a/src/solvers/simpdiv/simpdiv.cc b/src/solvers/simpdiv/simpdiv.cc index e3d9c8cf0..8ccd3dc43 100644 --- a/src/solvers/simpdiv/simpdiv.cc +++ b/src/solvers/simpdiv/simpdiv.cc @@ -66,13 +66,47 @@ template class PVector { explicit operator const Vector &() const { return m_values; } }; +class NashSimpdivStrategySolver { +public: + explicit NashSimpdivStrategySolver( + int p_gridResize = 2, int p_leashLength = 0, + const Rational &p_maxregret = Rational(1, 1000000), + StrategyCallbackType p_onEquilibrium = NullStrategyCallback) + : m_gridResize(p_gridResize), m_leashLength((p_leashLength > 0) ? p_leashLength : 32000), + m_maxregret(p_maxregret), m_onEquilibrium(p_onEquilibrium) + { + } + ~NashSimpdivStrategySolver() = default; + + List> Solve(const MixedStrategyProfile &p_start) const; + List> Solve(const Game &p_game) const; + +private: + int m_gridResize, m_leashLength; + Rational m_maxregret; + StrategyCallbackType m_onEquilibrium; + + class State; + + Rational Simplex(MixedStrategyProfile &, const Rational &d) const; + static void update(State &, RectArray &, RectArray &, PVector &, + const PVector &, int j, int i); + static void getY(const State &, MixedStrategyProfile &x, PVector &, + const PVector &, const PVector &, const PVector &, + const RectArray &, int k); + static void getnexty(const State &, MixedStrategyProfile &x, const RectArray &, + const PVector &, int i); + static int get_c(int j, int h, int nstrats, const PVector &); + static int get_b(int j, int h, int nstrats, const PVector &); +}; + //------------------------------------------------------------------------- // NashSimpdivStrategySolver: Private member functions //------------------------------------------------------------------------- inline GameStrategy GetStrategy(const Game &game, int pl, int st) { - return game->GetPlayer(pl)->GetStrategies()[st]; + return game->GetPlayer(pl)->GetStrategy(st); } class NashSimpdivStrategySolver::State { @@ -99,7 +133,7 @@ class NashSimpdivStrategySolver::State { Rational NashSimpdivStrategySolver::Simplex(MixedStrategyProfile &y, const Rational &d) const { - Game game = y.GetGame(); + const Game game = y.GetGame(); State state(m_leashLength); state.d = d; Array nstrats(game->NumStrategies()); @@ -120,13 +154,13 @@ Rational NashSimpdivStrategySolver::Simplex(MixedStrategyProfile &y, TT = 0; U = 0; ab = Rational(0); - for (j = 1; j <= game->NumPlayers(); j++) { - GamePlayer player = game->GetPlayer(j); + for (j = 1; j <= static_cast(game->NumPlayers()); j++) { + const GamePlayer player = game->GetPlayer(j); for (h = 1; h <= nstrats[j]; h++) { if (v(j, h) == Rational(0)) { U(j, h) = 1; } - y[player->GetStrategies()[h]] = v(j, h); + y[player->GetStrategy(h)] = v(j, h); } } @@ -334,7 +368,7 @@ Rational NashSimpdivStrategySolver::Simplex(MixedStrategyProfile &y, end: maxz = state.bestz; - for (i = 1; i <= game->NumPlayers(); i++) { + for (i = 1; i <= static_cast(game->NumPlayers()); i++) { for (j = 1; j <= nstrats[i]; j++) { y[GetStrategy(game, i, j)] = besty(i, j); } @@ -399,13 +433,13 @@ void NashSimpdivStrategySolver::getY(const State &state, MixedStrategyProfile &pi, int k) { x = static_cast &>(v); - for (int j = 1; j <= x.GetGame()->NumPlayers(); j++) { - GamePlayer player = x.GetGame()->GetPlayer(j); + for (int j = 1; j <= static_cast(x.GetGame()->NumPlayers()); j++) { + const GamePlayer player = x.GetGame()->GetPlayer(j); for (size_t h = 1; h <= player->GetStrategies().size(); h++) { if (TT(j, h) == 1 || U(j, h) == 1) { - x[player->GetStrategies()[h]] += state.d * ab(j, h); - int hh = (h > 1) ? h - 1 : player->GetStrategies().size(); - x[player->GetStrategies()[hh]] -= state.d * ab(j, h); + x[player->GetStrategy(h)] += state.d * ab(j, h); + const int hh = (h > 1) ? h - 1 : player->GetStrategies().size(); + x[player->GetStrategy(hh)] -= state.d * ab(j, h); } } } @@ -417,12 +451,12 @@ void NashSimpdivStrategySolver::getY(const State &state, MixedStrategyProfile &x, const RectArray &pi, const PVector &U, int i) { - int j = pi(i, 1); - GamePlayer player = x.GetGame()->GetPlayer(j); - int h = pi(i, 2); - x[player->GetStrategies()[h]] += state.d; - int hh = get_b(j, h, player->GetStrategies().size(), U); - x[player->GetStrategies()[hh]] -= state.d; + const int j = pi(i, 1); + const GamePlayer player = x.GetGame()->GetPlayer(j); + const int h = pi(i, 2); + x[player->GetStrategy(h)] += state.d; + const int hh = get_b(j, h, player->GetStrategies().size(), U); + x[player->GetStrategy(hh)] -= state.d; } int NashSimpdivStrategySolver::get_b(int j, int h, int nstrats, const PVector &U) @@ -439,7 +473,7 @@ int NashSimpdivStrategySolver::get_b(int j, int h, int nstrats, const PVector &U) { - int hh = get_b(j, h, nstrats, U) + 1; + const int hh = get_b(j, h, nstrats, U) + 1; return (hh > nstrats) ? 1 : hh; } @@ -450,14 +484,14 @@ Rational NashSimpdivStrategySolver::State::getlabel(MixedStrategyProfileNumPlayers(); i++) { - GamePlayer player = yy.GetGame()->GetPlayer(i); + for (int i = 1; i <= static_cast(yy.GetGame()->NumPlayers()); i++) { + const GamePlayer player = yy.GetGame()->GetPlayer(i); Rational payoff(0); Rational maxval(-1000000); int jj = 0; for (size_t j = 1; j <= player->GetStrategies().size(); j++) { - pay = yy.GetPayoff(player->GetStrategies()[j]); - payoff += yy[player->GetStrategies()[j]] * pay; + pay = yy.GetPayoff(player->GetStrategy(j)); + payoff += yy[player->GetStrategy(j)] * pay; if (pay > maxval) { maxval = pay; jj = j; @@ -471,10 +505,10 @@ Rational NashSimpdivStrategySolver::State::getlabel(MixedStrategyProfileNumPlayers(); i++) { - GamePlayer player = yy.GetGame()->GetPlayer(i); + for (int i = 1; i <= static_cast(yy.GetGame()->NumPlayers()); i++) { + const GamePlayer player = yy.GetGame()->GetPlayer(i); for (size_t j = 1; j <= player->GetStrategies().size(); j++) { - besty(i, j) = yy[player->GetStrategies()[j]]; + besty(i, j) = yy[player->GetStrategy(j)]; } } } @@ -502,26 +536,21 @@ NashSimpdivStrategySolver::Solve(const MixedStrategyProfile &p_start) "Computing equilibria of games with imperfect recall is not supported."); } Rational d(Integer(1), find_lcd(static_cast &>(p_start))); - Rational scale = p_start.GetGame()->GetMaxPayoff() - p_start.GetGame()->GetMinPayoff(); + const Rational scale = p_start.GetGame()->GetMaxPayoff() - p_start.GetGame()->GetMinPayoff(); MixedStrategyProfile y(p_start); - if (m_verbose) { - this->m_onEquilibrium->Render(y, "start"); - } + m_onEquilibrium(y, "start"); while (true) { d /= Rational(m_gridResize); - Rational regret = Simplex(y, d); - - if (m_verbose) { - this->m_onEquilibrium->Render(y, std::to_string(d)); - } + const Rational regret = Simplex(y, d); + m_onEquilibrium(y, std::to_string(d)); if (regret <= m_maxregret * scale) { break; } } - this->m_onEquilibrium->Render(y); + m_onEquilibrium(y, "NE"); List> sol; sol.push_back(y); return sol; @@ -543,10 +572,19 @@ List> NashSimpdivStrategySolver::Solve(const Game { MixedStrategyProfile start = p_game->NewMixedStrategyProfile(Rational(0)); start = Rational(0); - for (int pl = 1; pl <= p_game->NumPlayers(); pl++) { - start[p_game->GetPlayer(pl)->GetStrategies()[1]] = Rational(1); + for (const auto &player : p_game->GetPlayers()) { + start[player->GetStrategies().front()] = Rational(1); } return Solve(start); } +List> +SimpdivStrategySolve(const MixedStrategyProfile &p_start, const Rational &p_maxregret, + int p_gridResize, int p_leashLength, + StrategyCallbackType p_onEquilibrium) +{ + return NashSimpdivStrategySolver(p_gridResize, p_leashLength, p_maxregret, p_onEquilibrium) + .Solve(p_start); +} + } // end namespace Gambit::Nash diff --git a/src/solvers/simpdiv/simpdiv.h b/src/solvers/simpdiv/simpdiv.h index 4d67fbb01..8745354de 100644 --- a/src/solvers/simpdiv/simpdiv.h +++ b/src/solvers/simpdiv/simpdiv.h @@ -27,54 +27,16 @@ namespace Gambit::Nash { -template class PVector; /// /// This is a simplicial subdivision algorithm with restart, for finding /// mixed strategy solutions to general finite n-person games. It is based on /// van Der Laan, Talman and van Der Heyden, Math of Oper Res, 1987. /// -class NashSimpdivStrategySolver : public StrategySolver { -public: - explicit NashSimpdivStrategySolver( - int p_gridResize = 2, int p_leashLength = 0, - const Rational &p_maxregret = Rational(1, 1000000), bool p_verbose = false, - std::shared_ptr> p_onEquilibrium = nullptr) - : StrategySolver(p_onEquilibrium), m_gridResize(p_gridResize), - m_leashLength((p_leashLength > 0) ? p_leashLength : 32000), m_maxregret(p_maxregret), - m_verbose(p_verbose) - { - } - ~NashSimpdivStrategySolver() override = default; - - List> Solve(const MixedStrategyProfile &p_start) const; - List> Solve(const Game &p_game) const override; - -private: - int m_gridResize, m_leashLength; - Rational m_maxregret; - bool m_verbose; - - class State; - - Rational Simplex(MixedStrategyProfile &, const Rational &d) const; - static void update(State &, RectArray &, RectArray &, PVector &, - const PVector &, int j, int i); - static void getY(const State &, MixedStrategyProfile &x, PVector &, - const PVector &, const PVector &, const PVector &, - const RectArray &, int k); - static void getnexty(const State &, MixedStrategyProfile &x, const RectArray &, - const PVector &, int i); - static int get_c(int j, int h, int nstrats, const PVector &); - static int get_b(int j, int h, int nstrats, const PVector &); -}; - -inline List> -SimpdivStrategySolve(const MixedStrategyProfile &p_start, - const Rational &p_maxregret = Rational(1, 1000000), int p_gridResize = 2, - int p_leashLength = 0) -{ - return NashSimpdivStrategySolver(p_gridResize, p_leashLength, p_maxregret).Solve(p_start); -} +List> SimpdivStrategySolve( + const MixedStrategyProfile &p_start, + const Rational &p_maxregret = Rational(1, 1000000), int p_gridResize = 2, + int p_leashLength = 0, + StrategyCallbackType p_onEquilibrium = NullStrategyCallback); } // end namespace Gambit::Nash diff --git a/src/tools/convert/convert.cc b/src/tools/convert/convert.cc index e9b1ad19f..ea4497c24 100644 --- a/src/tools/convert/convert.cc +++ b/src/tools/convert/convert.cc @@ -122,13 +122,13 @@ int main(int argc, char *argv[]) } try { - Gambit::Game game = Gambit::ReadGame(*input_stream); + const Gambit::Game game = Gambit::ReadGame(*input_stream); - if (rowPlayer < 1 || rowPlayer > game->NumPlayers()) { + if (rowPlayer < 1 || rowPlayer > static_cast(game->NumPlayers())) { std::cerr << argv[0] << ": Player " << rowPlayer << " does not exist.\n"; return 1; } - if (colPlayer < 1 || colPlayer > game->NumPlayers()) { + if (colPlayer < 1 || colPlayer > static_cast(game->NumPlayers())) { std::cerr << argv[0] << ": Player " << colPlayer << " does not exist.\n"; return 1; } diff --git a/src/tools/enummixed/enummixed.cc b/src/tools/enummixed/enummixed.cc index a7043a9ec..2212bc104 100644 --- a/src/tools/enummixed/enummixed.cc +++ b/src/tools/enummixed/enummixed.cc @@ -26,6 +26,7 @@ #include #include "gambit.h" +#include "tools/util.h" #include "solvers/enummixed/enummixed.h" using namespace Gambit; @@ -59,7 +60,6 @@ void PrintHelp(char *progname) std::cerr << "Options:\n"; std::cerr << " -d DECIMALS compute using floating-point arithmetic;\n"; std::cerr << " display results with DECIMALS digits\n"; - std::cerr << " -D don't eliminate dominated strategies first\n"; std::cerr << " -c output connectedness information\n"; std::cerr << " -h, --help print this help message\n"; std::cerr << " -q quiet mode (suppresses banner)\n"; @@ -70,14 +70,14 @@ void PrintHelp(char *progname) int main(int argc, char *argv[]) { int c; - bool useFloat = false, uselrs = false, quiet = false, eliminate = true; + bool useFloat = false, quiet = false; bool showConnect = false; int numDecimals = 6; int long_opt_index = 0; struct option long_options[] = { {"help", 0, nullptr, 'h'}, {"version", 0, nullptr, 'v'}, {nullptr, 0, nullptr, 0}}; - while ((c = getopt_long(argc, argv, "d:DvhqcS", long_options, &long_opt_index)) != -1) { + while ((c = getopt_long(argc, argv, "d:vhqcS", long_options, &long_opt_index)) != -1) { switch (c) { case 'v': PrintBanner(std::cerr); @@ -86,9 +86,6 @@ int main(int argc, char *argv[]) useFloat = true; numDecimals = atoi(optarg); break; - case 'D': - eliminate = false; - break; case 'h': PrintHelp(argv[0]); break; @@ -131,25 +128,27 @@ int main(int argc, char *argv[]) } try { - Game game = ReadGame(*input_stream); + const Game game = ReadGame(*input_stream); if (useFloat) { - std::shared_ptr> renderer( - new MixedStrategyCSVRenderer(std::cout, numDecimals)); - EnumMixedStrategySolver solver(renderer); - std::shared_ptr> solution = solver.SolveDetailed(game); + std::shared_ptr> renderer = + std::make_shared>(std::cout, numDecimals); + auto solution = EnumMixedStrategySolveDetailed( + game, [&](const MixedStrategyProfile &p, const std::string &label) { + renderer->Render(p, label); + }); if (showConnect) { - List>> cliques = solution->GetCliques(); - PrintCliques(cliques, renderer); + PrintCliques(solution->GetCliques(), renderer); } } else { std::shared_ptr> renderer( new MixedStrategyCSVRenderer(std::cout)); - EnumMixedStrategySolver solver(renderer); - std::shared_ptr> solution = solver.SolveDetailed(game); + auto solution = EnumMixedStrategySolveDetailed( + game, [&](const MixedStrategyProfile &p, const std::string &label) { + renderer->Render(p, label); + }); if (showConnect) { - List>> cliques = solution->GetCliques(); - PrintCliques(cliques, renderer); + PrintCliques(solution->GetCliques(), renderer); } } return 0; diff --git a/src/tools/enumpoly/enumpoly.cc b/src/tools/enumpoly/enumpoly.cc index 704055912..d5e7d887b 100644 --- a/src/tools/enumpoly/enumpoly.cc +++ b/src/tools/enumpoly/enumpoly.cc @@ -190,7 +190,7 @@ int main(int argc, char *argv[]) } try { - Gambit::Game game = Gambit::ReadGame(*input_stream); + const Gambit::Game game = Gambit::ReadGame(*input_stream); if (!game->IsPerfectRecall()) { throw Gambit::UndefinedException( "Computing equilibria of games with imperfect recall is not supported."); diff --git a/src/tools/enumpure/enumpure.cc b/src/tools/enumpure/enumpure.cc index 3069b19fc..5ace4e5ca 100644 --- a/src/tools/enumpure/enumpure.cc +++ b/src/tools/enumpure/enumpure.cc @@ -26,6 +26,7 @@ #include #include "gambit.h" +#include "tools/util.h" #include "solvers/enumpure/enumpure.h" using namespace Gambit; @@ -119,7 +120,7 @@ int main(int argc, char *argv[]) } try { - Game game = ReadGame(*input_stream); + const Game game = ReadGame(*input_stream); std::shared_ptr> renderer; if (reportStrategic || !game->IsTree()) { if (printDetail) { @@ -140,31 +141,34 @@ int main(int argc, char *argv[]) if (game->IsTree()) { if (bySubgames) { - std::shared_ptr> stage; + BehaviorSolverType func; if (solveAgent) { - stage = std::make_shared(); + func = [&](const Game &g) { return EnumPureAgentSolve(g); }; } else { - std::shared_ptr> substage(new EnumPureStrategySolver()); - stage = std::make_shared>(substage); + func = [&](const Game &g) { + return ToMixedBehaviorProfile(EnumPureStrategySolve(g)); + }; } - SubgameBehavSolver algorithm(stage, renderer); - algorithm.Solve(game); + SolveBySubgames(game, func, + [&](const MixedBehaviorProfile &p, + const std::string &label) { renderer->Render(p, label); }); } else { if (solveAgent) { - EnumPureAgentSolver algorithm(renderer); - algorithm.Solve(game); + EnumPureAgentSolve(game, [&](const MixedBehaviorProfile &p, + const std::string &label) { renderer->Render(p, label); }); } else { - EnumPureStrategySolver algorithm(renderer); - algorithm.Solve(game); + EnumPureStrategySolve(game, + [&](const MixedStrategyProfile &p, + const std::string &label) { renderer->Render(p, label); }); } } } else { - EnumPureStrategySolver algorithm(renderer); - algorithm.Solve(game); + EnumPureStrategySolve(game, [&](const MixedStrategyProfile &p, + const std::string &label) { renderer->Render(p, label); }); } return 0; } diff --git a/src/tools/gt/nfggnm.cc b/src/tools/gt/nfggnm.cc index 98b0a2c1d..1e449eea5 100644 --- a/src/tools/gt/nfggnm.cc +++ b/src/tools/gt/nfggnm.cc @@ -25,6 +25,7 @@ #include #include #include "gambit.h" +#include "tools/util.h" #include "solvers/gnm/gnm.h" using namespace Gambit; @@ -171,8 +172,8 @@ int main(int argc, char *argv[]) } try { - Game game = ReadGame(*input_stream); - std::shared_ptr> renderer( + const Game game = ReadGame(*input_stream); + const std::shared_ptr> renderer( new MixedStrategyCSVRenderer(std::cout, numDecimals)); List> perts; diff --git a/src/tools/gt/nfgipa.cc b/src/tools/gt/nfgipa.cc index bb0321e7f..efc648c07 100644 --- a/src/tools/gt/nfgipa.cc +++ b/src/tools/gt/nfgipa.cc @@ -24,6 +24,7 @@ #include #include #include "gambit.h" +#include "tools/util.h" #include "solvers/ipa/ipa.h" using namespace Gambit; @@ -53,9 +54,7 @@ void PrintHelp(char *progname) std::cerr << " -d DECIMALS show equilibria as floating point with DECIMALS digits\n"; std::cerr << " -h, --help print this help message\n"; std::cerr << " -n COUNT number of perturbation vectors to generate\n"; - std::cerr << " -s FILE file containing perturbation vectors\n"; std::cerr << " -q quiet mode (suppresses banner)\n"; - std::cerr << " -V, --verbose verbose mode (shows intermediate output)\n"; std::cerr << " -v, --version print version information\n"; exit(1); } @@ -63,17 +62,15 @@ void PrintHelp(char *progname) int main(int argc, char *argv[]) { opterr = 0; - bool quiet = false, verbose = false; + bool quiet = false; int numDecimals = 6, numVectors = 1; - std::string startFile; + const std::string startFile; int long_opt_index = 0; - struct option long_options[] = {{"help", 0, nullptr, 'h'}, - {"version", 0, nullptr, 'v'}, - {"verbose", 0, nullptr, 'V'}, - {nullptr, 0, nullptr, 0}}; + struct option long_options[] = { + {"help", 0, nullptr, 'h'}, {"version", 0, nullptr, 'v'}, {nullptr, 0, nullptr, 0}}; int c; - while ((c = getopt_long(argc, argv, "d:n:s:vVqhS", long_options, &long_opt_index)) != -1) { + while ((c = getopt_long(argc, argv, "d:n:vqh", long_options, &long_opt_index)) != -1) { switch (c) { case 'v': PrintBanner(std::cerr); @@ -81,17 +78,12 @@ int main(int argc, char *argv[]) case 'q': quiet = true; break; - case 'V': - verbose = true; - break; case 'd': numDecimals = atoi(optarg); break; case 'n': numVectors = atoi(optarg); break; - case 'S': - break; case 'h': PrintHelp(argv[0]); break; @@ -126,8 +118,8 @@ int main(int argc, char *argv[]) } try { - Game game = ReadGame(*input_stream); - std::shared_ptr> renderer( + const Game game = ReadGame(*input_stream); + const std::shared_ptr> renderer( new MixedStrategyCSVRenderer(std::cout, numDecimals)); List> perts; @@ -141,9 +133,9 @@ int main(int argc, char *argv[]) } for (auto pert : perts) { - IPAStrategySolve(pert, [renderer, verbose](const MixedStrategyProfile &p_profile, - const std::string &p_label) { - if (p_label == "NE" || verbose) { + IPAStrategySolve(pert, [renderer](const MixedStrategyProfile &p_profile, + const std::string &p_label) { + if (p_label == "NE") { renderer->Render(p_profile, p_label); } }); diff --git a/src/tools/lcp/lcp.cc b/src/tools/lcp/lcp.cc index bbd2658c5..76eba3aec 100644 --- a/src/tools/lcp/lcp.cc +++ b/src/tools/lcp/lcp.cc @@ -26,6 +26,7 @@ #include #include #include "gambit.h" +#include "tools/util.h" #include "solvers/lcp/lcp.h" using namespace Gambit; @@ -132,7 +133,7 @@ int main(int argc, char *argv[]) } try { - Game game = ReadGame(*input_stream); + const Game game = ReadGame(*input_stream); if (!game->IsTree() || useStrategic) { if (useFloat) { std::shared_ptr> renderer; @@ -142,8 +143,9 @@ int main(int argc, char *argv[]) else { renderer = std::make_shared>(std::cout, numDecimals); } - NashLcpStrategySolver algorithm(stopAfter, maxDepth, renderer); - algorithm.Solve(game); + LcpStrategySolve(game, stopAfter, maxDepth, + [&](const MixedStrategyProfile &p, + const std::string &label) { renderer->Render(p, label); }); } else { std::shared_ptr> renderer; @@ -153,8 +155,9 @@ int main(int argc, char *argv[]) else { renderer = std::make_shared>(std::cout); } - NashLcpStrategySolver algorithm(stopAfter, maxDepth, renderer); - algorithm.Solve(game); + LcpStrategySolve(game, stopAfter, maxDepth, + [&](const MixedStrategyProfile &p, + const std::string &label) { renderer->Render(p, label); }); } } else { @@ -168,8 +171,9 @@ int main(int argc, char *argv[]) else { renderer = std::make_shared>(std::cout, numDecimals); } - NashLcpBehaviorSolver algorithm(stopAfter, maxDepth, renderer); - algorithm.Solve(game); + LcpBehaviorSolve(game, stopAfter, maxDepth, + [&](const MixedBehaviorProfile &p, + const std::string &label) { renderer->Render(p, label); }); } else { std::shared_ptr> renderer; @@ -179,14 +183,15 @@ int main(int argc, char *argv[]) else { renderer = std::make_shared>(std::cout); } - NashLcpBehaviorSolver algorithm(stopAfter, maxDepth, renderer); - algorithm.Solve(game); + LcpBehaviorSolve( + game, stopAfter, maxDepth, + [&](const MixedBehaviorProfile &p, const std::string &label) { + renderer->Render(p, label); + }); } } else { if (useFloat) { - std::shared_ptr> stage( - new NashLcpBehaviorSolver(stopAfter, maxDepth)); std::shared_ptr> renderer; if (printDetail) { renderer = @@ -195,12 +200,18 @@ int main(int argc, char *argv[]) else { renderer = std::make_shared>(std::cout, numDecimals); } - SubgameBehavSolver algorithm(stage, renderer); - algorithm.Solve(game); + const BehaviorSolverType func = [&](const Game &g) { + return LcpBehaviorSolve( + g, stopAfter, maxDepth, + [&](const MixedBehaviorProfile &p, const std::string &label) { + renderer->Render(p, label); + }); + }; + SolveBySubgames(game, func, + [&](const MixedBehaviorProfile &p, + const std::string &label) { renderer->Render(p, label); }); } else { - std::shared_ptr> stage( - new NashLcpBehaviorSolver(stopAfter, maxDepth)); std::shared_ptr> renderer; if (printDetail) { renderer = @@ -210,8 +221,16 @@ int main(int argc, char *argv[]) renderer = std::make_shared>(std::cout, numDecimals); } - SubgameBehavSolver algorithm(stage, renderer); - algorithm.Solve(game); + const BehaviorSolverType func = [&](const Game &g) { + return LcpBehaviorSolve( + g, stopAfter, maxDepth, + [&](const MixedBehaviorProfile &p, const std::string &label) { + renderer->Render(p, label); + }); + }; + SolveBySubgames(game, func, + [&](const MixedBehaviorProfile &p, + const std::string &label) { renderer->Render(p, label); }); } } } diff --git a/src/tools/liap/liap.cc b/src/tools/liap/liap.cc index 02c43b2f5..4baebb0cc 100644 --- a/src/tools/liap/liap.cc +++ b/src/tools/liap/liap.cc @@ -12,7 +12,7 @@ // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FO fR A PARTICULAR PURPOSE. See the +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License @@ -25,6 +25,7 @@ #include #include #include "gambit.h" +#include "tools/util.h" #include "solvers/liap/liap.h" using namespace Gambit; @@ -128,12 +129,11 @@ List> RandomBehaviorProfiles(const Game &p_game, in int main(int argc, char *argv[]) { opterr = 0; - bool quiet = false, useStrategic = false, useRandom = false, verbose = false; - int numTries = 10; + bool quiet = false, useStrategic = false, verbose = false; + const int numTries = 10; int maxitsN = 1000; int numDecimals = 6; double maxregret = 1.0e-4; - double tolN = 1.0e-10; std::string startFile; int long_opt_index = 0; @@ -202,7 +202,7 @@ int main(int argc, char *argv[]) } try { - Game game = ReadGame(*input_stream); + const Game game = ReadGame(*input_stream); if (!game->IsTree() || useStrategic) { List> starts; if (!startFile.empty()) { @@ -215,7 +215,7 @@ int main(int argc, char *argv[]) } for (size_t i = 1; i <= starts.size(); i++) { - std::shared_ptr> renderer( + const std::shared_ptr> renderer( new MixedStrategyCSVRenderer(std::cout, numDecimals)); LiapStrategySolve(starts[i], maxregret, maxitsN, @@ -239,7 +239,7 @@ int main(int argc, char *argv[]) } for (size_t i = 1; i <= starts.size(); i++) { - std::shared_ptr> renderer( + const std::shared_ptr> renderer( new BehavStrategyCSVRenderer(std::cout, numDecimals)); LiapBehaviorSolve(starts[i], maxregret, maxitsN, [renderer, verbose](const MixedBehaviorProfile &p_profile, diff --git a/src/tools/logit/logit.cc b/src/tools/logit/logit.cc index fa413eb41..b8663ef7d 100644 --- a/src/tools/logit/logit.cc +++ b/src/tools/logit/logit.cc @@ -110,7 +110,7 @@ int main(int argc, char *argv[]) opterr = 0; bool quiet = false, useStrategic = false; - double maxLambda = 1000000.0; + const double maxLambda = 1000000.0; double maxregret = 1.0e-8; std::string mleFile; double maxDecel = 1.1; @@ -190,7 +190,7 @@ int main(int argc, char *argv[]) } try { - Game game = ReadGame(*input_stream); + const Game game = ReadGame(*input_stream); if (!game->IsPerfectRecall()) { throw UndefinedException( "Computing equilibria of games with imperfect recall is not supported."); @@ -218,7 +218,7 @@ int main(int argc, char *argv[]) PrintProfile(std::cout, decimals, p); } }; - LogitQREMixedStrategyProfile start(game); + const LogitQREMixedStrategyProfile start(game); if (!targetLambda.empty()) { auto result = LogitStrategySolveLambda(start, targetLambda, 1.0, hStart, maxDecel, printer); @@ -237,7 +237,7 @@ int main(int argc, char *argv[]) PrintProfile(std::cout, decimals, p); } }; - LogitQREMixedBehaviorProfile start(game); + const LogitQREMixedBehaviorProfile start(game); if (!targetLambda.empty()) { auto result = LogitBehaviorSolveLambda(start, targetLambda, 1.0, hStart, maxDecel, printer); diff --git a/src/tools/lp/lp.cc b/src/tools/lp/lp.cc index 7a37e1aeb..a567e1de6 100644 --- a/src/tools/lp/lp.cc +++ b/src/tools/lp/lp.cc @@ -27,10 +27,11 @@ #include #include #include "gambit.h" -#include "solvers/lp/efglp.h" -#include "solvers/lp/nfglp.h" +#include "tools/util.h" +#include "solvers/lp/lp.h" using namespace Gambit; +using namespace Gambit::Nash; void PrintBanner(std::ostream &p_stream) { @@ -122,7 +123,7 @@ int main(int argc, char *argv[]) } try { - Gambit::Game game = Gambit::ReadGame(*input_stream); + const Gambit::Game game = Gambit::ReadGame(*input_stream); if (!game->IsTree() || useStrategic) { if (useFloat) { std::shared_ptr> renderer; @@ -132,8 +133,9 @@ int main(int argc, char *argv[]) else { renderer = std::make_shared>(std::cout, numDecimals); } - NashLpStrategySolver algorithm(renderer); - algorithm.Solve(game); + LpStrategySolve(game, + [&](const MixedStrategyProfile &p, + const std::string &label) { renderer->Render(p, label); }); } else { std::shared_ptr> renderer; @@ -143,8 +145,9 @@ int main(int argc, char *argv[]) else { renderer = std::make_shared>(std::cout); } - NashLpStrategySolver algorithm(renderer); - algorithm.Solve(game); + LpStrategySolve(game, + [&](const MixedStrategyProfile &p, + const std::string &label) { renderer->Render(p, label); }); } } else { @@ -158,8 +161,9 @@ int main(int argc, char *argv[]) else { renderer = std::make_shared>(std::cout, numDecimals); } - NashLpBehavSolver algorithm(renderer); - algorithm.Solve(game); + LpBehaviorSolve(game, + [&](const MixedBehaviorProfile &p, + const std::string &label) { renderer->Render(p, label); }); } else { std::shared_ptr> renderer; @@ -169,13 +173,13 @@ int main(int argc, char *argv[]) else { renderer = std::make_shared>(std::cout); } - NashLpBehavSolver algorithm(renderer); - algorithm.Solve(game); + LpBehaviorSolve(game, + [&](const MixedBehaviorProfile &p, + const std::string &label) { renderer->Render(p, label); }); } } else { if (useFloat) { - std::shared_ptr> stage(new NashLpBehavSolver()); std::shared_ptr> renderer; if (printDetail) { renderer = @@ -184,11 +188,17 @@ int main(int argc, char *argv[]) else { renderer = std::make_shared>(std::cout, numDecimals); } - SubgameBehavSolver algorithm(stage, renderer); - algorithm.Solve(game); + const BehaviorSolverType func = [&](const Game &g) { + return LpBehaviorSolve( + g, [&](const MixedBehaviorProfile &p, const std::string &label) { + renderer->Render(p, label); + }); + }; + SolveBySubgames(game, func, + [&](const MixedBehaviorProfile &p, + const std::string &label) { renderer->Render(p, label); }); } else { - std::shared_ptr> stage(new NashLpBehavSolver()); std::shared_ptr> renderer; if (printDetail) { renderer = @@ -198,8 +208,15 @@ int main(int argc, char *argv[]) renderer = std::make_shared>(std::cout, numDecimals); } - SubgameBehavSolver algorithm(stage, renderer); - algorithm.Solve(game); + const BehaviorSolverType func = [&](const Game &g) { + return LpBehaviorSolve( + g, [&](const MixedBehaviorProfile &p, const std::string &label) { + renderer->Render(p, label); + }); + }; + SolveBySubgames(game, func, + [&](const MixedBehaviorProfile &p, + const std::string &label) { renderer->Render(p, label); }); } } } diff --git a/src/tools/simpdiv/nfgsimpdiv.cc b/src/tools/simpdiv/nfgsimpdiv.cc index 378487cd0..60239929e 100644 --- a/src/tools/simpdiv/nfgsimpdiv.cc +++ b/src/tools/simpdiv/nfgsimpdiv.cc @@ -25,7 +25,7 @@ #include #include #include "gambit.h" -#include "games/nash.h" +#include "tools/util.h" #include "solvers/simpdiv/simpdiv.h" using namespace Gambit; @@ -203,7 +203,7 @@ int main(int argc, char *argv[]) } try { - Game game = ReadGame(*input_stream); + const Game game = ReadGame(*input_stream); List> starts; if (!startFile.empty()) { std::ifstream startPoints(startFile.c_str()); @@ -214,21 +214,31 @@ int main(int argc, char *argv[]) } else { starts.push_back(game->NewMixedStrategyProfile(Rational(0))); - starts[1] = Rational(0); - for (int pl = 1; pl <= game->NumPlayers(); pl++) { - starts[1][game->GetPlayer(pl)->GetStrategies()[1]] = Rational(1); + starts.back() = Rational(0); + for (const auto &player : game->GetPlayers()) { + starts.back()[player->GetStrategies().back()] = Rational(1); } } for (auto start : starts) { if (decimals > 0) { auto renderer = std::make_shared(std::cout, decimals); - NashSimpdivStrategySolver algorithm(gridResize, 0, maxregret, verbose, renderer); - algorithm.Solve(start); + SimpdivStrategySolve( + start, maxregret, gridResize, 0, + [&](const MixedStrategyProfile &p, const std::string &label) { + if (label == "NE" || verbose) { + renderer->Render(p, label); + } + }); } else { auto renderer = std::make_shared>(std::cout); - NashSimpdivStrategySolver algorithm(gridResize, 0, maxregret, verbose, renderer); - algorithm.Solve(start); + SimpdivStrategySolve( + start, maxregret, gridResize, 0, + [&](const MixedStrategyProfile &p, const std::string &label) { + if (label == "NE" || verbose) { + renderer->Render(p, label); + } + }); } } return 0; diff --git a/src/tools/util.h b/src/tools/util.h new file mode 100644 index 000000000..56419b188 --- /dev/null +++ b/src/tools/util.h @@ -0,0 +1,245 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2025, The Gambit Project (https://www.gambit-project.org) +// +// FILE: src/tools/util.h +// Utility functions common to command-line tools +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#ifndef TOOLS_UTIL_H +#define TOOLS_UTIL_H + +#include "gambit.h" + +namespace Gambit { + +template class StrategyProfileRenderer { +public: + virtual ~StrategyProfileRenderer() = default; + virtual void Render(const MixedStrategyProfile &p_profile, + const std::string &p_label = "NE") const = 0; + virtual void Render(const MixedBehaviorProfile &p_profile, + const std::string &p_label = "NE") const = 0; +}; + +// +// Encapsulates the rendering of a mixed strategy profile to various text formats. +// Implements automatic conversion of behavior strategy profiles to mixed +// strategy profiles. +// +template class MixedStrategyRenderer : public StrategyProfileRenderer { +public: + ~MixedStrategyRenderer() override = default; + void Render(const MixedStrategyProfile &p_profile, + const std::string &p_label = "NE") const override = 0; + void Render(const MixedBehaviorProfile &p_profile, + const std::string &p_label = "NE") const override + { + Render(p_profile.ToMixedProfile(), p_label); + } +}; + +template class MixedStrategyCSVRenderer : public MixedStrategyRenderer { +public: + explicit MixedStrategyCSVRenderer(std::ostream &p_stream, int p_numDecimals = 6) + : m_stream(p_stream), m_numDecimals(p_numDecimals) + { + } + ~MixedStrategyCSVRenderer() override = default; + void Render(const MixedStrategyProfile &p_profile, + const std::string &p_label = "NE") const override; + +private: + std::ostream &m_stream; + int m_numDecimals; +}; + +template class MixedStrategyDetailRenderer : public MixedStrategyRenderer { +public: + explicit MixedStrategyDetailRenderer(std::ostream &p_stream, int p_numDecimals = 6) + : m_stream(p_stream), m_numDecimals(p_numDecimals) + { + } + ~MixedStrategyDetailRenderer() override = default; + void Render(const MixedStrategyProfile &p_profile, + const std::string &p_label = "NE") const override; + +private: + std::ostream &m_stream; + int m_numDecimals; +}; + +// +// Encapsulates the rendering of a behavior profile to various text formats. +// +template class BehavStrategyRenderer : public StrategyProfileRenderer { +public: + ~BehavStrategyRenderer() override = default; + void Render(const MixedStrategyProfile &p_profile, + const std::string &p_label = "NE") const override + { + Render(MixedBehaviorProfile(p_profile), p_label); + } + void Render(const MixedBehaviorProfile &p_profile, + const std::string &p_label = "NE") const override = 0; +}; + +template class BehavStrategyCSVRenderer : public BehavStrategyRenderer { +public: + explicit BehavStrategyCSVRenderer(std::ostream &p_stream, int p_numDecimals = 6) + : m_stream(p_stream), m_numDecimals(p_numDecimals) + { + } + ~BehavStrategyCSVRenderer() override = default; + void Render(const MixedBehaviorProfile &p_profile, + const std::string &p_label = "NE") const override; + +private: + std::ostream &m_stream; + int m_numDecimals; +}; + +template class BehavStrategyDetailRenderer : public BehavStrategyRenderer { +public: + explicit BehavStrategyDetailRenderer(std::ostream &p_stream, int p_numDecimals = 6) + : m_stream(p_stream), m_numDecimals(p_numDecimals) + { + } + ~BehavStrategyDetailRenderer() override = default; + void Render(const MixedBehaviorProfile &p_profile, + const std::string &p_label = "NE") const override; + +private: + std::ostream &m_stream; + int m_numDecimals; +}; + +template +void MixedStrategyCSVRenderer::Render(const MixedStrategyProfile &p_profile, + const std::string &p_label) const +{ + m_stream << p_label; + for (size_t i = 1; i <= p_profile.MixedProfileLength(); i++) { + m_stream << "," << lexical_cast(p_profile[i], m_numDecimals); + } + m_stream << std::endl; +} + +template +void MixedStrategyDetailRenderer::Render(const MixedStrategyProfile &p_profile, + const std::string &p_label) const +{ + for (auto player : p_profile.GetGame()->GetPlayers()) { + m_stream << "Strategy profile for player " << player->GetNumber() << ":\n"; + + m_stream << "Strategy Prob Value\n"; + m_stream << "-------- ----------- -----------\n"; + + for (auto strategy : player->GetStrategies()) { + if (!strategy->GetLabel().empty()) { + m_stream << std::setw(8) << strategy->GetLabel() << " "; + } + else { + m_stream << std::setw(8) << strategy->GetNumber() << " "; + } + m_stream << std::setw(10); + m_stream << lexical_cast(p_profile[strategy], m_numDecimals); + m_stream << " "; + m_stream << std::setw(11); + m_stream << lexical_cast(p_profile.GetPayoff(strategy), m_numDecimals); + m_stream << std::endl; + } + } +} + +template +void BehavStrategyCSVRenderer::Render(const MixedBehaviorProfile &p_profile, + const std::string &p_label) const +{ + m_stream << p_label; + for (size_t i = 1; i <= p_profile.BehaviorProfileLength(); i++) { + m_stream << "," << lexical_cast(p_profile[i], m_numDecimals); + } + m_stream << std::endl; +} + +template +void BehavStrategyDetailRenderer::Render(const MixedBehaviorProfile &p_profile, + const std::string &p_label) const +{ + for (auto player : p_profile.GetGame()->GetPlayers()) { + m_stream << "Behavior profile for player " << player->GetNumber() << ":\n"; + + m_stream << "Infoset Action Prob Value\n"; + m_stream << "------- ------- ----------- -----------\n"; + + for (const auto &infoset : player->GetInfosets()) { + for (const auto &action : infoset->GetActions()) { + if (!infoset->GetLabel().empty()) { + m_stream << std::setw(7) << infoset->GetLabel() << " "; + } + else { + m_stream << std::setw(7) << infoset->GetNumber() << " "; + } + if (!action->GetLabel().empty()) { + m_stream << std::setw(7) << action->GetLabel() << " "; + } + else { + m_stream << std::setw(7) << action->GetNumber() << " "; + } + m_stream << std::setw(11); + m_stream << lexical_cast(p_profile[action], m_numDecimals); + m_stream << " "; + m_stream << std::setw(11); + m_stream << lexical_cast(p_profile.GetPayoff(action), m_numDecimals); + m_stream << std::endl; + } + } + + m_stream << std::endl; + m_stream << "Infoset Node Belief Prob\n"; + m_stream << "------- ------- ----------- -----------\n"; + + for (const auto &infoset : player->GetInfosets()) { + for (const auto &node : infoset->GetMembers()) { + if (!infoset->GetLabel().empty()) { + m_stream << std::setw(7) << infoset->GetLabel() << " "; + } + else { + m_stream << std::setw(7) << infoset->GetNumber() << " "; + } + if (!node->GetLabel().empty()) { + m_stream << std::setw(7) << node->GetLabel() << " "; + } + else { + m_stream << std::setw(7) << node->GetNumber() << " "; + } + m_stream << std::setw(11); + m_stream << lexical_cast(p_profile.GetBeliefProb(node), m_numDecimals); + m_stream << " "; + m_stream << std::setw(11); + m_stream << lexical_cast(p_profile.GetRealizProb(node), m_numDecimals); + m_stream << std::endl; + } + } + m_stream << std::endl; + } +} + +} // namespace Gambit + +#endif // TOOLS_UTIL_H diff --git a/tests/games.py b/tests/games.py index 652dafc73..361333af3 100644 --- a/tests/games.py +++ b/tests/games.py @@ -1,14 +1,17 @@ """A utility module to create/load games for the test suite.""" + import pathlib +import numpy as np + import pygambit as gbt def read_from_file(fn: str) -> gbt.Game: if fn.endswith(".efg"): - return gbt.read_efg(pathlib.Path("tests/test_games")/fn) + return gbt.read_efg(pathlib.Path("tests/test_games") / fn) elif fn.endswith(".nfg"): - return gbt.read_nfg(pathlib.Path("tests/test_games")/fn) + return gbt.read_nfg(pathlib.Path("tests/test_games") / fn) else: raise ValueError(f"Unknown file extension in {fn}") @@ -16,6 +19,7 @@ def read_from_file(fn: str) -> gbt.Game: ################################################################################################ # Normal-form (aka strategic-form) games (nfg) + def create_2x2_zero_nfg() -> gbt.Game: """ Returns @@ -73,6 +77,7 @@ def create_coord_4x4_nfg(outcome_version: bool = False) -> gbt.Game: ################################################################################################ # Extensive-form games (efg) + def create_mixed_behav_game_efg() -> gbt.Game: """ Returns @@ -89,7 +94,8 @@ def create_myerson_2_card_poker_efg() -> gbt.Game: Returns ------- Game - Myerson 2-card poker: Two-player extensive poker game with a chance move with two moves, + Simplied "stripped down" version of Myerson 2-card poker: + Two-player extensive poker game with a chance move with two moves, then player 1 can raise or fold; after raising player 2 is in an infoset with two nodes and can choose to meet or pass """ @@ -124,3 +130,112 @@ def create_selten_horse_game_efg() -> gbt.Game: 5-player Selten's Horse Game """ return read_from_file("e01.efg") + + +def create_reduction_generic_payoffs_efg() -> gbt.Game: + # tree with only root + g = gbt.Game.new_tree( + players=["1", "2"], title="2 player reduction generic payoffs" + ) + + # add four children + g.append_move(g.root, "2", ["a", "b", "c", "d"]) + + # add L and R after a + g.append_move(g.root.children[0], "1", ["L", "R"]) + + # add C and D to single infoset after b and c + nodes = [g.root.children[1], g.root.children[2]] + g.append_move(nodes, "1", ["C", "D"]) + + # add s and t from single infoset after rightmost C and D + g.append_move(g.root.children[2].children, "2", ["s", "t"]) + + # add p and q + g.append_move(g.root.children[0].children[1], "2", ["p", "q"]) + + # add U and V in a single infoset after p and q + g.append_move(g.root.children[0].children[1].children, "1", ["U", "V"]) + + # Set outcomes + + g.set_outcome(g.root.children[0].children[0], g.add_outcome([1, -1], label="aL")) + g.set_outcome( + g.root.children[0].children[1].children[0].children[0], + g.add_outcome([2, -2], label="aRpU"), + ) + g.set_outcome( + g.root.children[0].children[1].children[0].children[1], + g.add_outcome([3, -3], label="aRpV"), + ) + g.set_outcome( + g.root.children[0].children[1].children[1].children[0], + g.add_outcome([4, -4], label="aRqU"), + ) + g.set_outcome( + g.root.children[0].children[1].children[1].children[1], + g.add_outcome([5, -5], label="aRqV"), + ) + + g.set_outcome(g.root.children[1].children[0], g.add_outcome([6, -6], label="bC")) + g.set_outcome(g.root.children[1].children[1], g.add_outcome([7, -7], label="bD")) + + g.set_outcome( + g.root.children[2].children[0].children[0], g.add_outcome([8, -8], label="cCs") + ) + g.set_outcome( + g.root.children[2].children[0].children[1], g.add_outcome([9, -9], label="cCt") + ) + g.set_outcome( + g.root.children[2].children[1].children[0], + g.add_outcome([10, -10], label="cDs"), + ) + g.set_outcome( + g.root.children[2].children[1].children[1], + g.add_outcome([11, -11], label="cDt"), + ) + + g.set_outcome(g.root.children[3], g.add_outcome([12, -12], label="d")) + + return g + + +def create_reduction_one_player_generic_payoffs_efg() -> gbt.Game: + g = gbt.Game.new_tree(players=["1"], title="One player reduction generic payoffs") + g.append_move(g.root, "1", ["a", "b", "c", "d"]) + g.append_move(g.root.children[0], "1", ["e", "f"]) + g.set_outcome(g.root.children[0].children[0], g.add_outcome([1])) + g.set_outcome(g.root.children[0].children[1], g.add_outcome([2])) + g.set_outcome(g.root.children[1], g.add_outcome([3])) + g.set_outcome(g.root.children[2], g.add_outcome([4])) + g.set_outcome(g.root.children[3], g.add_outcome([5])) + return g + + +def create_reduction_both_players_payoff_ties_efg() -> gbt.Game: + g = gbt.Game.new_tree(players=["1", "2"], title="From GTE survey") + g.append_move(g.root, "1", ["A", "B", "C", "D"]) + g.append_move(g.root.children[0], "2", ["a", "b"]) + g.append_move(g.root.children[1], "2", ["c", "d"]) + g.append_move(g.root.children[2], "2", ["e", "f"]) + g.append_move(g.root.children[0].children[1], "2", ["g", "h"]) + g.append_move(g.root.children[2].children, "1", ["E", "F"]) + + g.set_outcome(g.root.children[0].children[0], g.add_outcome([2, 8])) + g.set_outcome(g.root.children[0].children[1].children[0], g.add_outcome([0, 1])) + g.set_outcome(g.root.children[0].children[1].children[1], g.add_outcome([5, 2])) + g.set_outcome(g.root.children[1].children[0], g.add_outcome([7, 6])) + g.set_outcome(g.root.children[1].children[1], g.add_outcome([4, 2])) + g.set_outcome(g.root.children[2].children[0].children[0], g.add_outcome([3, 7])) + g.set_outcome(g.root.children[2].children[0].children[1], g.add_outcome([8, 3])) + g.set_outcome(g.root.children[2].children[1].children[0], g.add_outcome([7, 8])) + g.set_outcome(g.root.children[2].children[1].children[1], g.add_outcome([2, 2])) + g.set_outcome(g.root.children[3], g.add_outcome([6, 4])) + return g + + +def make_rational(input: str): + return gbt.Rational(input) + + +vectorized_make_rational = np.vectorize(make_rational) diff --git a/tests/test_actions.py b/tests/test_actions.py index 10d55ca35..971a65cbb 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -127,3 +127,115 @@ def test_action_delete_chance(game: gbt.Game): assert p2 == p1 / (1-old_probs[0]) with pytest.raises(gbt.UndefinedOperationError): game.delete_action(chance_iset.actions[0]) + + +def test_action_plays(): + """Verify `action.plays` returns plays reachable from a given action. + """ + game = games.read_from_file("e01.efg") + list_nodes = list(game.nodes) + list_infosets = list(game.infosets) + + test_action = list_infosets[2].actions[0] # members' paths=[0, 1, 0], [0, 1] + + expected_set_of_plays = { + list_nodes[4], list_nodes[7] # paths=[0, 1, 0], [0, 1] + } + + assert set(test_action.plays) == expected_set_of_plays + + +@pytest.mark.parametrize( + "game, player_ind, str_ind, infoset_ind, expected_action_ind", + [ + (games.read_from_file("e01.efg"), 0, 0, 0, 0), + (games.read_from_file("e01.efg"), 0, 1, 0, 1), + (games.read_from_file("e01.efg"), 1, 0, 1, 0), + (games.read_from_file("e01.efg"), 1, 1, 1, 1), + (games.read_from_file("e01.efg"), 2, 0, 2, 0), + (games.read_from_file("e01.efg"), 2, 1, 2, 1), + (games.read_from_file("e02.efg"), 0, 0, 0, 0), + (games.read_from_file("e02.efg"), 0, 1, 0, 1), + (games.read_from_file("e02.efg"), 1, 0, 2, 0), + (games.read_from_file("e02.efg"), 1, 1, 2, 1), + (games.read_from_file("basic_extensive_game.efg"), 0, 0, 0, 0), + (games.read_from_file("basic_extensive_game.efg"), 0, 1, 0, 1), + (games.read_from_file("basic_extensive_game.efg"), 1, 0, 1, 0), + (games.read_from_file("basic_extensive_game.efg"), 1, 1, 1, 1), + (games.read_from_file("basic_extensive_game.efg"), 2, 0, 2, 0), + (games.read_from_file("basic_extensive_game.efg"), 2, 1, 2, 1), + ], +) +def test_strategy_action_defined(game, player_ind, str_ind, infoset_ind, expected_action_ind): + """Verify `Strategy.action` retrieves the correct action for defined actions. + """ + player = game.players[player_ind] + strategy = player.strategies[str_ind] + infoset = game.infosets[infoset_ind] + expected_action = infoset.actions[expected_action_ind] + + prescribed_action = strategy.action(infoset) + + assert prescribed_action == expected_action + + +@pytest.mark.parametrize( + "game, player_ind, str_ind, infoset_ind", + [ + (games.read_from_file("e02.efg"), 0, 0, 1), + (games.read_from_file("cent3.efg"), 0, 0, 1), + (games.read_from_file("cent3.efg"), 0, 0, 2), + (games.read_from_file("cent3.efg"), 0, 1, 2), + (games.read_from_file("cent3.efg"), 1, 0, 7), + (games.read_from_file("cent3.efg"), 1, 0, 7), + (games.read_from_file("cent3.efg"), 1, 1, 8), + ], +) +def test_strategy_action_undefined_returns_none(game, player_ind, str_ind, infoset_ind): + """Verify `Strategy.action` returns None when called on an unreached player's infoset + """ + player = game.players[player_ind] + strategy = player.strategies[str_ind] + infoset = game.infosets[infoset_ind] + + prescribed_action = strategy.action(infoset) + + assert prescribed_action is None + + +@pytest.mark.parametrize( + "game, player_ind, infoset_ind", + [ + (games.read_from_file("e01.efg"), 0, 1), + (games.read_from_file("e01.efg"), 1, 0), + (games.read_from_file("e02.efg"), 0, 2), + (games.read_from_file("e02.efg"), 1, 0), + (games.read_from_file("basic_extensive_game.efg"), 0, 1), + (games.read_from_file("basic_extensive_game.efg"), 1, 2), + (games.read_from_file("basic_extensive_game.efg"), 2, 0), + ], +) +def test_strategy_action_raises_value_error_for_wrong_player(game, player_ind, infoset_ind): + """ + Verify `Strategy.action` raises ValueError when the infoset belongs + to a different player than the strategy. + """ + player = game.players[player_ind] + strategy = player.strategies[0] + other_players_infoset = game.infosets[infoset_ind] + + with pytest.raises(ValueError): + strategy.action(other_players_infoset) + + +def test_strategy_action_raises_error_for_strategic_game(): + """Verify `Strategy.action` retrieves the action prescribed by the strategy + """ + game_efg = games.read_from_file("e02.efg") + game_nfg = game_efg.from_arrays(game_efg.to_arrays()[0], game_efg.to_arrays()[1]) + alice = game_nfg.players[0] + strategy = alice.strategies[0] + test_infoset = game_efg.infosets[0] + + with pytest.raises(gbt.UndefinedOperationError): + strategy.action(test_infoset) diff --git a/tests/test_behav.py b/tests/test_behav.py index 6636cf04c..fd7e93e85 100644 --- a/tests/test_behav.py +++ b/tests/test_behav.py @@ -641,7 +641,7 @@ def test_realiz_prob_nodes_reference(game: gbt.Game, node_idx: int, realiz_prob: typing.Union[str, float], rational_flag: bool): profile = game.mixed_behavior_profile(rational=rational_flag) realiz_prob = (gbt.Rational(realiz_prob) if rational_flag else realiz_prob) - node = game.nodes()[node_idx] + node = list(game.nodes)[node_idx] assert profile.realiz_prob(node) == realiz_prob @@ -890,7 +890,7 @@ def test_martingale_property_of_node_value(game: gbt.Game, rational_flag: bool): realization probabilities of those children """ profile = game.mixed_behavior_profile(rational=rational_flag) - for node in game.nodes(): + for node in game.nodes: if node.is_terminal or node.player.is_chance: continue expected_val = 0 @@ -1080,23 +1080,23 @@ def _get_and_check_answers(game: gbt.Game, action_probs1: tuple, action_probs2: ###################################################################################### # belief (at nodes) (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.belief(y), lambda x: x.nodes()), + lambda x, y: x.belief(y), lambda x: x.nodes), (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.belief(y), lambda x: x.nodes()), + lambda x, y: x.belief(y), lambda x: x.nodes), (games.create_myerson_2_card_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.belief(y), lambda x: x.nodes()), + lambda x, y: x.belief(y), lambda x: x.nodes), (games.create_myerson_2_card_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.belief(y), lambda x: x.nodes()), + lambda x, y: x.belief(y), lambda x: x.nodes), ###################################################################################### # realiz_prob (at nodes) (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, - lambda x, y: x.realiz_prob(y), lambda x: x.nodes()), + lambda x, y: x.realiz_prob(y), lambda x: x.nodes), (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.realiz_prob(y), lambda x: x.nodes()), + lambda x, y: x.realiz_prob(y), lambda x: x.nodes), (games.create_myerson_2_card_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, - lambda x, y: x.realiz_prob(y), lambda x: x.nodes()), + lambda x, y: x.realiz_prob(y), lambda x: x.nodes), (games.create_myerson_2_card_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, - lambda x, y: x.realiz_prob(y), lambda x: x.nodes()), + lambda x, y: x.realiz_prob(y), lambda x: x.nodes), ###################################################################################### # infoset_prob (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, @@ -1141,16 +1141,16 @@ def _get_and_check_answers(game: gbt.Game, action_probs1: tuple, action_probs2: # node_value (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, lambda x, y: x.node_value(player=y[0], node=y[1]), - lambda x: list(product(x.players, x.nodes()))), + lambda x: list(product(x.players, x.nodes))), (games.create_mixed_behav_game_efg(), PROBS_1A_rat, PROBS_2A_rat, True, lambda x, y: x.node_value(player=y[0], node=y[1]), - lambda x: list(product(x.players, x.nodes()))), + lambda x: list(product(x.players, x.nodes))), (games.create_myerson_2_card_poker_efg(), PROBS_1B_doub, PROBS_2B_doub, False, lambda x, y: x.node_value(player=y[0], node=y[1]), - lambda x: list(product(x.players, x.nodes()))), + lambda x: list(product(x.players, x.nodes))), (games.create_myerson_2_card_poker_efg(), PROBS_1A_rat, PROBS_2A_rat, True, lambda x, y: x.node_value(player=y[0], node=y[1]), - lambda x: list(product(x.players, x.nodes()))), + lambda x: list(product(x.players, x.nodes))), ###################################################################################### # liap_value (of profile, hence [1] for objects_to_test, any singleton collection would do) (games.create_mixed_behav_game_efg(), PROBS_1A_doub, PROBS_2A_doub, False, diff --git a/tests/test_extensive.py b/tests/test_extensive.py index 9fe517f8f..d896e1a77 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -1,5 +1,6 @@ import typing +import numpy as np import pytest import pygambit as gbt @@ -8,21 +9,17 @@ @pytest.mark.parametrize( - "players,title", - [([], "New game"), - (["Alice", "Bob"], "A poker game")] + "players,title", [([], "New game"), (["Alice", "Bob"], "A poker game")] ) def test_new_tree(players: list, title: typing.Optional[str]): game = gbt.Game.new_tree(players=players, title=title) assert len(game.players) == len(players) - for (player, label) in zip(game.players, players): + for player, label in zip(game.players, players): assert player.label == label assert game.title == title -@pytest.mark.parametrize( - "title", ["My game's new title"] -) +@pytest.mark.parametrize("title", ["My game's new title"]) def test_game_title(title: str): game = gbt.Game.new_tree() game.title = title @@ -38,15 +35,12 @@ def test_game_comment(comment: str): assert game.comment == comment -@pytest.mark.parametrize( - "players", - [["Alice"], ["Oscar", "Felix"]] -) +@pytest.mark.parametrize("players", [["Alice"], ["Oscar", "Felix"]]) def test_game_add_players_label(players: list): game = gbt.Game.new_tree() for player in players: game.add_player(player) - for (player, label) in zip(game.players, players): + for player, label in zip(game.players, players): assert player.label == label @@ -55,19 +49,28 @@ def test_game_add_players_nolabel(): game.add_player() -def test_game_num_nodes(): - game = games.read_from_file("basic_extensive_game.efg") - assert len(game.nodes()) == 15 - - -def test_game_is_perfect_recall(): - game = games.read_from_file("perfect_recall.efg") - assert game.is_perfect_recall +@pytest.mark.parametrize("game_filename,expected_result", [ + # Games that have perfect recall + ("e01.efg", True), + ("e02.efg", True), + ("cent3.efg", True), + ("poker.efg", True), + ("basic_extensive_game.efg", True), - -def test_game_is_not_perfect_recall(): - game = games.read_from_file("not_perfect_recall.efg") - assert not game.is_perfect_recall + # Games that do not have perfect recall + ("wichardt.efg", False), # forgetting past action + ("noPR-action-selten-horse.efg", False), # forgetting past action + ("noPR-information-no-deflate.efg", False), # forgetting past information + ("noPR-AM.efg", False), # absent-mindedness + ("noPR-action-AM.efg", False), # absent-mindedness + forgetting past action +]) +def test_is_perfect_recall(game_filename: str, expected_result: bool): + """ + Verify the IsPerfectRecall implementation against a suite of games + with and without the perfect recall property. + """ + game = games.read_from_file(game_filename) + assert game.is_perfect_recall == expected_result def test_getting_payoff_by_label_string(): @@ -100,3 +103,338 @@ def test_outcome_index_exception_label(): game = games.read_from_file("sample_extensive_game.efg") with pytest.raises(KeyError): _ = game[[0, 0]]["Not a player"] + + +@pytest.mark.parametrize( + "game,strategy_labels,np_arrays_of_rsf", + [ + ############################################################################### + # 1 player; reduction; generic payoffs + ( + games.create_reduction_one_player_generic_payoffs_efg(), + [["11", "12", "2*", "3*", "4*"]], + [np.array(range(1, 6))], + ), + # 2 players; reduction possible for player 1; payoff ties + ( + games.read_from_file("e02.efg"), + [["1*", "21", "22"], ["1", "2"]], + [ + np.array([[1, 1], [0, 0], [0, 2]]), + np.array([[1, 1], [2, 3], [2, 0]]), + ], + ), + # 2 players; 1 move each so no reduction possible + ( + games.read_from_file("sample_extensive_game.efg"), + [["1", "2"], ["11", "12", "21", "22"]], + [ + np.array([[2, 2, 2, 2], [4, 6, 4, 6]]), + np.array([[3, 3, 3, 3], [5, 7, 5, 7]]), + ], + ), + # Selten's Horse: game with three players + ( + games.read_from_file("e01.efg"), + [["1", "2"], ["1", "2"], ["1", "2"]], + [ + np.array([[[1, 1], [4, 0]], [[3, 0], [3, 0]]]), + np.array([[[1, 1], [4, 0]], [[2, 0], [2, 0]]]), + np.array([[[1, 1], [0, 1]], [[2, 0], [2, 0]]]), + ], + ), + # 2-player (zero-sum) game; reduction for both players; generic payoffs + ( + games.create_reduction_generic_payoffs_efg(), + [ + ["11*", "12*", "211", "221", "212", "222"], + ["1*1", "1*2", "2**", "31*", "32*", "4**"], + ], + [ + np.array( + [ + [1, 1, 6, 8, 9, 12], + [1, 1, 7, 10, 11, 12], + [2, 4, 6, 8, 9, 12], + [2, 4, 7, 10, 11, 12], + [3, 5, 6, 8, 9, 12], + [3, 5, 7, 10, 11, 12], + ] + ), + np.array( + [ + [-1, -1, -6, -8, -9, -12], + [-1, -1, -7, -10, -11, -12], + [-2, -4, -6, -8, -9, -12], + [-2, -4, -7, -10, -11, -12], + [-3, -5, -6, -8, -9, -12], + [-3, -5, -7, -10, -11, -12], + ] + ), + ], + ), + # 2-player (zero-sum) game; binary tree; reduction for player 1; generic payoffs + ( + games.read_from_file("binary_3_levels_generic_payoffs.efg"), + [ + ["11*", "12*", "2*1", "2*2"], + ["1", "2"], + ], + [ + np.array([[1, 3], [2, 4], [5, 7], [6, 8]]), + np.array([[-1, -3], [-2, -4], [-5, -7], [-6, -8]]), + ], + ), + # # 2-player game from GTE survey; reduction for both players; payoff ties + ( + games.create_reduction_both_players_payoff_ties_efg(), + [ + ["1*", "2*", "31", "32", "4*"], + [ + "111*", + "112*", + "121*", + "122*", + "2111", + "2121", + "2211", + "2221", + "2112", + "2122", + "2212", + "2222", + ], + ], + [ + np.array( + [ + [2, 2, 2, 2, 0, 0, 0, 0, 5, 5, 5, 5], + [7, 7, 4, 4, 7, 7, 4, 4, 7, 7, 4, 4], + [3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7], + [8, 2, 8, 2, 8, 2, 8, 2, 8, 2, 8, 2], + [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], + ] + ), + np.array( + [ + [8, 8, 8, 8, 1, 1, 1, 1, 2, 2, 2, 2], + [6, 6, 2, 2, 6, 6, 2, 2, 6, 6, 2, 2], + [7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8], + [3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2], + [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], + ] + ), + ], + ), + ########################################################################### + # Games with chance nodes + ########################################################################### + # 2-player long centipede game with chance playing at the root twice + ( + games.read_from_file("cent3.efg"), + [ + ["1**111", "21*111", "221111", "222111"], + ["1**111", "21*111", "221111", "222111"], + ], + [ + np.array( + [ + ["20027/25000", "5081/6250", "2689/3125", "163/125"], + ["541/1250", "19931/6250", "10114/3125", "92/25"], + ["3299/6250", "5362/3125", "39814/3125", "1648/125"], + ["227/250", "262/125", "856/125", "256/5"], + ] + ), + np.array( + [ + ["2689/12500", "3283/12500", "5659/12500", "163/500"], + ["3983/2500", "2677/3125", "3271/3125", "23/25"], + ["5053/3125", "19903/3125", "10696/3125", "412/125"], + ["214/125", "808/125", "3184/125", "64/5"], + ] + ), + ], + ), + # Stripped down "Myerson" 2-card poker; 2 player zero-sum game with chance at the root + ( + games.create_myerson_2_card_poker_efg(), + [["11", "12", "21", "22"], ["1", "2"]], + [ + np.array([[-1, 0], ["-1/2", -1], ["-5/2", -1], [-2, -2]]), + np.array([[1, 0], ["1/2", 1], ["5/2", 1], [2, 2]]), + ], + ), + # Nature playing at the root, 2 players, no reduction, non-generic payoffs + ( + games.read_from_file("nature_rooted_nongeneric.efg"), + [["1", "2"], ["11", "12", "21", "22"]], + [ + np.array([[-1, -1, 2, 2], [0, 0, 0, 0]]), + np.array([[-1, -1, 2, 2], [3, 4, 3, 4]]), + ], + ), + # Nature playing at the root, 2 players, no reduction, generic payoffs + ( + games.read_from_file("nature_rooted_generic.efg"), + [["1", "2"], ["11", "12", "21", "22"]], + [ + np.array([[3, 3, 4, 4], [5, 6, 5, 6]]), + np.array([[-3, -3, -4, -4], [-5, -6, -5, -6]]), + ], + ), + # Nature playing last determining the payoffs, 2 players, no reduction, non-generic payoffs + ( + games.read_from_file("nature_leaves_nongeneric.efg"), + [["1", "2"], ["11", "12", "21", "22"]], + [ + np.array([[-1, -1, 2, 2], [0, 0, 0, 0]]), + np.array([[-1, -1, 2, 2], [3, 4, 3, 4]]), + ], + ), + # Nature playing last determining the payoffs, 2 players, no reduction, generic payoffs + ( + games.read_from_file("nature_leaves_generic.efg"), + [["1", "2"], ["11", "12", "21", "22"]], + [ + np.array( + [["3/2", "3/2", "7/2", "7/2"], ["11/2", "15/2", "11/2", "15/2"]] + ), + np.array( + [ + ["-3/2", "-3/2", "-7/2", "-7/2"], + ["-11/2", "-15/2", "-11/2", "-15/2"], + ] + ), + ], + ), + # I M P E R F E C T R E C A L L + # Wichardt (2008): binary tree of height 3; 2 players; the root player forgets the action + ( + games.read_from_file("wichardt.efg"), + [["11", "12", "21", "22"], ["1", "2"]], + [ + np.array([[1, -1], [-5, -5], [-5, -5], [-1, 1]]), + np.array([[-1, 1], [5, 5], [5, 5], [1, -1]]), + ], + ), + # a simple game without Nature where two infosets of a player overlap + # without Thompson's Coalescing + ( + games.read_from_file("non-COAable.efg"), + [["1", "2", "3"], ["11", "12", "21", "22"]], + [ + np.array([[8, 8, 7, 7], [6, 5, 4, 3], [2, 1, 2, 1]]), + np.array([[-8, -8, -7, -7], [-6, -5, -4, -3], [-2, -1, -2, -1]]), + ] + ), + # Absent-minded driver with an infoset crossing a path more than once + ( + games.read_from_file("AM.efg"), + [["1", "2"], ["1", "2"]], + [ + np.array([[1, 0], [2, 2]]), + np.array([[1, 3], [0, 0]]), + ] + ), + # Absent-minded driver: Gilboa's reformulation with Nature and two crossing-over infosets + # (GEB, 1997) + ( + games.read_from_file("gilboa.efg"), + [["11", "12", "21", "22"]], + [ + np.array(["5/2", "7/2", "7/2", "9/2"]) + ] + ), + # Absent-minded driver: Gilboa's reformulation -- no Nature at the root + ( + games.read_from_file("gilboa-det.efg"), + [["1", "2"], ["11", "12", "21", "22"]], + [ + np.array([[1, 1, 2, 3], [4, 6, 5, 6]]), + np.array([[-1, -1, -2, -3], [-4, -6, -5, -6]]), + ] + ), + ], +) +# def test_reduced_strategic_form( +# game: gbt.Game, strategy_labels: list, np_arrays_of_rsf: list +# ): +# """ +# We test two things: +# - that the strategy labels are as expected +# (these use positive integers and '*'s, rather than labels of moves even if they exist) +# - that the payoff tables are correct, which is done via game.to_arrays() +# """ +# arrays = game.to_arrays() +# +# for i, player in enumerate(game.players): +# assert strategy_labels[i] == [s.label for s in player.strategies] +# # convert strings to rationals +# exp_array = games.vectorized_make_rational(np_arrays_of_rsf[i]) +# assert (arrays[i] == exp_array).all() +def test_reduced_strategic_form( + game: gbt.Game, strategy_labels: list, np_arrays_of_rsf: list +): + """ + We test three things: + - that the strategy labels are as expected + (these use positive integers and '*'s, rather than labels of moves even if they exist) + - that the payoff tables are correct, which is done via game.to_arrays() + - a permutation-invariance as a fallback + WARNING: the permutation-invariance check will fail for games with full payoff ties of players + having the following symmetric pattern: + [[a, e, e], + [b, c, d], + [b, d, c]] + """ + arrays = game.to_arrays() + actual_labels_per_player = [[s.label for s in p.strategies] for p in game.players] + try: + for i, player in enumerate(game.players): + assert strategy_labels[i] == [s.label for s in player.strategies] + # convert strings to rationals + exp_array = games.vectorized_make_rational(np_arrays_of_rsf[i]) + assert (arrays[i] == exp_array).all() + + except AssertionError: + print("\nStrict test failed. Checking permutation-invariance") + + num_players = len(game.players) + + for i in range(num_players): + assert len(strategy_labels[i]) == len(actual_labels_per_player[i]), \ + f"Wrong number of strategies for Player {i+1}." + assert set(strategy_labels[i]) == set(actual_labels_per_player[i]), \ + f"The set of strategy labels for Player {i+1} is incorrect." + + reordered_array = get_reordered_payoff_matrix( + player_index=i, game=game, expected_labels=strategy_labels, actual_payoffs=arrays + ) + + exp_array = games.vectorized_make_rational(np_arrays_of_rsf[i]) + + assert (reordered_array == exp_array).all(), \ + f"Payoff matrix for Player {i+1} is incorrect even after reordering strategies." + + +def get_reordered_payoff_matrix( + player_index: int, game: gbt.Game, expected_labels: list, actual_payoffs: list +) -> np.ndarray: + """ + Re-orders a player's payoff matrix to match a canonical label order. + This helper function contains no assertions. + """ + all_actual_labels = [[s.label for s in p.strategies] for p in game.players] + axis_permutations = [] + + for i, expected_labels_for_player in enumerate(expected_labels): + actual_labels_for_player = all_actual_labels[i] + indices = [actual_labels_for_player.index(label) for label in expected_labels_for_player] + axis_permutations.append(indices) + + reordered_payoffs = actual_payoffs[player_index] + + for axis, perm_indices in enumerate(axis_permutations): + reordered_payoffs = np.take(reordered_payoffs, perm_indices, axis=axis) + + return reordered_payoffs diff --git a/tests/test_game_resolve.py b/tests/test_game_resolve.py index e18c76794..8b7822151 100644 --- a/tests/test_game_resolve.py +++ b/tests/test_game_resolve.py @@ -106,7 +106,7 @@ def test_resolve_strategy_invalid( ] ) def test_resolve_node(game: gbt.Game) -> None: - _test_valid_resolutions(game.nodes(), + _test_valid_resolutions(game.nodes, lambda label, fn: game._resolve_node(label, fn)) diff --git a/tests/test_games/AM.efg b/tests/test_games/AM.efg new file mode 100644 index 000000000..a3eaec5dd --- /dev/null +++ b/tests/test_games/AM.efg @@ -0,0 +1,11 @@ + +EFG 2 R "Untitled Extensive Game" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "1" "2" } 0 +p "" 2 1 "" { "1" "2" } 0 +p "" 1 1 "" { "1" "2" } 0 +t "" 1 "Outcome 1" { 1, 1 } +t "" 2 "Outcome 2" { 0, 2 } +t "" 3 "Outcome 3" { 0, 3 } +t "" 4 "Outcome 4" { 2, 0 } diff --git a/tests/test_games/binary_3_levels_generic_payoffs.efg b/tests/test_games/binary_3_levels_generic_payoffs.efg new file mode 100644 index 000000000..ea33e53ba --- /dev/null +++ b/tests/test_games/binary_3_levels_generic_payoffs.efg @@ -0,0 +1,18 @@ +EFG 2 R "Binary Tree Game (L=3)" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "Left" "Right" } 0 +p "" 2 1 "" { "Left" "Right" } 0 +p "" 1 2 "" { "Left" "Right" } 0 +t "" 1 "" { 1, -1 } +t "" 2 "" { 2, -2 } +p "" 1 2 "" { "Left" "Right" } 0 +t "" 3 "" { 3, -3 } +t "" 4 "" { 4, -4 } +p "" 2 1 "" { "Left" "Right" } 0 +p "" 1 3 "" { "Left" "Right" } 0 +t "" 5 "" { 5, -5 } +t "" 6 "" { 6, -6 } +p "" 1 3 "" { "Left" "Right" } 0 +t "" 7 "" { 7, -7 } +t "" 8 "" { 8, -8 } diff --git a/tests/test_games/gilboa-det.efg b/tests/test_games/gilboa-det.efg new file mode 100644 index 000000000..bcbcdbb8a --- /dev/null +++ b/tests/test_games/gilboa-det.efg @@ -0,0 +1,14 @@ +EFG 2 R "Untitled Extensive Game" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "1" "2" } 0 +p "" 2 2 "" { "1" "2" } 0 +t "" 1 "Outcome 1" { 1, -1 } +p "" 2 1 "" { "1" "2" } 0 +t "" 2 "Outcome 2" { 2, -2 } +t "" 3 "Outcome 3" { 3, -3 } +p "" 2 1 "" { "1" "2" } 0 +p "" 2 2 "" { "1" "2" } 0 +t "" 4 "Outcome 4" { 4, -4 } +t "" 5 "Outcome 5" { 5, -5 } +t "" 6 "Outcome 6" { 6, -6 } diff --git a/tests/test_games/gilboa.efg b/tests/test_games/gilboa.efg new file mode 100644 index 000000000..6e2b0ae9c --- /dev/null +++ b/tests/test_games/gilboa.efg @@ -0,0 +1,14 @@ +EFG 2 R "Untitled Extensive Game" { "Player 1" } +"" + +c "" 1 "" { "1" 1/2 "2" 1/2 } 0 +p "" 1 2 "" { "1" "2" } 0 +t "" 1 "Outcome 1" { 1 } +p "" 1 1 "" { "1" "2" } 0 +t "" 2 "Outcome 2" { 2 } +t "" 3 "Outcome 3" { 3 } +p "" 1 1 "" { "1" "2" } 0 +p "" 1 2 "" { "1" "2" } 0 +t "" 4 "Outcome 4" { 4 } +t "" 5 "Outcome 5" { 5 } +t "" 6 "Outcome 6" { 6 } diff --git a/tests/test_games/nature_leaves_generic.efg b/tests/test_games/nature_leaves_generic.efg new file mode 100644 index 000000000..bda6d926d --- /dev/null +++ b/tests/test_games/nature_leaves_generic.efg @@ -0,0 +1,18 @@ +EFG 2 R "2 player game: chance plays last" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "B" "T" } 0 +p "" 2 1 "" { "r" "l" } 0 +c "" 1 "" { "1" 1/2 "2" 1/2 } 0 +t "" 1 "Outcome 1" { 1, -1 } +t "" 2 "Outcome 2" { 2, -2 } +c "" 2 "" { "1" 1/2 "2" 1/2 } 0 +t "" 3 "Outcome 3" { 3, -3 } +t "" 4 "Outcome 4" { 4, -4 } +p "" 2 2 "" { "R" "L" } 0 +c "" 3 "" { "1" 1/2 "2" 1/2 } 0 +t "" 5 "Outcome 5" { 5, -5 } +t "" 6 "Outcome 6" { 6, -6 } +c "" 4 "" { "1" 1/2 "2" 1/2 } 0 +t "" 7 "Outcome 7" { 7, -7 } +t "" 8 "Outcome 8" { 8, -8 } diff --git a/tests/test_games/nature_leaves_nongeneric.efg b/tests/test_games/nature_leaves_nongeneric.efg new file mode 100644 index 000000000..3abbbf4e1 --- /dev/null +++ b/tests/test_games/nature_leaves_nongeneric.efg @@ -0,0 +1,18 @@ +EFG 2 R "2 player game: chance plays last" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "B" "T" } 0 +p "" 2 1 "" { "r" "l" } 0 +c "" 1 "" { "1" 1/2 "2" 1/2 } 0 +t "" 1 "Outcome 1" { 0, 0 } +t "" 2 "Outcome 2" { -2, -2 } +c "" 2 "" { "1" 1/2 "2" 1/2 } 0 +t "" 3 "Outcome 3" { 3, 3 } +t "" 4 "Outcome 4" { 1, 1 } +p "" 2 2 "" { "R" "L" } 0 +c "" 3 "" { "1" 1/2 "2" 1/2 } 0 +t "" 5 "Outcome 5" { 0, 4 } +t "" 6 "Outcome 6" { 0, 2 } +c "" 4 "" { "1" 1/2 "2" 1/2 } 0 +t "" 7 "Outcome 7" { 0, 5 } +t "" 8 "Outcome 8" { 0, 3 } diff --git a/tests/test_games/nature_rooted_generic.efg b/tests/test_games/nature_rooted_generic.efg new file mode 100644 index 000000000..c66f76052 --- /dev/null +++ b/tests/test_games/nature_rooted_generic.efg @@ -0,0 +1,18 @@ +EFG 2 R "2 player game: chance plays at the root" { "Player 1" "Player 2" } +"" + +c "" 1 "" { "1" 1/2 "2" 1/2 } 0 +p "" 1 1 "" { "B" "T" } 0 +p "" 2 1 "" { "r" "l" } 0 +t "" 1 "Outcome 1" { 1, -1 } +t "" 2 "Outcome 2" { 2, -2 } +p "" 2 2 "" { "R" "L" } 0 +t "" 3 "Outcome 3" { 3, -3 } +t "" 4 "Outcome 4" { 4, -4 } +p "" 1 1 "" { "B" "T" } 0 +p "" 2 1 "" { "r" "l" } 0 +t "" 5 "Outcome 5" { 5, -5 } +t "" 6 "Outcome 6" { 6, -6 } +p "" 2 2 "" { "R" "L" } 0 +t "" 7 "Outcome 7" { 7, -7 } +t "" 8 "Outcome 8" { 8, -8 } diff --git a/tests/test_games/nature_rooted_nongeneric.efg b/tests/test_games/nature_rooted_nongeneric.efg new file mode 100644 index 000000000..ee54da784 --- /dev/null +++ b/tests/test_games/nature_rooted_nongeneric.efg @@ -0,0 +1,18 @@ +EFG 2 R "2 player game: chance plays at the root" { "Player 1" "Player 2" } +"" + +c "" 1 "" { "1" 1/2 "2" 1/2 } 0 +p "" 1 1 "" { "B" "T" } 0 +p "" 2 2 "" { "r" "l" } 0 +t "" 1 "Outcome 1" { 0, 0 } +t "" 2 "Outcome 2" { 3, 3 } +p "" 2 1 "" { "R" "L" } 0 +t "" 3 "Outcome 3" { 0, 4 } +t "" 4 "Outcome 4" { 0, 5 } +p "" 1 1 "" { "B" "T" } 0 +p "" 2 2 "" { "r" "l" } 0 +t "" 5 "Outcome 5" { -2, -2 } +t "" 6 "Outcome 6" { 1, 1 } +p "" 2 1 "" { "R" "L" } 0 +t "" 7 "Outcome 7" { 0, 2 } +t "" 8 "Outcome 8" { 0, 3 } diff --git a/tests/test_games/noPR-AM.efg b/tests/test_games/noPR-AM.efg new file mode 100644 index 000000000..816359cb6 --- /dev/null +++ b/tests/test_games/noPR-AM.efg @@ -0,0 +1,13 @@ +EFG 2 R "Untitled Extensive Game" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "1" "2" } 0 +p "" 2 1 "" { "1" "2" } 0 +p "" 1 1 "" { "1" "2" } 0 +t "" 1 "Outcome 1" { 1, 1 } +p "" 2 2 "" { "1" "2" "3" } 0 +t "" 2 "Outcome 2" { 0, 2 } +t "" 5 "Outcome 5" { 0, 5 } +t "" 6 "Outcome 6" { 0, 6 } +t "" 3 "Outcome 3" { 0, 3 } +t "" 4 "Outcome 4" { 2, 0 } diff --git a/tests/test_games/noPR-action-AM.efg b/tests/test_games/noPR-action-AM.efg new file mode 100644 index 000000000..92121a12e --- /dev/null +++ b/tests/test_games/noPR-action-AM.efg @@ -0,0 +1,18 @@ +EFG 2 R "Untitled Extensive Game" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "1" "2" } 0 +p "" 1 1 "" { "1" "2" } 0 +p "" 2 1 "" { "1" "2" } 0 +t "" 1 "Outcome 1" { 1, -1 } +t "" 2 "Outcome 2" { 2, -2 } +p "" 2 2 "" { "1" "2" } 0 +t "" 3 "Outcome 2" { 3, -3 } +t "" 4 "Outcome 2" { 4, -4 } +p "" 1 1 "" { "1" "2" } 0 +p "" 2 3 "" { "1" "2" } 0 +t "" 5 "Outcome 2" { 5, -5 } +t "" 6 "Outcome 2" { 6, -6 } +p "" 2 4 "" { "1" "2" } 0 +t "" 7 "Outcome 2" { 7, -7 } +t "" 8 "Outcome 2" { 8, -8 } diff --git a/tests/test_games/noPR-action-selten-horse.efg b/tests/test_games/noPR-action-selten-horse.efg new file mode 100644 index 000000000..dc377081e --- /dev/null +++ b/tests/test_games/noPR-action-selten-horse.efg @@ -0,0 +1,12 @@ +EFG 2 R "Selten's Horse': 2 players, imperfect recall" { "Player 1" "Player 2" } +"" + +p "" 1 1 "(1,1)" { "R" "L" } 0 +p "" 2 1 "(2,1)" { "R" "L" } 0 +t "" 1 "Outcome 1" { 1, 1 } +p "" 1 2 "(1,2)" { "R" "L" } 0 +t "" 2 "Outcome 2" { 4, 4 } +t "" 3 "Outcome 3" { 0, 0 } +p "" 1 2 "(1,2)" { "R" "L" } 0 +t "" 4 "Outcome 4" { 3, 2 } +t "" 5 "Outcome 5" { 0, 0 } diff --git a/tests/test_games/noPR-information-no-deflate.efg b/tests/test_games/noPR-information-no-deflate.efg new file mode 100644 index 000000000..af5abc77d --- /dev/null +++ b/tests/test_games/noPR-information-no-deflate.efg @@ -0,0 +1,15 @@ +EFG 2 R "Untitled Extensive Game" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "1" "2" "3" } 0 +p "" 2 1 "" { "1" "2" } 0 +t "" 1 "h" { 8, -8 } +t "" 2 "g" { 7, -7 } +p "" 2 1 "" { "1" "2" } 0 +t "" 0 +p "" 2 2 "" { "1" "2" } 0 +t "" 5 "d" { 4, -4 } +t "" 6 "c" { 3, -3 } +p "" 2 2 "" { "1" "2" } 0 +t "" 7 "b" { 2, -2 } +t "" 8 "a" { 1, -1 } diff --git a/tests/test_games/non-COAable.efg b/tests/test_games/non-COAable.efg new file mode 100644 index 000000000..22f093607 --- /dev/null +++ b/tests/test_games/non-COAable.efg @@ -0,0 +1,17 @@ +EFG 2 R "Untitled Extensive Game" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "1" "2" "3" } 0 +p "" 2 1 "" { "1" "2" } 0 +t "" 1 "h" { 8, -8 } +t "" 2 "g" { 7, -7 } +p "" 2 1 "" { "1" "2" } 0 +p "" 2 2 "" { "1" "2" } 0 +t "" 3 "f" { 6, -6 } +t "" 4 "e" { 5, -5 } +p "" 2 2 "" { "1" "2" } 0 +t "" 5 "d" { 4, -4 } +t "" 6 "c" { 3, -3 } +p "" 2 2 "" { "1" "2" } 0 +t "" 7 "b" { 2, -2 } +t "" 8 "a" { 1, -1 } diff --git a/tests/test_games/wichardt.efg b/tests/test_games/wichardt.efg new file mode 100644 index 000000000..03e4b05de --- /dev/null +++ b/tests/test_games/wichardt.efg @@ -0,0 +1,18 @@ +EFG 2 R "Wichardt (2008): 2 players, imperfect recall" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "R" "L" } 0 +p "" 1 2 "" { "r" "l" } 0 +p "" 2 1 "" { "B" "T" } 0 +t "" 1 "outcome 1" { 1, -1 } +t "" 2 "outcome 2" { -1, 1 } +p "" 2 1 "" { "B" "T" } 0 +t "" 3 "outcome 3" { -5, 5 } +t "" 4 "outcome 4" { -5, 5 } +p "" 1 2 "" { "r" "l" } 0 +p "" 2 1 "" { "B" "T" } 0 +t "" 5 "outcome 5" { -5, 5 } +t "" 6 "outcome 6" { -5, 5 } +p "" 2 1 "" { "B" "T" } 0 +t "" 7 "outcome 7" { -1, 1 } +t "" 8 "outcome 8" { 1, -1 } diff --git a/tests/test_infosets.py b/tests/test_infosets.py index 9d9c96495..a6f5b6d6a 100644 --- a/tests/test_infosets.py +++ b/tests/test_infosets.py @@ -53,3 +53,19 @@ 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_infoset_plays(): + """Verify `infoset.plays` returns plays reachable from a given infoset. + """ + game = games.read_from_file("e01.efg") + list_nodes = list(game.nodes) + list_infosets = list(game.infosets) + + test_infoset = list_infosets[2] # members' paths=[1, 0], [1] + + expected_set_of_plays = { + list_nodes[4], list_nodes[5], list_nodes[7], list_nodes[8] + } # paths=[0, 1, 0], [1, 1, 0], [0, 1], [1, 1] + + assert set(test_infoset.plays) == expected_set_of_plays diff --git a/tests/test_nash.py b/tests/test_nash.py index f47b69fb0..1cfde948a 100644 --- a/tests/test_nash.py +++ b/tests/test_nash.py @@ -32,6 +32,8 @@ def test_enummixed_strategy_double(): # For floating-point results are not exact, so we skip testing exact values for now +@pytest.mark.nash +@pytest.mark.nash_enummixed_strategy def test_enummixed_strategy_rational(): """Test calls of enumeration of mixed strategy equilibria, rational precision.""" game = games.read_from_file("poker.efg") @@ -53,6 +55,8 @@ def test_lcp_strategy_double(): # For floating-point results are not exact, so we skip testing exact values for now +@pytest.mark.nash +@pytest.mark.nash_lcp_strategy def test_lcp_strategy_rational(): """Test calls of LCP for mixed strategy equilibria, rational precision.""" game = games.read_from_file("poker.efg") @@ -74,6 +78,8 @@ def test_lcp_behavior_double(): # For floating-point results are not exact, so we skip testing exact values for now +@pytest.mark.nash +@pytest.mark.nash_lcp_behavior def test_lcp_behavior_rational(): """Test calls of LCP for mixed behavior equilibria, rational precision.""" game = games.read_from_file("poker.efg") @@ -94,6 +100,8 @@ def test_lp_strategy_double(): # For floating-point results are not exact, so we skip testing exact values for now +@pytest.mark.nash +@pytest.mark.nash_lp_strategy def test_lp_strategy_rational(): """Test calls of LP for mixed strategy equilibria, rational precision.""" game = games.read_from_file("poker.efg") @@ -115,6 +123,9 @@ def test_lp_behavior_double(): # For floating-point results are not exact, so we skip testing exact values for now +@pytest.mark.nash +@pytest.mark.slow +@pytest.mark.nash_lp_behavior def test_lp_behavior_rational(): """Test calls of LP for mixed behavior equilibria, rational precision.""" game = games.read_from_file("poker.efg") diff --git a/tests/test_node.py b/tests/test_node.py index a35290756..7a45b6ef7 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -1,3 +1,5 @@ +import typing + import pytest import pygambit as gbt @@ -171,6 +173,54 @@ def test_node_copy_across_games(): game1.copy_tree(game2.root, game1.root) +def _subtrees_equal( + n1: gbt.Node, + n2: gbt.Node, + recursion_stop_node: typing.Union[gbt.Node, None] = None +) -> bool: + if n1 == recursion_stop_node: + return n2.is_terminal + if n1.is_terminal and n2.is_terminal: + return n1.outcome == n2.outcome + if n1.is_terminal is not n2.is_terminal: + return False + # now, both n1 and n2 are non-terminal + # check that they are in the same infosets + if n1.infoset != n2.infoset: + return False + # check that they have the same number of children + if len(n1.children) != len(n2.children): + return False + + return all( + _subtrees_equal(c1, c2, recursion_stop_node) for (c1, c2) in zip(n1.children, n2.children) + ) + + +def test_copy_tree_onto_nondescendent_terminal_node(): + """Test copying a subtree to a non-descendent node.""" + g = games.read_from_file("e01.efg") + list_nodes = list(g.nodes) + src_node = list_nodes[3] # path=[1, 0] + dest_node = list_nodes[2] # path=[0, 0] + + g.copy_tree(src_node, dest_node) + + assert _subtrees_equal(src_node, dest_node) + + +def test_copy_tree_onto_descendent_terminal_node(): + """Test copying a subtree to a node that's a descendent of the original.""" + g = games.read_from_file("e01.efg") + list_nodes = list(g.nodes) + src_node = list_nodes[1] # path=[0] + dest_node = list_nodes[4] # path=[0, 1, 0] + + g.copy_tree(src_node, dest_node) + + assert _subtrees_equal(src_node, dest_node, dest_node) + + def test_node_move_nonterminal(): """Test on moving to a nonterminal node.""" game = games.read_from_file("basic_extensive_game.efg") @@ -356,3 +406,414 @@ def test_append_infoset_node_list_is_empty(): game.append_move(game.root.children[0].children[0], "Player 3", ["B", "F"]) with pytest.raises(ValueError): game.append_infoset([], game.root.children[0].children[0].infoset) + + +def _get_members(action: gbt.Action) -> set[gbt.Node]: + """Calculates the set of nodes resulting from taking a specific action + at all nodes within its information set. + """ + infoset = action.infoset + action_index = action.number + + return [member_node.children[action_index] for member_node in infoset.members] + + +def _count_subtree_nodes(start_node: gbt.Node, count_terminal: bool) -> int: + """Counts nodes in the subtree rooted at `start_node` (including `start_node`). + + Parameters + ---------- + start_node: Node + The root of the subtree + count_terminal: bool + Include or exclude terminal nodes from count + """ + count = 1 if count_terminal or not start_node.is_terminal else 0 + + for child in start_node.children: + count += _count_subtree_nodes(child, count_terminal) + return count + + +def test_len_matches_expected_node_count(): + """Verify `len(game.nodes)` matches expected node count + """ + game = games.read_from_file("e01.efg") + expected_node_count = 9 + + direct_len = len(game.nodes) + assert direct_len == expected_node_count + + assert direct_len == _count_subtree_nodes(game.root, True) + + +def test_len_after_delete_tree(): + """Verify `len(game.nodes)` is correct after `delete_tree`. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nodes = len(game.nodes) + list_nodes = list(game.nodes) + + root_of_the_deleted_subtree = list_nodes[3] + number_of_deleted_nodes = _count_subtree_nodes(root_of_the_deleted_subtree, True) - 1 + + game.delete_tree(root_of_the_deleted_subtree) + + assert len(game.nodes) == initial_number_of_nodes - number_of_deleted_nodes + + +def test_len_after_delete_parent(): + """Verify `len(game.nodes)` is correct after `delete_parent`. + """ + game = games.read_from_file("e02.efg") + initial_number_of_nodes = len(game.nodes) + list_nodes = list(game.nodes) + + node_parent_to_delete = list_nodes[4] + + number_of_node_ancestors = _count_subtree_nodes(node_parent_to_delete, True) + number_of_parent_ancestors = _count_subtree_nodes(node_parent_to_delete.parent, True) + diff = number_of_parent_ancestors - number_of_node_ancestors + + game.delete_parent(node_parent_to_delete) + + assert len(game.nodes) == initial_number_of_nodes - diff + + +def test_len_after_append_move(): + """Verify `len(game.nodes)` is correct after `append_move`. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nodes = len(game.nodes) + list_nodes = list(game.nodes) + + terminal_node = list_nodes[5] # path=[1, 1, 0] + player = game.players[0] + actions_to_add = ["T", "M", "B"] + + game.append_move(terminal_node, player, actions_to_add) + + assert len(game.nodes) == initial_number_of_nodes + len(actions_to_add) + + +def test_len_after_append_infoset(): + """Verify `len(game.nodes)` is correct after `append_infoset`. + """ + game = games.read_from_file("e02.efg") + initial_number_of_nodes = len(game.nodes) + list_nodes = list(game.nodes) + + member_node = list_nodes[2] # path=[1] + infoset_to_modify = member_node.infoset + number_of_infoset_actions = len(infoset_to_modify.actions) + terminal_node_to_add = list_nodes[6] # path=[1, 1, 1] + + game.append_infoset(terminal_node_to_add, infoset_to_modify) + + assert len(game.nodes) == initial_number_of_nodes + number_of_infoset_actions + + +def test_len_after_add_action(): + """Verify `len(game.nodes)` is correct after `add_action`. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nodes = len(game.nodes) + + infoset_to_modify = game.infosets[1] + + num_nodes_in_infoset = len(infoset_to_modify.members) + + game.add_action(infoset_to_modify) + + assert len(game.nodes) == initial_number_of_nodes + num_nodes_in_infoset + + +def test_len_after_delete_action(): + """Verify `len(game.nodes)` is correct after `delete_action`. + """ + game = games.read_from_file("e02.efg") + initial_number_of_nodes = len(game.nodes) + + action_to_delete = game.infosets[0].actions[1] + + # Calculate the total number of nodes within all subtrees + # that begin immediately after taking the specified action. + nodes_to_delete = 0 + action_nodes = _get_members(action_to_delete) + + for subtree_root in action_nodes: + nodes_to_delete += _count_subtree_nodes(subtree_root, True) + + game.delete_action(action_to_delete) + + assert len(game.nodes) == initial_number_of_nodes - nodes_to_delete + + +def test_len_after_insert_move(): + """Verify `len(game.nodes)` is correct after `insert_move`. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nodes = len(game.nodes) + list_nodes = list(game.nodes) + + node_to_insert_above = list_nodes[3] + + player = game.players[1] + num_actions_to_add = 3 + + game.insert_move(node_to_insert_above, player, num_actions_to_add) + + assert len(game.nodes) == initial_number_of_nodes + num_actions_to_add + + +def test_len_after_insert_infoset(): + """Verify `len(game.nodes)` is correct after `insert_infoset`. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nodes = len(game.nodes) + list_nodes = list(game.nodes) + + member_node = list_nodes[6] # path=[1] + infoset_to_modify = member_node.infoset + node_to_insert_above = list_nodes[7] # path=[0, 1] + number_of_infoset_actions = len(infoset_to_modify.actions) + + game.insert_infoset(node_to_insert_above, infoset_to_modify) + + assert len(game.nodes) == initial_number_of_nodes + number_of_infoset_actions + + +def test_len_after_copy_tree(): + """Verify `len(game.nodes)` is correct after `copy_tree`. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nodes = len(game.nodes) + list_nodes = list(game.nodes) + src_node = list_nodes[3] # path=[1, 0] + dest_node = list_nodes[2] # path=[0, 0] + number_of_src_ancestors = _count_subtree_nodes(src_node, True) + + game.copy_tree(src_node, dest_node) + + assert len(game.nodes) == initial_number_of_nodes + number_of_src_ancestors - 1 + + +def test_nonterminal_len_matches_expected_count(): + """Verify `len(game._nonterminal_nodes)` matches expected count + """ + game = games.read_from_file("e01.efg") + expected_nonterminal_node_count = 4 + + direct_nonterminal_len = len(game._nonterminal_nodes) + assert direct_nonterminal_len == expected_nonterminal_node_count + + +def test_nonterminal_len_after_delete_tree(): + """Verify `len(game._nonterminal_nodes)` is correct after `delete_tree`. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nonterminal_nodes = len(game._nonterminal_nodes) + list_nodes = list(game.nodes) + + root_of_the_deleted_subtree = list_nodes[1] + number_of_deleted_nonterminal_nodes = _count_subtree_nodes(root_of_the_deleted_subtree, False) + + game.delete_tree(root_of_the_deleted_subtree) + + assert len(game._nonterminal_nodes) == initial_number_of_nonterminal_nodes \ + - number_of_deleted_nonterminal_nodes + + +def test_nonterminal_len_after_delete_parent_of_nonterminal_node(): + """Verify `len(game._nonterminal_nodes)` is correct after `delete_parent`. + """ + game = games.read_from_file("e02.efg") + list_nodes = list(game.nodes) + node_parent_to_delete = list_nodes[4] # path=[1, 1] + + initial_number_of_nonterminal_nodes = len(game._nonterminal_nodes) + diff = _count_subtree_nodes(node_parent_to_delete.parent, False) \ + - _count_subtree_nodes(node_parent_to_delete, False) + + game.delete_parent(node_parent_to_delete) + + assert len(game._nonterminal_nodes) == initial_number_of_nonterminal_nodes - diff + + +def test_nonterminal_len_after_delete_parent_of_terminal_node(): + """Verify `len(game._nonterminal_nodes)` is correct after `delete_parent`. + """ + game = games.read_from_file("e02.efg") + list_nodes = list(game.nodes) + node_parent_to_delete = list_nodes[5] # path=[0, 1, 1] + + initial_number_of_nonterminal_nodes = len(game._nonterminal_nodes) + diff = _count_subtree_nodes(node_parent_to_delete.parent, False) \ + - _count_subtree_nodes(node_parent_to_delete, False) + + game.delete_parent(node_parent_to_delete) + + assert len(game._nonterminal_nodes) == initial_number_of_nonterminal_nodes - diff + + +def test_nonterminal_len_after_append_move(): + """Verify `len(game._nonterminal_nodes)` is correct after `append_move`. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nonterminal_nodes = len(game._nonterminal_nodes) + list_nodes = list(game.nodes) + + terminal_node = list_nodes[5] # path=[1, 1, 0] + player = game.players[0] + actions_to_add = ["T", "M", "B"] + + game.append_move(terminal_node, player, actions_to_add) + + assert len(game._nonterminal_nodes) == initial_number_of_nonterminal_nodes \ + + _count_subtree_nodes(terminal_node, False) + + +def test_nonterminal_len_after_append_infoset(): + """Verify `len(game._nonterminal_nodes)` is correct after `append_infoset`. + """ + game = games.read_from_file("e02.efg") + initial_number_of_nonterminal_nodes = len(game._nonterminal_nodes) + list_nodes = list(game.nodes) + + member_node = list_nodes[2] # path=[1] + infoset_to_modify = member_node.infoset + terminal_node_to_add = list_nodes[6] # path=[1, 1, 1] + + game.append_infoset(terminal_node_to_add, infoset_to_modify) + + assert len(game._nonterminal_nodes) == initial_number_of_nonterminal_nodes \ + + _count_subtree_nodes(terminal_node_to_add, False) + + +def test_nonterminal_len_after_add_action(): + """Verify `len(game._nonterminal_nodes)` does not change after `add_action` to an infoset. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nonterminal_nodes = len(game._nonterminal_nodes) + + infoset_to_modify = game.infosets[1] + + game.add_action(infoset_to_modify) + + assert len(game._nonterminal_nodes) == initial_number_of_nonterminal_nodes + + +def test_nonterminal_len_after_delete_action(): + """Verify `len(game._nonterminal_nodes)` is correct after `delete_action`. + """ + game = games.read_from_file("e02.efg") + initial_number_of_nonterminal_nodes = len(game._nonterminal_nodes) + + action_to_delete = game.infosets[0].actions[1] + + # Calculate the total number of nodes within all subtrees + # that begin immediately after taking the specified action. + nonterminal_nodes_to_delete = 0 + action_nodes = _get_members(action_to_delete) + + for subtree_root in action_nodes: + nonterminal_nodes_to_delete += _count_subtree_nodes(subtree_root, False) + + game.delete_action(action_to_delete) + + assert len(game._nonterminal_nodes) == initial_number_of_nonterminal_nodes \ + - nonterminal_nodes_to_delete + + +def test_nonterminal_len_after_insert_move(): + """Verify `len(game._nonterminal_nodes)` correctly increaces by 1 after `insert_move`. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nonterminal_nodes = len(game._nonterminal_nodes) + list_nodes = list(game.nodes) + + node_to_insert_above = list_nodes[3] + + player = game.players[1] + num_actions_to_add = 3 + + game.insert_move(node_to_insert_above, player, num_actions_to_add) + + assert len(game._nonterminal_nodes) == initial_number_of_nonterminal_nodes + 1 + + +def test_nonterminal_len_after_insert_infoset(): + """Verify `len(game._nonterminal_nodes)` correctly increaces by 1 after `insert_infoset`. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nonterminal_nodes = len(game._nonterminal_nodes) + list_nodes = list(game.nodes) + + member_node = list_nodes[6] # path=[1] + infoset_to_modify = member_node.infoset + node_to_insert_above = list_nodes[7] # path=[0, 1] + + game.insert_infoset(node_to_insert_above, infoset_to_modify) + + assert len(game._nonterminal_nodes) == initial_number_of_nonterminal_nodes + 1 + + +def test_nonterminal_len_after_copy_tree(): + """Verify `len(game._nonterminal_nodes)` is correct after `copy_tree`. + """ + game = games.read_from_file("e01.efg") + initial_number_of_nodes = len(game._nonterminal_nodes) + list_nodes = list(game.nodes) + src_node = list_nodes[3] # path=[1, 0] + dest_node = list_nodes[2] # path=[0, 0] + number_of_nonterminal_src_ancestors = _count_subtree_nodes(src_node, False) + + game.copy_tree(src_node, dest_node) + + assert len(game._nonterminal_nodes) == initial_number_of_nodes \ + + number_of_nonterminal_src_ancestors + + +def test_node_plays(): + """Verify `node.plays` returns plays reachable from a given node. + """ + game = games.read_from_file("e02.efg") + list_nodes = list(game.nodes) + + test_node = list_nodes[2] # path=[1] + + expected_set_of_plays = { + list_nodes[3], list_nodes[5], list_nodes[6] + } # paths=[0, 1], [0, 1, 1], [1, 1, 1] + + assert set(test_node.plays) == expected_set_of_plays + + +def test_nodes_iteration_matches_python_dfs(): + """ + Verify that game.nodes using the C++ iterator produces the same DFS traversal + as a pure Python recursive generator. + """ + game = games.read_from_file("e01.efg") + game2 = games.read_from_file("basic_extensive_game.efg") + + def dfs(node: gbt.Node) -> typing.Iterator[gbt.Node]: + yield node + for child in node.children: + yield from dfs(child) + + assert list(game.nodes) == list(dfs(game.root)) + assert list(game2.nodes) == list(dfs(game2.root)) + + +def test_nodes_iteration_single_node_game(): + """ + Test that iteration works correctly on a game with only a root node. + """ + game = gbt.Game.new_tree() + assert game.root.is_terminal + + nodes = list(game.nodes) + + assert len(nodes) == 1 + assert nodes[0] == game.root diff --git a/tests/test_players.py b/tests/test_players.py index 4597a5226..7743a2acf 100644 --- a/tests/test_players.py +++ b/tests/test_players.py @@ -39,8 +39,14 @@ def test_player_index_by_string(): def test_player_index_out_of_range(): game = gbt.Game.new_table([2, 2]) + print(f"Number of players: {len(game.players)}") + assert len(game.players) == 2 + with pytest.raises(IndexError): + _ = game.players[2] with pytest.raises(IndexError): _ = game.players[3] + with pytest.raises(IndexError): + _ = game.players[-1] def test_player_index_invalid(): diff --git a/tests/test_strategic.py b/tests/test_strategic.py index c8ab47d09..5b453ce78 100644 --- a/tests/test_strategic.py +++ b/tests/test_strategic.py @@ -25,7 +25,7 @@ def test_strategic_game_root(): def test_strategic_game_nodes(): game = gbt.Game.new_table([2, 2]) - assert game.nodes() == [] + assert list(game.nodes) == [] def test_game_behav_profile_error():